Skip to main content

agg_gui/widget/app/
keyboard.rs

1//! Keyboard-input methods on [`App`] — key down / up routing through the
2//! modal path, the focused widget, focus traversal, unconsumed-key
3//! accelerators, the root-level default / cancel action and finally the
4//! legacy global handler. Split out of `app.rs` (800-line guardrail);
5//! pointer input lives in `pointer.rs`, wheel routing stays in `app.rs`.
6
7use crate::event::{Event, EventResult, Key, Modifiers};
8use crate::geometry::Point;
9use crate::widget::tree::{
10    activate_action_at, active_modal_path, cancel_action_path, default_action_path, dispatch_event,
11    dispatch_unconsumed_key,
12};
13use crate::widget::App;
14
15impl App {
16    /// Key pressed. Delivered to the focused widget first, then to the visible
17    /// widget tree as an unconsumed key if focus ignores it.
18    pub fn on_key_down(&mut self, key: Key, mods: Modifiers) {
19        // Ctrl/Meta+Tab is a *direct* focus-traversal escape hatch: it advances
20        // focus without ever offering the event to the focused widget, so a
21        // widget that consumes plain Tab (e.g. the rich-text editor indenting)
22        // can never trap keyboard focus. Plain Tab / Shift+Tab are instead
23        // dispatched to the focused widget first (below) and only fall through
24        // to traversal when it Ignores them — that "consume Tab to opt out" is
25        // the established way for an editor to keep Tab for itself.
26        if key == Key::Tab && (mods.ctrl || mods.meta) {
27            self.advance_focus(!mods.shift);
28            return;
29        }
30        let event = Event::KeyDown {
31            key: key.clone(),
32            modifiers: mods,
33        };
34        let modal = active_modal_path(self.root.as_ref());
35        let result = if let Some(path) = modal.clone() {
36            // A focused widget INSIDE the modal (a dialog's text field)
37            // gets keys first; the modal subtree handles the rest (Esc).
38            let target = match self.focus.clone() {
39                Some(focus) if focus.starts_with(&path) => focus,
40                _ => path,
41            };
42            dispatch_event(&mut self.root, &target, &event, Point::ORIGIN)
43        } else if let Some(path) = self.focus.clone() {
44            dispatch_event(&mut self.root, &path, &event, Point::ORIGIN)
45        } else {
46            EventResult::Ignored
47        };
48        if !result.is_consumed() {
49            // A plain Tab / Shift+Tab the focused widget didn't consume falls
50            // back to focus traversal — the default — instead of the
51            // unconsumed-key / global-handler path other keys take.
52            if key == Key::Tab {
53                self.advance_focus(!mods.shift);
54                return;
55            }
56            let result = dispatch_unconsumed_key(self.root.as_mut(), &key, mods);
57            if !result.is_consumed() {
58                // Root-level default / cancel action (`Button::with_default_action`
59                // / `with_cancel_action`) — only when NO modal is showing. A
60                // modal owns Enter / Escape for its whole scope: it has already
61                // resolved its own default / cancel action (`ModalSheet`
62                // searches its content with `skip_modals = false`), and a
63                // modal without one must still shadow the actions behind it
64                // rather than let them fire under the sheet. That holds even
65                // for a `with_key_passthrough` sheet: passthrough forwards
66                // ordinary keys to the app behind, not the modal's own
67                // Enter / Escape semantics.
68                let result = match modal {
69                    Some(_) => EventResult::Ignored,
70                    None => self.dispatch_root_action(&key),
71                };
72                if !result.is_consumed() {
73                    if let Some(ref mut handler) = self.global_key_handler {
74                        handler(key, mods);
75                    }
76                }
77            }
78        }
79    }
80
81    /// Enter → the tree's first visible `default_action` widget;
82    /// Escape → the first `cancel_action` widget. Only called when no
83    /// modal is showing (see `on_key_down`); subtrees under an active
84    /// modal are skipped as a second line of defence.
85    fn dispatch_root_action(&mut self, key: &Key) -> EventResult {
86        let path = match key {
87            Key::Enter => default_action_path(self.root.as_ref(), true),
88            Key::Escape => cancel_action_path(self.root.as_ref(), true),
89            _ => None,
90        };
91        match path {
92            Some(path) => activate_action_at(self.root.as_mut(), &path),
93            None => EventResult::Ignored,
94        }
95    }
96
97    /// Key released. Delivered to the focused widget.
98    pub fn on_key_up(&mut self, key: Key, mods: Modifiers) {
99        let event = Event::KeyUp {
100            key,
101            modifiers: mods,
102        };
103        if let Some(path) = self.focus.clone() {
104            dispatch_event(&mut self.root, &path, &event, Point::ORIGIN);
105        }
106    }
107}