1use std::sync::OnceLock;
23
24use winit::event::{ElementState, MouseButton as WinitMouseButton, MouseScrollDelta, WindowEvent};
25use winit::keyboard::{KeyCode as WinitKeyCode, PhysicalKey};
26
27use khora_core::platform::{InputEvent, KeyCode, MouseButton};
28
29pub fn translate_winit_input(event: &WindowEvent) -> Option<InputEvent> {
34 match event {
35 WindowEvent::KeyboardInput {
36 event: key_event, ..
37 } => {
38 if let PhysicalKey::Code(keycode) = key_event.physical_key {
39 let key_code = map_keycode(keycode);
40 match key_event.state {
41 ElementState::Pressed if !key_event.repeat => {
42 Some(InputEvent::KeyPressed { key_code })
43 }
44 ElementState::Released => Some(InputEvent::KeyReleased { key_code }),
45 _ => None,
46 }
47 } else {
48 None
49 }
50 }
51 WindowEvent::CursorMoved { position, .. } => Some(InputEvent::MouseMoved {
52 x: position.x as f32,
53 y: position.y as f32,
54 }),
55 WindowEvent::MouseInput { state, button, .. } => {
56 let khora_button = map_mouse_button(*button);
57 match state {
58 ElementState::Pressed => Some(InputEvent::MouseButtonPressed {
59 button: khora_button,
60 }),
61 ElementState::Released => Some(InputEvent::MouseButtonReleased {
62 button: khora_button,
63 }),
64 }
65 }
66 WindowEvent::MouseWheel { delta, .. } => {
67 let (dx, dy): (f32, f32) = match delta {
68 MouseScrollDelta::LineDelta(x, y) => (*x, *y),
69 MouseScrollDelta::PixelDelta(pos) => (pos.x as f32, pos.y as f32),
70 };
71 if dx != 0.0 || dy != 0.0 {
72 Some(InputEvent::MouseWheelScrolled {
73 delta_x: dx,
74 delta_y: dy,
75 })
76 } else {
77 None
78 }
79 }
80 _ => None,
81 }
82}
83
84pub(crate) fn map_keycode(keycode: WinitKeyCode) -> KeyCode {
93 match keycode {
94 WinitKeyCode::Backquote => KeyCode::Backquote,
95 WinitKeyCode::Backslash => KeyCode::Backslash,
96 WinitKeyCode::BracketLeft => KeyCode::BracketLeft,
97 WinitKeyCode::BracketRight => KeyCode::BracketRight,
98 WinitKeyCode::Comma => KeyCode::Comma,
99 WinitKeyCode::Digit0 => KeyCode::Digit0,
100 WinitKeyCode::Digit1 => KeyCode::Digit1,
101 WinitKeyCode::Digit2 => KeyCode::Digit2,
102 WinitKeyCode::Digit3 => KeyCode::Digit3,
103 WinitKeyCode::Digit4 => KeyCode::Digit4,
104 WinitKeyCode::Digit5 => KeyCode::Digit5,
105 WinitKeyCode::Digit6 => KeyCode::Digit6,
106 WinitKeyCode::Digit7 => KeyCode::Digit7,
107 WinitKeyCode::Digit8 => KeyCode::Digit8,
108 WinitKeyCode::Digit9 => KeyCode::Digit9,
109 WinitKeyCode::Equal => KeyCode::Equal,
110 WinitKeyCode::IntlBackslash => KeyCode::IntlBackslash,
111 WinitKeyCode::IntlRo => KeyCode::IntlRo,
112 WinitKeyCode::IntlYen => KeyCode::IntlYen,
113 WinitKeyCode::KeyA => KeyCode::KeyA,
114 WinitKeyCode::KeyB => KeyCode::KeyB,
115 WinitKeyCode::KeyC => KeyCode::KeyC,
116 WinitKeyCode::KeyD => KeyCode::KeyD,
117 WinitKeyCode::KeyE => KeyCode::KeyE,
118 WinitKeyCode::KeyF => KeyCode::KeyF,
119 WinitKeyCode::KeyG => KeyCode::KeyG,
120 WinitKeyCode::KeyH => KeyCode::KeyH,
121 WinitKeyCode::KeyI => KeyCode::KeyI,
122 WinitKeyCode::KeyJ => KeyCode::KeyJ,
123 WinitKeyCode::KeyK => KeyCode::KeyK,
124 WinitKeyCode::KeyL => KeyCode::KeyL,
125 WinitKeyCode::KeyM => KeyCode::KeyM,
126 WinitKeyCode::KeyN => KeyCode::KeyN,
127 WinitKeyCode::KeyO => KeyCode::KeyO,
128 WinitKeyCode::KeyP => KeyCode::KeyP,
129 WinitKeyCode::KeyQ => KeyCode::KeyQ,
130 WinitKeyCode::KeyR => KeyCode::KeyR,
131 WinitKeyCode::KeyS => KeyCode::KeyS,
132 WinitKeyCode::KeyT => KeyCode::KeyT,
133 WinitKeyCode::KeyU => KeyCode::KeyU,
134 WinitKeyCode::KeyV => KeyCode::KeyV,
135 WinitKeyCode::KeyW => KeyCode::KeyW,
136 WinitKeyCode::KeyX => KeyCode::KeyX,
137 WinitKeyCode::KeyY => KeyCode::KeyY,
138 WinitKeyCode::KeyZ => KeyCode::KeyZ,
139 WinitKeyCode::Minus => KeyCode::Minus,
140 WinitKeyCode::Period => KeyCode::Period,
141 WinitKeyCode::Quote => KeyCode::Quote,
142 WinitKeyCode::Semicolon => KeyCode::Semicolon,
143 WinitKeyCode::Slash => KeyCode::Slash,
144 WinitKeyCode::AltLeft => KeyCode::AltLeft,
145 WinitKeyCode::AltRight => KeyCode::AltRight,
146 WinitKeyCode::Backspace => KeyCode::Backspace,
147 WinitKeyCode::CapsLock => KeyCode::CapsLock,
148 WinitKeyCode::ContextMenu => KeyCode::ContextMenu,
149 WinitKeyCode::ControlLeft => KeyCode::ControlLeft,
150 WinitKeyCode::ControlRight => KeyCode::ControlRight,
151 WinitKeyCode::Enter => KeyCode::Enter,
152 WinitKeyCode::SuperLeft => KeyCode::SuperLeft,
153 WinitKeyCode::SuperRight => KeyCode::SuperRight,
154 WinitKeyCode::ShiftLeft => KeyCode::ShiftLeft,
155 WinitKeyCode::ShiftRight => KeyCode::ShiftRight,
156 WinitKeyCode::Space => KeyCode::Space,
157 WinitKeyCode::Tab => KeyCode::Tab,
158 WinitKeyCode::Convert => KeyCode::Convert,
159 WinitKeyCode::KanaMode => KeyCode::KanaMode,
160 WinitKeyCode::Lang1 => KeyCode::Lang1,
161 WinitKeyCode::Lang2 => KeyCode::Lang2,
162 WinitKeyCode::Lang3 => KeyCode::Lang3,
163 WinitKeyCode::Lang4 => KeyCode::Lang4,
164 WinitKeyCode::Lang5 => KeyCode::Lang5,
165 WinitKeyCode::NonConvert => KeyCode::NonConvert,
166 WinitKeyCode::Delete => KeyCode::Delete,
167 WinitKeyCode::End => KeyCode::End,
168 WinitKeyCode::Help => KeyCode::Help,
169 WinitKeyCode::Home => KeyCode::Home,
170 WinitKeyCode::Insert => KeyCode::Insert,
171 WinitKeyCode::PageDown => KeyCode::PageDown,
172 WinitKeyCode::PageUp => KeyCode::PageUp,
173 WinitKeyCode::ArrowDown => KeyCode::ArrowDown,
174 WinitKeyCode::ArrowLeft => KeyCode::ArrowLeft,
175 WinitKeyCode::ArrowRight => KeyCode::ArrowRight,
176 WinitKeyCode::ArrowUp => KeyCode::ArrowUp,
177 WinitKeyCode::NumLock => KeyCode::NumLock,
178 WinitKeyCode::Numpad0 => KeyCode::Numpad0,
179 WinitKeyCode::Numpad1 => KeyCode::Numpad1,
180 WinitKeyCode::Numpad2 => KeyCode::Numpad2,
181 WinitKeyCode::Numpad3 => KeyCode::Numpad3,
182 WinitKeyCode::Numpad4 => KeyCode::Numpad4,
183 WinitKeyCode::Numpad5 => KeyCode::Numpad5,
184 WinitKeyCode::Numpad6 => KeyCode::Numpad6,
185 WinitKeyCode::Numpad7 => KeyCode::Numpad7,
186 WinitKeyCode::Numpad8 => KeyCode::Numpad8,
187 WinitKeyCode::Numpad9 => KeyCode::Numpad9,
188 WinitKeyCode::NumpadAdd => KeyCode::NumpadAdd,
189 WinitKeyCode::NumpadBackspace => KeyCode::NumpadBackspace,
190 WinitKeyCode::NumpadClear => KeyCode::NumpadClear,
191 WinitKeyCode::NumpadClearEntry => KeyCode::NumpadClearEntry,
192 WinitKeyCode::NumpadComma => KeyCode::NumpadComma,
193 WinitKeyCode::NumpadDecimal => KeyCode::NumpadDecimal,
194 WinitKeyCode::NumpadDivide => KeyCode::NumpadDivide,
195 WinitKeyCode::NumpadEnter => KeyCode::NumpadEnter,
196 WinitKeyCode::NumpadEqual => KeyCode::NumpadEqual,
197 WinitKeyCode::NumpadHash => KeyCode::NumpadHash,
198 WinitKeyCode::NumpadMemoryAdd => KeyCode::NumpadMemoryAdd,
199 WinitKeyCode::NumpadMemoryClear => KeyCode::NumpadMemoryClear,
200 WinitKeyCode::NumpadMemoryRecall => KeyCode::NumpadMemoryRecall,
201 WinitKeyCode::NumpadMemoryStore => KeyCode::NumpadMemoryStore,
202 WinitKeyCode::NumpadMemorySubtract => KeyCode::NumpadMemorySubtract,
203 WinitKeyCode::NumpadMultiply => KeyCode::NumpadMultiply,
204 WinitKeyCode::NumpadParenLeft => KeyCode::NumpadParenLeft,
205 WinitKeyCode::NumpadParenRight => KeyCode::NumpadParenRight,
206 WinitKeyCode::NumpadStar => KeyCode::NumpadStar,
207 WinitKeyCode::NumpadSubtract => KeyCode::NumpadSubtract,
208 WinitKeyCode::Escape => KeyCode::Escape,
209 WinitKeyCode::Fn => KeyCode::Fn,
210 WinitKeyCode::FnLock => KeyCode::FnLock,
211 WinitKeyCode::PrintScreen => KeyCode::PrintScreen,
212 WinitKeyCode::ScrollLock => KeyCode::ScrollLock,
213 WinitKeyCode::Pause => KeyCode::Pause,
214 WinitKeyCode::BrowserBack => KeyCode::BrowserBack,
215 WinitKeyCode::BrowserFavorites => KeyCode::BrowserFavorites,
216 WinitKeyCode::BrowserForward => KeyCode::BrowserForward,
217 WinitKeyCode::BrowserHome => KeyCode::BrowserHome,
218 WinitKeyCode::BrowserRefresh => KeyCode::BrowserRefresh,
219 WinitKeyCode::BrowserSearch => KeyCode::BrowserSearch,
220 WinitKeyCode::BrowserStop => KeyCode::BrowserStop,
221 WinitKeyCode::Eject => KeyCode::Eject,
222 WinitKeyCode::LaunchApp1 => KeyCode::LaunchApp1,
223 WinitKeyCode::LaunchApp2 => KeyCode::LaunchApp2,
224 WinitKeyCode::LaunchMail => KeyCode::LaunchMail,
225 WinitKeyCode::MediaPlayPause => KeyCode::MediaPlayPause,
226 WinitKeyCode::MediaSelect => KeyCode::MediaSelect,
227 WinitKeyCode::MediaStop => KeyCode::MediaStop,
228 WinitKeyCode::MediaTrackNext => KeyCode::MediaTrackNext,
229 WinitKeyCode::MediaTrackPrevious => KeyCode::MediaTrackPrevious,
230 WinitKeyCode::Power => KeyCode::Power,
231 WinitKeyCode::Sleep => KeyCode::Sleep,
232 WinitKeyCode::AudioVolumeDown => KeyCode::AudioVolumeDown,
233 WinitKeyCode::AudioVolumeMute => KeyCode::AudioVolumeMute,
234 WinitKeyCode::AudioVolumeUp => KeyCode::AudioVolumeUp,
235 WinitKeyCode::WakeUp => KeyCode::WakeUp,
236 WinitKeyCode::Meta => KeyCode::Meta,
237 WinitKeyCode::Hyper => KeyCode::Hyper,
238 WinitKeyCode::Turbo => KeyCode::Turbo,
239 WinitKeyCode::Abort => KeyCode::Abort,
240 WinitKeyCode::Resume => KeyCode::Resume,
241 WinitKeyCode::Suspend => KeyCode::Suspend,
242 WinitKeyCode::Again => KeyCode::Again,
243 WinitKeyCode::Copy => KeyCode::Copy,
244 WinitKeyCode::Cut => KeyCode::Cut,
245 WinitKeyCode::Find => KeyCode::Find,
246 WinitKeyCode::Open => KeyCode::Open,
247 WinitKeyCode::Paste => KeyCode::Paste,
248 WinitKeyCode::Props => KeyCode::Props,
249 WinitKeyCode::Select => KeyCode::Select,
250 WinitKeyCode::Undo => KeyCode::Undo,
251 WinitKeyCode::Hiragana => KeyCode::Hiragana,
252 WinitKeyCode::Katakana => KeyCode::Katakana,
253 WinitKeyCode::F1 => KeyCode::F1,
254 WinitKeyCode::F2 => KeyCode::F2,
255 WinitKeyCode::F3 => KeyCode::F3,
256 WinitKeyCode::F4 => KeyCode::F4,
257 WinitKeyCode::F5 => KeyCode::F5,
258 WinitKeyCode::F6 => KeyCode::F6,
259 WinitKeyCode::F7 => KeyCode::F7,
260 WinitKeyCode::F8 => KeyCode::F8,
261 WinitKeyCode::F9 => KeyCode::F9,
262 WinitKeyCode::F10 => KeyCode::F10,
263 WinitKeyCode::F11 => KeyCode::F11,
264 WinitKeyCode::F12 => KeyCode::F12,
265 WinitKeyCode::F13 => KeyCode::F13,
266 WinitKeyCode::F14 => KeyCode::F14,
267 WinitKeyCode::F15 => KeyCode::F15,
268 WinitKeyCode::F16 => KeyCode::F16,
269 WinitKeyCode::F17 => KeyCode::F17,
270 WinitKeyCode::F18 => KeyCode::F18,
271 WinitKeyCode::F19 => KeyCode::F19,
272 WinitKeyCode::F20 => KeyCode::F20,
273 WinitKeyCode::F21 => KeyCode::F21,
274 WinitKeyCode::F22 => KeyCode::F22,
275 WinitKeyCode::F23 => KeyCode::F23,
276 WinitKeyCode::F24 => KeyCode::F24,
277 WinitKeyCode::F25 => KeyCode::F25,
278 WinitKeyCode::F26 => KeyCode::F26,
279 WinitKeyCode::F27 => KeyCode::F27,
280 WinitKeyCode::F28 => KeyCode::F28,
281 WinitKeyCode::F29 => KeyCode::F29,
282 WinitKeyCode::F30 => KeyCode::F30,
283 WinitKeyCode::F31 => KeyCode::F31,
284 WinitKeyCode::F32 => KeyCode::F32,
285 WinitKeyCode::F33 => KeyCode::F33,
286 WinitKeyCode::F34 => KeyCode::F34,
287 WinitKeyCode::F35 => KeyCode::F35,
288 other => {
293 static WARNED: OnceLock<()> = OnceLock::new();
294 if WARNED.set(()).is_ok() {
295 log::warn!(
296 "khora-infra: unmapped winit::KeyCode variant {:?} (using KeyCode::Unidentified). \
297 Consider extending map_keycode in crates/khora-infra/src/platform/input.rs.",
298 other,
299 );
300 }
301 KeyCode::Unidentified
302 }
303 }
304}
305
306fn map_mouse_button(button: WinitMouseButton) -> MouseButton {
307 match button {
308 WinitMouseButton::Left => MouseButton::Left,
309 WinitMouseButton::Right => MouseButton::Right,
310 WinitMouseButton::Middle => MouseButton::Middle,
311 WinitMouseButton::Back => MouseButton::Back,
312 WinitMouseButton::Forward => MouseButton::Forward,
313 WinitMouseButton::Other(id) => MouseButton::Other(id),
314 }
315}
316
317#[cfg(test)]
319mod tests {
320 use super::*;
321 use winit::{dpi::PhysicalPosition, event::WindowEvent};
322
323 #[test]
324 fn test_map_keycode_simple() {
325 assert_eq!(map_keycode(WinitKeyCode::KeyA), KeyCode::KeyA);
326 assert_eq!(map_keycode(WinitKeyCode::Digit1), KeyCode::Digit1);
327 assert_eq!(map_keycode(WinitKeyCode::Space), KeyCode::Space);
328 assert_eq!(map_keycode(WinitKeyCode::ShiftLeft), KeyCode::ShiftLeft);
329 assert_eq!(map_keycode(WinitKeyCode::F12), KeyCode::F12);
330 }
331
332 #[test]
333 fn test_map_mouse_button_standard() {
334 assert_eq!(map_mouse_button(WinitMouseButton::Left), MouseButton::Left);
335 assert_eq!(
336 map_mouse_button(WinitMouseButton::Right),
337 MouseButton::Right
338 );
339 assert_eq!(
340 map_mouse_button(WinitMouseButton::Middle),
341 MouseButton::Middle
342 );
343 assert_eq!(map_mouse_button(WinitMouseButton::Back), MouseButton::Back);
344 assert_eq!(
345 map_mouse_button(WinitMouseButton::Forward),
346 MouseButton::Forward
347 );
348 }
349
350 #[test]
351 fn test_map_mouse_button_other() {
352 assert_eq!(
353 map_mouse_button(WinitMouseButton::Other(8)),
354 MouseButton::Other(8)
355 );
356 }
357
358 #[test]
359 fn test_translate_mouse_button_pressed() {
360 let winit_event = WindowEvent::MouseInput {
361 device_id: winit::event::DeviceId::dummy(),
362 state: ElementState::Pressed,
363 button: WinitMouseButton::Left,
364 };
365 assert_eq!(
366 translate_winit_input(&winit_event),
367 Some(InputEvent::MouseButtonPressed {
368 button: MouseButton::Left,
369 })
370 );
371 }
372
373 #[test]
374 fn test_translate_cursor_moved() {
375 let winit_event = WindowEvent::CursorMoved {
376 device_id: winit::event::DeviceId::dummy(),
377 position: PhysicalPosition::new(100.5, 200.75),
378 };
379 assert_eq!(
380 translate_winit_input(&winit_event),
381 Some(InputEvent::MouseMoved {
382 x: 100.5,
383 y: 200.75,
384 })
385 );
386 }
387
388 #[test]
389 fn test_translate_mouse_wheel_line() {
390 let winit_event = WindowEvent::MouseWheel {
391 device_id: winit::event::DeviceId::dummy(),
392 delta: MouseScrollDelta::LineDelta(-1.0, 2.0),
393 phase: winit::event::TouchPhase::Moved,
394 };
395 assert_eq!(
396 translate_winit_input(&winit_event),
397 Some(InputEvent::MouseWheelScrolled {
398 delta_x: -1.0,
399 delta_y: 2.0,
400 })
401 );
402 }
403
404 #[test]
405 fn test_translate_non_input_returns_none() {
406 let winit_event_resize = WindowEvent::Resized(winit::dpi::PhysicalSize::new(100, 100));
407 assert_eq!(translate_winit_input(&winit_event_resize), None);
408 }
409}