cranpose_ui/key_event.rs
1//! Keyboard input event types for Cranpose.
2//!
3//! This module provides platform-independent keyboard event types
4//! that are used to route keyboard input to focused components.
5
6use std::fmt;
7
8use cranpose_core::collections::perfect_map::PerfectMap;
9pub use cranpose_foundation::Modifiers;
10
11/// Type of keyboard event.
12#[derive(Debug, Clone, Copy, PartialEq, Eq)]
13pub enum KeyEventType {
14 /// Key was pressed down.
15 KeyDown,
16 /// Key was released.
17 KeyUp,
18}
19
20/// A key on a keyboard, remote control or game controller, named as in
21/// Jetpack Compose's `androidx.compose.ui.input.key.Key`.
22///
23/// Letters and digits name the physical key, independent of the character
24/// the keyboard layout gives it.
25#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
26pub enum Key {
27 /// The A key.
28 A,
29 /// The B key.
30 B,
31 /// The C key.
32 C,
33 /// The D key.
34 D,
35 /// The E key.
36 E,
37 /// The F key.
38 F,
39 /// The G key.
40 G,
41 /// The H key.
42 H,
43 /// The I key.
44 I,
45 /// The J key.
46 J,
47 /// The K key.
48 K,
49 /// The L key.
50 L,
51 /// The M key.
52 M,
53 /// The N key.
54 N,
55 /// The O key.
56 O,
57 /// The P key.
58 P,
59 /// The Q key.
60 Q,
61 /// The R key.
62 R,
63 /// The S key.
64 S,
65 /// The T key.
66 T,
67 /// The U key.
68 U,
69 /// The V key.
70 V,
71 /// The W key.
72 W,
73 /// The X key.
74 X,
75 /// The Y key.
76 Y,
77 /// The Z key.
78 Z,
79
80 /// The 0 key on the main number row.
81 Zero,
82 /// The 1 key on the main number row.
83 One,
84 /// The 2 key on the main number row.
85 Two,
86 /// The 3 key on the main number row.
87 Three,
88 /// The 4 key on the main number row.
89 Four,
90 /// The 5 key on the main number row.
91 Five,
92 /// The 6 key on the main number row.
93 Six,
94 /// The 7 key on the main number row.
95 Seven,
96 /// The 8 key on the main number row.
97 Eight,
98 /// The 9 key on the main number row.
99 Nine,
100
101 /// The F1 function key.
102 F1,
103 /// The F2 function key.
104 F2,
105 /// The F3 function key.
106 F3,
107 /// The F4 function key.
108 F4,
109 /// The F5 function key.
110 F5,
111 /// The F6 function key.
112 F6,
113 /// The F7 function key.
114 F7,
115 /// The F8 function key.
116 F8,
117 /// The F9 function key.
118 F9,
119 /// The F10 function key.
120 F10,
121 /// The F11 function key.
122 F11,
123 /// The F12 function key.
124 F12,
125
126 /// The up arrow key, or up on a D-pad.
127 DirectionUp,
128 /// The down arrow key, or down on a D-pad.
129 DirectionDown,
130 /// The left arrow key, or left on a D-pad.
131 DirectionLeft,
132 /// The right arrow key, or right on a D-pad.
133 DirectionRight,
134 /// The center button of a D-pad: OK or Select on a remote.
135 DirectionCenter,
136 /// The Home key, which moves to the start of a line or list.
137 MoveHome,
138 /// The End key, which moves to the end of a line or list.
139 MoveEnd,
140 /// The Page Up key.
141 PageUp,
142 /// The Page Down key.
143 PageDown,
144
145 /// The Backspace key, which deletes backward.
146 Backspace,
147 /// The Delete key, which deletes forward.
148 Delete,
149 /// The main Enter or Return key.
150 Enter,
151 /// The Enter key on the number pad.
152 NumPadEnter,
153 /// The Tab key.
154 Tab,
155 /// The space bar.
156 Spacebar,
157 /// The Escape key.
158 Escape,
159
160 /// The left Shift key.
161 ShiftLeft,
162 /// The right Shift key.
163 ShiftRight,
164 /// The left Control key.
165 CtrlLeft,
166 /// The right Control key.
167 CtrlRight,
168 /// The left Alt key, called Option on macOS.
169 AltLeft,
170 /// The right Alt key, called Option on macOS.
171 AltRight,
172 /// The left Meta key: Command on macOS, the Windows key elsewhere.
173 MetaLeft,
174 /// The right Meta key: Command on macOS, the Windows key elsewhere.
175 MetaRight,
176
177 /// The minus and underscore key on US layouts.
178 Minus,
179 /// The equals and plus key on US layouts.
180 Equals,
181 /// The left square bracket key on US layouts.
182 LeftBracket,
183 /// The right square bracket key on US layouts.
184 RightBracket,
185 /// The backslash key on US layouts.
186 Backslash,
187 /// The semicolon and colon key on US layouts.
188 Semicolon,
189 /// The apostrophe and quotation mark key on US layouts.
190 Apostrophe,
191 /// The comma key on US layouts.
192 Comma,
193 /// The period key on US layouts.
194 Period,
195 /// The forward slash key on US layouts.
196 Slash,
197 /// The grave accent and tilde key on US layouts, left of the 1 key.
198 Grave,
199
200 /// The Back key of a remote or of the Android system navigation.
201 Back,
202 /// The Menu key.
203 Menu,
204 /// The Search key of a remote.
205 Search,
206 /// The Info key of a remote.
207 Info,
208 /// The Guide key of a remote, which opens the program guide.
209 Guide,
210 /// The Settings key of a remote.
211 Settings,
212 /// The Channel Up key of a remote.
213 ChannelUp,
214 /// The Channel Down key of a remote.
215 ChannelDown,
216 /// The red program key of a remote.
217 ProgramRed,
218 /// The green program key of a remote.
219 ProgramGreen,
220 /// The yellow program key of a remote.
221 ProgramYellow,
222 /// The blue program key of a remote.
223 ProgramBlue,
224
225 /// The Play media key.
226 MediaPlay,
227 /// The Pause media key.
228 MediaPause,
229 /// The Play/Pause media key.
230 MediaPlayPause,
231 /// The Stop media key.
232 MediaStop,
233 /// The Next track media key.
234 MediaNext,
235 /// The Previous track media key.
236 MediaPrevious,
237 /// The Rewind media key.
238 MediaRewind,
239 /// The Fast Forward media key.
240 MediaFastForward,
241 /// The Record media key.
242 MediaRecord,
243
244 /// The A button of a game controller.
245 ButtonA,
246 /// The B button of a game controller.
247 ButtonB,
248 /// The X button of a game controller.
249 ButtonX,
250 /// The Y button of a game controller.
251 ButtonY,
252 /// The Select button of a game controller.
253 ButtonSelect,
254 /// The Start button of a game controller.
255 ButtonStart,
256
257 /// Key not recognized or not mapped.
258 Unknown,
259}
260
261// W3C `KeyboardEvent.code` values, found by a hash that gives each its own
262// slot: 4.2 ns a lookup where a scan of the 93 took 27 (M3 Pro, every code
263// and five misses in a shuffled order).
264static DOM_KEY_CODES: PerfectMap<Key, 93> = PerfectMap::new([
265 ("KeyA", Key::A),
266 ("KeyB", Key::B),
267 ("KeyC", Key::C),
268 ("KeyD", Key::D),
269 ("KeyE", Key::E),
270 ("KeyF", Key::F),
271 ("KeyG", Key::G),
272 ("KeyH", Key::H),
273 ("KeyI", Key::I),
274 ("KeyJ", Key::J),
275 ("KeyK", Key::K),
276 ("KeyL", Key::L),
277 ("KeyM", Key::M),
278 ("KeyN", Key::N),
279 ("KeyO", Key::O),
280 ("KeyP", Key::P),
281 ("KeyQ", Key::Q),
282 ("KeyR", Key::R),
283 ("KeyS", Key::S),
284 ("KeyT", Key::T),
285 ("KeyU", Key::U),
286 ("KeyV", Key::V),
287 ("KeyW", Key::W),
288 ("KeyX", Key::X),
289 ("KeyY", Key::Y),
290 ("KeyZ", Key::Z),
291 ("Digit0", Key::Zero),
292 ("Digit1", Key::One),
293 ("Digit2", Key::Two),
294 ("Digit3", Key::Three),
295 ("Digit4", Key::Four),
296 ("Digit5", Key::Five),
297 ("Digit6", Key::Six),
298 ("Digit7", Key::Seven),
299 ("Digit8", Key::Eight),
300 ("Digit9", Key::Nine),
301 ("F1", Key::F1),
302 ("F2", Key::F2),
303 ("F3", Key::F3),
304 ("F4", Key::F4),
305 ("F5", Key::F5),
306 ("F6", Key::F6),
307 ("F7", Key::F7),
308 ("F8", Key::F8),
309 ("F9", Key::F9),
310 ("F10", Key::F10),
311 ("F11", Key::F11),
312 ("F12", Key::F12),
313 ("ArrowUp", Key::DirectionUp),
314 ("ArrowDown", Key::DirectionDown),
315 ("ArrowLeft", Key::DirectionLeft),
316 ("ArrowRight", Key::DirectionRight),
317 ("Home", Key::MoveHome),
318 ("End", Key::MoveEnd),
319 ("PageUp", Key::PageUp),
320 ("PageDown", Key::PageDown),
321 ("Backspace", Key::Backspace),
322 ("Delete", Key::Delete),
323 ("Enter", Key::Enter),
324 ("NumpadEnter", Key::NumPadEnter),
325 ("Tab", Key::Tab),
326 ("Space", Key::Spacebar),
327 ("Escape", Key::Escape),
328 ("ShiftLeft", Key::ShiftLeft),
329 ("ShiftRight", Key::ShiftRight),
330 ("ControlLeft", Key::CtrlLeft),
331 ("ControlRight", Key::CtrlRight),
332 ("AltLeft", Key::AltLeft),
333 ("AltRight", Key::AltRight),
334 ("MetaLeft", Key::MetaLeft),
335 ("MetaRight", Key::MetaRight),
336 ("Minus", Key::Minus),
337 ("Equal", Key::Equals),
338 ("BracketLeft", Key::LeftBracket),
339 ("BracketRight", Key::RightBracket),
340 ("Backslash", Key::Backslash),
341 ("Semicolon", Key::Semicolon),
342 ("Quote", Key::Apostrophe),
343 ("Comma", Key::Comma),
344 ("Period", Key::Period),
345 ("Slash", Key::Slash),
346 ("Backquote", Key::Grave),
347 ("ContextMenu", Key::Menu),
348 ("BrowserSearch", Key::Search),
349 ("MediaPlay", Key::MediaPlay),
350 ("MediaPause", Key::MediaPause),
351 ("MediaPlayPause", Key::MediaPlayPause),
352 ("MediaStop", Key::MediaStop),
353 ("MediaTrackNext", Key::MediaNext),
354 ("MediaTrackPrevious", Key::MediaPrevious),
355 ("MediaRewind", Key::MediaRewind),
356 ("MediaFastForward", Key::MediaFastForward),
357 ("MediaRecord", Key::MediaRecord),
358]);
359
360// W3C `KeyboardEvent.key` values of the keys a TV remote sends, which TV
361// browsers report with no `code`.
362static DOM_KEY_VALUES: PerfectMap<Key, 23> = PerfectMap::new([
363 ("GoBack", Key::Back),
364 ("BrowserBack", Key::Back),
365 ("XF86Back", Key::Back),
366 ("Enter", Key::Enter),
367 ("ContextMenu", Key::Menu),
368 ("BrowserSearch", Key::Search),
369 ("Info", Key::Info),
370 ("Guide", Key::Guide),
371 ("Settings", Key::Settings),
372 ("ChannelUp", Key::ChannelUp),
373 ("ChannelDown", Key::ChannelDown),
374 ("ColorF0Red", Key::ProgramRed),
375 ("ColorF1Green", Key::ProgramGreen),
376 ("ColorF2Yellow", Key::ProgramYellow),
377 ("ColorF3Blue", Key::ProgramBlue),
378 ("MediaPlay", Key::MediaPlay),
379 ("MediaPause", Key::MediaPause),
380 ("MediaPlayPause", Key::MediaPlayPause),
381 ("MediaStop", Key::MediaStop),
382 ("MediaTrackNext", Key::MediaNext),
383 ("MediaTrackPrevious", Key::MediaPrevious),
384 ("MediaRewind", Key::MediaRewind),
385 ("MediaFastForward", Key::MediaFastForward),
386]);
387
388// The key codes Samsung's Tizen and LG's webOS give their remotes' Back key.
389const TV_BACK_KEY_CODES: [u32; 2] = [10009, 461];
390
391impl Key {
392 /// The key a W3C `KeyboardEvent.code` value names, such as `"KeyA"`,
393 /// `"ArrowUp"` or `"MediaPlayPause"`. A code with no counterpart here is
394 /// [`Key::Unknown`].
395 pub fn from_dom_code(code: &str) -> Self {
396 DOM_KEY_CODES.get(code).copied().unwrap_or(Self::Unknown)
397 }
398
399 /// The key a browser event names, as a TV browser reports a remote: by
400 /// its `code` when it has one, else by its `key` value (`"GoBack"`,
401 /// `"MediaPlayPause"`, `"ChannelUp"`), else by the `keyCode` Tizen
402 /// (10009) and webOS (461) give the Back key.
403 pub fn from_dom_event(code: &str, key: &str, key_code: u32) -> Self {
404 match Self::from_dom_code(code) {
405 Self::Unknown if TV_BACK_KEY_CODES.contains(&key_code) => Self::Back,
406 Self::Unknown => DOM_KEY_VALUES.get(key).copied().unwrap_or(Self::Unknown),
407 known => known,
408 }
409 }
410}
411
412/// What the platform reported about a key event beyond the key, as
413/// `KeyEvent.nativeKeyEvent` holds in Compose.
414#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
415pub struct NativeKeyEvent {
416 /// How many times the key repeated while held: 0 for the press itself,
417 /// 1 for the first repeat, and so on on Android. The desktop and the web
418 /// report only whether an event repeats, so every repeat there is 1.
419 pub repeat_count: u32,
420}
421
422/// A keyboard input event.
423///
424/// Holds the key, whether it went down or up, the modifier keys held with it
425/// and the text it typed.
426#[derive(Debug, Clone, PartialEq, Eq)]
427pub struct KeyEvent {
428 key: Key,
429 text: String,
430 modifiers: Modifiers,
431 type_: KeyEventType,
432 native: NativeKeyEvent,
433}
434
435impl KeyEvent {
436 /// Creates a key event. `text` is what the key typed with the keyboard
437 /// layout and modifiers applied (Shift+A types "A"), empty for keys that
438 /// type nothing.
439 pub fn new(
440 key: Key,
441 text: impl Into<String>,
442 modifiers: Modifiers,
443 type_: KeyEventType,
444 ) -> Self {
445 Self {
446 key,
447 text: text.into(),
448 modifiers,
449 type_,
450 native: NativeKeyEvent::default(),
451 }
452 }
453
454 /// Creates a key down event with the given key and text.
455 pub fn key_down(key: Key, text: impl Into<String>) -> Self {
456 Self::new(key, text, Modifiers::NONE, KeyEventType::KeyDown)
457 }
458
459 /// Creates a key up event for `key`.
460 pub fn key_up(key: Key) -> Self {
461 Self::new(key, "", Modifiers::NONE, KeyEventType::KeyUp)
462 }
463
464 /// Creates a key down event with modifiers.
465 pub fn key_down_with_modifiers(
466 key: Key,
467 text: impl Into<String>,
468 modifiers: Modifiers,
469 ) -> Self {
470 Self::new(key, text, modifiers, KeyEventType::KeyDown)
471 }
472
473 /// This event as the `repeat_count`th repeat of a held key.
474 #[must_use]
475 pub fn with_repeat_count(mut self, repeat_count: u32) -> Self {
476 self.native.repeat_count = repeat_count;
477 self
478 }
479
480 /// The key that went down or up.
481 pub fn key(&self) -> Key {
482 self.key
483 }
484
485 /// Whether the key went down or up.
486 pub fn type_(&self) -> KeyEventType {
487 self.type_
488 }
489
490 /// The text this key typed, empty for keys that type nothing.
491 pub fn text(&self) -> &str {
492 &self.text
493 }
494
495 /// The modifier keys held with this key.
496 pub fn modifiers(&self) -> Modifiers {
497 self.modifiers
498 }
499
500 /// Whether a Shift key was held.
501 pub fn is_shift_pressed(&self) -> bool {
502 self.modifiers.shift
503 }
504
505 /// Whether a Control key was held.
506 pub fn is_ctrl_pressed(&self) -> bool {
507 self.modifiers.ctrl
508 }
509
510 /// Whether an Alt key was held.
511 pub fn is_alt_pressed(&self) -> bool {
512 self.modifiers.alt
513 }
514
515 /// Whether a Meta key was held.
516 pub fn is_meta_pressed(&self) -> bool {
517 self.modifiers.meta
518 }
519
520 /// The Unicode code point of the character this key typed, if it typed
521 /// one.
522 pub fn utf16_code_point(&self) -> Option<u32> {
523 self.text.chars().next().map(u32::from)
524 }
525
526 /// What the platform reported beyond the key, such as the repeat count.
527 pub fn native_key_event(&self) -> NativeKeyEvent {
528 self.native
529 }
530
531 /// Returns true if this is a key down event.
532 pub fn is_key_down(&self) -> bool {
533 self.type_ == KeyEventType::KeyDown
534 }
535
536 /// Returns true if this key produces printable text.
537 pub fn has_text(&self) -> bool {
538 !self.text.is_empty()
539 }
540}
541
542impl fmt::Display for KeyEvent {
543 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
544 write!(
545 f,
546 "KeyEvent({:?}, text=\"{}\", {:?})",
547 self.key, self.text, self.type_
548 )
549 }
550}
551
552#[cfg(test)]
553#[path = "tests/key_event_tests.rs"]
554mod tests;