1#[cfg(feature = "crossterm")]
11use crossterm::event as crossterm_event;
12
13#[non_exhaustive]
19#[derive(Debug, Clone, PartialEq, Eq)]
20pub enum Event {
21 Key(KeyEvent),
23 Mouse(MouseEvent),
25 Resize(u32, u32),
27 Paste(String),
29 FocusGained,
31 FocusLost,
33}
34
35impl Event {
36 pub fn key_char(c: char) -> Self {
38 Event::Key(KeyEvent {
39 code: KeyCode::Char(c),
40 modifiers: KeyModifiers::NONE,
41 kind: KeyEventKind::Press,
42 })
43 }
44
45 pub fn key(code: KeyCode) -> Self {
47 Event::Key(KeyEvent {
48 code,
49 modifiers: KeyModifiers::NONE,
50 kind: KeyEventKind::Press,
51 })
52 }
53
54 pub fn key_ctrl(c: char) -> Self {
56 Event::Key(KeyEvent {
57 code: KeyCode::Char(c),
58 modifiers: KeyModifiers::CONTROL,
59 kind: KeyEventKind::Press,
60 })
61 }
62
63 pub fn key_mod(code: KeyCode, modifiers: KeyModifiers) -> Self {
65 Event::Key(KeyEvent {
66 code,
67 modifiers,
68 kind: KeyEventKind::Press,
69 })
70 }
71
72 pub fn resize(width: u32, height: u32) -> Self {
74 Event::Resize(width, height)
75 }
76
77 pub fn mouse_click(x: u32, y: u32) -> Self {
79 Event::Mouse(MouseEvent {
80 kind: MouseKind::Down(MouseButton::Left),
81 x,
82 y,
83 modifiers: KeyModifiers::NONE,
84 pixel_x: None,
85 pixel_y: None,
86 })
87 }
88
89 pub fn mouse_move(x: u32, y: u32) -> Self {
91 Event::Mouse(MouseEvent {
92 kind: MouseKind::Moved,
93 x,
94 y,
95 modifiers: KeyModifiers::NONE,
96 pixel_x: None,
97 pixel_y: None,
98 })
99 }
100
101 pub fn mouse_drag(x: u32, y: u32) -> Self {
103 Event::Mouse(MouseEvent {
104 kind: MouseKind::Drag(MouseButton::Left),
105 x,
106 y,
107 modifiers: KeyModifiers::NONE,
108 pixel_x: None,
109 pixel_y: None,
110 })
111 }
112
113 pub fn mouse_up(x: u32, y: u32) -> Self {
115 Event::Mouse(MouseEvent {
116 kind: MouseKind::Up(MouseButton::Left),
117 x,
118 y,
119 modifiers: KeyModifiers::NONE,
120 pixel_x: None,
121 pixel_y: None,
122 })
123 }
124
125 pub fn scroll_up(x: u32, y: u32) -> Self {
127 Event::Mouse(MouseEvent {
128 kind: MouseKind::ScrollUp,
129 x,
130 y,
131 modifiers: KeyModifiers::NONE,
132 pixel_x: None,
133 pixel_y: None,
134 })
135 }
136
137 pub fn scroll_down(x: u32, y: u32) -> Self {
139 Event::Mouse(MouseEvent {
140 kind: MouseKind::ScrollDown,
141 x,
142 y,
143 modifiers: KeyModifiers::NONE,
144 pixel_x: None,
145 pixel_y: None,
146 })
147 }
148
149 pub fn key_release(c: char) -> Self {
151 Event::Key(KeyEvent {
152 code: KeyCode::Char(c),
153 modifiers: KeyModifiers::NONE,
154 kind: KeyEventKind::Release,
155 })
156 }
157
158 pub fn paste(text: impl Into<String>) -> Self {
160 Event::Paste(text.into())
161 }
162
163 pub fn as_key(&self) -> Option<&KeyEvent> {
165 match self {
166 Event::Key(k) => Some(k),
167 _ => None,
168 }
169 }
170
171 pub fn as_mouse(&self) -> Option<&MouseEvent> {
173 match self {
174 Event::Mouse(m) => Some(m),
175 _ => None,
176 }
177 }
178
179 pub fn as_resize(&self) -> Option<(u32, u32)> {
181 match self {
182 Event::Resize(w, h) => Some((*w, *h)),
183 _ => None,
184 }
185 }
186
187 pub fn as_paste(&self) -> Option<&str> {
189 match self {
190 Event::Paste(s) => Some(s),
191 _ => None,
192 }
193 }
194
195 pub fn is_key(&self) -> bool {
197 matches!(self, Event::Key(_))
198 }
199
200 pub fn is_mouse(&self) -> bool {
202 matches!(self, Event::Mouse(_))
203 }
204}
205
206#[non_exhaustive]
208#[derive(Debug, Clone, PartialEq, Eq)]
209pub struct KeyEvent {
210 pub code: KeyCode,
212 pub modifiers: KeyModifiers,
214 pub kind: KeyEventKind,
216}
217
218impl KeyEvent {
219 pub fn is_char(&self, c: char) -> bool {
221 self.code == KeyCode::Char(c)
222 && self.modifiers == KeyModifiers::NONE
223 && self.kind == KeyEventKind::Press
224 }
225
226 pub fn is_ctrl_char(&self, c: char) -> bool {
228 self.code == KeyCode::Char(c)
229 && self.modifiers == KeyModifiers::CONTROL
230 && self.kind == KeyEventKind::Press
231 }
232
233 pub fn is_code(&self, code: KeyCode) -> bool {
235 self.code == code
236 && self.modifiers == KeyModifiers::NONE
237 && self.kind == KeyEventKind::Press
238 }
239}
240
241#[non_exhaustive]
243#[derive(Debug, Clone, Copy, PartialEq, Eq)]
244pub enum KeyEventKind {
245 Press,
247 Release,
249 Repeat,
251}
252
253#[non_exhaustive]
259#[derive(Debug, Clone, PartialEq, Eq)]
260pub enum KeyCode {
261 Char(char),
263 Enter,
265 Backspace,
267 Tab,
269 BackTab,
271 Esc,
273 Up,
275 Down,
277 Left,
279 Right,
281 Home,
283 End,
285 PageUp,
287 PageDown,
289 Delete,
291 Insert,
293 Null,
295 CapsLock,
297 ScrollLock,
299 NumLock,
301 PrintScreen,
303 Pause,
305 Menu,
307 KeypadBegin,
309 F(u8),
311 Modifier(ModifierKey),
321}
322
323#[non_exhaustive]
351#[derive(Debug, Clone, Copy, PartialEq, Eq)]
352pub enum ModifierKey {
353 LeftShift,
355 LeftCtrl,
357 LeftAlt,
359 LeftSuper,
361 RightShift,
363 RightCtrl,
365 RightAlt,
367 RightSuper,
369 LeftHyper,
371 LeftMeta,
373 RightHyper,
375 RightMeta,
377 IsoLevel3Shift,
379 IsoLevel5Shift,
381}
382
383#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
388pub struct KeyModifiers(pub u8);
389
390impl KeyModifiers {
391 pub const NONE: Self = Self(0);
393 pub const SHIFT: Self = Self(1 << 0);
395 pub const CONTROL: Self = Self(1 << 1);
397 pub const ALT: Self = Self(1 << 2);
399 pub const SUPER: Self = Self(1 << 3);
401 pub const HYPER: Self = Self(1 << 4);
403 pub const META: Self = Self(1 << 5);
405
406 #[inline]
408 pub fn contains(self, other: Self) -> bool {
409 (self.0 & other.0) == other.0
410 }
411}
412
413#[non_exhaustive]
423#[derive(Debug, Clone, PartialEq, Eq)]
424pub struct MouseEvent {
425 pub kind: MouseKind,
427 pub x: u32,
429 pub y: u32,
431 pub modifiers: KeyModifiers,
433 pub pixel_x: Option<u16>,
438 pub pixel_y: Option<u16>,
443}
444
445impl MouseEvent {
446 pub fn new(
448 kind: MouseKind,
449 x: u32,
450 y: u32,
451 modifiers: KeyModifiers,
452 pixel_x: Option<u16>,
453 pixel_y: Option<u16>,
454 ) -> Self {
455 Self {
456 kind,
457 x,
458 y,
459 modifiers,
460 pixel_x,
461 pixel_y,
462 }
463 }
464
465 pub fn is_scroll(&self) -> bool {
467 matches!(
468 self.kind,
469 MouseKind::ScrollUp
470 | MouseKind::ScrollDown
471 | MouseKind::ScrollLeft
472 | MouseKind::ScrollRight
473 )
474 }
475}
476
477#[non_exhaustive]
479#[derive(Debug, Clone, PartialEq, Eq)]
480pub enum MouseKind {
481 Down(MouseButton),
483 Up(MouseButton),
485 Drag(MouseButton),
487 ScrollUp,
489 ScrollDown,
491 ScrollLeft,
493 ScrollRight,
495 Moved,
497}
498
499#[non_exhaustive]
501#[derive(Debug, Clone, Copy, PartialEq, Eq)]
502pub enum MouseButton {
503 Left,
505 Right,
507 Middle,
509}
510
511#[cfg(feature = "crossterm")]
512fn convert_modifiers(modifiers: crossterm_event::KeyModifiers) -> KeyModifiers {
513 let mut out = KeyModifiers::NONE;
514 if modifiers.contains(crossterm_event::KeyModifiers::SHIFT) {
515 out.0 |= KeyModifiers::SHIFT.0;
516 }
517 if modifiers.contains(crossterm_event::KeyModifiers::CONTROL) {
518 out.0 |= KeyModifiers::CONTROL.0;
519 }
520 if modifiers.contains(crossterm_event::KeyModifiers::ALT) {
521 out.0 |= KeyModifiers::ALT.0;
522 }
523 if modifiers.contains(crossterm_event::KeyModifiers::SUPER) {
524 out.0 |= KeyModifiers::SUPER.0;
525 }
526 if modifiers.contains(crossterm_event::KeyModifiers::HYPER) {
527 out.0 |= KeyModifiers::HYPER.0;
528 }
529 if modifiers.contains(crossterm_event::KeyModifiers::META) {
530 out.0 |= KeyModifiers::META.0;
531 }
532 out
533}
534
535#[cfg(feature = "crossterm")]
536fn convert_modifier_key(mk: crossterm_event::ModifierKeyCode) -> ModifierKey {
537 use crossterm_event::ModifierKeyCode as C;
538 match mk {
539 C::LeftShift => ModifierKey::LeftShift,
540 C::LeftControl => ModifierKey::LeftCtrl,
541 C::LeftAlt => ModifierKey::LeftAlt,
542 C::LeftSuper => ModifierKey::LeftSuper,
543 C::RightShift => ModifierKey::RightShift,
544 C::RightControl => ModifierKey::RightCtrl,
545 C::RightAlt => ModifierKey::RightAlt,
546 C::RightSuper => ModifierKey::RightSuper,
547 C::LeftHyper => ModifierKey::LeftHyper,
548 C::LeftMeta => ModifierKey::LeftMeta,
549 C::RightHyper => ModifierKey::RightHyper,
550 C::RightMeta => ModifierKey::RightMeta,
551 C::IsoLevel3Shift => ModifierKey::IsoLevel3Shift,
552 C::IsoLevel5Shift => ModifierKey::IsoLevel5Shift,
553 }
554}
555
556#[cfg(feature = "crossterm")]
557fn convert_button(button: crossterm_event::MouseButton) -> MouseButton {
558 match button {
559 crossterm_event::MouseButton::Left => MouseButton::Left,
560 crossterm_event::MouseButton::Right => MouseButton::Right,
561 crossterm_event::MouseButton::Middle => MouseButton::Middle,
562 }
563}
564
565#[cfg(feature = "crossterm")]
570pub(crate) fn from_crossterm(raw: crossterm_event::Event) -> Option<Event> {
571 match raw {
572 crossterm_event::Event::Key(k) => {
573 let code = match k.code {
574 crossterm_event::KeyCode::Char(c) => KeyCode::Char(c),
575 crossterm_event::KeyCode::Enter => KeyCode::Enter,
576 crossterm_event::KeyCode::Backspace => KeyCode::Backspace,
577 crossterm_event::KeyCode::Tab => KeyCode::Tab,
578 crossterm_event::KeyCode::BackTab => KeyCode::BackTab,
579 crossterm_event::KeyCode::Esc => KeyCode::Esc,
580 crossterm_event::KeyCode::Up => KeyCode::Up,
581 crossterm_event::KeyCode::Down => KeyCode::Down,
582 crossterm_event::KeyCode::Left => KeyCode::Left,
583 crossterm_event::KeyCode::Right => KeyCode::Right,
584 crossterm_event::KeyCode::Home => KeyCode::Home,
585 crossterm_event::KeyCode::End => KeyCode::End,
586 crossterm_event::KeyCode::PageUp => KeyCode::PageUp,
587 crossterm_event::KeyCode::PageDown => KeyCode::PageDown,
588 crossterm_event::KeyCode::Delete => KeyCode::Delete,
589 crossterm_event::KeyCode::Insert => KeyCode::Insert,
590 crossterm_event::KeyCode::Null => KeyCode::Null,
591 crossterm_event::KeyCode::CapsLock => KeyCode::CapsLock,
592 crossterm_event::KeyCode::ScrollLock => KeyCode::ScrollLock,
593 crossterm_event::KeyCode::NumLock => KeyCode::NumLock,
594 crossterm_event::KeyCode::PrintScreen => KeyCode::PrintScreen,
595 crossterm_event::KeyCode::Pause => KeyCode::Pause,
596 crossterm_event::KeyCode::Menu => KeyCode::Menu,
597 crossterm_event::KeyCode::KeypadBegin => KeyCode::KeypadBegin,
598 crossterm_event::KeyCode::F(n) => KeyCode::F(n),
599 crossterm_event::KeyCode::Modifier(mk) => {
600 KeyCode::Modifier(convert_modifier_key(mk))
601 }
602 _ => return None,
603 };
604 let modifiers = convert_modifiers(k.modifiers);
605 let kind = match k.kind {
606 crossterm_event::KeyEventKind::Press => KeyEventKind::Press,
607 crossterm_event::KeyEventKind::Repeat => KeyEventKind::Repeat,
608 crossterm_event::KeyEventKind::Release => KeyEventKind::Release,
609 };
610 Some(Event::Key(KeyEvent {
611 code,
612 modifiers,
613 kind,
614 }))
615 }
616 crossterm_event::Event::Mouse(m) => {
617 let kind = match m.kind {
618 crossterm_event::MouseEventKind::Down(btn) => MouseKind::Down(convert_button(btn)),
619 crossterm_event::MouseEventKind::Up(btn) => MouseKind::Up(convert_button(btn)),
620 crossterm_event::MouseEventKind::Drag(btn) => MouseKind::Drag(convert_button(btn)),
621 crossterm_event::MouseEventKind::Moved => MouseKind::Moved,
622 crossterm_event::MouseEventKind::ScrollUp => MouseKind::ScrollUp,
623 crossterm_event::MouseEventKind::ScrollDown => MouseKind::ScrollDown,
624 crossterm_event::MouseEventKind::ScrollLeft => MouseKind::ScrollLeft,
625 crossterm_event::MouseEventKind::ScrollRight => MouseKind::ScrollRight,
626 };
627
628 Some(Event::Mouse(MouseEvent {
629 kind,
630 x: m.column as u32,
631 y: m.row as u32,
632 modifiers: convert_modifiers(m.modifiers),
633 pixel_x: None,
634 pixel_y: None,
635 }))
636 }
637 crossterm_event::Event::Resize(cols, rows) => Some(Event::Resize(cols as u32, rows as u32)),
638 crossterm_event::Event::Paste(mut s) => {
639 const MAX_PASTE_BYTES: usize = 1 << 20;
644 if s.len() > MAX_PASTE_BYTES {
645 let mut end = MAX_PASTE_BYTES;
646 while end > 0 && !s.is_char_boundary(end) {
647 end -= 1;
648 }
649 s.truncate(end);
650 s.push('…');
651 }
652 Some(Event::Paste(s))
653 }
654 crossterm_event::Event::FocusGained => Some(Event::FocusGained),
655 crossterm_event::Event::FocusLost => Some(Event::FocusLost),
656 }
657}
658
659#[cfg(test)]
660mod event_constructor_tests {
661 use super::*;
662
663 #[test]
664 fn test_key_char() {
665 let e = Event::key_char('q');
666 if let Event::Key(k) = e {
667 assert!(matches!(k.code, KeyCode::Char('q')));
668 assert_eq!(k.modifiers, KeyModifiers::NONE);
669 assert!(matches!(k.kind, KeyEventKind::Press));
670 } else {
671 panic!("Expected Key event");
672 }
673 }
674
675 #[test]
676 fn test_key() {
677 let e = Event::key(KeyCode::Enter);
678 if let Event::Key(k) = e {
679 assert!(matches!(k.code, KeyCode::Enter));
680 assert_eq!(k.modifiers, KeyModifiers::NONE);
681 assert!(matches!(k.kind, KeyEventKind::Press));
682 } else {
683 panic!("Expected Key event");
684 }
685 }
686
687 #[test]
688 fn test_key_ctrl() {
689 let e = Event::key_ctrl('s');
690 if let Event::Key(k) = e {
691 assert!(matches!(k.code, KeyCode::Char('s')));
692 assert_eq!(k.modifiers, KeyModifiers::CONTROL);
693 assert!(matches!(k.kind, KeyEventKind::Press));
694 } else {
695 panic!("Expected Key event");
696 }
697 }
698
699 #[test]
700 fn test_key_mod() {
701 let modifiers = KeyModifiers(KeyModifiers::SHIFT.0 | KeyModifiers::ALT.0);
702 let e = Event::key_mod(KeyCode::Tab, modifiers);
703 if let Event::Key(k) = e {
704 assert!(matches!(k.code, KeyCode::Tab));
705 assert_eq!(k.modifiers, modifiers);
706 assert!(matches!(k.kind, KeyEventKind::Press));
707 } else {
708 panic!("Expected Key event");
709 }
710 }
711
712 #[test]
713 fn test_resize() {
714 let e = Event::resize(80, 24);
715 assert!(matches!(e, Event::Resize(80, 24)));
716 }
717
718 #[test]
719 fn test_mouse_click() {
720 let e = Event::mouse_click(10, 5);
721 if let Event::Mouse(m) = e {
722 assert!(matches!(m.kind, MouseKind::Down(MouseButton::Left)));
723 assert_eq!(m.x, 10);
724 assert_eq!(m.y, 5);
725 assert_eq!(m.modifiers, KeyModifiers::NONE);
726 } else {
727 panic!("Expected Mouse event");
728 }
729 }
730
731 #[test]
732 fn test_mouse_move() {
733 let e = Event::mouse_move(10, 5);
734 if let Event::Mouse(m) = e {
735 assert!(matches!(m.kind, MouseKind::Moved));
736 assert_eq!(m.x, 10);
737 assert_eq!(m.y, 5);
738 assert_eq!(m.modifiers, KeyModifiers::NONE);
739 } else {
740 panic!("Expected Mouse event");
741 }
742 }
743
744 #[test]
745 fn test_paste() {
746 let e = Event::paste("hello");
747 assert!(matches!(e, Event::Paste(s) if s == "hello"));
748 }
749}
750
751#[cfg(all(test, feature = "crossterm"))]
752mod crossterm_conversion_tests {
753 use super::*;
754 use crossterm_event::{
755 Event as CtEvent, KeyCode as CtKeyCode, KeyEvent as CtKeyEvent,
756 KeyEventKind as CtKeyEventKind, KeyEventState, KeyModifiers as CtKeyModifiers,
757 ModifierKeyCode,
758 };
759
760 fn ct_modifier_event(mk: ModifierKeyCode, kind: CtKeyEventKind) -> CtEvent {
761 CtEvent::Key(CtKeyEvent {
762 code: CtKeyCode::Modifier(mk),
763 modifiers: CtKeyModifiers::NONE,
764 kind,
765 state: KeyEventState::NONE,
766 })
767 }
768
769 #[test]
770 fn from_crossterm_maps_modifier_key() {
771 let raw = ct_modifier_event(ModifierKeyCode::LeftControl, CtKeyEventKind::Press);
772 let converted = from_crossterm(raw);
773 match converted {
774 Some(Event::Key(k)) => {
775 assert_eq!(k.code, KeyCode::Modifier(ModifierKey::LeftCtrl));
776 assert!(matches!(k.kind, KeyEventKind::Press));
777 }
778 other => panic!("expected modifier key event, got {other:?}"),
779 }
780 }
781
782 #[test]
783 fn from_crossterm_modifier_release() {
784 let raw = ct_modifier_event(ModifierKeyCode::LeftControl, CtKeyEventKind::Release);
785 let converted = from_crossterm(raw);
786 match converted {
787 Some(Event::Key(k)) => {
788 assert_eq!(k.code, KeyCode::Modifier(ModifierKey::LeftCtrl));
789 assert!(matches!(k.kind, KeyEventKind::Release));
790 }
791 other => panic!("expected modifier release event, got {other:?}"),
792 }
793 }
794
795 #[test]
796 fn from_crossterm_modifier_key_exhaustive() {
797 let cases: [(ModifierKeyCode, ModifierKey); 14] = [
799 (ModifierKeyCode::LeftShift, ModifierKey::LeftShift),
800 (ModifierKeyCode::LeftControl, ModifierKey::LeftCtrl),
801 (ModifierKeyCode::LeftAlt, ModifierKey::LeftAlt),
802 (ModifierKeyCode::LeftSuper, ModifierKey::LeftSuper),
803 (ModifierKeyCode::RightShift, ModifierKey::RightShift),
804 (ModifierKeyCode::RightControl, ModifierKey::RightCtrl),
805 (ModifierKeyCode::RightAlt, ModifierKey::RightAlt),
806 (ModifierKeyCode::RightSuper, ModifierKey::RightSuper),
807 (ModifierKeyCode::LeftHyper, ModifierKey::LeftHyper),
808 (ModifierKeyCode::LeftMeta, ModifierKey::LeftMeta),
809 (ModifierKeyCode::RightHyper, ModifierKey::RightHyper),
810 (ModifierKeyCode::RightMeta, ModifierKey::RightMeta),
811 (ModifierKeyCode::IsoLevel3Shift, ModifierKey::IsoLevel3Shift),
812 (ModifierKeyCode::IsoLevel5Shift, ModifierKey::IsoLevel5Shift),
813 ];
814 for (ct, expected) in cases {
815 let raw = ct_modifier_event(ct, CtKeyEventKind::Press);
816 match from_crossterm(raw) {
817 Some(Event::Key(k)) => {
818 assert_eq!(k.code, KeyCode::Modifier(expected), "mismatch for {ct:?}")
819 }
820 other => panic!("expected modifier event for {ct:?}, got {other:?}"),
821 }
822 }
823 }
824}