1// Copyright © SixtyFPS GmbH <info@slint.dev>
2// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-1.1 OR LicenseRef-Slint-commercial
3
4/*!
5The backend is the abstraction for crates that need to do the actual drawing and event loop
6*/
7
8#![warn(missing_docs)]
9
10pub use crate::api::PlatformError;
11use crate::api::{LogicalPosition, LogicalSize};
12pub use crate::renderer::Renderer;
13#[cfg(feature = "software-renderer")]
14pub use crate::software_renderer;
15#[cfg(all(not(feature = "std"), feature = "unsafe-single-threaded"))]
16use crate::unsafe_single_threaded::OnceCell;
17pub use crate::window::{LayoutConstraints, WindowAdapter, WindowProperties};
18use crate::SharedString;
19#[cfg(not(feature = "std"))]
20use alloc::boxed::Box;
21use alloc::rc::Rc;
22#[cfg(not(feature = "std"))]
23use alloc::string::String;
24#[cfg(feature = "std")]
25use once_cell::sync::OnceCell;
26#[cfg(all(feature = "std", not(target_arch = "wasm32")))]
27use std::time;
28#[cfg(target_arch = "wasm32")]
29use web_time as time;
30
31/// This trait defines the interface between Slint and platform APIs typically provided by operating and windowing systems.
32pub trait Platform {
33 /// Instantiate a window for a component.
34 fn create_window_adapter(&self) -> Result<Rc<dyn WindowAdapter>, PlatformError>;
35
36 /// Spins an event loop and renders the visible windows.
37 fn run_event_loop(&self) -> Result<(), PlatformError> {
38 Err(PlatformError::NoEventLoopProvider)
39 }
40
41 /// Spins an event loop for a specified period of time.
42 ///
43 /// This function is similar to `run_event_loop()` with two differences:
44 /// * The function is expected to return after the provided timeout, but
45 /// allow for subsequent invocations to resume the previous loop. The
46 /// function can return earlier if the loop was terminated otherwise,
47 /// for example by `quit_event_loop()` or a last-window-closed mechanism.
48 /// * If the timeout is zero, the implementation should merely peek and
49 /// process any pending events, but then return immediately.
50 ///
51 /// When the function returns `ControlFlow::Continue`, it is assumed that
52 /// the loop remains intact and that in the future the caller should call
53 /// `process_events()` again, to permit the user to continue to interact with
54 /// windows.
55 /// When the function returns `ControlFlow::Break`, it is assumed that the
56 /// event loop was terminated. Any subsequent calls to `process_events()`
57 /// will start the event loop afresh.
58 #[doc(hidden)]
59 fn process_events(
60 &self,
61 _timeout: core::time::Duration,
62 _: crate::InternalToken,
63 ) -> Result<core::ops::ControlFlow<()>, PlatformError> {
64 Err(PlatformError::NoEventLoopProvider)
65 }
66
67 #[doc(hidden)]
68 #[deprecated(
69 note = "i-slint-core takes care of closing behavior. Application should call run_event_loop_until_quit"
70 )]
71 /// This is being phased out, see #1499.
72 fn set_event_loop_quit_on_last_window_closed(&self, quit_on_last_window_closed: bool) {
73 assert!(!quit_on_last_window_closed);
74 crate::context::GLOBAL_CONTEXT
75 .with(|ctx| (*ctx.get().unwrap().0.window_count.borrow_mut()) += 1);
76 }
77
78 /// Return an [`EventLoopProxy`] that can be used to send event to the event loop
79 ///
80 /// If this function returns `None` (the default implementation), then it will
81 /// not be possible to send event to the event loop and the function
82 /// [`slint::invoke_from_event_loop()`](crate::api::invoke_from_event_loop) and
83 /// [`slint::quit_event_loop()`](crate::api::quit_event_loop) will panic
84 fn new_event_loop_proxy(&self) -> Option<Box<dyn EventLoopProxy>> {
85 None
86 }
87
88 /// Returns the current time as a monotonic duration since the start of the program
89 ///
90 /// This is used by the animations and timer to compute the elapsed time.
91 ///
92 /// When the `std` feature is enabled, this function is implemented in terms of
93 /// [`std::time::Instant::now()`], but on `#![no_std]` platform, this function must
94 /// be implemented.
95 fn duration_since_start(&self) -> core::time::Duration {
96 #[cfg(feature = "std")]
97 {
98 let the_beginning = *INITIAL_INSTANT.get_or_init(time::Instant::now);
99 time::Instant::now() - the_beginning
100 }
101 #[cfg(not(feature = "std"))]
102 unimplemented!("The platform abstraction must implement `duration_since_start`")
103 }
104
105 /// Returns the current interval to internal measure the duration to send a double click event.
106 ///
107 /// A double click event is a series of two pointer clicks.
108 fn click_interval(&self) -> core::time::Duration {
109 // 500ms is the default delay according to https://en.wikipedia.org/wiki/Double-click#Speed_and_timing
110 core::time::Duration::from_millis(500)
111 }
112
113 /// Sends the given text into the system clipboard.
114 ///
115 /// If the platform doesn't support the specified clipboard, this function should do nothing
116 fn set_clipboard_text(&self, _text: &str, _clipboard: Clipboard) {}
117
118 /// Returns a copy of text stored in the system clipboard, if any.
119 ///
120 /// If the platform doesn't support the specified clipboard, the function should return None
121 fn clipboard_text(&self, _clipboard: Clipboard) -> Option<String> {
122 None
123 }
124
125 /// This function is called when debug() is used in .slint files. The implementation
126 /// should direct the output to some developer visible terminal. The default implementation
127 /// uses stderr if available, or `console.log` when targeting wasm.
128 fn debug_log(&self, _arguments: core::fmt::Arguments) {
129 crate::tests::default_debug_log(_arguments);
130 }
131}
132
133/// The clip board, used in [`Platform::clipboard_text`] and [Platform::set_clipboard_text`]
134#[repr(u8)]
135#[non_exhaustive]
136#[derive(PartialEq, Clone, Default)]
137pub enum Clipboard {
138 /// This is the default clipboard used for text action for Ctrl+V, Ctrl+C.
139 /// Corresponds to the secondary clipboard on X11.
140 #[default]
141 DefaultClipboard = 0,
142
143 /// This is the clipboard that is used when text is selected
144 /// Corresponds to the primary clipboard on X11.
145 /// The Platform implementation should do nothing if copy on select is not supported on that platform.
146 SelectionClipboard = 1,
147}
148
149/// Trait that is returned by the [`Platform::new_event_loop_proxy`]
150///
151/// This are the implementation details for the function that may need to
152/// communicate with the eventloop from different thread
153pub trait EventLoopProxy: Send + Sync {
154 /// Exits the event loop.
155 ///
156 /// This is what is called by [`slint::quit_event_loop()`](crate::api::quit_event_loop)
157 fn quit_event_loop(&self) -> Result<(), crate::api::EventLoopError>;
158
159 /// Invoke the function from the event loop.
160 ///
161 /// This is what is called by [`slint::invoke_from_event_loop()`](crate::api::invoke_from_event_loop)
162 fn invoke_from_event_loop(
163 &self,
164 event: Box<dyn FnOnce() + Send>,
165 ) -> Result<(), crate::api::EventLoopError>;
166}
167
168#[cfg(feature = "std")]
169static INITIAL_INSTANT: once_cell::sync::OnceCell<time::Instant> = once_cell::sync::OnceCell::new();
170
171#[cfg(feature = "std")]
172impl std::convert::From<crate::animations::Instant> for time::Instant {
173 fn from(our_instant: crate::animations::Instant) -> Self {
174 let the_beginning: Instant = *INITIAL_INSTANT.get_or_init(time::Instant::now);
175 the_beginning + core::time::Duration::from_millis(our_instant.0)
176 }
177}
178
179static EVENTLOOP_PROXY: OnceCell<Box<dyn EventLoopProxy + 'static>> = OnceCell::new();
180
181pub(crate) fn event_loop_proxy() -> Option<&'static dyn EventLoopProxy> {
182 EVENTLOOP_PROXY.get().map(core::ops::Deref::deref)
183}
184
185/// This enum describes the different error scenarios that may occur when [`set_platform`]
186/// fails.
187#[derive(Debug, Clone, PartialEq)]
188#[repr(C)]
189#[non_exhaustive]
190pub enum SetPlatformError {
191 /// The platform has been initialized in an earlier call to [`set_platform`].
192 AlreadySet,
193}
194
195/// Set the Slint platform abstraction.
196///
197/// If the platform abstraction was already set this will return `Err`.
198pub fn set_platform(platform: Box<dyn Platform + 'static>) -> Result<(), SetPlatformError> {
199 crate::context::GLOBAL_CONTEXT.with(|instance: &OnceCell| {
200 if instance.get().is_some() {
201 return Err(SetPlatformError::AlreadySet);
202 }
203 if let Some(proxy: Box) = platform.new_event_loop_proxy() {
204 EVENTLOOP_PROXY.set(proxy).map_err(|_| SetPlatformError::AlreadySet)?
205 }
206 instanceResult<(), SetPlatformError>
207 .set(crate::SlintContext::new(platform))
208 .map_err(|_| SetPlatformError::AlreadySet)
209 .unwrap();
210 // Ensure a sane starting point for the animation tick.
211 update_timers_and_animations();
212 Ok(())
213 })
214}
215
216/// Call this function to update and potentially activate any pending timers, as well
217/// as advance the state of any active animations.
218///
219/// This function should be called before rendering or processing input event, at the
220/// beginning of each event loop iteration.
221pub fn update_timers_and_animations() {
222 crate::animations::update_animations();
223 crate::timers::TimerList::maybe_activate_timers(crate::animations::Instant::now());
224}
225
226/// Returns the duration before the next timer is expected to be activated. This is the
227/// largest amount of time that you can wait before calling [`update_timers_and_animations()`].
228///
229/// `None` is returned if there is no active timer.
230///
231/// Call this in your own event loop implementation to know how long the current thread can
232/// go to sleep. Note that this does not take currently activate animations into account.
233/// Only go to sleep if [`Window::has_active_animations()`](crate::api::Window::has_active_animations())
234/// returns false.
235pub fn duration_until_next_timer_update() -> Option<core::time::Duration> {
236 crate::timers::TimerList::next_timeout().map(|timeout: Instant| {
237 let duration_since_start: Duration = crateOption::context::GLOBAL_CONTEXT
238 .with(|p: &OnceCell| p.get().map(|p: &SlintContext| p.0.platform.duration_since_start()))
239 .unwrap_or_default();
240 core::time::Duration::from_millis(
241 timeout.0.saturating_sub(duration_since_start.as_millis() as u64),
242 )
243 })
244}
245
246// reexport key enum to the public api
247pub use crate::input::key_codes::Key;
248pub use crate::input::PointerEventButton;
249
250/// A event that describes user input or windowing system events.
251///
252/// Slint backends typically receive events from the windowing system, translate them to this
253/// enum and deliver them to the scene of items via [`slint::Window::dispatch_event()`](`crate::api::Window::dispatch_event()`).
254///
255/// The pointer variants describe events originating from an input device such as a mouse
256/// or a contact point on a touch-enabled surface.
257///
258/// All position fields are in logical window coordinates.
259#[allow(missing_docs)]
260#[derive(Debug, Clone, PartialEq)]
261#[non_exhaustive]
262#[repr(u32)]
263pub enum WindowEvent {
264 /// A pointer was pressed.
265 PointerPressed {
266 position: LogicalPosition,
267 /// The button that was pressed.
268 button: PointerEventButton,
269 },
270 /// A pointer was released.
271 PointerReleased {
272 position: LogicalPosition,
273 /// The button that was released.
274 button: PointerEventButton,
275 },
276 /// The position of the pointer has changed.
277 PointerMoved { position: LogicalPosition },
278 /// The wheel button of a mouse was rotated to initiate scrolling.
279 PointerScrolled {
280 position: LogicalPosition,
281 /// The amount of logical pixels to scroll in the horizontal direction.
282 delta_x: f32,
283 /// The amount of logical pixels to scroll in the vertical direction.
284 delta_y: f32,
285 },
286 /// The pointer exited the window.
287 PointerExited,
288 /// A key was pressed.
289 KeyPressed {
290 /// The unicode representation of the key pressed.
291 ///
292 /// # Example
293 /// A specific key can be mapped to a unicode by using the [`Key`] enum
294 /// ```rust
295 /// let _ = slint::platform::WindowEvent::KeyPressed { text: slint::platform::Key::Shift.into() };
296 /// ```
297 text: SharedString,
298 },
299 /// A key press was auto-repeated.
300 KeyPressRepeated {
301 /// The unicode representation of the key pressed.
302 ///
303 /// # Example
304 /// A specific key can be mapped to a unicode by using the [`Key`] enum
305 /// ```rust
306 /// let _ = slint::platform::WindowEvent::KeyPressRepeated { text: slint::platform::Key::Shift.into() };
307 /// ```
308 text: SharedString,
309 },
310 /// A key was released.
311 KeyReleased {
312 /// The unicode representation of the key released.
313 ///
314 /// # Example
315 /// A specific key can be mapped to a unicode by using the [`Key`] enum
316 /// ```rust
317 /// let _ = slint::platform::WindowEvent::KeyReleased { text: slint::platform::Key::Shift.into() };
318 /// ```
319 text: SharedString,
320 },
321 /// The window's scale factor has changed. This can happen for example when the display's resolution
322 /// changes, the user selects a new scale factor in the system settings, or the window is moved to a
323 /// different screen.
324 /// Platform implementations should dispatch this event also right after the initial window creation,
325 /// to set the initial scale factor the windowing system provided for the window.
326 ScaleFactorChanged {
327 /// The window system provided scale factor to map logical pixels to physical pixels.
328 scale_factor: f32,
329 },
330 /// The window was resized.
331 ///
332 /// The backend must send this event to ensure that the `width` and `height` property of the root Window
333 /// element are properly set.
334 Resized {
335 /// The new logical size of the window
336 size: LogicalSize,
337 },
338 /// The user requested to close the window.
339 ///
340 /// The backend should send this event when the user tries to close the window,for example by pressing the close button.
341 ///
342 /// This will have the effect of invoking the callback set in [`Window::on_close_requested()`](`crate::api::Window::on_close_requested()`)
343 /// and then hiding the window depending on the return value of the callback.
344 CloseRequested,
345
346 /// The Window was activated or de-activated.
347 ///
348 /// The backend should dispatch this event with true when the window gains focus
349 /// and false when the window loses focus.
350 WindowActiveChanged(bool),
351}
352
353impl WindowEvent {
354 /// The position of the cursor for this event, if any
355 pub fn position(&self) -> Option<LogicalPosition> {
356 match self {
357 WindowEvent::PointerPressed { position: &LogicalPosition, .. } => Some(*position),
358 WindowEvent::PointerReleased { position: &LogicalPosition, .. } => Some(*position),
359 WindowEvent::PointerMoved { position: &LogicalPosition } => Some(*position),
360 WindowEvent::PointerScrolled { position: &LogicalPosition, .. } => Some(*position),
361 _ => None,
362 }
363 }
364}
365
366/**
367 * Test the animation tick is updated when a platform is set
368```rust
369use i_slint_core::platform::*;
370struct DummyBackend;
371impl Platform for DummyBackend {
372 fn create_window_adapter(
373 &self,
374 ) -> Result<std::rc::Rc<dyn WindowAdapter>, PlatformError> {
375 Err(PlatformError::Other("not implemented".into()))
376 }
377 fn duration_since_start(&self) -> core::time::Duration {
378 core::time::Duration::from_millis(100)
379 }
380}
381
382let start_time = i_slint_core::tests::slint_get_mocked_time();
383i_slint_core::platform::set_platform(Box::new(DummyBackend{}));
384let time_after_platform_init = i_slint_core::tests::slint_get_mocked_time();
385assert_ne!(time_after_platform_init, start_time);
386assert_eq!(time_after_platform_init, 100);
387```
388 */
389#[cfg(doctest)]
390const _ANIM_TICK_UPDATED_ON_PLATFORM_SET: () = ();
391