diff --git a/crates/ui-egui/src/shortcuts.rs b/crates/ui-egui/src/shortcuts.rs index f8aaf2dd2..c2e549740 100644 --- a/crates/ui-egui/src/shortcuts.rs +++ b/crates/ui-egui/src/shortcuts.rs @@ -350,10 +350,12 @@ pub fn handle(app: &mut VectorcraftApp, ctx: &egui::Context) { let row_key = |chord: &craft_keymap::Chord| { rows_keys && chord.is_plain() && !chord.mods.shift && matches!(chord.key, craft_keymap::Key::Delete | craft_keymap::Key::Backspace) }; + // An Escape that ended a ruler-origin drag or floating Tools move runs no command bound to it. + let gesture_escape = |chord: &craft_keymap::Chord| escape_ended_gesture && chord.is_plain() && chord.key == craft_keymap::Key::Escape; let hits = dispatch(app, ctx, |hit| { let chord = &hit.trigger.chord; let shifted_punctuation = chord.mods.shift && chord.key.name().chars().all(|c| !c.is_alphanumeric()); - !(chord.is_plain() && types_text(chord.key) && !shifted_punctuation || row_key(chord)) + !(chord.is_plain() && types_text(chord.key) && !shifted_punctuation || row_key(chord) || gesture_escape(chord)) }); let ran = !hits.is_empty(); for c in hits { diff --git a/crates/ui-egui/src/tests/screenmode.rs b/crates/ui-egui/src/tests/screenmode.rs index 2e3500630..3dffb4aa8 100644 --- a/crates/ui-egui/src/tests/screenmode.rs +++ b/crates/ui-egui/src/tests/screenmode.rs @@ -465,6 +465,29 @@ fn held_escape_that_cancels_ruler_origin_drag_keeps_the_tool() { } } +#[test] +fn held_escape_that_cancels_a_move_skips_a_command_bound_to_escape() { + for ruler in [false, true] { + let ctx = egui::Context::default(); + let mut app = in_screen_mode(&ctx, 0); + app.run("shortcuts.set", json!({"id": "select.none", "shortcut": "Escape", "force": true})).unwrap(); + let selected = app.session.active().unwrap().selection.objects.clone(); + assert!(!selected.is_empty()); + let to = if ruler { begin_ruler_origin_drag(&mut app, &ctx) } else { begin_tools_move(&mut app, &ctx).1 }; + frame(&mut app, &ctx, escape_event(true, false)); + assert_eq!(app.session.active().unwrap().selection.objects, selected, "the cancelling Escape runs no command (ruler: {ruler})"); + for _ in 0..3 { + frame(&mut app, &ctx, escape_event(true, true)); + } + release_drag(&mut app, &ctx, to); + frame(&mut app, &ctx, escape_event(true, true)); + assert_eq!(app.session.active().unwrap().selection.objects, selected, "held Escape runs no command (ruler: {ruler})"); + frame(&mut app, &ctx, escape_event(false, false)); + frame(&mut app, &ctx, escape()); + assert!(app.session.active().unwrap().selection.objects.is_empty(), "the next Escape runs the command (ruler: {ruler})"); + } +} + #[test] fn a_popup_takes_escape_before_full_screen_exits() { let ctx = egui::Context::default();