Skip to main content

slt/
event.rs

1/// A terminal input event.
2///
3/// Produced each frame by the run loop and passed to your UI closure via
4/// [`crate::Context`]. Use the helper methods on `Context` (e.g., `key()`,
5/// `key_code()`) rather than matching on this type directly.
6#[derive(Debug, Clone, PartialEq, Eq)]
7pub enum Event {
8    /// A keyboard event.
9    Key(KeyEvent),
10    /// A mouse event (requires `mouse: true` in [`crate::RunConfig`]).
11    Mouse(MouseEvent),
12    /// The terminal was resized to the given `(columns, rows)`.
13    Resize(u32, u32),
14}
15
16/// A keyboard event with key code and modifiers.
17#[derive(Debug, Clone, PartialEq, Eq)]
18pub struct KeyEvent {
19    /// The key that was pressed.
20    pub code: KeyCode,
21    /// Modifier keys held at the time of the press.
22    pub modifiers: KeyModifiers,
23}
24
25/// Key identifier.
26///
27/// Covers printable characters, control keys, arrow keys, function keys,
28/// and navigation keys. Unrecognized keys are silently dropped by the
29/// crossterm conversion layer.
30#[derive(Debug, Clone, PartialEq, Eq)]
31pub enum KeyCode {
32    /// A printable character (letter, digit, symbol, space, etc.).
33    Char(char),
34    /// Enter / Return key.
35    Enter,
36    /// Backspace key.
37    Backspace,
38    /// Tab key (forward tab).
39    Tab,
40    /// Shift+Tab (back tab).
41    BackTab,
42    /// Escape key.
43    Esc,
44    /// Up arrow key.
45    Up,
46    /// Down arrow key.
47    Down,
48    /// Left arrow key.
49    Left,
50    /// Right arrow key.
51    Right,
52    /// Home key.
53    Home,
54    /// End key.
55    End,
56    /// Page Up key.
57    PageUp,
58    /// Page Down key.
59    PageDown,
60    /// Delete (forward delete) key.
61    Delete,
62    /// Function key `F1`..`F12` (and beyond). The inner `u8` is the number.
63    F(u8),
64}
65
66/// Modifier keys held during a key press.
67///
68/// Stored as bitflags in a `u8`. Check individual modifiers with
69/// [`KeyModifiers::contains`].
70#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
71pub struct KeyModifiers(pub u8);
72
73impl KeyModifiers {
74    /// No modifier keys held.
75    pub const NONE: Self = Self(0);
76    /// Shift key held.
77    pub const SHIFT: Self = Self(1 << 0);
78    /// Control key held.
79    pub const CONTROL: Self = Self(1 << 1);
80    /// Alt / Option key held.
81    pub const ALT: Self = Self(1 << 2);
82
83    /// Returns `true` if all bits in `other` are set in `self`.
84    #[inline]
85    pub fn contains(self, other: Self) -> bool {
86        (self.0 & other.0) == other.0
87    }
88}
89
90/// A mouse event with position and kind.
91///
92/// Coordinates are zero-based terminal columns (`x`) and rows (`y`).
93/// Mouse events are only produced when `mouse: true` is set in
94/// [`crate::RunConfig`].
95#[derive(Debug, Clone, PartialEq, Eq)]
96pub struct MouseEvent {
97    /// The type of mouse action that occurred.
98    pub kind: MouseKind,
99    /// Column (horizontal position), zero-based.
100    pub x: u32,
101    /// Row (vertical position), zero-based.
102    pub y: u32,
103    /// Modifier keys held at the time of the event.
104    pub modifiers: KeyModifiers,
105}
106
107/// The type of mouse event.
108#[derive(Debug, Clone, PartialEq, Eq)]
109pub enum MouseKind {
110    /// A mouse button was pressed.
111    Down(MouseButton),
112    /// A mouse button was released.
113    Up(MouseButton),
114    /// The mouse was moved while a button was held.
115    Drag(MouseButton),
116    /// The scroll wheel was rotated upward.
117    ScrollUp,
118    /// The scroll wheel was rotated downward.
119    ScrollDown,
120    /// The mouse was moved without any button held.
121    Moved,
122}
123
124/// Mouse button identifier.
125#[derive(Debug, Clone, Copy, PartialEq, Eq)]
126pub enum MouseButton {
127    /// Primary (left) mouse button.
128    Left,
129    /// Secondary (right) mouse button.
130    Right,
131    /// Middle mouse button (scroll wheel click).
132    Middle,
133}
134
135fn convert_modifiers(modifiers: crossterm::event::KeyModifiers) -> KeyModifiers {
136    let mut out = KeyModifiers::NONE;
137    if modifiers.contains(crossterm::event::KeyModifiers::SHIFT) {
138        out.0 |= KeyModifiers::SHIFT.0;
139    }
140    if modifiers.contains(crossterm::event::KeyModifiers::CONTROL) {
141        out.0 |= KeyModifiers::CONTROL.0;
142    }
143    if modifiers.contains(crossterm::event::KeyModifiers::ALT) {
144        out.0 |= KeyModifiers::ALT.0;
145    }
146    out
147}
148
149fn convert_button(button: crossterm::event::MouseButton) -> MouseButton {
150    match button {
151        crossterm::event::MouseButton::Left => MouseButton::Left,
152        crossterm::event::MouseButton::Right => MouseButton::Right,
153        crossterm::event::MouseButton::Middle => MouseButton::Middle,
154    }
155}
156
157// ── crossterm conversions ────────────────────────────────────────────
158
159/// Convert a raw crossterm event into our lightweight [`Event`].
160/// Returns `None` for event kinds we don't handle (mouse, paste, …).
161pub(crate) fn from_crossterm(raw: crossterm::event::Event) -> Option<Event> {
162    match raw {
163        crossterm::event::Event::Key(k) => {
164            // Only handle key-press (not repeat/release) to avoid double-fire.
165            if k.kind != crossterm::event::KeyEventKind::Press {
166                return None;
167            }
168            let code = match k.code {
169                crossterm::event::KeyCode::Char(c) => KeyCode::Char(c),
170                crossterm::event::KeyCode::Enter => KeyCode::Enter,
171                crossterm::event::KeyCode::Backspace => KeyCode::Backspace,
172                crossterm::event::KeyCode::Tab => KeyCode::Tab,
173                crossterm::event::KeyCode::BackTab => KeyCode::BackTab,
174                crossterm::event::KeyCode::Esc => KeyCode::Esc,
175                crossterm::event::KeyCode::Up => KeyCode::Up,
176                crossterm::event::KeyCode::Down => KeyCode::Down,
177                crossterm::event::KeyCode::Left => KeyCode::Left,
178                crossterm::event::KeyCode::Right => KeyCode::Right,
179                crossterm::event::KeyCode::Home => KeyCode::Home,
180                crossterm::event::KeyCode::End => KeyCode::End,
181                crossterm::event::KeyCode::PageUp => KeyCode::PageUp,
182                crossterm::event::KeyCode::PageDown => KeyCode::PageDown,
183                crossterm::event::KeyCode::Delete => KeyCode::Delete,
184                crossterm::event::KeyCode::F(n) => KeyCode::F(n),
185                _ => return None,
186            };
187            let modifiers = convert_modifiers(k.modifiers);
188            Some(Event::Key(KeyEvent { code, modifiers }))
189        }
190        crossterm::event::Event::Mouse(m) => {
191            let kind = match m.kind {
192                crossterm::event::MouseEventKind::Down(btn) => MouseKind::Down(convert_button(btn)),
193                crossterm::event::MouseEventKind::Up(btn) => MouseKind::Up(convert_button(btn)),
194                crossterm::event::MouseEventKind::Drag(btn) => MouseKind::Drag(convert_button(btn)),
195                crossterm::event::MouseEventKind::Moved => MouseKind::Moved,
196                crossterm::event::MouseEventKind::ScrollUp => MouseKind::ScrollUp,
197                crossterm::event::MouseEventKind::ScrollDown => MouseKind::ScrollDown,
198                _ => return None,
199            };
200
201            Some(Event::Mouse(MouseEvent {
202                kind,
203                x: m.column as u32,
204                y: m.row as u32,
205                modifiers: convert_modifiers(m.modifiers),
206            }))
207        }
208        crossterm::event::Event::Resize(cols, rows) => {
209            Some(Event::Resize(cols as u32, rows as u32))
210        }
211        _ => None,
212    }
213}