From 5ce3aaf260acac86e5bfe4f2206a6b8f84661cfb Mon Sep 17 00:00:00 2001 From: Hakan Seven Date: Thu, 18 Jun 2026 14:00:03 +0300 Subject: [PATCH] fix: modal dialogs capture the keyboard MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Modals blocked the mouse but not the keyboard: keystrokes from the global key subscription leaked past an open dialog into the command line and fired as commands once it closed. Guard `update()` so that while a modal is open, Escape closes it and every other keystroke-derived message (command-line input/edit, F-key toggles, edit shortcuts, undo/redo) is swallowed. A modal's own text fields keep working — they emit their own messages, which are not in the blocked set. (#126) Co-Authored-By: Claude Opus 4.8 --- src/app/update.rs | 49 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) diff --git a/src/app/update.rs b/src/app/update.rs index f94fd0b3c..c4778ba72 100644 --- a/src/app/update.rs +++ b/src/app/update.rs @@ -15,6 +15,40 @@ use acadrust::{EntityType as AcadEntityType, Handle}; use iced::time::Instant; use iced::{mouse, Task}; +/// Keystroke-derived messages that an open modal dialog must swallow so the +/// keyboard can't reach the main window (command line, F-key toggles, edit +/// shortcuts) while a dialog is up. `CommandEscape` is handled separately (it +/// closes the modal); a modal's own text fields emit their own messages, which +/// are not in this set. See [`OpenCADStudio::update`] and #126. +fn is_modal_blocked_key_msg(msg: &Message) -> bool { + matches!( + msg, + Message::CommandInput(_) + | Message::CommandAppendChar(_) + | Message::CommandSpace + | Message::CommandFinalize + | Message::CommandBackspace + | Message::CommandHistoryPrev + | Message::CommandHistoryNext + | Message::DynTabNext + | Message::MTextCaretMove(_) + | Message::DeleteSelected + | Message::ToggleSnapEnabled + | Message::ToggleGrid + | Message::ToggleOrtho + | Message::ToggleGridSnap + | Message::TogglePolar + | Message::ToggleOTrack + | Message::ToggleDynInput + | Message::TabNew + | Message::OpenFile + | Message::SaveFile + | Message::SaveAs + | Message::Undo + | Message::Redo + ) +} + const VIEWCUBE_HIT_SIZE: f32 = VIEWCUBE_DRAW_PX; /// Pixel distance from a Model-tile inner divider that still registers as /// a resize grip on the press. @@ -96,6 +130,21 @@ impl OpenCADStudio { } pub fn update(&mut self, msg: Message) -> Task { + // A modal dialog must capture the keyboard the same way it already + // captures the mouse. Otherwise keystrokes from the global key + // subscription leak past the modal into the command line and fire as + // commands once the dialog closes. While a modal is open, Escape + // closes it and every other keystroke-derived message is swallowed; + // the modal's own text fields keep working because they emit their own + // (non-blocked) messages. (#126) + if self.active_modal.is_some() { + if matches!(msg, Message::CommandEscape) { + return self.update(Message::CloseModal); + } + if is_modal_blocked_key_msg(&msg) { + return Task::none(); + } + } let task = self.update_inner(msg); // After every message, mirror the active command step's prompt so // its history line stays pinned (non-fading) until the step changes.