use prometeu_hal::InputSignals; use winit::event::{ElementState, MouseButton, WindowEvent}; use winit::keyboard::{KeyCode, PhysicalKey}; use winit::window::Window; #[derive(Debug, Clone, Copy)] struct FbViewport { pub x: f32, pub y: f32, pub w: f32, pub h: f32, pub scale: f32, } impl FbViewport { fn framebuffer_viewport(window: &Window, display_size: (usize, usize)) -> Option { let size = window.inner_size(); let win_w = size.width as f32; let win_h = size.height as f32; if win_w <= 0.0 || win_h <= 0.0 { return None; } let fb_w = display_size.0 as f32; let fb_h = display_size.1 as f32; let scale_x = (win_w / fb_w).floor(); let scale_y = (win_h / fb_h).floor(); let scale = scale_x.min(scale_y).max(1.0); let viewport_w = fb_w * scale; let viewport_h = fb_h * scale; let viewport_x = ((win_w - viewport_w) * 0.5).floor(); let viewport_y = ((win_h - viewport_h) * 0.5).floor(); Some(FbViewport { x: viewport_x, y: viewport_y, w: viewport_w, h: viewport_h, scale }) } } pub struct HostInputHandler { pub signals: InputSignals, system_controls: HostSystemControls, display_size: (usize, usize), } #[derive(Debug, Default, Clone, Copy, PartialEq, Eq)] pub struct HostSystemControls { home_requested: bool, } impl HostSystemControls { pub fn request_home(&mut self) { self.home_requested = true; } pub fn take_home_requested(&mut self) -> bool { let requested = self.home_requested; self.home_requested = false; requested } } impl Default for HostInputHandler { fn default() -> Self { Self::new((480, 270)) } } impl HostInputHandler { pub fn new(display_size: (usize, usize)) -> Self { Self { signals: InputSignals::default(), system_controls: HostSystemControls::default(), display_size, } } pub fn take_home_requested(&mut self) -> bool { self.system_controls.take_home_requested() } pub fn handle_key_code(&mut self, code: KeyCode, state: ElementState) { let is_down = state == ElementState::Pressed; match code { KeyCode::Escape | KeyCode::Home if is_down => self.system_controls.request_home(), KeyCode::ArrowUp => self.signals.up_signal = is_down, KeyCode::ArrowDown => self.signals.down_signal = is_down, KeyCode::ArrowLeft => self.signals.left_signal = is_down, KeyCode::ArrowRight => self.signals.right_signal = is_down, KeyCode::KeyA => self.signals.a_signal = is_down, KeyCode::KeyD => self.signals.b_signal = is_down, KeyCode::KeyW => self.signals.x_signal = is_down, KeyCode::KeyS => self.signals.y_signal = is_down, KeyCode::KeyQ => self.signals.l_signal = is_down, KeyCode::KeyE => self.signals.r_signal = is_down, KeyCode::KeyZ => self.signals.start_signal = is_down, KeyCode::ShiftLeft | KeyCode::ShiftRight => self.signals.select_signal = is_down, _ => {} } } pub fn handle_event(&mut self, event: &WindowEvent, window: &Window) { match event { WindowEvent::KeyboardInput { event, .. } => { if let PhysicalKey::Code(code) = event.physical_key { self.handle_key_code(code, event.state); } } WindowEvent::CursorMoved { position, .. } => { if let Some((x, y)) = window_to_fb(position.x as f32, position.y as f32, window, self.display_size) { self.signals.x_pos = x; self.signals.y_pos = y; } } WindowEvent::MouseInput { state, button, .. } if *button == MouseButton::Left => { match state { ElementState::Pressed => { self.signals.f_signal = true; } ElementState::Released => { self.signals.f_signal = false; } } } _ => {} } fn window_to_fb( wx: f32, wy: f32, window: &Window, display_size: (usize, usize), ) -> Option<(i32, i32)> { let viewport = FbViewport::framebuffer_viewport(window, display_size)?; let local_x = wx - viewport.x; let local_y = wy - viewport.y; if local_x < 0.0 || local_y < 0.0 || local_x >= viewport.w || local_y >= viewport.h { return None; } let fb_x = (local_x / viewport.scale).floor() as i32; let fb_y = (local_y / viewport.scale).floor() as i32; Some(( fb_x.clamp(0, display_size.0 as i32 - 1), fb_y.clamp(0, display_size.1 as i32 - 1), )) } } } #[cfg(test)] mod tests { use super::*; fn assert_no_pad_signal(signals: InputSignals) { assert!(!signals.up_signal); assert!(!signals.down_signal); assert!(!signals.left_signal); assert!(!signals.right_signal); assert!(!signals.a_signal); assert!(!signals.b_signal); assert!(!signals.x_signal); assert!(!signals.y_signal); assert!(!signals.l_signal); assert!(!signals.r_signal); assert!(!signals.start_signal); assert!(!signals.select_signal); assert!(!signals.f_signal); } #[test] fn escape_requests_home_without_mutating_guest_pad() { let mut input = HostInputHandler::default(); input.handle_key_code(KeyCode::Escape, ElementState::Pressed); assert!(input.take_home_requested()); assert!(!input.take_home_requested()); assert_no_pad_signal(input.signals); } #[test] fn physical_home_requests_home_without_mutating_guest_pad() { let mut input = HostInputHandler::default(); input.handle_key_code(KeyCode::Home, ElementState::Pressed); assert!(input.take_home_requested()); assert_no_pad_signal(input.signals); } #[test] fn home_release_does_not_emit_a_new_home_request() { let mut input = HostInputHandler::default(); input.handle_key_code(KeyCode::Home, ElementState::Released); assert!(!input.take_home_requested()); assert_no_pad_signal(input.signals); } #[test] fn existing_keyboard_pad_mapping_is_preserved() { let mut input = HostInputHandler::default(); input.handle_key_code(KeyCode::KeyZ, ElementState::Pressed); input.handle_key_code(KeyCode::ShiftLeft, ElementState::Pressed); input.handle_key_code(KeyCode::ArrowUp, ElementState::Pressed); assert!(input.signals.start_signal); assert!(input.signals.select_signal); assert!(input.signals.up_signal); assert!(!input.take_home_requested()); } }