use crate::input::{Event, KeyCode, KeyEventKind, Mods as InMods, MouseKind as InMouseKind};
use crate::ui::{Key, KeyEvent, Mods, MouseButton, MouseEvent, MouseKind, UiEvent};
pub(crate) fn convert_mods(m: InMods) -> Mods {
let mut out = Mods::NONE;
if m.contains(InMods::SHIFT) {
out = out | Mods::SHIFT;
}
if m.contains(InMods::CTRL) {
out = out | Mods::CTRL;
}
if m.contains(InMods::ALT) {
out = out | Mods::ALT;
}
if m.contains(InMods::SUPER) {
out = out | Mods::SUPER;
}
out
}
fn convert_key(code: KeyCode) -> Option<Key> {
Some(match code {
KeyCode::Char(c) => Key::Char(c),
KeyCode::Enter => Key::Enter,
KeyCode::Tab => Key::Tab,
KeyCode::Backspace => Key::Backspace,
KeyCode::Esc => Key::Escape,
KeyCode::Left => Key::Left,
KeyCode::Right => Key::Right,
KeyCode::Up => Key::Up,
KeyCode::Down => Key::Down,
KeyCode::Home => Key::Home,
KeyCode::End => Key::End,
KeyCode::PageUp => Key::PageUp,
KeyCode::PageDown => Key::PageDown,
KeyCode::Insert => Key::Insert,
KeyCode::Delete => Key::Delete,
KeyCode::F(n) => Key::F(n),
KeyCode::CapsLock
| KeyCode::ScrollLock
| KeyCode::NumLock
| KeyCode::PrintScreen
| KeyCode::Pause
| KeyCode::Menu
| KeyCode::Modifier(_)
| KeyCode::Functional(_)
| KeyCode::Unidentified => return None,
})
}
pub(crate) fn convert_event(event: &Event) -> Option<UiEvent> {
match event {
Event::Key(k) => {
if k.kind == KeyEventKind::Release {
return None;
}
let key = convert_key(k.code)?;
Some(UiEvent::Key(KeyEvent::new(
key,
convert_mods(k.mods.without_locks()),
)))
}
Event::Mouse(m) => {
let button = |b: crate::input::MouseButton| -> Option<MouseButton> {
match b {
crate::input::MouseButton::Left => Some(MouseButton::Left),
crate::input::MouseButton::Middle => Some(MouseButton::Middle),
crate::input::MouseButton::Right => Some(MouseButton::Right),
_ => None,
}
};
let kind = match m.kind {
InMouseKind::Down => MouseKind::Down(button(m.button)?),
InMouseKind::Up => MouseKind::Up(button(m.button)?),
InMouseKind::Drag => MouseKind::Drag(button(m.button)?),
InMouseKind::Move => MouseKind::Move,
InMouseKind::WheelUp => MouseKind::ScrollUp,
InMouseKind::WheelDown => MouseKind::ScrollDown,
InMouseKind::WheelLeft => MouseKind::ScrollLeft,
InMouseKind::WheelRight => MouseKind::ScrollRight,
};
Some(UiEvent::Mouse(MouseEvent {
pos: m.pos,
kind,
mods: convert_mods(m.mods),
}))
}
Event::Paste(s) => Some(UiEvent::Paste(s.clone())),
Event::FocusGained
| Event::FocusLost
| Event::Resize(_)
| Event::CapsReply(_)
| Event::Unknown(_) => None,
}
}
pub(crate) fn is_default_quit(event: &UiEvent) -> bool {
matches!(
event,
UiEvent::Key(k) if k.key == Key::Char('c') && k.mods == Mods::CTRL
)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::input::KeyEvent as InKeyEvent;
#[test]
fn keys_and_mods_map_field_by_field() {
let ev = Event::Key(InKeyEvent::char('s').with_mods(InMods::CTRL | InMods::ALT));
let Some(UiEvent::Key(k)) = convert_event(&ev) else {
panic!("must convert")
};
assert_eq!(k.key, Key::Char('s'));
assert!(k.mods.contains(Mods::CTRL) && k.mods.contains(Mods::ALT));
assert!(!k.mods.contains(Mods::SHIFT));
}
#[test]
fn locks_strip_and_releases_drop() {
let ev = Event::Key(InKeyEvent::char('s').with_mods(InMods::CTRL | InMods::CAPS_LOCK));
let Some(UiEvent::Key(k)) = convert_event(&ev) else {
panic!()
};
assert_eq!(k.mods, Mods::CTRL, "caps lock must not break chords");
let mut rel = InKeyEvent::char('a');
rel.kind = KeyEventKind::Release;
assert_eq!(convert_event(&Event::Key(rel)), None);
}
#[test]
fn ctrl_c_is_the_default_quit() {
let ev = Event::Key(InKeyEvent::char('c').with_mods(InMods::CTRL));
let ui = convert_event(&ev).expect("converts");
assert!(is_default_quit(&ui));
let plain = convert_event(&Event::Key(InKeyEvent::plain(KeyCode::Char('c')))).unwrap();
assert!(!is_default_quit(&plain));
}
}