Seregon/StratoSDK

StratoSDK is a framework with a declarative approach similar to Flutter/React, written and designed entirely for Rust.

Rust/27.3 KB/No license
crates/strato-platform/src/event_loop.rs
StratoSDK / crates / strato-platform / src / event_loop.rs
1//! Event loop management
2 
3use crate::Application;
4use std::cell::RefCell;
5use std::rc::Rc;
6use std::sync::Arc;
7use std::time::{Duration, Instant};
8use strato_core::event::{
9 Event, KeyCode, KeyboardEvent, Modifiers, MouseButton, MouseEvent, WindowEvent,
10};
11use strato_renderer::backend::WgpuBackend;
12use strato_renderer::Backend;
13use winit::window::Window;
14 
15/// Custom event for the event loop
16#[derive(Debug)]
17pub struct CustomEvent {
18 pub event: Event,
19}
20 
21/// Application state for managing event loop state safely
22struct AppState {
23 window_created: bool,
24 winit_window: Option<Arc<Window>>,
25 backend: Option<Box<dyn Backend>>,
26 renderer_initialized: bool,
27 needs_redraw: bool,
28 last_update: Instant,
29 app: Option<Application>,
30 cursor_position: winit::dpi::PhysicalPosition<f64>,
31 scale_factor: f64,
32}
33 
34impl AppState {
35 fn new() -> Self {
36 Self {
37 window_created: false,
38 winit_window: None,
39 backend: None,
40 renderer_initialized: false,
41 needs_redraw: false,
42 last_update: Instant::now(),
43 app: None,
44 cursor_position: winit::dpi::PhysicalPosition::new(0.0, 0.0),
45 scale_factor: 1.0,
46 }
47 }
48}
49 
50/// Event loop wrapper with cross-platform support
51pub struct EventLoop {
52 #[cfg(not(target_arch = "wasm32"))]
53 inner: winit::event_loop::EventLoop<CustomEvent>,
54 #[cfg(target_arch = "wasm32")]
55 _phantom: std::marker::PhantomData<()>,
56}
57 
58impl EventLoop {
59 /// Create a new event loop
60 #[cfg(not(target_arch = "wasm32"))]
61 pub fn new() -> Result<Self, EventLoopError> {
62 use winit::event_loop::EventLoopBuilder;
63 
64 let inner = EventLoopBuilder::with_user_event()
65 .build()
66 .map_err(|_| EventLoopError::CreationFailed)?;
67 
68 Ok(Self { inner })
69 }
70 
71 #[cfg(target_arch = "wasm32")]
72 pub fn new() -> Result<Self, EventLoopError> {
73 Ok(Self {
74 _phantom: std::marker::PhantomData,
75 })
76 }
77 
78 /// Create an event loop proxy for sending custom events
79 pub fn create_proxy(&self) -> EventLoopProxy {
80 #[cfg(not(target_arch = "wasm32"))]
81 {
82 EventLoopProxy {
83 inner: self.inner.create_proxy(),
84 }
85 }
86 
87 #[cfg(target_arch = "wasm32")]
88 {
89 let (sender, _) = mpsc::channel();
90 EventLoopProxy { sender }
91 }
92 }
93 
94 /// Run the event loop with basic event handling
95 #[cfg(not(target_arch = "wasm32"))]
96 pub fn run<F>(self, mut handler: F) -> !
97 where
98 F: FnMut(Event) + 'static,
99 {
100 use winit::event::{Event as WinitEvent, WindowEvent as WinitWindowEvent};
101 use winit::event_loop::ControlFlow;
102 
103 let mut last_update = Instant::now();
104 let mut cursor_position = winit::dpi::PhysicalPosition::new(0.0, 0.0);
105 let mut scale_factor = 1.0;
106 
107 self.inner
108 .run(move |event, elwt| {
109 match event {
110 WinitEvent::WindowEvent { event, .. } => {
111 match event {
112 WinitWindowEvent::CloseRequested => {
113 elwt.exit();
114 }
115 WinitWindowEvent::RedrawRequested => {
116 // Handle redraw - emit a custom redraw event
117 handler(Event::Window(WindowEvent::Resize {
118 width: 0,
119 height: 0,
120 }));
121 }
122 WinitWindowEvent::CursorMoved {
123 position,
124 device_id,
125 ..
126 } => {
127 cursor_position = position;
128 if let Some(strato_event) = convert_window_event(
129 WinitWindowEvent::CursorMoved {
130 position,
131 device_id,
132 },
133 cursor_position,
134 scale_factor,
135 ) {
136 handler(strato_event);
137 }
138 }
139 WinitWindowEvent::ScaleFactorChanged {
140 scale_factor: sf,
141 inner_size_writer,
142 } => {
143 scale_factor = sf;
144 if let Some(strato_event) = convert_window_event(
145 WinitWindowEvent::ScaleFactorChanged {
146 scale_factor: sf,
147 inner_size_writer,
148 },
149 cursor_position,
150 scale_factor,
151 ) {
152 handler(strato_event);
153 }
154 }
155 _ => {
156 if let Some(strato_event) =
157 convert_window_event(event, cursor_position, scale_factor)
158 {
159 handler(strato_event);
160 }
161 }
162 }
163 }
164 WinitEvent::AboutToWait => {
165 // Implement frame rate limiting
166 let now = Instant::now();
167 let frame_time = Duration::from_millis(16); // ~60 FPS
168 
169 if now.duration_since(last_update) >= frame_time {
170 last_update = now;
171 elwt.set_control_flow(ControlFlow::Poll);
172 } else {
173 elwt.set_control_flow(ControlFlow::WaitUntil(last_update + frame_time));
174 }
175 }
176 WinitEvent::UserEvent(custom) => {
177 handler(Event::Custom(Arc::new(custom.event)));
178 }
179 _ => {}
180 }
181 })
182 .expect("Event loop failed");
183 
184 // This should never be reached, but if it is, exit the process
185 std::process::exit(0);
186 }
187 
188 /// Run the event loop with a window
189 #[cfg(not(target_arch = "wasm32"))]
190 pub fn run_with_window<F>(
191 self,
192 window_builder: crate::WindowBuilder,
193 mut handler: F,
194 ) -> Result<(), EventLoopError>
195 where
196 F: FnMut(Event) + 'static,
197 {
198 use winit::event::{Event as WinitEvent, WindowEvent as WinitWindowEvent};
199 use winit::event_loop::ControlFlow;
200 
201 let state = Rc::new(RefCell::new(AppState::new()));
202 
203 self.inner
204 .run(move |event, event_loop_window_target| {
205 event_loop_window_target.set_control_flow(ControlFlow::Poll);
206 
207 let mut state = state.borrow_mut();
208 
209 match event {
210 WinitEvent::Resumed => {
211 if !state.window_created {
212 let window = Arc::new(
213 window_builder
214 .build_winit(event_loop_window_target)
215 .expect("Failed to create window"),
216 );
217 
218 state.winit_window = Some(window);
219 state.window_created = true;
220 state.needs_redraw = true;
221 state.last_update = Instant::now();
222 }
223 }
224 WinitEvent::WindowEvent { event, .. } => match event {
225 WinitWindowEvent::CursorMoved {
226 position,
227 device_id,
228 ..
229 } => {
230 state.cursor_position = position;
231 if let Some(strato_event) = convert_window_event(
232 WinitWindowEvent::CursorMoved {
233 position,
234 device_id,
235 },
236 state.cursor_position,
237 state.scale_factor,
238 ) {
239 handler(strato_event);
240 }
241 }
242 WinitWindowEvent::ScaleFactorChanged {
243 scale_factor,
244 inner_size_writer,
245 } => {
246 state.scale_factor = scale_factor;
247 if let Some(strato_event) = convert_window_event(
248 WinitWindowEvent::ScaleFactorChanged {
249 scale_factor,
250 inner_size_writer,
251 },
252 state.cursor_position,
253 state.scale_factor,
254 ) {
255 handler(strato_event);
256 }
257 }
258 _ => {
259 if let Some(strato_event) = convert_window_event(
260 event,
261 state.cursor_position,
262 state.scale_factor,
263 ) {
264 handler(strato_event);
265 }
266 }
267 },
268 WinitEvent::AboutToWait => {
269 let now = Instant::now();
270 let frame_time = Duration::from_millis(16);
271 
272 if state.needs_redraw && now.duration_since(state.last_update) >= frame_time
273 {
274 if let Some(ref window) = state.winit_window {
275 window.request_redraw();
276 state.last_update = now;
277 }
278 }
279 }
280 WinitEvent::UserEvent(custom) => {
281 handler(Event::Custom(Arc::new(custom.event)));
282 }
283 _ => {}
284 }
285 })
286 .map_err(|_| EventLoopError::RunFailed)
287 }
288 
289 /// Run the event loop with a window and application
290 #[cfg(not(target_arch = "wasm32"))]
291 pub fn run_with_window_and_app<F>(
292 self,
293 window_builder: crate::WindowBuilder,
294 app: Application,
295 handler: F,
296 ) -> Result<(), Box<dyn std::error::Error>>
297 where
298 F: FnMut(Event) + 'static,
299 {
300 use winit::event::{Event as WinitEvent, WindowEvent};
301 
302 let app_state = Rc::new(RefCell::new(AppState::new()));
303 let mut handler = handler;
304 
305 // Store the application in the state
306 app_state.borrow_mut().app = Some(app);
307 
308 self.inner
309 .run(move |event, event_loop_window_target| {
310 let mut state = app_state.borrow_mut();
311 
312 match event {
313 WinitEvent::Resumed => {
314 if !state.window_created {
315 let window = Arc::new(
316 window_builder
317 .build_winit(event_loop_window_target)
318 .expect("Failed to create window"),
319 );
320 
321 // Store the window first
322 state.winit_window = Some(window.clone());
323 
324 // Initialize Backend
325 println!("=== INITIALIZING BACKEND ===");
326 let mut backend = Box::new(WgpuBackend::new());
327 pollster::block_on(backend.init(&*window))
328 .expect("Failed to init backend");
329 
330 // Set initial scale factor
331 let scale_factor = window.scale_factor();
332 backend.set_scale_factor(scale_factor);
333 state.scale_factor = scale_factor;
334 
335 state.backend = Some(backend);
336 
337 state.renderer_initialized = true;
338 state.window_created = true;
339 state.needs_redraw = true;
340 state.last_update = Instant::now();
341 }
342 }
343 WinitEvent::WindowEvent { event, .. } => {
344 match event {
345 WindowEvent::ScaleFactorChanged { scale_factor, .. } => {
346 println!("Scale factor changed to: {}", scale_factor);
347 state.scale_factor = scale_factor;
348 if let Some(backend) = &mut state.backend {
349 backend.set_scale_factor(scale_factor);
350 }
351 }
352 WindowEvent::CursorMoved {
353 position,
354 device_id,
355 ..
356 } => {
357 state.cursor_position = position;
358 if let Some(strato_event) = convert_window_event(
359 WindowEvent::CursorMoved {
360 position,
361 device_id,
362 },
363 state.cursor_position,
364 state.scale_factor,
365 ) {
366 // println!("EventLoop: CursorMoved to {:?} (Logical), Scale: {}", strato_event, state.scale_factor);
367 if let Some(app) = &mut state.app {
368 app.handle_event(strato_event.clone());
369 }
370 handler(strato_event);
371 }
372 }
373 WindowEvent::Resized(physical_size) => {
374 // Resize the backend when the window is resized
375 if let Some(backend) = &mut state.backend {
376 backend.resize(physical_size.width, physical_size.height);
377 }
378 
379 let event = strato_core::event::Event::Window(
380 strato_core::event::WindowEvent::Resize {
381 width: physical_size.width,
382 height: physical_size.height,
383 },
384 );
385 
386 if let Some(app) = &mut state.app {
387 app.handle_event(event.clone());
388 }
389 handler(event);
390 }
391 WindowEvent::RedrawRequested => {
392 state.needs_redraw = false;
393 
394 // Get window size before borrowing app
395 let (physical_width, physical_height) =
396 if let Some(window) = &state.winit_window {
397 let size = window.inner_size();
398 (size.width as f32, size.height as f32)
399 } else {
400 (800.0, 600.0) // Default fallback
401 };
402 
403 // Get scale factor
404 let scale_factor = if let Some(window) = &state.winit_window {
405 window.scale_factor() as f32
406 } else {
407 1.0
408 };
409 
410 // Use logical size for layout
411 let logical_width = physical_width / scale_factor;
412 let logical_height = physical_height / scale_factor;
413 
414 // Call the application's render method and get the render batch
415 if let Some(app) = &mut state.app {
416 if let Err(e) = app.render_simple(logical_width, logical_height)
417 {
418 eprintln!("Render error: {}", e);
419 } else {
420 // Get the render batch
421 if let Some(batch) = app.get_render_batch() {
422 // Use Backend
423 if let Some(backend) = &mut state.backend {
424 if let Err(e) = backend.begin_frame() {
425 tracing::error!(
426 "Backend begin_frame error: {}",
427 e
428 );
429 } else {
430 // Submit the batch directly using the optimized path
431 if let Err(e) = backend.submit_batch(&batch) {
432 tracing::error!(
433 "Backend submit_batch error: {}",
434 e
435 );
436 }
437 
438 if let Err(e) = backend.end_frame() {
439 tracing::error!(
440 "Backend end_frame error: {}",
441 e
442 );
443 }
444 }
445 }
446 }
447 }
448 }
449 }
450 WindowEvent::CloseRequested => {
451 let event = strato_core::event::Event::Window(
452 strato_core::event::WindowEvent::Close,
453 );
454 if let Some(app) = &mut state.app {
455 app.handle_event(event.clone());
456 }
457 handler(event);
458 event_loop_window_target.exit();
459 }
460 _ => {
461 if let Some(strato_event) = convert_window_event(
462 event,
463 state.cursor_position,
464 state.scale_factor,
465 ) {
466 // println!("EventLoop: Event {:?}", strato_event);
467 if let Some(app) = &mut state.app {
468 app.handle_event(strato_event.clone());
469 }
470 handler(strato_event);
471 }
472 }
473 }
474 }
475 WinitEvent::AboutToWait => {
476 // Always request redraw to maintain continuous rendering
477 if state.renderer_initialized {
478 if let Some(window) = &state.winit_window {
479 window.request_redraw();
480 }
481 state.needs_redraw = true; // Keep requesting redraws
482 }
483 }
484 WinitEvent::UserEvent(custom_event) => {
485 if let Some(app) = &mut state.app {
486 app.handle_event(custom_event.event.clone());
487 }
488 handler(custom_event.event);
489 }
490 _ => {}
491 }
492 })
493 .expect("Event loop failed");
494 
495 Ok(())
496 }
497 
498 /// Run the event loop for WASM
499 #[cfg(target_arch = "wasm32")]
500 pub fn run_wasm<F>(self, mut handler: F)
501 where
502 F: FnMut(Event) + 'static,
503 {
504 use wasm_bindgen::prelude::*;
505 use web_sys::{window, Document, Element};
506 
507 // Set up web event listeners
508 let window = window().expect("should have a window in this context");
509 let document = window.document().expect("window should have a document");
510 
511 // Mouse events
512 let handler_clone = std::rc::Rc::new(std::cell::RefCell::new(handler));
513 
514 // Example: mouse click handler
515 let handler_ref = handler_clone.clone();
516 let closure = Closure::wrap(Box::new(move |event: web_sys::MouseEvent| {
517 let mouse_event = MouseEvent {
518 position: glam::Vec2::new(event.client_x() as f32, event.client_y() as f32),
519 button: Some(MouseButton::Left),
520 modifiers: Modifiers {
521 shift: event.shift_key(),
522 control: event.ctrl_key(),
523 alt: event.alt_key(),
524 super_key: event.meta_key(),
525 },
526 delta: glam::Vec2::ZERO,
527 };
528 handler_ref.borrow_mut()(Event::MouseDown(mouse_event));
529 }) as Box<dyn FnMut(_)>);
530 
531 document
532 .add_event_listener_with_callback("click", closure.as_ref().unchecked_ref())
533 .expect("should register click handler");
534 closure.forget();
535 
536 // Start the animation loop
537 self.start_animation_loop();
538 }
539 
540 #[cfg(target_arch = "wasm32")]
541 fn start_animation_loop(&self) {
542 use wasm_bindgen::prelude::*;
543 use web_sys::window;
544 
545 let f = std::rc::Rc::new(std::cell::RefCell::new(None));
546 let g = f.clone();
547 
548 *g.borrow_mut() = Some(Closure::wrap(Box::new(move || {
549 // Animation frame logic here
550 
551 // Schedule next frame
552 if let Some(window) = window() {
553 window
554 .request_animation_frame(f.borrow().as_ref().unwrap().as_ref().unchecked_ref())
555 .expect("should register animation frame");
556 }
557 }) as Box<dyn FnMut()>));
558 
559 if let Some(window) = window() {
560 window
561 .request_animation_frame(g.borrow().as_ref().unwrap().as_ref().unchecked_ref())
562 .expect("should register animation frame");
563 }
564 }
565}
566 
567impl Default for EventLoop {
568 fn default() -> Self {
569 Self::new().expect("Failed to create default event loop")
570 }
571}
572 
573/// Event loop proxy for sending custom events
574pub struct EventLoopProxy {
575 #[cfg(not(target_arch = "wasm32"))]
576 inner: winit::event_loop::EventLoopProxy<CustomEvent>,
577 #[cfg(target_arch = "wasm32")]
578 sender: mpsc::Sender<Event>,
579}
580 
581impl EventLoopProxy {
582 /// Send a custom event
583 pub fn send_event(&self, event: Event) -> Result<(), Box<dyn std::error::Error>> {
584 #[cfg(not(target_arch = "wasm32"))]
585 {
586 self.inner
587 .send_event(CustomEvent { event })
588 .map_err(|e| Box::new(e) as Box<dyn std::error::Error>)
589 }
590 
591 #[cfg(target_arch = "wasm32")]
592 {
593 self.sender
594 .send(event)
595 .map_err(|e| Box::new(e) as Box<dyn std::error::Error>)
596 }
597 }
598}
599 
600/// Event loop error
601#[derive(Debug, thiserror::Error)]
602pub enum EventLoopError {
603 #[error("Failed to send event")]
604 SendFailed,
605 #[error("Failed to create event loop")]
606 CreationFailed,
607 #[error("Failed to run event loop")]
608 RunFailed,
609}
610 
611/// Convert winit event to StratoUI event
612#[cfg(not(target_arch = "wasm32"))]
613pub fn convert_window_event(
614 event: winit::event::WindowEvent,
615 cursor_position: winit::dpi::PhysicalPosition<f64>,
616 scale_factor: f64,
617) -> Option<Event> {
618 use glam::Vec2;
619 use winit::event::{ElementState, MouseButton as MB, WindowEvent as WE};
620 
621 match event {
622 WE::CloseRequested => Some(Event::Window(WindowEvent::Close)),
623 
624 WE::Resized(size) => Some(Event::Window(WindowEvent::Resize {
625 width: size.width,
626 height: size.height,
627 })),
628 
629 WE::Moved(pos) => Some(Event::Window(WindowEvent::Move { x: pos.x, y: pos.y })),
630 
631 WE::Focused(focused) => Some(Event::Window(WindowEvent::Focus(focused))),
632 
633 WE::CursorMoved { position, .. } => {
634 let logical_x = position.x / scale_factor;
635 let logical_y = position.y / scale_factor;
636 Some(Event::MouseMove(MouseEvent {
637 position: Vec2::new(logical_x as f32, logical_y as f32),
638 button: None,
639 modifiers: Modifiers::default(),
640 delta: Vec2::ZERO,
641 }))
642 }
643 
644 WE::MouseInput { state, button, .. } => {
645 let button = match button {
646 MB::Left => MouseButton::Left,
647 MB::Right => MouseButton::Right,
648 MB::Middle => MouseButton::Middle,
649 MB::Back => MouseButton::Other(3),
650 MB::Forward => MouseButton::Other(4),
651 MB::Other(n) => MouseButton::Other(n),
652 };
653 
654 let logical_x = cursor_position.x / scale_factor;
655 let logical_y = cursor_position.y / scale_factor;
656 
657 match state {
658 ElementState::Pressed => Some(Event::MouseDown(MouseEvent {
659 position: Vec2::new(logical_x as f32, logical_y as f32),
660 button: Some(button),
661 modifiers: Modifiers::default(),
662 delta: Vec2::ZERO,
663 })),
664 ElementState::Released => Some(Event::MouseUp(MouseEvent {
665 position: Vec2::new(logical_x as f32, logical_y as f32),
666 button: Some(button),
667 modifiers: Modifiers::default(),
668 delta: Vec2::ZERO,
669 })),
670 }
671 }
672 
673 WE::MouseWheel { delta, .. } => {
674 let delta_vec = match delta {
675 winit::event::MouseScrollDelta::LineDelta(x, y) => Vec2::new(x * 20.0, y * 20.0),
676 winit::event::MouseScrollDelta::PixelDelta(pos) => {
677 Vec2::new(pos.x as f32, pos.y as f32)
678 }
679 };
680 
681 Some(Event::MouseWheel {
682 delta: delta_vec,
683 modifiers: Modifiers::default(),
684 })
685 }
686 
687 WE::KeyboardInput {
688 device_id: _,
689 event,
690 is_synthetic: _,
691 } => {
692 if let winit::keyboard::PhysicalKey::Code(keycode) = event.physical_key {
693 let key_code = convert_physical_key_code(keycode);
694 
695 match event.state {
696 ElementState::Pressed => Some(Event::KeyDown(KeyboardEvent {
697 key_code,
698 modifiers: Modifiers::default(),
699 is_repeat: event.repeat,
700 text: None,
701 })),
702 ElementState::Released => Some(Event::KeyUp(KeyboardEvent {
703 key_code,
704 modifiers: Modifiers::default(),
705 is_repeat: event.repeat,
706 text: None,
707 })),
708 }
709 } else {
710 None
711 }
712 }
713 
714 WE::Ime(winit::event::Ime::Commit(text)) => Some(Event::TextInput(text)),
715 
716 _ => None,
717 }
718}
719 
720/// Convert winit key code to StratoUI key code
721#[cfg(not(target_arch = "wasm32"))]
722fn convert_physical_key_code(keycode: winit::keyboard::KeyCode) -> KeyCode {
723 use winit::keyboard::KeyCode as WK;
724 
725 match keycode {
726 WK::KeyA => KeyCode::A,
727 WK::KeyB => KeyCode::B,
728 WK::KeyC => KeyCode::C,
729 WK::KeyD => KeyCode::D,
730 WK::KeyE => KeyCode::E,
731 WK::KeyF => KeyCode::F,
732 WK::KeyG => KeyCode::G,
733 WK::KeyH => KeyCode::H,
734 WK::KeyI => KeyCode::I,
735 WK::KeyJ => KeyCode::J,
736 WK::KeyK => KeyCode::K,
737 WK::KeyL => KeyCode::L,
738 WK::KeyM => KeyCode::M,
739 WK::KeyN => KeyCode::N,
740 WK::KeyO => KeyCode::O,
741 WK::KeyP => KeyCode::P,
742 WK::KeyQ => KeyCode::Q,
743 WK::KeyR => KeyCode::R,
744 WK::KeyS => KeyCode::S,
745 WK::KeyT => KeyCode::T,
746 WK::KeyU => KeyCode::U,
747 WK::KeyV => KeyCode::V,
748 WK::KeyW => KeyCode::W,
749 WK::KeyX => KeyCode::X,
750 WK::KeyY => KeyCode::Y,
751 WK::KeyZ => KeyCode::Z,
752 
753 WK::Digit0 => KeyCode::Num0,
754 WK::Digit1 => KeyCode::Num1,
755 WK::Digit2 => KeyCode::Num2,
756 WK::Digit3 => KeyCode::Num3,
757 WK::Digit4 => KeyCode::Num4,
758 WK::Digit5 => KeyCode::Num5,
759 WK::Digit6 => KeyCode::Num6,
760 WK::Digit7 => KeyCode::Num7,
761 WK::Digit8 => KeyCode::Num8,
762 WK::Digit9 => KeyCode::Num9,
763 
764 WK::F1 => KeyCode::F1,
765 WK::F2 => KeyCode::F2,
766 WK::F3 => KeyCode::F3,
767 WK::F4 => KeyCode::F4,
768 WK::F5 => KeyCode::F5,
769 WK::F6 => KeyCode::F6,
770 WK::F7 => KeyCode::F7,
771 WK::F8 => KeyCode::F8,
772 WK::F9 => KeyCode::F9,
773 WK::F10 => KeyCode::F10,
774 WK::F11 => KeyCode::F11,
775 WK::F12 => KeyCode::F12,
776 
777 WK::Enter => KeyCode::Enter,
778 WK::Escape => KeyCode::Escape,
779 WK::Backspace => KeyCode::Backspace,
780 WK::Tab => KeyCode::Tab,
781 WK::Space => KeyCode::Space,
782 
783 WK::ArrowLeft => KeyCode::Left,
784 WK::ArrowRight => KeyCode::Right,
785 WK::ArrowUp => KeyCode::Up,
786 WK::ArrowDown => KeyCode::Down,
787 
788 WK::ShiftLeft | WK::ShiftRight => KeyCode::Shift,
789 WK::ControlLeft | WK::ControlRight => KeyCode::Control,
790 WK::AltLeft | WK::AltRight => KeyCode::Alt,
791 WK::SuperLeft | WK::SuperRight => KeyCode::Super,
792 
793 WK::Delete => KeyCode::Delete,
794 WK::Insert => KeyCode::Insert,
795 WK::Home => KeyCode::Home,
796 WK::End => KeyCode::End,
797 WK::PageUp => KeyCode::PageUp,
798 WK::PageDown => KeyCode::PageDown,
799 
800 _ => KeyCode::A, // Default fallback
801 }
802}
803