use std::sync::Arc;
use unlit_ecs::World;
use winit::event::{ElementState, WindowEvent};
use winit::event_loop::{EventLoopProxy, OwnedDisplayHandle};
use winit::keyboard::{KeyCode, PhysicalKey};
use winit::window::{Window, WindowAttributes};
use crate::input::winit::WinitInput;
use super::event::{OnAboutToWait, OnResumed, OnWindowEvent};
#[derive(Clone)]
pub struct WindowSpec(pub WindowAttributes);
pub struct WinitWindow(pub Arc<Window>);
pub fn window(world: &World) -> Option<Arc<Window>> {
world
.query::<&WinitWindow>()
.next()
.map(|(_, window)| window.0.clone())
}
pub struct CreateWindowRequest(pub Option<WindowAttributes>);
pub struct ExitRequest(pub bool);
pub struct Resumed(pub bool);
pub struct DisplayHandle(pub OwnedDisplayHandle);
pub struct WinitProxy<U: 'static>(pub EventLoopProxy<U>);
pub fn create_window_on_resume() -> OnResumed {
OnResumed::new(|world, entity, ()| {
if window(world).is_some() {
return;
}
let Some(attributes) = world.get::<WindowSpec>(entity).map(|spec| spec.0.clone()) else {
log::warn!(
"create_window_on_resume: the entity carries no WindowSpec, so no window is created"
);
return;
};
let _ = world
.with_mut::<CreateWindowRequest, _>(entity, |request| request.0 = Some(attributes));
})
}
pub fn exit_on_close_requested() -> OnWindowEvent {
OnWindowEvent::new(|world, entity, payload| {
if matches!(payload.event, WindowEvent::CloseRequested) {
let _ = world.with_mut::<ExitRequest, _>(entity, |request| request.0 = true);
}
})
}
pub fn exit_on_escape() -> OnWindowEvent {
OnWindowEvent::new(|world, entity, payload| {
if let WindowEvent::KeyboardInput { event, .. } = &payload.event
&& event.state == ElementState::Pressed
&& !event.repeat
&& event.physical_key == PhysicalKey::Code(KeyCode::Escape)
{
let _ = world.with_mut::<ExitRequest, _>(entity, |request| request.0 = true);
}
})
}
pub fn request_redraw_while_foreground() -> OnAboutToWait {
OnAboutToWait::new(|world, _entity, ()| {
let resumed = world
.query::<&Resumed>()
.next()
.is_some_and(|(_, state)| state.0);
if resumed && let Some(window) = window(world) {
window.request_redraw();
}
})
}
pub fn winit_input_behaviour(input: WinitInput) -> OnWindowEvent {
OnWindowEvent::new(move |_world, _entity, payload| {
input.on_window_event(&payload.event);
})
}
#[cfg(test)]
mod tests {
use super::*;
use crate::winit::event::WinitWindowEvent;
use winit::window::WindowId;
fn window_event(event: WindowEvent) -> WinitWindowEvent {
WinitWindowEvent {
window_id: WindowId::dummy(),
event,
}
}
#[test]
fn exit_on_close_requested_asks_the_host_to_exit() {
let mut world = World::new();
let entity = world.spawn((ExitRequest(false),));
world.apply();
let mut behaviour = exit_on_close_requested();
behaviour.run(&world, entity, &window_event(WindowEvent::CloseRequested));
assert_eq!(
world.with_mut::<ExitRequest, _>(entity, |request| request.0),
Some(true)
);
}
#[test]
fn exit_on_close_requested_ignores_other_events() {
let mut world = World::new();
let entity = world.spawn((ExitRequest(false),));
world.apply();
let mut behaviour = exit_on_close_requested();
behaviour.run(&world, entity, &window_event(WindowEvent::Focused(true)));
assert_eq!(
world.with_mut::<ExitRequest, _>(entity, |request| request.0),
Some(false)
);
}
#[test]
fn create_window_on_resume_copies_the_spec_into_the_request() {
let mut world = World::new();
let spec = WindowSpec(Window::default_attributes().with_title("unlit3d"));
let entity = world.spawn((spec, CreateWindowRequest(None), create_window_on_resume()));
world.apply();
let mut behaviour = world
.get_mut::<OnResumed>(entity)
.expect("the behaviour is there");
behaviour.run(&world, entity, &());
let attributes = world
.with_mut::<CreateWindowRequest, _>(entity, |request| request.0.take())
.flatten()
.expect("the request is filled");
assert_eq!(attributes.title, "unlit3d");
}
#[test]
fn request_redraw_while_foreground_needs_the_resumed_state() {
let mut world = World::new();
let entity = world.spawn((Resumed(false), request_redraw_while_foreground()));
world.apply();
let mut behaviour = world
.get_mut::<OnAboutToWait>(entity)
.expect("the behaviour is there");
behaviour.run(&world, entity, &());
}
}