From e80cc9b49b9dd7672064523c2f83ae45ac98ffdf Mon Sep 17 00:00:00 2001 From: "Ayberk A." <105879357+AyberkA0@users.noreply.github.com> Date: Tue, 13 Oct 2026 21:00:32 +0300 Subject: [PATCH] Fix #1743: Escape cancels a Direct Selection drag Escape while dragging anchors, handles, segments, objects, a type area, a blend spine or a marquee with the Direct (or Group) Selection tool cancels the drag: what it moved goes back and the release after it does nothing. --- crates/tools/src/direct.rs | 32 +++++++++++++++++++++++++++++++- 1 file changed, 31 insertions(+), 1 deletion(-) diff --git a/crates/tools/src/direct.rs b/crates/tools/src/direct.rs index 55a9bff2..35c71926 100644 --- a/crates/tools/src/direct.rs +++ b/crates/tools/src/direct.rs @@ -41,7 +41,7 @@ use crate::meshedit::MeshEdit; use crate::pathtype::{self, BracketDrag, over_bracket}; use crate::rulerguide::GuideEdit; use crate::select::{MoveSnap, is_area_type, matrix_json}; -use crate::{Action, Cursor, Mods, Overlay, PointerEvent, PointerKind, Tool, ToolContext}; +use crate::{Action, Cursor, Mods, Overlay, PointerEvent, PointerKind, Tool, ToolContext, ToolKey}; #[derive(Clone, Debug)] enum State { @@ -361,6 +361,18 @@ impl Tool for DirectSelectionTool { fn options(&self) -> Value { self.mesh.options() } + /// Escape during a drag (anchors, handles, segments, objects, type areas, blend spines, a + /// marquee) cancels it: what it moved goes back and the tool waits for the next press (#1743). + fn key(&mut self, _cx: &ToolContext, key: ToolKey, _mods: Mods) -> Vec { + if key != ToolKey::Escape || matches!(self.state, State::Idle | State::Mesh | State::Corner(_) | State::Bracket(_)) { + return vec![]; + } + self.state = State::Idle; + self.guides.clear(); + (self.anchor_snap, self.copy_snap, self.move_snap) = (None, None, None); + (self.click, self.click_segment) = (None, None); + vec![Action::Cancel] + } fn pointer(&mut self, cx: &ToolContext, ev: &PointerEvent) -> Vec { if let Some(out) = self.guide.pointer(cx, ev) { return out; @@ -1190,6 +1202,24 @@ mod tests { (d, id) } + /// #1743: Escape mid-drag cancels it; the release after it does nothing more. + #[test] + fn escape_cancels_a_drag() { + let (d, _) = arch_doc(); + let s = Selection::default(); + let p = paint(); + let cx = cx(&d, &s, &p); + let mut t = DirectSelectionTool::new(false); + t.pointer(&cx, &PointerEvent::new(PointerKind::Down, 150.0, 262.5)); + t.pointer(&cx, &PointerEvent::new(PointerKind::Drag, 150.0, 240.0)); + assert!(t.busy()); + assert_eq!(t.key(&cx, ToolKey::Escape, Mods::default()), vec![Action::Cancel]); + assert!(!t.busy()); + assert_eq!(t.pointer(&cx, &PointerEvent::new(PointerKind::Drag, 150.0, 220.0)), vec![]); + assert_eq!(t.pointer(&cx, &PointerEvent::new(PointerKind::Up, 150.0, 220.0)), vec![]); + assert_eq!(t.key(&cx, ToolKey::Escape, Mods::default()), vec![], "nothing to cancel"); + } + #[test] fn dragging_a_curved_segment_reshapes_it_and_a_straight_one_moves_its_anchors() { let (d, arch) = arch_doc();