dartboard-cli 0.1.3

Terminal drawing board application for local and shared sessions
Documentation
use crossterm::event::{
    Event, KeyCode, KeyEvent, KeyEventKind, KeyModifiers, MouseButton, MouseEvent, MouseEventKind,
};
pub use dartboard_editor::{
    AppIntent, AppKey, AppKeyCode, AppModifiers, AppPointerButton, AppPointerEvent, AppPointerKind,
};

fn app_modifiers_from_crossterm(modifiers: KeyModifiers) -> AppModifiers {
    AppModifiers {
        ctrl: modifiers.contains(KeyModifiers::CONTROL),
        alt: modifiers.contains(KeyModifiers::ALT),
        shift: modifiers.contains(KeyModifiers::SHIFT),
        meta: modifiers.contains(KeyModifiers::META),
    }
}

pub fn app_key_from_crossterm(key: KeyEvent) -> Option<AppKey> {
    if key.kind != KeyEventKind::Press {
        return None;
    }

    let code = match key.code {
        KeyCode::Backspace => AppKeyCode::Backspace,
        KeyCode::Enter => AppKeyCode::Enter,
        KeyCode::Left => AppKeyCode::Left,
        KeyCode::Right => AppKeyCode::Right,
        KeyCode::Up => AppKeyCode::Up,
        KeyCode::Down => AppKeyCode::Down,
        KeyCode::Home => AppKeyCode::Home,
        KeyCode::End => AppKeyCode::End,
        KeyCode::PageUp => AppKeyCode::PageUp,
        KeyCode::PageDown => AppKeyCode::PageDown,
        KeyCode::Tab => AppKeyCode::Tab,
        KeyCode::BackTab => AppKeyCode::BackTab,
        KeyCode::Delete => AppKeyCode::Delete,
        KeyCode::Esc => AppKeyCode::Esc,
        KeyCode::F(n) => AppKeyCode::F(n),
        KeyCode::Char(ch) => AppKeyCode::Char(ch),
        _ => return None,
    };

    Some(AppKey {
        code,
        modifiers: app_modifiers_from_crossterm(key.modifiers),
    })
}

pub fn app_pointer_event_from_crossterm(mouse: MouseEvent) -> Option<AppPointerEvent> {
    let map_button = |button: MouseButton| match button {
        MouseButton::Left => Some(AppPointerButton::Left),
        MouseButton::Right => Some(AppPointerButton::Right),
        MouseButton::Middle => Some(AppPointerButton::Middle),
    };

    let kind = match mouse.kind {
        MouseEventKind::Down(button) => AppPointerKind::Down(map_button(button)?),
        MouseEventKind::Up(button) => AppPointerKind::Up(map_button(button)?),
        MouseEventKind::Drag(button) => AppPointerKind::Drag(map_button(button)?),
        MouseEventKind::Moved => AppPointerKind::Moved,
        MouseEventKind::ScrollUp => AppPointerKind::ScrollUp,
        MouseEventKind::ScrollDown => AppPointerKind::ScrollDown,
        MouseEventKind::ScrollLeft => AppPointerKind::ScrollLeft,
        MouseEventKind::ScrollRight => AppPointerKind::ScrollRight,
    };

    Some(AppPointerEvent {
        column: mouse.column,
        row: mouse.row,
        kind,
        modifiers: app_modifiers_from_crossterm(mouse.modifiers),
    })
}

pub fn app_intent_from_crossterm(event: Event) -> Option<AppIntent> {
    match event {
        Event::Key(key) => app_key_from_crossterm(key).map(AppIntent::KeyPress),
        Event::Mouse(mouse) => app_pointer_event_from_crossterm(mouse).map(AppIntent::Pointer),
        Event::Paste(data) => Some(AppIntent::Paste(data)),
        _ => None,
    }
}

#[cfg(test)]
mod tests {
    use super::{
        app_intent_from_crossterm, app_key_from_crossterm, app_pointer_event_from_crossterm,
        AppIntent, AppKey, AppKeyCode, AppModifiers, AppPointerButton, AppPointerEvent,
        AppPointerKind,
    };
    use crossterm::event::{
        Event, KeyCode, KeyEvent, KeyEventKind, KeyEventState, KeyModifiers, MouseEvent,
        MouseEventKind,
    };

    #[test]
    fn key_adapter_ignores_non_press_events() {
        let key = KeyEvent {
            code: KeyCode::Char('x'),
            modifiers: KeyModifiers::CONTROL,
            kind: KeyEventKind::Release,
            state: KeyEventState::NONE,
        };

        assert_eq!(app_key_from_crossterm(key), None);
    }

    #[test]
    fn pointer_adapter_maps_drag_and_modifiers() {
        let mouse = MouseEvent {
            kind: MouseEventKind::Drag(crossterm::event::MouseButton::Left),
            column: 12,
            row: 7,
            modifiers: KeyModifiers::SHIFT | KeyModifiers::ALT,
        };

        assert_eq!(
            app_pointer_event_from_crossterm(mouse),
            Some(AppPointerEvent {
                column: 12,
                row: 7,
                kind: AppPointerKind::Drag(AppPointerButton::Left),
                modifiers: AppModifiers {
                    alt: true,
                    shift: true,
                    ..Default::default()
                },
            })
        );
    }

    #[test]
    fn pointer_adapter_maps_horizontal_scroll() {
        let mouse = MouseEvent {
            kind: MouseEventKind::ScrollRight,
            column: 12,
            row: 7,
            modifiers: KeyModifiers::NONE,
        };

        assert_eq!(
            app_pointer_event_from_crossterm(mouse),
            Some(AppPointerEvent {
                column: 12,
                row: 7,
                kind: AppPointerKind::ScrollRight,
                modifiers: AppModifiers::default(),
            })
        );
    }

    #[test]
    fn event_adapter_maps_paste_and_keys() {
        assert_eq!(
            app_intent_from_crossterm(Event::Paste("hi".to_string())),
            Some(AppIntent::Paste("hi".to_string()))
        );

        let enter = app_intent_from_crossterm(Event::Key(KeyEvent::new(
            KeyCode::Enter,
            KeyModifiers::NONE,
        )));
        assert_eq!(
            enter,
            Some(AppIntent::KeyPress(AppKey {
                code: AppKeyCode::Enter,
                modifiers: AppModifiers::default(),
            }))
        );
    }
}