Skip to main content

dartboard_cli/
input.rs

1use crossterm::event::{
2    Event, KeyCode, KeyEvent, KeyEventKind, KeyModifiers, MouseButton, MouseEvent, MouseEventKind,
3};
4pub use dartboard_editor::{
5    AppIntent, AppKey, AppKeyCode, AppModifiers, AppPointerButton, AppPointerEvent, AppPointerKind,
6};
7
8fn app_modifiers_from_crossterm(modifiers: KeyModifiers) -> AppModifiers {
9    AppModifiers {
10        ctrl: modifiers.contains(KeyModifiers::CONTROL),
11        alt: modifiers.contains(KeyModifiers::ALT),
12        shift: modifiers.contains(KeyModifiers::SHIFT),
13        meta: modifiers.contains(KeyModifiers::META),
14    }
15}
16
17pub fn app_key_from_crossterm(key: KeyEvent) -> Option<AppKey> {
18    if key.kind != KeyEventKind::Press {
19        return None;
20    }
21
22    let code = match key.code {
23        KeyCode::Backspace => AppKeyCode::Backspace,
24        KeyCode::Enter => AppKeyCode::Enter,
25        KeyCode::Left => AppKeyCode::Left,
26        KeyCode::Right => AppKeyCode::Right,
27        KeyCode::Up => AppKeyCode::Up,
28        KeyCode::Down => AppKeyCode::Down,
29        KeyCode::Home => AppKeyCode::Home,
30        KeyCode::End => AppKeyCode::End,
31        KeyCode::PageUp => AppKeyCode::PageUp,
32        KeyCode::PageDown => AppKeyCode::PageDown,
33        KeyCode::Tab => AppKeyCode::Tab,
34        KeyCode::BackTab => AppKeyCode::BackTab,
35        KeyCode::Delete => AppKeyCode::Delete,
36        KeyCode::Esc => AppKeyCode::Esc,
37        KeyCode::F(n) => AppKeyCode::F(n),
38        KeyCode::Char(ch) => AppKeyCode::Char(ch),
39        _ => return None,
40    };
41
42    Some(AppKey {
43        code,
44        modifiers: app_modifiers_from_crossterm(key.modifiers),
45    })
46}
47
48pub fn app_pointer_event_from_crossterm(mouse: MouseEvent) -> Option<AppPointerEvent> {
49    let map_button = |button: MouseButton| match button {
50        MouseButton::Left => Some(AppPointerButton::Left),
51        MouseButton::Right => Some(AppPointerButton::Right),
52        MouseButton::Middle => Some(AppPointerButton::Middle),
53    };
54
55    let kind = match mouse.kind {
56        MouseEventKind::Down(button) => AppPointerKind::Down(map_button(button)?),
57        MouseEventKind::Up(button) => AppPointerKind::Up(map_button(button)?),
58        MouseEventKind::Drag(button) => AppPointerKind::Drag(map_button(button)?),
59        MouseEventKind::Moved => AppPointerKind::Moved,
60        MouseEventKind::ScrollUp => AppPointerKind::ScrollUp,
61        MouseEventKind::ScrollDown => AppPointerKind::ScrollDown,
62        MouseEventKind::ScrollLeft => AppPointerKind::ScrollLeft,
63        MouseEventKind::ScrollRight => AppPointerKind::ScrollRight,
64    };
65
66    Some(AppPointerEvent {
67        column: mouse.column,
68        row: mouse.row,
69        kind,
70        modifiers: app_modifiers_from_crossterm(mouse.modifiers),
71    })
72}
73
74pub fn app_intent_from_crossterm(event: Event) -> Option<AppIntent> {
75    match event {
76        Event::Key(key) => app_key_from_crossterm(key).map(AppIntent::KeyPress),
77        Event::Mouse(mouse) => app_pointer_event_from_crossterm(mouse).map(AppIntent::Pointer),
78        Event::Paste(data) => Some(AppIntent::Paste(data)),
79        _ => None,
80    }
81}
82
83#[cfg(test)]
84mod tests {
85    use super::{
86        app_intent_from_crossterm, app_key_from_crossterm, app_pointer_event_from_crossterm,
87        AppIntent, AppKey, AppKeyCode, AppModifiers, AppPointerButton, AppPointerEvent,
88        AppPointerKind,
89    };
90    use crossterm::event::{
91        Event, KeyCode, KeyEvent, KeyEventKind, KeyEventState, KeyModifiers, MouseEvent,
92        MouseEventKind,
93    };
94
95    #[test]
96    fn key_adapter_ignores_non_press_events() {
97        let key = KeyEvent {
98            code: KeyCode::Char('x'),
99            modifiers: KeyModifiers::CONTROL,
100            kind: KeyEventKind::Release,
101            state: KeyEventState::NONE,
102        };
103
104        assert_eq!(app_key_from_crossterm(key), None);
105    }
106
107    #[test]
108    fn pointer_adapter_maps_drag_and_modifiers() {
109        let mouse = MouseEvent {
110            kind: MouseEventKind::Drag(crossterm::event::MouseButton::Left),
111            column: 12,
112            row: 7,
113            modifiers: KeyModifiers::SHIFT | KeyModifiers::ALT,
114        };
115
116        assert_eq!(
117            app_pointer_event_from_crossterm(mouse),
118            Some(AppPointerEvent {
119                column: 12,
120                row: 7,
121                kind: AppPointerKind::Drag(AppPointerButton::Left),
122                modifiers: AppModifiers {
123                    alt: true,
124                    shift: true,
125                    ..Default::default()
126                },
127            })
128        );
129    }
130
131    #[test]
132    fn pointer_adapter_maps_horizontal_scroll() {
133        let mouse = MouseEvent {
134            kind: MouseEventKind::ScrollRight,
135            column: 12,
136            row: 7,
137            modifiers: KeyModifiers::NONE,
138        };
139
140        assert_eq!(
141            app_pointer_event_from_crossterm(mouse),
142            Some(AppPointerEvent {
143                column: 12,
144                row: 7,
145                kind: AppPointerKind::ScrollRight,
146                modifiers: AppModifiers::default(),
147            })
148        );
149    }
150
151    #[test]
152    fn event_adapter_maps_paste_and_keys() {
153        assert_eq!(
154            app_intent_from_crossterm(Event::Paste("hi".to_string())),
155            Some(AppIntent::Paste("hi".to_string()))
156        );
157
158        let enter = app_intent_from_crossterm(Event::Key(KeyEvent::new(
159            KeyCode::Enter,
160            KeyModifiers::NONE,
161        )));
162        assert_eq!(
163            enter,
164            Some(AppIntent::KeyPress(AppKey {
165                code: AppKeyCode::Enter,
166                modifiers: AppModifiers::default(),
167            }))
168        );
169    }
170}