From 7b5f66be11c5f3af3daa1ce5da9aa87dbd36cf58 Mon Sep 17 00:00:00 2001 From: NetterSilver <133535268+Silvertree2010@users.noreply.github.com> Date: Mon, 19 Oct 2026 20:13:40 +0200 Subject: [PATCH] Don't paint from the sampled point when Alt is released mid-press Fixes #7802 Co-Authored-By: Claude Haiku 5.5 Co-Authored-By: Claude Opus 5.5 --- crates/ui-egui/src/canvas.rs | 57 +++++++++++++++++++++++++++++++++++- 1 file changed, 56 insertions(+), 1 deletion(-) diff --git a/crates/ui-egui/src/canvas.rs b/crates/ui-egui/src/canvas.rs index 053716a572..c91d7e0230 100644 --- a/crates/ui-egui/src/canvas.rs +++ b/crates/ui-egui/src/canvas.rs @@ -3714,7 +3714,9 @@ pub fn canvas_view(app: &mut PhotocraftApp, ui: &mut egui::Ui, idx: usize, rect: app.press_stroke = app.drag.is_some(); } let press_stroke = app.press_stroke; - if press_stroke { + // A live tool's press that went to the ⌥ eyedropper keeps sampling for the whole press: the + // drag recognised later must not decide again with ⌥ already let go and paint (#7802). + if press_stroke || (strokes_live(tool) && app.alt_sampling) { buttons.started = false; } if buttons.started @@ -8062,6 +8064,59 @@ mod tests { assert_ne!(dragged.center, center0, "a secondary window's Hand pans while transforming"); } + #[test] + fn letting_go_of_alt_during_an_alt_sample_press_does_not_paint() { + // #7802: Alt-pressing with the Brush samples a colour on the press. If Alt was let go + // before egui recognised the drag, that drag started a stroke from the sampled point. + let ctx = egui::Context::default(); + let mut app = PhotocraftApp::new(photocraft_engine::Session::new(), Default::default()); + app.run("file.new", json!({"width": 400, "height": 300, "background": "white"})).unwrap(); + app.run("layer.new.layer", json!({})).unwrap(); + app.run("view.actualPixels", json!({})).unwrap(); + app.ui.tool = Tool::Brush; + app.sync_views(); + let mut view = app.ui.views[0].clone(); + view.zoom = 1.0; + view.center = [200.0, 150.0]; + view.fit_pending = false; + view.fill_pending = false; + let rect = Rect::from_min_size(Pos2::ZERO, vec2(400.0, 300.0)); + let frame = |app: &mut PhotocraftApp, events: Vec| { + let input = egui::RawInput { screen_rect: Some(rect), events, ..Default::default() }; + let mut out = ctx.run_ui(input, |ui| { + canvas_view(app, ui, 0, rect, view.clone(), true); + }); + out.textures_delta.clear(); + }; + let alt = egui::Modifiers::ALT; + let none = egui::Modifiers::NONE; + let start = Pos2::new(200.0, 150.0); + let press = + |pos: Pos2, pressed: bool, modifiers: egui::Modifiers| egui::Event::PointerButton { pos, button: egui::PointerButton::Primary, pressed, modifiers }; + // Alt released mid-press, and Alt held throughout: both only sample. + for release_alt in [true, false] { + app.run("tools.setColors", json!({"foreground": [0.0, 0.0, 1.0, 1.0]})).unwrap(); + let rev = app.session.active().unwrap().revision; + frame(&mut app, vec![egui::Event::ModifiersChanged(alt), egui::Event::PointerMoved(start)]); + frame(&mut app, vec![press(start, true, alt)]); + assert_eq!(app.session.tools.foreground, [1.0, 1.0, 1.0, 1.0], "the press sampled the white canvas"); + let later = if release_alt { none } else { alt }; + let end = start + vec2(20.0, 0.0); + frame(&mut app, vec![egui::Event::ModifiersChanged(later), egui::Event::PointerMoved(end)]); + frame(&mut app, vec![press(end, false, later)]); + frame(&mut app, vec![egui::Event::ModifiersChanged(none)]); + assert_eq!(app.session.active().unwrap().revision, rev, "release_alt={release_alt}: an Alt-sample press must not paint"); + assert!(app.drag.is_none() && !app.alt_sampling, "release_alt={release_alt}"); + } + // Without Alt the same gesture paints. + let rev = app.session.active().unwrap().revision; + frame(&mut app, vec![egui::Event::PointerMoved(start)]); + frame(&mut app, vec![press(start, true, none)]); + frame(&mut app, vec![egui::Event::PointerMoved(start + vec2(20.0, 0.0))]); + frame(&mut app, vec![press(start + vec2(20.0, 0.0), false, none)]); + assert!(app.session.active().unwrap().revision > rev, "a plain drag paints"); + } + #[test] fn guides_draw_over_the_transform_preview_and_under_its_box() { // #5086: a guide crossing a layer under Free Transform was hidden behind the preview of