From 8d39dbf4a0a2014dcd72c6759a3acd9e6dbc3611 Mon Sep 17 00:00:00 2001 From: Kirill Chibisov Date: Sun, 11 May 2025 20:07:27 +0900 Subject: [PATCH] event_loop: group core API in `EventLoopProvider` This helps with portability and defines some top-level structure around the event loop, so in the future, backends can get an idea of what API to use. This also changes the API to be object safe by using `dyn` throughout. --- examples/application.rs | 2 +- examples/child_window.rs | 2 +- examples/control_flow.rs | 2 +- examples/dnd.rs | 2 +- examples/window.rs | 2 +- examples/x11_embed.rs | 2 +- src/application.rs | 14 +- src/changelog/unreleased.md | 4 + src/event_loop.rs | 265 +++++++--------------------------- src/lib.rs | 10 +- src/platform/event_loop.rs | 193 +++++++++++++++++++++++++ src/platform/mod.rs | 2 + src/platform/pump_events.rs | 12 +- src/platform/run_on_demand.rs | 15 +- src/platform/web.rs | 2 +- 15 files changed, 286 insertions(+), 243 deletions(-) create mode 100644 src/platform/event_loop.rs diff --git a/examples/application.rs b/examples/application.rs index 931c83665..83f4b6835 100644 --- a/examples/application.rs +++ b/examples/application.rs @@ -19,7 +19,7 @@ use winit::application::ApplicationHandler; use winit::dpi::{LogicalSize, PhysicalPosition, PhysicalSize}; use winit::error::RequestError; use winit::event::{DeviceEvent, DeviceId, Ime, MouseButton, MouseScrollDelta, WindowEvent}; -use winit::event_loop::{ActiveEventLoop, EventLoop}; +use winit::event_loop::{ActiveEventLoop, EventLoop, EventLoopProvider}; use winit::icon::RgbaIcon; use winit::keyboard::{Key, ModifiersState}; use winit::monitor::Fullscreen; diff --git a/examples/child_window.rs b/examples/child_window.rs index e87207828..2a9473233 100644 --- a/examples/child_window.rs +++ b/examples/child_window.rs @@ -6,7 +6,7 @@ fn main() -> Result<(), impl std::error::Error> { use winit::application::ApplicationHandler; use winit::dpi::{LogicalPosition, LogicalSize, Position}; use winit::event::{ElementState, KeyEvent, WindowEvent}; - use winit::event_loop::{ActiveEventLoop, EventLoop}; + use winit::event_loop::{ActiveEventLoop, EventLoop, EventLoopProvider}; use winit::raw_window_handle::HasRawWindowHandle; use winit::window::{Window, WindowAttributes, WindowId}; diff --git a/examples/control_flow.rs b/examples/control_flow.rs index 8fb09bb3f..050569c23 100644 --- a/examples/control_flow.rs +++ b/examples/control_flow.rs @@ -9,7 +9,7 @@ use ::tracing::{info, warn}; use web_time as time; use winit::application::ApplicationHandler; use winit::event::{ElementState, KeyEvent, StartCause, WindowEvent}; -use winit::event_loop::{ActiveEventLoop, ControlFlow, EventLoop}; +use winit::event_loop::{ActiveEventLoop, ControlFlow, EventLoop, EventLoopProvider}; use winit::keyboard::{Key, NamedKey}; use winit::window::{Window, WindowAttributes, WindowId}; diff --git a/examples/dnd.rs b/examples/dnd.rs index 51d1ee450..3a5cf48d9 100644 --- a/examples/dnd.rs +++ b/examples/dnd.rs @@ -2,7 +2,7 @@ use std::error::Error; use winit::application::ApplicationHandler; use winit::event::WindowEvent; -use winit::event_loop::{ActiveEventLoop, EventLoop}; +use winit::event_loop::{ActiveEventLoop, EventLoop, EventLoopProvider}; use winit::window::{Window, WindowAttributes, WindowId}; #[path = "util/fill.rs"] diff --git a/examples/window.rs b/examples/window.rs index b6077270b..c3da7554b 100644 --- a/examples/window.rs +++ b/examples/window.rs @@ -4,7 +4,7 @@ use std::error::Error; use winit::application::ApplicationHandler; use winit::event::WindowEvent; -use winit::event_loop::{ActiveEventLoop, EventLoop}; +use winit::event_loop::{ActiveEventLoop, EventLoop, EventLoopProvider}; #[cfg(web_platform)] use winit::platform::web::WindowAttributesExtWeb; use winit::window::{Window, WindowAttributes, WindowId}; diff --git a/examples/x11_embed.rs b/examples/x11_embed.rs index e600a5d05..f7eb9fddd 100644 --- a/examples/x11_embed.rs +++ b/examples/x11_embed.rs @@ -5,7 +5,7 @@ use std::error::Error; fn main() -> Result<(), Box> { use winit::application::ApplicationHandler; use winit::event::WindowEvent; - use winit::event_loop::{ActiveEventLoop, EventLoop}; + use winit::event_loop::{ActiveEventLoop, EventLoop, EventLoopProvider}; use winit::platform::x11::WindowAttributesExtX11; use winit::window::{Window, WindowAttributes, WindowId}; diff --git a/src/application.rs b/src/application.rs index 0020ca992..5018543ae 100644 --- a/src/application.rs +++ b/src/application.rs @@ -8,14 +8,14 @@ use crate::window::WindowId; /// The handler of application-level events. /// -/// See [the top-level docs] for example usage, and [`EventLoop::run_app`] for an overview of when -/// events are delivered. +/// See [the top-level docs] for example usage, and [`EventLoopProvider::run_app`] for an overview +/// of when events are delivered. /// /// This is [dropped] when the event loop is shut down. Note that this only works if you're passing -/// the entire state to [`EventLoop::run_app`] (passing `&mut app` won't work). +/// the entire state to [`EventLoopProvider::run_app`] (passing `&mut app` won't work). /// /// [the top-level docs]: crate -/// [`EventLoop::run_app`]: crate::event_loop::EventLoop::run_app +/// [`EventLoopProvider::run_app`]: crate::event_loop::EventLoopProvider::run_app /// [dropped]: std::ops::Drop pub trait ApplicationHandler { /// Emitted when new events arrive from the OS to be processed. @@ -136,7 +136,7 @@ pub trait ApplicationHandler { /// use std::time::Duration; /// /// use winit::application::ApplicationHandler; - /// use winit::event_loop::{ActiveEventLoop, EventLoop}; + /// use winit::event_loop::{ActiveEventLoop, EventLoop, EventLoopProvider}; /// /// struct MyApp { /// receiver: mpsc::Receiver, @@ -210,9 +210,9 @@ pub trait ApplicationHandler { /// Emitted when the OS sends an event to a device. /// - /// For this to be called, it must be enabled with [`EventLoop::listen_device_events`]. + /// For this to be called, it must be enabled with [`EventLoopProvider::listen_device_events`]. /// - /// [`EventLoop::listen_device_events`]: crate::event_loop::EventLoop::listen_device_events + /// [`EventLoopProvider::listen_device_events`]: crate::event_loop::EventLoopProvider::listen_device_events fn device_event( &mut self, event_loop: &dyn ActiveEventLoop, diff --git a/src/changelog/unreleased.md b/src/changelog/unreleased.md index d56b69318..1eabf279c 100644 --- a/src/changelog/unreleased.md +++ b/src/changelog/unreleased.md @@ -195,6 +195,10 @@ changelog entry. - Renamed "super" key to "meta", to match the naming in the W3C specification. `NamedKey::Super` still exists, but it's non-functional and deprecated, `NamedKey::Meta` should be used instead. - Move `IconExtWindows` into `WinIcon`. +- `run_app_on_demand`/`pump_app_events` now accept `&mut dyn ApplicationHandler` instead of generic. +- Moved common `EventLoop` methods like `run_app` into `EventLoopProvider` trait. +- Moved `event_loop::EventLoop` into `platform::event_loop::EventLoop` keeping the old re-export in place. +- `EventLoopProvider::run_app` now takes `Box, // Not Send nor Sync -} - -/// Object that allows building the event loop. -/// -/// This is used to make specifying options that affect the whole application -/// easier. But note that constructing multiple event loops is not supported. -/// -/// This can be created using [`EventLoop::builder`]. -#[derive(Default, Debug, PartialEq, Eq, Hash)] -pub struct EventLoopBuilder { - pub(crate) platform_specific: platform_impl::PlatformSpecificEventLoopAttributes, -} - -static EVENT_LOOP_CREATED: AtomicBool = AtomicBool::new(false); - -impl EventLoopBuilder { - /// Builds a new event loop. - /// - /// ***For cross-platform compatibility, the [`EventLoop`] must be created on the main thread, - /// and only once per application.*** - /// - /// Calling this function will result in display backend initialisation. - /// - /// ## Panics - /// - /// Attempting to create the event loop off the main thread will panic. This - /// restriction isn't strictly necessary on all platforms, but is imposed to - /// eliminate any nasty surprises when porting to platforms that require it. - /// `EventLoopBuilderExt::with_any_thread` functions are exposed in the relevant - /// [`platform`] module if the target platform supports creating an event - /// loop on any thread. - /// - /// ## Platform-specific - /// - /// - **Wayland/X11:** to prevent running under `Wayland` or `X11` unset `WAYLAND_DISPLAY` or - /// `DISPLAY` respectively when building the event loop. - /// - **Android:** must be configured with an `AndroidApp` from `android_main()` by calling - /// [`.with_android_app(app)`] before calling `.build()`, otherwise it'll panic. - /// - /// [`platform`]: crate::platform - #[cfg_attr( - android_platform, - doc = "[`.with_android_app(app)`]: \ - crate::platform::android::EventLoopBuilderExtAndroid::with_android_app" - )] - #[cfg_attr( - not(android_platform), - doc = "[`.with_android_app(app)`]: #only-available-on-android" - )] - #[inline] - pub fn build(&mut self) -> Result { - let _span = tracing::debug_span!("winit::EventLoopBuilder::build").entered(); - - if EVENT_LOOP_CREATED.swap(true, Ordering::Relaxed) { - return Err(EventLoopError::RecreationAttempt); - } - - // Certain platforms accept a mutable reference in their API. - #[allow(clippy::unnecessary_mut_passed)] - Ok(EventLoop { - event_loop: platform_impl::EventLoop::new(&mut self.platform_specific)?, - _marker: PhantomData, - }) - } - - #[cfg(web_platform)] - pub(crate) fn allow_event_loop_recreation() { - EVENT_LOOP_CREATED.store(false, Ordering::Relaxed); - } -} - -/// Set through [`ActiveEventLoop::set_control_flow()`]. -/// -/// Indicates the desired behavior of the event loop after [`about_to_wait`] is called. -/// -/// Defaults to [`Wait`]. -/// -/// [`Wait`]: Self::Wait -/// [`about_to_wait`]: crate::application::ApplicationHandler::about_to_wait -#[derive(Copy, Clone, Debug, Default, PartialEq, Eq, Hash)] -pub enum ControlFlow { - /// When the current loop iteration finishes, immediately begin a new iteration regardless of - /// whether or not new events are available to process. - Poll, - - /// When the current loop iteration finishes, suspend the thread until another event arrives. - #[default] - Wait, - - /// When the current loop iteration finishes, suspend the thread until either another event - /// arrives or the given time is reached. - /// - /// Useful for implementing efficient timers. Applications which want to render at the - /// display's native refresh rate should instead use [`Poll`] and the VSync functionality - /// of a graphics API to reduce odds of missed frames. - /// - /// [`Poll`]: Self::Poll - WaitUntil(Instant), -} - -impl ControlFlow { - /// Creates a [`ControlFlow`] that waits until a timeout has expired. - /// - /// In most cases, this is set to [`WaitUntil`]. However, if the timeout overflows, it is - /// instead set to [`Wait`]. - /// - /// [`WaitUntil`]: Self::WaitUntil - /// [`Wait`]: Self::Wait - pub fn wait_duration(timeout: Duration) -> Self { - match Instant::now().checked_add(timeout) { - Some(instant) => Self::WaitUntil(instant), - None => Self::Wait, - } - } -} - -impl EventLoop { - /// Create the event loop. - /// - /// This is an alias of `EventLoop::builder().build()`. - #[inline] - pub fn new() -> Result { - Self::builder().build() - } - - /// Start building a new event loop. - /// - /// This returns an [`EventLoopBuilder`], to allow configuring the event loop before creation. - /// - /// To get the actual event loop, call [`build`][EventLoopBuilder::build] on that. - #[inline] - pub fn builder() -> EventLoopBuilder { - EventLoopBuilder { platform_specific: Default::default() } - } -} - -impl EventLoop { +/// Common interface to describe event loop. +pub trait EventLoopProvider: AsAny + fmt::Debug { /// Run the application with the event loop on the calling thread. /// /// ## Event loop flow @@ -262,88 +105,84 @@ impl EventLoop { /// /// [`set_control_flow()`]: ActiveEventLoop::set_control_flow() /// [`run_app()`]: Self::run_app() - #[inline] - #[cfg(not(all(web_platform, target_feature = "exception-handling")))] - pub fn run_app(self, app: A) -> Result<(), EventLoopError> { - self.event_loop.run_app(app) - } + fn run_app(self, app: impl ApplicationHandler) -> Result<(), EventLoopError> + where + Self: Sized; /// Creates an [`EventLoopProxy`] that can be used to dispatch user events /// to the main event loop, possibly from another thread. - pub fn create_proxy(&self) -> EventLoopProxy { - self.event_loop.window_target().create_proxy() - } + fn create_proxy(&self) -> EventLoopProxy; /// Gets a persistent reference to the underlying platform display. /// /// See the [`OwnedDisplayHandle`] type for more information. - pub fn owned_display_handle(&self) -> OwnedDisplayHandle { - self.event_loop.window_target().owned_display_handle() - } + fn owned_display_handle(&self) -> OwnedDisplayHandle; /// Change if or when [`DeviceEvent`]s are captured. /// /// See [`ActiveEventLoop::listen_device_events`] for details. /// /// [`DeviceEvent`]: crate::event::DeviceEvent - pub fn listen_device_events(&self, allowed: DeviceEvents) { - let _span = tracing::debug_span!( - "winit::EventLoop::listen_device_events", - allowed = ?allowed - ) - .entered(); - self.event_loop.window_target().listen_device_events(allowed) - } + fn listen_device_events(&self, allowed: DeviceEvents); /// Sets the [`ControlFlow`]. - pub fn set_control_flow(&self, control_flow: ControlFlow) { - self.event_loop.window_target().set_control_flow(control_flow); - } + fn set_control_flow(&self, control_flow: ControlFlow); /// Create custom cursor. /// /// ## Platform-specific /// /// **iOS / Android / Orbital:** Unsupported. - pub fn create_custom_cursor( + fn create_custom_cursor( &self, custom_cursor: CustomCursorSource, - ) -> Result { - self.event_loop.window_target().create_custom_cursor(custom_cursor) - } + ) -> Result; } -impl HasDisplayHandle for EventLoop { - fn display_handle(&self) -> Result, HandleError> { - HasDisplayHandle::display_handle(self.event_loop.window_target().rwh_06_handle()) - } -} +impl_dyn_casting!(EventLoopProvider); -#[cfg(any(x11_platform, wayland_platform))] -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_app_events`] API. +/// Set through [`ActiveEventLoop::set_control_flow()`]. +/// +/// Indicates the desired behavior of the event loop after [`about_to_wait`] is called. +/// +/// Defaults to [`Wait`]. +/// +/// [`Wait`]: Self::Wait +/// [`about_to_wait`]: crate::application::ApplicationHandler::about_to_wait +#[derive(Copy, Clone, Debug, Default, PartialEq, Eq, Hash)] +pub enum ControlFlow { + /// When the current loop iteration finishes, immediately begin a new iteration regardless of + /// whether or not new events are available to process. + Poll, + + /// When the current loop iteration finishes, suspend the thread until another event arrives. + #[default] + Wait, + + /// When the current loop iteration finishes, suspend the thread until either another event + /// arrives or the given time is reached. /// - /// [`calloop`]: https://crates.io/crates/calloop - /// [`mio`]: https://crates.io/crates/mio - /// [`pump_app_events`]: crate::platform::pump_events::EventLoopExtPumpEvents::pump_app_events - fn as_fd(&self) -> BorrowedFd<'_> { - self.event_loop.as_fd() - } + /// Useful for implementing efficient timers. Applications which want to render at the + /// display's native refresh rate should instead use [`Poll`] and the VSync functionality + /// of a graphics API to reduce odds of missed frames. + /// + /// [`Poll`]: Self::Poll + WaitUntil(Instant), } -#[cfg(any(x11_platform, wayland_platform))] -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_app_events`] API. +impl ControlFlow { + /// Creates a [`ControlFlow`] that waits until a timeout has expired. /// - /// [`calloop`]: https://crates.io/crates/calloop - /// [`mio`]: https://crates.io/crates/mio - /// [`pump_app_events`]: crate::platform::pump_events::EventLoopExtPumpEvents::pump_app_events - fn as_raw_fd(&self) -> RawFd { - self.event_loop.as_raw_fd() + /// In most cases, this is set to [`WaitUntil`]. However, if the timeout overflows, it is + /// instead set to [`Wait`]. + /// + /// [`WaitUntil`]: Self::WaitUntil + /// [`Wait`]: Self::Wait + pub fn wait_duration(timeout: Duration) -> Self { + match Instant::now().checked_add(timeout) { + Some(instant) => Self::WaitUntil(instant), + None => Self::Wait, + } } } diff --git a/src/lib.rs b/src/lib.rs index 2a11bd6e3..d5ba9f790 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -26,8 +26,8 @@ //! Some user activity, like mouse movement, can generate both a [`WindowEvent`] *and* a //! [`DeviceEvent`]. //! -//! You can retrieve events by calling [`EventLoop::run_app()`]. This function will dispatch events -//! for every [`Window`] that was created with that particular [`EventLoop`]. +//! You can retrieve events by calling [`EventLoopProvider::run_app()`]. This function will dispatch +//! events for every [`Window`] that was created with that particular [`EventLoop`]. //! //! Winit no longer uses a `EventLoop::poll_events() -> impl Iterator`-based event loop //! model, since that can't be implemented properly on some platforms (e.g Web, iOS) and works @@ -47,7 +47,7 @@ //! ```no_run //! use winit::application::ApplicationHandler; //! use winit::event::WindowEvent; -//! use winit::event_loop::{ActiveEventLoop, ControlFlow, EventLoop}; +//! use winit::event_loop::{ActiveEventLoop, ControlFlow, EventLoop, EventLoopProvider}; //! use winit::window::{Window, WindowId, WindowAttributes}; //! //! #[derive(Default)] @@ -69,7 +69,7 @@ //! id: WindowId, //! event: WindowEvent, //! ) { -//! // Called by `EventLoop::run_app` when a new event happens on the window. +//! // Called by `EventLoopProvider::run_app` when a new event happens on the window. //! match event { //! WindowEvent::CloseRequested => { //! println!("The close button was pressed; stopping"); @@ -265,7 +265,7 @@ //! //! [`EventLoop`]: event_loop::EventLoop //! [`EventLoop::new()`]: event_loop::EventLoop::new -//! [`EventLoop::run_app()`]: event_loop::EventLoop::run_app +//! [`EventLoopProvider::run_app()`]: event_loop::EventLoopProvider::run_app //! [`exit()`]: event_loop::ActiveEventLoop::exit //! [`Window`]: window::Window //! [`WindowId`]: window::WindowId diff --git a/src/platform/event_loop.rs b/src/platform/event_loop.rs new file mode 100644 index 000000000..9a399a422 --- /dev/null +++ b/src/platform/event_loop.rs @@ -0,0 +1,193 @@ +use std::marker::PhantomData; +#[cfg(any(x11_platform, wayland_platform))] +use std::os::unix::io::{AsFd, AsRawFd, BorrowedFd, RawFd}; +use std::sync::atomic::{AtomicBool, Ordering}; + +use rwh_06::{DisplayHandle, HandleError, HasDisplayHandle}; + +use crate::application::ApplicationHandler; +use crate::cursor::{CustomCursor, CustomCursorSource}; +use crate::error::{EventLoopError, RequestError}; +use crate::event_loop::{ + ControlFlow, DeviceEvents, EventLoopProvider, EventLoopProxy, OwnedDisplayHandle, +}; +use crate::platform_impl; + +/// Provides a way to retrieve events from the system and from the windows that were registered to +/// the events loop. +/// +/// An `EventLoop` can be seen more or less as a "context". Calling [`EventLoop::new`] +/// initializes everything that will be required to create windows. For example on Linux creating +/// an event loop opens a connection to the X or Wayland server. +/// +/// To wake up an `EventLoop` from a another thread, see the [`EventLoopProxy`] docs. +/// +/// Note that this cannot be shared across threads (due to platform-dependant logic +/// forbidding it), as such it is neither [`Send`] nor [`Sync`]. If you need cross-thread access, +/// the [`Window`] created from this _can_ be sent to an other thread, and the +/// [`EventLoopProxy`] allows you to wake up an `EventLoop` from another thread. +/// +/// [`Window`]: crate::window::Window +#[derive(Debug)] +pub struct EventLoop { + pub(crate) event_loop: platform_impl::EventLoop, + pub(crate) _marker: PhantomData<*mut ()>, // Not Send nor Sync +} + +impl EventLoop { + /// Create the event loop. + /// + /// This is an alias of `EventLoop::builder().build()`. + #[inline] + pub fn new() -> Result { + Self::builder().build() + } + + /// Start building a new event loop. + /// + /// This returns an [`EventLoopBuilder`], to allow configuring the event loop before creation. + /// + /// To get the actual event loop, call [`build`][EventLoopBuilder::build] on that. + #[inline] + pub fn builder() -> EventLoopBuilder { + EventLoopBuilder { platform_specific: Default::default() } + } +} + +impl EventLoopProvider for EventLoop { + #[cfg(not(all(web_platform, target_feature = "exception-handling")))] + fn run_app(self, app: impl ApplicationHandler) -> Result<(), EventLoopError> { + self.event_loop.run_app(app) + } + + fn create_proxy(&self) -> EventLoopProxy { + self.event_loop.window_target().create_proxy() + } + + fn owned_display_handle(&self) -> OwnedDisplayHandle { + self.event_loop.window_target().owned_display_handle() + } + + fn listen_device_events(&self, allowed: DeviceEvents) { + let _span = tracing::debug_span!( + "winit::EventLoop::listen_device_events", + allowed = ?allowed + ) + .entered(); + self.event_loop.window_target().listen_device_events(allowed) + } + + fn set_control_flow(&self, control_flow: ControlFlow) { + self.event_loop.window_target().set_control_flow(control_flow); + } + + fn create_custom_cursor( + &self, + custom_cursor: CustomCursorSource, + ) -> Result { + self.event_loop.window_target().create_custom_cursor(custom_cursor) + } +} + +impl HasDisplayHandle for EventLoop { + fn display_handle(&self) -> Result, HandleError> { + HasDisplayHandle::display_handle(self.event_loop.window_target().rwh_06_handle()) + } +} + +#[cfg(any(x11_platform, wayland_platform))] +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_app_events`] API. + /// + /// [`calloop`]: https://crates.io/crates/calloop + /// [`mio`]: https://crates.io/crates/mio + /// [`pump_app_events`]: crate::platform::pump_events::EventLoopExtPumpEvents::pump_app_events + fn as_fd(&self) -> BorrowedFd<'_> { + self.event_loop.as_fd() + } +} + +#[cfg(any(x11_platform, wayland_platform))] +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_app_events`] API. + /// + /// [`calloop`]: https://crates.io/crates/calloop + /// [`mio`]: https://crates.io/crates/mio + /// [`pump_app_events`]: crate::platform::pump_events::EventLoopExtPumpEvents::pump_app_events + fn as_raw_fd(&self) -> RawFd { + self.event_loop.as_raw_fd() + } +} + +/// Object that allows building the event loop. +/// +/// This is used to make specifying options that affect the whole application +/// easier. But note that constructing multiple event loops is not supported. +/// +/// This can be created using [`EventLoop::builder`]. +#[derive(Default, Debug, PartialEq, Eq, Hash)] +pub struct EventLoopBuilder { + pub(crate) platform_specific: platform_impl::PlatformSpecificEventLoopAttributes, +} + +static EVENT_LOOP_CREATED: AtomicBool = AtomicBool::new(false); + +impl EventLoopBuilder { + /// Builds a new event loop. + /// + /// ***For cross-platform compatibility, the [`EventLoop`] must be created on the main thread, + /// and only once per application.*** + /// + /// Calling this function will result in display backend initialisation. + /// + /// ## Panics + /// + /// Attempting to create the event loop off the main thread will panic. This + /// restriction isn't strictly necessary on all platforms, but is imposed to + /// eliminate any nasty surprises when porting to platforms that require it. + /// `EventLoopBuilderExt::with_any_thread` functions are exposed in the relevant + /// [`platform`] module if the target platform supports creating an event + /// loop on any thread. + /// + /// ## Platform-specific + /// + /// - **Wayland/X11:** to prevent running under `Wayland` or `X11` unset `WAYLAND_DISPLAY` or + /// `DISPLAY` respectively when building the event loop. + /// - **Android:** must be configured with an `AndroidApp` from `android_main()` by calling + /// [`.with_android_app(app)`] before calling `.build()`, otherwise it'll panic. + /// + /// [`platform`]: crate::platform + #[cfg_attr( + android_platform, + doc = "[`.with_android_app(app)`]: \ + crate::platform::android::EventLoopBuilderExtAndroid::with_android_app" + )] + #[cfg_attr( + not(android_platform), + doc = "[`.with_android_app(app)`]: #only-available-on-android" + )] + #[inline] + pub fn build(&mut self) -> Result { + let _span = tracing::debug_span!("winit::EventLoopBuilder::build").entered(); + + if EVENT_LOOP_CREATED.swap(true, Ordering::Relaxed) { + return Err(EventLoopError::RecreationAttempt); + } + + // Certain platforms accept a mutable reference in their API. + #[allow(clippy::unnecessary_mut_passed)] + Ok(EventLoop { + event_loop: platform_impl::EventLoop::new(&mut self.platform_specific)?, + _marker: PhantomData, + }) + } + + #[cfg(web_platform)] + pub(crate) fn allow_event_loop_recreation() { + EVENT_LOOP_CREATED.store(false, Ordering::Relaxed); + } +} diff --git a/src/platform/mod.rs b/src/platform/mod.rs index baa7de802..ef0b8fdbc 100644 --- a/src/platform/mod.rs +++ b/src/platform/mod.rs @@ -44,3 +44,5 @@ pub mod pump_events; #[cfg(any(windows_platform, macos_platform, x11_platform, wayland_platform, docsrs))] pub mod scancode; + +pub mod event_loop; diff --git a/src/platform/pump_events.rs b/src/platform/pump_events.rs index 2932284ca..e0137c075 100644 --- a/src/platform/pump_events.rs +++ b/src/platform/pump_events.rs @@ -99,18 +99,20 @@ 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( + fn pump_app_events( &mut self, timeout: Option, - app: A, - ) -> PumpStatus; + app: impl ApplicationHandler, + ) -> PumpStatus + where + Self: Sized; } impl EventLoopExtPumpEvents for EventLoop { - fn pump_app_events( + fn pump_app_events( &mut self, timeout: Option, - app: A, + app: impl ApplicationHandler, ) -> PumpStatus { self.event_loop.pump_app_events(timeout, app) } diff --git a/src/platform/run_on_demand.rs b/src/platform/run_on_demand.rs index e24aa7d80..994666d29 100644 --- a/src/platform/run_on_demand.rs +++ b/src/platform/run_on_demand.rs @@ -10,8 +10,8 @@ use crate::{ pub trait EventLoopExtRunOnDemand { /// 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 + /// Unlike [`EventLoopProvider::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. /// @@ -40,8 +40,8 @@ pub trait EventLoopExtRunOnDemand { /// [^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_app()`] for portability, unless you - /// specifically need the ability to re-run a single event loop more than once + /// You are strongly encouraged to use [`EventLoopProvider::run_app()`] for portability, unless + /// you specifically need the ability to re-run a single event loop more than once /// /// # Supported Platforms /// - Windows @@ -60,11 +60,14 @@ pub trait EventLoopExtRunOnDemand { /// /// [`exit()`]: ActiveEventLoop::exit() /// [`set_control_flow()`]: ActiveEventLoop::set_control_flow() - fn run_app_on_demand(&mut self, app: A) -> Result<(), EventLoopError>; + /// [`EventLoopProvider::run_app()`]: crate::event_loop::EventLoopProvider::run_app + fn run_app_on_demand(&mut self, app: impl ApplicationHandler) -> Result<(), EventLoopError> + where + Self: Sized; } impl EventLoopExtRunOnDemand for EventLoop { - fn run_app_on_demand(&mut self, app: A) -> Result<(), EventLoopError> { + fn run_app_on_demand(&mut self, app: impl ApplicationHandler) -> Result<(), EventLoopError> { self.event_loop.run_app_on_demand(app) } } diff --git a/src/platform/web.rs b/src/platform/web.rs index db7387306..28b4572fa 100644 --- a/src/platform/web.rs +++ b/src/platform/web.rs @@ -200,7 +200,7 @@ pub trait EventLoopExtWeb { /// #[cfg_attr( not(all(web_platform, target_feature = "exception-handling")), - doc = "[`run_app()`]: EventLoop::run_app()" + doc = "[`run_app()`]: crate::event_loop::EventLoopProvider::run_app()" )] /// [^1]: `run_app()` is _not_ available on Wasm when the target supports `exception-handling`. fn spawn_app(self, app: A);