use retroglyph_core::event::{
Event, KeyCode, KeyEvent, KeyEventKind, KeyLocation, KeyModifiers, ModifierKey, MouseButton,
PhysicalPos,
};
use retroglyph_core::grid::Pos;
fn key_code_from_logical(key: &winit::keyboard::Key, modifiers: KeyModifiers) -> Option<KeyCode> {
use winit::keyboard::{Key, NamedKey};
Some(match key {
Key::Named(NamedKey::Enter) => KeyCode::Enter,
Key::Named(NamedKey::Escape) => KeyCode::Escape,
Key::Named(NamedKey::Backspace) => KeyCode::Backspace,
Key::Named(NamedKey::Delete) => KeyCode::Delete,
Key::Named(NamedKey::Insert) => KeyCode::Insert,
Key::Named(NamedKey::Tab) if modifiers.contains(KeyModifiers::SHIFT) => KeyCode::BackTab,
Key::Named(NamedKey::Tab) => KeyCode::Tab,
Key::Named(NamedKey::Space) => KeyCode::Char(' '),
Key::Named(NamedKey::ArrowUp) => KeyCode::Up,
Key::Named(NamedKey::ArrowDown) => KeyCode::Down,
Key::Named(NamedKey::ArrowLeft) => KeyCode::Left,
Key::Named(NamedKey::ArrowRight) => KeyCode::Right,
Key::Named(NamedKey::Home) => KeyCode::Home,
Key::Named(NamedKey::End) => KeyCode::End,
Key::Named(NamedKey::PageUp) => KeyCode::PageUp,
Key::Named(NamedKey::PageDown) => KeyCode::PageDown,
Key::Named(NamedKey::F1) => KeyCode::F(1),
Key::Named(NamedKey::F2) => KeyCode::F(2),
Key::Named(NamedKey::F3) => KeyCode::F(3),
Key::Named(NamedKey::F4) => KeyCode::F(4),
Key::Named(NamedKey::F5) => KeyCode::F(5),
Key::Named(NamedKey::F6) => KeyCode::F(6),
Key::Named(NamedKey::F7) => KeyCode::F(7),
Key::Named(NamedKey::F8) => KeyCode::F(8),
Key::Named(NamedKey::F9) => KeyCode::F(9),
Key::Named(NamedKey::F10) => KeyCode::F(10),
Key::Named(NamedKey::F11) => KeyCode::F(11),
Key::Named(NamedKey::F12) => KeyCode::F(12),
Key::Named(NamedKey::Shift) => KeyCode::Modifier(ModifierKey::Shift),
Key::Named(NamedKey::Control) => KeyCode::Modifier(ModifierKey::Control),
Key::Named(NamedKey::Alt) => KeyCode::Modifier(ModifierKey::Alt),
Key::Named(NamedKey::Super) => KeyCode::Modifier(ModifierKey::Super),
Key::Named(NamedKey::CapsLock) => KeyCode::CapsLock,
Key::Named(NamedKey::ScrollLock) => KeyCode::ScrollLock,
Key::Named(NamedKey::NumLock) => KeyCode::NumLock,
Key::Named(NamedKey::PrintScreen) => KeyCode::PrintScreen,
Key::Named(NamedKey::Pause) => KeyCode::Pause,
Key::Named(NamedKey::ContextMenu) => KeyCode::Menu,
Key::Character(s) => KeyCode::Char(s.chars().next()?),
_ => return None,
})
}
#[must_use]
pub fn translate_ime(ime: winit::event::Ime) -> Option<Event> {
match ime {
winit::event::Ime::Commit(text) if !text.is_empty() => Some(Event::Paste(text)),
_ => None,
}
}
#[must_use]
#[allow(clippy::needless_pass_by_value)]
pub fn translate_key(input: winit::event::KeyEvent, modifiers: KeyModifiers) -> Option<Event> {
let kind = key_event_kind(input.state, input.repeat);
let code = key_code_from_logical(&input.logical_key, modifiers)?;
let location = translate_key_location(input.location);
Some(Event::Key(KeyEvent::with_location(
code, modifiers, kind, location,
)))
}
#[must_use]
pub const fn translate_key_location(location: winit::keyboard::KeyLocation) -> KeyLocation {
use winit::keyboard::KeyLocation as WL;
match location {
WL::Standard => KeyLocation::Standard,
WL::Left => KeyLocation::Left,
WL::Right => KeyLocation::Right,
WL::Numpad => KeyLocation::Numpad,
}
}
#[must_use]
#[allow(clippy::cast_sign_loss, clippy::cast_possible_truncation)]
pub const fn physical_pos_from(x: f64, y: f64) -> PhysicalPos {
PhysicalPos {
x: x.max(0.0) as u32,
y: y.max(0.0) as u32,
}
}
#[must_use]
#[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)]
pub fn pixel_to_cell(px_x: f64, px_y: f64, cell_w: u32, cell_h: u32) -> Pos {
let col =
u32::checked_div(px_x.max(0.0) as u32, cell_w).map_or(0, |v| v.min(u32::from(u16::MAX)));
let col = u16::try_from(col).unwrap_or(u16::MAX);
let row =
u32::checked_div(px_y.max(0.0) as u32, cell_h).map_or(0, |v| v.min(u32::from(u16::MAX)));
let row = u16::try_from(row).unwrap_or(u16::MAX);
Pos { x: col, y: row }
}
#[must_use]
pub const fn translate_mouse_button(button: winit::event::MouseButton) -> Option<MouseButton> {
match button {
winit::event::MouseButton::Left => Some(MouseButton::Left),
winit::event::MouseButton::Right => Some(MouseButton::Right),
winit::event::MouseButton::Middle => Some(MouseButton::Middle),
_ => None,
}
}
#[must_use]
pub const fn key_event_kind(state: winit::event::ElementState, repeat: bool) -> KeyEventKind {
use winit::event::ElementState;
match (state, repeat) {
(ElementState::Pressed, false) => KeyEventKind::Press,
(ElementState::Pressed, true) => KeyEventKind::Repeat,
(ElementState::Released, _) => KeyEventKind::Release,
}
}
#[must_use]
pub fn translate_modifiers(state: winit::keyboard::ModifiersState) -> KeyModifiers {
let mut m = KeyModifiers::NONE;
if state.shift_key() {
m |= KeyModifiers::SHIFT;
}
if state.control_key() {
m |= KeyModifiers::CONTROL;
}
if state.alt_key() {
m |= KeyModifiers::ALT;
}
if state.super_key() {
m |= KeyModifiers::SUPER;
}
m
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn space_maps_to_char_space() {
let key = winit::keyboard::Key::Named(winit::keyboard::NamedKey::Space);
assert_eq!(
key_code_from_logical(&key, KeyModifiers::NONE),
Some(KeyCode::Char(' '))
);
}
#[test]
fn shift_tab_normalizes_to_backtab() {
let key = winit::keyboard::Key::Named(winit::keyboard::NamedKey::Tab);
assert_eq!(
key_code_from_logical(&key, KeyModifiers::SHIFT),
Some(KeyCode::BackTab)
);
}
#[test]
fn plain_tab_is_unaffected() {
let key = winit::keyboard::Key::Named(winit::keyboard::NamedKey::Tab);
assert_eq!(
key_code_from_logical(&key, KeyModifiers::NONE),
Some(KeyCode::Tab)
);
}
#[test]
fn shift_modifier_on_non_tab_keys_is_unaffected() {
let key = winit::keyboard::Key::Character("a".into());
assert_eq!(
key_code_from_logical(&key, KeyModifiers::SHIFT),
Some(KeyCode::Char('a'))
);
}
#[test]
fn left_shift_alone_maps_to_modifier_shift_with_left_location() {
let key = winit::keyboard::Key::Named(winit::keyboard::NamedKey::Shift);
assert_eq!(
key_code_from_logical(&key, KeyModifiers::SHIFT),
Some(KeyCode::Modifier(ModifierKey::Shift))
);
assert_eq!(
translate_key_location(winit::keyboard::KeyLocation::Left),
KeyLocation::Left
);
}
#[test]
fn caps_lock_maps_straight_through() {
let key = winit::keyboard::Key::Named(winit::keyboard::NamedKey::CapsLock);
assert_eq!(
key_code_from_logical(&key, KeyModifiers::NONE),
Some(KeyCode::CapsLock)
);
}
#[test]
fn unmapped_key_returns_none() {
let key = winit::keyboard::Key::Named(winit::keyboard::NamedKey::AudioVolumeUp);
assert_eq!(key_code_from_logical(&key, KeyModifiers::NONE), None);
}
#[test]
fn ime_commit_maps_to_paste_event() {
let ime = winit::event::Ime::Commit("hello".to_string());
assert_eq!(translate_ime(ime), Some(Event::Paste("hello".to_string())));
}
#[test]
fn ime_empty_commit_produces_no_event() {
let ime = winit::event::Ime::Commit(String::new());
assert_eq!(translate_ime(ime), None);
}
#[test]
fn ime_enabled_produces_no_event() {
assert_eq!(translate_ime(winit::event::Ime::Enabled), None);
}
#[test]
fn ime_disabled_produces_no_event() {
assert_eq!(translate_ime(winit::event::Ime::Disabled), None);
}
#[test]
fn ime_preedit_produces_no_event() {
let ime = winit::event::Ime::Preedit("nihon".to_string(), Some((0, 5)));
assert_eq!(translate_ime(ime), None);
}
#[test]
fn pixel_to_cell_basic() {
let pos = pixel_to_cell(20.0, 48.0, 8, 16);
assert_eq!(pos, Pos { x: 2, y: 3 });
}
#[test]
fn pixel_to_cell_origin() {
let pos = pixel_to_cell(0.0, 0.0, 8, 16);
assert_eq!(pos, Pos { x: 0, y: 0 });
}
#[test]
fn pixel_to_cell_negative_coords_clamp_to_zero() {
let pos = pixel_to_cell(-5.0, -10.0, 8, 16);
assert_eq!(pos, Pos { x: 0, y: 0 });
}
#[test]
fn pixel_to_cell_zero_cell_size_returns_origin() {
let pos = pixel_to_cell(100.0, 200.0, 0, 0);
assert_eq!(pos, Pos { x: 0, y: 0 });
}
#[test]
fn pixel_to_cell_clamps_to_u16_max() {
let pos = pixel_to_cell(f64::from(u32::MAX), f64::from(u32::MAX), 1, 1);
assert_eq!(
pos,
Pos {
x: u16::MAX,
y: u16::MAX
}
);
}
#[test]
fn translate_modifiers_none() {
let state = winit::keyboard::ModifiersState::empty();
assert_eq!(translate_modifiers(state), KeyModifiers::NONE);
}
#[test]
fn translate_modifiers_super_only() {
let state = winit::keyboard::ModifiersState::SUPER;
let mods = translate_modifiers(state);
assert!(mods.contains(KeyModifiers::SUPER));
assert!(!mods.contains(KeyModifiers::SHIFT));
assert!(!mods.contains(KeyModifiers::CONTROL));
assert!(!mods.contains(KeyModifiers::ALT));
}
#[test]
fn translate_modifiers_super_without_super_key() {
let state = winit::keyboard::ModifiersState::SHIFT;
let mods = translate_modifiers(state);
assert!(!mods.contains(KeyModifiers::SUPER));
}
#[test]
fn translate_modifiers_super_combined_with_shift() {
let state = winit::keyboard::ModifiersState::SUPER | winit::keyboard::ModifiersState::SHIFT;
let mods = translate_modifiers(state);
assert!(mods.contains(KeyModifiers::SUPER));
assert!(mods.contains(KeyModifiers::SHIFT));
assert!(!mods.contains(KeyModifiers::CONTROL));
assert!(!mods.contains(KeyModifiers::ALT));
}
#[test]
fn translate_modifiers_all_together() {
let state = winit::keyboard::ModifiersState::SHIFT
| winit::keyboard::ModifiersState::CONTROL
| winit::keyboard::ModifiersState::ALT
| winit::keyboard::ModifiersState::SUPER;
let mods = translate_modifiers(state);
assert!(mods.contains(KeyModifiers::SHIFT));
assert!(mods.contains(KeyModifiers::CONTROL));
assert!(mods.contains(KeyModifiers::ALT));
assert!(mods.contains(KeyModifiers::SUPER));
}
#[test]
fn key_event_kind_press_repeat_release() {
use winit::event::ElementState;
assert_eq!(
key_event_kind(ElementState::Pressed, false),
KeyEventKind::Press
);
assert_eq!(
key_event_kind(ElementState::Pressed, true),
KeyEventKind::Repeat
);
assert_eq!(
key_event_kind(ElementState::Released, false),
KeyEventKind::Release
);
assert_eq!(
key_event_kind(ElementState::Released, true),
KeyEventKind::Release
);
}
#[test]
fn translate_key_location_maps_all_variants() {
use winit::keyboard::KeyLocation as WL;
assert_eq!(translate_key_location(WL::Standard), KeyLocation::Standard);
assert_eq!(translate_key_location(WL::Left), KeyLocation::Left);
assert_eq!(translate_key_location(WL::Right), KeyLocation::Right);
assert_eq!(translate_key_location(WL::Numpad), KeyLocation::Numpad);
}
#[test]
fn translate_mouse_button_left() {
assert_eq!(
translate_mouse_button(winit::event::MouseButton::Left),
Some(MouseButton::Left)
);
}
#[test]
fn translate_mouse_button_right() {
assert_eq!(
translate_mouse_button(winit::event::MouseButton::Right),
Some(MouseButton::Right)
);
}
#[test]
fn translate_mouse_button_middle() {
assert_eq!(
translate_mouse_button(winit::event::MouseButton::Middle),
Some(MouseButton::Middle)
);
}
#[test]
fn translate_mouse_button_other_is_none() {
assert_eq!(
translate_mouse_button(winit::event::MouseButton::Back),
None
);
assert_eq!(
translate_mouse_button(winit::event::MouseButton::Forward),
None
);
assert_eq!(
translate_mouse_button(winit::event::MouseButton::Other(7)),
None
);
}
}