diff --git a/CHANGELOG.md b/CHANGELOG.md index 0f711b3951..774c632b5c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,10 @@ Unreleased` header. # Unreleased - On Wayland, don't reapply cursor grab when unchanged. +- Deprecate `EventLoop::run` in favor of `EventLoop::run_app`. +- Deprecate `EventLoopExtRunOnDemand::run_on_demand` in favor of `EventLoop::run_app_on_demand`. +- Deprecate `EventLoopExtPumpEvents::pump_events` in favor of `EventLoopExtPumpEvents::pump_app_events`. +- Add `ApplicationHandler` trait which mimics `Event`. - Move `dpi` types to its own crate, and re-export it from the root crate. - Implement `Sync` for `EventLoopProxy`. - **Breaking:** Move `Window::new` to `ActiveEventLoop::create_window` and `EventLoop::create_window` (with the latter being deprecated). diff --git a/examples/control_flow.rs b/examples/control_flow.rs index cbfe360a9b..4d1049acbb 100644 --- a/examples/control_flow.rs +++ b/examples/control_flow.rs @@ -3,29 +3,30 @@ use std::thread; #[cfg(not(web_platform))] use std::time; + #[cfg(web_platform)] use web_time as time; -use winit::{ - event::{ElementState, Event, KeyEvent, WindowEvent}, - event_loop::{ControlFlow, EventLoop}, - keyboard::{Key, NamedKey}, - window::Window, -}; +use winit::application::ApplicationHandler; +use winit::event::{ElementState, KeyEvent, StartCause, WindowEvent}; +use winit::event_loop::{ActiveEventLoop, ControlFlow, EventLoop}; +use winit::keyboard::{Key, NamedKey}; +use winit::window::{Window, WindowId}; #[path = "util/fill.rs"] mod fill; -#[derive(Debug, Clone, Copy, PartialEq, Eq)] +const WAIT_TIME: time::Duration = time::Duration::from_millis(100); +const POLL_SLEEP_TIME: time::Duration = time::Duration::from_millis(100); + +#[derive(Default, Debug, Clone, Copy, PartialEq, Eq)] enum Mode { + #[default] Wait, WaitUntil, Poll, } -const WAIT_TIME: time::Duration = time::Duration::from_millis(100); -const POLL_SLEEP_TIME: time::Duration = time::Duration::from_millis(100); - fn main() -> Result<(), impl std::error::Error> { tracing_subscriber::fmt::init(); @@ -37,96 +38,110 @@ fn main() -> Result<(), impl std::error::Error> { let event_loop = EventLoop::new().unwrap(); - let mut mode = Mode::Wait; - let mut request_redraw = false; - let mut wait_cancelled = false; - let mut close_requested = false; + let mut app = ControlFlowDemo::default(); + event_loop.run_app(&mut app) +} - let mut window = None; - event_loop.run(move |event, event_loop| { - use winit::event::StartCause; +#[derive(Default)] +struct ControlFlowDemo { + mode: Mode, + request_redraw: bool, + wait_cancelled: bool, + close_requested: bool, + window: Option, +} + +impl ApplicationHandler for ControlFlowDemo { + fn new_events(&mut self, _event_loop: &ActiveEventLoop, cause: StartCause) { + println!("new_events: {cause:?}"); + + self.wait_cancelled = match cause { + StartCause::WaitCancelled { .. } => self.mode == Mode::WaitUntil, + _ => false, + } + } + + fn resumed(&mut self, event_loop: &ActiveEventLoop) { + let window_attributes = Window::default_attributes().with_title( + "Press 1, 2, 3 to change control flow mode. Press R to toggle redraw requests.", + ); + self.window = Some(event_loop.create_window(window_attributes).unwrap()); + } + + fn window_event( + &mut self, + _event_loop: &ActiveEventLoop, + _window_id: WindowId, + event: WindowEvent, + ) { println!("{event:?}"); + match event { - Event::NewEvents(start_cause) => { - wait_cancelled = match start_cause { - StartCause::WaitCancelled { .. } => mode == Mode::WaitUntil, - _ => false, - } - } - Event::Resumed => { - let window_attributes = Window::default_attributes().with_title( - "Press 1, 2, 3 to change control flow mode. Press R to toggle redraw requests.", - ); - window = Some(event_loop.create_window(window_attributes).unwrap()); + 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_modifiers()` 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_modifiers()` 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::Named(NamedKey::Escape) => { - close_requested = true; - } - _ => (), - }, - WindowEvent::RedrawRequested => { - let window = window.as_ref().unwrap(); - window.pre_present_notify(); - fill::fill_window(window); + Key::Character("r") => { + self.request_redraw = !self.request_redraw; + println!("\nrequest_redraw: {}\n", self.request_redraw); + } + Key::Named(NamedKey::Escape) => { + self.close_requested = true; } _ => (), }, - Event::AboutToWait => { - if request_redraw && !wait_cancelled && !close_requested { - window.as_ref().unwrap().request_redraw(); - } + WindowEvent::RedrawRequested => { + let window = self.window.as_ref().unwrap(); + window.pre_present_notify(); + fill::fill_window(window); + } + _ => (), + } + } + + fn about_to_wait(&mut self, event_loop: &ActiveEventLoop) { + if self.request_redraw && !self.wait_cancelled && !self.close_requested { + self.window.as_ref().unwrap().request_redraw(); + } - match mode { - Mode::Wait => event_loop.set_control_flow(ControlFlow::Wait), - Mode::WaitUntil => { - if !wait_cancelled { - event_loop.set_control_flow(ControlFlow::WaitUntil( - time::Instant::now() + WAIT_TIME, - )); - } - } - Mode::Poll => { - thread::sleep(POLL_SLEEP_TIME); - event_loop.set_control_flow(ControlFlow::Poll); - } - }; - - if close_requested { - event_loop.exit(); + match self.mode { + Mode::Wait => event_loop.set_control_flow(ControlFlow::Wait), + Mode::WaitUntil => { + if !self.wait_cancelled { + event_loop + .set_control_flow(ControlFlow::WaitUntil(time::Instant::now() + WAIT_TIME)); } } - _ => (), + Mode::Poll => { + thread::sleep(POLL_SLEEP_TIME); + event_loop.set_control_flow(ControlFlow::Poll); + } + }; + + if self.close_requested { + event_loop.exit(); } - }) + } } diff --git a/examples/pump_events.rs b/examples/pump_events.rs index 9e748d43c1..c7c64990cc 100644 --- a/examples/pump_events.rs +++ b/examples/pump_events.rs @@ -11,50 +11,59 @@ fn main() -> std::process::ExitCode { use std::{process::ExitCode, thread::sleep, time::Duration}; - use winit::{ - event::{Event, WindowEvent}, - event_loop::EventLoop, - platform::pump_events::{EventLoopExtPumpEvents, PumpStatus}, - window::Window, - }; + use winit::application::ApplicationHandler; + use winit::event::WindowEvent; + use winit::event_loop::{ActiveEventLoop, EventLoop}; + use winit::platform::pump_events::{EventLoopExtPumpEvents, PumpStatus}; + use winit::window::{Window, WindowId}; #[path = "util/fill.rs"] mod fill; - let mut event_loop = EventLoop::new().unwrap(); + #[derive(Default)] + struct PumpDemo { + window: Option, + } - tracing_subscriber::fmt::init(); + impl ApplicationHandler for PumpDemo { + fn resumed(&mut self, event_loop: &ActiveEventLoop) { + let window_attributes = Window::default_attributes().with_title("A fantastic window!"); + self.window = Some(event_loop.create_window(window_attributes).unwrap()); + } - let mut window = None; + fn window_event( + &mut self, + event_loop: &ActiveEventLoop, + _window_id: WindowId, + event: WindowEvent, + ) { + println!("{event:?}"); - loop { - let timeout = Some(Duration::ZERO); - let status = event_loop.pump_events(timeout, |event, event_loop| { - if let Event::WindowEvent { event, .. } = &event { - // Print only Window events to reduce noise - println!("{event:?}"); - } + let window = match self.window.as_ref() { + Some(window) => window, + None => return, + }; match event { - Event::Resumed => { - let window_attributes = - Window::default_attributes().with_title("A fantastic window!"); - window = Some(event_loop.create_window(window_attributes).unwrap()); - } - Event::WindowEvent { event, .. } => { - let window = window.as_ref().unwrap(); - match event { - WindowEvent::CloseRequested => event_loop.exit(), - WindowEvent::RedrawRequested => fill::fill_window(window), - _ => (), - } - } - Event::AboutToWait => { - window.as_ref().unwrap().request_redraw(); + WindowEvent::CloseRequested => event_loop.exit(), + WindowEvent::RedrawRequested => { + fill::fill_window(window); + window.request_redraw(); } _ => (), } - }); + } + } + + let mut event_loop = EventLoop::new().unwrap(); + + tracing_subscriber::fmt::init(); + + let mut app = PumpDemo::default(); + + loop { + let timeout = Some(Duration::ZERO); + let status = event_loop.pump_app_events(timeout, &mut app); if let PumpStatus::Exit(exit_code) = status { break ExitCode::from(exit_code as u8); diff --git a/examples/run_on_demand.rs b/examples/run_on_demand.rs index b298fdf8c0..33488c1f9e 100644 --- a/examples/run_on_demand.rs +++ b/examples/run_on_demand.rs @@ -2,86 +2,94 @@ // Limit this example to only compatible platforms. #[cfg(any(windows_platform, macos_platform, x11_platform, wayland_platform,))] -fn main() -> Result<(), impl std::error::Error> { +fn main() -> Result<(), Box> { use std::time::Duration; - use winit::{ - error::EventLoopError, - event::{Event, WindowEvent}, - event_loop::EventLoop, - platform::run_on_demand::EventLoopExtRunOnDemand, - window::{Window, WindowId}, - }; + use winit::application::ApplicationHandler; + use winit::event::WindowEvent; + use winit::event_loop::{ActiveEventLoop, EventLoop}; + use winit::platform::run_on_demand::EventLoopExtRunOnDemand; + use winit::window::{Window, WindowId}; #[path = "util/fill.rs"] mod fill; #[derive(Default)] struct App { + idx: usize, window_id: Option, window: Option, } - tracing_subscriber::fmt::init(); - let mut event_loop = EventLoop::new().unwrap(); + impl ApplicationHandler for App { + fn about_to_wait(&mut self, _event_loop: &ActiveEventLoop) { + if let Some(window) = self.window.as_ref() { + window.request_redraw(); + } + } + + fn resumed(&mut self, event_loop: &ActiveEventLoop) { + let window_attributes = Window::default_attributes() + .with_title("Fantastic window number one!") + .with_inner_size(winit::dpi::LogicalSize::new(128.0, 128.0)); + let window = event_loop.create_window(window_attributes).unwrap(); + self.window_id = Some(window.id()); + self.window = Some(window); + } - fn run_app(event_loop: &mut EventLoop<()>, idx: usize) -> Result<(), EventLoopError> { - let mut app = App::default(); + fn window_event( + &mut self, + event_loop: &ActiveEventLoop, + window_id: WindowId, + event: WindowEvent, + ) { + if event == WindowEvent::Destroyed && self.window_id == Some(window_id) { + println!( + "--------------------------------------------------------- Window {} Destroyed", + self.idx + ); + self.window_id = None; + event_loop.exit(); + return; + } - event_loop.run_on_demand(move |event, event_loop| { - println!("Run {idx}: {:?}", event); + let window = match self.window.as_mut() { + Some(window) => window, + None => return, + }; - if let Some(window) = &app.window { - match event { - Event::WindowEvent { - event: WindowEvent::CloseRequested, - window_id, - } if window.id() == window_id => { - println!("--------------------------------------------------------- Window {idx} CloseRequested"); - fill::cleanup_window(window); - app.window = None; - } - Event::AboutToWait => window.request_redraw(), - Event::WindowEvent { - event: WindowEvent::RedrawRequested, - .. - } => { - fill::fill_window(window); - } - _ => (), + match event { + WindowEvent::CloseRequested => { + println!("--------------------------------------------------------- Window {} CloseRequested", self.idx); + fill::cleanup_window(window); + self.window = None; } - } else if let Some(id) = app.window_id { - match event { - Event::WindowEvent { - event: WindowEvent::Destroyed, - window_id, - } if id == window_id => { - println!("--------------------------------------------------------- Window {idx} Destroyed"); - app.window_id = None; - event_loop.exit(); - } - _ => (), + WindowEvent::RedrawRequested => { + fill::fill_window(window); } - } else if let Event::Resumed = event { - let window_attributes = Window::default_attributes() - .with_title("Fantastic window number one!") - .with_inner_size(winit::dpi::LogicalSize::new(128.0, 128.0)); - let window = event_loop.create_window(window_attributes).unwrap(); - app.window_id = Some(window.id()); - app.window = Some(window); + _ => (), } - }) + } } - run_app(&mut event_loop, 1)?; + tracing_subscriber::fmt::init(); + + let mut event_loop = EventLoop::new().unwrap(); + + let mut app = App { + idx: 1, + ..Default::default() + }; + event_loop.run_app_on_demand(&mut app)?; println!("--------------------------------------------------------- Finished first loop"); println!("--------------------------------------------------------- Waiting 5 seconds"); std::thread::sleep(Duration::from_secs(5)); - let ret = run_app(&mut event_loop, 2); + app.idx += 1; + event_loop.run_app_on_demand(&mut app)?; println!("--------------------------------------------------------- Finished second loop"); - ret + Ok(()) } #[cfg(not(any(windows_platform, macos_platform, x11_platform, wayland_platform,)))] diff --git a/examples/window.rs b/examples/window.rs index 3aa7f99096..427ee405e1 100644 --- a/examples/window.rs +++ b/examples/window.rs @@ -14,8 +14,9 @@ use rwh_05::HasRawDisplayHandle; #[cfg(not(any(android_platform, ios_platform)))] use softbuffer::{Context, Surface}; +use winit::application::ApplicationHandler; use winit::dpi::{LogicalSize, PhysicalPosition, PhysicalSize}; -use winit::event::{DeviceEvent, DeviceId, Event, Ime, WindowEvent}; +use winit::event::{DeviceEvent, DeviceId, Ime, WindowEvent}; use winit::event::{MouseButton, MouseScrollDelta}; use winit::event_loop::{ActiveEventLoop, EventLoop}; use winit::keyboard::{Key, ModifiersState}; @@ -53,38 +54,7 @@ fn main() -> Result<(), Box> { let mut state = Application::new(&event_loop); - event_loop.run(move |event, event_loop| match event { - Event::NewEvents(_) => (), - Event::Resumed => { - println!("Resumed the event loop"); - state.dump_monitors(event_loop); - - // Create initial window. - state - .create_window(event_loop, None) - .expect("failed to create initial window"); - - state.print_help(); - } - Event::AboutToWait => { - if state.windows.is_empty() { - println!("No windows left, exiting..."); - event_loop.exit(); - } - } - Event::WindowEvent { window_id, event } => { - state.handle_window_event(event_loop, window_id, event) - } - Event::DeviceEvent { device_id, event } => { - state.handle_device_event(event_loop, device_id, event) - } - Event::UserEvent(event) => { - println!("User event: {event:?}"); - } - Event::Suspended | Event::LoopExiting | Event::MemoryWarning => (), - })?; - - Ok(()) + event_loop.run_app(&mut state).map_err(Into::into) } #[allow(dead_code)] @@ -104,15 +74,14 @@ struct Application { /// /// With OpenGL it could be EGLDisplay. #[cfg(not(any(android_platform, ios_platform)))] - context: Context, + context: Option, } impl Application { fn new(event_loop: &EventLoop) -> Self { - // SAFETY: the context is dropped inside the loop, since the state we're using - // is moved inside the closure. + // SAFETY: we drop the context right before the event loop is stopped, thus making it safe. #[cfg(not(any(android_platform, ios_platform)))] - let context = unsafe { Context::from_raw(event_loop.raw_display_handle()).unwrap() }; + let context = Some(unsafe { Context::from_raw(event_loop.raw_display_handle()).unwrap() }); // You'll have to choose an icon size at your own discretion. On X11, the desired size varies // by WM, and on Windows, you still have to account for screen scaling. Here we use 32px, @@ -227,7 +196,97 @@ impl Application { } } - fn handle_window_event( + fn dump_monitors(&self, event_loop: &ActiveEventLoop) { + println!("Monitors information"); + let primary_monitor = event_loop.primary_monitor(); + for monitor in event_loop.available_monitors() { + let intro = if primary_monitor.as_ref() == Some(&monitor) { + "Primary monitor" + } else { + "Monitor" + }; + + if let Some(name) = monitor.name() { + println!("{intro}: {name}"); + } else { + println!("{intro}: [no name]"); + } + + let PhysicalSize { width, height } = monitor.size(); + print!(" Current mode: {width}x{height}"); + if let Some(m_hz) = monitor.refresh_rate_millihertz() { + println!(" @ {}.{} Hz", m_hz / 1000, m_hz % 1000); + } else { + println!(); + } + + let PhysicalPosition { x, y } = monitor.position(); + println!(" Position: {x},{y}"); + + println!(" Scale factor: {}", monitor.scale_factor()); + + println!(" Available modes (width x height x bit-depth):"); + for mode in monitor.video_modes() { + let PhysicalSize { width, height } = mode.size(); + let bits = mode.bit_depth(); + let m_hz = mode.refresh_rate_millihertz(); + println!( + " {width}x{height}x{bits} @ {}.{} Hz", + m_hz / 1000, + m_hz % 1000 + ); + } + } + } + + /// Process the key binding. + fn process_key_binding(key: &str, mods: &ModifiersState) -> Option { + KEY_BINDINGS.iter().find_map(|binding| { + binding + .is_triggered_by(&key, mods) + .then_some(binding.action) + }) + } + + /// Process mouse binding. + fn process_mouse_binding(button: MouseButton, mods: &ModifiersState) -> Option { + MOUSE_BINDINGS.iter().find_map(|binding| { + binding + .is_triggered_by(&button, mods) + .then_some(binding.action) + }) + } + + fn print_help(&self) { + println!("Keyboard bindings:"); + for binding in KEY_BINDINGS { + println!( + "{}{:<10} - {} ({})", + modifiers_to_string(binding.mods), + binding.trigger, + binding.action, + binding.action.help(), + ); + } + println!("Mouse bindings:"); + for binding in MOUSE_BINDINGS { + println!( + "{}{:<10} - {} ({})", + modifiers_to_string(binding.mods), + mouse_button_to_string(binding.trigger), + binding.action, + binding.action.help(), + ); + } + } +} + +impl ApplicationHandler for Application { + fn user_event(&mut self, _event_loop: &ActiveEventLoop, event: UserEvent) { + println!("User event: {event:?}"); + } + + fn window_event( &mut self, event_loop: &ActiveEventLoop, window_id: WindowId, @@ -371,92 +430,37 @@ impl Application { } } - fn handle_device_event(&mut self, _: &ActiveEventLoop, _: DeviceId, event: DeviceEvent) { - println!("Device event: {event:?}"); + fn device_event( + &mut self, + _event_loop: &ActiveEventLoop, + device_id: DeviceId, + event: DeviceEvent, + ) { + println!("Device {device_id:?} event: {event:?}"); } - fn dump_monitors(&self, event_loop: &ActiveEventLoop) { - println!("Monitors information"); - let primary_monitor = event_loop.primary_monitor(); - for monitor in event_loop.available_monitors() { - let intro = if primary_monitor.as_ref() == Some(&monitor) { - "Primary monitor" - } else { - "Monitor" - }; - - if let Some(name) = monitor.name() { - println!("{intro}: {name}"); - } else { - println!("{intro}: [no name]"); - } - - let PhysicalSize { width, height } = monitor.size(); - print!(" Current mode: {width}x{height}"); - if let Some(m_hz) = monitor.refresh_rate_millihertz() { - println!(" @ {}.{} Hz", m_hz / 1000, m_hz % 1000); - } else { - println!(); - } - - let PhysicalPosition { x, y } = monitor.position(); - println!(" Position: {x},{y}"); - - println!(" Scale factor: {}", monitor.scale_factor()); + fn resumed(&mut self, event_loop: &ActiveEventLoop) { + println!("Resumed the event loop"); + self.dump_monitors(event_loop); - println!(" Available modes (width x height x bit-depth):"); - for mode in monitor.video_modes() { - let PhysicalSize { width, height } = mode.size(); - let bits = mode.bit_depth(); - let m_hz = mode.refresh_rate_millihertz(); - println!( - " {width}x{height}x{bits} @ {}.{} Hz", - m_hz / 1000, - m_hz % 1000 - ); - } - } - } + // Create initial window. + self.create_window(event_loop, None) + .expect("failed to create initial window"); - /// Process the key binding. - fn process_key_binding(key: &str, mods: &ModifiersState) -> Option { - KEY_BINDINGS.iter().find_map(|binding| { - binding - .is_triggered_by(&key, mods) - .then_some(binding.action) - }) + self.print_help(); } - /// Process mouse binding. - fn process_mouse_binding(button: MouseButton, mods: &ModifiersState) -> Option { - MOUSE_BINDINGS.iter().find_map(|binding| { - binding - .is_triggered_by(&button, mods) - .then_some(binding.action) - }) + fn about_to_wait(&mut self, event_loop: &ActiveEventLoop) { + if self.windows.is_empty() { + println!("No windows left, exiting..."); + event_loop.exit(); + } } - fn print_help(&self) { - println!("Keyboard bindings:"); - for binding in KEY_BINDINGS { - println!( - "{}{:<10} - {} ({})", - modifiers_to_string(binding.mods), - binding.trigger, - binding.action, - binding.action.help(), - ); - } - println!("Mouse bindings:"); - for binding in MOUSE_BINDINGS { - println!( - "{}{:<10} - {} ({})", - modifiers_to_string(binding.mods), - mouse_button_to_string(binding.trigger), - binding.action, - binding.action.help(), - ); - } + #[cfg(not(any(android_platform, ios_platform)))] + fn exiting(&mut self, _event_loop: &ActiveEventLoop) { + // We must drop the context here. + self.context = None; } } @@ -496,11 +500,11 @@ struct WindowState { } impl WindowState { - fn new(application: &Application, window: Window) -> Result> { + fn new(app: &Application, window: Window) -> Result> { // SAFETY: the surface is dropped before the `window` which provided it with handle, thus // it doesn't outlive it. #[cfg(not(any(android_platform, ios_platform)))] - let surface = unsafe { Surface::new(&application.context, &window)? }; + let surface = unsafe { Surface::new(app.context.as_ref().unwrap(), &window)? }; let theme = window.theme().unwrap_or(Theme::Dark); println!("Theme: {theme:?}"); @@ -515,7 +519,7 @@ impl WindowState { let mut state = Self { #[cfg(macos_platform)] option_as_alt: window.option_as_alt(), - custom_idx: application.custom_cursors.len() - 1, + custom_idx: app.custom_cursors.len() - 1, cursor_grab: CursorGrabMode::None, named_idx, #[cfg(not(any(android_platform, ios_platform)))] diff --git a/examples/x11_embed.rs b/examples/x11_embed.rs index 027e3ba54b..9e77694c3b 100644 --- a/examples/x11_embed.rs +++ b/examples/x11_embed.rs @@ -3,38 +3,37 @@ use std::error::Error; #[cfg(x11_platform)] fn main() -> Result<(), Box> { - use winit::{ - event::{Event, WindowEvent}, - event_loop::EventLoop, - platform::x11::WindowAttributesExtX11, - window::Window, - }; + use winit::application::ApplicationHandler; + use winit::event::WindowEvent; + use winit::event_loop::{ActiveEventLoop, EventLoop}; + use winit::platform::x11::WindowAttributesExtX11; + use winit::window::{Window, WindowId}; #[path = "util/fill.rs"] mod fill; - // First argument should be a 32-bit X11 window ID. - let parent_window_id = std::env::args() - .nth(1) - .ok_or("Expected a 32-bit X11 window ID as the first argument.")? - .parse::()?; + pub struct XEmbedDemo { + parent_window_id: u32, + window: Option, + } - tracing_subscriber::fmt::init(); - let event_loop = EventLoop::new()?; - - let mut window = None; - event_loop.run(move |event, event_loop| match event { - Event::Resumed => { + impl ApplicationHandler for XEmbedDemo { + fn resumed(&mut self, event_loop: &ActiveEventLoop) { let window_attributes = Window::default_attributes() .with_title("An embedded window!") .with_inner_size(winit::dpi::LogicalSize::new(128.0, 128.0)) - .with_embed_parent_window(parent_window_id); + .with_embed_parent_window(self.parent_window_id); - window = Some(event_loop.create_window(window_attributes).unwrap()); + self.window = Some(event_loop.create_window(window_attributes).unwrap()); } - Event::WindowEvent { event, .. } => { - let window = window.as_ref().unwrap(); + fn window_event( + &mut self, + event_loop: &ActiveEventLoop, + _window_id: WindowId, + event: WindowEvent, + ) { + let window = self.window.as_ref().unwrap(); match event { WindowEvent::CloseRequested => event_loop.exit(), WindowEvent::RedrawRequested => { @@ -44,13 +43,26 @@ fn main() -> Result<(), Box> { _ => (), } } - Event::AboutToWait => { - window.as_ref().unwrap().request_redraw(); + + fn about_to_wait(&mut self, _event_loop: &ActiveEventLoop) { + self.window.as_ref().unwrap().request_redraw(); } - _ => (), - })?; + } - Ok(()) + // First argument should be a 32-bit X11 window ID. + let parent_window_id = std::env::args() + .nth(1) + .ok_or("Expected a 32-bit X11 window ID as the first argument.")? + .parse::()?; + + tracing_subscriber::fmt::init(); + let event_loop = EventLoop::new()?; + + let mut app = XEmbedDemo { + parent_window_id, + window: None, + }; + event_loop.run_app(&mut app).map_err(Into::into) } #[cfg(not(x11_platform))] diff --git a/src/application.rs b/src/application.rs new file mode 100644 index 0000000000..acb5af0979 --- /dev/null +++ b/src/application.rs @@ -0,0 +1,221 @@ +//! End user application handling. + +use crate::event::{DeviceEvent, DeviceId, StartCause, WindowEvent}; +use crate::event_loop::ActiveEventLoop; +use crate::window::WindowId; + +/// The handler of the application events. +pub trait ApplicationHandler { + /// Emitted when new events arrive from the OS to be processed. + /// + /// This is a useful place to put code that should be done before you start processing + /// events, such as updating frame timing information for benchmarking or checking the + /// [`StartCause`] to see if a timer set by + /// [`ControlFlow::WaitUntil`](crate::event_loop::ControlFlow::WaitUntil) has elapsed. + fn new_events(&mut self, event_loop: &ActiveEventLoop, cause: StartCause) { + let _ = (event_loop, cause); + } + + /// Emitted when the application has been resumed. + /// + /// For consistency, all platforms emit a `Resumed` event even if they don't themselves have a + /// formal suspend/resume lifecycle. For systems without a formal suspend/resume lifecycle + /// the `Resumed` event is always emitted after the [`NewEvents(StartCause::Init)`][StartCause::Init] + /// event. + /// + /// # Portability + /// + /// It's recommended that applications should only initialize their graphics context and create + /// a window after they have received their first `Resumed` event. Some systems + /// (specifically Android) won't allow applications to create a render surface until they are + /// resumed. + /// + /// Considering that the implementation of [`Suspended`] and `Resumed` events may be internally + /// driven by multiple platform-specific events, and that there may be subtle differences across + /// platforms with how these internal events are delivered, it's recommended that applications + /// be able to gracefully handle redundant (i.e. back-to-back) [`Suspended`] or `Resumed` events. + /// + /// Also see [`Suspended`] notes. + /// + /// ## Android + /// + /// On Android, the `Resumed` event is sent when a new [`SurfaceView`] has been created. This is + /// expected to closely correlate with the [`onResume`] lifecycle event but there may technically + /// be a discrepancy. + /// + /// [`onResume`]: https://developer.android.com/reference/android/app/Activity#onResume() + /// + /// Applications that need to run on Android must wait until they have been `Resumed` + /// before they will be able to create a render surface (such as an `EGLSurface`, + /// [`VkSurfaceKHR`] or [`wgpu::Surface`]) which depend on having a + /// [`SurfaceView`]. Applications must also assume that if they are [`Suspended`], then their + /// render surfaces are invalid and should be dropped. + /// + /// Also see [`Suspended`] notes. + /// + /// [`SurfaceView`]: https://developer.android.com/reference/android/view/SurfaceView + /// [Activity lifecycle]: https://developer.android.com/guide/components/activities/activity-lifecycle + /// [`VkSurfaceKHR`]: https://www.khronos.org/registry/vulkan/specs/1.3-extensions/man/html/VkSurfaceKHR.html + /// [`wgpu::Surface`]: https://docs.rs/wgpu/latest/wgpu/struct.Surface.html + /// + /// ## iOS + /// + /// On iOS, the `Resumed` event is emitted in response to an [`applicationDidBecomeActive`] + /// callback which means the application is "active" (according to the + /// [iOS application lifecycle]). + /// + /// [`applicationDidBecomeActive`]: https://developer.apple.com/documentation/uikit/uiapplicationdelegate/1622956-applicationdidbecomeactive + /// [iOS application lifecycle]: https://developer.apple.com/documentation/uikit/app_and_environment/managing_your_app_s_life_cycle + /// + /// ## Web + /// + /// On Web, the `Resumed` event is emitted in response to a [`pageshow`] event + /// with the property [`persisted`] being true, which means that the page is being + /// restored from the [`bfcache`] (back/forward cache) - an in-memory cache that + /// stores a complete snapshot of a page (including the JavaScript heap) as the + /// user is navigating away. + /// + /// [`pageshow`]: https://developer.mozilla.org/en-US/docs/Web/API/Window/pageshow_event + /// [`persisted`]: https://developer.mozilla.org/en-US/docs/Web/API/PageTransitionEvent/persisted + /// [`bfcache`]: https://web.dev/bfcache/ + /// [`Suspended`]: Self::suspended + fn resumed(&mut self, event_loop: &ActiveEventLoop); + + /// Emitted when an event is sent from [`EventLoopProxy::send_event`]. + /// + /// [`EventLoopProxy::send_event`]: crate::event_loop::EventLoopProxy::send_event + fn user_event(&mut self, event_loop: &ActiveEventLoop, event: T) { + let _ = (event_loop, event); + } + + /// Emitted when the OS sends an event to a winit window. + fn window_event( + &mut self, + event_loop: &ActiveEventLoop, + window_id: WindowId, + event: WindowEvent, + ); + + /// Emitted when the OS sends an event to a device. + fn device_event( + &mut self, + event_loop: &ActiveEventLoop, + device_id: DeviceId, + event: DeviceEvent, + ) { + let _ = (event_loop, device_id, event); + } + + /// Emitted when the event loop is about to block and wait for new events. + /// + /// Most applications shouldn't need to hook into this event since there is no real relationship + /// between how often the event loop needs to wake up and the dispatching of any specific events. + /// + /// High frequency event sources, such as input devices could potentially lead to lots of wake + /// ups and also lots of corresponding `AboutToWait` events. + /// + /// This is not an ideal event to drive application rendering from and instead applications + /// should render in response to [`WindowEvent::RedrawRequested`] events. + fn about_to_wait(&mut self, event_loop: &ActiveEventLoop) { + let _ = event_loop; + } + + /// Emitted when the application has been suspended. + /// + /// # Portability + /// + /// Not all platforms support the notion of suspending applications, and there may be no + /// technical way to guarantee being able to emit a `Suspended` event if the OS has + /// no formal application lifecycle (currently only Android, iOS, and Web do). For this reason, + /// Winit does not currently try to emit pseudo `Suspended` events before the application + /// quits on platforms without an application lifecycle. + /// + /// Considering that the implementation of `Suspended` and [`Resumed`] events may be internally + /// driven by multiple platform-specific events, and that there may be subtle differences across + /// platforms with how these internal events are delivered, it's recommended that applications + /// be able to gracefully handle redundant (i.e. back-to-back) `Suspended` or [`Resumed`] events. + /// + /// Also see [`Resumed`] notes. + /// + /// ## Android + /// + /// On Android, the `Suspended` event is only sent when the application's associated + /// [`SurfaceView`] is destroyed. This is expected to closely correlate with the [`onPause`] + /// lifecycle event but there may technically be a discrepancy. + /// + /// [`onPause`]: https://developer.android.com/reference/android/app/Activity#onPause() + /// + /// Applications that need to run on Android should assume their [`SurfaceView`] has been + /// destroyed, which indirectly invalidates any existing render surfaces that may have been + /// created outside of Winit (such as an `EGLSurface`, [`VkSurfaceKHR`] or [`wgpu::Surface`]). + /// + /// After being `Suspended` on Android applications must drop all render surfaces before + /// the event callback completes, which may be re-created when the application is next [`Resumed`]. + /// + /// [`SurfaceView`]: https://developer.android.com/reference/android/view/SurfaceView + /// [Activity lifecycle]: https://developer.android.com/guide/components/activities/activity-lifecycle + /// [`VkSurfaceKHR`]: https://www.khronos.org/registry/vulkan/specs/1.3-extensions/man/html/VkSurfaceKHR.html + /// [`wgpu::Surface`]: https://docs.rs/wgpu/latest/wgpu/struct.Surface.html + /// + /// ## iOS + /// + /// On iOS, the `Suspended` event is currently emitted in response to an + /// [`applicationWillResignActive`] callback which means that the application is + /// about to transition from the active to inactive state (according to the + /// [iOS application lifecycle]). + /// + /// [`applicationWillResignActive`]: https://developer.apple.com/documentation/uikit/uiapplicationdelegate/1622950-applicationwillresignactive + /// [iOS application lifecycle]: https://developer.apple.com/documentation/uikit/app_and_environment/managing_your_app_s_life_cycle + /// + /// ## Web + /// + /// On Web, the `Suspended` event is emitted in response to a [`pagehide`] event + /// with the property [`persisted`] being true, which means that the page is being + /// put in the [`bfcache`] (back/forward cache) - an in-memory cache that stores a + /// complete snapshot of a page (including the JavaScript heap) as the user is + /// navigating away. + /// + /// [`pagehide`]: https://developer.mozilla.org/en-US/docs/Web/API/Window/pagehide_event + /// [`persisted`]: https://developer.mozilla.org/en-US/docs/Web/API/PageTransitionEvent/persisted + /// [`bfcache`]: https://web.dev/bfcache/ + /// [`Resumed`]: Self::resumed + fn suspended(&mut self, event_loop: &ActiveEventLoop) { + let _ = event_loop; + } + + /// Emitted when the event loop is being shut down. + /// + /// This is irreversible - if this method is called, it is guaranteed that the event loop + /// will exist right after. + fn exiting(&mut self, event_loop: &ActiveEventLoop) { + let _ = event_loop; + } + + /// Emitted when the application has received a memory warning. + /// + /// ## Platform-specific + /// + /// ### Android + /// + /// On Android, the `MemoryWarning` event is sent when [`onLowMemory`] was called. The application + /// must [release memory] or risk being killed. + /// + /// [`onLowMemory`]: https://developer.android.com/reference/android/app/Application.html#onLowMemory() + /// [release memory]: https://developer.android.com/topic/performance/memory#release + /// + /// ### iOS + /// + /// On iOS, the `MemoryWarning` event is emitted in response to an [`applicationDidReceiveMemoryWarning`] + /// callback. The application must free as much memory as possible or risk being terminated, see + /// [how to respond to memory warnings]. + /// + /// [`applicationDidReceiveMemoryWarning`]: https://developer.apple.com/documentation/uikit/uiapplicationdelegate/1623063-applicationdidreceivememorywarni + /// [how to respond to memory warnings]: https://developer.apple.com/documentation/uikit/app_and_environment/managing_your_app_s_life_cycle/responding_to_memory_warnings + /// + /// ### Others + /// + /// - **macOS / Orbital / Wayland / Web / Windows:** Unsupported. + fn memory_warning(&mut self, event_loop: &ActiveEventLoop) { + let _ = event_loop; + } +} diff --git a/src/event.rs b/src/event.rs index ff3a004219..e2bca908be 100644 --- a/src/event.rs +++ b/src/event.rs @@ -1,36 +1,37 @@ //! The [`Event`] enum and assorted supporting types. //! -//! These are sent to the closure given to [`EventLoop::run(...)`], where they get +//! These are sent to the closure given to [`EventLoop::run_app(...)`], where they get //! processed and used to modify the program state. For more details, see the root-level documentation. //! //! Some of these events represent different "parts" of a traditional event-handling loop. You could -//! approximate the basic ordering loop of [`EventLoop::run(...)`] like this: +//! approximate the basic ordering loop of [`EventLoop::run_app(...)`] like this: //! //! ```rust,ignore //! let mut start_cause = StartCause::Init; //! //! while !elwt.exiting() { -//! event_handler(NewEvents(start_cause), elwt); +//! app.new_events(event_loop, start_cause); //! -//! for e in (window events, user events, device events) { -//! event_handler(e, elwt); +//! for event in (window events, user events, device events) { +//! // This will pick the right method on the application based on the event. +//! app.handle_event(event_loop, event); //! } //! -//! for w in (redraw windows) { -//! event_handler(RedrawRequested(w), elwt); +//! for window_id in (redraw windows) { +//! app.window_event(event_loop, window_id, RedrawRequested); //! } //! -//! event_handler(AboutToWait, elwt); +//! app.about_to_wait(event_loop); //! start_cause = wait_if_necessary(); //! } //! -//! event_handler(LoopExiting, elwt); +//! app.exiting(event_loop); //! ``` //! //! This leaves out timing details like [`ControlFlow::WaitUntil`] but hopefully //! describes what happens in what order. //! -//! [`EventLoop::run(...)`]: crate::event_loop::EventLoop::run +//! [`EventLoop::run_app(...)`]: crate::event_loop::EventLoop::run_app //! [`ControlFlow::WaitUntil`]: crate::event_loop::ControlFlow::WaitUntil use std::path::PathBuf; use std::sync::{Mutex, Weak}; @@ -59,199 +60,55 @@ use crate::{ /// See the module-level docs for more information on the event loop manages each event. #[derive(Debug, Clone, PartialEq)] pub enum Event { - /// Emitted when new events arrive from the OS to be processed. + /// See [`ApplicationHandler::new_events`] for details. /// - /// This event type is useful as a place to put code that should be done before you start - /// processing events, such as updating frame timing information for benchmarking or checking - /// the [`StartCause`] to see if a timer set by - /// [`ControlFlow::WaitUntil`](crate::event_loop::ControlFlow::WaitUntil) has elapsed. + /// [`ApplicationHandler::new_events`]: crate::application::ApplicationHandler::new_events NewEvents(StartCause), - /// Emitted when the OS sends an event to a winit window. + /// See [`ApplicationHandler::window_event`] for details. + /// + /// [`ApplicationHandler::window_event`]: crate::application::ApplicationHandler::window_event WindowEvent { window_id: WindowId, event: WindowEvent, }, - /// Emitted when the OS sends an event to a device. + /// See [`ApplicationHandler::device_event`] for details. + /// + /// [`ApplicationHandler::device_event`]: crate::application::ApplicationHandler::device_event DeviceEvent { device_id: DeviceId, event: DeviceEvent, }, - /// Emitted when an event is sent from [`EventLoopProxy::send_event`](crate::event_loop::EventLoopProxy::send_event) + /// See [`ApplicationHandler::user_event`] for details. + /// + /// [`ApplicationHandler::user_event`]: crate::application::ApplicationHandler::user_event UserEvent(T), - /// Emitted when the application has been suspended. - /// - /// # Portability - /// - /// Not all platforms support the notion of suspending applications, and there may be no - /// technical way to guarantee being able to emit a `Suspended` event if the OS has - /// no formal application lifecycle (currently only Android, iOS, and Web do). For this reason, - /// Winit does not currently try to emit pseudo `Suspended` events before the application - /// quits on platforms without an application lifecycle. - /// - /// Considering that the implementation of `Suspended` and [`Resumed`] events may be internally - /// driven by multiple platform-specific events, and that there may be subtle differences across - /// platforms with how these internal events are delivered, it's recommended that applications - /// be able to gracefully handle redundant (i.e. back-to-back) `Suspended` or [`Resumed`] events. - /// - /// Also see [`Resumed`] notes. - /// - /// ## Android - /// - /// On Android, the `Suspended` event is only sent when the application's associated - /// [`SurfaceView`] is destroyed. This is expected to closely correlate with the [`onPause`] - /// lifecycle event but there may technically be a discrepancy. - /// - /// [`onPause`]: https://developer.android.com/reference/android/app/Activity#onPause() - /// - /// Applications that need to run on Android should assume their [`SurfaceView`] has been - /// destroyed, which indirectly invalidates any existing render surfaces that may have been - /// created outside of Winit (such as an `EGLSurface`, [`VkSurfaceKHR`] or [`wgpu::Surface`]). - /// - /// After being `Suspended` on Android applications must drop all render surfaces before - /// the event callback completes, which may be re-created when the application is next [`Resumed`]. - /// - /// [`SurfaceView`]: https://developer.android.com/reference/android/view/SurfaceView - /// [Activity lifecycle]: https://developer.android.com/guide/components/activities/activity-lifecycle - /// [`VkSurfaceKHR`]: https://www.khronos.org/registry/vulkan/specs/1.3-extensions/man/html/VkSurfaceKHR.html - /// [`wgpu::Surface`]: https://docs.rs/wgpu/latest/wgpu/struct.Surface.html + /// See [`ApplicationHandler::suspended`] for details. /// - /// ## iOS - /// - /// On iOS, the `Suspended` event is currently emitted in response to an - /// [`applicationWillResignActive`] callback which means that the application is - /// about to transition from the active to inactive state (according to the - /// [iOS application lifecycle]). - /// - /// [`applicationWillResignActive`]: https://developer.apple.com/documentation/uikit/uiapplicationdelegate/1622950-applicationwillresignactive - /// [iOS application lifecycle]: https://developer.apple.com/documentation/uikit/app_and_environment/managing_your_app_s_life_cycle - /// - /// ## Web - /// - /// On Web, the `Suspended` event is emitted in response to a [`pagehide`] event - /// with the property [`persisted`] being true, which means that the page is being - /// put in the [`bfcache`] (back/forward cache) - an in-memory cache that stores a - /// complete snapshot of a page (including the JavaScript heap) as the user is - /// navigating away. - /// - /// [`pagehide`]: https://developer.mozilla.org/en-US/docs/Web/API/Window/pagehide_event - /// [`persisted`]: https://developer.mozilla.org/en-US/docs/Web/API/PageTransitionEvent/persisted - /// [`bfcache`]: https://web.dev/bfcache/ - /// - /// [`Resumed`]: Self::Resumed + /// [`ApplicationHandler::suspended`]: crate::application::ApplicationHandler::suspended Suspended, - /// Emitted when the application has been resumed. - /// - /// For consistency, all platforms emit a `Resumed` event even if they don't themselves have a - /// formal suspend/resume lifecycle. For systems without a standard suspend/resume lifecycle - /// the `Resumed` event is always emitted after the [`NewEvents(StartCause::Init)`][StartCause::Init] - /// event. - /// - /// # Portability - /// - /// It's recommended that applications should only initialize their graphics context and create - /// a window after they have received their first `Resumed` event. Some systems - /// (specifically Android) won't allow applications to create a render surface until they are - /// resumed. - /// - /// Considering that the implementation of [`Suspended`] and `Resumed` events may be internally - /// driven by multiple platform-specific events, and that there may be subtle differences across - /// platforms with how these internal events are delivered, it's recommended that applications - /// be able to gracefully handle redundant (i.e. back-to-back) [`Suspended`] or `Resumed` events. - /// - /// Also see [`Suspended`] notes. - /// - /// ## Android + /// See [`ApplicationHandler::resumed`] for details. /// - /// On Android, the `Resumed` event is sent when a new [`SurfaceView`] has been created. This is - /// expected to closely correlate with the [`onResume`] lifecycle event but there may technically - /// be a discrepancy. - /// - /// [`onResume`]: https://developer.android.com/reference/android/app/Activity#onResume() - /// - /// Applications that need to run on Android must wait until they have been `Resumed` - /// before they will be able to create a render surface (such as an `EGLSurface`, - /// [`VkSurfaceKHR`] or [`wgpu::Surface`]) which depend on having a - /// [`SurfaceView`]. Applications must also assume that if they are [`Suspended`], then their - /// render surfaces are invalid and should be dropped. - /// - /// Also see [`Suspended`] notes. - /// - /// [`SurfaceView`]: https://developer.android.com/reference/android/view/SurfaceView - /// [Activity lifecycle]: https://developer.android.com/guide/components/activities/activity-lifecycle - /// [`VkSurfaceKHR`]: https://www.khronos.org/registry/vulkan/specs/1.3-extensions/man/html/VkSurfaceKHR.html - /// [`wgpu::Surface`]: https://docs.rs/wgpu/latest/wgpu/struct.Surface.html - /// - /// ## iOS - /// - /// On iOS, the `Resumed` event is emitted in response to an [`applicationDidBecomeActive`] - /// callback which means the application is "active" (according to the - /// [iOS application lifecycle]). - /// - /// [`applicationDidBecomeActive`]: https://developer.apple.com/documentation/uikit/uiapplicationdelegate/1622956-applicationdidbecomeactive - /// [iOS application lifecycle]: https://developer.apple.com/documentation/uikit/app_and_environment/managing_your_app_s_life_cycle - /// - /// ## Web - /// - /// On Web, the `Resumed` event is emitted in response to a [`pageshow`] event - /// with the property [`persisted`] being true, which means that the page is being - /// restored from the [`bfcache`] (back/forward cache) - an in-memory cache that - /// stores a complete snapshot of a page (including the JavaScript heap) as the - /// user is navigating away. - /// - /// [`pageshow`]: https://developer.mozilla.org/en-US/docs/Web/API/Window/pageshow_event - /// [`persisted`]: https://developer.mozilla.org/en-US/docs/Web/API/PageTransitionEvent/persisted - /// [`bfcache`]: https://web.dev/bfcache/ - /// - /// [`Suspended`]: Self::Suspended + /// [`ApplicationHandler::resumed`]: crate::application::ApplicationHandler::resumed Resumed, - /// Emitted when the event loop is about to block and wait for new events. - /// - /// Most applications shouldn't need to hook into this event since there is no real relationship - /// between how often the event loop needs to wake up and the dispatching of any specific events. + /// See [`ApplicationHandler::about_to_wait`] for details. /// - /// High frequency event sources, such as input devices could potentially lead to lots of wake - /// ups and also lots of corresponding `AboutToWait` events. - /// - /// This is not an ideal event to drive application rendering from and instead applications - /// should render in response to [`WindowEvent::RedrawRequested`] events. + /// [`ApplicationHandler::about_to_wait`]: crate::application::ApplicationHandler::about_to_wait AboutToWait, - /// Emitted when the event loop is being shut down. + /// See [`ApplicationHandler::exiting`] for details. /// - /// This is irreversible - if this event is emitted, it is guaranteed to be the last event that - /// gets emitted. You generally want to treat this as a "do on quit" event. + /// [`ApplicationHandler::exiting`]: crate::application::ApplicationHandler::exiting LoopExiting, - /// Emitted when the application has received a memory warning. - /// - /// ## Platform-specific - /// - /// ### Android - /// - /// On Android, the `MemoryWarning` event is sent when [`onLowMemory`] was called. The application - /// must [release memory] or risk being killed. - /// - /// [`onLowMemory`]: https://developer.android.com/reference/android/app/Application.html#onLowMemory() - /// [release memory]: https://developer.android.com/topic/performance/memory#release - /// - /// ### iOS - /// - /// On iOS, the `MemoryWarning` event is emitted in response to an [`applicationDidReceiveMemoryWarning`] - /// callback. The application must free as much memory as possible or risk being terminated, see - /// [how to respond to memory warnings]. - /// - /// [`applicationDidReceiveMemoryWarning`]: https://developer.apple.com/documentation/uikit/uiapplicationdelegate/1623063-applicationdidreceivememorywarni - /// [how to respond to memory warnings]: https://developer.apple.com/documentation/uikit/app_and_environment/managing_your_app_s_life_cycle/responding_to_memory_warnings - /// - /// ### Others + /// See [`ApplicationHandler::memory_warning`] for details. /// - /// - **macOS / Wayland / Windows / Orbital:** Unsupported. + /// [`ApplicationHandler::memory_warning`]: crate::application::ApplicationHandler::memory_warning MemoryWarning, } diff --git a/src/event_loop.rs b/src/event_loop.rs index c1f32da61b..af99ea1bda 100644 --- a/src/event_loop.rs +++ b/src/event_loop.rs @@ -18,6 +18,7 @@ use std::time::{Duration, Instant}; #[cfg(web_platform)] use web_time::{Duration, Instant}; +use crate::application::ApplicationHandler; use crate::error::{EventLoopError, OsError}; use crate::window::{CustomCursor, CustomCursorSource, Window, WindowAttributes}; use crate::{event::Event, monitor::MonitorHandle, platform_impl}; @@ -215,8 +216,22 @@ impl EventLoop { } } - /// Runs the event loop in the calling thread and calls the given `event_handler` closure - /// to dispatch any pending events. + /// See [`run_app`]. + /// + /// [`run_app`]: Self::run_app + #[inline] + #[deprecated = "use `EventLoop::run_app` instead"] + #[cfg(not(all(web_platform, target_feature = "exception-handling")))] + pub fn run(self, event_handler: F) -> Result<(), EventLoopError> + where + F: FnMut(Event, &ActiveEventLoop), + { + let _span = tracing::debug_span!("winit::EventLoop::run").entered(); + + self.event_loop.run(event_handler) + } + + /// Run the application with the event loop on the calling thread. /// /// See the [`set_control_flow()`] docs on how to change the event loop's behavior. /// @@ -231,10 +246,10 @@ impl EventLoop { /// Web applications are recommended to use #[cfg_attr( web_platform, - doc = "[`EventLoopExtWebSys::spawn()`][crate::platform::web::EventLoopExtWebSys::spawn()]" + doc = "[`EventLoopExtWebSys::spawn_app()`][crate::platform::web::EventLoopExtWebSys::spawn_app()]" )] #[cfg_attr(not(web_platform), doc = "`EventLoopExtWebSys::spawn()`")] - /// [^1] instead of [`run()`] to avoid the need + /// [^1] instead of [`run_app()`] to avoid the need /// for the Javascript exception trick, and to make it clearer that the event loop runs /// asynchronously (via the browser's own, internal, event loop) and doesn't block the /// current thread of execution like it does on other platforms. @@ -242,17 +257,13 @@ impl EventLoop { /// This function won't be available with `target_feature = "exception-handling"`. /// /// [`set_control_flow()`]: ActiveEventLoop::set_control_flow() - /// [`run()`]: Self::run() - /// [^1]: `EventLoopExtWebSys::spawn()` is only available on Web. + /// [`run_app()`]: Self::run_app() + /// [^1]: `EventLoopExtWebSys::spawn_app()` is only available on Web. #[inline] #[cfg(not(all(web_platform, target_feature = "exception-handling")))] - pub fn run(self, event_handler: F) -> Result<(), EventLoopError> - where - F: FnMut(Event, &ActiveEventLoop), - { - let _span = tracing::debug_span!("winit::EventLoop::run").entered(); - - self.event_loop.run(event_handler) + pub fn run_app>(self, app: &mut A) -> Result<(), EventLoopError> { + self.event_loop + .run(|event, event_loop| dispatch_event_for_app(app, event_loop, event)) } /// Creates an [`EventLoopProxy`] that can be used to dispatch user events @@ -344,11 +355,11 @@ unsafe impl rwh_05::HasRawDisplayHandle for EventLoop { impl AsFd for EventLoop { /// Get the underlying [EventLoop]'s `fd` which you can register /// into other event loop, like [`calloop`] or [`mio`]. When doing so, the - /// loop must be polled with the [`pump_events`] API. + /// loop must be polled with the [`pump_app_events`] API. /// /// [`calloop`]: https://crates.io/crates/calloop /// [`mio`]: https://crates.io/crates/mio - /// [`pump_events`]: crate::platform::pump_events::EventLoopExtPumpEvents::pump_events + /// [`pump_app_events`]: crate::platform::pump_events::EventLoopExtPumpEvents::pump_app_events fn as_fd(&self) -> BorrowedFd<'_> { self.event_loop.as_fd() } @@ -358,11 +369,11 @@ impl AsFd for EventLoop { impl AsRawFd for EventLoop { /// Get the underlying [EventLoop]'s raw `fd` which you can register /// into other event loop, like [`calloop`] or [`mio`]. When doing so, the - /// loop must be polled with the [`pump_events`] API. + /// loop must be polled with the [`pump_app_events`] API. /// /// [`calloop`]: https://crates.io/crates/calloop /// [`mio`]: https://crates.io/crates/mio - /// [`pump_events`]: crate::platform::pump_events::EventLoopExtPumpEvents::pump_events + /// [`pump_app_events`]: crate::platform::pump_events::EventLoopExtPumpEvents::pump_app_events fn as_raw_fd(&self) -> RawFd { self.event_loop.as_raw_fd() } @@ -630,3 +641,23 @@ impl AsyncRequestSerial { Self { serial } } } + +/// Shim for various run APIs. +#[inline(always)] +pub(crate) fn dispatch_event_for_app>( + app: &mut A, + event_loop: &ActiveEventLoop, + event: Event, +) { + match event { + Event::NewEvents(cause) => app.new_events(event_loop, cause), + Event::WindowEvent { window_id, event } => app.window_event(event_loop, window_id, event), + Event::DeviceEvent { device_id, event } => app.device_event(event_loop, device_id, event), + Event::UserEvent(event) => app.user_event(event_loop, event), + Event::Suspended => app.suspended(event_loop), + Event::Resumed => app.resumed(event_loop), + Event::AboutToWait => app.about_to_wait(event_loop), + Event::LoopExiting => app.exiting(event_loop), + Event::MemoryWarning => app.memory_warning(event_loop), + } +} diff --git a/src/lib.rs b/src/lib.rs index 28a9879be0..5f99012cd1 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -21,7 +21,7 @@ //! Some user activity, like mouse movement, can generate both a [`WindowEvent`] *and* a //! [`DeviceEvent`]. You can also create and handle your own custom [`Event::UserEvent`]s, if desired. //! -//! You can retrieve events by calling [`EventLoop::run()`]. This function will +//! You can retrieve events by calling [`EventLoop::run_app()`]. This function will //! dispatch events for every [`Window`] that was created with that particular [`EventLoop`], and //! will run until [`exit()`] is used, at which point [`Event::LoopExiting`]. //! @@ -36,7 +36,7 @@ x11_platform, wayland_platform ), - doc = "[`EventLoopExtPumpEvents::pump_events()`][platform::pump_events::EventLoopExtPumpEvents::pump_events()]" + doc = "[`EventLoopExtPumpEvents::pump_app_events()`][platform::pump_events::EventLoopExtPumpEvents::pump_app_events()]" )] #![cfg_attr( not(any( @@ -46,18 +46,54 @@ x11_platform, wayland_platform )), - doc = "`EventLoopExtPumpEvents::pump_events()`" + doc = "`EventLoopExtPumpEvents::pump_app_events()`" )] //! [^1]. See that method's documentation for more reasons about why //! it's discouraged beyond compatibility reasons. //! //! //! ```no_run -//! use winit::{ -//! event::{Event, WindowEvent}, -//! event_loop::{ControlFlow, EventLoop}, -//! window::Window, -//! }; +//! use winit::application::ApplicationHandler; +//! use winit::event::WindowEvent; +//! use winit::event_loop::{ActiveEventLoop, ControlFlow, EventLoop}; +//! use winit::window::{Window, WindowId}; +//! +//! #[derive(Default)] +//! struct App { +//! window: Option, +//! } +//! +//! impl ApplicationHandler for App { +//! fn resumed(&mut self, event_loop: &ActiveEventLoop) { +//! self.window = Some(event_loop.create_window(Window::default_attributes()).unwrap()); +//! } +//! +//! fn window_event(&mut self, event_loop: &ActiveEventLoop, id: WindowId, event: WindowEvent) { +//! match event { +//! WindowEvent::CloseRequested => { +//! println!("The close button was pressed; stopping"); +//! event_loop.exit(); +//! }, +//! WindowEvent::RedrawRequested => { +//! // Redraw the application. +//! // +//! // It's preferable for applications that do not render continuously to render in +//! // this event rather than in AboutToWait, since rendering in here allows +//! // the program to gracefully handle redraws requested by the OS. +//! +//! // Draw. +//! +//! // Queue a RedrawRequested event. +//! // +//! // You only need to call this if you've determined that you need to redraw in +//! // applications which do not always need to. Applications that redraw continuously +//! // can render here instead. +//! self.window.as_ref().unwrap().request_redraw(); +//! } +//! _ => (), +//! } +//! } +//! } //! //! let event_loop = EventLoop::new().unwrap(); //! @@ -70,43 +106,8 @@ //! // input, and uses significantly less power/CPU time than ControlFlow::Poll. //! event_loop.set_control_flow(ControlFlow::Wait); //! -//! let mut window = None; -//! -//! event_loop.run(move |event, event_loop| { -//! match event { -//! Event::Resumed => { -//! window = Some(event_loop.create_window(Window::default_attributes()).unwrap()); -//! } -//! Event::WindowEvent { -//! event: WindowEvent::CloseRequested, -//! .. -//! } => { -//! println!("The close button was pressed; stopping"); -//! event_loop.exit(); -//! }, -//! Event::AboutToWait => { -//! // Application update code. -//! -//! // Queue a RedrawRequested event. -//! // -//! // You only need to call this if you've determined that you need to redraw in -//! // applications which do not always need to. Applications that redraw continuously -//! // can render here instead. -//! window.as_ref().unwrap().request_redraw(); -//! }, -//! Event::WindowEvent { -//! event: WindowEvent::RedrawRequested, -//! .. -//! } => { -//! // Redraw the application. -//! // -//! // It's preferable for applications that do not render continuously to render in -//! // this event rather than in AboutToWait, since rendering in here allows -//! // the program to gracefully handle redraws requested by the OS. -//! }, -//! _ => () -//! } -//! }); +//! let mut app = App::default(); +//! event_loop.run_app(&mut app); //! ``` //! //! [`WindowEvent`] has a [`WindowId`] member. In multi-window environments, it should be @@ -164,7 +165,7 @@ //! //! [`EventLoop`]: event_loop::EventLoop //! [`EventLoop::new()`]: event_loop::EventLoop::new -//! [`EventLoop::run()`]: event_loop::EventLoop::run +//! [`EventLoop::run_app()`]: event_loop::EventLoop::run_app //! [`exit()`]: event_loop::ActiveEventLoop::exit //! [`Window`]: window::Window //! [`WindowId`]: window::WindowId @@ -178,7 +179,7 @@ //! [`Event::LoopExiting`]: event::Event::LoopExiting //! [`raw_window_handle`]: ./window/struct.Window.html#method.raw_window_handle //! [`raw_display_handle`]: ./window/struct.Window.html#method.raw_display_handle -//! [^1]: `EventLoopExtPumpEvents::pump_events()` is only available on Windows, macOS, Android, X11 and Wayland. +//! [^1]: `EventLoopExtPumpEvents::pump_app_events()` is only available on Windows, macOS, Android, X11 and Wayland. #![deny(rust_2018_idioms)] #![deny(rustdoc::broken_intra_doc_links)] @@ -200,6 +201,7 @@ pub use rwh_06 as raw_window_handle; #[doc(inline)] pub use dpi; +pub mod application; #[macro_use] pub mod error; mod cursor; diff --git a/src/platform/pump_events.rs b/src/platform/pump_events.rs index 804723dcbf..37a31ea615 100644 --- a/src/platform/pump_events.rs +++ b/src/platform/pump_events.rs @@ -1,22 +1,13 @@ use std::time::Duration; -use crate::{ - event::Event, - event_loop::{ActiveEventLoop, EventLoop}, -}; - -/// The return status for `pump_events` -pub enum PumpStatus { - /// Continue running external loop. - Continue, - /// Exit external loop. - Exit(i32), -} +use crate::application::ApplicationHandler; +use crate::event::Event; +use crate::event_loop::{self, ActiveEventLoop, EventLoop}; /// Additional methods on [`EventLoop`] for pumping events within an external event loop pub trait EventLoopExtPumpEvents { /// A type provided by the user that can be passed through [`Event::UserEvent`]. - type UserEvent; + type UserEvent: 'static; /// Pump the `EventLoop` to check for and dispatch pending events. /// @@ -113,6 +104,21 @@ pub trait EventLoopExtPumpEvents { /// If you render outside of Winit you are likely to see window resizing artifacts /// since MacOS expects applications to render synchronously during any `drawRect` /// callback. + fn pump_app_events>( + &mut self, + timeout: Option, + app: &mut A, + ) -> PumpStatus { + #[allow(deprecated)] + self.pump_events(timeout, |event, event_loop| { + event_loop::dispatch_event_for_app(app, event_loop, event) + }) + } + + /// See [`pump_app_events`]. + /// + /// [`pump_app_events`]: Self::pump_app_events + #[deprecated = "use EventLoopExtPumpEvents::pump_app_events"] fn pump_events(&mut self, timeout: Option, event_handler: F) -> PumpStatus where F: FnMut(Event, &ActiveEventLoop); @@ -128,3 +134,11 @@ impl EventLoopExtPumpEvents for EventLoop { self.event_loop.pump_events(timeout, event_handler) } } + +/// The return status for `pump_events` +pub enum PumpStatus { + /// Continue running external loop. + Continue, + /// Exit external loop. + Exit(i32), +} diff --git a/src/platform/run_on_demand.rs b/src/platform/run_on_demand.rs index 0010eab886..886ff49df1 100644 --- a/src/platform/run_on_demand.rs +++ b/src/platform/run_on_demand.rs @@ -1,8 +1,7 @@ -use crate::{ - error::EventLoopError, - event::Event, - event_loop::{ActiveEventLoop, EventLoop}, -}; +use crate::application::ApplicationHandler; +use crate::error::EventLoopError; +use crate::event::Event; +use crate::event_loop::{self, ActiveEventLoop, EventLoop}; #[cfg(doc)] use crate::{platform::pump_events::EventLoopExtPumpEvents, window::Window}; @@ -10,12 +9,19 @@ use crate::{platform::pump_events::EventLoopExtPumpEvents, window::Window}; /// Additional methods on [`EventLoop`] to return control flow to the caller. pub trait EventLoopExtRunOnDemand { /// A type provided by the user that can be passed through [`Event::UserEvent`]. - type UserEvent; + type UserEvent: 'static; - /// Runs the event loop in the calling thread and calls the given `event_handler` closure - /// to dispatch any window system events. + /// See [`run_app_on_demand`]. /// - /// Unlike [`EventLoop::run`], this function accepts non-`'static` (i.e. non-`move`) closures + /// [`run_app_on_demand`]: Self::run_app_on_demand + #[deprecated = "use EventLoopExtRunOnDemand::run_app_on_demand"] + fn run_on_demand(&mut self, event_handler: F) -> Result<(), EventLoopError> + where + F: FnMut(Event, &ActiveEventLoop); + + /// Run the application with the event loop on the calling thread. + /// + /// Unlike [`EventLoop::run_app`], this function accepts non-`'static` (i.e. non-`move`) closures /// and it is possible to return control back to the caller without /// consuming the `EventLoop` (by using [`exit()`]) and /// so the event loop can be re-run after it has exit. @@ -26,11 +32,10 @@ pub trait EventLoopExtRunOnDemand { /// /// This API is not designed to run an event loop in bursts that you can exit from and return /// to while maintaining the full state of your application. (If you need something like this - /// you can look at the [`EventLoopExtPumpEvents::pump_events()`] API) + /// you can look at the [`EventLoopExtPumpEvents::pump_app_events()`] API) /// - /// Each time `run_on_demand` is called the `event_handler` can expect to receive a - /// `NewEvents(Init)` and `Resumed` event (even on platforms that have no suspend/resume - /// lifecycle) - which can be used to consistently initialize application state. + /// Each time `run_app_on_demand` is called the startup sequence of `init`, followed by + /// `resume` is being preserved. /// /// See the [`set_control_flow()`] docs on how to change the event loop's behavior. /// @@ -40,8 +45,8 @@ pub trait EventLoopExtRunOnDemand { /// backend it is possible to use `EventLoopExtWebSys::spawn()`[^1] more than once instead). /// - No [`Window`] state can be carried between separate runs of the event loop. /// - /// You are strongly encouraged to use [`EventLoop::run()`] for portability, unless you specifically need - /// the ability to re-run a single event loop more than once + /// You are strongly encouraged to use [`EventLoop::run_app()`] for portability, unless you + /// specifically need the ability to re-run a single event loop more than once /// /// # Supported Platforms /// - Windows @@ -64,9 +69,15 @@ pub trait EventLoopExtRunOnDemand { /// /// [`exit()`]: ActiveEventLoop::exit() /// [`set_control_flow()`]: ActiveEventLoop::set_control_flow() - fn run_on_demand(&mut self, event_handler: F) -> Result<(), EventLoopError> - where - F: FnMut(Event, &ActiveEventLoop); + fn run_app_on_demand>( + &mut self, + app: &mut A, + ) -> Result<(), EventLoopError> { + #[allow(deprecated)] + self.run_on_demand(|event, event_loop| { + event_loop::dispatch_event_for_app(app, event_loop, event) + }) + } } impl EventLoopExtRunOnDemand for EventLoop { diff --git a/src/platform/web.rs b/src/platform/web.rs index fd4982702a..dfca6cf54b 100644 --- a/src/platform/web.rs +++ b/src/platform/web.rs @@ -53,9 +53,10 @@ use std::time::Duration; #[cfg(web_platform)] use web_sys::HtmlCanvasElement; +use crate::application::ApplicationHandler; use crate::cursor::CustomCursorSource; use crate::event::Event; -use crate::event_loop::{ActiveEventLoop, EventLoop}; +use crate::event_loop::{self, ActiveEventLoop, EventLoop}; #[cfg(web_platform)] use crate::platform_impl::CustomCursorFuture as PlatformCustomCursorFuture; use crate::platform_impl::PlatformCustomCursorSource; @@ -160,18 +161,18 @@ impl WindowAttributesExtWebSys for WindowAttributes { /// Additional methods on `EventLoop` that are specific to the web. pub trait EventLoopExtWebSys { /// A type provided by the user that can be passed through `Event::UserEvent`. - type UserEvent; + type UserEvent: 'static; /// Initializes the winit event loop. /// /// Unlike #[cfg_attr( all(web_platform, target_feature = "exception-handling"), - doc = "`run()`" + doc = "`run_app()`" )] #[cfg_attr( not(all(web_platform, target_feature = "exception-handling")), - doc = "[`run()`]" + doc = "[`run_app()`]" )] /// [^1], this returns immediately, and doesn't throw an exception in order to /// satisfy its [`!`] return type. @@ -183,9 +184,15 @@ pub trait EventLoopExtWebSys { /// #[cfg_attr( not(all(web_platform, target_feature = "exception-handling")), - doc = "[`run()`]: EventLoop::run()" + doc = "[`run_app()`]: EventLoop::run_app()" )] - /// [^1]: `run()` is _not_ available on WASM when the target supports `exception-handling`. + /// [^1]: `run_app()` is _not_ available on WASM when the target supports `exception-handling`. + fn spawn_app + 'static>(self, app: A); + + /// See [`spawn_app`]. + /// + /// [`spawn_app`]: Self::spawn_app + #[deprecated = "use EventLoopExtWebSys::spawn_app"] fn spawn(self, event_handler: F) where F: 'static + FnMut(Event, &ActiveEventLoop); @@ -194,6 +201,12 @@ pub trait EventLoopExtWebSys { impl EventLoopExtWebSys for EventLoop { type UserEvent = T; + fn spawn_app + 'static>(self, mut app: A) { + self.event_loop.spawn(move |event, event_loop| { + event_loop::dispatch_event_for_app(&mut app, event_loop, event) + }); + } + fn spawn(self, event_handler: F) where F: 'static + FnMut(Event, &ActiveEventLoop), diff --git a/src/platform_impl/ios/event_loop.rs b/src/platform_impl/ios/event_loop.rs index 5fc30ea658..27921c1bed 100644 --- a/src/platform_impl/ios/event_loop.rs +++ b/src/platform_impl/ios/event_loop.rs @@ -182,7 +182,7 @@ impl EventLoop { application.is_none(), "\ `EventLoop` cannot be `run` after a call to `UIApplicationMain` on iOS\n\ - Note: `EventLoop::run` calls `UIApplicationMain` on iOS", + Note: `EventLoop::run_app` calls `UIApplicationMain` on iOS", ); let handler = map_user_event(handler, self.receiver); diff --git a/src/platform_impl/ios/window.rs b/src/platform_impl/ios/window.rs index a7c7b328fe..03f63cc73e 100644 --- a/src/platform_impl/ios/window.rs +++ b/src/platform_impl/ios/window.rs @@ -689,7 +689,7 @@ impl Inner { let screen_frame = self.rect_to_screen_space(bounds); let status_bar_frame = { let app = UIApplication::shared(MainThreadMarker::new().unwrap()).expect( - "`Window::get_inner_position` cannot be called before `EventLoop::run` on iOS", + "`Window::get_inner_position` cannot be called before `EventLoop::run_app` on iOS", ); app.statusBarFrame() }; diff --git a/src/platform_impl/macos/app_delegate.rs b/src/platform_impl/macos/app_delegate.rs index 574b115d3c..7d528ab2f3 100644 --- a/src/platform_impl/macos/app_delegate.rs +++ b/src/platform_impl/macos/app_delegate.rs @@ -91,7 +91,7 @@ declare_class!( self.set_is_running(true); self.dispatch_init_events(); - // If the application is being launched via `EventLoop::pump_events()` then we'll + // If the application is being launched via `EventLoop::pump_app_events()` then we'll // want to stop the app once it is launched (and return to the external loop) // // In this case we still want to consider Winit's `EventLoop` to be "running", diff --git a/src/platform_impl/web/event_loop/runner.rs b/src/platform_impl/web/event_loop/runner.rs index 9f7ea93464..06f925316d 100644 --- a/src/platform_impl/web/event_loop/runner.rs +++ b/src/platform_impl/web/event_loop/runner.rs @@ -75,8 +75,8 @@ enum RunnerEnum { Pending, /// The `EventLoop` is being run. Running(Runner), - /// The `EventLoop` is exited after being started with `EventLoop::run`. Since - /// `EventLoop::run` takes ownership of the `EventLoop`, we can be certain + /// The `EventLoop` is exited after being started with `EventLoop::run_app`. Since + /// `EventLoop::run_app` takes ownership of the `EventLoop`, we can be certain /// that this event loop will never be run again. Destroyed, } @@ -735,7 +735,7 @@ impl Shared { // * `self`, i.e. the item which triggered this event loop wakeup, which // is usually a `wasm-bindgen` `Closure`, which will be dropped after // returning to the JS glue code. - // * The `ActiveEventLoop` leaked inside `EventLoop::run` due to the + // * The `ActiveEventLoop` leaked inside `EventLoop::run_app` due to the // JS exception thrown at the end. // * For each undropped `Window`: // * The `register_redraw_request` closure. diff --git a/src/window.rs b/src/window.rs index 2aa49dde79..e3c456d4b7 100644 --- a/src/window.rs +++ b/src/window.rs @@ -20,7 +20,7 @@ use serde::{Deserialize, Serialize}; /// /// The window is closed when dropped. /// -/// # Threading +/// ## Threading /// /// This is `Send + Sync`, meaning that it can be freely used from other /// threads. @@ -30,37 +30,6 @@ use serde::{Deserialize, Serialize}; /// window from a thread other than the main, the code is scheduled to run on /// the main thread, and your thread may be blocked until that completes. /// -/// # Example -/// -/// ```no_run -/// use winit::{ -/// event::{Event, WindowEvent}, -/// event_loop::{ControlFlow, EventLoop}, -/// window::Window, -/// }; -/// -/// let mut event_loop = EventLoop::new().unwrap(); -/// event_loop.set_control_flow(ControlFlow::Wait); -/// let mut windows = Vec::new(); -/// -/// event_loop.run(move |event, event_loop| { -/// match event { -/// Event::Resumed => { -/// let window = event_loop.create_window(Window::default_attributes()).unwrap(); -/// windows.push(window); -/// } -/// Event::WindowEvent { -/// event: WindowEvent::CloseRequested, -/// .. -/// } => { -/// windows.clear(); -/// event_loop.exit(); -/// } -/// _ => (), -/// } -/// }); -/// ``` -/// /// ## Platform-specific /// /// **Web:** The [`Window`], which is represented by a `HTMLElementCanvas`, can