mirror of
https://github.com/rust-windowing/winit.git
synced 2026-06-26 22:53:15 -04:00
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.
This commit is contained in:
@@ -19,7 +19,7 @@ use winit::application::ApplicationHandler;
|
|||||||
use winit::dpi::{LogicalSize, PhysicalPosition, PhysicalSize};
|
use winit::dpi::{LogicalSize, PhysicalPosition, PhysicalSize};
|
||||||
use winit::error::RequestError;
|
use winit::error::RequestError;
|
||||||
use winit::event::{DeviceEvent, DeviceId, Ime, MouseButton, MouseScrollDelta, WindowEvent};
|
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::icon::RgbaIcon;
|
||||||
use winit::keyboard::{Key, ModifiersState};
|
use winit::keyboard::{Key, ModifiersState};
|
||||||
use winit::monitor::Fullscreen;
|
use winit::monitor::Fullscreen;
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ fn main() -> Result<(), impl std::error::Error> {
|
|||||||
use winit::application::ApplicationHandler;
|
use winit::application::ApplicationHandler;
|
||||||
use winit::dpi::{LogicalPosition, LogicalSize, Position};
|
use winit::dpi::{LogicalPosition, LogicalSize, Position};
|
||||||
use winit::event::{ElementState, KeyEvent, WindowEvent};
|
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::raw_window_handle::HasRawWindowHandle;
|
||||||
use winit::window::{Window, WindowAttributes, WindowId};
|
use winit::window::{Window, WindowAttributes, WindowId};
|
||||||
|
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ use ::tracing::{info, warn};
|
|||||||
use web_time as time;
|
use web_time as time;
|
||||||
use winit::application::ApplicationHandler;
|
use winit::application::ApplicationHandler;
|
||||||
use winit::event::{ElementState, KeyEvent, StartCause, WindowEvent};
|
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::keyboard::{Key, NamedKey};
|
||||||
use winit::window::{Window, WindowAttributes, WindowId};
|
use winit::window::{Window, WindowAttributes, WindowId};
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ use std::error::Error;
|
|||||||
|
|
||||||
use winit::application::ApplicationHandler;
|
use winit::application::ApplicationHandler;
|
||||||
use winit::event::WindowEvent;
|
use winit::event::WindowEvent;
|
||||||
use winit::event_loop::{ActiveEventLoop, EventLoop};
|
use winit::event_loop::{ActiveEventLoop, EventLoop, EventLoopProvider};
|
||||||
use winit::window::{Window, WindowAttributes, WindowId};
|
use winit::window::{Window, WindowAttributes, WindowId};
|
||||||
|
|
||||||
#[path = "util/fill.rs"]
|
#[path = "util/fill.rs"]
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ use std::error::Error;
|
|||||||
|
|
||||||
use winit::application::ApplicationHandler;
|
use winit::application::ApplicationHandler;
|
||||||
use winit::event::WindowEvent;
|
use winit::event::WindowEvent;
|
||||||
use winit::event_loop::{ActiveEventLoop, EventLoop};
|
use winit::event_loop::{ActiveEventLoop, EventLoop, EventLoopProvider};
|
||||||
#[cfg(web_platform)]
|
#[cfg(web_platform)]
|
||||||
use winit::platform::web::WindowAttributesExtWeb;
|
use winit::platform::web::WindowAttributesExtWeb;
|
||||||
use winit::window::{Window, WindowAttributes, WindowId};
|
use winit::window::{Window, WindowAttributes, WindowId};
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ use std::error::Error;
|
|||||||
fn main() -> Result<(), Box<dyn Error>> {
|
fn main() -> Result<(), Box<dyn Error>> {
|
||||||
use winit::application::ApplicationHandler;
|
use winit::application::ApplicationHandler;
|
||||||
use winit::event::WindowEvent;
|
use winit::event::WindowEvent;
|
||||||
use winit::event_loop::{ActiveEventLoop, EventLoop};
|
use winit::event_loop::{ActiveEventLoop, EventLoop, EventLoopProvider};
|
||||||
use winit::platform::x11::WindowAttributesExtX11;
|
use winit::platform::x11::WindowAttributesExtX11;
|
||||||
use winit::window::{Window, WindowAttributes, WindowId};
|
use winit::window::{Window, WindowAttributes, WindowId};
|
||||||
|
|
||||||
|
|||||||
@@ -8,14 +8,14 @@ use crate::window::WindowId;
|
|||||||
|
|
||||||
/// The handler of application-level events.
|
/// The handler of application-level events.
|
||||||
///
|
///
|
||||||
/// See [the top-level docs] for example usage, and [`EventLoop::run_app`] for an overview of when
|
/// See [the top-level docs] for example usage, and [`EventLoopProvider::run_app`] for an overview
|
||||||
/// events are delivered.
|
/// of when events are delivered.
|
||||||
///
|
///
|
||||||
/// This is [dropped] when the event loop is shut down. Note that this only works if you're passing
|
/// 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
|
/// [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
|
/// [dropped]: std::ops::Drop
|
||||||
pub trait ApplicationHandler {
|
pub trait ApplicationHandler {
|
||||||
/// Emitted when new events arrive from the OS to be processed.
|
/// Emitted when new events arrive from the OS to be processed.
|
||||||
@@ -136,7 +136,7 @@ pub trait ApplicationHandler {
|
|||||||
/// use std::time::Duration;
|
/// use std::time::Duration;
|
||||||
///
|
///
|
||||||
/// use winit::application::ApplicationHandler;
|
/// use winit::application::ApplicationHandler;
|
||||||
/// use winit::event_loop::{ActiveEventLoop, EventLoop};
|
/// use winit::event_loop::{ActiveEventLoop, EventLoop, EventLoopProvider};
|
||||||
///
|
///
|
||||||
/// struct MyApp {
|
/// struct MyApp {
|
||||||
/// receiver: mpsc::Receiver<u64>,
|
/// receiver: mpsc::Receiver<u64>,
|
||||||
@@ -210,9 +210,9 @@ pub trait ApplicationHandler {
|
|||||||
|
|
||||||
/// Emitted when the OS sends an event to a device.
|
/// 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(
|
fn device_event(
|
||||||
&mut self,
|
&mut self,
|
||||||
event_loop: &dyn ActiveEventLoop,
|
event_loop: &dyn ActiveEventLoop,
|
||||||
|
|||||||
@@ -195,6 +195,10 @@ changelog entry.
|
|||||||
- Renamed "super" key to "meta", to match the naming in the W3C specification.
|
- 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.
|
`NamedKey::Super` still exists, but it's non-functional and deprecated, `NamedKey::Meta` should be used instead.
|
||||||
- Move `IconExtWindows` into `WinIcon`.
|
- 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<dyn ApplicationHandler` instead of owned generic.
|
||||||
|
|
||||||
### Removed
|
### Removed
|
||||||
|
|
||||||
|
|||||||
@@ -9,10 +9,7 @@
|
|||||||
//! See the root-level documentation for information on how to create and use an event loop to
|
//! See the root-level documentation for information on how to create and use an event loop to
|
||||||
//! handle events.
|
//! handle events.
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
use std::marker::PhantomData;
|
use std::sync::atomic::{AtomicUsize, Ordering};
|
||||||
#[cfg(any(x11_platform, wayland_platform))]
|
|
||||||
use std::os::unix::io::{AsFd, AsRawFd, BorrowedFd, RawFd};
|
|
||||||
use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
|
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
#[cfg(not(web_platform))]
|
#[cfg(not(web_platform))]
|
||||||
use std::time::{Duration, Instant};
|
use std::time::{Duration, Instant};
|
||||||
@@ -24,166 +21,12 @@ use web_time::{Duration, Instant};
|
|||||||
use crate::application::ApplicationHandler;
|
use crate::application::ApplicationHandler;
|
||||||
use crate::error::{EventLoopError, RequestError};
|
use crate::error::{EventLoopError, RequestError};
|
||||||
use crate::monitor::MonitorHandle;
|
use crate::monitor::MonitorHandle;
|
||||||
use crate::platform_impl;
|
pub use crate::platform::event_loop::*;
|
||||||
use crate::utils::{impl_dyn_casting, AsAny};
|
use crate::utils::{impl_dyn_casting, AsAny};
|
||||||
use crate::window::{CustomCursor, CustomCursorSource, Theme, Window, WindowAttributes};
|
use crate::window::{CustomCursor, CustomCursorSource, Theme, Window, WindowAttributes};
|
||||||
|
|
||||||
/// Provides a way to retrieve events from the system and from the windows that were registered to
|
/// Common interface to describe event loop.
|
||||||
/// the events loop.
|
pub trait EventLoopProvider: AsAny + fmt::Debug {
|
||||||
///
|
|
||||||
/// 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
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 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<EventLoop, EventLoopError> {
|
|
||||||
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<EventLoop, EventLoopError> {
|
|
||||||
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 {
|
|
||||||
/// Run the application with the event loop on the calling thread.
|
/// Run the application with the event loop on the calling thread.
|
||||||
///
|
///
|
||||||
/// ## Event loop flow
|
/// ## Event loop flow
|
||||||
@@ -262,88 +105,84 @@ impl EventLoop {
|
|||||||
///
|
///
|
||||||
/// [`set_control_flow()`]: ActiveEventLoop::set_control_flow()
|
/// [`set_control_flow()`]: ActiveEventLoop::set_control_flow()
|
||||||
/// [`run_app()`]: Self::run_app()
|
/// [`run_app()`]: Self::run_app()
|
||||||
#[inline]
|
fn run_app(self, app: impl ApplicationHandler) -> Result<(), EventLoopError>
|
||||||
#[cfg(not(all(web_platform, target_feature = "exception-handling")))]
|
where
|
||||||
pub fn run_app<A: ApplicationHandler>(self, app: A) -> Result<(), EventLoopError> {
|
Self: Sized;
|
||||||
self.event_loop.run_app(app)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Creates an [`EventLoopProxy`] that can be used to dispatch user events
|
/// Creates an [`EventLoopProxy`] that can be used to dispatch user events
|
||||||
/// to the main event loop, possibly from another thread.
|
/// to the main event loop, possibly from another thread.
|
||||||
pub fn create_proxy(&self) -> EventLoopProxy {
|
fn create_proxy(&self) -> EventLoopProxy;
|
||||||
self.event_loop.window_target().create_proxy()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Gets a persistent reference to the underlying platform display.
|
/// Gets a persistent reference to the underlying platform display.
|
||||||
///
|
///
|
||||||
/// See the [`OwnedDisplayHandle`] type for more information.
|
/// See the [`OwnedDisplayHandle`] type for more information.
|
||||||
pub fn owned_display_handle(&self) -> OwnedDisplayHandle {
|
fn owned_display_handle(&self) -> OwnedDisplayHandle;
|
||||||
self.event_loop.window_target().owned_display_handle()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Change if or when [`DeviceEvent`]s are captured.
|
/// Change if or when [`DeviceEvent`]s are captured.
|
||||||
///
|
///
|
||||||
/// See [`ActiveEventLoop::listen_device_events`] for details.
|
/// See [`ActiveEventLoop::listen_device_events`] for details.
|
||||||
///
|
///
|
||||||
/// [`DeviceEvent`]: crate::event::DeviceEvent
|
/// [`DeviceEvent`]: crate::event::DeviceEvent
|
||||||
pub fn listen_device_events(&self, allowed: DeviceEvents) {
|
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)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Sets the [`ControlFlow`].
|
/// Sets the [`ControlFlow`].
|
||||||
pub fn set_control_flow(&self, control_flow: ControlFlow) {
|
fn set_control_flow(&self, control_flow: ControlFlow);
|
||||||
self.event_loop.window_target().set_control_flow(control_flow);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Create custom cursor.
|
/// Create custom cursor.
|
||||||
///
|
///
|
||||||
/// ## Platform-specific
|
/// ## Platform-specific
|
||||||
///
|
///
|
||||||
/// **iOS / Android / Orbital:** Unsupported.
|
/// **iOS / Android / Orbital:** Unsupported.
|
||||||
pub fn create_custom_cursor(
|
fn create_custom_cursor(
|
||||||
&self,
|
&self,
|
||||||
custom_cursor: CustomCursorSource,
|
custom_cursor: CustomCursorSource,
|
||||||
) -> Result<CustomCursor, RequestError> {
|
) -> Result<CustomCursor, RequestError>;
|
||||||
self.event_loop.window_target().create_custom_cursor(custom_cursor)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl HasDisplayHandle for EventLoop {
|
impl_dyn_casting!(EventLoopProvider);
|
||||||
fn display_handle(&self) -> Result<DisplayHandle<'_>, HandleError> {
|
|
||||||
HasDisplayHandle::display_handle(self.event_loop.window_target().rwh_06_handle())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(any(x11_platform, wayland_platform))]
|
/// Set through [`ActiveEventLoop::set_control_flow()`].
|
||||||
impl AsFd for EventLoop {
|
///
|
||||||
/// Get the underlying [EventLoop]'s `fd` which you can register
|
/// Indicates the desired behavior of the event loop after [`about_to_wait`] is called.
|
||||||
/// into other event loop, like [`calloop`] or [`mio`]. When doing so, the
|
///
|
||||||
/// loop must be polled with the [`pump_app_events`] API.
|
/// 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
|
/// Useful for implementing efficient timers. Applications which want to render at the
|
||||||
/// [`mio`]: https://crates.io/crates/mio
|
/// display's native refresh rate should instead use [`Poll`] and the VSync functionality
|
||||||
/// [`pump_app_events`]: crate::platform::pump_events::EventLoopExtPumpEvents::pump_app_events
|
/// of a graphics API to reduce odds of missed frames.
|
||||||
fn as_fd(&self) -> BorrowedFd<'_> {
|
///
|
||||||
self.event_loop.as_fd()
|
/// [`Poll`]: Self::Poll
|
||||||
}
|
WaitUntil(Instant),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(any(x11_platform, wayland_platform))]
|
impl ControlFlow {
|
||||||
impl AsRawFd for EventLoop {
|
/// Creates a [`ControlFlow`] that waits until a timeout has expired.
|
||||||
/// 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
|
/// In most cases, this is set to [`WaitUntil`]. However, if the timeout overflows, it is
|
||||||
/// [`mio`]: https://crates.io/crates/mio
|
/// instead set to [`Wait`].
|
||||||
/// [`pump_app_events`]: crate::platform::pump_events::EventLoopExtPumpEvents::pump_app_events
|
///
|
||||||
fn as_raw_fd(&self) -> RawFd {
|
/// [`WaitUntil`]: Self::WaitUntil
|
||||||
self.event_loop.as_raw_fd()
|
/// [`Wait`]: Self::Wait
|
||||||
|
pub fn wait_duration(timeout: Duration) -> Self {
|
||||||
|
match Instant::now().checked_add(timeout) {
|
||||||
|
Some(instant) => Self::WaitUntil(instant),
|
||||||
|
None => Self::Wait,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
10
src/lib.rs
10
src/lib.rs
@@ -26,8 +26,8 @@
|
|||||||
//! 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`].
|
//! [`DeviceEvent`].
|
||||||
//!
|
//!
|
||||||
//! You can retrieve events by calling [`EventLoop::run_app()`]. This function will dispatch events
|
//! You can retrieve events by calling [`EventLoopProvider::run_app()`]. This function will dispatch
|
||||||
//! for every [`Window`] that was created with that particular [`EventLoop`].
|
//! events for every [`Window`] that was created with that particular [`EventLoop`].
|
||||||
//!
|
//!
|
||||||
//! Winit no longer uses a `EventLoop::poll_events() -> impl Iterator<Event>`-based event loop
|
//! Winit no longer uses a `EventLoop::poll_events() -> impl Iterator<Event>`-based event loop
|
||||||
//! model, since that can't be implemented properly on some platforms (e.g Web, iOS) and works
|
//! model, since that can't be implemented properly on some platforms (e.g Web, iOS) and works
|
||||||
@@ -47,7 +47,7 @@
|
|||||||
//! ```no_run
|
//! ```no_run
|
||||||
//! use winit::application::ApplicationHandler;
|
//! use winit::application::ApplicationHandler;
|
||||||
//! use winit::event::WindowEvent;
|
//! 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};
|
//! use winit::window::{Window, WindowId, WindowAttributes};
|
||||||
//!
|
//!
|
||||||
//! #[derive(Default)]
|
//! #[derive(Default)]
|
||||||
@@ -69,7 +69,7 @@
|
|||||||
//! id: WindowId,
|
//! id: WindowId,
|
||||||
//! event: WindowEvent,
|
//! 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 {
|
//! match event {
|
||||||
//! WindowEvent::CloseRequested => {
|
//! WindowEvent::CloseRequested => {
|
||||||
//! println!("The close button was pressed; stopping");
|
//! println!("The close button was pressed; stopping");
|
||||||
@@ -265,7 +265,7 @@
|
|||||||
//!
|
//!
|
||||||
//! [`EventLoop`]: event_loop::EventLoop
|
//! [`EventLoop`]: event_loop::EventLoop
|
||||||
//! [`EventLoop::new()`]: event_loop::EventLoop::new
|
//! [`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
|
//! [`exit()`]: event_loop::ActiveEventLoop::exit
|
||||||
//! [`Window`]: window::Window
|
//! [`Window`]: window::Window
|
||||||
//! [`WindowId`]: window::WindowId
|
//! [`WindowId`]: window::WindowId
|
||||||
|
|||||||
193
src/platform/event_loop.rs
Normal file
193
src/platform/event_loop.rs
Normal file
@@ -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<EventLoop, EventLoopError> {
|
||||||
|
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<CustomCursor, RequestError> {
|
||||||
|
self.event_loop.window_target().create_custom_cursor(custom_cursor)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl HasDisplayHandle for EventLoop {
|
||||||
|
fn display_handle(&self) -> Result<DisplayHandle<'_>, 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<EventLoop, EventLoopError> {
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -44,3 +44,5 @@ pub mod pump_events;
|
|||||||
|
|
||||||
#[cfg(any(windows_platform, macos_platform, x11_platform, wayland_platform, docsrs))]
|
#[cfg(any(windows_platform, macos_platform, x11_platform, wayland_platform, docsrs))]
|
||||||
pub mod scancode;
|
pub mod scancode;
|
||||||
|
|
||||||
|
pub mod event_loop;
|
||||||
|
|||||||
@@ -99,18 +99,20 @@ 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>(
|
fn pump_app_events(
|
||||||
&mut self,
|
&mut self,
|
||||||
timeout: Option<Duration>,
|
timeout: Option<Duration>,
|
||||||
app: A,
|
app: impl ApplicationHandler,
|
||||||
) -> PumpStatus;
|
) -> PumpStatus
|
||||||
|
where
|
||||||
|
Self: Sized;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl EventLoopExtPumpEvents for EventLoop {
|
impl EventLoopExtPumpEvents for EventLoop {
|
||||||
fn pump_app_events<A: ApplicationHandler>(
|
fn pump_app_events(
|
||||||
&mut self,
|
&mut self,
|
||||||
timeout: Option<Duration>,
|
timeout: Option<Duration>,
|
||||||
app: A,
|
app: impl ApplicationHandler,
|
||||||
) -> PumpStatus {
|
) -> PumpStatus {
|
||||||
self.event_loop.pump_app_events(timeout, app)
|
self.event_loop.pump_app_events(timeout, app)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,8 +10,8 @@ use crate::{
|
|||||||
pub trait EventLoopExtRunOnDemand {
|
pub trait EventLoopExtRunOnDemand {
|
||||||
/// Run the application with the event loop on the calling thread.
|
/// Run the application with the event loop on the calling thread.
|
||||||
///
|
///
|
||||||
/// Unlike [`EventLoop::run_app`], this function accepts non-`'static` (i.e. non-`move`)
|
/// Unlike [`EventLoopProvider::run_app()`], this function accepts non-`'static` (i.e.
|
||||||
/// closures and it is possible to return control back to the caller without
|
/// non-`move`) closures 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.
|
||||||
///
|
///
|
||||||
@@ -40,8 +40,8 @@ pub trait EventLoopExtRunOnDemand {
|
|||||||
/// [^1] more than once instead).
|
/// [^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_app()`] for portability, unless you
|
/// You are strongly encouraged to use [`EventLoopProvider::run_app()`] for portability, unless
|
||||||
/// specifically need the ability to re-run a single event loop more than once
|
/// you specifically need the ability to re-run a single event loop more than once
|
||||||
///
|
///
|
||||||
/// # Supported Platforms
|
/// # Supported Platforms
|
||||||
/// - Windows
|
/// - Windows
|
||||||
@@ -60,11 +60,14 @@ 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_app_on_demand<A: ApplicationHandler>(&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 {
|
impl EventLoopExtRunOnDemand for EventLoop {
|
||||||
fn run_app_on_demand<A: ApplicationHandler>(&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)
|
self.event_loop.run_app_on_demand(app)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -200,7 +200,7 @@ pub trait EventLoopExtWeb {
|
|||||||
///
|
///
|
||||||
#[cfg_attr(
|
#[cfg_attr(
|
||||||
not(all(web_platform, target_feature = "exception-handling")),
|
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`.
|
/// [^1]: `run_app()` is _not_ available on Wasm when the target supports `exception-handling`.
|
||||||
fn spawn_app<A: ApplicationHandler + 'static>(self, app: A);
|
fn spawn_app<A: ApplicationHandler + 'static>(self, app: A);
|
||||||
|
|||||||
Reference in New Issue
Block a user