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