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}