tao/event_loop.rs
1// Copyright 2014-2021 The winit contributors
2// Copyright 2021-2023 Tauri Programme within The Commons Conservancy
3// SPDX-License-Identifier: Apache-2.0
4
5//! The `EventLoop` struct and assorted supporting types, including `ControlFlow`.
6//!
7//! If you want to send custom events to the event loop, use [`EventLoop::create_proxy()`][create_proxy]
8//! to acquire an [`EventLoopProxy`][event_loop_proxy] and call its [`send_event`][send_event] method.
9//!
10//! See the root-level documentation for information on how to create and use an event loop to
11//! handle events.
12//!
13//! [create_proxy]: crate::event_loop::EventLoop::create_proxy
14//! [event_loop_proxy]: crate::event_loop::EventLoopProxy
15//! [send_event]: crate::event_loop::EventLoopProxy::send_event
16use std::{error, fmt, marker::PhantomData, ops::Deref, time::Instant};
17
18use crate::{
19 dpi::PhysicalPosition,
20 error::ExternalError,
21 event::Event,
22 monitor::MonitorHandle,
23 platform_impl,
24 window::{ProgressBarState, Theme},
25};
26
27/// Provides a way to retrieve events from the system and from the windows that were registered to
28/// the events loop.
29///
30/// An `EventLoop` can be seen more or less as a "context". Calling `EventLoop::new()`
31/// initializes everything that will be required to create windows.
32///
33/// To wake up an `EventLoop` from a another thread, see the `EventLoopProxy` docs.
34///
35/// Note that the `EventLoop` cannot be shared across threads (due to platform-dependant logic
36/// forbidding it), as such it is neither `Send` nor `Sync`. If you need cross-thread access, the
37/// `Window` created from this `EventLoop` _can_ be sent to an other thread, and the
38/// `EventLoopProxy` allows you to wake up an `EventLoop` from another thread.
39///
40pub struct EventLoop<T: 'static> {
41 pub(crate) event_loop: platform_impl::EventLoop<T>,
42 pub(crate) _marker: ::std::marker::PhantomData<*mut ()>, // Not Send nor Sync
43}
44
45/// Target that associates windows with an `EventLoop`.
46///
47/// This type exists to allow you to create new windows while Tao executes
48/// your callback. `EventLoop` will coerce into this type (`impl<T> Deref for
49/// EventLoop<T>`), so functions that take this as a parameter can also take
50/// `&EventLoop`.
51#[derive(Clone)]
52pub struct EventLoopWindowTarget<T: 'static> {
53 pub(crate) p: platform_impl::EventLoopWindowTarget<T>,
54 pub(crate) _marker: ::std::marker::PhantomData<*mut ()>, // Not Send nor Sync
55}
56
57impl<T> fmt::Debug for EventLoop<T> {
58 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
59 f.pad("EventLoop { .. }")
60 }
61}
62
63impl<T> fmt::Debug for EventLoopWindowTarget<T> {
64 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
65 f.pad("EventLoopWindowTarget { .. }")
66 }
67}
68
69/// Object that allows building the event loop.
70///
71/// This is used to make specifying options that affect the whole application
72/// easier. But note that constructing multiple event loops is not supported.
73#[derive(Default)]
74pub struct EventLoopBuilder<T: 'static> {
75 pub(crate) platform_specific: platform_impl::PlatformSpecificEventLoopAttributes,
76 _p: PhantomData<T>,
77}
78impl EventLoopBuilder<()> {
79 /// Start building a new event loop.
80 #[inline]
81 pub fn new() -> Self {
82 Self::with_user_event()
83 }
84}
85impl<T> EventLoopBuilder<T> {
86 /// Start building a new event loop, with the given type as the user event
87 /// type.
88 #[inline]
89 pub fn with_user_event() -> Self {
90 Self {
91 platform_specific: Default::default(),
92 _p: PhantomData,
93 }
94 }
95 /// Builds a new event loop.
96 ///
97 /// ***For cross-platform compatibility, the `EventLoop` must be created on the main thread.***
98 /// Attempting to create the event loop on a different thread will panic. This restriction isn't
99 /// strictly necessary on all platforms, but is imposed to eliminate any nasty surprises when
100 /// porting to platforms that require it. `EventLoopBuilderExt::any_thread` functions are exposed
101 /// in the relevant `platform` module if the target platform supports creating an event loop on
102 /// any thread.
103 ///
104 /// Usage will result in display backend initialisation, this can be controlled on linux
105 /// using an environment variable `WINIT_UNIX_BACKEND`. Legal values are `x11` and `wayland`.
106 /// If it is not set, winit will try to connect to a wayland connection, and if it fails will
107 /// fallback on x11. If this variable is set with any other value, winit will panic.
108 ///
109 /// ## Platform-specific
110 ///
111 /// - **iOS:** Can only be called on the main thread.
112 #[inline]
113 pub fn build(&mut self) -> EventLoop<T> {
114 EventLoop {
115 #[cfg_attr(
116 any(
117 target_os = "linux",
118 target_os = "dragonfly",
119 target_os = "freebsd",
120 target_os = "netbsd",
121 target_os = "openbsd"
122 ),
123 allow(clippy::unnecessary_mut_passed)
124 )]
125 event_loop: platform_impl::EventLoop::new(&mut self.platform_specific),
126 _marker: PhantomData,
127 }
128 }
129}
130
131/// Set by the user callback given to the `EventLoop::run` method.
132///
133/// Indicates the desired behavior of the event loop after [`Event::RedrawEventsCleared`][events_cleared]
134/// is emitted. Defaults to `Poll`.
135///
136/// ## Persistency
137/// Almost every change is persistent between multiple calls to the event loop closure within a
138/// given run loop. The only exception to this is `ExitWithCode` which, once set, cannot be unset.
139/// Changes are **not** persistent between multiple calls to `run_return` - issuing a new call will
140/// reset the control flow to `Poll`.
141///
142/// [events_cleared]: crate::event::Event::RedrawEventsCleared
143#[non_exhaustive]
144#[derive(Copy, Clone, Debug, PartialEq, Eq)]
145pub enum ControlFlow {
146 /// When the current loop iteration finishes, immediately begin a new iteration regardless of
147 /// whether or not new events are available to process.
148 Poll,
149 /// When the current loop iteration finishes, suspend the thread until another event arrives.
150 Wait,
151 /// When the current loop iteration finishes, suspend the thread until either another event
152 /// arrives or the given time is reached.
153 WaitUntil(Instant),
154 /// Send a `LoopDestroyed` event and stop the event loop. This variant is *sticky* - once set,
155 /// `control_flow` cannot be changed from `ExitWithCode`, and any future attempts to do so will
156 /// result in the `control_flow` parameter being reset to `ExitWithCode`.
157 ///
158 /// The contained number will be used as exit code. The [`Exit`] constant is a shortcut for this
159 /// with exit code 0.
160 ///
161 /// ## Platform-specific
162 ///
163 /// - **Android / iOS / WASM**: The supplied exit code is unused.
164 /// - **Unix**: On most Unix-like platforms, only the 8 least significant bits will be used,
165 /// which can cause surprises with negative exit values (`-42` would end up as `214`). See
166 /// [`std::process::exit`].
167 ///
168 /// [`Exit`]: ControlFlow::Exit
169 ExitWithCode(i32),
170}
171
172impl ControlFlow {
173 /// Alias for [`ExitWithCode`]`(0)`.
174 ///
175 /// [`ExitWithCode`]: ControlFlow::ExitWithCode
176 #[allow(non_upper_case_globals)]
177 pub const Exit: Self = Self::ExitWithCode(0);
178}
179
180impl Default for ControlFlow {
181 #[inline(always)]
182 fn default() -> ControlFlow {
183 ControlFlow::Poll
184 }
185}
186
187impl EventLoop<()> {
188 /// Alias for [`EventLoopBuilder::new().build()`].
189 ///
190 /// [`EventLoopBuilder::new().build()`]: EventLoopBuilder::build
191 #[inline]
192 pub fn new() -> EventLoop<()> {
193 EventLoopBuilder::new().build()
194 }
195}
196
197impl Default for EventLoop<()> {
198 fn default() -> Self {
199 Self::new()
200 }
201}
202
203impl<T> EventLoop<T> {
204 /// Hijacks the calling thread and initializes the tao event loop with the provided
205 /// closure. Since the closure is `'static`, it must be a `move` closure if it needs to
206 /// access any data from the calling context.
207 ///
208 /// See the [`ControlFlow`] docs for information on how changes to `&mut ControlFlow` impact the
209 /// event loop's behavior.
210 ///
211 /// Any values not passed to this function will *not* be dropped.
212 ///
213 /// ## Platform-specific
214 ///
215 /// - **Unix**: The program terminates with exit code 1 if the display server
216 /// disconnects.
217 ///
218 /// [`ControlFlow`]: crate::event_loop::ControlFlow
219 #[inline]
220 pub fn run<F>(self, event_handler: F) -> !
221 where
222 F: 'static + FnMut(Event<'_, T>, &EventLoopWindowTarget<T>, &mut ControlFlow),
223 {
224 self.event_loop.run(event_handler)
225 }
226
227 /// Creates an `EventLoopProxy` that can be used to dispatch user events to the main event loop.
228 pub fn create_proxy(&self) -> EventLoopProxy<T> {
229 EventLoopProxy {
230 event_loop_proxy: self.event_loop.create_proxy(),
231 }
232 }
233}
234
235impl<T> Deref for EventLoop<T> {
236 type Target = EventLoopWindowTarget<T>;
237 fn deref(&self) -> &EventLoopWindowTarget<T> {
238 self.event_loop.window_target()
239 }
240}
241
242impl<T> EventLoopWindowTarget<T> {
243 /// Returns the list of all the monitors available on the system.
244 #[inline]
245 pub fn available_monitors(&self) -> impl Iterator<Item = MonitorHandle> {
246 self
247 .p
248 .available_monitors()
249 .into_iter()
250 .map(|inner| MonitorHandle { inner })
251 }
252
253 /// Returns the primary monitor of the system.
254 ///
255 /// Returns `None` if it can't identify any monitor as a primary one.
256 #[inline]
257 pub fn primary_monitor(&self) -> Option<MonitorHandle> {
258 self.p.primary_monitor()
259 }
260
261 /// Returns the monitor that contains the given point.
262 ///
263 /// ## Platform-specific:
264 ///
265 /// - **Android / iOS:** Unsupported.
266 #[inline]
267 pub fn monitor_from_point(&self, x: f64, y: f64) -> Option<MonitorHandle> {
268 self
269 .p
270 .monitor_from_point(x, y)
271 .map(|inner| MonitorHandle { inner })
272 }
273
274 /// Change [`DeviceEvent`] filter mode.
275 ///
276 /// Since the [`DeviceEvent`] capture can lead to high CPU usage for unfocused windows, tao
277 /// will ignore them by default for unfocused windows. This method allows changing
278 /// this filter at runtime to explicitly capture them again.
279 ///
280 /// ## Platform-specific
281 ///
282 /// - **Linux / macOS / iOS / Android:** Unsupported.
283 ///
284 /// [`DeviceEvent`]: crate::event::DeviceEvent
285 pub fn set_device_event_filter(&self, _filter: DeviceEventFilter) {
286 #[cfg(target_os = "windows")]
287 self.p.set_device_event_filter(_filter);
288 }
289
290 /// Returns the current cursor position
291 ///
292 /// ## Platform-specific
293 ///
294 /// - **iOS / Android / Linux(Wayland)**: Unsupported, returns `0,0`.
295 #[inline]
296 pub fn cursor_position(&self) -> Result<PhysicalPosition<f64>, ExternalError> {
297 self.p.cursor_position()
298 }
299
300 /// Sets the progress bar state
301 ///
302 /// ## Platform-specific
303 ///
304 /// - **Windows:** Unsupported. Use the Progress Bar Function Available in Window (Windows can have different progress bars for different window)
305 /// - **Linux:** Only supported desktop environments with `libunity` (e.g. GNOME).
306 /// - **iOS / Android:** Unsupported.
307 #[inline]
308 pub fn set_progress_bar(&self, _progress: ProgressBarState) {
309 #[cfg(any(target_os = "linux", target_os = "macos"))]
310 self.p.set_progress_bar(_progress)
311 }
312
313 /// Sets the theme for the application.
314 ///
315 /// ## Platform-specific
316 ///
317 /// - **iOS / Android:** Unsupported.
318 #[inline]
319 pub fn set_theme(&self, _theme: Option<Theme>) {
320 #[cfg(any(
321 windows,
322 target_os = "linux",
323 target_os = "dragonfly",
324 target_os = "freebsd",
325 target_os = "netbsd",
326 target_os = "openbsd",
327 target_os = "macos",
328 ))]
329 self.p.set_theme(_theme)
330 }
331}
332
333#[cfg(feature = "rwh_05")]
334unsafe impl<T> rwh_05::HasRawDisplayHandle for EventLoop<T> {
335 fn raw_display_handle(&self) -> rwh_05::RawDisplayHandle {
336 rwh_05::HasRawDisplayHandle::raw_display_handle(&**self)
337 }
338}
339
340#[cfg(feature = "rwh_06")]
341impl<T> rwh_06::HasDisplayHandle for EventLoop<T> {
342 fn display_handle(&self) -> Result<rwh_06::DisplayHandle<'_>, rwh_06::HandleError> {
343 rwh_06::HasDisplayHandle::display_handle(&**self)
344 }
345}
346
347#[cfg(feature = "rwh_05")]
348unsafe impl<T> rwh_05::HasRawDisplayHandle for EventLoopWindowTarget<T> {
349 fn raw_display_handle(&self) -> rwh_05::RawDisplayHandle {
350 self.p.raw_display_handle_rwh_05()
351 }
352}
353
354#[cfg(feature = "rwh_06")]
355impl<T> rwh_06::HasDisplayHandle for EventLoopWindowTarget<T> {
356 fn display_handle(&self) -> Result<rwh_06::DisplayHandle<'_>, rwh_06::HandleError> {
357 let raw = self.p.raw_display_handle_rwh_06()?;
358 // SAFETY: The display will never be deallocated while the event loop is alive.
359 Ok(unsafe { rwh_06::DisplayHandle::borrow_raw(raw) })
360 }
361}
362
363/// Used to send custom events to `EventLoop`.
364pub struct EventLoopProxy<T: 'static> {
365 event_loop_proxy: platform_impl::EventLoopProxy<T>,
366}
367
368impl<T: 'static> Clone for EventLoopProxy<T> {
369 fn clone(&self) -> Self {
370 Self {
371 event_loop_proxy: self.event_loop_proxy.clone(),
372 }
373 }
374}
375
376impl<T: 'static> EventLoopProxy<T> {
377 /// Send an event to the `EventLoop` from which this proxy was created. This emits a
378 /// `UserEvent(event)` event in the event loop, where `event` is the value passed to this
379 /// function.
380 ///
381 /// Returns an `Err` if the associated `EventLoop` no longer exists.
382 pub fn send_event(&self, event: T) -> Result<(), EventLoopClosed<T>> {
383 self.event_loop_proxy.send_event(event)
384 }
385}
386
387impl<T: 'static> fmt::Debug for EventLoopProxy<T> {
388 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
389 f.pad("EventLoopProxy { .. }")
390 }
391}
392
393/// The error that is returned when an `EventLoopProxy` attempts to wake up an `EventLoop` that
394/// no longer exists. Contains the original event given to `send_event`.
395#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
396pub struct EventLoopClosed<T>(pub T);
397
398impl<T> fmt::Display for EventLoopClosed<T> {
399 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
400 f.write_str("Tried to wake up a closed `EventLoop`")
401 }
402}
403
404impl<T: fmt::Debug> error::Error for EventLoopClosed<T> {}
405
406/// Fiter controlling the propagation of device events.
407#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Default)]
408pub enum DeviceEventFilter {
409 /// Always filter out device events.
410 Always,
411 /// Filter out device events while the window is not focused.
412 #[default]
413 Unfocused,
414 /// Report all device events regardless of window focus.
415 Never,
416}