Windows: selection and other crosshair tools stay visible over grey (#737) (#954)

Windows' crosshair cursor inverts the pixels under it, so it vanishes over
mid-grey (the pasteboard, many photos). On Windows the canvas now draws a
black-and-white crosshair itself and hides the system one.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Brandon Thomas 2026-10-08 04:01:33 -04:00 committed by GitHub
parent d64f809e4d
commit 3d003bd9d0
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
1 changed files with 29 additions and 0 deletions

View File

@ -213,6 +213,23 @@ pub(crate) fn stroke_command(tool: Tool) -> &'static str {
if tool == Tool::Pencil { "paint.pencil" } else { "paint.stroke" }
}
/// Windows' crosshair cursor inverts the pixels under it, so over mid-grey (the pasteboard, many
/// photos) it vanishes (#737). There the canvas draws a black-and-white crosshair itself, like
/// Photoshop's, and hides the system one.
fn visible_crosshair(icon: egui::CursorIcon, painter: &egui::Painter, p: Pos2, draw: bool) -> egui::CursorIcon {
if !draw || icon != egui::CursorIcon::Crosshair {
return icon;
}
let p = pos2(p.x.round() + 0.5, p.y.round() + 0.5);
let (gap, len) = (2.0, 8.0);
for (w, c) in [(3.0, Color32::from_black_alpha(160)), (1.0, Color32::from_white_alpha(235))] {
for d in [vec2(1.0, 0.0), vec2(-1.0, 0.0), vec2(0.0, 1.0), vec2(0.0, -1.0)] {
painter.line_segment([p + d * gap, p + d * len], Stroke::new(w, c));
}
}
egui::CursorIcon::None
}
/// The Pencil's cursor at `doc` (document pixels): the whole-pixel square its dab fills
/// (`paint::grid_square`), in screen points with its edges on physical pixels (`ppp` = pixels
/// per point), so it lines up with the pixel grid at any zoom.
@ -2060,6 +2077,7 @@ pub fn canvas_view(app: &mut PhotocraftApp, ui: &mut egui::Ui, idx: usize, rect:
Tool::MagneticLasso => crate::magnetic_lasso_ui::cursor(app, &painter, p, view.zoom),
_ => egui::CursorIcon::Crosshair,
};
let icon = visible_crosshair(icon, &painter, p, cfg!(target_os = "windows"));
ui.ctx().set_cursor_icon(icon);
// Selection tools: + / − / × badge for the effective mode (#170). A gesture keeps the
// mode it started with (⇧ then constrains the marquee instead of adding).
@ -2892,6 +2910,17 @@ fn hex(c: [f32; 4]) -> String {
#[cfg(test)]
mod tests {
#[test]
fn windows_draws_its_own_crosshair() {
// #737: Windows' inverting crosshair vanishes over mid-grey; the canvas draws one instead.
let ctx = egui::Context::default();
let painter = egui::Painter::new(ctx, egui::LayerId::background(), egui::Rect::EVERYTHING);
let p = egui::pos2(10.0, 10.0);
assert_eq!(super::visible_crosshair(egui::CursorIcon::Crosshair, &painter, p, true), egui::CursorIcon::None);
assert_eq!(super::visible_crosshair(egui::CursorIcon::Crosshair, &painter, p, false), egui::CursorIcon::Crosshair);
assert_eq!(super::visible_crosshair(egui::CursorIcon::Move, &painter, p, true), egui::CursorIcon::Move);
}
use super::*;
/// #569: displays 1 (sRGB) and 4 (Display P3) side by side, and a document filled with an