ratatui_widgets/events/
crossterm.rs

1use crossterm::event::{
2    Event as CrosstermEvent, KeyCode as CrosstermKeyCode, KeyEvent as CrosstermKeyEvent,
3    KeyEventKind, KeyModifiers as CrosstermKeyModifiers, MouseEvent as CrosstermMouseEvent,
4};
5use thiserror::Error;
6
7use super::MouseEventKind;
8use super::{Event, Key, KeyModifiers, KeyPressedEvent, MouseButton, MouseEvent};
9
10#[derive(Debug, Clone, PartialEq, Eq, Hash, Error)]
11pub enum ConversionError {
12    #[error("Unknown crossterm event: {event:?}")]
13    UnknownEvent { event: CrosstermEvent },
14    #[error("Unknown crossterm key code: {key_code:?}")]
15    UnknownKey { key_code: CrosstermKeyCode },
16    #[error("Unknown crossterm modifiers: {modifiers:?}")]
17    UnknownModifiers { modifiers: u8 },
18    #[error("Key repeated event not supported")]
19    KeyReleasedEventNotSupported,
20    #[error("Key repeated event not supported")]
21    KeyRepeatedEventNotSupported,
22}
23
24impl TryFrom<CrosstermEvent> for Event {
25    type Error = ConversionError;
26    fn try_from(event: CrosstermEvent) -> Result<Self, Self::Error> {
27        use CrosstermEvent::*;
28        let event = match event {
29            Key(key_event) => Event::KeyPressed(key_event.try_into()?),
30            Mouse(mouse_event) => Event::Mouse(mouse_event.into()),
31            _ => return Err(ConversionError::UnknownEvent { event }),
32            // TODO maybe handle these later if needed
33            // FocusGained => todo!(),
34            // FocusLost => todo!(),
35            // Paste(_) => todo!(),
36            // Resize(_, _) => todo!(),
37        };
38        Ok(event)
39    }
40}
41
42impl TryFrom<CrosstermKeyEvent> for KeyPressedEvent {
43    type Error = ConversionError;
44    fn try_from(key_event: CrosstermKeyEvent) -> Result<Self, Self::Error> {
45        match key_event.kind {
46            KeyEventKind::Press => Ok(KeyPressedEvent {
47                key: key_event.code.try_into()?,
48                modifiers: key_event.modifiers.into(),
49            }),
50            KeyEventKind::Release => Err(ConversionError::KeyReleasedEventNotSupported),
51            KeyEventKind::Repeat => Err(ConversionError::KeyRepeatedEventNotSupported),
52        }
53    }
54}
55
56impl TryFrom<CrosstermKeyCode> for Key {
57    type Error = ConversionError;
58    fn try_from(key_code: CrosstermKeyCode) -> Result<Self, Self::Error> {
59        use Key::*;
60        let key = match key_code {
61            CrosstermKeyCode::Char(c) => Char(c),
62            CrosstermKeyCode::Esc => Esc,
63            CrosstermKeyCode::Enter => Enter,
64            CrosstermKeyCode::Tab => Tab,
65            CrosstermKeyCode::BackTab => BackTab,
66            CrosstermKeyCode::Backspace => Backspace,
67            CrosstermKeyCode::Delete => Delete,
68            CrosstermKeyCode::Insert => Insert,
69            CrosstermKeyCode::Left => Left,
70            CrosstermKeyCode::Right => Right,
71            CrosstermKeyCode::Up => Up,
72            CrosstermKeyCode::Down => Down,
73            CrosstermKeyCode::Home => Home,
74            CrosstermKeyCode::End => End,
75            CrosstermKeyCode::PageUp => PageUp,
76            CrosstermKeyCode::PageDown => PageDown,
77            CrosstermKeyCode::F(num) => F(num),
78            CrosstermKeyCode::Null => Null,
79
80            key_code => return Err(ConversionError::UnknownKey { key_code }),
81            // TODO maybe handle these later if needed
82            // CrosstermKeyCode::CapsLock => todo!(),
83            // CrosstermKeyCode::ScrollLock => todo!(),
84            // CrosstermKeyCode::NumLock => todo!(),
85            // CrosstermKeyCode::PrintScreen => todo!(),
86            // CrosstermKeyCode::Pause => todo!(),
87            // CrosstermKeyCode::Menu => todo!(),
88            // CrosstermKeyCode::KeypadBegin => todo!(),
89            // CrosstermKeyCode::Media(_) => todo!(),
90            // CrosstermKeyCode::Modifier(_) => todo!(),
91        };
92        Ok(key)
93    }
94}
95
96impl From<CrosstermKeyModifiers> for KeyModifiers {
97    fn from(modifiers: CrosstermKeyModifiers) -> Self {
98        // our modifiers area superset of crossterm's modifiers and are bit compatible so this is
99        // safe
100        KeyModifiers::from_bits(modifiers.bits()).unwrap()
101    }
102}
103
104impl From<CrosstermMouseEvent> for MouseEvent {
105    fn from(mouse_event: CrosstermMouseEvent) -> Self {
106        MouseEvent {
107            column: mouse_event.column,
108            row: mouse_event.row,
109            kind: mouse_event.kind.into(),
110            modifiers: mouse_event.modifiers.into(),
111        }
112    }
113}
114use crossterm::event::MouseButton as CrosstermMouseButton;
115use crossterm::event::MouseEventKind as CrosstermMouseEventKind;
116
117impl From<CrosstermMouseButton> for MouseButton {
118    fn from(mouse_button: CrosstermMouseButton) -> Self {
119        match mouse_button {
120            CrosstermMouseButton::Left => MouseButton::Left,
121            CrosstermMouseButton::Right => MouseButton::Right,
122            CrosstermMouseButton::Middle => MouseButton::Middle,
123        }
124    }
125}
126
127impl From<CrosstermMouseEventKind> for MouseEventKind {
128    fn from(mouse_event_kind: CrosstermMouseEventKind) -> Self {
129        match mouse_event_kind {
130            CrosstermMouseEventKind::Down(button) => MouseEventKind::Down(button.into()),
131            CrosstermMouseEventKind::Up(button) => MouseEventKind::Up(button.into()),
132            CrosstermMouseEventKind::Drag(button) => MouseEventKind::Drag(button.into()),
133            CrosstermMouseEventKind::Moved => MouseEventKind::Moved,
134            CrosstermMouseEventKind::ScrollUp => MouseEventKind::ScrollUp,
135            CrosstermMouseEventKind::ScrollDown => MouseEventKind::ScrollDown,
136            CrosstermMouseEventKind::ScrollLeft => MouseEventKind::ScrollLeft,
137            CrosstermMouseEventKind::ScrollRight => MouseEventKind::ScrollRight,
138        }
139    }
140}
141
142#[cfg(test)]
143mod tests {
144    use super::*;
145    use rstest::rstest;
146
147    #[rstest]
148    #[case(CrosstermKeyModifiers::empty(), KeyModifiers::empty())]
149    #[case(CrosstermKeyModifiers::NONE, KeyModifiers::empty())]
150    #[case(CrosstermKeyModifiers::SHIFT, KeyModifiers::SHIFT)]
151    #[case(CrosstermKeyModifiers::CONTROL, KeyModifiers::CTRL)]
152    #[case(CrosstermKeyModifiers::CONTROL, KeyModifiers::CONTROL)]
153    #[case(CrosstermKeyModifiers::ALT, KeyModifiers::ALT)]
154    #[case(CrosstermKeyModifiers::ALT, KeyModifiers::OPTION)]
155    #[case(CrosstermKeyModifiers::SUPER, KeyModifiers::SUPER)]
156    #[case(CrosstermKeyModifiers::SUPER, KeyModifiers::WIN)]
157    #[case(CrosstermKeyModifiers::SUPER, KeyModifiers::COMMAND)]
158    #[case(CrosstermKeyModifiers::HYPER, KeyModifiers::HYPER)]
159    #[case(CrosstermKeyModifiers::META, KeyModifiers::META)]
160    #[case(
161        CrosstermKeyModifiers::all(),
162        KeyModifiers::SHIFT | KeyModifiers::CTRL | KeyModifiers::ALT | KeyModifiers::SUPER |
163        KeyModifiers::HYPER | KeyModifiers::META,
164    )]
165    fn try_from_modifiers(
166        #[case] modifiers: CrosstermKeyModifiers,
167        #[case] expected: KeyModifiers,
168    ) {
169        let result = KeyModifiers::from(modifiers);
170        assert_eq!(result, expected);
171    }
172}