diff options
Diffstat (limited to 'adapters/fparkan-platform-winit')
| -rw-r--r-- | adapters/fparkan-platform-winit/Cargo.toml | 13 | ||||
| -rw-r--r-- | adapters/fparkan-platform-winit/src/lib.rs | 257 |
2 files changed, 270 insertions, 0 deletions
diff --git a/adapters/fparkan-platform-winit/Cargo.toml b/adapters/fparkan-platform-winit/Cargo.toml new file mode 100644 index 0000000..e0ec438 --- /dev/null +++ b/adapters/fparkan-platform-winit/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "fparkan-platform-winit" +version.workspace = true +edition.workspace = true +license.workspace = true +repository.workspace = true + +[dependencies] +fparkan-platform = { path = "../../crates/fparkan-platform" } +winit = "0.30" + +[lints] +workspace = true diff --git a/adapters/fparkan-platform-winit/src/lib.rs b/adapters/fparkan-platform-winit/src/lib.rs new file mode 100644 index 0000000..ec30908 --- /dev/null +++ b/adapters/fparkan-platform-winit/src/lib.rs @@ -0,0 +1,257 @@ +#![forbid(unsafe_code)] +//! Minimal `winit`-backed platform adapter shim. + +use fparkan_platform::{ + EventSource, MonotonicClock, MonotonicInstant, PlatformEvent, PlatformError, PhysicalSize, + RenderRequest, WindowHandle, WindowPort, +}; +use winit::event::{MouseButton, WindowEvent}; +use winit::event_loop::Event; +use std::collections::VecDeque; +use std::sync::atomic::{AtomicU64, Ordering}; +use std::time::{SystemTime, UNIX_EPOCH}; +use winit::window::Window; + +static NEXT_WINDOW_HANDLE_ID: AtomicU64 = AtomicU64::new(1); + +fn next_window_id() -> u64 { + NEXT_WINDOW_HANDLE_ID.fetch_add(1, Ordering::Relaxed) +} + +/// Simple monotonic clock for windowing abstractions. +#[derive(Clone, Copy, Debug)] +pub struct WinitClock; + +impl MonotonicClock for WinitClock { + fn now(&self) -> MonotonicInstant { + let duration = SystemTime::now() + .duration_since(UNIX_EPOCH) + .unwrap_or_default(); + MonotonicInstant(duration.as_millis().try_into().unwrap_or(u64::MAX)) + } +} + +/// Event source backed by pre-buffered platform events. +#[derive(Clone, Debug, Default)] +pub struct WinitEventSource { + queue: VecDeque<PlatformEvent>, +} + +impl WinitEventSource { + /// Creates an empty source. + #[must_use] + pub const fn new() -> Self { + Self { + queue: VecDeque::new(), + } + } + + /// Pushes a synthetic event (used by tests and smoke stubs). + pub fn push(&mut self, event: PlatformEvent) { + self.queue.push_back(event); + } + + /// Pushes a mapped native window event. + pub fn push_window_event(&mut self, event: &WindowEvent<'_>) { + match event { + WindowEvent::KeyboardInput { event, .. } => { + self.queue.push_back(PlatformEvent::KeyboardInput { + scancode: event.physical_key.to_scancode().unwrap_or(0), + pressed: event.state.is_pressed(), + }); + } + WindowEvent::MouseInput { state, button, .. } => { + self.queue.push_back(PlatformEvent::MouseInput { + button: mouse_button_code(*button), + pressed: state.is_pressed(), + x: 0.0, + y: 0.0, + }); + } + WindowEvent::CursorMoved { position, .. } => { + self.queue.push_back(PlatformEvent::CursorMoved { + x: position.x, + y: position.y, + }); + } + WindowEvent::Resized(size) => { + self.queue.push_back(PlatformEvent::Resize { + width: size.width, + height: size.height, + }); + } + WindowEvent::Focused(focused) => { + self.queue.push_back(PlatformEvent::FocusChanged { focused: *focused }); + } + WindowEvent::ScaleFactorChanged { + scale_factor, + .. + } => { + self.queue + .push_back(PlatformEvent::DpiChanged { scale: *scale_factor }); + } + WindowEvent::CloseRequested => { + self.queue.push_back(PlatformEvent::QuitRequested); + } + _ => {} + } + } + + /// Pushes events from an event loop event. + pub fn push_event<T>(&mut self, event: &Event<'_, T>) { + if let Event::WindowEvent { event, .. } = event { + self.push_window_event(event); + } + } +} + +fn mouse_button_code(button: MouseButton) -> u16 { + match button { + MouseButton::Left => 0, + MouseButton::Right => 1, + MouseButton::Middle => 2, + MouseButton::Back => 3, + MouseButton::Forward => 4, + MouseButton::Other(index) => 100 + u16::try_from(index).unwrap_or(0), + } +} + +impl EventSource for WinitEventSource { + fn poll(&mut self, out: &mut Vec<PlatformEvent>) -> Result<(), PlatformError> { + while let Some(event) = self.queue.pop_front() { + out.push(event); + } + Ok(()) + } +} + +/// Minimal window view over a `winit` window. +#[derive(Clone, Debug)] +pub struct WinitWindow { + handle: WindowHandle, + width: u32, + height: u32, + scale: f64, + focused: bool, + minimized: bool, + occluded: bool, +} + +impl WinitWindow { + /// Builds a stable descriptor from a `winit` window. + #[must_use] + pub fn from_window(window: &Window) -> Self { + let scale = window.scale_factor(); + let size = window.inner_size(); + Self { + handle: WindowHandle { + id: next_window_id(), + }, + width: size.width, + height: size.height, + scale, + focused: true, + minimized: false, + occluded: false, + } + } + + /// Returns conservative defaults if a native window is not available yet. + #[must_use] + pub fn synthetic(width: u32, height: u32) -> Self { + Self { + handle: WindowHandle { + id: next_window_id(), + }, + width, + height, + scale: 1.0, + focused: true, + minimized: false, + occluded: false, + } + } + + /// Returns requested default render profile for integration points. + #[must_use] + pub const fn default_render_request() -> RenderRequest { + RenderRequest::conservative() + } +} + +impl WindowPort for WinitWindow { + fn drawable_size(&self) -> PhysicalSize { + PhysicalSize { + width: self.width, + height: self.height, + } + } + + fn dpi_scale(&self) -> f64 { + self.scale + } + + fn has_focus(&self) -> bool { + self.focused + } + + fn is_minimized(&self) -> bool { + self.minimized + } + + fn is_occluded(&self) -> bool { + self.occluded + } + + fn handle(&self) -> WindowHandle { + self.handle + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn event_source_buffers_synthetic_events() -> Result<(), PlatformError> { + let mut source = WinitEventSource::new(); + source.push(PlatformEvent::Resumed); + source.push(PlatformEvent::QuitRequested); + let mut events = Vec::new(); + source.poll(&mut events)?; + assert_eq!(events, vec![PlatformEvent::Resumed, PlatformEvent::QuitRequested]); + Ok(()) + } + + #[test] + fn window_port_reports_default_request_profile() { + let window = WinitWindow::synthetic(640, 360); + let request = WinitWindow::default_render_request(); + assert_eq!(request.presentation, fparkan_platform::PresentationMode::Fifo); + assert_eq!(window.drawable_size(), PhysicalSize { width: 640, height: 360 }); + } + + #[test] + fn window_events_push_expected_platform_events() { + let mut source = WinitEventSource::new(); + let size = winit::dpi::PhysicalSize::new(1024u32, 768u32); + + source.push_window_event(&WindowEvent::Resized(size)); + source.push_window_event(&WindowEvent::Focused(false)); + source.push_window_event(&WindowEvent::CloseRequested); + + let mut events = Vec::new(); + source + .poll(&mut events) + .expect("platform event pump should never fail"); + + assert!(events.contains(&PlatformEvent::Resize { + width: 1024, + height: 768, + })); + assert!(events.contains(&PlatformEvent::FocusChanged { focused: false })); + assert!(events.contains(&PlatformEvent::QuitRequested)); + } +} + +// SAFETY: no unsafe usage in this crate. |
