Skip to main content

tui_lipan/core/
event.rs

1/// Mouse button.
2#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
3pub enum MouseButton {
4    /// Left mouse button.
5    Left,
6    /// Right mouse button.
7    Right,
8    /// Middle mouse button.
9    Middle,
10}
11
12/// High-level mouse event kind.
13#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
14pub enum MouseKind {
15    /// Button pressed.
16    Down(MouseButton),
17    /// Button released.
18    Up(MouseButton),
19    /// Mouse moved while a button is pressed.
20    Drag(MouseButton),
21    /// Mouse moved.
22    Moved,
23    /// Scroll up.
24    ScrollUp,
25    /// Scroll down.
26    ScrollDown,
27}
28
29/// A mouse event in terminal content coordinates.
30///
31/// In inline mode, the runtime removes the viewport's terminal-row offset before delivering the
32/// event to widgets and callbacks.
33#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
34pub struct MouseEvent {
35    /// X coordinate (column).
36    pub x: u16,
37    /// Y coordinate (row).
38    pub y: u16,
39    /// Event kind.
40    pub kind: MouseKind,
41    /// Modifiers.
42    pub mods: KeyMods,
43}
44
45/// A mouse-move event with both global and local coordinates.
46#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
47pub struct MouseMoveEvent {
48    /// Global X coordinate (column) in content-space.
49    pub x: u16,
50    /// Global Y coordinate (row) in content-space.
51    pub y: u16,
52    /// X coordinate relative to the target widget rect.
53    pub local_x: u16,
54    /// Y coordinate relative to the target widget rect.
55    pub local_y: u16,
56    /// Target widget width in cells.
57    pub target_w: u16,
58    /// Target widget height in cells.
59    pub target_h: u16,
60    /// Modifiers for the move event.
61    pub mods: KeyMods,
62}
63
64/// A mouse drag event with global and region-local coordinates.
65#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
66pub struct MouseDragEvent {
67    /// Global X coordinate where the drag started.
68    pub from_x: u16,
69    /// Global Y coordinate where the drag started.
70    pub from_y: u16,
71    /// Starting X coordinate relative to the target widget rect.
72    pub from_local_x: u16,
73    /// Starting Y coordinate relative to the target widget rect.
74    pub from_local_y: u16,
75    /// Current global X coordinate.
76    pub x: u16,
77    /// Current global Y coordinate.
78    pub y: u16,
79    /// Current X coordinate relative to the target widget rect.
80    pub local_x: u16,
81    /// Current Y coordinate relative to the target widget rect.
82    pub local_y: u16,
83    /// X delta since the previous drag tick.
84    pub delta_x: i16,
85    /// Y delta since the previous drag tick.
86    pub delta_y: i16,
87    /// Target widget width in cells.
88    pub target_w: u16,
89    /// Target widget height in cells.
90    pub target_h: u16,
91    /// Modifiers for the drag event.
92    pub mods: KeyMods,
93}
94
95/// Key modifiers.
96#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
97pub struct KeyMods {
98    /// Control modifier.
99    pub ctrl: bool,
100    /// Alt modifier.
101    pub alt: bool,
102    /// Shift modifier.
103    pub shift: bool,
104    /// Super modifier (Windows/Command/Meta).
105    pub super_key: bool,
106}
107
108impl KeyMods {
109    /// No modifiers.
110    pub const NONE: Self = Self {
111        ctrl: false,
112        alt: false,
113        shift: false,
114        super_key: false,
115    };
116
117    /// Shift modifier only.
118    pub const SHIFT: Self = Self {
119        ctrl: false,
120        alt: false,
121        shift: true,
122        super_key: false,
123    };
124
125    /// Control modifier only.
126    pub const CTRL: Self = Self {
127        ctrl: true,
128        alt: false,
129        shift: false,
130        super_key: false,
131    };
132
133    /// Alt modifier only.
134    pub const ALT: Self = Self {
135        ctrl: false,
136        alt: true,
137        shift: false,
138        super_key: false,
139    };
140
141    /// Returns true when no modifiers are set.
142    pub fn is_empty(&self) -> bool {
143        !self.ctrl && !self.alt && !self.shift && !self.super_key
144    }
145}
146
147/// Key code.
148#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
149pub enum KeyCode {
150    /// A unicode character.
151    Char(char),
152    /// Insert.
153    Insert,
154    /// Enter.
155    Enter,
156    /// Escape.
157    Esc,
158    /// Tab.
159    Tab,
160    /// Shift+Tab.
161    BackTab,
162    /// Backspace.
163    Backspace,
164    /// Delete.
165    Delete,
166    /// Home.
167    Home,
168    /// End.
169    End,
170    /// Page up.
171    PageUp,
172    /// Page down.
173    PageDown,
174    /// Arrow up.
175    Up,
176    /// Arrow down.
177    Down,
178    /// Arrow left.
179    Left,
180    /// Arrow right.
181    Right,
182    /// Function key.
183    F(u8),
184}
185
186/// A keyboard event.
187///
188/// Common pitfall: matching only `key.code` ignores modifiers. Prefer
189/// `key.is(...)` for plain-key checks or `key.is_with(...)` for exact
190/// key+modifier combinations.
191#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
192pub struct KeyEvent {
193    /// Key code.
194    pub code: KeyCode,
195    /// Modifiers.
196    pub mods: KeyMods,
197}
198
199impl KeyEvent {
200    /// Returns true when this key matches `code` with no modifiers.
201    pub fn is(&self, code: KeyCode) -> bool {
202        self.code == code && self.mods.is_empty()
203    }
204
205    /// Returns true when this key matches `code` with exactly `mods`.
206    pub fn is_with(&self, code: KeyCode, mods: KeyMods) -> bool {
207        self.code == code && self.mods == mods
208    }
209
210    /// Formats the key event into a readable string (e.g., "Ctrl+E" or "ctrl+e").
211    ///
212    /// If `lowercase` is true, the resulting string will be in lowercase.
213    pub fn to_formatted_string(&self, lowercase: bool) -> String {
214        let mut parts = Vec::new();
215
216        if self.mods.ctrl {
217            parts.push("Ctrl");
218        }
219        if self.mods.alt {
220            parts.push("Alt");
221        }
222        if self.mods.super_key {
223            parts.push("Super");
224        }
225        if self.mods.shift {
226            parts.push("Shift");
227        }
228
229        let code_str = match self.code {
230            KeyCode::Char(c) => {
231                if c == ' ' {
232                    "Space".to_string()
233                } else {
234                    c.to_uppercase().to_string()
235                }
236            }
237            KeyCode::Insert => "Insert".to_string(),
238            KeyCode::Enter => "Enter".to_string(),
239            KeyCode::Esc => "Esc".to_string(),
240            KeyCode::Tab => "Tab".to_string(),
241            KeyCode::BackTab => "BackTab".to_string(),
242            KeyCode::Backspace => "Backspace".to_string(),
243            KeyCode::Delete => "Delete".to_string(),
244            KeyCode::Home => "Home".to_string(),
245            KeyCode::End => "End".to_string(),
246            KeyCode::PageUp => "PageUp".to_string(),
247            KeyCode::PageDown => "PageDown".to_string(),
248            KeyCode::Up => "Up".to_string(),
249            KeyCode::Down => "Down".to_string(),
250            KeyCode::Left => "Left".to_string(),
251            KeyCode::Right => "Right".to_string(),
252            KeyCode::F(n) => format!("F{n}"),
253        };
254        parts.push(&code_str);
255
256        let result = parts.join("+");
257        if lowercase {
258            result.to_lowercase()
259        } else {
260            result
261        }
262    }
263}
264
265impl std::fmt::Display for KeyEvent {
266    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
267        write!(f, "{}", self.to_formatted_string(false))
268    }
269}
270
271#[cfg(test)]
272mod tests {
273    use super::*;
274
275    #[test]
276    fn test_key_event_formatting() {
277        let key1 = KeyEvent {
278            code: KeyCode::Char('e'),
279            mods: KeyMods {
280                ctrl: true,
281                alt: false,
282                shift: false,
283                super_key: false,
284            },
285        };
286        assert_eq!(key1.to_formatted_string(false), "Ctrl+E");
287        assert_eq!(key1.to_formatted_string(true), "ctrl+e");
288        assert_eq!(key1.to_string(), "Ctrl+E");
289
290        let key2 = KeyEvent {
291            code: KeyCode::Enter,
292            mods: KeyMods {
293                ctrl: true,
294                alt: true,
295                shift: true,
296                super_key: false,
297            },
298        };
299        assert_eq!(key2.to_formatted_string(false), "Ctrl+Alt+Shift+Enter");
300        assert_eq!(key2.to_formatted_string(true), "ctrl+alt+shift+enter");
301        assert_eq!(key2.to_string(), "Ctrl+Alt+Shift+Enter");
302
303        let key3 = KeyEvent {
304            code: KeyCode::Char(' '),
305            mods: KeyMods {
306                ctrl: false,
307                alt: false,
308                shift: false,
309                super_key: false,
310            },
311        };
312        assert_eq!(key3.to_formatted_string(false), "Space");
313        assert_eq!(key3.to_string(), "Space");
314
315        let key4 = KeyEvent {
316            code: KeyCode::F(12),
317            mods: KeyMods {
318                ctrl: false,
319                alt: false,
320                shift: false,
321                super_key: false,
322            },
323        };
324        assert_eq!(key4.to_formatted_string(false), "F12");
325        assert_eq!(key4.to_formatted_string(true), "f12");
326        assert_eq!(key4.to_string(), "F12");
327    }
328
329    #[test]
330    fn test_key_event_matching_helpers_and_mod_constants() {
331        let plain_enter = KeyEvent {
332            code: KeyCode::Enter,
333            mods: KeyMods::NONE,
334        };
335        assert!(plain_enter.is(KeyCode::Enter));
336        assert!(plain_enter.is_with(KeyCode::Enter, KeyMods::NONE));
337
338        let shift_enter = KeyEvent {
339            code: KeyCode::Enter,
340            mods: KeyMods::SHIFT,
341        };
342        assert!(!shift_enter.is(KeyCode::Enter));
343        assert!(shift_enter.is_with(KeyCode::Enter, KeyMods::SHIFT));
344        assert!(!shift_enter.is_with(KeyCode::Enter, KeyMods::NONE));
345
346        assert!(KeyMods::NONE.is_empty());
347        assert!(!KeyMods::SHIFT.is_empty());
348    }
349}