api: add ApplicationHandler and matching run APIs

Add a simple `ApplicationHandler` trait since winit is moving towards
trait based API. Add `run_app` group of APIs to accept `&mut impl
ApplicationHandler` deprecating the old `run` APIs.

Part-of: https://github.com/rust-windowing/winit/issues/3432
This commit is contained in:
Kirill Chibisov 2024-02-23 14:37:21 +04:00
parent fc8a008b25
commit d123cd2f8e
18 changed files with 821 additions and 651 deletions

View file

@ -12,6 +12,10 @@ Unreleased` header.
# Unreleased # Unreleased
- On Wayland, don't reapply cursor grab when unchanged. - 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<T>` trait which mimics `Event<T>`.
- Move `dpi` types to its own crate, and re-export it from the root crate. - Move `dpi` types to its own crate, and re-export it from the root crate.
- Implement `Sync` for `EventLoopProxy<T: Send>`. - Implement `Sync` for `EventLoopProxy<T: Send>`.
- **Breaking:** Move `Window::new` to `ActiveEventLoop::create_window` and `EventLoop::create_window` (with the latter being deprecated). - **Breaking:** Move `Window::new` to `ActiveEventLoop::create_window` and `EventLoop::create_window` (with the latter being deprecated).

View file

@ -3,29 +3,30 @@
use std::thread; use std::thread;
#[cfg(not(web_platform))] #[cfg(not(web_platform))]
use std::time; use std::time;
#[cfg(web_platform)] #[cfg(web_platform)]
use web_time as time; use web_time as time;
use winit::{ use winit::application::ApplicationHandler;
event::{ElementState, Event, KeyEvent, WindowEvent}, use winit::event::{ElementState, KeyEvent, StartCause, WindowEvent};
event_loop::{ControlFlow, EventLoop}, use winit::event_loop::{ActiveEventLoop, ControlFlow, EventLoop};
keyboard::{Key, NamedKey}, use winit::keyboard::{Key, NamedKey};
window::Window, use winit::window::{Window, WindowId};
};
#[path = "util/fill.rs"] #[path = "util/fill.rs"]
mod fill; 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 { enum Mode {
#[default]
Wait, Wait,
WaitUntil, WaitUntil,
Poll, 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> { fn main() -> Result<(), impl std::error::Error> {
tracing_subscriber::fmt::init(); tracing_subscriber::fmt::init();
@ -37,96 +38,110 @@ fn main() -> Result<(), impl std::error::Error> {
let event_loop = EventLoop::new().unwrap(); let event_loop = EventLoop::new().unwrap();
let mut mode = Mode::Wait; let mut app = ControlFlowDemo::default();
let mut request_redraw = false; event_loop.run_app(&mut app)
let mut wait_cancelled = false; }
let mut close_requested = false;
let mut window = None; #[derive(Default)]
event_loop.run(move |event, event_loop| { struct ControlFlowDemo {
use winit::event::StartCause; mode: Mode,
request_redraw: bool,
wait_cancelled: bool,
close_requested: bool,
window: Option<Window>,
}
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:?}"); println!("{event:?}");
match event { match event {
Event::NewEvents(start_cause) => { WindowEvent::CloseRequested => {
wait_cancelled = match start_cause { self.close_requested = true;
StartCause::WaitCancelled { .. } => mode == Mode::WaitUntil,
_ => false,
}
} }
Event::Resumed => { WindowEvent::KeyboardInput {
let window_attributes = Window::default_attributes().with_title( event:
"Press 1, 2, 3 to change control flow mode. Press R to toggle redraw requests.", KeyEvent {
); logical_key: key,
window = Some(event_loop.create_window(window_attributes).unwrap()); state: ElementState::Pressed,
} ..
Event::WindowEvent { event, .. } => match event { },
WindowEvent::CloseRequested => { ..
close_requested = true; } => 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);
} }
WindowEvent::KeyboardInput { Key::Character("2") => {
event: self.mode = Mode::WaitUntil;
KeyEvent { println!("\nmode: {:?}\n", self.mode);
logical_key: key, }
state: ElementState::Pressed, Key::Character("3") => {
.. self.mode = Mode::Poll;
}, println!("\nmode: {:?}\n", self.mode);
.. }
} => match key.as_ref() { Key::Character("r") => {
// WARNING: Consider using `key_without_modifiers()` if available on your platform. self.request_redraw = !self.request_redraw;
// See the `key_binding` example println!("\nrequest_redraw: {}\n", self.request_redraw);
Key::Character("1") => { }
mode = Mode::Wait; Key::Named(NamedKey::Escape) => {
println!("\nmode: {mode:?}\n"); self.close_requested = true;
}
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);
} }
_ => (), _ => (),
}, },
Event::AboutToWait => { WindowEvent::RedrawRequested => {
if request_redraw && !wait_cancelled && !close_requested { let window = self.window.as_ref().unwrap();
window.as_ref().unwrap().request_redraw(); window.pre_present_notify();
} fill::fill_window(window);
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();
}
} }
_ => (), _ => (),
} }
}) }
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 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();
}
}
} }

