use std::marker::PhantomData;
use std::sync::Arc;
use unlit_ecs::{Entity, World};
use winit::application::ApplicationHandler;
use winit::error::EventLoopError;
use winit::event::{DeviceEvent, DeviceId, StartCause, WindowEvent};
use winit::event_loop::{ActiveEventLoop, EventLoop};
use winit::window::{WindowAttributes, WindowId};
use crate::behaviour::{EventCallback, behaviour};
use crate::input::distribute_input;
use super::builtin::{CreateWindowRequest, DisplayHandle, ExitRequest, Resumed, WinitWindow};
pub type WinitCallback<E> = EventCallback<E>;
behaviour!(
OnNewEvents,
StartCause,
"Runs when the event loop begins a batch of events."
);
behaviour!(
OnResumed,
(),
"Runs when the window system resumes the application."
);
behaviour!(
OnSuspended,
(),
"Runs when the window system suspends the application."
);
behaviour!(
OnWindowEvent,
WinitWindowEvent,
"Runs on every window event."
);
behaviour!(
OnDeviceEvent,
WinitDeviceEvent,
"Runs on every device event."
);
behaviour!(
OnAboutToWait,
(),
"Runs when the event loop is about to wait for events."
);
behaviour!(OnExiting, (), "Runs when the event loop is about to exit.");
behaviour!(
OnMemoryWarning,
(),
"Runs when the system reports a memory warning."
);
pub type WinitUserCallback<U> = Box<dyn FnMut(&World, Entity, &mut U)>;
pub struct OnUserEvent<U: 'static>(pub WinitUserCallback<U>);
impl<U: 'static> OnUserEvent<U> {
pub fn new(f: impl FnMut(&World, Entity, &mut U) + 'static) -> Self {
Self(Box::new(f))
}
pub fn run(&mut self, world: &World, entity: Entity, event: &mut U) {
(self.0)(world, entity, event);
}
}
pub struct WinitWindowEvent {
pub window_id: WindowId,
pub event: WindowEvent,
}
pub struct WinitDeviceEvent {
pub device_id: DeviceId,
pub event: DeviceEvent,
}
pub struct WinitHost<U: 'static = ()> {
world: World,
marker: PhantomData<fn() -> U>,
}
impl<U: 'static> WinitHost<U> {
pub fn new() -> Self {
Self {
world: World::new(),
marker: PhantomData,
}
}
pub fn world(&self) -> &World {
&self.world
}
pub fn world_mut(&mut self) -> &mut World {
&mut self.world
}
#[cfg(not(target_arch = "wasm32"))]
pub fn run(mut self, event_loop: EventLoop<U>) -> Result<(), EventLoopError> {
self.prepare(&event_loop);
event_loop.run_app(&mut self)
}
#[cfg(target_arch = "wasm32")]
pub fn run(mut self, event_loop: EventLoop<U>) -> Result<(), EventLoopError> {
use winit::platform::web::EventLoopExtWebSys;
self.prepare(&event_loop);
event_loop.spawn_app(self);
Ok(())
}
fn prepare(&mut self, event_loop: &EventLoop<U>) {
let display = event_loop.owned_display_handle();
self.world.spawn((Resumed(false), DisplayHandle(display)));
}
fn set_resumed(&mut self, resumed: bool) {
let Some(entity) = self
.world
.query::<&Resumed>()
.next()
.map(|(entity, _)| entity)
else {
return;
};
let _ = self
.world
.with_mut::<Resumed, _>(entity, |state| state.0 = resumed);
}
fn settle(&mut self, event_loop: &ActiveEventLoop) {
self.world.apply();
self.create_windows(event_loop);
self.exit_requested(event_loop);
distribute_input(&self.world);
}
fn create_windows(&mut self, event_loop: &ActiveEventLoop) {
let requests: Vec<WindowAttributes> = self
.world
.query::<&mut CreateWindowRequest>()
.filter_map(|(_, mut request)| request.0.take())
.collect();
for attributes in requests {
match event_loop.create_window(attributes) {
Ok(window) => {
self.world.spawn((WinitWindow(Arc::new(window)),));
}
Err(error) => {
log::error!("could not create the window: {error}");
event_loop.exit();
}
}
}
}
fn exit_requested(&mut self, event_loop: &ActiveEventLoop) {
let mut exit = false;
for (_entity, mut request) in self.world.query::<&mut ExitRequest>() {
if request.0 {
request.0 = false;
exit = true;
}
}
if exit {
event_loop.exit();
}
}
}
impl<U: 'static> Default for WinitHost<U> {
fn default() -> Self {
Self::new()
}
}
impl<U: 'static> ApplicationHandler<U> for WinitHost<U> {
fn new_events(&mut self, event_loop: &ActiveEventLoop, cause: StartCause) {
for (entity, mut behaviour) in self.world.query::<&mut OnNewEvents>() {
behaviour.run(&self.world, entity, &cause);
}
self.settle(event_loop);
}
fn resumed(&mut self, event_loop: &ActiveEventLoop) {
self.set_resumed(true);
for (entity, mut behaviour) in self.world.query::<&mut OnResumed>() {
behaviour.run(&self.world, entity, &());
}
self.settle(event_loop);
}
fn suspended(&mut self, event_loop: &ActiveEventLoop) {
for (entity, mut behaviour) in self.world.query::<&mut OnSuspended>() {
behaviour.run(&self.world, entity, &());
}
self.settle(event_loop);
self.set_resumed(false);
}
fn user_event(&mut self, event_loop: &ActiveEventLoop, mut event: U) {
for (entity, mut behaviour) in self.world.query::<&mut OnUserEvent<U>>() {
behaviour.run(&self.world, entity, &mut event);
}
self.settle(event_loop);
}
fn window_event(
&mut self,
event_loop: &ActiveEventLoop,
window_id: WindowId,
event: WindowEvent,
) {
let event = WinitWindowEvent { window_id, event };
for (entity, mut behaviour) in self.world.query::<&mut OnWindowEvent>() {
behaviour.run(&self.world, entity, &event);
}
self.settle(event_loop);
}
fn device_event(
&mut self,
event_loop: &ActiveEventLoop,
device_id: DeviceId,
event: DeviceEvent,
) {
let event = WinitDeviceEvent { device_id, event };
for (entity, mut behaviour) in self.world.query::<&mut OnDeviceEvent>() {
behaviour.run(&self.world, entity, &event);
}
self.settle(event_loop);
}
fn about_to_wait(&mut self, event_loop: &ActiveEventLoop) {
for (entity, mut behaviour) in self.world.query::<&mut OnAboutToWait>() {
behaviour.run(&self.world, entity, &());
}
self.settle(event_loop);
}
fn exiting(&mut self, event_loop: &ActiveEventLoop) {
for (entity, mut behaviour) in self.world.query::<&mut OnExiting>() {
behaviour.run(&self.world, entity, &());
}
self.settle(event_loop);
}
fn memory_warning(&mut self, event_loop: &ActiveEventLoop) {
for (entity, mut behaviour) in self.world.query::<&mut OnMemoryWarning>() {
behaviour.run(&self.world, entity, &());
}
self.settle(event_loop);
}
}
#[cfg(test)]
mod tests {
use std::cell::RefCell;
use std::rc::Rc;
use super::*;
use crate::winit::builtin::Resumed;
use winit::window::WindowId;
#[test]
fn set_resumed_writes_the_host_state() {
let mut host = WinitHost::<()>::new();
host.world_mut().spawn((Resumed(false),));
host.set_resumed(true);
assert_eq!(
host.world()
.query::<&Resumed>()
.next()
.map(|(_, state)| state.0),
Some(true)
);
}
#[test]
fn every_behaviour_of_a_family_runs_with_the_payload() {
let world = World::new();
let seen = Rc::new(RefCell::new(0u32));
let payload = WinitWindowEvent {
window_id: WindowId::dummy(),
event: WindowEvent::Focused(true),
};
let first = OnWindowEvent::new({
let seen = seen.clone();
move |_world, _entity, event| {
assert!(matches!(event.event, WindowEvent::Focused(true)));
*seen.borrow_mut() += 1;
}
});
let second = OnWindowEvent::new({
let seen = seen.clone();
move |_world, _entity, _event| *seen.borrow_mut() += 1
});
for mut behaviour in [first, second] {
behaviour.run(&world, Entity::from_bits(0), &payload);
}
assert_eq!(*seen.borrow(), 2);
}
#[test]
fn a_user_event_carries_its_own_type() {
let world = World::new();
let seen = Rc::new(RefCell::new(None));
let mut behaviour = OnUserEvent::<u32>::new({
let seen = seen.clone();
move |_world, _entity, event| *seen.borrow_mut() = Some(*event)
});
behaviour.run(&world, Entity::from_bits(0), &mut 7);
assert_eq!(*seen.borrow(), Some(7));
}
}