Skip to content

Comments

Add Global Eyedropper for Windows #3801

Open
yush-1018 wants to merge 1 commit intoGraphiteEditor:masterfrom
yush-1018:feat/global-eyedropper
Open

Add Global Eyedropper for Windows #3801
yush-1018 wants to merge 1 commit intoGraphiteEditor:masterfrom
yush-1018:feat/global-eyedropper

Conversation

@yush-1018
Copy link

Added a global eyedropper tool for the desktop app on Windows.

Changes include:

  • A floating magnifier window that follows the cursor.
  • Native Windows GDI integration to sample colors from any application.
  • Seamless color updates in the Graphite editor.

@gemini-code-assist
Copy link

Summary of Changes

Hello @yush-1018, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request introduces a significant new feature for Windows users: a global eyedropper tool. This tool enhances the color selection capabilities by allowing users to pick colors from anywhere on their screen, not just within the application. It provides a visual magnifier for precision and integrates the sampled colors directly into the editor's color palette, streamlining the design workflow.

Highlights

  • Global Eyedropper Tool for Windows: A new global eyedropper tool has been implemented specifically for the Windows desktop application, allowing users to sample colors from any application on their screen.
  • Magnifier Window and GDI Integration: The eyedropper features a floating magnifier window that follows the cursor, and it utilizes native Windows GDI (Graphics Device Interface) calls to accurately sample colors from the desktop.
  • Seamless Color Updates: Sampled colors are seamlessly integrated and updated within the Graphite editor, allowing for immediate use of the picked color.
  • Application Event Handling: The main application event loop now manages the lifecycle of the global eyedropper, including starting, stopping, updating its position, rendering its magnifier, and processing sampled colors.
  • Frontend-Backend Communication: New messages have been introduced to facilitate communication between the web frontend and the desktop backend, enabling the activation/deactivation of the global eyedropper and the transfer of sampled color data.
Changelog
  • desktop/Cargo.toml
    • Added graphene-std as a workspace dependency to support color types.
  • desktop/src/app.rs
    • Introduced a global_eyedropper field to the App struct.
    • Initialized the GlobalEyedropper instance upon application startup.
    • Implemented handling for DesktopFrontendMessage::GlobalEyedropper to start or stop the eyedropper.
    • Added logic to process AppEvent::StartGlobalEyedropper to activate the tool.
    • Integrated window_event and RedrawRequested handlers to manage the eyedropper's magnifier window interactions and rendering.
  • desktop/src/event.rs
    • Added a new StartGlobalEyedropper event to trigger the eyedropper functionality.
  • desktop/src/global_eyedropper.rs
    • Created a new module defining the GlobalEyedropper struct.
    • Implemented methods for new, start, stop, is_active, and window_id to control the eyedropper's state and window.
    • Added update method to position and show the magnifier window based on cursor movement.
    • Developed render method to draw the magnified pixel grid and highlight the central pixel using Windows GDI.
    • Implemented sample_color method to retrieve the color under the cursor using Windows GDI's GetPixel function.
    • Included is_primary method to determine if the eyedropper is sampling for the primary or secondary color.
  • desktop/src/lib.rs
    • Declared the new global_eyedropper module.
  • desktop/wrapper/src/handle_desktop_wrapper_message.rs
    • Added handling for DesktopWrapperMessage::PickGlobalColor to update the editor's working color and signal the end of color sampling.
  • desktop/wrapper/src/intercept_frontend_message.rs
    • Implemented interception for FrontendMessage::GlobalEyedropper to forward the request to the desktop application.
  • desktop/wrapper/src/messages.rs
    • Extended DesktopFrontendMessage with GlobalEyedropper to signal the desktop app to open or close the eyedropper.
    • Added PickGlobalColor to DesktopWrapperMessage to send the sampled color back to the editor.
  • editor/src/messages/frontend/frontend_message.rs
    • Added GlobalEyedropper message to communicate eyedropper state (open/close, primary/secondary) to the desktop application.
  • editor/src/messages/tool/tool_messages/eyedropper_tool.rs
    • Modified the eyedropper tool's FSM to send FrontendMessage::GlobalEyedropper when the pointer moves outside the canvas, activating the global eyedropper.
    • Ensured the global eyedropper is closed when sampling ends or is aborted.
