Skip to main content

slt/
event.rs

1//! Terminal input events.
2//!
3//! This module defines the event types that SLT delivers to your UI closure
4//! each frame: keyboard, mouse, resize, paste, and focus events. In most
5//! cases you'll use the convenience methods on [`crate::Context`] (e.g.,
6//! [`Context::key`](crate::Context::key),
7//! [`Context::mouse_down`](crate::Context::mouse_down)) instead of matching
8//! on these types directly.
9
10#[cfg(feature = "crossterm")]
11use crossterm::event as crossterm_event;
12
13/// A terminal input event.
14///
15/// Produced each frame by the run loop and passed to your UI closure via
16/// [`crate::Context`]. Use the helper methods on `Context` (e.g., `key()`,
17/// `key_code()`) rather than matching on this type directly.
18#[non_exhaustive]
19#[derive(Debug, Clone, PartialEq, Eq)]
20pub enum Event {
21    /// A keyboard event.
22    Key(KeyEvent),
23    /// A mouse event (requires `mouse: true` in [`crate::RunConfig`]).
24    Mouse(MouseEvent),
25    /// The terminal was resized to the given `(columns, rows)`.
26    Resize(u32, u32),
27    /// Pasted text (bracketed paste). May contain newlines.
28    Paste(String),
29    /// The terminal window gained focus.
30    FocusGained,
31    /// The terminal window lost focus. Used to clear hover state.
32    FocusLost,
33}
34
35impl Event {
36    /// Create a key press event for a character.
37    pub fn key_char(c: char) -> Self {
38        Event::Key(KeyEvent {
39            code: KeyCode::Char(c),
40            modifiers: KeyModifiers::NONE,
41            kind: KeyEventKind::Press,
42        })
43    }
44
45    /// Create a key press event for a special key.
46    pub fn key(code: KeyCode) -> Self {
47        Event::Key(KeyEvent {
48            code,
49            modifiers: KeyModifiers::NONE,
50            kind: KeyEventKind::Press,
51        })
52    }
53
54    /// Create a key press event with Ctrl modifier.
55    pub fn key_ctrl(c: char) -> Self {
56        Event::Key(KeyEvent {
57            code: KeyCode::Char(c),
58            modifiers: KeyModifiers::CONTROL,
59            kind: KeyEventKind::Press,
60        })
61    }
62
63    /// Create a key press event with custom modifiers.
64    pub fn key_mod(code: KeyCode, modifiers: KeyModifiers) -> Self {
65        Event::Key(KeyEvent {
66            code,
67            modifiers,
68            kind: KeyEventKind::Press,
69        })
70    }
71
72    /// Create a terminal resize event.
73    pub fn resize(width: u32, height: u32) -> Self {
74        Event::Resize(width, height)
75    }
76
77    /// Create a left mouse click event at (x, y).
78    pub fn mouse_click(x: u32, y: u32) -> Self {
79        Event::Mouse(MouseEvent {
80            kind: MouseKind::Down(MouseButton::Left),
81            x,
82            y,
83            modifiers: KeyModifiers::NONE,
84            pixel_x: None,
85            pixel_y: None,
86        })
87    }
88
89    /// Create a mouse move event at the given position.
90    pub fn mouse_move(x: u32, y: u32) -> Self {
91        Event::Mouse(MouseEvent {
92            kind: MouseKind::Moved,
93            x,
94            y,
95            modifiers: KeyModifiers::NONE,
96            pixel_x: None,
97            pixel_y: None,
98        })
99    }
100
101    /// Create a left mouse drag event at (x, y).
102    pub fn mouse_drag(x: u32, y: u32) -> Self {
103        Event::Mouse(MouseEvent {
104            kind: MouseKind::Drag(MouseButton::Left),
105            x,
106            y,
107            modifiers: KeyModifiers::NONE,
108            pixel_x: None,
109            pixel_y: None,
110        })
111    }
112
113    /// Create a left mouse button release event at (x, y).
114    pub fn mouse_up(x: u32, y: u32) -> Self {
115        Event::Mouse(MouseEvent {
116            kind: MouseKind::Up(MouseButton::Left),
117            x,
118            y,
119            modifiers: KeyModifiers::NONE,
120            pixel_x: None,
121            pixel_y: None,
122        })
123    }
124
125    /// Create a scroll up event at the given position.
126    pub fn scroll_up(x: u32, y: u32) -> Self {
127        Event::Mouse(MouseEvent {
128            kind: MouseKind::ScrollUp,
129            x,
130            y,
131            modifiers: KeyModifiers::NONE,
132            pixel_x: None,
133            pixel_y: None,
134        })
135    }
136
137    /// Create a scroll down event at the given position.
138    pub fn scroll_down(x: u32, y: u32) -> Self {
139        Event::Mouse(MouseEvent {
140            kind: MouseKind::ScrollDown,
141            x,
142            y,
143            modifiers: KeyModifiers::NONE,
144            pixel_x: None,
145            pixel_y: None,
146        })
147    }
148
149    /// Create a key release event for a character.
150    pub fn key_release(c: char) -> Self {
151        Event::Key(KeyEvent {
152            code: KeyCode::Char(c),
153            modifiers: KeyModifiers::NONE,
154            kind: KeyEventKind::Release,
155        })
156    }
157
158    /// Create a paste event with the given text.
159    pub fn paste(text: impl Into<String>) -> Self {
160        Event::Paste(text.into())
161    }
162
163    /// Returns the key event data if this is a `Key` variant.
164    pub fn as_key(&self) -> Option<&KeyEvent> {
165        match self {
166            Event::Key(k) => Some(k),
167            _ => None,
168        }
169    }
170
171    /// Returns the mouse event data if this is a `Mouse` variant.
172    pub fn as_mouse(&self) -> Option<&MouseEvent> {
173        match self {
174            Event::Mouse(m) => Some(m),
175            _ => None,
176        }
177    }
178
179    /// Returns `(columns, rows)` if this is a `Resize` variant.
180    pub fn as_resize(&self) -> Option<(u32, u32)> {
181        match self {
182            Event::Resize(w, h) => Some((*w, *h)),
183            _ => None,
184        }
185    }
186
187    /// Returns the pasted text if this is a `Paste` variant.
188    pub fn as_paste(&self) -> Option<&str> {
189        match self {
190            Event::Paste(s) => Some(s),
191            _ => None,
192        }
193    }
194
195    /// Returns `true` if this is a `Key` event.
196    pub fn is_key(&self) -> bool {
197        matches!(self, Event::Key(_))
198    }
199
200    /// Returns `true` if this is a `Mouse` event.
201    pub fn is_mouse(&self) -> bool {
202        matches!(self, Event::Mouse(_))
203    }
204}
205
206/// A keyboard event with key code and modifiers.
207#[non_exhaustive]
208#[derive(Debug, Clone, PartialEq, Eq)]
209pub struct KeyEvent {
210    /// The key that was pressed.
211    pub code: KeyCode,
212    /// Modifier keys held at the time of the press.
213    pub modifiers: KeyModifiers,
214    /// The type of key event. Always `Press` without Kitty keyboard protocol.
215    pub kind: KeyEventKind,
216}
217
218impl KeyEvent {
219    /// Returns `true` if this is a press of the given character (no modifiers).
220    pub fn is_char(&self, c: char) -> bool {
221        self.code == KeyCode::Char(c)
222            && self.modifiers == KeyModifiers::NONE
223            && self.kind == KeyEventKind::Press
224    }
225
226    /// Returns `true` if this is Ctrl+`c`.
227    pub fn is_ctrl_char(&self, c: char) -> bool {
228        self.code == KeyCode::Char(c)
229            && self.modifiers == KeyModifiers::CONTROL
230            && self.kind == KeyEventKind::Press
231    }
232
233    /// Returns `true` if this is a press of the given key code (no modifiers).
234    pub fn is_code(&self, code: KeyCode) -> bool {
235        self.code == code
236            && self.modifiers == KeyModifiers::NONE
237            && self.kind == KeyEventKind::Press
238    }
239}
240
241/// The type of key event.
242#[non_exhaustive]
243#[derive(Debug, Clone, Copy, PartialEq, Eq)]
244pub enum KeyEventKind {
245    /// Key was pressed.
246    Press,
247    /// Key was released (requires Kitty keyboard protocol).
248    Release,
249    /// Key is being held/repeated (requires Kitty keyboard protocol).
250    Repeat,
251}
252
253/// Key identifier.
254///
255/// Covers printable characters, control keys, arrow keys, function keys,
256/// and navigation keys. Unrecognized keys are silently dropped by the
257/// crossterm conversion layer.
258#[non_exhaustive]
259#[derive(Debug, Clone, PartialEq, Eq)]
260pub enum KeyCode {
261    /// A printable character (letter, digit, symbol, space, etc.).
262    Char(char),
263    /// Enter / Return key.
264    Enter,
265    /// Backspace key.
266    Backspace,
267    /// Tab key (forward tab).
268    Tab,
269    /// Shift+Tab (back tab).
270    BackTab,
271    /// Escape key.
272    Esc,
273    /// Up arrow key.
274    Up,
275    /// Down arrow key.
276    Down,
277    /// Left arrow key.
278    Left,
279    /// Right arrow key.
280    Right,
281    /// Home key.
282    Home,
283    /// End key.
284    End,
285    /// Page Up key.
286    PageUp,
287    /// Page Down key.
288    PageDown,
289    /// Delete (forward delete) key.
290    Delete,
291    /// Insert key.
292    Insert,
293    /// Null key (Ctrl+Space on some terminals).
294    Null,
295    /// Caps Lock key (Kitty keyboard protocol only).
296    CapsLock,
297    /// Scroll Lock key (Kitty keyboard protocol only).
298    ScrollLock,
299    /// Num Lock key (Kitty keyboard protocol only).
300    NumLock,
301    /// Print Screen key (Kitty keyboard protocol only).
302    PrintScreen,
303    /// Pause/Break key (Kitty keyboard protocol only).
304    Pause,
305    /// Menu / context menu key.
306    Menu,
307    /// Keypad center key (numpad 5 without NumLock).
308    KeypadBegin,
309    /// Function key `F1`..`F12` (and beyond). The inner `u8` is the number.
310    F(u8),
311    /// A modifier key pressed on its own (a bare Ctrl/Shift/Alt/Super press
312    /// or release, with no accompanying character).
313    ///
314    /// Only delivered when the Kitty keyboard protocol is active **and**
315    /// [`RunConfig::report_all_keys(true)`](crate::RunConfig::report_all_keys)
316    /// was set, on a supporting terminal (kitty, Ghostty, WezTerm). Most
317    /// terminals never emit these, so handlers must treat them as optional.
318    ///
319    /// Since 0.21.0.
320    Modifier(ModifierKey),
321}
322
323/// A modifier key reported on its own (no character), e.g. a bare Ctrl press.
324///
325/// Mirrors crossterm's `ModifierKeyCode` with SLT naming. Only delivered when
326/// the Kitty keyboard protocol is active **and**
327/// [`RunConfig::report_all_keys(true)`](crate::RunConfig::report_all_keys) was
328/// set, on a supporting terminal (kitty, Ghostty, WezTerm). Most terminals
329/// never emit these.
330///
331/// Useful for vi-style leader/chord overlays ("Ctrl is being held") and
332/// hold-to-repeat affordances keyed off a modifier-down → modifier-up window.
333///
334/// Since 0.21.0.
335///
336/// # Example
337///
338/// ```no_run
339/// use slt::{KeyCode, ModifierKey, RunConfig};
340///
341/// // `report_all_keys` only has an effect with `kitty_keyboard` enabled.
342/// let cfg = RunConfig::default().kitty_keyboard(true).report_all_keys(true);
343/// slt::run_with(cfg, |ui| {
344///     if ui.key_code(KeyCode::Modifier(ModifierKey::LeftCtrl)) {
345///         ui.text("Left Ctrl is down");
346///     }
347/// })
348/// .unwrap();
349/// ```
350#[non_exhaustive]
351#[derive(Debug, Clone, Copy, PartialEq, Eq)]
352pub enum ModifierKey {
353    /// Left Shift key.
354    LeftShift,
355    /// Left Control key.
356    LeftCtrl,
357    /// Left Alt / Option key.
358    LeftAlt,
359    /// Left Super key (Cmd on macOS, Win on Windows).
360    LeftSuper,
361    /// Right Shift key.
362    RightShift,
363    /// Right Control key.
364    RightCtrl,
365    /// Right Alt / Option key.
366    RightAlt,
367    /// Right Super key (Cmd on macOS, Win on Windows).
368    RightSuper,
369    /// Left Hyper key.
370    LeftHyper,
371    /// Left Meta key.
372    LeftMeta,
373    /// Right Hyper key.
374    RightHyper,
375    /// Right Meta key.
376    RightMeta,
377    /// ISO Level 3 Shift key (AltGr).
378    IsoLevel3Shift,
379    /// ISO Level 5 Shift key.
380    IsoLevel5Shift,
381}
382
383/// Modifier keys held during a key press.
384///
385/// Stored as bitflags in a `u8`. Check individual modifiers with
386/// [`KeyModifiers::contains`].
387#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
388pub struct KeyModifiers(pub u8);
389
390impl KeyModifiers {
391    /// No modifier keys held.
392    pub const NONE: Self = Self(0);
393    /// Shift key held.
394    pub const SHIFT: Self = Self(1 << 0);
395    /// Control key held.
396    pub const CONTROL: Self = Self(1 << 1);
397    /// Alt / Option key held.
398    pub const ALT: Self = Self(1 << 2);
399    /// Super key (Cmd on macOS, Win on Windows). Kitty keyboard protocol only.
400    pub const SUPER: Self = Self(1 << 3);
401    /// Hyper modifier. Kitty keyboard protocol only.
402    pub const HYPER: Self = Self(1 << 4);
403    /// Meta modifier. Kitty keyboard protocol only.
404    pub const META: Self = Self(1 << 5);
405
406    /// Returns `true` if all bits in `other` are set in `self`.
407    #[inline]
408    pub fn contains(self, other: Self) -> bool {
409        (self.0 & other.0) == other.0
410    }
411}
412
413/// A mouse event with position and kind.
414///
415/// Coordinates are zero-based terminal columns (`x`) and rows (`y`).
416/// `pixel_x` and `pixel_y` are reserved for future sub-cell precision
417/// (e.g. Kitty pixel mouse protocol, WASM backend); they are currently
418/// always `None` with the crossterm backend, since SGR 1006 only reports
419/// cell coordinates and crossterm 0.28 has no pixel mouse fields.
420/// Mouse events are only produced when `mouse: true` is set in
421/// [`crate::RunConfig`].
422#[non_exhaustive]
423#[derive(Debug, Clone, PartialEq, Eq)]
424pub struct MouseEvent {
425    /// The type of mouse action that occurred.
426    pub kind: MouseKind,
427    /// Column (horizontal position), zero-based.
428    pub x: u32,
429    /// Row (vertical position), zero-based.
430    pub y: u32,
431    /// Modifier keys held at the time of the event.
432    pub modifiers: KeyModifiers,
433    /// Pixel-level x coordinate (reserved).
434    ///
435    /// Currently always `None` with the crossterm backend; populated only
436    /// when a Kitty-capable or WASM backend provides sub-cell precision.
437    pub pixel_x: Option<u16>,
438    /// Pixel-level y coordinate (reserved).
439    ///
440    /// Currently always `None` with the crossterm backend; populated only
441    /// when a Kitty-capable or WASM backend provides sub-cell precision.
442    pub pixel_y: Option<u16>,
443}
444
445impl MouseEvent {
446    /// Create a new MouseEvent with all fields.
447    pub fn new(
448        kind: MouseKind,
449        x: u32,
450        y: u32,
451        modifiers: KeyModifiers,
452        pixel_x: Option<u16>,
453        pixel_y: Option<u16>,
454    ) -> Self {
455        Self {
456            kind,
457            x,
458            y,
459            modifiers,
460            pixel_x,
461            pixel_y,
462        }
463    }
464
465    /// Returns true if this is a scroll event.
466    pub fn is_scroll(&self) -> bool {
467        matches!(
468            self.kind,
469            MouseKind::ScrollUp
470                | MouseKind::ScrollDown
471                | MouseKind::ScrollLeft
472                | MouseKind::ScrollRight
473        )
474    }
475}
476
477/// The type of mouse event.
478#[non_exhaustive]
479#[derive(Debug, Clone, PartialEq, Eq)]
480pub enum MouseKind {
481    /// A mouse button was pressed.
482    Down(MouseButton),
483    /// A mouse button was released.
484    Up(MouseButton),
485    /// The mouse was moved while a button was held.
486    Drag(MouseButton),
487    /// The scroll wheel was rotated upward.
488    ScrollUp,
489    /// The scroll wheel was rotated downward.
490    ScrollDown,
491    /// The scroll wheel was rotated leftward (horizontal scroll).
492    ScrollLeft,
493    /// The scroll wheel was rotated rightward (horizontal scroll).
494    ScrollRight,
495    /// The mouse was moved without any button held.
496    Moved,
497}
498
499/// Mouse button identifier.
500#[non_exhaustive]
501#[derive(Debug, Clone, Copy, PartialEq, Eq)]
502pub enum MouseButton {
503    /// Primary (left) mouse button.
504    Left,
505    /// Secondary (right) mouse button.
506    Right,
507    /// Middle mouse button (scroll wheel click).
508    Middle,
509}
510
511#[cfg(feature = "crossterm")]
512fn convert_modifiers(modifiers: crossterm_event::KeyModifiers) -> KeyModifiers {
513    let mut out = KeyModifiers::NONE;
514    if modifiers.contains(crossterm_event::KeyModifiers::SHIFT) {
515        out.0 |= KeyModifiers::SHIFT.0;
516    }
517    if modifiers.contains(crossterm_event::KeyModifiers::CONTROL) {
518        out.0 |= KeyModifiers::CONTROL.0;
519    }
520    if modifiers.contains(crossterm_event::KeyModifiers::ALT) {
521        out.0 |= KeyModifiers::ALT.0;
522    }
523    if modifiers.contains(crossterm_event::KeyModifiers::SUPER) {
524        out.0 |= KeyModifiers::SUPER.0;
525    }
526    if modifiers.contains(crossterm_event::KeyModifiers::HYPER) {
527        out.0 |= KeyModifiers::HYPER.0;
528    }
529    if modifiers.contains(crossterm_event::KeyModifiers::META) {
530        out.0 |= KeyModifiers::META.0;
531    }
532    out
533}
534
535#[cfg(feature = "crossterm")]
536fn convert_modifier_key(mk: crossterm_event::ModifierKeyCode) -> ModifierKey {
537    use crossterm_event::ModifierKeyCode as C;
538    match mk {
539        C::LeftShift => ModifierKey::LeftShift,
540        C::LeftControl => ModifierKey::LeftCtrl,
541        C::LeftAlt => ModifierKey::LeftAlt,
542        C::LeftSuper => ModifierKey::LeftSuper,
543        C::RightShift => ModifierKey::RightShift,
544        C::RightControl => ModifierKey::RightCtrl,
545        C::RightAlt => ModifierKey::RightAlt,
546        C::RightSuper => ModifierKey::RightSuper,
547        C::LeftHyper => ModifierKey::LeftHyper,
548        C::LeftMeta => ModifierKey::LeftMeta,
549        C::RightHyper => ModifierKey::RightHyper,
550        C::RightMeta => ModifierKey::RightMeta,
551        C::IsoLevel3Shift => ModifierKey::IsoLevel3Shift,
552        C::IsoLevel5Shift => ModifierKey::IsoLevel5Shift,
553    }
554}
555
556#[cfg(feature = "crossterm")]
557fn convert_button(button: crossterm_event::MouseButton) -> MouseButton {
558    match button {
559        crossterm_event::MouseButton::Left => MouseButton::Left,
560        crossterm_event::MouseButton::Right => MouseButton::Right,
561        crossterm_event::MouseButton::Middle => MouseButton::Middle,
562    }
563}
564
565// ── crossterm conversions ────────────────────────────────────────────
566
567/// Convert a raw crossterm event into our lightweight [`Event`].
568/// Returns `None` for event kinds we don't handle.
569#[cfg(feature = "crossterm")]
570pub(crate) fn from_crossterm(raw: crossterm_event::Event) -> Option<Event> {
571    match raw {
572        crossterm_event::Event::Key(k) => {
573            let code = match k.code {
574                crossterm_event::KeyCode::Char(c) => KeyCode::Char(c),
575                crossterm_event::KeyCode::Enter => KeyCode::Enter,
576                crossterm_event::KeyCode::Backspace => KeyCode::Backspace,
577                crossterm_event::KeyCode::Tab => KeyCode::Tab,
578                crossterm_event::KeyCode::BackTab => KeyCode::BackTab,
579                crossterm_event::KeyCode::Esc => KeyCode::Esc,
580                crossterm_event::KeyCode::Up => KeyCode::Up,
581                crossterm_event::KeyCode::Down => KeyCode::Down,
582                crossterm_event::KeyCode::Left => KeyCode::Left,
583                crossterm_event::KeyCode::Right => KeyCode::Right,
584                crossterm_event::KeyCode::Home => KeyCode::Home,
585                crossterm_event::KeyCode::End => KeyCode::End,
586                crossterm_event::KeyCode::PageUp => KeyCode::PageUp,
587                crossterm_event::KeyCode::PageDown => KeyCode::PageDown,
588                crossterm_event::KeyCode::Delete => KeyCode::Delete,
589                crossterm_event::KeyCode::Insert => KeyCode::Insert,
590                crossterm_event::KeyCode::Null => KeyCode::Null,
591                crossterm_event::KeyCode::CapsLock => KeyCode::CapsLock,
592                crossterm_event::KeyCode::ScrollLock => KeyCode::ScrollLock,
593                crossterm_event::KeyCode::NumLock => KeyCode::NumLock,
594                crossterm_event::KeyCode::PrintScreen => KeyCode::PrintScreen,
595                crossterm_event::KeyCode::Pause => KeyCode::Pause,
596                crossterm_event::KeyCode::Menu => KeyCode::Menu,
597                crossterm_event::KeyCode::KeypadBegin => KeyCode::KeypadBegin,
598                crossterm_event::KeyCode::F(n) => KeyCode::F(n),
599                crossterm_event::KeyCode::Modifier(mk) => {
600                    KeyCode::Modifier(convert_modifier_key(mk))
601                }
602                _ => return None,
603            };
604            let modifiers = convert_modifiers(k.modifiers);
605            let kind = match k.kind {
606                crossterm_event::KeyEventKind::Press => KeyEventKind::Press,
607                crossterm_event::KeyEventKind::Repeat => KeyEventKind::Repeat,
608                crossterm_event::KeyEventKind::Release => KeyEventKind::Release,
609            };
610            Some(Event::Key(KeyEvent {
611                code,
612                modifiers,
613                kind,
614            }))
615        }
616        crossterm_event::Event::Mouse(m) => {
617            let kind = match m.kind {
618                crossterm_event::MouseEventKind::Down(btn) => MouseKind::Down(convert_button(btn)),
619                crossterm_event::MouseEventKind::Up(btn) => MouseKind::Up(convert_button(btn)),
620                crossterm_event::MouseEventKind::Drag(btn) => MouseKind::Drag(convert_button(btn)),
621                crossterm_event::MouseEventKind::Moved => MouseKind::Moved,
622                crossterm_event::MouseEventKind::ScrollUp => MouseKind::ScrollUp,
623                crossterm_event::MouseEventKind::ScrollDown => MouseKind::ScrollDown,
624                crossterm_event::MouseEventKind::ScrollLeft => MouseKind::ScrollLeft,
625                crossterm_event::MouseEventKind::ScrollRight => MouseKind::ScrollRight,
626            };
627
628            Some(Event::Mouse(MouseEvent {
629                kind,
630                x: m.column as u32,
631                y: m.row as u32,
632                modifiers: convert_modifiers(m.modifiers),
633                pixel_x: None,
634                pixel_y: None,
635            }))
636        }
637        crossterm_event::Event::Resize(cols, rows) => Some(Event::Resize(cols as u32, rows as u32)),
638        crossterm_event::Event::Paste(mut s) => {
639            // Defense-in-depth: cap bracketed-paste payload to bound memory and
640            // prevent O(n²) handling in text widgets. 1 MiB is far more than any
641            // reasonable interactive paste; larger payloads are truncated on a
642            // char boundary with an ellipsis so the user sees that it was cut.
643            const MAX_PASTE_BYTES: usize = 1 << 20;
644            if s.len() > MAX_PASTE_BYTES {
645                let mut end = MAX_PASTE_BYTES;
646                while end > 0 && !s.is_char_boundary(end) {
647                    end -= 1;
648                }
649                s.truncate(end);
650                s.push('…');
651            }
652            Some(Event::Paste(s))
653        }
654        crossterm_event::Event::FocusGained => Some(Event::FocusGained),
655        crossterm_event::Event::FocusLost => Some(Event::FocusLost),
656    }
657}
658
659#[cfg(test)]
660mod event_constructor_tests {
661    use super::*;
662
663    #[test]
664    fn test_key_char() {
665        let e = Event::key_char('q');
666        if let Event::Key(k) = e {
667            assert!(matches!(k.code, KeyCode::Char('q')));
668            assert_eq!(k.modifiers, KeyModifiers::NONE);
669            assert!(matches!(k.kind, KeyEventKind::Press));
670        } else {
671            panic!("Expected Key event");
672        }
673    }
674
675    #[test]
676    fn test_key() {
677        let e = Event::key(KeyCode::Enter);
678        if let Event::Key(k) = e {
679            assert!(matches!(k.code, KeyCode::Enter));
680            assert_eq!(k.modifiers, KeyModifiers::NONE);
681            assert!(matches!(k.kind, KeyEventKind::Press));
682        } else {
683            panic!("Expected Key event");
684        }
685    }
686
687    #[test]
688    fn test_key_ctrl() {
689        let e = Event::key_ctrl('s');
690        if let Event::Key(k) = e {
691            assert!(matches!(k.code, KeyCode::Char('s')));
692            assert_eq!(k.modifiers, KeyModifiers::CONTROL);
693            assert!(matches!(k.kind, KeyEventKind::Press));
694        } else {
695            panic!("Expected Key event");
696        }
697    }
698
699    #[test]
700    fn test_key_mod() {
701        let modifiers = KeyModifiers(KeyModifiers::SHIFT.0 | KeyModifiers::ALT.0);
702        let e = Event::key_mod(KeyCode::Tab, modifiers);
703        if let Event::Key(k) = e {
704            assert!(matches!(k.code, KeyCode::Tab));
705            assert_eq!(k.modifiers, modifiers);
706            assert!(matches!(k.kind, KeyEventKind::Press));
707        } else {
708            panic!("Expected Key event");
709        }
710    }
711
712    #[test]
713    fn test_resize() {
714        let e = Event::resize(80, 24);
715        assert!(matches!(e, Event::Resize(80, 24)));
716    }
717
718    #[test]
719    fn test_mouse_click() {
720        let e = Event::mouse_click(10, 5);
721        if let Event::Mouse(m) = e {
722            assert!(matches!(m.kind, MouseKind::Down(MouseButton::Left)));
723            assert_eq!(m.x, 10);
724            assert_eq!(m.y, 5);
725            assert_eq!(m.modifiers, KeyModifiers::NONE);
726        } else {
727            panic!("Expected Mouse event");
728        }
729    }
730
731    #[test]
732    fn test_mouse_move() {
733        let e = Event::mouse_move(10, 5);
734        if let Event::Mouse(m) = e {
735            assert!(matches!(m.kind, MouseKind::Moved));
736            assert_eq!(m.x, 10);
737            assert_eq!(m.y, 5);
738            assert_eq!(m.modifiers, KeyModifiers::NONE);
739        } else {
740            panic!("Expected Mouse event");
741        }
742    }
743
744    #[test]
745    fn test_paste() {
746        let e = Event::paste("hello");
747        assert!(matches!(e, Event::Paste(s) if s == "hello"));
748    }
749}
750
751#[cfg(all(test, feature = "crossterm"))]
752mod crossterm_conversion_tests {
753    use super::*;
754    use crossterm_event::{
755        Event as CtEvent, KeyCode as CtKeyCode, KeyEvent as CtKeyEvent,
756        KeyEventKind as CtKeyEventKind, KeyEventState, KeyModifiers as CtKeyModifiers,
757        ModifierKeyCode,
758    };
759
760    fn ct_modifier_event(mk: ModifierKeyCode, kind: CtKeyEventKind) -> CtEvent {
761        CtEvent::Key(CtKeyEvent {
762            code: CtKeyCode::Modifier(mk),
763            modifiers: CtKeyModifiers::NONE,
764            kind,
765            state: KeyEventState::NONE,
766        })
767    }
768
769    #[test]
770    fn from_crossterm_maps_modifier_key() {
771        let raw = ct_modifier_event(ModifierKeyCode::LeftControl, CtKeyEventKind::Press);
772        let converted = from_crossterm(raw);
773        match converted {
774            Some(Event::Key(k)) => {
775                assert_eq!(k.code, KeyCode::Modifier(ModifierKey::LeftCtrl));
776                assert!(matches!(k.kind, KeyEventKind::Press));
777            }
778            other => panic!("expected modifier key event, got {other:?}"),
779        }
780    }
781
782    #[test]
783    fn from_crossterm_modifier_release() {
784        let raw = ct_modifier_event(ModifierKeyCode::LeftControl, CtKeyEventKind::Release);
785        let converted = from_crossterm(raw);
786        match converted {
787            Some(Event::Key(k)) => {
788                assert_eq!(k.code, KeyCode::Modifier(ModifierKey::LeftCtrl));
789                assert!(matches!(k.kind, KeyEventKind::Release));
790            }
791            other => panic!("expected modifier release event, got {other:?}"),
792        }
793    }
794
795    #[test]
796    fn from_crossterm_modifier_key_exhaustive() {
797        // Guards against drift if crossterm reorders/extends ModifierKeyCode.
798        let cases: [(ModifierKeyCode, ModifierKey); 14] = [
799            (ModifierKeyCode::LeftShift, ModifierKey::LeftShift),
800            (ModifierKeyCode::LeftControl, ModifierKey::LeftCtrl),
801            (ModifierKeyCode::LeftAlt, ModifierKey::LeftAlt),
802            (ModifierKeyCode::LeftSuper, ModifierKey::LeftSuper),
803            (ModifierKeyCode::RightShift, ModifierKey::RightShift),
804            (ModifierKeyCode::RightControl, ModifierKey::RightCtrl),
805            (ModifierKeyCode::RightAlt, ModifierKey::RightAlt),
806            (ModifierKeyCode::RightSuper, ModifierKey::RightSuper),
807            (ModifierKeyCode::LeftHyper, ModifierKey::LeftHyper),
808            (ModifierKeyCode::LeftMeta, ModifierKey::LeftMeta),
809            (ModifierKeyCode::RightHyper, ModifierKey::RightHyper),
810            (ModifierKeyCode::RightMeta, ModifierKey::RightMeta),
811            (ModifierKeyCode::IsoLevel3Shift, ModifierKey::IsoLevel3Shift),
812            (ModifierKeyCode::IsoLevel5Shift, ModifierKey::IsoLevel5Shift),
813        ];
814        for (ct, expected) in cases {
815            let raw = ct_modifier_event(ct, CtKeyEventKind::Press);
816            match from_crossterm(raw) {
817                Some(Event::Key(k)) => {
818                    assert_eq!(k.code, KeyCode::Modifier(expected), "mismatch for {ct:?}")
819                }
820                other => panic!("expected modifier event for {ct:?}, got {other:?}"),
821            }
822        }
823    }
824}