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