Activity
  • No specific activity (comments, reviews, progress updates) has been recorded for this pull request yet.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

The addition of a global eyedropper for Windows is a great feature. However, the current implementation has several critical issues regarding performance and correctness. The most significant issue is the redundant creation and destruction of the magnifier window on every mouse move when the cursor is outside the editor viewport, which will cause severe performance degradation and resource churn. Additionally, the GDI drawing logic incorrectly uses color values as brush handles, which will prevent the magnifier from displaying the correct colors. There are also concerns about the magnifier sampling itself and the efficiency of using GetPixel in a loop.

Comment on lines +407 to +413
DesktopFrontendMessage::GlobalEyedropper { open, primary } => {
if open {
self.app_event_scheduler.schedule(AppEvent::StartGlobalEyedropper { primary });
} else {
self.global_eyedropper.stop();
}
}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

critical

The global eyedropper window is being recreated on every PointerMove event when the cursor is outside the viewport because eyedropper_tool.rs spams this message. This causes significant performance overhead and resource churn. You should check if the eyedropper is already active before scheduling a start event.

			DesktopFrontendMessage::GlobalEyedropper { open, primary } => {
				if open {
					if !self.global_eyedropper.is_active() {
						self.app_event_scheduler.schedule(AppEvent::StartGlobalEyedropper { primary });
					}
				} else {
					self.global_eyedropper.stop();
				}
			}

right: ((x + 1) * pixel_size) as i32,
bottom: ((y + 1) * pixel_size) as i32,
};
windows::Win32::Graphics::Gdi::FillRect(window_dc, &rect, windows::Win32::Graphics::Gdi::HBRUSH((color.0 + 1) as isize));

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

critical

Using HBRUSH((color.0 + 1) as isize) is incorrect for arbitrary colors. In GDI, this syntax only works for system color indices (e.g., COLOR_WINDOW + 1). For arbitrary RGB colors, you must create a solid brush using CreateSolidBrush and ensure it is deleted afterwards to avoid GDI object leaks.

					let brush = windows::Win32::Graphics::Gdi::CreateSolidBrush(color);
					windows::Win32::Graphics::Gdi::FillRect(window_dc, &rect, brush);
					let _ = windows::Win32::Graphics::Gdi::DeleteObject(brush);

for x in 0..res {
let sx = pt.x - (res as i32 / 2) + x as i32;
let sy = pt.y - (res as i32 / 2) + y as i32;
let color = GetPixel(desktop_dc, sx, sy);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

Calling GetPixel 121 times per frame is very inefficient. Additionally, since the magnifier window is moved to the cursor position before rendering, GetPixel on the desktop DC will likely sample the magnifier window itself rather than the content beneath it. Consider capturing the screen area into a memory DC before moving/showing the magnifier, or using StretchBlt which is significantly faster.

right: ((mid + 1) * pixel_size) as i32,
bottom: ((mid + 1) * pixel_size) as i32,
};
windows::Win32::Graphics::Gdi::FrameRect(window_dc, &rect, windows::Win32::Graphics::Gdi::HBRUSH(1));

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

HBRUSH(1) corresponds to COLOR_SCROLLBAR + 1. If you intended to draw a black or neutral frame, it's better to use a stock object like BLACK_BRUSH.

			let brush = windows::Win32::Graphics::Gdi::GetStockObject(windows::Win32::Graphics::Gdi::BLACK_BRUSH);
			windows::Win32::Graphics::Gdi::FrameRect(window_dc, &rect, windows::Win32::Graphics::Gdi::HBRUSH(brush.0));

let g = ((pixel.0 >> 8) & 0xFF) as f32 / 255.0;
let b = ((pixel.0 >> 16) & 0xFF) as f32 / 255.0;

Some(Color::from_rgbaf32(r, g, b, 1.0).unwrap())

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Since the RGB values are derived from u8 division, they are guaranteed to be within the valid range [0, 1]. You can use from_rgbaf32_unchecked to avoid the overhead of checks and the unwrap() call.

			Some(Color::from_rgbaf32_unchecked(r, g, b, 1.0))

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant