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}