1use retroglyph_core::event::{
6 Event, KeyCode, KeyEvent, KeyEventKind, KeyLocation, KeyModifiers, ModifierKey, MouseButton,
7 PhysicalPos,
8};
9
10fn key_code_from_logical(key: &winit::keyboard::Key, modifiers: KeyModifiers) -> Option<KeyCode> {
17 use winit::keyboard::{Key, NamedKey};
18
19 Some(match key {
20 Key::Named(NamedKey::Enter) => KeyCode::Enter,
21 Key::Named(NamedKey::Escape) => KeyCode::Escape,
22 Key::Named(NamedKey::Backspace) => KeyCode::Backspace,
23 Key::Named(NamedKey::Delete) => KeyCode::Delete,
24 Key::Named(NamedKey::Insert) => KeyCode::Insert,
25 Key::Named(NamedKey::Tab) if modifiers.contains(KeyModifiers::SHIFT) => KeyCode::BackTab,
31 Key::Named(NamedKey::Tab) => KeyCode::Tab,
32 Key::Named(NamedKey::Space) => KeyCode::Char(' '),
37 Key::Named(NamedKey::ArrowUp) => KeyCode::Up,
38 Key::Named(NamedKey::ArrowDown) => KeyCode::Down,
39 Key::Named(NamedKey::ArrowLeft) => KeyCode::Left,
40 Key::Named(NamedKey::ArrowRight) => KeyCode::Right,
41 Key::Named(NamedKey::Home) => KeyCode::Home,
42 Key::Named(NamedKey::End) => KeyCode::End,
43 Key::Named(NamedKey::PageUp) => KeyCode::PageUp,
44 Key::Named(NamedKey::PageDown) => KeyCode::PageDown,
45 Key::Named(NamedKey::F1) => KeyCode::F(1),
46 Key::Named(NamedKey::F2) => KeyCode::F(2),
47 Key::Named(NamedKey::F3) => KeyCode::F(3),
48 Key::Named(NamedKey::F4) => KeyCode::F(4),
49 Key::Named(NamedKey::F5) => KeyCode::F(5),
50 Key::Named(NamedKey::F6) => KeyCode::F(6),
51 Key::Named(NamedKey::F7) => KeyCode::F(7),
52 Key::Named(NamedKey::F8) => KeyCode::F(8),
53 Key::Named(NamedKey::F9) => KeyCode::F(9),
54 Key::Named(NamedKey::F10) => KeyCode::F(10),
55 Key::Named(NamedKey::F11) => KeyCode::F(11),
56 Key::Named(NamedKey::F12) => KeyCode::F(12),
57 Key::Named(NamedKey::Shift) => KeyCode::Modifier(ModifierKey::Shift),
61 Key::Named(NamedKey::Control) => KeyCode::Modifier(ModifierKey::Control),
62 Key::Named(NamedKey::Alt) => KeyCode::Modifier(ModifierKey::Alt),
63 Key::Named(NamedKey::Super) => KeyCode::Modifier(ModifierKey::Super),
64 Key::Named(NamedKey::CapsLock) => KeyCode::CapsLock,
65 Key::Named(NamedKey::ScrollLock) => KeyCode::ScrollLock,
66 Key::Named(NamedKey::NumLock) => KeyCode::NumLock,
67 Key::Named(NamedKey::PrintScreen) => KeyCode::PrintScreen,
68 Key::Named(NamedKey::Pause) => KeyCode::Pause,
69 Key::Named(NamedKey::ContextMenu) => KeyCode::Menu,
70 Key::Character(s) => KeyCode::Char(s.chars().next()?),
71 _ => return None,
72 })
73}
74
75#[must_use]
89pub fn translate_ime(ime: winit::event::Ime) -> Option<Event> {
90 match ime {
91 winit::event::Ime::Commit(text) if !text.is_empty() => Some(Event::Paste(text)),
92 _ => None,
93 }
94}
95
96#[must_use]
101#[allow(clippy::needless_pass_by_value)]
102pub fn translate_key(input: winit::event::KeyEvent, modifiers: KeyModifiers) -> Option<Event> {
103 let kind = translate_key_event_kind(input.state, input.repeat);
104 let code = key_code_from_logical(&input.logical_key, modifiers)?;
105 let location = translate_key_location(input.location);
106 Some(Event::Key(KeyEvent::with_location(
107 code, modifiers, kind, location,
108 )))
109}
110
111#[must_use]
113pub const fn translate_key_location(location: winit::keyboard::KeyLocation) -> KeyLocation {
114 use winit::keyboard::KeyLocation as WL;
115 match location {
116 WL::Standard => KeyLocation::Standard,
117 WL::Left => KeyLocation::Left,
118 WL::Right => KeyLocation::Right,
119 WL::Numpad => KeyLocation::Numpad,
120 }
121}
122
123#[must_use]
128#[allow(clippy::cast_sign_loss, clippy::cast_possible_truncation)]
129pub const fn translate_physical_pos(x: f64, y: f64) -> PhysicalPos {
130 PhysicalPos {
131 x: x.max(0.0) as u32,
132 y: y.max(0.0) as u32,
133 }
134}
135
136#[must_use]
140pub const fn translate_mouse_button(button: winit::event::MouseButton) -> Option<MouseButton> {
141 match button {
142 winit::event::MouseButton::Left => Some(MouseButton::Left),
143 winit::event::MouseButton::Right => Some(MouseButton::Right),
144 winit::event::MouseButton::Middle => Some(MouseButton::Middle),
145 _ => None,
146 }
147}
148
149#[must_use]
151pub const fn translate_key_event_kind(
152 state: winit::event::ElementState,
153 repeat: bool,
154) -> KeyEventKind {
155 use winit::event::ElementState;
156 match (state, repeat) {
157 (ElementState::Pressed, false) => KeyEventKind::Press,
158 (ElementState::Pressed, true) => KeyEventKind::Repeat,
159 (ElementState::Released, _) => KeyEventKind::Release,
160 }
161}
162
163#[must_use]
165pub fn translate_modifiers(state: winit::keyboard::ModifiersState) -> KeyModifiers {
166 KeyModifiers::from_parts(
167 state.shift_key(),
168 state.control_key(),
169 state.alt_key(),
170 state.super_key(),
171 )
172}
173
174#[cfg(test)]
175mod tests {
176 use super::*;
177
178 #[test]
181 fn space_maps_to_char_space() {
182 let key = winit::keyboard::Key::Named(winit::keyboard::NamedKey::Space);
186 assert_eq!(
187 key_code_from_logical(&key, KeyModifiers::NONE),
188 Some(KeyCode::Char(' '))
189 );
190 }
191
192 #[test]
193 fn shift_tab_normalizes_to_backtab() {
194 let key = winit::keyboard::Key::Named(winit::keyboard::NamedKey::Tab);
199 assert_eq!(
200 key_code_from_logical(&key, KeyModifiers::SHIFT),
201 Some(KeyCode::BackTab)
202 );
203 }
204
205 #[test]
206 fn plain_tab_is_unaffected() {
207 let key = winit::keyboard::Key::Named(winit::keyboard::NamedKey::Tab);
208 assert_eq!(
209 key_code_from_logical(&key, KeyModifiers::NONE),
210 Some(KeyCode::Tab)
211 );
212 }
213
214 #[test]
215 fn shift_modifier_on_non_tab_keys_is_unaffected() {
216 let key = winit::keyboard::Key::Character("a".into());
217 assert_eq!(
218 key_code_from_logical(&key, KeyModifiers::SHIFT),
219 Some(KeyCode::Char('a'))
220 );
221 }
222
223 #[test]
224 fn left_shift_alone_maps_to_modifier_shift_with_left_location() {
225 let key = winit::keyboard::Key::Named(winit::keyboard::NamedKey::Shift);
229 assert_eq!(
230 key_code_from_logical(&key, KeyModifiers::SHIFT),
231 Some(KeyCode::Modifier(ModifierKey::Shift))
232 );
233 assert_eq!(
234 translate_key_location(winit::keyboard::KeyLocation::Left),
235 KeyLocation::Left
236 );
237 }
238
239 #[test]
240 fn caps_lock_maps_straight_through() {
241 let key = winit::keyboard::Key::Named(winit::keyboard::NamedKey::CapsLock);
242 assert_eq!(
243 key_code_from_logical(&key, KeyModifiers::NONE),
244 Some(KeyCode::CapsLock)
245 );
246 }
247
248 #[test]
249 fn unmapped_key_returns_none() {
250 let key = winit::keyboard::Key::Named(winit::keyboard::NamedKey::AudioVolumeUp);
251 assert_eq!(key_code_from_logical(&key, KeyModifiers::NONE), None);
252 }
253
254 #[test]
257 fn ime_commit_maps_to_paste_event() {
258 let ime = winit::event::Ime::Commit("hello".to_string());
259 assert_eq!(translate_ime(ime), Some(Event::Paste("hello".to_string())));
260 }
261
262 #[test]
263 fn ime_empty_commit_produces_no_event() {
264 let ime = winit::event::Ime::Commit(String::new());
267 assert_eq!(translate_ime(ime), None);
268 }
269
270 #[test]
271 fn ime_enabled_produces_no_event() {
272 assert_eq!(translate_ime(winit::event::Ime::Enabled), None);
273 }
274
275 #[test]
276 fn ime_disabled_produces_no_event() {
277 assert_eq!(translate_ime(winit::event::Ime::Disabled), None);
278 }
279
280 #[test]
281 fn ime_preedit_produces_no_event() {
282 let ime = winit::event::Ime::Preedit("nihon".to_string(), Some((0, 5)));
285 assert_eq!(translate_ime(ime), None);
286 }
287
288 #[test]
291 fn translate_modifiers_none() {
292 let state = winit::keyboard::ModifiersState::empty();
293 assert_eq!(translate_modifiers(state), KeyModifiers::NONE);
294 }
295
296 #[test]
297 fn translate_modifiers_super_only() {
298 let state = winit::keyboard::ModifiersState::SUPER;
299 let mods = translate_modifiers(state);
300 assert!(mods.contains(KeyModifiers::SUPER));
301 assert!(!mods.contains(KeyModifiers::SHIFT));
302 assert!(!mods.contains(KeyModifiers::CONTROL));
303 assert!(!mods.contains(KeyModifiers::ALT));
304 }
305
306 #[test]
307 fn translate_modifiers_super_without_super_key() {
308 let state = winit::keyboard::ModifiersState::SHIFT;
309 let mods = translate_modifiers(state);
310 assert!(!mods.contains(KeyModifiers::SUPER));
311 }
312
313 #[test]
314 fn translate_modifiers_super_combined_with_shift() {
315 let state = winit::keyboard::ModifiersState::SUPER | winit::keyboard::ModifiersState::SHIFT;
316 let mods = translate_modifiers(state);
317 assert!(mods.contains(KeyModifiers::SUPER));
318 assert!(mods.contains(KeyModifiers::SHIFT));
319 assert!(!mods.contains(KeyModifiers::CONTROL));
320 assert!(!mods.contains(KeyModifiers::ALT));
321 }
322
323 #[test]
324 fn translate_modifiers_all_together() {
325 let state = winit::keyboard::ModifiersState::SHIFT
326 | winit::keyboard::ModifiersState::CONTROL
327 | winit::keyboard::ModifiersState::ALT
328 | winit::keyboard::ModifiersState::SUPER;
329 let mods = translate_modifiers(state);
330 assert!(mods.contains(KeyModifiers::SHIFT));
331 assert!(mods.contains(KeyModifiers::CONTROL));
332 assert!(mods.contains(KeyModifiers::ALT));
333 assert!(mods.contains(KeyModifiers::SUPER));
334 }
335
336 #[test]
339 fn key_event_kind_press_repeat_release() {
340 use winit::event::ElementState;
341 assert_eq!(
342 translate_key_event_kind(ElementState::Pressed, false),
343 KeyEventKind::Press
344 );
345 assert_eq!(
346 translate_key_event_kind(ElementState::Pressed, true),
347 KeyEventKind::Repeat
348 );
349 assert_eq!(
350 translate_key_event_kind(ElementState::Released, false),
351 KeyEventKind::Release
352 );
353 assert_eq!(
355 translate_key_event_kind(ElementState::Released, true),
356 KeyEventKind::Release
357 );
358 }
359
360 #[test]
363 fn translate_key_location_maps_all_variants() {
364 use winit::keyboard::KeyLocation as WL;
365 assert_eq!(translate_key_location(WL::Standard), KeyLocation::Standard);
366 assert_eq!(translate_key_location(WL::Left), KeyLocation::Left);
367 assert_eq!(translate_key_location(WL::Right), KeyLocation::Right);
368 assert_eq!(translate_key_location(WL::Numpad), KeyLocation::Numpad);
369 }
370
371 #[test]
374 fn translate_mouse_button_left() {
375 assert_eq!(
376 translate_mouse_button(winit::event::MouseButton::Left),
377 Some(MouseButton::Left)
378 );
379 }
380
381 #[test]
382 fn translate_mouse_button_right() {
383 assert_eq!(
384 translate_mouse_button(winit::event::MouseButton::Right),
385 Some(MouseButton::Right)
386 );
387 }
388
389 #[test]
390 fn translate_mouse_button_middle() {
391 assert_eq!(
392 translate_mouse_button(winit::event::MouseButton::Middle),
393 Some(MouseButton::Middle)
394 );
395 }
396
397 #[test]
398 fn translate_mouse_button_other_is_none() {
399 assert_eq!(
400 translate_mouse_button(winit::event::MouseButton::Back),
401 None
402 );
403 assert_eq!(
404 translate_mouse_button(winit::event::MouseButton::Forward),
405 None
406 );
407 assert_eq!(
408 translate_mouse_button(winit::event::MouseButton::Other(7)),
409 None
410 );
411 }
412}