1use crossterm::event::{KeyCode, KeyEventKind, KeyModifiers, MouseEventKind as CtMouseEventKind};
4
5#[derive(Debug, Clone, PartialEq, Eq)]
7pub enum Event {
8 Key(KeyEvent),
10 Mouse(MouseEvent),
12 Resize { width: u16, height: u16 },
14 FocusGained,
16 FocusLost,
18 Paste(String),
21}
22
23#[derive(Debug, Clone, PartialEq, Eq)]
25pub struct KeyEvent {
26 pub code: KeyCode,
27 pub modifiers: KeyModifiers,
28}
29
30impl KeyEvent {
31 pub fn is_char(&self, c: char) -> bool {
33 self.code == KeyCode::Char(c) && self.modifiers == KeyModifiers::NONE
34 }
35
36 pub fn is_ctrl(&self, c: char) -> bool {
38 self.code == KeyCode::Char(c) && self.modifiers.contains(KeyModifiers::CONTROL)
39 }
40
41 pub fn is_alt(&self, c: char) -> bool {
43 self.code == KeyCode::Char(c) && self.modifiers.contains(KeyModifiers::ALT)
44 }
45
46 pub fn is_key(&self, code: KeyCode) -> bool {
48 self.code == code
49 }
50
51 pub fn is_esc(&self) -> bool {
53 self.code == KeyCode::Esc
54 }
55
56 pub fn is_enter(&self) -> bool {
58 self.code == KeyCode::Enter
59 }
60
61 pub fn char(&self) -> Option<char> {
63 match self.code {
64 KeyCode::Char(c) => Some(c),
65 _ => None,
66 }
67 }
68}
69
70#[derive(Debug, Clone, PartialEq, Eq)]
72pub struct MouseEvent {
73 pub kind: MouseEventKind,
74 pub column: u16,
75 pub row: u16,
76 pub modifiers: KeyModifiers,
77}
78
79#[derive(Debug, Clone, PartialEq, Eq)]
81pub enum MouseEventKind {
82 Down(MouseButton),
83 Up(MouseButton),
84 Drag(MouseButton),
85 Moved,
86 ScrollDown,
87 ScrollUp,
88 ScrollLeft,
89 ScrollRight,
90}
91
92#[derive(Debug, Clone, Copy, PartialEq, Eq)]
93pub enum MouseButton {
94 Left,
95 Right,
96 Middle,
97}
98
99impl From<crossterm::event::Event> for Event {
100 fn from(event: crossterm::event::Event) -> Self {
101 match event {
102 crossterm::event::Event::Key(key) => Event::Key(KeyEvent {
103 code: key.code,
104 modifiers: key.modifiers,
105 }),
106 crossterm::event::Event::Mouse(mouse) => Event::Mouse(MouseEvent {
107 kind: convert_mouse_kind(mouse.kind),
108 column: mouse.column,
109 row: mouse.row,
110 modifiers: mouse.modifiers,
111 }),
112 crossterm::event::Event::Resize(w, h) => Event::Resize {
113 width: w,
114 height: h,
115 },
116 crossterm::event::Event::FocusGained => Event::FocusGained,
117 crossterm::event::Event::FocusLost => Event::FocusLost,
118 crossterm::event::Event::Paste(text) => Event::Paste(text),
119 }
120 }
121}
122
123impl Event {
124 pub fn from_crossterm(event: crossterm::event::Event) -> Option<Self> {
127 match event {
128 crossterm::event::Event::Key(key) if key.kind == KeyEventKind::Release => None,
129 event => Some(event.into()),
130 }
131 }
132}
133
134fn convert_mouse_button(button: crossterm::event::MouseButton) -> MouseButton {
135 match button {
136 crossterm::event::MouseButton::Left => MouseButton::Left,
137 crossterm::event::MouseButton::Right => MouseButton::Right,
138 crossterm::event::MouseButton::Middle => MouseButton::Middle,
139 }
140}
141
142fn convert_mouse_kind(kind: CtMouseEventKind) -> MouseEventKind {
143 match kind {
144 CtMouseEventKind::Down(b) => MouseEventKind::Down(convert_mouse_button(b)),
145 CtMouseEventKind::Up(b) => MouseEventKind::Up(convert_mouse_button(b)),
146 CtMouseEventKind::Drag(b) => MouseEventKind::Drag(convert_mouse_button(b)),
147 CtMouseEventKind::Moved => MouseEventKind::Moved,
148 CtMouseEventKind::ScrollDown => MouseEventKind::ScrollDown,
149 CtMouseEventKind::ScrollUp => MouseEventKind::ScrollUp,
150 CtMouseEventKind::ScrollLeft => MouseEventKind::ScrollLeft,
151 CtMouseEventKind::ScrollRight => MouseEventKind::ScrollRight,
152 }
153}
154
155#[cfg(test)]
156mod tests {
157 use super::*;
158
159 #[test]
160 fn crossterm_key_release_is_ignored() {
161 let event = crossterm::event::Event::Key(crossterm::event::KeyEvent::new_with_kind(
162 KeyCode::Char('a'),
163 KeyModifiers::NONE,
164 KeyEventKind::Release,
165 ));
166
167 assert_eq!(Event::from_crossterm(event), None);
168 }
169
170 #[test]
171 fn crossterm_key_press_and_repeat_are_kept() {
172 for kind in [KeyEventKind::Press, KeyEventKind::Repeat] {
173 let event = crossterm::event::Event::Key(crossterm::event::KeyEvent::new_with_kind(
174 KeyCode::Char('a'),
175 KeyModifiers::NONE,
176 kind,
177 ));
178
179 assert_eq!(
180 Event::from_crossterm(event),
181 Some(Event::Key(KeyEvent {
182 code: KeyCode::Char('a'),
183 modifiers: KeyModifiers::NONE,
184 }))
185 );
186 }
187 }
188
189 #[test]
190 fn is_char() {
191 let e = KeyEvent {
192 code: KeyCode::Char('q'),
193 modifiers: KeyModifiers::NONE,
194 };
195 assert!(e.is_char('q'));
196 assert!(!e.is_char('x'));
197 }
198
199 #[test]
200 fn is_ctrl() {
201 let e = KeyEvent {
202 code: KeyCode::Char('c'),
203 modifiers: KeyModifiers::CONTROL,
204 };
205 assert!(e.is_ctrl('c'));
206 assert!(!e.is_ctrl('x'));
207 let plain = KeyEvent {
208 code: KeyCode::Char('c'),
209 modifiers: KeyModifiers::NONE,
210 };
211 assert!(!plain.is_ctrl('c'));
212 }
213
214 #[test]
215 fn is_alt() {
216 let e = KeyEvent {
217 code: KeyCode::Char('x'),
218 modifiers: KeyModifiers::ALT,
219 };
220 assert!(e.is_alt('x'));
221 }
222
223 #[test]
224 fn is_key() {
225 let e = KeyEvent {
226 code: KeyCode::Enter,
227 modifiers: KeyModifiers::NONE,
228 };
229 assert!(e.is_key(KeyCode::Enter));
230 assert!(e.is_enter());
231 assert!(!e.is_esc());
232 }
233
234 #[test]
235 fn char_extraction() {
236 let e = KeyEvent {
237 code: KeyCode::Char('a'),
238 modifiers: KeyModifiers::NONE,
239 };
240 assert_eq!(e.char(), Some('a'));
241 let esc = KeyEvent {
242 code: KeyCode::Esc,
243 modifiers: KeyModifiers::NONE,
244 };
245 assert_eq!(esc.char(), None);
246 }
247}