2026-07-03 23:00:49 +01:00

233 lines
7.0 KiB
Rust

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<FbViewport> {
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());
}
}