View file

@ -11,50 +11,59 @@
fn main() -> std::process::ExitCode { fn main() -> std::process::ExitCode {
use std::{process::ExitCode, thread::sleep, time::Duration}; use std::{process::ExitCode, thread::sleep, time::Duration};
use winit::{ use winit::application::ApplicationHandler;
event::{Event, WindowEvent}, use winit::event::WindowEvent;
event_loop::EventLoop, use winit::event_loop::{ActiveEventLoop, EventLoop};
platform::pump_events::{EventLoopExtPumpEvents, PumpStatus}, use winit::platform::pump_events::{EventLoopExtPumpEvents, PumpStatus};
window::Window, use winit::window::{Window, WindowId};
};
#[path = "util/fill.rs"] #[path = "util/fill.rs"]
mod fill; mod fill;
#[derive(Default)]
struct PumpDemo {
window: Option<Window>,
}
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());
}
fn window_event(
&mut self,
event_loop: &ActiveEventLoop,
_window_id: WindowId,
event: WindowEvent,
) {
println!("{event:?}");
let window = match self.window.as_ref() {
Some(window) => window,
None => return,
};
match event {
WindowEvent::CloseRequested => event_loop.exit(),
WindowEvent::RedrawRequested => {
fill::fill_window(window);
window.request_redraw();
}
_ => (),
}
}
}
let mut event_loop = EventLoop::new().unwrap(); let mut event_loop = EventLoop::new().unwrap();
tracing_subscriber::fmt::init(); tracing_subscriber::fmt::init();
let mut window = None; let mut app = PumpDemo::default();
loop { loop {
let timeout = Some(Duration::ZERO); let timeout = Some(Duration::ZERO);
let status = event_loop.pump_events(timeout, |event, event_loop| { let status = event_loop.pump_app_events(timeout, &mut app);
if let Event::WindowEvent { event, .. } = &event {
// Print only Window events to reduce noise
println!("{event:?}");
}
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();
}
_ => (),
}
});
if let PumpStatus::Exit(exit_code) = status { if let PumpStatus::Exit(exit_code) = status {
break ExitCode::from(exit_code as u8); break ExitCode::from(exit_code as u8);

View file

@ -2,86 +2,94 @@
// Limit this example to only compatible platforms. // Limit this example to only compatible platforms.
#[cfg(any(windows_platform, macos_platform, x11_platform, wayland_platform,))] #[cfg(any(windows_platform, macos_platform, x11_platform, wayland_platform,))]
fn main() -> Result<(), impl std::error::Error> { fn main() -> Result<(), Box<dyn std::error::Error>> {
use std::time::Duration; use std::time::Duration;
use winit::{ use winit::application::ApplicationHandler;
error::EventLoopError, use winit::event::WindowEvent;
event::{Event, WindowEvent}, use winit::event_loop::{ActiveEventLoop, EventLoop};
event_loop::EventLoop, use winit::platform::run_on_demand::EventLoopExtRunOnDemand;
platform::run_on_demand::EventLoopExtRunOnDemand, use winit::window::{Window, WindowId};
window::{Window, WindowId},
};
#[path = "util/fill.rs"] #[path = "util/fill.rs"]
mod fill; mod fill;
#[derive(Default)] #[derive(Default)]
struct App { struct App {
idx: usize,
window_id: Option<WindowId>, window_id: Option<WindowId>,
window: Option<Window>, window: Option<Window>,
} }
tracing_subscriber::fmt::init(); impl ApplicationHandler for App {
let mut event_loop = EventLoop::new().unwrap(); fn about_to_wait(&mut self, _event_loop: &ActiveEventLoop) {
if let Some(window) = self.window.as_ref() {
fn run_app(event_loop: &mut EventLoop<()>, idx: usize) -> Result<(), EventLoopError> { window.request_redraw();
let mut app = App::default();
event_loop.run_on_demand(move |event, event_loop| {
println!("Run {idx}: {:?}", event);
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);
}
_ => (),
}
} 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();
}
_ => (),
}
} 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);
} }
}) }
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 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;
}
let window = match self.window.as_mut() {
Some(window) => window,
None => return,
};
match event {
WindowEvent::CloseRequested => {
println!("--------------------------------------------------------- Window {} CloseRequested", self.idx);
fill::cleanup_window(window);
self.window = None;
}
WindowEvent::RedrawRequested => {
fill::fill_window(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!("--------------------------------------------------------- Finished first loop");
println!("--------------------------------------------------------- Waiting 5 seconds"); println!("--------------------------------------------------------- Waiting 5 seconds");
std::thread::sleep(Duration::from_secs(5)); 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"); println!("--------------------------------------------------------- Finished second loop");
ret Ok(())
} }
#[cfg(not(any(windows_platform, macos_platform, x11_platform, wayland_platform,)))] #[cfg(not(any(windows_platform, macos_platform, x11_platform, wayland_platform,)))]

View file

@ -14,8 +14,9 @@ use rwh_05::HasRawDisplayHandle;
#[cfg(not(any(android_platform, ios_platform)))] #[cfg(not(any(android_platform, ios_platform)))]
use softbuffer::{Context, Surface}; use softbuffer::{Context, Surface};
use winit::application::ApplicationHandler;
use winit::dpi::{LogicalSize, PhysicalPosition, PhysicalSize}; 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::{MouseButton, MouseScrollDelta};
use winit::event_loop::{ActiveEventLoop, EventLoop}; use winit::event_loop::{ActiveEventLoop, EventLoop};
use winit::keyboard::{Key, ModifiersState}; use winit::keyboard::{Key, ModifiersState};
@ -53,38 +54,7 @@ fn main() -> Result<(), Box<dyn Error>> {
let mut state = Application::new(&event_loop); let mut state = Application::new(&event_loop);
event_loop.run(move |event, event_loop| match event { event_loop.run_app(&mut state).map_err(Into::into)
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(())
} }
#[allow(dead_code)] #[allow(dead_code)]
@ -104,15 +74,14 @@ struct Application {
/// ///
/// With OpenGL it could be EGLDisplay. /// With OpenGL it could be EGLDisplay.
#[cfg(not(any(android_platform, ios_platform)))] #[cfg(not(any(android_platform, ios_platform)))]
context: Context, context: Option<Context>,
} }
impl Application { impl Application {
fn new<T>(event_loop: &EventLoop<T>) -> Self { fn new<T>(event_loop: &EventLoop<T>) -> Self {
// SAFETY: the context is dropped inside the loop, since the state we're using // SAFETY: we drop the context right before the event loop is stopped, thus making it safe.
// is moved inside the closure.
#[cfg(not(any(android_platform, ios_platform)))] #[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 // 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, // 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<Action> {
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<Action> {
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<UserEvent> for Application {
fn user_event(&mut self, _event_loop: &ActiveEventLoop, event: UserEvent) {
println!("User event: {event:?}");
}
fn window_event(
&mut self, &mut self,
event_loop: &ActiveEventLoop, event_loop: &ActiveEventLoop,
window_id: WindowId, window_id: WindowId,
@ -371,92 +430,37 @@ impl Application {
} }
} }
fn handle_device_event(&mut self, _: &ActiveEventLoop, _: DeviceId, event: DeviceEvent) { fn device_event(
println!("Device event: {event:?}"); &mut self,
_event_loop: &ActiveEventLoop,
device_id: DeviceId,
event: DeviceEvent,
) {
println!("Device {device_id:?} event: {event:?}");
} }
fn dump_monitors(&self, event_loop: &ActiveEventLoop) { fn resumed(&mut self, event_loop: &ActiveEventLoop) {
println!("Monitors information"); println!("Resumed the event loop");
let primary_monitor = event_loop.primary_monitor(); self.dump_monitors(event_loop);
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() { // Create initial window.
println!("{intro}: {name}"); self.create_window(event_loop, None)
} else { .expect("failed to create initial window");
println!("{intro}: [no name]");
}
let PhysicalSize { width, height } = monitor.size(); self.print_help();
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(); fn about_to_wait(&mut self, event_loop: &ActiveEventLoop) {
println!(" Position: {x},{y}"); if self.windows.is_empty() {
println!("No windows left, exiting...");
println!(" Scale factor: {}", monitor.scale_factor()); event_loop.exit();
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. #[cfg(not(any(android_platform, ios_platform)))]
fn process_key_binding(key: &str, mods: &ModifiersState) -> Option<Action> { fn exiting(&mut self, _event_loop: &ActiveEventLoop) {
KEY_BINDINGS.iter().find_map(|binding| { // We must drop the context here.
binding self.context = None;
.is_triggered_by(&key, mods)
.then_some(binding.action)
})
}
/// Process mouse binding.
fn process_mouse_binding(button: MouseButton, mods: &ModifiersState) -> Option<Action> {
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(),
);
}
} }
} }
@ -496,11 +500,11 @@ struct WindowState {
} }
impl WindowState { impl WindowState {
fn new(application: &Application, window: Window) -> Result<Self, Box<dyn Error>> { fn new(app: &Application, window: Window) -> Result<Self, Box<dyn Error>> {
// SAFETY: the surface is dropped before the `window` which provided it with handle, thus // SAFETY: the surface is dropped before the `window` which provided it with handle, thus
// it doesn't outlive it. // it doesn't outlive it.
#[cfg(not(any(android_platform, ios_platform)))] #[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); let theme = window.theme().unwrap_or(Theme::Dark);
println!("Theme: {theme:?}"); println!("Theme: {theme:?}");
@ -515,7 +519,7 @@ impl WindowState {
let mut state = Self { let mut state = Self {
#[cfg(macos_platform)] #[cfg(macos_platform)]
option_as_alt: window.option_as_alt(), 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, cursor_grab: CursorGrabMode::None,
named_idx, named_idx,
#[cfg(not(any(android_platform, ios_platform)))] #[cfg(not(any(android_platform, ios_platform)))]

View file

@ -3,16 +3,52 @@ use std::error::Error;
#[cfg(x11_platform)] #[cfg(x11_platform)]
fn main() -> Result<(), Box<dyn Error>> { fn main() -> Result<(), Box<dyn Error>> {
use winit::{ use winit::application::ApplicationHandler;
event::{Event, WindowEvent}, use winit::event::WindowEvent;
event_loop::EventLoop, use winit::event_loop::{ActiveEventLoop, EventLoop};
platform::x11::WindowAttributesExtX11, use winit::platform::x11::WindowAttributesExtX11;
window::Window, use winit::window::{Window, WindowId};
};
#[path = "util/fill.rs"] #[path = "util/fill.rs"]
mod fill; mod fill;
pub struct XEmbedDemo {
parent_window_id: u32,
window: Option<Window>,
}
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(self.parent_window_id);
self.window = Some(event_loop.create_window(window_attributes).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 => {
window.pre_present_notify();
fill::fill_window(window);
}
_ => (),
}
}
fn about_to_wait(&mut self, _event_loop: &ActiveEventLoop) {
self.window.as_ref().unwrap().request_redraw();
}
}
// First argument should be a 32-bit X11 window ID. // First argument should be a 32-bit X11 window ID.
let parent_window_id = std::env::args() let parent_window_id = std::env::args()
.nth(1) .nth(1)
@ -22,35 +58,11 @@ fn main() -> Result<(), Box<dyn Error>> {
tracing_subscriber::fmt::init(); tracing_subscriber::fmt::init();
let event_loop = EventLoop::new()?; let event_loop = EventLoop::new()?;
let mut window = None; let mut app = XEmbedDemo {
event_loop.run(move |event, event_loop| match event { parent_window_id,
Event::Resumed => { window: None,
let window_attributes = Window::default_attributes() };
.with_title("An embedded window!") event_loop.run_app(&mut app).map_err(Into::into)
.with_inner_size(winit::dpi::LogicalSize::new(128.0, 128.0))
.with_embed_parent_window(parent_window_id);
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 => {
window.pre_present_notify();
fill::fill_window(window);
}
_ => (),
}
}
Event::AboutToWait => {
window.as_ref().unwrap().request_redraw();
}
_ => (),
})?;
Ok(())
} }
#[cfg(not(x11_platform))] #[cfg(not(x11_platform))]

221
src/application.rs Normal file
View file

@ -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<T: 'static = ()> {
/// 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;
}
}

View file

@ -1,36 +1,37 @@
//! The [`Event`] enum and assorted supporting types. //! 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. //! 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 //! 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 //! ```rust,ignore
//! let mut start_cause = StartCause::Init; //! let mut start_cause = StartCause::Init;
//! //!
//! while !elwt.exiting() { //! 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) { //! for event in (window events, user events, device events) {
//! event_handler(e, elwt); //! // This will pick the right method on the application based on the event.
//! app.handle_event(event_loop, event);
//! } //! }
//! //!
//! for w in (redraw windows) { //! for window_id in (redraw windows) {
//! event_handler(RedrawRequested(w), elwt); //! app.window_event(event_loop, window_id, RedrawRequested);
//! } //! }
//! //!
//! event_handler(AboutToWait, elwt); //! app.about_to_wait(event_loop);
//! start_cause = wait_if_necessary(); //! start_cause = wait_if_necessary();
//! } //! }
//! //!
//! event_handler(LoopExiting, elwt); //! app.exiting(event_loop);
//! ``` //! ```
//! //!
//! This leaves out timing details like [`ControlFlow::WaitUntil`] but hopefully //! This leaves out timing details like [`ControlFlow::WaitUntil`] but hopefully
//! describes what happens in what order. //! 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 //! [`ControlFlow::WaitUntil`]: crate::event_loop::ControlFlow::WaitUntil
use std::path::PathBuf; use std::path::PathBuf;
use std::sync::{Mutex, Weak}; 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. /// See the module-level docs for more information on the event loop manages each event.
#[derive(Debug, Clone, PartialEq)] #[derive(Debug, Clone, PartialEq)]
pub enum Event<T: 'static> { pub enum Event<T: 'static> {
/// 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 /// [`ApplicationHandler::new_events`]: crate::application::ApplicationHandler::new_events
/// 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.
NewEvents(StartCause), 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 { WindowEvent {
window_id: WindowId, window_id: WindowId,
event: WindowEvent, 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 { DeviceEvent {
device_id: DeviceId, device_id: DeviceId,
event: DeviceEvent, 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), UserEvent(T),
/// Emitted when the application has been suspended. /// See [`ApplicationHandler::suspended`] for details.
/// ///
/// # Portability /// [`ApplicationHandler::suspended`]: crate::application::ApplicationHandler::suspended
///
/// 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
Suspended, Suspended,
/// Emitted when the application has been resumed. /// See [`ApplicationHandler::resumed`] for details.
/// ///
/// For consistency, all platforms emit a `Resumed` event even if they don't themselves have a /// [`ApplicationHandler::resumed`]: crate::application::ApplicationHandler::resumed
/// 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
///
/// 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
Resumed, Resumed,
/// Emitted when the event loop is about to block and wait for new events. /// See [`ApplicationHandler::about_to_wait`] for details.
/// ///
/// Most applications shouldn't need to hook into this event since there is no real relationship /// [`ApplicationHandler::about_to_wait`]: crate::application::ApplicationHandler::about_to_wait
/// 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.
AboutToWait, 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 /// [`ApplicationHandler::exiting`]: crate::application::ApplicationHandler::exiting
/// gets emitted. You generally want to treat this as a "do on quit" event.
LoopExiting, LoopExiting,
/// Emitted when the application has received a memory warning. /// See [`ApplicationHandler::memory_warning`] for details.
/// ///
/// ## Platform-specific /// [`ApplicationHandler::memory_warning`]: crate::application::ApplicationHandler::memory_warning
///
/// ### 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 / Wayland / Windows / Orbital:** Unsupported.
MemoryWarning, MemoryWarning,
} }

View file

@ -18,6 +18,7 @@ use std::time::{Duration, Instant};
#[cfg(web_platform)] #[cfg(web_platform)]
use web_time::{Duration, Instant}; use web_time::{Duration, Instant};
use crate::application::ApplicationHandler;
use crate::error::{EventLoopError, OsError}; use crate::error::{EventLoopError, OsError};
use crate::window::{CustomCursor, CustomCursorSource, Window, WindowAttributes}; use crate::window::{CustomCursor, CustomCursorSource, Window, WindowAttributes};
use crate::{event::Event, monitor::MonitorHandle, platform_impl}; use crate::{event::Event, monitor::MonitorHandle, platform_impl};
@ -215,8 +216,22 @@ impl<T> EventLoop<T> {
} }
} }
/// Runs the event loop in the calling thread and calls the given `event_handler` closure /// See [`run_app`].
/// to dispatch any pending events. ///
/// [`run_app`]: Self::run_app
#[inline]
#[deprecated = "use `EventLoop::run_app` instead"]
#[cfg(not(all(web_platform, target_feature = "exception-handling")))]
pub fn run<F>(self, event_handler: F) -> Result<(), EventLoopError>
where
F: FnMut(Event<T>, &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. /// See the [`set_control_flow()`] docs on how to change the event loop's behavior.
/// ///
@ -231,10 +246,10 @@ impl<T> EventLoop<T> {
/// Web applications are recommended to use /// Web applications are recommended to use
#[cfg_attr( #[cfg_attr(
web_platform, 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()`")] #[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 /// 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 /// asynchronously (via the browser's own, internal, event loop) and doesn't block the
/// current thread of execution like it does on other platforms. /// current thread of execution like it does on other platforms.
@ -242,17 +257,13 @@ impl<T> EventLoop<T> {
/// This function won't be available with `target_feature = "exception-handling"`. /// This function won't be available with `target_feature = "exception-handling"`.
/// ///
/// [`set_control_flow()`]: ActiveEventLoop::set_control_flow() /// [`set_control_flow()`]: ActiveEventLoop::set_control_flow()
/// [`run()`]: Self::run() /// [`run_app()`]: Self::run_app()
/// [^1]: `EventLoopExtWebSys::spawn()` is only available on Web. /// [^1]: `EventLoopExtWebSys::spawn_app()` is only available on Web.
#[inline] #[inline]
#[cfg(not(all(web_platform, target_feature = "exception-handling")))] #[cfg(not(all(web_platform, target_feature = "exception-handling")))]
pub fn run<F>(self, event_handler: F) -> Result<(), EventLoopError> pub fn run_app<A: ApplicationHandler<T>>(self, app: &mut A) -> Result<(), EventLoopError> {
where self.event_loop
F: FnMut(Event<T>, &ActiveEventLoop), .run(|event, event_loop| dispatch_event_for_app(app, event_loop, event))
{
let _span = tracing::debug_span!("winit::EventLoop::run").entered();
self.event_loop.run(event_handler)
} }
/// Creates an [`EventLoopProxy`] that can be used to dispatch user events /// Creates an [`EventLoopProxy`] that can be used to dispatch user events
@ -344,11 +355,11 @@ unsafe impl<T> rwh_05::HasRawDisplayHandle for EventLoop<T> {
impl<T> AsFd for EventLoop<T> { impl<T> AsFd for EventLoop<T> {
/// Get the underlying [EventLoop]'s `fd` which you can register /// Get the underlying [EventLoop]'s `fd` which you can register
/// into other event loop, like [`calloop`] or [`mio`]. When doing so, the /// 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 /// [`calloop`]: https://crates.io/crates/calloop
/// [`mio`]: https://crates.io/crates/mio /// [`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<'_> { fn as_fd(&self) -> BorrowedFd<'_> {
self.event_loop.as_fd() self.event_loop.as_fd()
} }
@ -358,11 +369,11 @@ impl<T> AsFd for EventLoop<T> {
impl<T> AsRawFd for EventLoop<T> { impl<T> AsRawFd for EventLoop<T> {
/// Get the underlying [EventLoop]'s raw `fd` which you can register /// Get the underlying [EventLoop]'s raw `fd` which you can register
/// into other event loop, like [`calloop`] or [`mio`]. When doing so, the /// 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 /// [`calloop`]: https://crates.io/crates/calloop
/// [`mio`]: https://crates.io/crates/mio /// [`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 { fn as_raw_fd(&self) -> RawFd {
self.event_loop.as_raw_fd() self.event_loop.as_raw_fd()
} }
@ -630,3 +641,23 @@ impl AsyncRequestSerial {
Self { serial } Self { serial }
} }
} }
/// Shim for various run APIs.
#[inline(always)]
pub(crate) fn dispatch_event_for_app<T: 'static, A: ApplicationHandler<T>>(
app: &mut A,
event_loop: &ActiveEventLoop,
event: Event<T>,
) {
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),
}
}

View file

@ -21,7 +21,7 @@
//! Some user activity, like mouse movement, can generate both a [`WindowEvent`] *and* a //! 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. //! [`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 //! dispatch events for every [`Window`] that was created with that particular [`EventLoop`], and
//! will run until [`exit()`] is used, at which point [`Event::LoopExiting`]. //! will run until [`exit()`] is used, at which point [`Event::LoopExiting`].
//! //!
@ -36,7 +36,7 @@
x11_platform, x11_platform,
wayland_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( #![cfg_attr(
not(any( not(any(
@ -46,18 +46,54 @@
x11_platform, x11_platform,
wayland_platform wayland_platform
)), )),
doc = "`EventLoopExtPumpEvents::pump_events()`" doc = "`EventLoopExtPumpEvents::pump_app_events()`"
)] )]
//! [^1]. See that method's documentation for more reasons about why //! [^1]. See that method's documentation for more reasons about why
//! it's discouraged beyond compatibility reasons. //! it's discouraged beyond compatibility reasons.
//! //!
//! //!
//! ```no_run //! ```no_run
//! use winit::{ //! use winit::application::ApplicationHandler;
//! event::{Event, WindowEvent}, //! use winit::event::WindowEvent;
//! event_loop::{ControlFlow, EventLoop}, //! use winit::event_loop::{ActiveEventLoop, ControlFlow, EventLoop};
//! window::Window, //! use winit::window::{Window, WindowId};
//! }; //!
//! #[derive(Default)]
//! struct App {
//! window: Option<Window>,
//! }
//!
//! 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(); //! let event_loop = EventLoop::new().unwrap();
//! //!
@ -70,43 +106,8 @@
//! // input, and uses significantly less power/CPU time than ControlFlow::Poll. //! // input, and uses significantly less power/CPU time than ControlFlow::Poll.
//! event_loop.set_control_flow(ControlFlow::Wait); //! event_loop.set_control_flow(ControlFlow::Wait);
//! //!
//! let mut window = None; //! let mut app = App::default();
//! //! event_loop.run_app(&mut app);
//! 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.
//! },
//! _ => ()
//! }
//! });
//! ``` //! ```
//! //!
//! [`WindowEvent`] has a [`WindowId`] member. In multi-window environments, it should be //! [`WindowEvent`] has a [`WindowId`] member. In multi-window environments, it should be
@ -164,7 +165,7 @@
//! //!
//! [`EventLoop`]: event_loop::EventLoop //! [`EventLoop`]: event_loop::EventLoop
//! [`EventLoop::new()`]: event_loop::EventLoop::new //! [`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 //! [`exit()`]: event_loop::ActiveEventLoop::exit
//! [`Window`]: window::Window //! [`Window`]: window::Window
//! [`WindowId`]: window::WindowId //! [`WindowId`]: window::WindowId
@ -178,7 +179,7 @@
//! [`Event::LoopExiting`]: event::Event::LoopExiting //! [`Event::LoopExiting`]: event::Event::LoopExiting
//! [`raw_window_handle`]: ./window/struct.Window.html#method.raw_window_handle //! [`raw_window_handle`]: ./window/struct.Window.html#method.raw_window_handle
//! [`raw_display_handle`]: ./window/struct.Window.html#method.raw_display_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(rust_2018_idioms)]
#![deny(rustdoc::broken_intra_doc_links)] #![deny(rustdoc::broken_intra_doc_links)]
@ -200,6 +201,7 @@ pub use rwh_06 as raw_window_handle;
#[doc(inline)] #[doc(inline)]
pub use dpi; pub use dpi;
pub mod application;
#[macro_use] #[macro_use]
pub mod error; pub mod error;
mod cursor; mod cursor;

