Skip to main content

qframe/
event.rs

1//! Input events delivered to widgets.
2
3use crate::keymap::{Key, KeyChord, Modifiers};
4
5/// Whether a key went down, auto-repeated or came up.
6#[derive(Debug, Clone, Copy, PartialEq, Eq)]
7pub enum KeyKind {
8    /// The key went down.
9    Press,
10    /// The key is held and the terminal repeats it (only reported by terminals with the kitty
11    /// keyboard protocol).
12    Repeat,
13    /// The key came up (kitty keyboard protocol only).
14    Release,
15}
16
17/// A key event.
18#[derive(Debug, Clone, Copy, PartialEq, Eq)]
19pub struct KeyEvent {
20    /// The key and modifiers, normalised for keymap matching: letters are lowercase with
21    /// `shift` set when typed uppercase.
22    pub chord: KeyChord,
23    /// Down, repeat or up.
24    pub kind: KeyKind,
25    /// The character to insert when typing, if this key produces one.
26    pub text: Option<char>,
27}
28
29impl KeyEvent {
30    /// A key press from a chord such as `"ctrl+s"`, with the character it types.
31    ///
32    /// # Panics
33    ///
34    /// Panics when `chord` is not a valid chord; meant for tests and fixed bindings.
35    #[must_use]
36    pub fn press(chord: &str) -> Self {
37        let chord: KeyChord = chord.parse().unwrap_or_else(|message| panic!("invalid chord `{chord}`: {message}"));
38        Self::from_chord(chord)
39    }
40
41    /// A press of `chord`; `text` is derived from plain character and space keys.
42    #[must_use]
43    pub fn from_chord(chord: KeyChord) -> Self {
44        let typing = !chord.mods.ctrl && !chord.mods.alt;
45        let text = match chord.key {
46            Key::Char(c) if typing && chord.mods.shift => Some(c.to_uppercase().next().unwrap_or(c)),
47            Key::Char(c) if typing => Some(c),
48            Key::Space if typing => Some(' '),
49            _ => None,
50        };
51        Self { chord, kind: KeyKind::Press, text }
52    }
53
54    /// Whether this is a press (not repeat or release) of exactly `key` with no modifiers.
55    #[must_use]
56    pub fn is_plain(&self, key: Key) -> bool {
57        self.kind != KeyKind::Release && self.chord.key == key && self.chord.mods == Modifiers::default()
58    }
59}
60
61/// A mouse button.
62#[derive(Debug, Clone, Copy, PartialEq, Eq)]
63pub enum MouseButton {
64    /// Primary button.
65    Left,
66    /// Secondary button.
67    Right,
68    /// Wheel button.
69    Middle,
70}
71
72/// What the mouse did.
73#[derive(Debug, Clone, Copy, PartialEq, Eq)]
74pub enum MouseKind {
75    /// A button went down.
76    Down(MouseButton),
77    /// A button came up.
78    Up(MouseButton),
79    /// The mouse moved with a button held.
80    Drag(MouseButton),
81    /// The mouse moved with no button held.
82    Moved,
83    /// Wheel towards the top of the content.
84    ScrollUp,
85    /// Wheel towards the bottom of the content.
86    ScrollDown,
87}
88
89/// A mouse event at a screen cell.
90#[derive(Debug, Clone, Copy, PartialEq, Eq)]
91pub struct MouseEvent {
92    /// What happened.
93    pub kind: MouseKind,
94    /// Screen column.
95    pub x: i32,
96    /// Screen row.
97    pub y: i32,
98    /// Held modifiers.
99    pub mods: Modifiers,
100}
101
102/// An input event.
103#[derive(Debug, Clone, PartialEq, Eq)]
104pub enum Event {
105    /// A key.
106    Key(KeyEvent),
107    /// The mouse.
108    Mouse(MouseEvent),
109    /// Text pasted into the terminal.
110    Paste(String),
111    /// The pointer went down outside the widget that captured the keyboard, e.g. outside an
112    /// open dropdown. The widget usually closes.
113    PointerOutside,
114}
115
116#[cfg(test)]
117mod tests {
118    use super::*;
119
120    #[test]
121    fn press_derives_typed_text() {
122        assert_eq!(KeyEvent::press("a").text, Some('a'));
123        assert_eq!(KeyEvent::press("shift+a").text, Some('A'));
124        assert_eq!(KeyEvent::press("space").text, Some(' '));
125        assert_eq!(KeyEvent::press("ctrl+a").text, None);
126        assert_eq!(KeyEvent::press("enter").text, None);
127        assert!(KeyEvent::press("enter").is_plain(Key::Enter));
128        assert!(!KeyEvent::press("shift+enter").is_plain(Key::Enter));
129    }
130}