From a9e2229cd68bbf57923549232e0a6923b9aacbcb Mon Sep 17 00:00:00 2001 From: aiongg Date: Fri, 16 Oct 2026 06:25:01 +0000 Subject: [PATCH] Integration: Esc that closes a dialog doesn't reach the canvas --- crates/ui-egui/src/canvas.rs | 37 ++++++++++++++++++++++++++++++++++-- 1 file changed, 35 insertions(+), 2 deletions(-) diff --git a/crates/ui-egui/src/canvas.rs b/crates/ui-egui/src/canvas.rs index 015adbed..2dcd631e 100644 --- a/crates/ui-egui/src/canvas.rs +++ b/crates/ui-egui/src/canvas.rs @@ -2448,7 +2448,7 @@ fn handle_input(app: &mut DesignApp, ui: &mut egui::Ui, resp: &egui::Response, r // Keyboard for the active tool. A field focused when the frame began keeps this frame's keys. let field_id = field_focused_id(ui.ctx()); let field = ui.ctx().text_edit_focused() || ui.data(|d| d.get_temp::(field_id)).unwrap_or(false); - if menu_open || (field && !resp.has_focus()) || !keyboard_pane(app) { + if menu_open || modal_open(app) || (field && !resp.has_focus()) || !keyboard_pane(app) { return; } // Keys of a frame that began composing are the IME's too (a Backspace that empties the marked @@ -2519,6 +2519,12 @@ fn handle_input(app: &mut DesignApp, ui: &mut egui::Ui, resp: &egui::Response, r let _ = DVec2::ZERO; } +/// A dialog or Quick Apply has the keyboard. The canvas keeps egui's focus behind a modal, and it +/// runs before the modal draws, so it must not also act on the Esc or Return that closes it. +fn modal_open(app: &DesignApp) -> bool { + app.ui.dialog.is_some() || app.ui.palette.is_some() +} + /// Does the current pane take the keyboard? Both panes (split, or the second window) see the same /// events, so typing, tool keys, the clipboard and the input method serve only the focused pane. fn keyboard_pane(app: &DesignApp) -> bool { @@ -2588,7 +2594,7 @@ fn ime_output(app: &mut DesignApp, ctx: &egui::Context, xf: &Xf) { return; } // A text field, dialog or Quick Apply has the keyboard: egui's own text editing serves the IME. - let ours = app.session.wants_text() && !ctx.text_edit_focused() && app.ui.dialog.is_none() && app.ui.palette.is_none(); + let ours = app.session.wants_text() && !ctx.text_edit_focused() && !modal_open(app); let caret = if ours { app.session.tool_ime_caret(app.view_info()) } else { None }; let Some((a, b)) = caret else { // The IME goes away with its marked text: keep that text as typed, like a click away. @@ -4189,6 +4195,33 @@ mod tests { assert!(got[0].is_empty(), "one click places the caret: {got:?}"); } + #[test] + fn esc_that_closes_a_dialog_keeps_the_type_tool_and_its_text_selection() { + let (mut h, id) = typing("One two three"); + // A click in the text gives the canvas the keyboard focus, as when typing. + let p = screen_at(&mut h, id, 5..6); + h.hover_at(p); + h.step(); + for _ in 0..2 { + click(&mut h, p); + } + h.run_steps(4); + let words = selected_range(&h); + assert_eq!(words, 4..7, "the double-click selects \"two\""); + let app = h.state_mut(); + let name = app.run("style.character.create", json!({"name": "Key Term"})).unwrap()["name"].as_str().unwrap().to_string(); + app.ui.dialog = Some(crate::dialogs::Dialog::new("characterStyleOptions", json!({"name": name}))); + h.run_steps(3); + h.key_press(egui::Key::Escape); + h.run_steps(3); + assert!(h.state().ui.dialog.is_none(), "Esc closes the dialog"); + assert_eq!((h.state().session.tool_id(), selected_range(&h)), ("type", words)); + // With no dialog open, Esc in the text still leaves it for the Selection tool. + h.key_press(egui::Key::Escape); + h.run_steps(3); + assert_eq!(h.state().session.tool_id(), "selection"); + } + #[test] fn slow_clicks_do_not_add_up() { let (mut h, id) = typing("One two. Three four!");