From 6cb7b341d6d57f5f4af1659e264b94e41bbbb9c4 Mon Sep 17 00:00:00 2001 From: dotCooCoo Date: Thu, 29 Oct 2026 05:18:59 -0700 Subject: [PATCH] Shape Builder: Escape cancels a gesture made while the map is built A drag started while the map of a large selection was being built was recorded for replay. Escape ended the drag but kept the recorded gesture, so the release was ignored, the tool kept waiting, and the next drag's events were added to the canceled one. Escape now drops it. --- crates/tools/src/builder.rs | 2 ++ crates/tools/src/builder/tests.rs | 30 ++++++++++++++++++++++++++++++ 2 files changed, 32 insertions(+) diff --git a/crates/tools/src/builder.rs b/crates/tools/src/builder.rs index 4203a6b7c..dd7b1de2a 100644 --- a/crates/tools/src/builder.rs +++ b/crates/tools/src/builder.rs @@ -718,6 +718,8 @@ impl Tool for ShapeBuilderTool { self.dragging = false; self.points.clear(); self.touched = Touched::default(); + // A gesture recorded while the map is being built is canceled with it. + self.deferred = None; (self.marquee, self.hover, self.far) = (None, None, false); return vec![]; } diff --git a/crates/tools/src/builder/tests.rs b/crates/tools/src/builder/tests.rs index 5f69acce1..499d40668 100644 --- a/crates/tools/src/builder/tests.rs +++ b/crates/tools/src/builder/tests.rs @@ -430,6 +430,36 @@ fn shape_builder_gesture_waits_for_the_map_without_holding_the_caller() { assert!(!t.editing()); } +/// Escape during a drag made while the map of a large selection is being built cancels the +/// recorded gesture too: the release and the polls after it merge nothing, the tool stops +/// waiting once the map is there, and the next drag merges on its own. +#[test] +fn shape_builder_escape_cancels_a_gesture_made_while_the_map_is_built() { + let (d, s) = square_row(200); + let p = paint(); + let cx1 = ToolContext { revision: (1, 1), ..cx(&d, &s, &p) }; + let mut t = ShapeBuilderTool::default(); + t.pointer(&cx1, &ev(PointerKind::Move, 15.0, 10.0)); + assert!(t.pointer(&cx1, &ev(PointerKind::Down, 15.0, 10.0)).is_empty()); + assert!(t.pointer(&cx1, &ev(PointerKind::Drag, 35.0, 10.0)).is_empty()); + assert_eq!(t.cache.builds, 0, "the gesture did not wait for the map"); + assert!(t.claims_key(&cx1, ToolKey::Escape)); + assert!(t.key(&cx1, ToolKey::Escape, Mods::default()).is_empty()); + assert!(t.pointer(&cx1, &ev(PointerKind::Up, 35.0, 10.0)).is_empty()); + assert!(poll_actions(&mut t, &cx1).is_empty(), "the canceled gesture merges nothing"); + assert!(!t.waiting() && !t.editing()); + // The next drag merges what it touches, with no event of the canceled one. + let mut fresh = ShapeBuilderTool::default(); + fresh.pointer(&cx1, &ev(PointerKind::Move, 55.0, 10.0)); + poll_until_ready(&mut fresh, &cx1); + let next = [ev(PointerKind::Down, 55.0, 10.0), ev(PointerKind::Drag, 75.0, 10.0), ev(PointerKind::Up, 75.0, 10.0)]; + for e in &next { + fresh.pointer(&cx1, e); + t.pointer(&cx1, e); + } + assert_eq!(poll_actions(&mut t, &cx1), poll_actions(&mut fresh, &cx1)); +} + #[test] fn shape_builder_hover_builds_a_small_map_at_once() { let (d, s) = square_row(3);