Skip to main content

retroglyph_core/event/
key.rs

1//! Keyboard events: [`KeyModifiers`], [`ModifierKey`], [`KeyCode`], [`KeyEventKind`],
2//! [`KeyLocation`], [`KeyEvent`], and [`KeyState`].
3
4use alloc::vec::Vec;
5use core::ops::{BitAnd, BitAndAssign, BitOr, BitOrAssign, Not};
6
7#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
8#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
9/// Keyboard modifier flags.
10///
11/// Implemented as a manual bitflag over `u8` (`SHIFT = 1`, `CONTROL = 2`, `ALT = 4`,
12/// `SUPER = 8`) rather than a [`bitflags`](https://crates.io/crates/bitflags)-generated type, so
13/// this stays a plain value type with no macro-generated API surface. Combine with `|`.
14pub struct KeyModifiers(u8);
15
16impl KeyModifiers {
17    /// No modifiers.
18    pub const NONE: Self = Self(0);
19    /// Shift key.
20    pub const SHIFT: Self = Self(1 << 0);
21    /// Control key.
22    pub const CONTROL: Self = Self(1 << 1);
23    /// Alt key.
24    pub const ALT: Self = Self(1 << 2);
25    /// Super/Meta key (macOS Cmd, Windows/Super key).
26    pub const SUPER: Self = Self(1 << 3);
27
28    /// Builds modifiers from a raw bitmask, silently ignoring any bits above `SUPER` (`0b1111`).
29    ///
30    /// Bitmask layout: `SHIFT = 1`, `CONTROL = 2`, `ALT = 4`, `SUPER = 8`. This is the wire format
31    /// shared by backends that encode modifiers as a single byte (for example the WASM backend's
32    /// JS/Rust boundary).
33    #[must_use]
34    pub const fn from_bits_truncate(bits: u8) -> Self {
35        Self(bits & 0b1111)
36    }
37
38    /// Builds modifiers from four independent flags, one per platform modifier key.
39    ///
40    /// Naming all four as separate parameters (rather than taking a platform-specific modifiers
41    /// type) makes the modifier set exhaustive by function signature: a backend that gains a
42    /// fifth modifier fails to compile at every call site instead of silently dropping it.
43    #[must_use]
44    // Four bools is the point: it makes the modifier set exhaustive by signature (see above).
45    #[allow(clippy::fn_params_excessive_bools)]
46    pub const fn from_parts(shift: bool, control: bool, alt: bool, super_: bool) -> Self {
47        Self((shift as u8) | (control as u8) << 1 | (alt as u8) << 2 | (super_ as u8) << 3)
48    }
49
50    /// Returns the raw bitmask, in the same layout [`from_bits_truncate`](Self::from_bits_truncate)
51    /// accepts. Only the low four bits are ever set.
52    #[must_use]
53    pub const fn bits(self) -> u8 {
54        self.0
55    }
56
57    /// Returns `true` if all bits in `other` are set in `self`.
58    #[must_use]
59    pub const fn contains(self, other: Self) -> bool {
60        (self.0 & other.0) == other.0
61    }
62
63    /// Returns `true` if no modifiers are set.
64    #[must_use]
65    pub const fn is_empty(self) -> bool {
66        self.0 == 0
67    }
68}
69
70impl BitOr for KeyModifiers {
71    type Output = Self;
72    fn bitor(self, rhs: Self) -> Self {
73        Self(self.0 | rhs.0)
74    }
75}
76
77impl BitOrAssign for KeyModifiers {
78    fn bitor_assign(&mut self, rhs: Self) {
79        self.0 |= rhs.0;
80    }
81}
82
83impl BitAnd for KeyModifiers {
84    type Output = Self;
85    fn bitand(self, rhs: Self) -> Self {
86        Self(self.0 & rhs.0)
87    }
88}
89
90impl BitAndAssign for KeyModifiers {
91    fn bitand_assign(&mut self, rhs: Self) {
92        self.0 &= rhs.0;
93    }
94}
95
96impl Not for KeyModifiers {
97    type Output = Self;
98    fn not(self) -> Self {
99        // Mask to the defined bits so the result stays within the same invariant that
100        // `from_bits_truncate` upholds; otherwise unused high bits leak into `Eq`/`Hash`.
101        Self(!self.0 & 0b1111)
102    }
103}
104
105#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
106#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
107#[non_exhaustive]
108/// A modifier key pressed as a standalone key event, independent of the [`KeyModifiers`](crate::event::KeyModifiers) flags
109/// carried on non-modifier key events.
110///
111/// This is flat (no per-side variants) because side is conveyed separately: pair this with the
112/// surrounding [`KeyEvent`](crate::event::KeyEvent)'s [`KeyLocation::Left`](crate::event::KeyLocation::Left)/[`KeyLocation::Right`](crate::event::KeyLocation::Right) rather than duplicating
113/// left/right into `ModifierKey` itself.
114///
115/// Reporting a bare modifier press as a [`KeyCode::Modifier`](crate::event::KeyCode::Modifier) event is backend-dependent: the
116/// crossterm backend requires the terminal to support the kitty keyboard protocol with the
117/// `REPORT_ALL_KEYS_AS_ESCAPE_CODES` enhancement flag enabled; plain terminals never report these.
118pub enum ModifierKey {
119    /// Shift.
120    Shift,
121    /// Control.
122    Control,
123    /// Alt.
124    Alt,
125    /// Super/Meta (macOS Cmd, Windows/Super key).
126    Super,
127}
128
129#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
130#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
131#[non_exhaustive]
132/// Keyboard key codes.
133pub enum KeyCode {
134    /// A character key.
135    Char(char),
136    /// A function key.
137    F(u8),
138    /// Backspace.
139    Backspace,
140    /// Enter.
141    Enter,
142    /// Left arrow.
143    Left,
144    /// Right arrow.
145    Right,
146    /// Up arrow.
147    Up,
148    /// Down arrow.
149    Down,
150    /// Home.
151    Home,
152    /// End.
153    End,
154    /// Page Up.
155    PageUp,
156    /// Page Down.
157    PageDown,
158    /// Tab.
159    Tab,
160    /// Backtab.
161    BackTab,
162    /// Delete.
163    Delete,
164    /// Insert.
165    Insert,
166    /// Escape.
167    Escape,
168    /// A modifier key pressed on its own, without another key. See [`ModifierKey`] for the
169    /// backend-availability caveat.
170    Modifier(ModifierKey),
171    /// Caps Lock.
172    CapsLock,
173    /// Scroll Lock.
174    ScrollLock,
175    /// Num Lock.
176    NumLock,
177    /// Print Screen.
178    PrintScreen,
179    /// Pause.
180    Pause,
181    /// Menu (context menu key).
182    Menu,
183}
184
185#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
186#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
187#[non_exhaustive]
188/// Whether a key event is a press, an auto-repeat, or a release.
189///
190/// Not every backend can distinguish these. Plain terminals only ever emit
191/// [`Press`](Self::Press). Backends with richer input report the full set:
192///
193/// - The winit/software backend emits `Press`, `Repeat` (winit's `repeat`
194///   flag), and `Release`.
195/// - The crossterm backend emits the full set only when the terminal supports
196///   the kitty keyboard protocol (kitty, `WezTerm`, foot, Ghostty, recent
197///   Alacritty); otherwise it degrades to `Press`-only.
198///
199/// Marked `#[non_exhaustive]` for consistency with sibling public enums, in case a future source
200/// reports a state finer-grained than this set (e.g. distinguishing an OS-level key-repeat from a
201/// backend-synthesized one).
202pub enum KeyEventKind {
203    /// The key was pressed.
204    #[default]
205    Press,
206    /// The key is held and auto-repeating.
207    Repeat,
208    /// The key was released.
209    Release,
210}
211
212#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
213#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
214#[non_exhaustive]
215/// The physical location of a key on the keyboard, for keys that appear in more than one place.
216///
217/// Mirrors [winit's `KeyLocation`](https://docs.rs/winit/latest/winit/keyboard/enum.KeyLocation.html):
218/// a key like "1" carries the same [`KeyCode`](crate::event::KeyCode) whether it's pressed above the letters or on the
219/// numpad, and modifier keys like Shift exist on both the left and right sides. This field
220/// disambiguates those cases.
221pub enum KeyLocation {
222    /// The key is in its single, non-duplicated location, or the backend cannot determine which
223    /// side/area a duplicated key came from.
224    #[default]
225    Standard,
226    /// The key is the left-hand copy of a duplicated key (e.g. left Shift).
227    Left,
228    /// The key is the right-hand copy of a duplicated key (e.g. right Shift).
229    Right,
230    /// The key originates from the numeric keypad.
231    Numpad,
232}
233
234#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
235#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
236#[non_exhaustive]
237/// Keyboard input event.
238pub struct KeyEvent {
239    /// The key code.
240    pub code: KeyCode,
241    /// Modifiers held down during the event.
242    pub modifiers: KeyModifiers,
243    /// Whether this is a press, auto-repeat, or release.
244    ///
245    /// Backends that cannot distinguish these always report
246    /// [`KeyEventKind::Press`](crate::event::KeyEventKind::Press). See [`KeyEventKind`](crate::event::KeyEventKind) for per-backend behavior.
247    pub kind: KeyEventKind,
248    /// The physical location of the key, for keys that appear in more than one place.
249    ///
250    /// Backends that cannot determine this always report [`KeyLocation::Standard`](crate::event::KeyLocation::Standard).
251    pub location: KeyLocation,
252}
253
254impl KeyEvent {
255    /// Creates a key press event with the given code and modifiers, and
256    /// [`KeyLocation::Standard`](crate::event::KeyLocation::Standard).
257    #[must_use]
258    pub const fn new(code: KeyCode, modifiers: KeyModifiers) -> Self {
259        Self {
260            code,
261            modifiers,
262            kind: KeyEventKind::Press,
263            location: KeyLocation::Standard,
264        }
265    }
266
267    /// Creates a key event with an explicit [`KeyEventKind`](crate::event::KeyEventKind) and [`KeyLocation::Standard`](crate::event::KeyLocation::Standard).
268    #[must_use]
269    pub const fn with_kind(code: KeyCode, modifiers: KeyModifiers, kind: KeyEventKind) -> Self {
270        Self {
271            code,
272            modifiers,
273            kind,
274            location: KeyLocation::Standard,
275        }
276    }
277
278    /// Creates a key event with an explicit [`KeyEventKind`](crate::event::KeyEventKind) and [`KeyLocation`](crate::event::KeyLocation).
279    #[must_use]
280    pub const fn with_location(
281        code: KeyCode,
282        modifiers: KeyModifiers,
283        kind: KeyEventKind,
284        location: KeyLocation,
285    ) -> Self {
286        Self {
287            code,
288            modifiers,
289            kind,
290            location,
291        }
292    }
293
294    /// Returns `true` if this event is a press or auto-repeat (i.e. the key is
295    /// down), and `false` for a release.
296    #[must_use]
297    pub const fn is_down(self) -> bool {
298        matches!(self.kind, KeyEventKind::Press | KeyEventKind::Repeat)
299    }
300}
301
302/// Tracks which keys are currently held down.
303///
304/// Feed it every [`KeyEvent`] (or [`Event`](super::Event)) you receive and query
305/// [`is_held`](Self::is_held) each frame for held-key movement. A key is
306/// considered held from its first [`KeyEventKind::Press`] until a matching
307/// [`KeyEventKind::Release`], or until [`apply_event`](Self::apply_event) sees an
308/// [`Event::FocusLost`](super::Event::FocusLost), whichever comes first.
309///
310/// Held keys are keyed by `(KeyCode, KeyLocation)`, so a held Numpad8 and a held digit-row 8 are
311/// tracked separately: [`is_held`](Self::is_held) takes the pair, and [`held`](Self::held) yields
312/// it.
313///
314/// Release events are what actually clear a key, and not every backend emits them: plain
315/// terminals only ever report [`KeyEventKind::Press`](crate::event::KeyEventKind::Press) (see [`KeyEventKind`](crate::event::KeyEventKind)). On those
316/// press-only backends a key never leaves the held set on its own -- not even on focus loss, since
317/// there is no release to match -- so call [`clear`](Self::clear) at a suitable boundary (e.g.
318/// once per turn) if you rely on held-key state there. Backends rich enough to emit releases
319/// (winit, or a terminal with the kitty keyboard protocol) are exactly the ones that also emit
320/// [`Event::FocusLost`](super::Event::FocusLost), so [`apply_event`](Self::apply_event) clears the
321/// held set on it: without that, alt-tabbing or clicking away while a key is down would leave it
322/// stuck held forever, since the release is delivered to whichever window/app gains focus instead.
323#[derive(Debug, Clone, Default)]
324#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
325pub struct KeyState {
326    held: Vec<(KeyCode, KeyLocation)>,
327}
328
329impl KeyState {
330    /// Creates an empty key-state tracker.
331    #[must_use]
332    pub const fn new() -> Self {
333        Self { held: Vec::new() }
334    }
335
336    /// Updates the held set from a key event.
337    ///
338    /// [`Press`](crate::event::KeyEventKind::Press) and [`Repeat`](crate::event::KeyEventKind::Repeat) add
339    /// the `(code, location)` pair; [`Release`](crate::event::KeyEventKind::Release) removes it.
340    pub fn apply(&mut self, event: KeyEvent) {
341        let entry = (event.code, event.location);
342        match event.kind {
343            KeyEventKind::Press | KeyEventKind::Repeat => {
344                if !self.held.contains(&entry) {
345                    self.held.push(entry);
346                }
347            }
348            KeyEventKind::Release => {
349                self.held.retain(|&e| e != entry);
350            }
351        }
352    }
353
354    /// Updates the held set from an [`Event`](super::Event).
355    ///
356    /// Key events update the held set as [`apply`](Self::apply) does;
357    /// [`Event::FocusLost`](super::Event::FocusLost) clears it entirely (see the type-level docs
358    /// for why); every other event is ignored.
359    pub fn apply_event(&mut self, event: &super::Event) {
360        match event {
361            super::Event::Key(key) => self.apply(*key),
362            super::Event::FocusLost => self.clear(),
363            _ => {}
364        }
365    }
366
367    /// Returns `true` if `code` at `location` is currently held.
368    #[must_use]
369    pub fn is_held(&self, code: KeyCode, location: KeyLocation) -> bool {
370        self.held.contains(&(code, location))
371    }
372
373    /// Iterates the currently held `(code, location)` pairs, in first-pressed order.
374    pub fn held(&self) -> impl Iterator<Item = (KeyCode, KeyLocation)> + '_ {
375        self.held.iter().copied()
376    }
377
378    /// Clears all held keys.
379    pub fn clear(&mut self) {
380        self.held.clear();
381    }
382}
383
384#[cfg(test)]
385mod tests {
386    use super::super::Event;
387    use super::*;
388    use alloc::vec;
389
390    #[test]
391    fn test_key_modifiers() {
392        let mods = KeyModifiers::SHIFT | KeyModifiers::CONTROL;
393        assert!(mods.contains(KeyModifiers::SHIFT));
394        assert!(mods.contains(KeyModifiers::CONTROL));
395        assert!(!mods.contains(KeyModifiers::ALT));
396        assert!(!mods.is_empty());
397
398        let inverse = !mods;
399        assert!(inverse.contains(KeyModifiers::ALT));
400        assert!(inverse.contains(KeyModifiers::SUPER));
401        assert!(!inverse.contains(KeyModifiers::SHIFT));
402        assert!(!inverse.contains(KeyModifiers::CONTROL));
403    }
404
405    /// Minimal [`Hasher`] so the regression test below works without `std` (`DefaultHasher`
406    /// is a `std`-only type, and this crate is `no_std`-compatible; see `crates/core/src/lib.rs`).
407    #[derive(Default)]
408    struct TestHasher(u64);
409
410    impl core::hash::Hasher for TestHasher {
411        fn finish(&self) -> u64 {
412            self.0
413        }
414        fn write(&mut self, bytes: &[u8]) {
415            for byte in bytes {
416                self.0 = self.0.wrapping_mul(31).wrapping_add(u64::from(*byte));
417            }
418        }
419    }
420
421    #[test]
422    fn test_key_modifiers_not_masks_unused_bits() {
423        use core::hash::Hash;
424
425        let all =
426            KeyModifiers::SHIFT | KeyModifiers::CONTROL | KeyModifiers::ALT | KeyModifiers::SUPER;
427        let inverse = !KeyModifiers::NONE;
428
429        // `!NONE` must equal the explicit combination of all defined bits, not just satisfy
430        // `contains` (which masks internally and would hide leaked high bits).
431        assert_eq!(inverse, all, "NOT NONE should equal ALL");
432
433        let mut inverse_hasher = TestHasher::default();
434        inverse.hash(&mut inverse_hasher);
435        let mut all_hasher = TestHasher::default();
436        all.hash(&mut all_hasher);
437        assert_eq!(
438            core::hash::Hasher::finish(&inverse_hasher),
439            core::hash::Hasher::finish(&all_hasher),
440            "NOT NONE should hash the same as ALL"
441        );
442    }
443
444    #[test]
445    fn test_key_modifiers_super() {
446        let mods = KeyModifiers::SUPER;
447        assert!(mods.contains(KeyModifiers::SUPER));
448        assert!(!mods.contains(KeyModifiers::SHIFT));
449        assert!(!mods.contains(KeyModifiers::CONTROL));
450        assert!(!mods.contains(KeyModifiers::ALT));
451
452        let all =
453            KeyModifiers::SHIFT | KeyModifiers::CONTROL | KeyModifiers::ALT | KeyModifiers::SUPER;
454        assert!(all.contains(KeyModifiers::SUPER));
455        assert!(all.contains(KeyModifiers::SHIFT));
456        assert!(all.contains(KeyModifiers::CONTROL));
457        assert!(all.contains(KeyModifiers::ALT));
458    }
459
460    #[test]
461    fn test_key_modifiers_from_bits_truncate() {
462        assert_eq!(KeyModifiers::from_bits_truncate(0), KeyModifiers::NONE);
463        assert_eq!(KeyModifiers::from_bits_truncate(1), KeyModifiers::SHIFT);
464        assert_eq!(KeyModifiers::from_bits_truncate(2), KeyModifiers::CONTROL);
465        assert_eq!(KeyModifiers::from_bits_truncate(4), KeyModifiers::ALT);
466        assert_eq!(KeyModifiers::from_bits_truncate(8), KeyModifiers::SUPER);
467        assert_eq!(
468            KeyModifiers::from_bits_truncate(0b1111),
469            KeyModifiers::SHIFT | KeyModifiers::CONTROL | KeyModifiers::ALT | KeyModifiers::SUPER
470        );
471        // Bits above SUPER are silently truncated.
472        assert_eq!(
473            KeyModifiers::from_bits_truncate(0xFF),
474            KeyModifiers::SHIFT | KeyModifiers::CONTROL | KeyModifiers::ALT | KeyModifiers::SUPER
475        );
476    }
477
478    #[test]
479    fn test_key_modifiers_bits_round_trip() {
480        for bits in 0..=u8::MAX {
481            assert_eq!(KeyModifiers::from_bits_truncate(bits).bits(), bits & 0b1111);
482        }
483    }
484
485    #[test]
486    fn test_key_modifiers_from_parts() {
487        assert_eq!(
488            KeyModifiers::from_parts(false, false, false, false),
489            KeyModifiers::NONE
490        );
491        assert_eq!(
492            KeyModifiers::from_parts(true, false, false, false),
493            KeyModifiers::SHIFT
494        );
495        assert_eq!(
496            KeyModifiers::from_parts(false, true, false, false),
497            KeyModifiers::CONTROL
498        );
499        assert_eq!(
500            KeyModifiers::from_parts(false, false, true, false),
501            KeyModifiers::ALT
502        );
503        assert_eq!(
504            KeyModifiers::from_parts(false, false, false, true),
505            KeyModifiers::SUPER
506        );
507        assert_eq!(
508            KeyModifiers::from_parts(true, true, true, true),
509            KeyModifiers::SHIFT | KeyModifiers::CONTROL | KeyModifiers::ALT | KeyModifiers::SUPER
510        );
511    }
512
513    #[test]
514    fn test_event_construction() {
515        let key_event = KeyEvent::new(KeyCode::Char('a'), KeyModifiers::SHIFT);
516        let event = Event::Key(key_event);
517
518        if let Event::Key(ke) = event {
519            assert_eq!(ke.code, KeyCode::Char('a'));
520            assert!(ke.modifiers.contains(KeyModifiers::SHIFT));
521            assert_eq!(ke.kind, KeyEventKind::Press);
522        } else {
523            panic!("Expected Event::Key");
524        }
525    }
526
527    #[test]
528    fn test_key_event_kind_helpers() {
529        let press = KeyEvent::new(KeyCode::Char('x'), KeyModifiers::NONE);
530        assert_eq!(press.kind, KeyEventKind::Press);
531        assert!(press.is_down());
532
533        let repeat =
534            KeyEvent::with_kind(KeyCode::Char('x'), KeyModifiers::NONE, KeyEventKind::Repeat);
535        assert!(repeat.is_down());
536
537        let release = KeyEvent::with_kind(
538            KeyCode::Char('x'),
539            KeyModifiers::NONE,
540            KeyEventKind::Release,
541        );
542        assert!(!release.is_down());
543    }
544
545    #[test]
546    fn test_key_state_tracks_held_keys() {
547        let mut state = KeyState::new();
548        assert!(!state.is_held(KeyCode::Left, KeyLocation::Standard));
549
550        state.apply(KeyEvent::new(KeyCode::Left, KeyModifiers::NONE));
551        assert!(state.is_held(KeyCode::Left, KeyLocation::Standard));
552
553        // Repeat keeps it held.
554        state.apply(KeyEvent::with_kind(
555            KeyCode::Left,
556            KeyModifiers::NONE,
557            KeyEventKind::Repeat,
558        ));
559        assert!(state.is_held(KeyCode::Left, KeyLocation::Standard));
560
561        state.apply(KeyEvent::with_kind(
562            KeyCode::Left,
563            KeyModifiers::NONE,
564            KeyEventKind::Release,
565        ));
566        assert!(!state.is_held(KeyCode::Left, KeyLocation::Standard));
567    }
568
569    #[test]
570    fn test_key_state_distinguishes_numpad_from_standard() {
571        let mut state = KeyState::new();
572        state.apply(KeyEvent::with_location(
573            KeyCode::Char('8'),
574            KeyModifiers::NONE,
575            KeyEventKind::Press,
576            KeyLocation::Numpad,
577        ));
578        assert!(state.is_held(KeyCode::Char('8'), KeyLocation::Numpad));
579        assert!(!state.is_held(KeyCode::Char('8'), KeyLocation::Standard));
580
581        state.apply(KeyEvent::new(KeyCode::Char('8'), KeyModifiers::NONE));
582        assert!(state.is_held(KeyCode::Char('8'), KeyLocation::Standard));
583        assert!(state.is_held(KeyCode::Char('8'), KeyLocation::Numpad));
584
585        state.apply(KeyEvent::with_kind(
586            KeyCode::Char('8'),
587            KeyModifiers::NONE,
588            KeyEventKind::Release,
589        ));
590        assert!(!state.is_held(KeyCode::Char('8'), KeyLocation::Standard));
591        assert!(state.is_held(KeyCode::Char('8'), KeyLocation::Numpad));
592    }
593
594    #[test]
595    fn test_key_state_apply_event_ignores_non_key() {
596        let mut state = KeyState::new();
597        state.apply_event(&Event::Resize(1, 1));
598        assert!(state.held().next().is_none());
599        state.apply_event(&Event::Key(KeyEvent::new(KeyCode::Up, KeyModifiers::NONE)));
600        assert!(state.is_held(KeyCode::Up, KeyLocation::Standard));
601    }
602
603    #[test]
604    fn test_key_state_apply_event_clears_on_focus_lost() {
605        let mut state = KeyState::new();
606        state.apply_event(&Event::Key(KeyEvent::new(KeyCode::Up, KeyModifiers::NONE)));
607        state.apply_event(&Event::Key(KeyEvent::new(
608            KeyCode::Left,
609            KeyModifiers::NONE,
610        )));
611        assert!(state.is_held(KeyCode::Up, KeyLocation::Standard));
612        assert!(state.is_held(KeyCode::Left, KeyLocation::Standard));
613
614        // Focus loss clears every held key at once, simulating alt-tabbing away while keys are
615        // down: the release that would normally clear them is delivered to whichever window/app
616        // gains focus instead, never to us.
617        state.apply_event(&Event::FocusLost);
618        assert!(!state.is_held(KeyCode::Up, KeyLocation::Standard));
619        assert!(!state.is_held(KeyCode::Left, KeyLocation::Standard));
620        assert!(state.held().next().is_none());
621    }
622
623    #[test]
624    fn test_key_state_clear() {
625        let mut state = KeyState::new();
626        state.apply(KeyEvent::new(KeyCode::Left, KeyModifiers::NONE));
627        state.apply(KeyEvent::new(KeyCode::Right, KeyModifiers::NONE));
628        assert_eq!(state.held().count(), 2);
629
630        state.clear();
631        assert!(state.held().next().is_none());
632        assert!(!state.is_held(KeyCode::Left, KeyLocation::Standard));
633        assert!(!state.is_held(KeyCode::Right, KeyLocation::Standard));
634
635        // Clearing an already-empty state is a harmless no-op.
636        state.clear();
637        assert!(state.held().next().is_none());
638    }
639
640    #[test]
641    fn test_key_state_held_is_in_first_pressed_order() {
642        let mut state = KeyState::new();
643        state.apply(KeyEvent::new(KeyCode::Left, KeyModifiers::NONE));
644        state.apply(KeyEvent::new(KeyCode::Up, KeyModifiers::NONE));
645        state.apply(KeyEvent::new(KeyCode::Right, KeyModifiers::NONE));
646        assert_eq!(
647            state.held().collect::<Vec<_>>(),
648            vec![
649                (KeyCode::Left, KeyLocation::Standard),
650                (KeyCode::Up, KeyLocation::Standard),
651                (KeyCode::Right, KeyLocation::Standard),
652            ]
653        );
654
655        // Releasing and re-pressing a key moves it to the back: it is first-pressed order, not
656        // insertion-slot order.
657        state.apply(KeyEvent::with_kind(
658            KeyCode::Left,
659            KeyModifiers::NONE,
660            KeyEventKind::Release,
661        ));
662        state.apply(KeyEvent::new(KeyCode::Left, KeyModifiers::NONE));
663        assert_eq!(
664            state.held().collect::<Vec<_>>(),
665            vec![
666                (KeyCode::Up, KeyLocation::Standard),
667                (KeyCode::Right, KeyLocation::Standard),
668                (KeyCode::Left, KeyLocation::Standard),
669            ]
670        );
671    }
672
673    #[test]
674    fn test_key_state_release_of_unpressed_key_is_a_no_op() {
675        let mut state = KeyState::new();
676        state.apply(KeyEvent::new(KeyCode::Up, KeyModifiers::NONE));
677
678        // Releasing a key that was never pressed must not disturb keys that are actually held.
679        state.apply(KeyEvent::with_kind(
680            KeyCode::Down,
681            KeyModifiers::NONE,
682            KeyEventKind::Release,
683        ));
684        assert!(!state.is_held(KeyCode::Down, KeyLocation::Standard));
685        assert!(state.is_held(KeyCode::Up, KeyLocation::Standard));
686        assert_eq!(state.held().count(), 1);
687    }
688
689    #[cfg(feature = "serde")]
690    #[test]
691    fn key_event_and_key_state_round_trip_through_serde_json() {
692        let event = KeyEvent::with_location(
693            KeyCode::Modifier(ModifierKey::Shift),
694            KeyModifiers::SHIFT | KeyModifiers::ALT,
695            KeyEventKind::Repeat,
696            KeyLocation::Left,
697        );
698        let json = serde_json::to_string(&event).expect("serialize");
699        assert_eq!(
700            serde_json::from_str::<KeyEvent>(&json).expect("deserialize"),
701            event
702        );
703
704        let mut state = KeyState::new();
705        state.apply(KeyEvent::new(KeyCode::Up, KeyModifiers::NONE));
706        let json = serde_json::to_string(&state).expect("serialize");
707        assert_eq!(
708            serde_json::from_str::<KeyState>(&json)
709                .expect("deserialize")
710                .held()
711                .collect::<Vec<_>>(),
712            state.held().collect::<Vec<_>>()
713        );
714    }
715
716    #[test]
717    fn test_key_state_double_press_without_release_does_not_duplicate() {
718        let mut state = KeyState::new();
719        state.apply(KeyEvent::new(KeyCode::Up, KeyModifiers::NONE));
720        state.apply(KeyEvent::new(KeyCode::Up, KeyModifiers::NONE));
721        assert_eq!(state.held().count(), 1);
722
723        // A single release still clears it: the dedup guard didn't push a second entry that a
724        // release would need to match twice.
725        state.apply(KeyEvent::with_kind(
726            KeyCode::Up,
727            KeyModifiers::NONE,
728            KeyEventKind::Release,
729        ));
730        assert!(!state.is_held(KeyCode::Up, KeyLocation::Standard));
731        assert!(state.held().next().is_none());
732    }
733}