Don't paint from the sampled point when Alt is released mid-press

Fixes #7802

Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
NetterSilver
2026-10-19 20:13:40 +02:00
co-authored by Claude Haiku 5.5
parent 2ef8a9d3f5
commit 7b5f66be11
+56 -1
View File
@@ -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<egui::Event>| {
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