mirror of
https://github.com/storytold/lightcraft.git
synced 2026-10-24 10:09:01 -06:00
fix(guided): the press position survives press+release in one frame
egui clears `press_origin` on the release, so a slow frame that swallows press, move and release into one batch reports `primary_pressed` with no origin: the gesture fell back to the release position and a real drag wrote nothing (its length check saw a zero-length stub). The gate now reads the primary press back from the frame's own events before that fallback — the press decides what it means, wherever the batch ended.
This commit is contained in:
@@ -1883,9 +1883,21 @@ fn guided_overlay(
|
||||
// a menu can receive the press and the pointer can leave it within the same frame, so the
|
||||
// press itself must belong to the loupe's layer. Anything floating above the photo — a
|
||||
// context menu, a dropdown, a dialog — owns its own press; the guide under it stays put.
|
||||
// A slow frame can also swallow the whole press-move-release into one batch, and egui
|
||||
// clears `press_origin` on the release: then the press position is read back from the
|
||||
// frame's own events, so the gesture still starts where the pointer went down.
|
||||
let nav = app.widgets.iter().rev().find(|(w, _)| w == "canvas:navigator").map(|(_, r)| *r);
|
||||
if ui.input(|i| i.pointer.primary_pressed())
|
||||
&& let Some(q) = ui.input(|i| i.pointer.press_origin()).or(resp.interact_pointer_pos())
|
||||
&& let Some(q) = ui
|
||||
.input(|i| {
|
||||
i.pointer.press_origin().or_else(|| {
|
||||
i.events.iter().rev().find_map(|e| match e {
|
||||
egui::Event::PointerButton { pos, button: egui::PointerButton::Primary, pressed: true, .. } => Some(*pos),
|
||||
_ => None,
|
||||
})
|
||||
})
|
||||
})
|
||||
.or(resp.interact_pointer_pos())
|
||||
&& resp.contains_pointer()
|
||||
&& ui.ctx().layer_id_at(q) == Some(resp.layer_id)
|
||||
&& resp.rect.contains(q)
|
||||
|
||||
@@ -207,6 +207,31 @@ fn a_new_guide_starts_at_the_press_point() {
|
||||
assert!(rect.right_top().distance(p2) < 2.0, "and ends at the release point: {rect:?} vs {p2:?}");
|
||||
}
|
||||
|
||||
/// A slow frame can swallow press, move and release into a single batch: egui then reports
|
||||
/// `primary_pressed` with `press_origin` already cleared by the release, and the gesture must
|
||||
/// still start at the press point — the guide spans the drag, not a zero-length stub at the
|
||||
/// release. The control channel spreads pointer events one per frame, so the batch is pushed
|
||||
/// raw into one frame here.
|
||||
#[test]
|
||||
fn a_drag_whose_press_and_release_share_one_frame_still_draws() {
|
||||
let mut h = guided();
|
||||
assert_eq!(guides_len(&h), 2);
|
||||
let undo = h.app.session.undo.len();
|
||||
let img = h.app.image_rect.expect("the photo on screen");
|
||||
let p1 = egui::pos2(img.left() + 0.35 * img.width(), img.top() + 0.55 * img.height());
|
||||
let p2 = egui::pos2(img.left() + 0.6 * img.width(), img.top() + 0.55 * img.height());
|
||||
h.events.push(egui::Event::PointerMoved(p1));
|
||||
h.events.push(egui::Event::PointerButton { pos: p1, button: egui::PointerButton::Primary, pressed: true, modifiers: egui::Modifiers::NONE });
|
||||
h.events.push(egui::Event::PointerMoved(p2));
|
||||
h.events.push(egui::Event::PointerButton { pos: p2, button: egui::PointerButton::Primary, pressed: false, modifiers: egui::Modifiers::NONE });
|
||||
h.settle(SETTLE);
|
||||
assert_eq!(guides_len(&h), 3, "the drag that shared one frame still draws its guide");
|
||||
assert_eq!(h.app.session.undo.len(), undo + 1, "one edit was written");
|
||||
let rect = guide_rect(&h, 2);
|
||||
assert!(rect.left_top().distance(p1) < 2.0, "the guide starts at the press point: {rect:?} vs {p1:?}");
|
||||
assert!(rect.right_top().distance(p2) < 2.0, "and ends at the release point: {rect:?} vs {p2:?}");
|
||||
}
|
||||
|
||||
/// The right button is the pan / context menu's, never the guides': on a guide it selects
|
||||
/// nothing and draws nothing.
|
||||
#[test]
|
||||
|
||||
Reference in New Issue
Block a user