View file

@ -1,22 +1,13 @@
use std::time::Duration; use std::time::Duration;
use crate::{ use crate::application::ApplicationHandler;
event::Event, use crate::event::Event;
event_loop::{ActiveEventLoop, EventLoop}, use crate::event_loop::{self, ActiveEventLoop, EventLoop};
};
/// The return status for `pump_events`
pub enum PumpStatus {
/// Continue running external loop.
Continue,
/// Exit external loop.
Exit(i32),
}
/// Additional methods on [`EventLoop`] for pumping events within an external event loop /// Additional methods on [`EventLoop`] for pumping events within an external event loop
pub trait EventLoopExtPumpEvents { pub trait EventLoopExtPumpEvents {
/// A type provided by the user that can be passed through [`Event::UserEvent`]. /// 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. /// 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 /// If you render outside of Winit you are likely to see window resizing artifacts
/// since MacOS expects applications to render synchronously during any `drawRect` /// since MacOS expects applications to render synchronously during any `drawRect`
/// callback. /// callback.
fn pump_app_events<A: ApplicationHandler<Self::UserEvent>>(
&mut self,
timeout: Option<Duration>,
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<F>(&mut self, timeout: Option<Duration>, event_handler: F) -> PumpStatus fn pump_events<F>(&mut self, timeout: Option<Duration>, event_handler: F) -> PumpStatus
where where
F: FnMut(Event<Self::UserEvent>, &ActiveEventLoop); F: FnMut(Event<Self::UserEvent>, &ActiveEventLoop);
@ -128,3 +134,11 @@ impl<T> EventLoopExtPumpEvents for EventLoop<T> {
self.event_loop.pump_events(timeout, event_handler) 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),
}

View file

@ -1,8 +1,7 @@
use crate::{ use crate::application::ApplicationHandler;
error::EventLoopError, use crate::error::EventLoopError;
event::Event, use crate::event::Event;
event_loop::{ActiveEventLoop, EventLoop}, use crate::event_loop::{self, ActiveEventLoop, EventLoop};
};
#[cfg(doc)] #[cfg(doc)]
use crate::{platform::pump_events::EventLoopExtPumpEvents, window::Window}; 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. /// Additional methods on [`EventLoop`] to return control flow to the caller.
pub trait EventLoopExtRunOnDemand { pub trait EventLoopExtRunOnDemand {
/// A type provided by the user that can be passed through [`Event::UserEvent`]. /// 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 /// See [`run_app_on_demand`].
/// to dispatch any window system events.
/// ///
/// 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<F>(&mut self, event_handler: F) -> Result<(), EventLoopError>
where
F: FnMut(Event<Self::UserEvent>, &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 /// and it is possible to return control back to the caller without
/// consuming the `EventLoop` (by using [`exit()`]) and /// consuming the `EventLoop` (by using [`exit()`]) and
/// so the event loop can be re-run after it has exit. /// 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 /// 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 /// 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 /// Each time `run_app_on_demand` is called the startup sequence of `init`, followed by
/// `NewEvents(Init)` and `Resumed` event (even on platforms that have no suspend/resume /// `resume` is being preserved.
/// lifecycle) - which can be used to consistently initialize application state.
/// ///
/// See the [`set_control_flow()`] docs on how to change the event loop's behavior. /// 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). /// 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. /// - 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 /// You are strongly encouraged to use [`EventLoop::run_app()`] for portability, unless you
/// the ability to re-run a single event loop more than once /// specifically need the ability to re-run a single event loop more than once
/// ///
/// # Supported Platforms /// # Supported Platforms
/// - Windows /// - Windows
@ -64,9 +69,15 @@ pub trait EventLoopExtRunOnDemand {
/// ///
/// [`exit()`]: ActiveEventLoop::exit() /// [`exit()`]: ActiveEventLoop::exit()
/// [`set_control_flow()`]: ActiveEventLoop::set_control_flow() /// [`set_control_flow()`]: ActiveEventLoop::set_control_flow()
fn run_on_demand<F>(&mut self, event_handler: F) -> Result<(), EventLoopError> fn run_app_on_demand<A: ApplicationHandler<Self::UserEvent>>(
where &mut self,
F: FnMut(Event<Self::UserEvent>, &ActiveEventLoop); 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<T> EventLoopExtRunOnDemand for EventLoop<T> { impl<T> EventLoopExtRunOnDemand for EventLoop<T> {

View file

@ -53,9 +53,10 @@ use std::time::Duration;
#[cfg(web_platform)] #[cfg(web_platform)]
use web_sys::HtmlCanvasElement; use web_sys::HtmlCanvasElement;
use crate::application::ApplicationHandler;
use crate::cursor::CustomCursorSource; use crate::cursor::CustomCursorSource;
use crate::event::Event; use crate::event::Event;
use crate::event_loop::{ActiveEventLoop, EventLoop}; use crate::event_loop::{self, ActiveEventLoop, EventLoop};
#[cfg(web_platform)] #[cfg(web_platform)]
use crate::platform_impl::CustomCursorFuture as PlatformCustomCursorFuture; use crate::platform_impl::CustomCursorFuture as PlatformCustomCursorFuture;
use crate::platform_impl::PlatformCustomCursorSource; use crate::platform_impl::PlatformCustomCursorSource;
@ -160,18 +161,18 @@ impl WindowAttributesExtWebSys for WindowAttributes {
/// Additional methods on `EventLoop` that are specific to the web. /// Additional methods on `EventLoop` that are specific to the web.
pub trait EventLoopExtWebSys { pub trait EventLoopExtWebSys {
/// A type provided by the user that can be passed through `Event::UserEvent`. /// A type provided by the user that can be passed through `Event::UserEvent`.
type UserEvent; type UserEvent: 'static;
/// Initializes the winit event loop. /// Initializes the winit event loop.
/// ///
/// Unlike /// Unlike
#[cfg_attr( #[cfg_attr(
all(web_platform, target_feature = "exception-handling"), all(web_platform, target_feature = "exception-handling"),
doc = "`run()`" doc = "`run_app()`"
)] )]
#[cfg_attr( #[cfg_attr(
not(all(web_platform, target_feature = "exception-handling")), 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 /// [^1], this returns immediately, and doesn't throw an exception in order to
/// satisfy its [`!`] return type. /// satisfy its [`!`] return type.
@ -183,9 +184,15 @@ pub trait EventLoopExtWebSys {
/// ///
#[cfg_attr( #[cfg_attr(
not(all(web_platform, target_feature = "exception-handling")), 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<A: ApplicationHandler<Self::UserEvent> + 'static>(self, app: A);
/// See [`spawn_app`].
///
/// [`spawn_app`]: Self::spawn_app
#[deprecated = "use EventLoopExtWebSys::spawn_app"]
fn spawn<F>(self, event_handler: F) fn spawn<F>(self, event_handler: F)
where where
F: 'static + FnMut(Event<Self::UserEvent>, &ActiveEventLoop); F: 'static + FnMut(Event<Self::UserEvent>, &ActiveEventLoop);
@ -194,6 +201,12 @@ pub trait EventLoopExtWebSys {
impl<T> EventLoopExtWebSys for EventLoop<T> { impl<T> EventLoopExtWebSys for EventLoop<T> {
type UserEvent = T; type UserEvent = T;
fn spawn_app<A: ApplicationHandler<Self::UserEvent> + '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<F>(self, event_handler: F) fn spawn<F>(self, event_handler: F)
where where
F: 'static + FnMut(Event<Self::UserEvent>, &ActiveEventLoop), F: 'static + FnMut(Event<Self::UserEvent>, &ActiveEventLoop),

View file

@ -182,7 +182,7 @@ impl<T: 'static> EventLoop<T> {
application.is_none(), application.is_none(),
"\ "\
`EventLoop` cannot be `run` after a call to `UIApplicationMain` on iOS\n\ `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); let handler = map_user_event(handler, self.receiver);

View file

@ -689,7 +689,7 @@ impl Inner {
let screen_frame = self.rect_to_screen_space(bounds); let screen_frame = self.rect_to_screen_space(bounds);
let status_bar_frame = { let status_bar_frame = {
let app = UIApplication::shared(MainThreadMarker::new().unwrap()).expect( 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() app.statusBarFrame()
}; };

View file

@ -91,7 +91,7 @@ declare_class!(
self.set_is_running(true); self.set_is_running(true);
self.dispatch_init_events(); 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) // 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", // In this case we still want to consider Winit's `EventLoop` to be "running",

View file

@ -75,8 +75,8 @@ enum RunnerEnum {
Pending, Pending,
/// The `EventLoop` is being run. /// The `EventLoop` is being run.
Running(Runner), Running(Runner),
/// The `EventLoop` is exited after being started with `EventLoop::run`. Since /// The `EventLoop` is exited after being started with `EventLoop::run_app`. Since
/// `EventLoop::run` takes ownership of the `EventLoop`, we can be certain /// `EventLoop::run_app` takes ownership of the `EventLoop`, we can be certain
/// that this event loop will never be run again. /// that this event loop will never be run again.
Destroyed, Destroyed,
} }
@ -735,7 +735,7 @@ impl Shared {
// * `self`, i.e. the item which triggered this event loop wakeup, which // * `self`, i.e. the item which triggered this event loop wakeup, which
// is usually a `wasm-bindgen` `Closure`, which will be dropped after // is usually a `wasm-bindgen` `Closure`, which will be dropped after
// returning to the JS glue code. // 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. // JS exception thrown at the end.
// * For each undropped `Window`: // * For each undropped `Window`:
// * The `register_redraw_request` closure. // * The `register_redraw_request` closure.

View file

@ -20,7 +20,7 @@ use serde::{Deserialize, Serialize};
/// ///
/// The window is closed when dropped. /// The window is closed when dropped.
/// ///
/// # Threading /// ## Threading
/// ///
/// This is `Send + Sync`, meaning that it can be freely used from other /// This is `Send + Sync`, meaning that it can be freely used from other
/// threads. /// threads.
@ -30,37 +30,6 @@ use serde::{Deserialize, Serialize};
/// window from a thread other than the main, the code is scheduled to run on /// 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. /// 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 /// ## Platform-specific
/// ///
/// **Web:** The [`Window`], which is represented by a `HTMLElementCanvas`, can /// **Web:** The [`Window`], which is represented by a `HTMLElementCanvas`, can