1use retroglyph_core::event::{
6 Event, KeyCode, KeyEvent, KeyEventKind, KeyLocation, KeyModifiers, ModifierKey, MouseButton,
7 PhysicalPos,
8};
9use retroglyph_core::grid::Pos;
10
11fn key_code_from_logical(key: &winit::keyboard::Key, modifiers: KeyModifiers) -> Option<KeyCode> {
18 use winit::keyboard::{Key, NamedKey};
19
20 Some(match key {
21 Key::Named(NamedKey::Enter) => KeyCode::Enter,
22 Key::Named(NamedKey::Escape) => KeyCode::Escape,
23 Key::Named(NamedKey::Backspace) => KeyCode::Backspace,
24 Key::Named(NamedKey::Delete) => KeyCode::Delete,
25 Key::Named(NamedKey::Insert) => KeyCode::Insert,
26 Key::Named(NamedKey::Tab) if modifiers.contains(KeyModifiers::SHIFT) => KeyCode::BackTab,
32 Key::Named(NamedKey::Tab) => KeyCode::Tab,
33 Key::Named(NamedKey::Space) => KeyCode::Char(' '),
38 Key::Named(NamedKey::ArrowUp) => KeyCode::Up,
39 Key::Named(NamedKey::ArrowDown) => KeyCode::Down,
40 Key::Named(NamedKey::ArrowLeft) => KeyCode::Left,
41 Key::Named(NamedKey::ArrowRight) => KeyCode::Right,
42 Key::Named(NamedKey::Home) => KeyCode::Home,
43 Key::Named(NamedKey::End) => KeyCode::End,
44 Key::Named(NamedKey::PageUp) => KeyCode::PageUp,
45 Key::Named(NamedKey::PageDown) => KeyCode::PageDown,
46 Key::Named(NamedKey::F1) => KeyCode::F(1),
47 Key::Named(NamedKey::F2) => KeyCode::F(2),
48 Key::Named(NamedKey::F3) => KeyCode::F(3),
49 Key::Named(NamedKey::F4) => KeyCode::F(4),
50 Key::Named(NamedKey::F5) => KeyCode::F(5),
51 Key::Named(NamedKey::F6) => KeyCode::F(6),
52 Key::Named(NamedKey::F7) => KeyCode::F(7),
53 Key::Named(NamedKey::F8) => KeyCode::F(8),
54 Key::Named(NamedKey::F9) => KeyCode::F(9),
55 Key::Named(NamedKey::F10) => KeyCode::F(10),
56 Key::Named(NamedKey::F11) => KeyCode::F(11),
57 Key::Named(NamedKey::F12) => KeyCode::F(12),
58 Key::Named(NamedKey::Shift) => KeyCode::Modifier(ModifierKey::Shift),
62 Key::Named(NamedKey::Control) => KeyCode::Modifier(ModifierKey::Control),
63 Key::Named(NamedKey::Alt) => KeyCode::Modifier(ModifierKey::Alt),
64 Key::Named(NamedKey::Super) => KeyCode::Modifier(ModifierKey::Super),
65 Key::Named(NamedKey::CapsLock) => KeyCode::CapsLock,
66 Key::Named(NamedKey::ScrollLock) => KeyCode::ScrollLock,
67 Key::Named(NamedKey::NumLock) => KeyCode::NumLock,
68 Key::Named(NamedKey::PrintScreen) => KeyCode::PrintScreen,
69 Key::Named(NamedKey::Pause) => KeyCode::Pause,
70 Key::Named(NamedKey::ContextMenu) => KeyCode::Menu,
71 Key::Character(s) => KeyCode::Char(s.chars().next()?),
72 _ => return None,
73 })
74}
75
76#[must_use]
90pub fn translate_ime(ime: winit::event::Ime) -> Option<Event> {
91 match ime {
92 winit::event::Ime::Commit(text) if !text.is_empty() => Some(Event::Paste(text)),
93 _ => None,
94 }
95}
96
97#[must_use]
102#[allow(clippy::needless_pass_by_value)]
103pub fn translate_key(input: winit::event::KeyEvent, modifiers: KeyModifiers) -> Option<Event> {
104 let kind = translate_key_event_kind(input.state, input.repeat);
105 let code = key_code_from_logical(&input.logical_key, modifiers)?;
106 let location = translate_key_location(input.location);
107 Some(Event::Key(KeyEvent::with_location(
108 code, modifiers, kind, location,
109 )))
110}
111
112#[must_use]
114pub const fn translate_key_location(location: winit::keyboard::KeyLocation) -> KeyLocation {
115 use winit::keyboard::KeyLocation as WL;
116 match location {
117 WL::Standard => KeyLocation::Standard,
118 WL::Left => KeyLocation::Left,
119 WL::Right => KeyLocation::Right,
120 WL::Numpad => KeyLocation::Numpad,
121 }
122}
123
124#[must_use]
129#[allow(clippy::cast_sign_loss, clippy::cast_possible_truncation)]
130pub const fn translate_physical_pos(x: f64, y: f64) -> PhysicalPos {
131 PhysicalPos {
132 x: x.max(0.0) as u32,
133 y: y.max(0.0) as u32,
134 }
135}
136
137#[must_use]
151pub fn translate_pixel_to_cell(px_x: f64, px_y: f64, cell_w: u32, cell_h: u32) -> Pos {
152 Pos {
153 x: crate::geometry::pixel_to_cell_axis(px_x, cell_w),
154 y: crate::geometry::pixel_to_cell_axis(px_y, cell_h),
155 }
156}
157
158#[must_use]
162pub const fn translate_mouse_button(button: winit::event::MouseButton) -> Option<MouseButton> {
163 match button {
164 winit::event::MouseButton::Left => Some(MouseButton::Left),
165 winit::event::MouseButton::Right => Some(MouseButton::Right),
166 winit::event::MouseButton::Middle => Some(MouseButton::Middle),
167 _ => None,
168 }
169}
170
171#[must_use]
173pub const fn translate_key_event_kind(
174 state: winit::event::ElementState,
175 repeat: bool,
176) -> KeyEventKind {
177 use winit::event::ElementState;
178 match (state, repeat) {
179 (ElementState::Pressed, false) => KeyEventKind::Press,
180 (ElementState::Pressed, true) => KeyEventKind::Repeat,
181 (ElementState::Released, _) => KeyEventKind::Release,
182 }
183}
184
185#[must_use]
187pub fn translate_modifiers(state: winit::keyboard::ModifiersState) -> KeyModifiers {
188 KeyModifiers::from_parts(
189 state.shift_key(),
190 state.control_key(),
191 state.alt_key(),
192 state.super_key(),
193 )
194}
195
196#[cfg(test)]
197mod tests {
198 use super::*;
199
200 #[test]
203 fn space_maps_to_char_space() {
204 let key = winit::keyboard::Key::Named(winit::keyboard::NamedKey::Space);
208 assert_eq!(
209 key_code_from_logical(&key, KeyModifiers::NONE),
210 Some(KeyCode::Char(' '))
211 );
212 }
213
214 #[test]
215 fn shift_tab_normalizes_to_backtab() {
216 let key = winit::keyboard::Key::Named(winit::keyboard::NamedKey::Tab);
221 assert_eq!(
222 key_code_from_logical(&key, KeyModifiers::SHIFT),
223 Some(KeyCode::BackTab)
224 );
225 }
226
227 #[test]
228 fn plain_tab_is_unaffected() {
229 let key = winit::keyboard::Key::Named(winit::keyboard::NamedKey::Tab);
230 assert_eq!(
231 key_code_from_logical(&key, KeyModifiers::NONE),
232 Some(KeyCode::Tab)
233 );
234 }
235
236 #[test]
237 fn shift_modifier_on_non_tab_keys_is_unaffected() {
238 let key = winit::keyboard::Key::Character("a".into());
239 assert_eq!(
240 key_code_from_logical(&key, KeyModifiers::SHIFT),
241 Some(KeyCode::Char('a'))
242 );
243 }
244
245 #[test]
246 fn left_shift_alone_maps_to_modifier_shift_with_left_location() {
247 let key = winit::keyboard::Key::Named(winit::keyboard::NamedKey::Shift);
251 assert_eq!(
252 key_code_from_logical(&key, KeyModifiers::SHIFT),
253 Some(KeyCode::Modifier(ModifierKey::Shift))
254 );
255 assert_eq!(
256 translate_key_location(winit::keyboard::KeyLocation::Left),
257 KeyLocation::Left
258 );
259 }
260
261 #[test]
262 fn caps_lock_maps_straight_through() {
263 let key = winit::keyboard::Key::Named(winit::keyboard::NamedKey::CapsLock);
264 assert_eq!(
265 key_code_from_logical(&key, KeyModifiers::NONE),
266 Some(KeyCode::CapsLock)
267 );
268 }
269
270 #[test]
271 fn unmapped_key_returns_none() {
272 let key = winit::keyboard::Key::Named(winit::keyboard::NamedKey::AudioVolumeUp);
273 assert_eq!(key_code_from_logical(&key, KeyModifiers::NONE), None);
274 }
275
276 #[test]
279 fn ime_commit_maps_to_paste_event() {
280 let ime = winit::event::Ime::Commit("hello".to_string());
281 assert_eq!(translate_ime(ime), Some(Event::Paste("hello".to_string())));
282 }
283
284 #[test]
285 fn ime_empty_commit_produces_no_event() {
286 let ime = winit::event::Ime::Commit(String::new());
289 assert_eq!(translate_ime(ime), None);
290 }
291
292 #[test]
293 fn ime_enabled_produces_no_event() {
294 assert_eq!(translate_ime(winit::event::Ime::Enabled), None);
295 }
296
297 #[test]
298 fn ime_disabled_produces_no_event() {
299 assert_eq!(translate_ime(winit::event::Ime::Disabled), None);
300 }
301
302 #[test]
303 fn ime_preedit_produces_no_event() {
304 let ime = winit::event::Ime::Preedit("nihon".to_string(), Some((0, 5)));
307 assert_eq!(translate_ime(ime), None);
308 }
309
310 #[test]
313 fn pixel_to_cell_basic() {
314 let pos = translate_pixel_to_cell(20.0, 48.0, 8, 16);
316 assert_eq!(pos, Pos { x: 2, y: 3 });
317 }
318
319 #[test]
320 fn pixel_to_cell_origin() {
321 let pos = translate_pixel_to_cell(0.0, 0.0, 8, 16);
322 assert_eq!(pos, Pos { x: 0, y: 0 });
323 }
324
325 #[test]
326 fn pixel_to_cell_negative_coords_clamp_to_zero() {
327 let pos = translate_pixel_to_cell(-5.0, -10.0, 8, 16);
329 assert_eq!(pos, Pos { x: 0, y: 0 });
330 }
331
332 #[test]
333 fn pixel_to_cell_zero_cell_size_returns_origin() {
334 let pos = translate_pixel_to_cell(100.0, 200.0, 0, 0);
336 assert_eq!(pos, Pos { x: 0, y: 0 });
337 }
338
339 #[test]
340 fn pixel_to_cell_clamps_to_u16_max() {
341 let pos = translate_pixel_to_cell(f64::from(u32::MAX), f64::from(u32::MAX), 1, 1);
343 assert_eq!(
344 pos,
345 Pos {
346 x: u16::MAX,
347 y: u16::MAX
348 }
349 );
350 }
351
352 #[test]
355 fn translate_modifiers_none() {
356 let state = winit::keyboard::ModifiersState::empty();
357 assert_eq!(translate_modifiers(state), KeyModifiers::NONE);
358 }
359
360 #[test]
361 fn translate_modifiers_super_only() {
362 let state = winit::keyboard::ModifiersState::SUPER;
363 let mods = translate_modifiers(state);
364 assert!(mods.contains(KeyModifiers::SUPER));
365 assert!(!mods.contains(KeyModifiers::SHIFT));
366 assert!(!mods.contains(KeyModifiers::CONTROL));
367 assert!(!mods.contains(KeyModifiers::ALT));
368 }
369
370 #[test]
371 fn translate_modifiers_super_without_super_key() {
372 let state = winit::keyboard::ModifiersState::SHIFT;
373 let mods = translate_modifiers(state);
374 assert!(!mods.contains(KeyModifiers::SUPER));
375 }
376
377 #[test]
378 fn translate_modifiers_super_combined_with_shift() {
379 let state = winit::keyboard::ModifiersState::SUPER | winit::keyboard::ModifiersState::SHIFT;
380 let mods = translate_modifiers(state);
381 assert!(mods.contains(KeyModifiers::SUPER));
382 assert!(mods.contains(KeyModifiers::SHIFT));
383 assert!(!mods.contains(KeyModifiers::CONTROL));
384 assert!(!mods.contains(KeyModifiers::ALT));
385 }
386
387 #[test]
388 fn translate_modifiers_all_together() {
389 let state = winit::keyboard::ModifiersState::SHIFT
390 | winit::keyboard::ModifiersState::CONTROL
391 | winit::keyboard::ModifiersState::ALT
392 | winit::keyboard::ModifiersState::SUPER;
393 let mods = translate_modifiers(state);
394 assert!(mods.contains(KeyModifiers::SHIFT));
395 assert!(mods.contains(KeyModifiers::CONTROL));
396 assert!(mods.contains(KeyModifiers::ALT));
397 assert!(mods.contains(KeyModifiers::SUPER));
398 }
399
400 #[test]
403 fn key_event_kind_press_repeat_release() {
404 use winit::event::ElementState;
405 assert_eq!(
406 translate_key_event_kind(ElementState::Pressed, false),
407 KeyEventKind::Press
408 );
409 assert_eq!(
410 translate_key_event_kind(ElementState::Pressed, true),
411 KeyEventKind::Repeat
412 );
413 assert_eq!(
414 translate_key_event_kind(ElementState::Released, false),
415 KeyEventKind::Release
416 );
417 assert_eq!(
419 translate_key_event_kind(ElementState::Released, true),
420 KeyEventKind::Release
421 );
422 }
423
424 #[test]
427 fn translate_key_location_maps_all_variants() {
428 use winit::keyboard::KeyLocation as WL;
429 assert_eq!(translate_key_location(WL::Standard), KeyLocation::Standard);
430 assert_eq!(translate_key_location(WL::Left), KeyLocation::Left);
431 assert_eq!(translate_key_location(WL::Right), KeyLocation::Right);
432 assert_eq!(translate_key_location(WL::Numpad), KeyLocation::Numpad);
433 }
434
435 #[test]
438 fn translate_mouse_button_left() {
439 assert_eq!(
440 translate_mouse_button(winit::event::MouseButton::Left),
441 Some(MouseButton::Left)
442 );
443 }
444
445 #[test]
446 fn translate_mouse_button_right() {
447 assert_eq!(
448 translate_mouse_button(winit::event::MouseButton::Right),
449 Some(MouseButton::Right)
450 );
451 }
452
453 #[test]
454 fn translate_mouse_button_middle() {
455 assert_eq!(
456 translate_mouse_button(winit::event::MouseButton::Middle),
457 Some(MouseButton::Middle)
458 );
459 }
460
461 #[test]
462 fn translate_mouse_button_other_is_none() {
463 assert_eq!(
464 translate_mouse_button(winit::event::MouseButton::Back),
465 None
466 );
467 assert_eq!(
468 translate_mouse_button(winit::event::MouseButton::Forward),
469 None
470 );
471 assert_eq!(
472 translate_mouse_button(winit::event::MouseButton::Other(7)),
473 None
474 );
475 }
476}