aboutsummaryrefslogtreecommitdiff
path: root/adapters/fparkan-platform-winit/src/lib.rs
diff options
context:
space:
mode:
Diffstat (limited to 'adapters/fparkan-platform-winit/src/lib.rs')
-rw-r--r--adapters/fparkan-platform-winit/src/lib.rs257
1 files changed, 257 insertions, 0 deletions
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.