From a12b508c446a49c2edabb2e89ae4c6632b2b57b3 Mon Sep 17 00:00:00 2001 From: Mads Marquart Date: Thu, 22 Dec 2022 04:45:36 +0100 Subject: [PATCH] Add ApplicationHandler trait --- examples/control_flow.rs | 222 ++++++++++++++++++------------ examples/multiwindow.rs | 116 ++++++++++------ examples/window.rs | 99 ++++++++++---- examples/window_ondemand.rs | 5 +- examples/window_pump_events.rs | 3 + src/application.rs | 243 +++++++++++++++++++++++++++++++++ src/event_loop.rs | 2 +- src/lib.rs | 3 + 8 files changed, 536 insertions(+), 157 deletions(-) create mode 100644 src/application.rs diff --git a/examples/control_flow.rs b/examples/control_flow.rs index 5aa7aaa830..96ad374952 100644 --- a/examples/control_flow.rs +++ b/examples/control_flow.rs @@ -8,15 +8,19 @@ use web_time as time; use simple_logger::SimpleLogger; use winit::{ - event::{ElementState, Event, KeyEvent, WindowEvent}, + event::{ElementState, KeyEvent, WindowEvent}, event_loop::EventLoop, keyboard::Key, - window::WindowBuilder, + window::{Window, WindowBuilder}, + ApplicationHandler, }; #[path = "util/fill.rs"] mod fill; +const WAIT_TIME: time::Duration = time::Duration::from_millis(100); +const POLL_SLEEP_TIME: time::Duration = time::Duration::from_millis(100); + #[derive(Debug, Clone, Copy, PartialEq, Eq)] enum Mode { Wait, @@ -24,103 +28,149 @@ enum Mode { Poll, } -const WAIT_TIME: time::Duration = time::Duration::from_millis(100); -const POLL_SLEEP_TIME: time::Duration = time::Duration::from_millis(100); +#[derive(Debug)] +struct App { + mode: Mode, + request_redraw: bool, + wait_cancelled: bool, + close_requested: bool, + window: Window, +} -fn main() -> Result<(), impl std::error::Error> { - SimpleLogger::new().init().unwrap(); +impl ApplicationHandler for App { + type Suspended = Self; - println!("Press '1' to switch to Wait mode."); - println!("Press '2' to switch to WaitUntil mode."); - println!("Press '3' to switch to Poll mode."); - println!("Press 'R' to toggle request_redraw() calls."); - println!("Press 'Esc' to close the window."); + fn resume( + suspended: Self::Suspended, + _elwt: &winit::event_loop::EventLoopWindowTarget, + ) -> Self { + suspended + } - let event_loop = EventLoop::new().unwrap(); - let window = WindowBuilder::new() - .with_title("Press 1, 2, 3 to change control flow mode. Press R to toggle redraw requests.") - .build(&event_loop) - .unwrap(); - - let mut mode = Mode::Wait; - let mut request_redraw = false; - let mut wait_cancelled = false; - let mut close_requested = false; - - event_loop.run(move |event, elwt| { - use winit::event::StartCause; + fn suspend(self) -> Self::Suspended { + self + } + + fn window_event( + &mut self, + _elwt: &winit::event_loop::EventLoopWindowTarget, + _window_id: winit::window::WindowId, + event: WindowEvent, + ) { println!("{event:?}"); match event { - Event::NewEvents(start_cause) => { - wait_cancelled = match start_cause { - StartCause::WaitCancelled { .. } => mode == Mode::WaitUntil, - _ => false, - } + WindowEvent::CloseRequested => { + self.close_requested = true; } - Event::WindowEvent { event, .. } => match event { - WindowEvent::CloseRequested => { - close_requested = true; + WindowEvent::KeyboardInput { + event: + KeyEvent { + logical_key: key, + state: ElementState::Pressed, + .. + }, + .. + } => match key.as_ref() { + // WARNING: Consider using `key_without_modifers()` if available on your platform. + // See the `key_binding` example + Key::Character("1") => { + self.mode = Mode::Wait; + println!("\nmode: {:?}\n", self.mode); + } + Key::Character("2") => { + self.mode = Mode::WaitUntil; + println!("\nmode: {:?}\n", self.mode); + } + Key::Character("3") => { + self.mode = Mode::Poll; + println!("\nmode: {:?}\n", self.mode); } - WindowEvent::KeyboardInput { - event: - KeyEvent { - logical_key: key, - state: ElementState::Pressed, - .. - }, - .. - } => match key.as_ref() { - // WARNING: Consider using `key_without_modifers()` if available on your platform. - // See the `key_binding` example - Key::Character("1") => { - mode = Mode::Wait; - println!("\nmode: {mode:?}\n"); - } - Key::Character("2") => { - mode = Mode::WaitUntil; - println!("\nmode: {mode:?}\n"); - } - Key::Character("3") => { - mode = Mode::Poll; - println!("\nmode: {mode:?}\n"); - } - Key::Character("r") => { - request_redraw = !request_redraw; - println!("\nrequest_redraw: {request_redraw}\n"); - } - Key::Escape => { - close_requested = true; - } - _ => (), - }, - WindowEvent::RedrawRequested => { - fill::fill_window(&window); + Key::Character("r") => { + self.request_redraw = !self.request_redraw; + println!("\nrequest_redraw: {}\n", self.request_redraw); + } + Key::Escape => { + self.close_requested = true; } _ => (), }, - Event::AboutToWait => { - if request_redraw && !wait_cancelled && !close_requested { - window.request_redraw(); - } + WindowEvent::RedrawRequested => { + fill::fill_window(&self.window); + } + _ => (), + } + } - match mode { - Mode::Wait => elwt.set_wait(), - Mode::WaitUntil => { - if !wait_cancelled { - elwt.set_wait_until(time::Instant::now() + WAIT_TIME); - } - } - Mode::Poll => { - thread::sleep(POLL_SLEEP_TIME); - elwt.set_poll(); - } - }; - - if close_requested { - elwt.exit(); + fn start_wait_cancelled( + &mut self, + _elwt: &winit::event_loop::EventLoopWindowTarget<()>, + _start: time::Instant, + _requested_resume: Option, + ) { + self.wait_cancelled = self.mode == Mode::WaitUntil; + } + + fn start_resume_time_reached( + &mut self, + _elwt: &winit::event_loop::EventLoopWindowTarget<()>, + _start: time::Instant, + _requested_resume: time::Instant, + ) { + self.wait_cancelled = false; + } + + fn start_poll(&mut self, _elwt: &winit::event_loop::EventLoopWindowTarget<()>) { + self.wait_cancelled = false; + } + + fn about_to_wait(&mut self, elwt: &winit::event_loop::EventLoopWindowTarget) { + if self.request_redraw && !self.wait_cancelled && !self.close_requested { + self.window.request_redraw(); + } + + match self.mode { + Mode::Wait => elwt.set_wait(), + Mode::WaitUntil => { + if !self.wait_cancelled { + elwt.set_wait_until(time::Instant::now() + WAIT_TIME); } } - _ => (), + Mode::Poll => { + thread::sleep(POLL_SLEEP_TIME); + elwt.set_poll(); + } + }; + + if self.close_requested { + elwt.exit(); + } + } +} + +fn main() -> Result<(), impl std::error::Error> { + SimpleLogger::new().init().unwrap(); + + println!("Press '1' to switch to Wait mode."); + println!("Press '2' to switch to WaitUntil mode."); + println!("Press '3' to switch to Poll mode."); + println!("Press 'R' to toggle request_redraw() calls."); + println!("Press 'Esc' to close the window."); + + let event_loop = EventLoop::new().unwrap(); + event_loop.run_with::(|elwt| { + let window = WindowBuilder::new() + .with_title( + "Press 1, 2, 3 to change control flow mode. Press R to toggle redraw requests.", + ) + .build(elwt) + .unwrap(); + + App { + window, + mode: Mode::Wait, + request_redraw: false, + wait_cancelled: false, + close_requested: false, } }) } diff --git a/examples/multiwindow.rs b/examples/multiwindow.rs index b69215a841..957755ce33 100644 --- a/examples/multiwindow.rs +++ b/examples/multiwindow.rs @@ -4,64 +4,96 @@ use std::collections::HashMap; use simple_logger::SimpleLogger; use winit::{ - event::{ElementState, Event, KeyEvent, WindowEvent}, + event::{ElementState, KeyEvent, WindowEvent}, event_loop::EventLoop, keyboard::Key, - window::Window, + window::{Window, WindowId}, + ApplicationHandler, }; #[path = "util/fill.rs"] mod fill; -fn main() -> Result<(), impl std::error::Error> { - SimpleLogger::new().init().unwrap(); - let event_loop = EventLoop::new().unwrap(); +#[derive(Debug)] +struct App { + windows: HashMap, +} - let mut windows = HashMap::new(); - for _ in 0..3 { - let window = Window::new(&event_loop).unwrap(); - println!("Opened a new window: {:?}", window.id()); - windows.insert(window.id(), window); - } +impl ApplicationHandler for App { + type Suspended = Self; - println!("Press N to open a new window."); + fn resume( + suspended: Self::Suspended, + _elwt: &winit::event_loop::EventLoopWindowTarget, + ) -> Self { + suspended + } - event_loop.run(move |event, elwt| { - elwt.set_wait(); + fn suspend(self) -> Self::Suspended { + self + } - if let Event::WindowEvent { event, window_id } = event { - match event { - WindowEvent::CloseRequested => { - println!("Window {window_id:?} has received the signal to close"); + fn window_event( + &mut self, + elwt: &winit::event_loop::EventLoopWindowTarget, + window_id: winit::window::WindowId, + event: WindowEvent, + ) { + match event { + WindowEvent::CloseRequested => { + println!("Window {window_id:?} has received the signal to close"); - // This drops the window, causing it to close. - windows.remove(&window_id); + // This drops the window, causing it to close. + self.windows.remove(&window_id); - if windows.is_empty() { - elwt.exit(); - } + if self.windows.is_empty() { + elwt.exit(); } - WindowEvent::KeyboardInput { - event: - KeyEvent { - state: ElementState::Pressed, - logical_key: Key::Character(c), - .. - }, - is_synthetic: false, - .. - } if matches!(c.as_ref(), "n" | "N") => { - let window = Window::new(elwt).unwrap(); - println!("Opened a new window: {:?}", window.id()); - windows.insert(window.id(), window); - } - WindowEvent::RedrawRequested => { - if let Some(window) = windows.get(&window_id) { - fill::fill_window(window); - } + } + WindowEvent::KeyboardInput { + event: + KeyEvent { + state: ElementState::Pressed, + logical_key: Key::Character(c), + .. + }, + is_synthetic: false, + .. + } if matches!(c.as_ref(), "n" | "N") => { + let window = Window::new(elwt).unwrap(); + println!("Opened a new window: {:?}", window.id()); + self.windows.insert(window.id(), window); + } + WindowEvent::RedrawRequested => { + if let Some(window) = self.windows.get(&window_id) { + fill::fill_window(window); } - _ => (), } + _ => (), } + } + + fn about_to_wait(&mut self, _elwt: &winit::event_loop::EventLoopWindowTarget) { + // self.window.request_redraw(); + } +} + +fn main() -> Result<(), impl std::error::Error> { + SimpleLogger::new().init().unwrap(); + let event_loop = EventLoop::new().unwrap(); + + println!("Press N to open a new window."); + + event_loop.run_with::(|elwt| { + elwt.set_wait(); + + let mut windows = HashMap::new(); + for _ in 0..3 { + let window = Window::new(elwt).unwrap(); + println!("Opened a new window: {:?}", window.id()); + windows.insert(window.id(), window); + } + + App { windows } }) } diff --git a/examples/window.rs b/examples/window.rs index 7b48266e8c..cc811dbdf4 100644 --- a/examples/window.rs +++ b/examples/window.rs @@ -2,43 +2,88 @@ use simple_logger::SimpleLogger; use winit::{ - event::{Event, WindowEvent}, - event_loop::EventLoop, - window::WindowBuilder, + event::WindowEvent, + event_loop::{EventLoop, EventLoopWindowTarget}, + window::{Window, WindowBuilder, WindowId}, + ApplicationHandler, }; #[path = "util/fill.rs"] mod fill; -fn main() -> Result<(), impl std::error::Error> { - SimpleLogger::new().init().unwrap(); - let event_loop = EventLoop::new().unwrap(); +#[derive(Debug)] +struct GraphicsContext; - let window = WindowBuilder::new() - .with_title("A fantastic window!") - .with_inner_size(winit::dpi::LogicalSize::new(128.0, 128.0)) - .build(&event_loop) - .unwrap(); +#[derive(Debug)] +struct App { + window: Window, + // TODO: Put the context & surface from `fill` in here + _graphics_context: GraphicsContext, +} - event_loop.run(move |event, elwt| { - elwt.set_wait(); - println!("{event:?}"); +struct SuspendedApp { + window: Window, +} +impl ApplicationHandler for App { + type Suspended = SuspendedApp; + + fn resume(suspended: Self::Suspended, _elwt: &EventLoopWindowTarget) -> Self { + println!("---resumed---"); + Self { + window: suspended.window, + _graphics_context: GraphicsContext, + } + } + + fn suspend(self) -> Self::Suspended { + println!("---suspended---"); + SuspendedApp { + window: self.window, + } + } + + fn window_event( + &mut self, + elwt: &EventLoopWindowTarget, + window_id: WindowId, + event: WindowEvent, + ) { + println!("{event:?}"); + if window_id != self.window.id() { + return; + } match event { - Event::WindowEvent { event, window_id } if window_id == window.id() => match event { - WindowEvent::CloseRequested => elwt.exit(), - WindowEvent::RedrawRequested => { - // Notify the windowing system that we'll be presenting to the window. - window.pre_present_notify(); - fill::fill_window(&window); - } - _ => (), - }, - Event::AboutToWait => { - window.request_redraw(); + WindowEvent::CloseRequested => elwt.exit(), + WindowEvent::RedrawRequested => { + // Notify the windowing system that we'll be presenting to the window. + self.window.pre_present_notify(); + fill::fill_window(&self.window); } - _ => (), } - }) + } + + fn about_to_wait(&mut self, _elwt: &EventLoopWindowTarget) { + // self.window.request_redraw(); + } +} + +fn main() -> Result<(), Box> { + SimpleLogger::new().init().unwrap(); + let event_loop = EventLoop::new().unwrap(); + + event_loop.run_with::(|elwt| { + elwt.set_wait(); + + let window = WindowBuilder::new() + .with_title("A fantastic window!") + .with_inner_size(winit::dpi::LogicalSize::new(128.0, 128.0)) + .build(elwt) + .unwrap(); + + SuspendedApp { window } + })?; + + Ok(()) } diff --git a/examples/window_ondemand.rs b/examples/window_ondemand.rs index ec543cee1b..6b9cb04ba7 100644 --- a/examples/window_ondemand.rs +++ b/examples/window_ondemand.rs @@ -36,6 +36,9 @@ fn main() -> Result<(), impl std::error::Error> { if let Some(window) = &app.window { match event { + Event::NewEvents(winit::event::StartCause::Init) => println!("---init"), + Event::Resumed => println!("---resumed"), + Event::Suspended => println!("---suspended"), Event::WindowEvent { event: WindowEvent::CloseRequested, window_id, @@ -43,7 +46,7 @@ fn main() -> Result<(), impl std::error::Error> { println!("--------------------------------------------------------- Window {idx} CloseRequested"); app.window = None; } - Event::AboutToWait => window.request_redraw(), + // Event::AboutToWait => window.request_redraw(), Event::WindowEvent { event: WindowEvent::RedrawRequested, .. diff --git a/examples/window_pump_events.rs b/examples/window_pump_events.rs index 53a8da25c1..522c1b2d7c 100644 --- a/examples/window_pump_events.rs +++ b/examples/window_pump_events.rs @@ -41,6 +41,9 @@ fn main() -> std::process::ExitCode { } match event { + Event::NewEvents(winit::event::StartCause::Init) => println!("---init"), + Event::Resumed => println!("---resumed"), + Event::Suspended => println!("---suspended"), Event::WindowEvent { event: WindowEvent::CloseRequested, window_id, diff --git a/src/application.rs b/src/application.rs new file mode 100644 index 0000000000..36071b3dc0 --- /dev/null +++ b/src/application.rs @@ -0,0 +1,243 @@ +// TMP: For showcase +#![allow(unreachable_code)] +#![allow(unused_variables)] +#![allow(dead_code)] +use std::fmt; +#[cfg(not(wasm_platform))] +use std::time::Instant; + +#[cfg(wasm_platform)] +use web_time::Instant; + +use crate::{ + error::EventLoopError, + event::{DeviceEvent, DeviceId, Event, StartCause, WindowEvent}, + event_loop::{EventLoop, EventLoopWindowTarget}, + window::WindowId, +}; + +/// TODO +#[allow(missing_docs)] +pub trait ApplicationHandler { + type Suspended; + + fn resume(suspended: Self::Suspended, elwt: &EventLoopWindowTarget) -> Self; + + // Note: We do _not_ pass the elwt here, since we don't want users to + // create windows when the app is about to suspend. + fn suspend(self) -> Self::Suspended; + + fn window_event( + &mut self, + elwt: &EventLoopWindowTarget, + window_id: WindowId, + event: WindowEvent, + ); + + // Default noop events + + fn device_event( + &mut self, + elwt: &EventLoopWindowTarget, + device_id: DeviceId, + event: DeviceEvent, + ) { + let _ = elwt; + let _ = device_id; + let _ = event; + } + + fn user_event(&mut self, elwt: &EventLoopWindowTarget, event: T) { + let _ = elwt; + let _ = event; + } + + // Unsure about these, we should probably figure out better timer support + + fn start_wait_cancelled( + &mut self, + elwt: &EventLoopWindowTarget, + start: Instant, + requested_resume: Option, + ) { + let _ = elwt; + let _ = start; + let _ = requested_resume; + } + + fn start_resume_time_reached( + &mut self, + elwt: &EventLoopWindowTarget, + start: Instant, + requested_resume: Instant, + ) { + let _ = elwt; + let _ = start; + let _ = requested_resume; + } + + fn start_poll(&mut self, elwt: &EventLoopWindowTarget) { + let _ = elwt; + } + + fn about_to_wait(&mut self, elwt: &EventLoopWindowTarget) { + let _ = elwt; + } +} + +enum State, I> { + /// Stores an initialization closure. + Uninitialized(I), + /// Stores the suspended state. + Suspended(A::Suspended), + /// Stores the application. + Running(A), + /// Stores nothing, the application has been dropped at this point. + Exited, +} + +impl State +where + A: ApplicationHandler, + I: FnOnce(&EventLoopWindowTarget) -> A::Suspended, +{ + // Handle the event, and possibly transition to another state + fn next(self, event: Event, elwt: &EventLoopWindowTarget) -> Self { + match event { + Event::NewEvents(StartCause::Init) => match self { + State::Uninitialized(init) => State::Suspended(init(elwt)), + state => unreachable!("invalid initialization: state was {state:?}"), + }, + Event::LoopExiting => match self { + // Don't forward the event; users should just overwrite `Drop` for their type if they want to do something on exit. + State::Suspended(_) | State::Running(_) => State::Exited, + state => unreachable!("invalid exit: state was {state:?}"), + }, + Event::Suspended => match self { + State::Running(app) => State::Suspended(app.suspend()), + state => unreachable!("invalid suspend: state was {state:?}"), + }, + Event::Resumed => match self { + State::Suspended(suspended_state) => { + State::Running(A::resume(suspended_state, elwt)) + } + state => unreachable!("invalid resume: state was {state:?}"), + }, + Event::WindowEvent { window_id, event } => match self { + State::Running(mut app) => { + app.window_event(elwt, window_id, event); + State::Running(app) + } + state => unreachable!("invalid window event: state was {state:?}"), + }, + Event::DeviceEvent { device_id, event } => match self { + State::Running(mut app) => { + app.device_event(elwt, device_id, event); + State::Running(app) + } + state => unreachable!("invalid device event: state was {state:?}"), + }, + Event::UserEvent(event) => match self { + State::Running(mut app) => { + app.user_event(elwt, event); + State::Running(app) + } + state => unreachable!("invalid user event: state was {state:?}"), + }, + Event::NewEvents(StartCause::ResumeTimeReached { + start, + requested_resume, + }) => match self { + State::Running(mut app) => { + app.start_resume_time_reached(elwt, start, requested_resume); + State::Running(app) + } + state => unreachable!("invalid resume time reached event: state was {state:?}"), + }, + Event::NewEvents(StartCause::WaitCancelled { + start, + requested_resume, + }) => match self { + State::Running(mut app) => { + app.start_wait_cancelled(elwt, start, requested_resume); + State::Running(app) + } + state => unreachable!("invalid wait cancelled event: state was {state:?}"), + }, + Event::NewEvents(StartCause::Poll) => match self { + State::Running(mut app) => { + app.start_poll(elwt); + State::Running(app) + } + state => unreachable!("invalid poll event: state was {state:?}"), + }, + Event::AboutToWait => match self { + State::Running(mut app) => { + app.about_to_wait(elwt); + State::Running(app) + } + state => unreachable!("invalid about to wait event: state was {state:?}"), + }, + } + } +} + +impl, I> fmt::Debug for State { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::Uninitialized(_) => f.write_str("Uninitialized"), + Self::Suspended(_) => f.write_str("Suspended"), + Self::Running(_) => f.write_str("Running"), + Self::Exited => f.write_str("Exited"), + } + } +} + +impl EventLoop { + pub fn run_with>( + self, + init: impl FnOnce(&EventLoopWindowTarget) -> A::Suspended, + ) -> Result<(), EventLoopError> { + let mut state_storage: Option> = Some(State::Uninitialized(init)); + + self.run(move |event, elwt| { + let state = state_storage + .take() + .expect("failed extracting state, either due to re-entrancy or because a a panic occurred previously"); + + state_storage = Some(state.next(event, elwt)); + }) + } +} + +// Extensions + +// Simpler version of the State enum above, used for communicating the current +// state to the user when using `pump_events`. +// +// The intention is that the application is always dropped by the user +// themselves, and the application is never returned in a suspended state. +enum PumpEventStatus { + Uninitialized(I), + Running(A), +} + +struct ShouldExit(pub bool); + +impl EventLoop { + fn pump_events_with>( + self, + status: &mut PumpEventStatus) -> A::Suspended>, + ) -> Result { + *status = PumpEventStatus::Running(todo!()); + Ok(ShouldExit(false)) + } + + // Same signature and semantics as `run_with`, except for taking `&mut self`. + fn run_ondemand_with>( + &mut self, + init: impl FnOnce(&EventLoopWindowTarget) -> A::Suspended, + ) -> Result<(), EventLoopError> { + todo!() + } +} diff --git a/src/event_loop.rs b/src/event_loop.rs index 8c4f1cc1d1..91dce63e10 100644 --- a/src/event_loop.rs +++ b/src/event_loop.rs @@ -46,7 +46,7 @@ pub struct EventLoop { /// your callback. [`EventLoop`] will coerce into this type (`impl Deref for /// EventLoop`), so functions that take this as a parameter can also take /// `&EventLoop`. -pub struct EventLoopWindowTarget { +pub struct EventLoopWindowTarget { pub(crate) p: platform_impl::EventLoopWindowTarget, pub(crate) _marker: PhantomData<*mut ()>, // Not Send nor Sync } diff --git a/src/lib.rs b/src/lib.rs index e7f641cea6..7fcb2e2cd1 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -151,6 +151,7 @@ extern crate bitflags; pub mod dpi; #[macro_use] pub mod error; +mod application; pub mod event; pub mod event_loop; mod icon; @@ -160,3 +161,5 @@ mod platform_impl; pub mod window; pub mod platform; + +pub use self::application::ApplicationHandler;