2026-06-23 22:05:16 +04:00
|
|
|
#![forbid(unsafe_code)]
|
2026-06-23 22:32:50 +04:00
|
|
|
#![cfg_attr(
|
|
|
|
|
test,
|
|
|
|
|
allow(
|
|
|
|
|
clippy::cast_possible_truncation,
|
|
|
|
|
clippy::cast_possible_wrap,
|
|
|
|
|
clippy::cast_precision_loss,
|
|
|
|
|
clippy::expect_used,
|
|
|
|
|
clippy::float_cmp,
|
|
|
|
|
clippy::identity_op,
|
|
|
|
|
clippy::too_many_lines,
|
|
|
|
|
clippy::uninlined_format_args,
|
|
|
|
|
clippy::map_unwrap_or,
|
|
|
|
|
clippy::needless_raw_string_hashes,
|
|
|
|
|
clippy::semicolon_if_nothing_returned,
|
|
|
|
|
clippy::type_complexity,
|
|
|
|
|
clippy::panic,
|
|
|
|
|
clippy::unwrap_used
|
|
|
|
|
)
|
|
|
|
|
)]
|
2026-06-23 22:05:16 +04:00
|
|
|
//! Minimal `winit`-backed platform adapter shim.
|
|
|
|
|
|
|
|
|
|
use fparkan_platform::{
|
2026-06-23 22:50:32 +04:00
|
|
|
EventSource, MonotonicClock, MonotonicInstant, NativeWindowHandles, PhysicalSize,
|
|
|
|
|
PlatformError, PlatformEvent, RenderRequest, WindowHandle, WindowPort,
|
2026-06-23 22:05:16 +04:00
|
|
|
};
|
2026-06-23 22:50:32 +04:00
|
|
|
use raw_window_handle::{HasDisplayHandle, HasWindowHandle};
|
2026-06-23 22:05:16 +04:00
|
|
|
use std::collections::VecDeque;
|
|
|
|
|
use std::sync::atomic::{AtomicU64, Ordering};
|
2026-06-25 04:18:32 +04:00
|
|
|
use std::sync::OnceLock;
|
|
|
|
|
use std::time::Instant;
|
2026-06-23 22:32:50 +04:00
|
|
|
use winit::event::{Event, MouseButton, WindowEvent};
|
|
|
|
|
use winit::platform::scancode::PhysicalKeyExtScancode;
|
2026-06-25 04:18:32 +04:00
|
|
|
use winit::window::Window;
|
2026-06-23 22:05:16 +04:00
|
|
|
|
|
|
|
|
static NEXT_WINDOW_HANDLE_ID: AtomicU64 = AtomicU64::new(1);
|
2026-06-25 04:18:32 +04:00
|
|
|
static CLOCK_START: OnceLock<Instant> = OnceLock::new();
|
2026-06-23 23:42:20 +04:00
|
|
|
const DEFAULT_SMOKE_WIDTH: u32 = 1280;
|
|
|
|
|
const DEFAULT_SMOKE_HEIGHT: u32 = 720;
|
2026-06-23 22:05:16 +04:00
|
|
|
|
|
|
|
|
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 {
|
2026-06-25 04:18:32 +04:00
|
|
|
let elapsed = CLOCK_START.get_or_init(Instant::now).elapsed();
|
|
|
|
|
MonotonicInstant(elapsed.as_millis().try_into().unwrap_or(u64::MAX))
|
2026-06-23 22:05:16 +04:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Event source backed by pre-buffered platform events.
|
|
|
|
|
#[derive(Clone, Debug, Default)]
|
|
|
|
|
pub struct WinitEventSource {
|
|
|
|
|
queue: VecDeque<PlatformEvent>,
|
2026-06-25 04:32:38 +04:00
|
|
|
cursor_position: Option<(f64, f64)>,
|
|
|
|
|
minimized: Option<bool>,
|
|
|
|
|
occluded: Option<bool>,
|
2026-06-23 22:05:16 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl WinitEventSource {
|
|
|
|
|
/// Creates an empty source.
|
|
|
|
|
#[must_use]
|
|
|
|
|
pub const fn new() -> Self {
|
|
|
|
|
Self {
|
|
|
|
|
queue: VecDeque::new(),
|
2026-06-25 04:32:38 +04:00
|
|
|
cursor_position: None,
|
|
|
|
|
minimized: None,
|
|
|
|
|
occluded: None,
|
2026-06-23 22:05:16 +04:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// 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.
|
2026-06-23 22:32:50 +04:00
|
|
|
pub fn push_window_event(&mut self, event: &WindowEvent) {
|
2026-06-23 22:05:16 +04:00
|
|
|
match event {
|
|
|
|
|
WindowEvent::KeyboardInput { event, .. } => {
|
2026-06-25 04:32:38 +04:00
|
|
|
if let Some(scancode) = event.physical_key.to_scancode() {
|
|
|
|
|
self.queue.push_back(PlatformEvent::KeyboardInput {
|
|
|
|
|
scancode,
|
|
|
|
|
pressed: event.state.is_pressed(),
|
|
|
|
|
});
|
|
|
|
|
}
|
2026-06-23 22:05:16 +04:00
|
|
|
}
|
|
|
|
|
WindowEvent::MouseInput { state, button, .. } => {
|
2026-06-25 04:32:38 +04:00
|
|
|
let (x, y) = self.cursor_position.unwrap_or((0.0, 0.0));
|
2026-06-23 22:05:16 +04:00
|
|
|
self.queue.push_back(PlatformEvent::MouseInput {
|
|
|
|
|
button: mouse_button_code(*button),
|
|
|
|
|
pressed: state.is_pressed(),
|
2026-06-25 04:32:38 +04:00
|
|
|
x,
|
|
|
|
|
y,
|
2026-06-23 22:05:16 +04:00
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
WindowEvent::CursorMoved { position, .. } => {
|
2026-06-25 04:32:38 +04:00
|
|
|
self.cursor_position = Some((position.x, position.y));
|
2026-06-23 22:05:16 +04:00
|
|
|
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,
|
|
|
|
|
});
|
2026-06-25 04:32:38 +04:00
|
|
|
let minimized = size.width == 0 || size.height == 0;
|
|
|
|
|
if self.minimized != Some(minimized) {
|
|
|
|
|
self.minimized = Some(minimized);
|
|
|
|
|
self.queue.push_back(PlatformEvent::Minimized { minimized });
|
|
|
|
|
}
|
2026-06-23 22:05:16 +04:00
|
|
|
}
|
|
|
|
|
WindowEvent::Focused(focused) => {
|
|
|
|
|
self.queue
|
2026-06-23 22:32:50 +04:00
|
|
|
.push_back(PlatformEvent::FocusChanged { focused: *focused });
|
|
|
|
|
}
|
2026-06-25 04:32:38 +04:00
|
|
|
WindowEvent::Occluded(occluded) => {
|
|
|
|
|
if self.occluded != Some(*occluded) {
|
|
|
|
|
self.occluded = Some(*occluded);
|
|
|
|
|
self.queue.push_back(PlatformEvent::Occluded {
|
|
|
|
|
occluded: *occluded,
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-06-23 22:32:50 +04:00
|
|
|
WindowEvent::ScaleFactorChanged { scale_factor, .. } => {
|
|
|
|
|
self.queue.push_back(PlatformEvent::DpiChanged {
|
|
|
|
|
scale: *scale_factor,
|
|
|
|
|
});
|
2026-06-23 22:05:16 +04:00
|
|
|
}
|
|
|
|
|
WindowEvent::CloseRequested => {
|
|
|
|
|
self.queue.push_back(PlatformEvent::QuitRequested);
|
|
|
|
|
}
|
|
|
|
|
_ => {}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Pushes events from an event loop event.
|
2026-06-23 22:32:50 +04:00
|
|
|
pub fn push_event<T>(&mut self, event: &Event<T>) {
|
2026-06-25 04:32:38 +04:00
|
|
|
match event {
|
|
|
|
|
Event::Resumed => self.queue.push_back(PlatformEvent::Resumed),
|
|
|
|
|
Event::Suspended => self.queue.push_back(PlatformEvent::Suspended),
|
|
|
|
|
Event::WindowEvent { event, .. } => self.push_window_event(event),
|
|
|
|
|
_ => {}
|
2026-06-23 22:05:16 +04:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn mouse_button_code(button: MouseButton) -> u16 {
|
|
|
|
|
match button {
|
|
|
|
|
MouseButton::Left => 0,
|
|
|
|
|
MouseButton::Right => 1,
|
|
|
|
|
MouseButton::Middle => 2,
|
|
|
|
|
MouseButton::Back => 3,
|
|
|
|
|
MouseButton::Forward => 4,
|
2026-06-25 04:32:38 +04:00
|
|
|
MouseButton::Other(index) => 100_u16.saturating_add(index),
|
2026-06-23 22:05:16 +04:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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(())
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-23 23:42:20 +04:00
|
|
|
/// Window creation plan for native smoke entrypoints.
|
|
|
|
|
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
|
|
|
|
pub struct WinitWindowPlan {
|
|
|
|
|
/// Requested drawable width in physical pixels.
|
|
|
|
|
pub width: u32,
|
|
|
|
|
/// Requested drawable height in physical pixels.
|
|
|
|
|
pub height: u32,
|
|
|
|
|
/// Whether native window/display handles are required by the caller.
|
|
|
|
|
pub requires_native_handles: bool,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl WinitWindowPlan {
|
|
|
|
|
/// Returns the Stage 0 native smoke window plan.
|
|
|
|
|
#[must_use]
|
|
|
|
|
pub const fn smoke() -> Self {
|
|
|
|
|
Self {
|
|
|
|
|
width: DEFAULT_SMOKE_WIDTH,
|
|
|
|
|
height: DEFAULT_SMOKE_HEIGHT,
|
|
|
|
|
requires_native_handles: true,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Validates the window plan before a native event loop is entered.
|
|
|
|
|
///
|
|
|
|
|
/// # Errors
|
|
|
|
|
///
|
|
|
|
|
/// Returns [`PlatformError`] when the drawable extent is zero.
|
|
|
|
|
pub fn validate(self) -> Result<Self, PlatformError> {
|
|
|
|
|
if self.width == 0 || self.height == 0 {
|
|
|
|
|
return Err(PlatformError::Backend {
|
|
|
|
|
context: "winit window plan",
|
|
|
|
|
message: "drawable extent must be non-zero".to_string(),
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
Ok(self)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-23 22:05:16 +04:00
|
|
|
/// Minimal window view over a `winit` window.
|
2026-06-23 23:56:40 +04:00
|
|
|
#[derive(Clone, Copy, Debug)]
|
2026-06-23 22:05:16 +04:00
|
|
|
pub struct WinitWindow {
|
|
|
|
|
handle: WindowHandle,
|
|
|
|
|
width: u32,
|
|
|
|
|
height: u32,
|
|
|
|
|
scale: f64,
|
|
|
|
|
focused: bool,
|
|
|
|
|
minimized: bool,
|
|
|
|
|
occluded: bool,
|
2026-06-23 22:50:32 +04:00
|
|
|
native_handles: Option<NativeWindowHandles>,
|
2026-06-23 22:05:16 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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,
|
2026-06-25 04:18:32 +04:00
|
|
|
native_handles: window_native_handles(window),
|
2026-06-23 22:05:16 +04:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// 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,
|
2026-06-23 22:50:32 +04:00
|
|
|
native_handles: None,
|
2026-06-23 22:05:16 +04:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Returns requested default render profile for integration points.
|
|
|
|
|
#[must_use]
|
|
|
|
|
pub const fn default_render_request() -> RenderRequest {
|
|
|
|
|
RenderRequest::conservative()
|
|
|
|
|
}
|
2026-06-25 07:49:19 +04:00
|
|
|
|
|
|
|
|
/// Applies one platform event to the cached window descriptor state.
|
|
|
|
|
pub fn apply_event(&mut self, event: &PlatformEvent) {
|
|
|
|
|
match event {
|
|
|
|
|
PlatformEvent::Resize { width, height } => {
|
|
|
|
|
self.width = *width;
|
|
|
|
|
self.height = *height;
|
|
|
|
|
}
|
|
|
|
|
PlatformEvent::DpiChanged { scale } => {
|
|
|
|
|
self.scale = *scale;
|
|
|
|
|
}
|
|
|
|
|
PlatformEvent::FocusChanged { focused } => {
|
|
|
|
|
self.focused = *focused;
|
|
|
|
|
}
|
|
|
|
|
PlatformEvent::Minimized { minimized } => {
|
|
|
|
|
self.minimized = *minimized;
|
|
|
|
|
}
|
|
|
|
|
PlatformEvent::Occluded { occluded } => {
|
|
|
|
|
self.occluded = *occluded;
|
|
|
|
|
}
|
|
|
|
|
PlatformEvent::Suspended
|
|
|
|
|
| PlatformEvent::Resumed
|
|
|
|
|
| PlatformEvent::QuitRequested
|
|
|
|
|
| PlatformEvent::KeyboardInput { .. }
|
|
|
|
|
| PlatformEvent::MouseInput { .. }
|
|
|
|
|
| PlatformEvent::CursorMoved { .. } => {}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Applies a sequence of platform events to the cached window descriptor state.
|
|
|
|
|
pub fn apply_events<'a>(&mut self, events: impl IntoIterator<Item = &'a PlatformEvent>) {
|
|
|
|
|
for event in events {
|
|
|
|
|
self.apply_event(event);
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-06-23 22:05:16 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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
|
|
|
|
|
}
|
2026-06-23 22:50:32 +04:00
|
|
|
|
|
|
|
|
fn native_handles(&self) -> Option<NativeWindowHandles> {
|
|
|
|
|
self.native_handles
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-25 04:18:32 +04:00
|
|
|
/// Extracts raw handles from a live `winit::Window`.
|
|
|
|
|
#[must_use]
|
|
|
|
|
pub fn window_native_handles(window: &Window) -> Option<NativeWindowHandles> {
|
2026-06-23 22:50:32 +04:00
|
|
|
let display = window.display_handle().ok()?.as_raw();
|
|
|
|
|
let window = window.window_handle().ok()?.as_raw();
|
|
|
|
|
Some(NativeWindowHandles { display, window })
|
2026-06-23 22:05:16 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
|
mod tests {
|
|
|
|
|
use super::*;
|
2026-06-25 04:32:38 +04:00
|
|
|
use winit::event::{DeviceId, ElementState};
|
2026-06-23 22:05:16 +04:00
|
|
|
|
|
|
|
|
#[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)?;
|
2026-06-23 22:32:50 +04:00
|
|
|
assert_eq!(
|
|
|
|
|
events,
|
|
|
|
|
vec![PlatformEvent::Resumed, PlatformEvent::QuitRequested]
|
|
|
|
|
);
|
2026-06-23 22:05:16 +04:00
|
|
|
Ok(())
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn window_port_reports_default_request_profile() {
|
|
|
|
|
let window = WinitWindow::synthetic(640, 360);
|
|
|
|
|
let request = WinitWindow::default_render_request();
|
2026-06-23 22:32:50 +04:00
|
|
|
assert_eq!(
|
|
|
|
|
request.presentation,
|
|
|
|
|
fparkan_platform::PresentationMode::Fifo
|
|
|
|
|
);
|
|
|
|
|
assert_eq!(
|
|
|
|
|
window.drawable_size(),
|
|
|
|
|
PhysicalSize {
|
|
|
|
|
width: 640,
|
|
|
|
|
height: 360
|
|
|
|
|
}
|
|
|
|
|
);
|
2026-06-23 22:50:32 +04:00
|
|
|
assert!(window.native_handles().is_none());
|
2026-06-23 22:05:16 +04:00
|
|
|
}
|
|
|
|
|
|
2026-06-23 23:42:20 +04:00
|
|
|
#[test]
|
|
|
|
|
fn smoke_window_plan_requires_native_handles_and_nonzero_extent() -> Result<(), PlatformError> {
|
|
|
|
|
let plan = WinitWindowPlan::smoke().validate()?;
|
|
|
|
|
|
|
|
|
|
assert_eq!(plan.width, DEFAULT_SMOKE_WIDTH);
|
|
|
|
|
assert_eq!(plan.height, DEFAULT_SMOKE_HEIGHT);
|
|
|
|
|
assert!(plan.requires_native_handles);
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn smoke_window_plan_rejects_zero_extent() {
|
|
|
|
|
let plan = WinitWindowPlan {
|
|
|
|
|
width: 0,
|
|
|
|
|
height: DEFAULT_SMOKE_HEIGHT,
|
|
|
|
|
requires_native_handles: true,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
assert!(matches!(
|
|
|
|
|
plan.validate(),
|
|
|
|
|
Err(PlatformError::Backend {
|
|
|
|
|
context: "winit window plan",
|
|
|
|
|
..
|
|
|
|
|
})
|
|
|
|
|
));
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-23 23:56:40 +04:00
|
|
|
#[test]
|
2026-06-25 04:18:32 +04:00
|
|
|
fn monotonic_clock_uses_process_local_epoch() {
|
|
|
|
|
let clock = WinitClock;
|
|
|
|
|
let first = clock.now();
|
|
|
|
|
let second = clock.now();
|
2026-06-23 23:56:40 +04:00
|
|
|
|
2026-06-25 04:18:32 +04:00
|
|
|
assert!(second >= first);
|
2026-06-23 23:56:40 +04:00
|
|
|
}
|
|
|
|
|
|
2026-06-23 22:05:16 +04:00
|
|
|
#[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));
|
|
|
|
|
}
|
2026-06-25 04:32:38 +04:00
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn push_event_maps_lifecycle_resumed_and_suspended() -> Result<(), PlatformError> {
|
|
|
|
|
let mut source = WinitEventSource::new();
|
|
|
|
|
source.push_event(&Event::<()>::Resumed);
|
|
|
|
|
source.push_event(&Event::<()>::Suspended);
|
|
|
|
|
|
|
|
|
|
let mut events = Vec::new();
|
|
|
|
|
source.poll(&mut events)?;
|
|
|
|
|
|
|
|
|
|
assert_eq!(
|
|
|
|
|
events,
|
|
|
|
|
vec![PlatformEvent::Resumed, PlatformEvent::Suspended]
|
|
|
|
|
);
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn cursor_position_and_occlusion_are_preserved_for_mouse_input() -> Result<(), PlatformError> {
|
|
|
|
|
let mut source = WinitEventSource::new();
|
|
|
|
|
source.push_window_event(&WindowEvent::CursorMoved {
|
|
|
|
|
device_id: DeviceId::dummy(),
|
|
|
|
|
position: (320.0, 240.0).into(),
|
|
|
|
|
});
|
|
|
|
|
source.push_window_event(&WindowEvent::MouseInput {
|
|
|
|
|
device_id: DeviceId::dummy(),
|
|
|
|
|
state: ElementState::Pressed,
|
|
|
|
|
button: MouseButton::Other(u16::MAX),
|
|
|
|
|
});
|
|
|
|
|
source.push_window_event(&WindowEvent::Occluded(true));
|
|
|
|
|
|
|
|
|
|
let mut events = Vec::new();
|
|
|
|
|
source.poll(&mut events)?;
|
|
|
|
|
|
|
|
|
|
assert!(events.contains(&PlatformEvent::CursorMoved { x: 320.0, y: 240.0 }));
|
|
|
|
|
assert!(events.contains(&PlatformEvent::MouseInput {
|
|
|
|
|
button: u16::MAX,
|
|
|
|
|
pressed: true,
|
|
|
|
|
x: 320.0,
|
|
|
|
|
y: 240.0,
|
|
|
|
|
}));
|
|
|
|
|
assert!(events.contains(&PlatformEvent::Occluded { occluded: true }));
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn zero_extent_resize_updates_minimized_state() -> Result<(), PlatformError> {
|
|
|
|
|
let mut source = WinitEventSource::new();
|
|
|
|
|
source.push_window_event(&WindowEvent::Resized(winit::dpi::PhysicalSize::new(
|
|
|
|
|
0u32, 720u32,
|
|
|
|
|
)));
|
|
|
|
|
source.push_window_event(&WindowEvent::Resized(winit::dpi::PhysicalSize::new(
|
|
|
|
|
1280u32, 720u32,
|
|
|
|
|
)));
|
|
|
|
|
|
|
|
|
|
let mut events = Vec::new();
|
|
|
|
|
source.poll(&mut events)?;
|
|
|
|
|
|
|
|
|
|
assert!(events.contains(&PlatformEvent::Minimized { minimized: true }));
|
|
|
|
|
assert!(events.contains(&PlatformEvent::Minimized { minimized: false }));
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|
2026-06-25 07:49:19 +04:00
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn window_descriptor_applies_lifecycle_and_resize_events() {
|
|
|
|
|
let mut window = WinitWindow::synthetic(640, 360);
|
|
|
|
|
let events = [
|
|
|
|
|
PlatformEvent::Resize {
|
|
|
|
|
width: 1280,
|
|
|
|
|
height: 720,
|
|
|
|
|
},
|
|
|
|
|
PlatformEvent::DpiChanged { scale: 2.0 },
|
|
|
|
|
PlatformEvent::FocusChanged { focused: false },
|
|
|
|
|
PlatformEvent::Minimized { minimized: true },
|
|
|
|
|
PlatformEvent::Occluded { occluded: true },
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
window.apply_events(events.iter());
|
|
|
|
|
|
|
|
|
|
assert_eq!(
|
|
|
|
|
window.drawable_size(),
|
|
|
|
|
PhysicalSize {
|
|
|
|
|
width: 1280,
|
|
|
|
|
height: 720,
|
|
|
|
|
}
|
|
|
|
|
);
|
|
|
|
|
assert_eq!(window.dpi_scale(), 2.0);
|
|
|
|
|
assert!(!window.has_focus());
|
|
|
|
|
assert!(window.is_minimized());
|
|
|
|
|
assert!(window.is_occluded());
|
|
|
|
|
}
|
2026-06-23 22:05:16 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// SAFETY: no unsafe usage in this crate.
|