1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
//! Keyboard modifiers, hotkeys, and focused-key dispatch.
use crate::event::{KeyChord, KeyModifiers, KeyPress, UiEvent, UiEventKind, UiKey};
use super::UiState;
impl UiState {
/// Replace the hotkey registry. Called by the host runner from
/// `App::hotkeys()` once per build cycle.
pub fn set_hotkeys(&mut self, hotkeys: Vec<(KeyChord, String)>) {
self.hotkeys.registry = hotkeys;
}
/// Update the tracked modifier mask. Hosts call this from their
/// platform's "modifiers changed" hook (e.g. winit's
/// `WindowEvent::ModifiersChanged`); the value is stamped into
/// `UiEvent.modifiers` for every subsequent pointer event so
/// widgets can detect Shift+click / Ctrl+drag without needing a
/// per-call modifier parameter.
pub fn set_modifiers(&mut self, modifiers: KeyModifiers) {
self.modifiers = modifiers;
}
/// Match `key + modifiers` against the registered hotkey chords.
/// Returns a `Hotkey` event if any registered chord matches; the
/// `event.key` is the chord's registered name. Used by both the
/// library-default path and the capture-keys path (hotkeys always
/// win over a widget's raw key capture).
pub fn try_hotkey(
&self,
key: &UiKey,
modifiers: KeyModifiers,
repeat: bool,
) -> Option<UiEvent> {
let (_, name) = self
.hotkeys
.registry
.iter()
.find(|(chord, _)| chord.matches(key, modifiers))?;
Some(UiEvent {
key: Some(name.clone()),
target: None,
pointer: None,
key_press: Some(KeyPress {
key: key.clone(),
modifiers,
repeat,
}),
text: None,
selection: None,
modifiers,
click_count: 0,
path: None,
kind: UiEventKind::Hotkey,
})
}
/// Build a raw `KeyDown` event routed to the focused target,
/// bypassing the library's Tab/Enter/Escape interpretation. Used
/// by the runner when the focused node has `capture_keys=true`.
/// Returns `None` if no node is focused.
pub fn key_down_raw(
&self,
key: UiKey,
modifiers: KeyModifiers,
repeat: bool,
) -> Option<UiEvent> {
let target = self.focused.clone()?;
Some(UiEvent {
key: Some(target.key.clone()),
target: Some(target),
pointer: None,
key_press: Some(KeyPress {
key,
modifiers,
repeat,
}),
text: None,
selection: None,
modifiers,
click_count: 0,
path: None,
kind: UiEventKind::KeyDown,
})
}
pub fn key_down(
&mut self,
key: UiKey,
modifiers: KeyModifiers,
repeat: bool,
) -> Option<UiEvent> {
if matches!(key, UiKey::Tab) {
if modifiers.shift {
self.focus_prev();
} else {
self.focus_next();
}
self.set_focus_visible(true);
return None;
}
// Hotkeys win over focused-Enter activation: a focused button
// with no hotkey on Enter still activates, but Ctrl+Enter (if
// registered) routes to its hotkey instead. Registration order
// is precedence — first match wins.
if let Some(event) = self.try_hotkey(&key, modifiers, repeat) {
return Some(event);
}
let target = self.focused.clone();
// Non-hotkey keypress on a focused element (Space, Escape,
// arrow-nudge a slider, …) is keyboard activity — `:focus-
// visible` rule: raise the ring even if a prior click had
// suppressed it.
if target.is_some() {
self.set_focus_visible(true);
}
let kind = match (&key, target.is_some()) {
(UiKey::Enter | UiKey::Space, true) => UiEventKind::Activate,
(UiKey::Escape, _) => UiEventKind::Escape,
_ => UiEventKind::KeyDown,
};
Some(UiEvent {
key: target.as_ref().map(|t| t.key.clone()),
target,
pointer: None,
key_press: Some(KeyPress {
key,
modifiers,
repeat,
}),
text: None,
selection: None,
modifiers,
click_count: 0,
path: None,
kind,
})
}
}