wingy_core/event_loop/mod.rs
1pub mod never_return;
2pub mod primary_selection;
3pub mod pump_events;
4pub mod register;
5pub mod run_on_demand;
6
7use std::any::Any;
8use std::fmt::{self, Debug};
9use std::sync::Arc;
10use std::sync::atomic::{AtomicUsize, Ordering};
11use std::time::Duration;
12
13use rwh_06::{DisplayHandle, HandleError, HasDisplayHandle};
14
15use crate::Instant;
16use crate::application::ApplicationHandler;
17use crate::cursor::{CustomCursor, CustomCursorSource};
18use crate::data_transfer::{DataTransfer, DataTransferId, DataTransferSend, TransferType};
19use crate::error::{
20 CreateWindowError, CustomCursorError, EventLoopError, NotSupportedError, TransferError,
21};
22use crate::icon::Icon;
23use crate::monitor::MonitorHandle;
24use crate::window::{Theme, Window, WindowAttributes, WindowId};
25
26/// Common methods to implement for the platform event loop.
27pub trait EventLoopProvider: fmt::Debug {
28 /// Run the event loop with the given application on the calling thread.
29 ///
30 /// The `app` is dropped when the event loop is shut down.
31 ///
32 /// ## Event loop flow
33 ///
34 /// This function internally handles the different parts of a traditional event-handling loop.
35 /// You can imagine this method as being implemented like this:
36 ///
37 /// ```rust,ignore
38 /// let mut start_cause = StartCause::Init;
39 ///
40 /// // Run the event loop.
41 /// while !event_loop.exiting() {
42 /// // Wake up.
43 /// app.new_events(event_loop, start_cause);
44 ///
45 /// // Indicate that surfaces can now safely be created.
46 /// if start_cause == StartCause::Init {
47 /// app.can_create_surfaces(event_loop);
48 /// }
49 ///
50 /// // Handle proxy wake-up event.
51 /// if event_loop.proxy_wake_up_set() {
52 /// event_loop.proxy_wake_up_clear();
53 /// app.proxy_wake_up(event_loop);
54 /// }
55 ///
56 /// // Handle actions done by the user / system such as moving the cursor, resizing the
57 /// // window, changing the window theme, etc.
58 /// for event in event_loop.events() {
59 /// match event {
60 /// window event => app.window_event(event_loop, window_id, event),
61 /// device event => app.device_event(event_loop, device_id, event),
62 /// }
63 /// }
64 ///
65 /// // Handle redraws.
66 /// for window_id in event_loop.pending_redraws() {
67 /// app.window_event(event_loop, window_id, WindowEvent::RedrawRequested);
68 /// }
69 ///
70 /// // Done handling events, wait until we're woken up again.
71 /// app.about_to_wait(event_loop);
72 /// start_cause = event_loop.wait_if_necessary();
73 /// }
74 ///
75 /// // Finished running, drop application state.
76 /// drop(app);
77 /// ```
78 ///
79 /// This is of course a very coarse-grained overview, and leaves out timing details like
80 /// [`ControlFlow::WaitUntil`] and life-cycle methods like [`ApplicationHandler::resumed`], but
81 /// it should give you an idea of how things fit together.
82 ///
83 /// ## Returns
84 ///
85 /// The semantics of this function is defined by the target platform. Consult the implementor
86 /// docs for details.
87 fn run_app<A: ApplicationHandler + 'static>(self, app: A) -> Result<(), EventLoopError>;
88
89 /// Creates an [`EventLoopProxy`] that can be used to dispatch user events
90 /// to the main event loop, possibly from another thread.
91 fn create_proxy(&self) -> EventLoopProxy;
92
93 /// Gets a persistent reference to the underlying platform display.
94 ///
95 /// See the [`OwnedDisplayHandle`] type for more information.
96 fn owned_display_handle(&self) -> OwnedDisplayHandle;
97
98 /// Change if or when [`DeviceEvent`]s are captured.
99 ///
100 /// See [`ActiveEventLoop::listen_device_events`] for details.
101 ///
102 /// [`DeviceEvent`]: crate::event::DeviceEvent
103 fn listen_device_events(&self, allowed: DeviceEvents);
104
105 /// Sets the [`ControlFlow`].
106 fn set_control_flow(&self, control_flow: ControlFlow);
107
108 /// Create custom cursor.
109 fn create_custom_cursor(
110 &self,
111 custom_cursor: CustomCursorSource,
112 ) -> Result<CustomCursor, CustomCursorError>;
113}
114
115pub trait ActiveEventLoop: Any + fmt::Debug {
116 /// Creates an [`EventLoopProxy`] that can be used to dispatch user events
117 /// to the main event loop, possibly from another thread.
118 fn create_proxy(&self) -> EventLoopProxy;
119
120 /// Create the window.
121 ///
122 /// Possible causes of error include denied permission, incompatible system, and lack of memory.
123 ///
124 /// ## Platform-specific
125 ///
126 /// - **Web:** The window is created but not inserted into the Web page automatically. Please
127 /// see the Web platform module for more information.
128 fn create_window(
129 &self,
130 window_attributes: WindowAttributes,
131 ) -> Result<Box<dyn Window>, CreateWindowError>;
132
133 /// Create custom cursor.
134 ///
135 /// ## Platform-specific
136 ///
137 /// **iOS / Android / Orbital:** Unsupported.
138 fn create_custom_cursor(
139 &self,
140 custom_cursor: CustomCursorSource,
141 ) -> Result<CustomCursor, CustomCursorError>;
142
143 /// Returns the list of all the monitors available on the system.
144 ///
145 /// ## Platform-specific
146 ///
147 /// **Web:** Only returns the current monitor without `detailed monitor permissions`.
148 fn available_monitors(&self) -> Box<dyn Iterator<Item = MonitorHandle>>;
149
150 /// Returns the primary monitor of the system.
151 ///
152 /// Returns `None` if it can't identify any monitor as a primary one.
153 ///
154 /// ## Platform-specific
155 ///
156 /// - **Wayland:** Always returns `None`.
157 /// - **Web:** Always returns `None` without `detailed monitor permissions`.
158 fn primary_monitor(&self) -> Option<MonitorHandle>;
159
160 /// Change if or when [`DeviceEvent`]s are captured.
161 ///
162 /// Since the [`DeviceEvent`] capture can lead to high CPU usage for unfocused windows, wingy
163 /// will ignore them by default for unfocused windows on Linux/BSD. This method allows changing
164 /// this at runtime to explicitly capture them again.
165 ///
166 /// ## Platform-specific
167 ///
168 /// - **Wayland / macOS / iOS / Android / Orbital:** Unsupported.
169 ///
170 /// [`DeviceEvent`]: crate::event::DeviceEvent
171 fn listen_device_events(&self, allowed: DeviceEvents);
172
173 /// Returns the current system theme.
174 ///
175 /// Returns `None` if it cannot be determined on the current platform.
176 ///
177 /// ## Platform-specific
178 ///
179 /// - **iOS / Android / Wayland / x11 / Orbital:** Unsupported.
180 fn system_theme(&self) -> Option<Theme>;
181
182 /// Sets the [`ControlFlow`].
183 fn set_control_flow(&self, control_flow: ControlFlow);
184
185 /// Gets the current [`ControlFlow`].
186 fn control_flow(&self) -> ControlFlow;
187
188 /// Stop the event loop.
189 ///
190 /// ## Platform-specific
191 ///
192 /// ### iOS
193 ///
194 /// It is not possible to programmatically exit/quit an application on iOS, so this function is
195 /// a no-op there. See also [this technical Q&A][qa1561].
196 ///
197 /// [qa1561]: https://developer.apple.com/library/archive/qa/qa1561/_index.html
198 fn exit(&self);
199
200 /// Returns whether the [`ActiveEventLoop`] is about to stop.
201 ///
202 /// Set by [`exit()`][Self::exit].
203 fn exiting(&self) -> bool;
204
205 /// Gets a persistent reference to the underlying platform display.
206 ///
207 /// See the [`OwnedDisplayHandle`] type for more information.
208 fn owned_display_handle(&self) -> OwnedDisplayHandle;
209
210 /// Get the raw-window-handle handle.
211 fn rwh_06_handle(&self) -> &dyn HasDisplayHandle;
212
213 /// Request to fetch a type from a [data transfer](crate::data_transfer::DataTransfer).
214 ///
215 /// This may be called multiple times on the same [`DataTransferId`] with different types,
216 /// and may be called at any point during the drag operation, including during handling the
217 /// [`DragDropped`](crate::event::WindowEvent::DragDropped) event. After that event has been
218 /// received, though, the data transfer is not guaranteed to be available. The data is
219 /// _not_ guaranteed to be available during (or after) handling of
220 /// [`DragLeft](crate::event::WindowEvent::DragLeft).
221 ///
222 /// Once available, the data will be supplied to the application with the
223 /// [`DataTransferReceived`](crate::event::WindowEvent::DataTransferReceived) event.
224 fn fetch_data_transfer(
225 &self,
226 id: DataTransferId,
227 type_: &dyn TransferType,
228 ) -> Result<AsyncRequestSerial, TransferError> {
229 let _ = id;
230 let _ = type_;
231 Err(DATA_TRANSFER_NOT_SUPPORTED)
232 }
233
234 /// Get a [data transfer](DataTransfer) by its ID.
235 ///
236 /// If the ID is invalid (e.g. if the lifetime of the data transfer has expired), this will
237 /// return an error.
238 fn data_transfer(&self, id: DataTransferId) -> Result<Box<dyn DataTransfer>, TransferError> {
239 let _ = id;
240 Err(DATA_TRANSFER_NOT_SUPPORTED)
241 }
242
243 /// Set a given set of `DndAction`s as the valid actions for the given [`DataTransferId`],
244 /// if the transfer ID is from an incoming drag-and-drop operation.
245 ///
246 /// This allows the OS/compositor to display the correct UI, indicating that the dragged data
247 /// can be dropped. If the data transfer does not exist or is not from a drag-and-drop
248 /// operation, will return an error.
249 ///
250 /// The operating system will consider the drag either accepted or rejected based on the
251 /// set of valid actions supplied using this method, combined with the set of valid actions
252 /// on the drag source. If the drag is rejected at the point that the user finalizes the drop,
253 /// the application will receive [`DragLeft`](crate::event::WindowEvent::DragLeft) instead
254 /// of [`DragDropped`](crate::event::WindowEvent::DragDropped).
255 ///
256 /// Note that _rejecting_ the drag is not the same as _canceling_ the drag. A rejected drag can
257 /// be accepted later and the user can continue dragging it over other potential targets. On
258 /// most platforms, there is no way for an application to explicitly cancel a drag
259 /// operation.
260 ///
261 /// The set of actions is expected to be ordered by preference.
262 fn set_valid_dnd_actions(
263 &self,
264 id: DataTransferId,
265 actions: &[DndAction],
266 ) -> Result<(), TransferError> {
267 let _ = id;
268 let _ = actions;
269 Err(DATA_TRANSFER_NOT_SUPPORTED)
270 }
271
272 /// Initiate a new drag-and-drop operation.
273 ///
274 /// See [`DataTransferSendBuilder`](crate::data_transfer::DataTransferSendBuilder) for how to
275 /// create a new cross-platform data transfer, or [`DataTransferSend`] for a generic trait
276 /// which can be implemented manually.
277 ///
278 /// The [`DataTransferId`] returned from this method, identifying the outgoing drag, is
279 /// currently only used for identifying the drag in the
280 /// [`OutgoingDragDropped`](crate::event::WindowEvent::OutgoingDragDropped) event. In most
281 /// cases, a drag will be started while the mouse is over the window which started it. This
282 /// means that, directly after this method is called, the window will then receive a
283 /// [`DragEntered`](crate::event::WindowEvent::DragEntered) event. However, the ID identifying
284 /// the incoming drag is not guaranteed to be the same as the ID returned from this method.
285 ///
286 /// For most cases, applications can treat all `DragEntered` events the same, whether they were
287 /// initiated by the same application or a different application. However, if the user wants to
288 /// have some kind of special handling for internal drag-and-drop, they will currently need
289 /// to implement it via workaround. On all systems where drag-and-drop is implemented in
290 /// Wingy, the application can make the assumption that only a single drag operation can
291 /// occur at one time. Therefore, if `DragEntered` is received between calling this method
292 /// and receiving `OutgoingDragDropped`, then you can assume that it's the same drag.
293 /// In theory, Wayland allows multiple simultaneous drag operations at a time, but Wingy does
294 /// not currently guarantee that this is supported correctly for either internal or external
295 /// drag.
296 ///
297 /// ### Arguments
298 ///
299 /// - `source` - The ID of the window that initiated the drag operation.
300 /// - `send_data` - The data provided by this drag operation. See
301 /// [`DataTransferSendBuilder`](crate::data_transfer::DataTransferSendBuilder).
302 /// - `actions` - The set of valid actions for this drag operation. See [`DndAction`]. On
303 /// Wayland, this is expected to be ordered by preference.
304 /// - `icon` - The icon to show while dragging.
305 ///
306 /// Some platforms have a more-expressive way of setting the visual component of a drag
307 /// operation. For those platforms, consider using the platform-specific implementation of
308 /// [`DataTransferSend`] for `send_data` and set this field to `None`.
309 ///
310 /// ### Returns
311 ///
312 /// A unique identifier for this drag operation, which will be later supplied by
313 /// [`OutgoingDragDropped`](crate::event::WindowEvent::OutgoingDragDropped).
314 fn start_drag(
315 &self,
316 source: WindowId,
317 send_data: Box<dyn DataTransferSend>,
318 actions: &[DndAction],
319 icon: Option<DragIcon>,
320 ) -> Result<DataTransferId, TransferError> {
321 let _ = source;
322 let _ = send_data;
323 let _ = actions;
324 let _ = icon;
325 Err(DATA_TRANSFER_NOT_SUPPORTED)
326 }
327
328 /// Get the current clipboard contents as a [data transfer](crate::data_transfer), or `None`
329 /// if the clipboard is empty.
330 ///
331 /// The returned ID can be used with [`data_transfer`](Self::data_transfer) to inspect the
332 /// available types, and with [`fetch_data_transfer`](Self::fetch_data_transfer) to read the
333 /// data.
334 ///
335 /// As the clipboard is not tied to a window, the resulting
336 /// [`DataTransferReceived`](crate::event::WindowEvent::DataTransferReceived)
337 /// event is delivered to the focused window, or to an unspecified window
338 /// if focus could not be determined.
339 ///
340 /// The ID is only valid until the clipboard contents change.
341 fn clipboard(&self) -> Result<Option<DataTransferId>, TransferError> {
342 Err(DATA_TRANSFER_NOT_SUPPORTED)
343 }
344
345 /// Set the clipboard contents.
346 ///
347 /// See [`DataTransferSendBuilder`](crate::data_transfer::DataTransferSendBuilder) for how to
348 /// create a new cross-platform data transfer.
349 ///
350 /// The data is sent lazily where supported by the platform: it is only encoded when another
351 /// application requests one of the advertised types, and otherwise dropped once another
352 /// application takes over the clipboard.
353 fn set_clipboard(&self, send_data: Box<dyn DataTransferSend>) -> Result<(), TransferError> {
354 let _ = send_data;
355 Err(DATA_TRANSFER_NOT_SUPPORTED)
356 }
357
358 /// Access the primary selection platform extension, where available.
359 ///
360 /// ## Platform-specific
361 ///
362 /// - **Wayland:** Available if supported by the compositor.
363 /// - **X11:** Always available.
364 /// - **Others:** Unsupported.
365 #[inline(always)]
366 fn primary_selection_ext(&self) -> Option<&dyn primary_selection::PrimarySelectionExt> {
367 None
368 }
369}
370
371const DATA_TRANSFER_NOT_SUPPORTED: TransferError =
372 TransferError::NotSupported(NotSupportedError::new(
373 "Cross-application data transfer (e.g. drag-and-drop, clipboard) is unsupported on this \
374 platform",
375 ));
376
377impl HasDisplayHandle for dyn ActiveEventLoop + '_ {
378 fn display_handle(&self) -> Result<DisplayHandle<'_>, HandleError> {
379 self.rwh_06_handle().display_handle()
380 }
381}
382
383impl_dyn_casting!(ActiveEventLoop);
384
385/// Information needed to initiate a new drag operation.
386pub struct DragIcon {
387 /// The icon to apply to the cursor.
388 pub icon: Icon,
389 /// An x offset applied to the dragged icon.
390 ///
391 /// This is specified in image pixels. 0 means that the left side of the icon will be at
392 /// the cursor.
393 pub offset_x: i32,
394 /// A y offset applied to the dragged icon.
395 ///
396 /// This is specified in image pixels. 0 means that the top of the icon will be at the
397 /// cursor.
398 pub offset_y: i32,
399}
400
401impl From<Icon> for DragIcon {
402 fn from(value: Icon) -> Self {
403 Self { icon: value, offset_x: 0, offset_y: 0 }
404 }
405}
406
407/// The set of available actions for a drag operation.
408///
409/// This is _not_ a bitset, as on some platforms (e.g. Wayland, macOS) the source and/or destination
410/// are expected to provide some kind of order of preference.
411#[repr(u8)]
412#[derive(Debug, Clone, Copy, PartialEq)]
413#[non_exhaustive]
414pub enum DndAction {
415 /// Move the dragged item from the source to the destination.
416 ///
417 /// # Platforms
418 ///
419 /// - Wayland
420 /// - macOS
421 /// - Windows
422 Move,
423 /// Copy the dragged item from the source to the destination.
424 ///
425 /// # Platforms
426 ///
427 /// - X11
428 /// - Wayland
429 /// - macOS
430 /// - Windows
431 Copy,
432 /// A link is established between the source and the destination.
433 ///
434 /// # Platforms
435 ///
436 /// - macOS
437 /// - Windows
438 Link,
439 /// The user will be prompted for what should be done
440 ///
441 /// # Platforms
442 ///
443 /// - Wayland
444 Ask,
445 /// The source and destination will negotiate the drag operation privately
446 ///
447 /// # Platforms
448 ///
449 /// - macOS
450 Private,
451}
452
453/// Control the [`ActiveEventLoop`], possibly from a different thread, without referencing it
454/// directly.
455#[derive(Clone, Debug)]
456pub struct EventLoopProxy {
457 pub(crate) proxy: Arc<dyn EventLoopProxyProvider>,
458}
459
460impl EventLoopProxy {
461 /// Wake up the [`ActiveEventLoop`], resulting in [`ApplicationHandler::proxy_wake_up()`] being
462 /// called.
463 ///
464 /// Calls to this method are coalesced into a single call to [`proxy_wake_up`], see the
465 /// documentation on that for details.
466 ///
467 /// If the event loop is no longer running, this is a no-op.
468 ///
469 /// [`proxy_wake_up`]: crate::application::ApplicationHandler::proxy_wake_up
470 /// [`ApplicationHandler::proxy_wake_up()`]: crate::application::ApplicationHandler::proxy_wake_up
471 ///
472 /// # Platform-specific
473 ///
474 /// - **Windows**: The wake-up may be ignored under high contention, see [#3687].
475 ///
476 /// [#3687]: https://github.com/rust-windowing/winit/pull/3687
477 pub fn wake_up(&self) {
478 self.proxy.wake_up();
479 }
480
481 pub fn new(proxy: Arc<dyn EventLoopProxyProvider>) -> Self {
482 Self { proxy }
483 }
484}
485
486pub trait EventLoopProxyProvider: Send + Sync + Debug {
487 /// See [`EventLoopProxy::wake_up`] for details.
488 fn wake_up(&self);
489}
490
491/// A proxy for the underlying display handle.
492///
493/// The purpose of this type is to provide a cheaply cloneable handle to the underlying
494/// display handle. This is often used by graphics APIs to connect to the underlying APIs.
495/// It is difficult to keep a handle to the underlying event loop type or the [`ActiveEventLoop`]
496/// type. In contrast, this type involves no lifetimes and can be persisted for as long as
497/// needed.
498///
499/// For all platforms, this is one of the following:
500///
501/// - A zero-sized type that is likely optimized out.
502/// - A reference-counted pointer to the underlying type.
503#[derive(Clone)]
504pub struct OwnedDisplayHandle {
505 pub(crate) handle: Arc<dyn HasDisplayHandle + Send + Sync>,
506}
507
508impl OwnedDisplayHandle {
509 pub fn new(handle: Arc<dyn HasDisplayHandle + Send + Sync>) -> Self {
510 Self { handle }
511 }
512}
513
514impl HasDisplayHandle for OwnedDisplayHandle {
515 fn display_handle(&self) -> Result<DisplayHandle<'_>, HandleError> {
516 self.handle.display_handle()
517 }
518}
519
520impl fmt::Debug for OwnedDisplayHandle {
521 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
522 f.debug_struct("OwnedDisplayHandle").finish_non_exhaustive()
523 }
524}
525
526impl PartialEq for OwnedDisplayHandle {
527 fn eq(&self, other: &Self) -> bool {
528 match (self.display_handle(), other.display_handle()) {
529 (Ok(lhs), Ok(rhs)) => lhs == rhs,
530 _ => false,
531 }
532 }
533}
534
535impl Eq for OwnedDisplayHandle {}
536
537/// Set through [`ActiveEventLoop::set_control_flow()`].
538///
539/// Indicates the desired behavior of the event loop after [`about_to_wait`] is called.
540///
541/// Defaults to [`Wait`].
542///
543/// [`Wait`]: Self::Wait
544/// [`about_to_wait`]: crate::application::ApplicationHandler::about_to_wait
545#[derive(Copy, Clone, Debug, Default, PartialEq, Eq, Hash)]
546#[allow(clippy::exhaustive_enums)]
547pub enum ControlFlow {
548 /// When the current loop iteration finishes, immediately begin a new iteration regardless of
549 /// whether or not new events are available to process.
550 Poll,
551
552 /// When the current loop iteration finishes, suspend the thread until another event arrives.
553 #[default]
554 Wait,
555
556 /// When the current loop iteration finishes, suspend the thread until either another event
557 /// arrives or the given time is reached.
558 ///
559 /// Useful for implementing efficient timers. Applications which want to render at the
560 /// display's native refresh rate should instead use [`Poll`] and the VSync functionality
561 /// of a graphics API to reduce odds of missed frames.
562 ///
563 /// [`Poll`]: Self::Poll
564 WaitUntil(Instant),
565}
566
567impl ControlFlow {
568 /// Creates a [`ControlFlow`] that waits until a timeout has expired.
569 ///
570 /// In most cases, this is set to [`WaitUntil`]. However, if the timeout overflows, it is
571 /// instead set to [`Wait`].
572 ///
573 /// [`WaitUntil`]: Self::WaitUntil
574 /// [`Wait`]: Self::Wait
575 pub fn wait_duration(timeout: Duration) -> Self {
576 match Instant::now().checked_add(timeout) {
577 Some(instant) => Self::WaitUntil(instant),
578 None => Self::Wait,
579 }
580 }
581}
582
583/// Control when device events are captured.
584#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Default)]
585#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
586#[allow(clippy::exhaustive_enums)]
587pub enum DeviceEvents {
588 /// Report device events regardless of window focus.
589 Always,
590 /// Only capture device events while the window is focused.
591 #[default]
592 WhenFocused,
593 /// Never capture device events.
594 Never,
595}
596
597/// A unique identifier of the wingy's async request.
598///
599/// This could be used to identify the async request once it's done
600/// and a specific action must be taken.
601///
602/// One of the handling scenarios could be to maintain a working list
603/// containing [`AsyncRequestSerial`] and some closure associated with it.
604/// Then once event is arriving the working list is being traversed and a job
605/// executed and removed from the list.
606#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
607pub struct AsyncRequestSerial {
608 serial: usize,
609}
610
611impl AsyncRequestSerial {
612 pub fn get() -> Self {
613 static CURRENT_SERIAL: AtomicUsize = AtomicUsize::new(0);
614 // NOTE: We rely on wrap around here, while the user may just request
615 // in the loop usize::MAX times that's issue is considered on them.
616 let serial = CURRENT_SERIAL.fetch_add(1, Ordering::Relaxed);
617 Self { serial }
618 }
619}