diff --git a/deny.toml b/deny.toml index c789555924..337413f3af 100644 --- a/deny.toml +++ b/deny.toml @@ -51,6 +51,10 @@ allow = [ ] crate = "android-activity" +[[bans.build.bypass]] +allow-globs = ["ci/*", "githooks/*"] +crate = "zerocopy" + [[bans.build.bypass]] allow-globs = ["freetype2/*"] crate = "freetype-sys" diff --git a/examples/application.rs b/examples/application.rs index 931c83665f..83f4b6835c 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 e87207828b..2a9473233c 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 8fb09bb3f0..050569c23b 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 51d1ee4501..3a5cf48d9c 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 b6077270b1..c3da7554bd 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 e600a5d052..f7eb9fddd7 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 0020ca9929..5018543ae1 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 d56b693180..1eabf279c9 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. - /// - /// [`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() - } +/// 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), } -#[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() +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, + } } } diff --git a/src/lib.rs b/src/lib.rs index 2a11bd6e31..d5ba9f7903 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 0000000000..9a399a4221 --- /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 baa7de8026..ef0b8fdbc3 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 2932284ca4..e0137c0758 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 e24aa7d808..994666d295 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 db7387306a..28b4572faa 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);