diff --git a/crates/ui-egui/src/shortcuts.rs b/crates/ui-egui/src/shortcuts.rs index 4866833d5..bb0cb06e0 100644 --- a/crates/ui-egui/src/shortcuts.rs +++ b/crates/ui-egui/src/shortcuts.rs @@ -205,8 +205,9 @@ pub fn handle(app: &mut VectorcraftApp, ctx: &egui::Context) { presentation_keys(app, ctx, escape_pair); return; } - let escape_held_by_drag = - crate::canvas::dragging(ctx) || crate::canvas::ruler_origin_dragging(ctx) || crate::tools_float::escape_cancelled_move(ctx); + // Escape that cancels a ruler-origin drag or floating Tools move skips the tool and later Escape actions. + let escape_ended_gesture = crate::canvas::ruler_origin_dragging(ctx) || crate::tools_float::escape_cancelled_move(ctx); + let escape_held_by_drag = crate::canvas::dragging(ctx) || escape_ended_gesture; let typing = ctx.egui_wants_keyboard_input(); // Escape closes Edit Toolbar's drawer first (egui drops a drag from it on Escape already). if app.ui.tools_drawer && !typing && !escape_held_by_drag && ctx.input_mut(|i| i.consume_key(Modifiers::NONE, Key::Escape)) { @@ -226,6 +227,10 @@ pub fn handle(app: &mut VectorcraftApp, ctx: &egui::Context) { if (text || !busy) && ctx.input(|i| runs_a_command(i, k, text)) { continue; } + if k == Key::Escape && escape_ended_gesture { + remember_escape_pair(ctx, k, escape_pair); + continue; + } // A key the tool claims (Esc with a loaded place cursor) is only the tool's. let claimed = app.session.tool_claims_key(tk, view); let mods = ctx.input(|i| { @@ -276,10 +281,7 @@ pub fn handle(app: &mut VectorcraftApp, ctx: &egui::Context) { } } // An Escape that ended something can start a pair too. One that closed a popup cannot. - if k == Key::Escape && escape_pair.is_some() && !egui::Popup::is_any_open(ctx) { - let now = ctx.input(|i| i.time); - ctx.data_mut(|d| d.insert_temp(escape_pair_id(), now)); - } + remember_escape_pair(ctx, k, escape_pair); if k == Key::Enter && !busy && !claimed { // Enter with a selection tool opens the Move dialog, as a double-click on its button // does. @@ -344,6 +346,13 @@ pub fn handle(app: &mut VectorcraftApp, ctx: &egui::Context) { single_key_shortcuts(app, ctx); } +fn remember_escape_pair(ctx: &egui::Context, key: Key, escape_pair: Option) { + if key == Key::Escape && escape_pair.is_some() && !egui::Popup::is_any_open(ctx) { + let now = ctx.input(|i| i.time); + ctx.data_mut(|d| d.insert_temp(escape_pair_id(), now)); + } +} + /// Dispatch unmodified text keys to command bindings or tools. fn single_key_shortcuts(app: &mut VectorcraftApp, ctx: &egui::Context) { // Single-key tool shortcuts (no Cmd/Ctrl/Alt). With a layout without Latin letters (Persian, diff --git a/crates/ui-egui/src/tests/screenmode.rs b/crates/ui-egui/src/tests/screenmode.rs index 783577ed4..6011fa334 100644 --- a/crates/ui-egui/src/tests/screenmode.rs +++ b/crates/ui-egui/src/tests/screenmode.rs @@ -365,10 +365,26 @@ fn cancelling_tools_move_keeps_the_next_escape_action() { } } +#[test] +fn cancelling_tools_move_keeps_the_tools_escape() { + for mode in [0, 1, 2] { + let ctx = egui::Context::default(); + let mut app = in_screen_mode(&ctx, mode); + app.select_tool("intertwine"); + let (original, to) = begin_tools_move(&mut app, &ctx); + frame(&mut app, &ctx, escape()); + assert_eq!(app.ui.toolbar_pos, original, "move canceled in mode {mode}"); + assert_eq!(app.session.tool_id(), "intertwine", "move Escape keeps the tool in mode {mode}"); + release_drag(&mut app, &ctx, to); + frame(&mut app, &ctx, escape()); + assert_eq!(app.session.tool_id(), "selection", "later Escape reaches the tool in mode {mode}"); + } +} + #[test] fn cancelling_ruler_origin_drag_keeps_the_next_escape_action() { for mode in [0, 1, 2] { - for action in ["pattern", "isolation", "flyout"] { + for action in ["drawer", "pattern", "isolation", "global", "flyout"] { let ctx = egui::Context::default(); let mut app = in_screen_mode(&ctx, mode); let to = begin_ruler_origin_drag(&mut app, &ctx); @@ -384,6 +400,22 @@ fn cancelling_ruler_origin_drag_keeps_the_next_escape_action() { } } +#[test] +fn cancelling_ruler_origin_drag_keeps_the_tools_escape() { + for mode in [0, 1, 2] { + let ctx = egui::Context::default(); + let mut app = in_screen_mode(&ctx, mode); + app.select_tool("intertwine"); + let to = begin_ruler_origin_drag(&mut app, &ctx); + frame(&mut app, &ctx, escape()); + assert_eq!(app.session.tool_id(), "intertwine", "drag Escape keeps the tool in mode {mode}"); + release_drag(&mut app, &ctx, to); + assert!(!crate::canvas::ruler_origin_dragging(&ctx)); + frame(&mut app, &ctx, escape()); + assert_eq!(app.session.tool_id(), "selection", "later Escape reaches the tool in mode {mode}"); + } +} + #[test] fn a_popup_takes_escape_before_full_screen_exits() { let ctx = egui::Context::default();