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. Widgets hear it only when they ask with
82    /// [`PaintCx::track_pointer_moves`](crate::widget::PaintCx::track_pointer_moves) and the
83    /// pointer is over them or over a child of theirs: most widgets take any mouse event under
84    /// them as theirs, and a move alone is no reason to act. Hover looks need no event: they
85    /// are painted from [`PaintCx::is_hovered`](crate::widget::PaintCx::is_hovered).
86    Moved,
87    /// Wheel towards the top of the content.
88    ScrollUp,
89    /// Wheel towards the bottom of the content.
90    ScrollDown,
91}
92
93/// A mouse event at a screen cell.
94#[derive(Debug, Clone, Copy, PartialEq, Eq)]
95pub struct MouseEvent {
96    /// What happened.
97    pub kind: MouseKind,
98    /// Screen column.
99    pub x: i32,
100    /// Screen row.
101    pub y: i32,
102    /// Held modifiers.
103    pub mods: Modifiers,
104}
105
106/// An input event.
107#[derive(Debug, Clone, PartialEq, Eq)]
108pub enum Event {
109    /// A key.
110    Key(KeyEvent),
111    /// The mouse.
112    Mouse(MouseEvent),
113    /// Text pasted into the terminal.
114    Paste(String),
115    /// The pointer went down outside the widget that captured the keyboard, e.g. outside an
116    /// open dropdown. The widget usually closes.
117    PointerOutside,
118}
119
120#[cfg(test)]
121mod tests {
122    use super::*;
123
124    #[test]
125    fn press_derives_typed_text() {
126        assert_eq!(KeyEvent::press("a").text, Some('a'));
127        assert_eq!(KeyEvent::press("shift+a").text, Some('A'));
128        assert_eq!(KeyEvent::press("space").text, Some(' '));
129        assert_eq!(KeyEvent::press("ctrl+a").text, None);
130        assert_eq!(KeyEvent::press("enter").text, None);
131        assert!(KeyEvent::press("enter").is_plain(Key::Enter));
132        assert!(!KeyEvent::press("shift+enter").is_plain(Key::Enter));
133    }
134}