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")]
576pub fn from_crossterm(raw: crossterm_event::Event) -> Option<Event> {
577 match raw {
578 crossterm_event::Event::Key(k) => {
579 let code = match k.code {
580 crossterm_event::KeyCode::Char(c) => KeyCode::Char(c),
581 crossterm_event::KeyCode::Enter => KeyCode::Enter,
582 crossterm_event::KeyCode::Backspace => KeyCode::Backspace,
583 crossterm_event::KeyCode::Tab => KeyCode::Tab,
584 crossterm_event::KeyCode::BackTab => KeyCode::BackTab,
585 crossterm_event::KeyCode::Esc => KeyCode::Esc,
586 crossterm_event::KeyCode::Up => KeyCode::Up,
587 crossterm_event::KeyCode::Down => KeyCode::Down,
588 crossterm_event::KeyCode::Left => KeyCode::Left,
589 crossterm_event::KeyCode::Right => KeyCode::Right,
590 crossterm_event::KeyCode::Home => KeyCode::Home,
591 crossterm_event::KeyCode::End => KeyCode::End,
592 crossterm_event::KeyCode::PageUp => KeyCode::PageUp,
593 crossterm_event::KeyCode::PageDown => KeyCode::PageDown,
594 crossterm_event::KeyCode::Delete => KeyCode::Delete,
595 crossterm_event::KeyCode::Insert => KeyCode::Insert,
596 crossterm_event::KeyCode::Null => KeyCode::Null,
597 crossterm_event::KeyCode::CapsLock => KeyCode::CapsLock,
598 crossterm_event::KeyCode::ScrollLock => KeyCode::ScrollLock,
599 crossterm_event::KeyCode::NumLock => KeyCode::NumLock,
600 crossterm_event::KeyCode::PrintScreen => KeyCode::PrintScreen,
601 crossterm_event::KeyCode::Pause => KeyCode::Pause,
602 crossterm_event::KeyCode::Menu => KeyCode::Menu,
603 crossterm_event::KeyCode::KeypadBegin => KeyCode::KeypadBegin,
604 crossterm_event::KeyCode::F(n) => KeyCode::F(n),
605 crossterm_event::KeyCode::Modifier(mk) => {
606 KeyCode::Modifier(convert_modifier_key(mk))
607 }
608 _ => return None,
609 };
610 let modifiers = convert_modifiers(k.modifiers);
611 let kind = match k.kind {
612 crossterm_event::KeyEventKind::Press => KeyEventKind::Press,
613 crossterm_event::KeyEventKind::Repeat => KeyEventKind::Repeat,
614 crossterm_event::KeyEventKind::Release => KeyEventKind::Release,
615 };
616 Some(Event::Key(KeyEvent {
617 code,
618 modifiers,
619 kind,
620 }))
621 }
622 crossterm_event::Event::Mouse(m) => {
623 let kind = match m.kind {
624 crossterm_event::MouseEventKind::Down(btn) => MouseKind::Down(convert_button(btn)),
625 crossterm_event::MouseEventKind::Up(btn) => MouseKind::Up(convert_button(btn)),
626 crossterm_event::MouseEventKind::Drag(btn) => MouseKind::Drag(convert_button(btn)),
627 crossterm_event::MouseEventKind::Moved => MouseKind::Moved,
628 crossterm_event::MouseEventKind::ScrollUp => MouseKind::ScrollUp,
629 crossterm_event::MouseEventKind::ScrollDown => MouseKind::ScrollDown,
630 crossterm_event::MouseEventKind::ScrollLeft => MouseKind::ScrollLeft,
631 crossterm_event::MouseEventKind::ScrollRight => MouseKind::ScrollRight,
632 };
633
634 Some(Event::Mouse(MouseEvent {
635 kind,
636 x: m.column as u32,
637 y: m.row as u32,
638 modifiers: convert_modifiers(m.modifiers),
639 pixel_x: None,
640 pixel_y: None,
641 }))
642 }
643 crossterm_event::Event::Resize(cols, rows) => Some(Event::Resize(cols as u32, rows as u32)),
644 crossterm_event::Event::Paste(mut s) => {
645 const MAX_PASTE_BYTES: usize = 1 << 20;
650 if s.len() > MAX_PASTE_BYTES {
651 let mut end = MAX_PASTE_BYTES;
652 while end > 0 && !s.is_char_boundary(end) {
653 end -= 1;
654 }
655 s.truncate(end);
656 s.push('…');
657 }
658 Some(Event::Paste(s))
659 }
660 crossterm_event::Event::FocusGained => Some(Event::FocusGained),
661 crossterm_event::Event::FocusLost => Some(Event::FocusLost),
662 }
663}
664
665#[cfg(test)]
666mod event_constructor_tests {
667 use super::*;
668
669 #[cfg(feature = "crossterm")]
673 #[test]
674 fn issue_278_backend_and_conversion_are_public() {
675 fn _accepts_terminal(_t: Option<crate::Terminal>) {}
676 fn _accepts_inline(_t: Option<crate::InlineTerminal>) {}
677 let _convert: fn(crate::crossterm::event::Event) -> Option<Event> =
678 crate::event::from_crossterm;
679 }
680
681 #[test]
682 fn test_key_char() {
683 let e = Event::key_char('q');
684 if let Event::Key(k) = e {
685 assert!(matches!(k.code, KeyCode::Char('q')));
686 assert_eq!(k.modifiers, KeyModifiers::NONE);
687 assert!(matches!(k.kind, KeyEventKind::Press));
688 } else {
689 panic!("Expected Key event");
690 }
691 }
692
693 #[test]
694 fn test_key() {
695 let e = Event::key(KeyCode::Enter);
696 if let Event::Key(k) = e {
697 assert!(matches!(k.code, KeyCode::Enter));
698 assert_eq!(k.modifiers, KeyModifiers::NONE);
699 assert!(matches!(k.kind, KeyEventKind::Press));
700 } else {
701 panic!("Expected Key event");
702 }
703 }
704
705 #[test]
706 fn test_key_ctrl() {
707 let e = Event::key_ctrl('s');
708 if let Event::Key(k) = e {
709 assert!(matches!(k.code, KeyCode::Char('s')));
710 assert_eq!(k.modifiers, KeyModifiers::CONTROL);
711 assert!(matches!(k.kind, KeyEventKind::Press));
712 } else {
713 panic!("Expected Key event");
714 }
715 }
716
717 #[test]
718 fn test_key_mod() {
719 let modifiers = KeyModifiers(KeyModifiers::SHIFT.0 | KeyModifiers::ALT.0);
720 let e = Event::key_mod(KeyCode::Tab, modifiers);
721 if let Event::Key(k) = e {
722 assert!(matches!(k.code, KeyCode::Tab));
723 assert_eq!(k.modifiers, modifiers);
724 assert!(matches!(k.kind, KeyEventKind::Press));
725 } else {
726 panic!("Expected Key event");
727 }
728 }
729
730 #[test]
731 fn test_resize() {
732 let e = Event::resize(80, 24);
733 assert!(matches!(e, Event::Resize(80, 24)));
734 }
735
736 #[test]
737 fn test_mouse_click() {
738 let e = Event::mouse_click(10, 5);
739 if let Event::Mouse(m) = e {
740 assert!(matches!(m.kind, MouseKind::Down(MouseButton::Left)));
741 assert_eq!(m.x, 10);
742 assert_eq!(m.y, 5);
743 assert_eq!(m.modifiers, KeyModifiers::NONE);
744 } else {
745 panic!("Expected Mouse event");
746 }
747 }
748
749 #[test]
750 fn test_mouse_move() {
751 let e = Event::mouse_move(10, 5);
752 if let Event::Mouse(m) = e {
753 assert!(matches!(m.kind, MouseKind::Moved));
754 assert_eq!(m.x, 10);
755 assert_eq!(m.y, 5);
756 assert_eq!(m.modifiers, KeyModifiers::NONE);
757 } else {
758 panic!("Expected Mouse event");
759 }
760 }
761
762 #[test]
763 fn test_paste() {
764 let e = Event::paste("hello");
765 assert!(matches!(e, Event::Paste(s) if s == "hello"));
766 }
767}
768
769#[cfg(all(test, feature = "crossterm"))]
770mod crossterm_conversion_tests {
771 use super::*;
772 use crossterm_event::{
773 Event as CtEvent, KeyCode as CtKeyCode, KeyEvent as CtKeyEvent,
774 KeyEventKind as CtKeyEventKind, KeyEventState, KeyModifiers as CtKeyModifiers,
775 ModifierKeyCode,
776 };
777
778 fn ct_modifier_event(mk: ModifierKeyCode, kind: CtKeyEventKind) -> CtEvent {
779 CtEvent::Key(CtKeyEvent {
780 code: CtKeyCode::Modifier(mk),
781 modifiers: CtKeyModifiers::NONE,
782 kind,
783 state: KeyEventState::NONE,
784 })
785 }
786
787 #[test]
788 fn from_crossterm_maps_modifier_key() {
789 let raw = ct_modifier_event(ModifierKeyCode::LeftControl, CtKeyEventKind::Press);
790 let converted = from_crossterm(raw);
791 match converted {
792 Some(Event::Key(k)) => {
793 assert_eq!(k.code, KeyCode::Modifier(ModifierKey::LeftCtrl));
794 assert!(matches!(k.kind, KeyEventKind::Press));
795 }
796 other => panic!("expected modifier key event, got {other:?}"),
797 }
798 }
799
800 #[test]
801 fn from_crossterm_modifier_release() {
802 let raw = ct_modifier_event(ModifierKeyCode::LeftControl, CtKeyEventKind::Release);
803 let converted = from_crossterm(raw);
804 match converted {
805 Some(Event::Key(k)) => {
806 assert_eq!(k.code, KeyCode::Modifier(ModifierKey::LeftCtrl));
807 assert!(matches!(k.kind, KeyEventKind::Release));
808 }
809 other => panic!("expected modifier release event, got {other:?}"),
810 }
811 }
812
813 #[test]
814 fn from_crossterm_modifier_key_exhaustive() {
815 let cases: [(ModifierKeyCode, ModifierKey); 14] = [
817 (ModifierKeyCode::LeftShift, ModifierKey::LeftShift),
818 (ModifierKeyCode::LeftControl, ModifierKey::LeftCtrl),
819 (ModifierKeyCode::LeftAlt, ModifierKey::LeftAlt),
820 (ModifierKeyCode::LeftSuper, ModifierKey::LeftSuper),
821 (ModifierKeyCode::RightShift, ModifierKey::RightShift),
822 (ModifierKeyCode::RightControl, ModifierKey::RightCtrl),
823 (ModifierKeyCode::RightAlt, ModifierKey::RightAlt),
824 (ModifierKeyCode::RightSuper, ModifierKey::RightSuper),
825 (ModifierKeyCode::LeftHyper, ModifierKey::LeftHyper),
826 (ModifierKeyCode::LeftMeta, ModifierKey::LeftMeta),
827 (ModifierKeyCode::RightHyper, ModifierKey::RightHyper),
828 (ModifierKeyCode::RightMeta, ModifierKey::RightMeta),
829 (ModifierKeyCode::IsoLevel3Shift, ModifierKey::IsoLevel3Shift),
830 (ModifierKeyCode::IsoLevel5Shift, ModifierKey::IsoLevel5Shift),
831 ];
832 for (ct, expected) in cases {
833 let raw = ct_modifier_event(ct, CtKeyEventKind::Press);
834 match from_crossterm(raw) {
835 Some(Event::Key(k)) => {
836 assert_eq!(k.code, KeyCode::Modifier(expected), "mismatch for {ct:?}")
837 }
838 other => panic!("expected modifier event for {ct:?}, got {other:?}"),
839 }
840 }
841 }
842}