use retroglyph_core::event::{
Event, KeyCode, KeyEvent, KeyEventKind, KeyModifiers, 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::Character(s) => KeyCode::Char(s.chars().next()?),
_ => return 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)?;
Some(Event::Key(KeyEvent::with_kind(code, modifiers, kind)))
}
#[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;
}
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 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 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 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_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
);
}
}