i_slint_core/platform.rs
1// Copyright © SixtyFPS GmbH <info@slint.dev>
2// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0
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
10use crate::SharedString;
11pub use crate::api::PlatformError;
12use crate::api::{LogicalPosition, LogicalSize};
13pub use crate::renderer::Renderer;
14#[cfg(all(not(feature = "std"), feature = "unsafe-single-threaded"))]
15use crate::unsafe_single_threaded::OnceCell;
16pub use crate::window::{LayoutConstraints, WindowAdapter, WindowProperties};
17use alloc::boxed::Box;
18use alloc::rc::Rc;
19use alloc::string::String;
20#[cfg(all(feature = "std", not(target_os = "android")))]
21use once_cell::sync::OnceCell;
22#[cfg(all(feature = "std", not(target_arch = "wasm32")))]
23use std::time;
24#[cfg(target_arch = "wasm32")]
25use web_time as time;
26
27/// This trait defines the interface between Slint and platform APIs typically provided by operating and windowing systems.
28pub trait Platform {
29 /// Instantiate a window for a component.
30 fn create_window_adapter(&self) -> Result<Rc<dyn WindowAdapter>, PlatformError>;
31
32 /// Spins an event loop and renders the visible windows.
33 fn run_event_loop(&self) -> Result<(), PlatformError> {
34 Err(PlatformError::NoEventLoopProvider)
35 }
36
37 /// Processes pending events and waits for new ones up to the given timeout.
38 ///
39 /// This function is similar to `run_event_loop()` with two differences:
40 /// * It processes any pending events,
41 /// then blocks waiting for new events for up to `timeout`.
42 /// It may return earlier than the timeout if events were received,
43 /// if the loop was terminated via `quit_event_loop()`,
44 /// or through a last-window-closed mechanism.
45 /// Callers shouldn't assume the full timeout has elapsed when the function returns.
46 /// * If the timeout is `None`, the implementation should wait
47 /// indefinitely for events.
48 /// * If the timeout is `Some(Duration::ZERO)`,
49 /// the implementation should merely peek and process any pending events,
50 /// then return immediately.
51 ///
52 /// When the function returns `ControlFlow::Continue`, it is assumed that
53 /// the loop remains intact and that in the future the caller should call
54 /// `process_events()` again, to permit the user to continue to interact with
55 /// windows.
56 /// When the function returns `ControlFlow::Break`, it is assumed that the
57 /// event loop was terminated. Any subsequent calls to `process_events()`
58 /// will start the event loop afresh.
59 #[doc(hidden)]
60 fn process_events(
61 &self,
62 _timeout: Option<core::time::Duration>,
63 _: crate::InternalToken,
64 ) -> Result<core::ops::ControlFlow<()>, PlatformError> {
65 Err(PlatformError::NoEventLoopProvider)
66 }
67
68 /// Called once by [`crate::SlintContext::new`], as the context that owns this platform
69 /// finishes construction, to give the platform a weak handle to it. Platforms can stash
70 /// the handle and later use it to spawn futures or write context-wide state without
71 /// going through a window adapter. The default impl drops the handle.
72 ///
73 /// Every context binds its own platform, not only the one installed as the thread's
74 /// global context, so a backend driving a context can always find it.
75 #[doc(hidden)]
76 fn bind_context(&self, _ctx: crate::SlintContextWeak, _: crate::InternalToken) {}
77
78 #[doc(hidden)]
79 #[deprecated(
80 note = "i-slint-core takes care of closing behavior. Application should call run_event_loop_until_quit"
81 )]
82 /// This is being phased out, see #1499.
83 fn set_event_loop_quit_on_last_window_closed(&self, quit_on_last_window_closed: bool) {
84 assert!(!quit_on_last_window_closed);
85 crate::context::GLOBAL_CONTEXT
86 .with(|ctx| (*ctx.get().unwrap().0.window_count.borrow_mut()) += 1);
87 }
88
89 /// Return an [`EventLoopProxy`] that can be used to send event to the event loop
90 ///
91 /// If this function returns `None` (the default implementation), then it will
92 /// not be possible to send event to the event loop and the function
93 /// [`slint::invoke_from_event_loop()`](crate::api::invoke_from_event_loop) and
94 /// [`slint::quit_event_loop()`](crate::api::quit_event_loop) will panic. These
95 /// functions are used internally by `slint::spawn_local()`
96 /// and features like live_preview. Implementing this function is necessary for
97 /// aforementioned functionalities to work.
98 fn new_event_loop_proxy(&self) -> Option<Box<dyn EventLoopProxy>> {
99 None
100 }
101
102 /// Returns the current time as a monotonic duration since the start of the program
103 ///
104 /// This is used by the animations and timer to compute the elapsed time.
105 ///
106 /// When the `std` feature is enabled, this function is implemented in terms of
107 /// [`std::time::Instant::now()`], but on `#![no_std]` platform, this function must
108 /// be implemented.
109 fn duration_since_start(&self) -> core::time::Duration {
110 #[cfg(feature = "std")]
111 {
112 let the_beginning = *INITIAL_INSTANT.get_or_init(time::Instant::now);
113 let now = time::Instant::now();
114 assert!(now >= the_beginning, "The platform's clock is not monotonic!");
115 now - the_beginning
116 }
117 #[cfg(not(feature = "std"))]
118 unimplemented!("The platform abstraction must implement `duration_since_start`")
119 }
120
121 /// Returns the current interval to internal measure the duration to send a double click event.
122 ///
123 /// A double click event is a series of two pointer clicks.
124 fn click_interval(&self) -> core::time::Duration {
125 // 500ms is the default delay according to https://en.wikipedia.org/wiki/Double-click#Speed_and_timing
126 core::time::Duration::from_millis(500)
127 }
128
129 /// Returns the current rate at which the text cursor should flash or blink.
130 ///
131 /// This is the length of the entire visible-hidden-visible cycle, so for a duration of 1000ms
132 /// it is visible for 500ms then hidden for 500ms, then visible again.
133 ///
134 /// If this value is `Duration::ZERO` then the cycle is disabled.
135 fn cursor_flash_cycle(&self) -> core::time::Duration {
136 core::time::Duration::from_millis(1000)
137 }
138
139 /// Sends the given text into the system clipboard.
140 ///
141 /// If the platform doesn't support the specified clipboard, this function should do nothing
142 fn set_clipboard_text(&self, _text: &str, _clipboard: Clipboard) {}
143
144 /// Returns a copy of text stored in the system clipboard, if any.
145 ///
146 /// If the platform doesn't support the specified clipboard, the function should return None
147 fn clipboard_text(&self, _clipboard: Clipboard) -> Option<String> {
148 None
149 }
150
151 /// This function is called when debug() is used in .slint files. The implementation
152 /// should direct the output to some developer visible terminal. The default implementation
153 /// uses stderr if available, or `console.log` when targeting wasm.
154 fn debug_log(&self, _arguments: core::fmt::Arguments) {
155 crate::debug_log::default_log_message(_arguments);
156 }
157
158 /// Opens the given URL in an external browser.
159 ///
160 /// Returns [`PlatformError::Unsupported`] if the platform doesn't support opening URLs.
161 fn open_url(&self, _url: &str) -> Result<(), PlatformError> {
162 Err(PlatformError::Unsupported)
163 }
164
165 #[cfg(target_os = "android")]
166 #[doc(hidden)]
167 /// The long press interval before showing a context menu
168 fn long_press_interval(&self, _: crate::InternalToken) -> core::time::Duration {
169 core::time::Duration::from_millis(500)
170 }
171}
172
173/// The clip board, used in [`Platform::clipboard_text`] and [Platform::set_clipboard_text`]
174#[repr(u8)]
175#[non_exhaustive]
176#[derive(Debug, PartialEq, Clone, Default)]
177pub enum Clipboard {
178 /// This is the default clipboard used for text action for Ctrl+V, Ctrl+C.
179 /// Corresponds to the secondary clipboard on X11.
180 #[default]
181 DefaultClipboard = 0,
182
183 /// This is the clipboard that is used when text is selected
184 /// Corresponds to the primary clipboard on X11.
185 /// The Platform implementation should do nothing if copy on select is not supported on that platform.
186 SelectionClipboard = 1,
187}
188
189/// Trait that is returned by the [`Platform::new_event_loop_proxy`]
190///
191/// This are the implementation details for the function that may need to
192/// communicate with the eventloop from different thread
193pub trait EventLoopProxy: Send + Sync {
194 /// Exits the event loop.
195 ///
196 /// This is what is called by [`slint::quit_event_loop()`](crate::api::quit_event_loop)
197 fn quit_event_loop(&self) -> Result<(), crate::api::EventLoopError>;
198
199 /// Invoke the function from the event loop.
200 ///
201 /// This is what is called by [`slint::invoke_from_event_loop()`](crate::api::invoke_from_event_loop)
202 fn invoke_from_event_loop(
203 &self,
204 event: Box<dyn FnOnce() + Send>,
205 ) -> Result<(), crate::api::EventLoopError>;
206}
207
208#[cfg(feature = "std")]
209static INITIAL_INSTANT: once_cell::sync::OnceCell<time::Instant> = once_cell::sync::OnceCell::new();
210
211#[cfg(feature = "std")]
212impl std::convert::From<crate::animations::Instant> for time::Instant {
213 fn from(our_instant: crate::animations::Instant) -> Self {
214 let the_beginning = *INITIAL_INSTANT.get_or_init(time::Instant::now);
215 the_beginning + core::time::Duration::from_millis(our_instant.0)
216 }
217}
218
219#[cfg(not(target_os = "android"))]
220static EVENTLOOP_PROXY: OnceCell<Box<dyn EventLoopProxy + 'static>> = OnceCell::new();
221
222// On android, we allow the platform to be reset and the global eventloop proxy to be replaced.
223#[cfg(target_os = "android")]
224static EVENTLOOP_PROXY: std::sync::Mutex<Option<Box<dyn EventLoopProxy + 'static>>> =
225 std::sync::Mutex::new(None);
226
227pub(crate) fn with_event_loop_proxy<R>(f: impl FnOnce(Option<&dyn EventLoopProxy>) -> R) -> R {
228 #[cfg(not(target_os = "android"))]
229 return f(EVENTLOOP_PROXY.get().map(core::ops::Deref::deref));
230 #[cfg(target_os = "android")]
231 return f(EVENTLOOP_PROXY.lock().unwrap().as_ref().map(core::ops::Deref::deref));
232}
233
234/// This enum describes the different error scenarios that may occur when [`set_platform`]
235/// fails.
236#[derive(Debug, Clone, PartialEq)]
237#[repr(C)]
238#[non_exhaustive]
239pub enum SetPlatformError {
240 /// The platform has already been initialized in an earlier call to [`set_platform`].
241 AlreadySet,
242}
243
244impl core::fmt::Display for SetPlatformError {
245 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
246 match self {
247 SetPlatformError::AlreadySet => {
248 f.write_str("The platform has already been initialized.")
249 }
250 }
251 }
252}
253
254impl core::error::Error for SetPlatformError {}
255
256/// Set the Slint platform abstraction.
257///
258/// If the platform abstraction was already set this will return `Err`.
259pub fn set_platform(platform: Box<dyn Platform + 'static>) -> Result<(), SetPlatformError> {
260 crate::context::GLOBAL_CONTEXT.with(|instance| {
261 if instance.get().is_some() {
262 return Err(SetPlatformError::AlreadySet);
263 }
264 if let Some(proxy) = platform.new_event_loop_proxy() {
265 #[cfg(not(target_os = "android"))]
266 {
267 EVENTLOOP_PROXY.set(proxy).map_err(|_| SetPlatformError::AlreadySet)?;
268 }
269 #[cfg(target_os = "android")]
270 {
271 *EVENTLOOP_PROXY.lock().unwrap() = Some(proxy);
272 }
273 }
274 // The slot is free, so this claims it. The returned handle is dropped here; the
275 // context stays alive because the slot holds it.
276 drop(crate::SlintContext::new(platform));
277 debug_assert!(instance.get().is_some(), "SlintContext::new claims a free slot");
278 // Ensure a sane starting point for the animation tick.
279 update_timers_and_animations();
280 Ok(())
281 })
282}
283
284/// Call this function to update and potentially activate any pending timers, as well
285/// as advance the state of any active animations.
286///
287/// This function should be called before rendering or processing input event, at the
288/// beginning of each event loop iteration.
289pub fn update_timers_and_animations() {
290 match crate::context::GLOBAL_CONTEXT.with(|ctx| ctx.get().cloned()) {
291 Some(ctx) => ctx.update_timers_and_animations(),
292 None => {
293 // Pre-platform behavior: with no context there is no clock either, so only
294 // zero-duration timers in the pending list are due.
295 crate::animations::update_animations(Default::default());
296 crate::timers::TimerList::maybe_activate_timers(Default::default());
297 crate::properties::ChangeTracker::run_change_handlers();
298 }
299 }
300}
301
302/// Returns the duration before the next timer is expected to be activated. This is the
303/// largest amount of time that you can wait before calling [`update_timers_and_animations()`].
304///
305/// `None` is returned if there is no active timer.
306///
307/// Call this in your own event loop implementation to know how long the current thread can
308/// go to sleep. Note that this does not take currently activate animations into account.
309/// Only go to sleep if [`Window::has_active_animations()`](crate::api::Window::has_active_animations())
310/// returns false.
311pub fn duration_until_next_timer_update() -> Option<core::time::Duration> {
312 match crate::context::GLOBAL_CONTEXT.with(|ctx| ctx.get().cloned()) {
313 Some(ctx) => ctx.duration_until_next_timer_update(),
314 // No context, hence no clock: the deadline is measured from a zero origin.
315 None => crate::timers::TimerList::next_timeout()
316 .map(|timeout| core::time::Duration::from_millis(timeout.0)),
317 }
318}
319
320// reexport key enum to the public api
321pub use crate::input::PointerEventButton;
322pub use crate::input::key_codes::Key;
323
324/// Result of dispatching a window event through Slint's runtime with
325/// [`Window::dispatch_event_with_result()`](crate::api::Window::dispatch_event_with_result).
326#[derive(Clone, Debug, PartialEq)]
327#[non_exhaustive]
328pub enum WindowEventDispatchResult {
329 /// The event was handled. For example, a key handler consumed a key press, or
330 /// the window acted on a resize or close request.
331 Accepted,
332 /// The event wasn't handled: no element consumed it, or a handler actively refused it,
333 /// such as a `close-requested` callback returning `reject` to keep the window open.
334 Rejected,
335}
336
337impl From<crate::input::KeyEventResult> for WindowEventDispatchResult {
338 fn from(value: crate::input::KeyEventResult) -> Self {
339 match value {
340 crate::input::KeyEventResult::EventAccepted => Self::Accepted,
341 crate::input::KeyEventResult::EventIgnored => Self::Rejected,
342 }
343 }
344}
345
346impl From<Option<crate::window::MouseDispatchResult>> for WindowEventDispatchResult {
347 /// `None` (no component to dispatch to) and `accepted: false` both map to `Rejected`.
348 fn from(value: Option<crate::window::MouseDispatchResult>) -> Self {
349 if value.is_some_and(|r| r.accepted) { Self::Accepted } else { Self::Rejected }
350 }
351}
352
353// api/node/build.rs parses this enum to generate the Node.js window event types.
354/// A event that describes user input or windowing system events.
355///
356/// Slint backends typically receive events from the windowing system, translate them to this
357/// enum and deliver them to the scene of items via [`slint::Window::dispatch_event_with_result()`](`crate::api::Window::dispatch_event_with_result()`).
358///
359/// The pointer variants describe events originating from an input device such as a mouse
360/// or a contact point on a touch-enabled surface.
361///
362/// All position fields are in logical window coordinates.
363#[allow(missing_docs)]
364#[derive(Debug, Clone, PartialEq)]
365#[non_exhaustive]
366#[repr(u32)]
367pub enum WindowEvent {
368 /// A pointer was pressed.
369 PointerPressed {
370 /// The position of the pointer, in logical pixels relative to the top left corner of the window.
371 position: LogicalPosition,
372 /// The button that was pressed.
373 button: PointerEventButton,
374 },
375 /// A pointer was released.
376 PointerReleased {
377 /// The position of the pointer, in logical pixels relative to the top left corner of the window.
378 position: LogicalPosition,
379 /// The button that was released.
380 button: PointerEventButton,
381 },
382 /// The position of the pointer has changed.
383 PointerMoved {
384 /// The new position of the pointer, in logical pixels relative to the top left corner of the window.
385 position: LogicalPosition,
386 },
387 /// The wheel button of a mouse was rotated to initiate scrolling.
388 PointerScrolled {
389 /// The position of the pointer when the scroll occurred.
390 position: LogicalPosition,
391 /// The amount of logical pixels to scroll in the horizontal direction.
392 delta_x: f32,
393 /// The amount of logical pixels to scroll in the vertical direction.
394 delta_y: f32,
395 },
396 /// The pointer exited the window.
397 ///
398 /// Always reported as [`Accepted`](WindowEventDispatchResult::Accepted).
399 PointerExited,
400 /// A key was pressed.
401 KeyPressed {
402 /// The unicode representation of the key pressed.
403 ///
404 /// # Example
405 /// A specific key can be mapped to a unicode by using the [`Key`] enum
406 /// ```rust
407 /// let _ = slint::platform::WindowEvent::KeyPressed { text: slint::platform::Key::Shift.into() };
408 /// ```
409 text: SharedString,
410 },
411 /// A key press was auto-repeated.
412 KeyPressRepeated {
413 /// The unicode representation of the key pressed.
414 ///
415 /// # Example
416 /// A specific key can be mapped to a unicode by using the [`Key`] enum
417 /// ```rust
418 /// let _ = slint::platform::WindowEvent::KeyPressRepeated { text: slint::platform::Key::Shift.into() };
419 /// ```
420 text: SharedString,
421 },
422 /// A key was released.
423 KeyReleased {
424 /// The unicode representation of the key released.
425 ///
426 /// # Example
427 /// A specific key can be mapped to a unicode by using the [`Key`] enum
428 /// ```rust
429 /// let _ = slint::platform::WindowEvent::KeyReleased { text: slint::platform::Key::Shift.into() };
430 /// ```
431 text: SharedString,
432 },
433 /// The window's scale factor has changed. This can happen for example when the display's resolution
434 /// changes, the user selects a new scale factor in the system settings, or the window is moved to a
435 /// different screen.
436 /// Platform implementations should dispatch this event also right after the initial window creation,
437 /// to set the initial scale factor the windowing system provided for the window.
438 ScaleFactorChanged {
439 /// The window system provided scale factor to map logical pixels to physical pixels.
440 scale_factor: f32,
441 },
442 /// The window was resized.
443 ///
444 /// The backend must send this event to ensure that the `width` and `height` property of the root Window
445 /// element are properly set.
446 Resized {
447 /// The new logical size of the window.
448 size: LogicalSize,
449 },
450 /// The user requested to close the window.
451 ///
452 /// The backend should send this event when the user tries to close the window,for example by pressing the close button.
453 ///
454 /// This will have the effect of invoking the callback set in [`Window::on_close_requested()`](`crate::api::Window::on_close_requested()`)
455 /// and then hiding the window depending on the return value of the callback.
456 CloseRequested,
457
458 /// The Window was activated or de-activated.
459 ///
460 /// The backend should dispatch this event with true when the window gains focus
461 /// and false when the window loses focus.
462 WindowActiveChanged(bool),
463
464 /// An event that one of Slint's own backends delivers in the runtime's internal representation.
465 ///
466 /// This isn't public API, use [`WindowEvent::internal()`] to construct it.
467 #[doc(hidden)]
468 Internal(InternalEventBox),
469}
470
471impl WindowEvent {
472 /// The position of the cursor for this event, if any
473 pub fn position(&self) -> Option<LogicalPosition> {
474 match self {
475 WindowEvent::PointerPressed { position, .. } => Some(*position),
476 WindowEvent::PointerReleased { position, .. } => Some(*position),
477 WindowEvent::PointerMoved { position } => Some(*position),
478 WindowEvent::PointerScrolled { position, .. } => Some(*position),
479 WindowEvent::Internal(event) => event.position(),
480 _ => None,
481 }
482 }
483
484 /// Wraps an event in the runtime's internal representation,
485 /// for Slint's own backends to deliver via [`Window::dispatch_event_with_result()`](crate::api::Window::dispatch_event_with_result).
486 #[doc(hidden)]
487 pub fn internal(event: impl Into<InternalEvent>) -> Self {
488 Self::Internal(InternalEventBox::new(event.into()))
489 }
490}
491
492/// Owning pointer to an [`InternalEvent`].
493///
494/// The payload lives behind a pointer, so that the size of [`WindowEvent`] and the way the C++
495/// bindings represent it don't depend on the internal event.
496/// It's a dedicated type rather than a `Box`, because a `Box` of an unsized type is two pointers
497/// wide, which the generated C++ struct wouldn't match.
498#[doc(hidden)]
499#[repr(transparent)]
500pub struct InternalEventBox(core::ptr::NonNull<InternalEvent>);
501
502// Safety: the box exclusively owns the event it points to, which the assertion below keeps
503// `Send` and `Sync`.
504#[allow(unsafe_code)]
505unsafe impl Send for InternalEventBox {}
506#[allow(unsafe_code)]
507unsafe impl Sync for InternalEventBox {}
508
509// A backend may build events on one thread and dispatch them from the event loop's thread.
510const _: () = {
511 const fn assert_send_sync<T: Send + Sync>() {}
512 assert_send_sync::<WindowEvent>();
513 assert_send_sync::<InternalEvent>();
514};
515
516#[allow(unsafe_code)]
517impl InternalEventBox {
518 fn new(event: InternalEvent) -> Self {
519 // Safety: `Box::into_raw` never returns null.
520 Self(unsafe { core::ptr::NonNull::new_unchecked(Box::into_raw(Box::new(event))) })
521 }
522
523 /// Takes the event out of the box.
524 pub(crate) fn into_inner(self) -> InternalEvent {
525 let this = core::mem::ManuallyDrop::new(self);
526 // Safety: the pointer comes from `Box::into_raw` in `new()`, and `ManuallyDrop` keeps
527 // `Drop` from freeing it a second time.
528 *unsafe { Box::from_raw(this.0.as_ptr()) }
529 }
530}
531
532#[allow(unsafe_code)]
533impl Drop for InternalEventBox {
534 fn drop(&mut self) {
535 // Safety: the pointer comes from `Box::into_raw` in `new()` and is freed only here or in
536 // `into_inner()`, which doesn't run this.
537 drop(unsafe { Box::from_raw(self.0.as_ptr()) });
538 }
539}
540
541#[allow(unsafe_code)]
542impl core::ops::Deref for InternalEventBox {
543 type Target = InternalEvent;
544 fn deref(&self) -> &InternalEvent {
545 // Safety: the pointer comes from `Box::into_raw` in `new()` and stays valid until `Drop`.
546 unsafe { self.0.as_ref() }
547 }
548}
549
550impl Clone for InternalEventBox {
551 fn clone(&self) -> Self {
552 Self::new(InternalEvent::clone(self))
553 }
554}
555
556impl PartialEq for InternalEventBox {
557 fn eq(&self, other: &Self) -> bool {
558 **self == **other
559 }
560}
561
562impl core::fmt::Debug for InternalEventBox {
563 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
564 InternalEvent::fmt(self, f)
565 }
566}
567
568/// An event that one of Slint's own backends delivers in the representation the runtime uses internally.
569///
570/// These carry information that the [`WindowEvent`] variants can't express,
571/// such as the touch finger id, the click count, the gesture phase, drag payloads or input method composition.
572/// Backends wrap them with [`WindowEvent::internal()`] and dispatch them like any other event,
573/// so that all input takes the same path into the runtime and is observed by the window event hook.
574///
575/// This isn't public API: the variants and their payload change without notice.
576#[doc(hidden)]
577#[derive(Debug, Clone, PartialEq)]
578pub enum InternalEvent {
579 /// A pointer event, including the ones that have no public representation, such as gestures.
580 Mouse(crate::input::BackendMouseEvent),
581 /// A key event, including input method composition updates.
582 Key(crate::input::InternalKeyEvent),
583 /// A touch point update, which the runtime turns into pointer or gesture events.
584 Touch {
585 /// The id of the finger that produced the event.
586 /// Must be non-negative and distinct among the fingers currently down.
587 /// Ids may be reused once a finger lifts.
588 id: i32,
589 /// The position of the finger, in logical coordinates.
590 position: crate::lengths::LogicalPoint,
591 /// Whether the finger was put down, moved, lifted or cancelled.
592 phase: crate::input::TouchPhase,
593 },
594}
595
596impl From<crate::input::BackendMouseEvent> for InternalEvent {
597 fn from(event: crate::input::BackendMouseEvent) -> Self {
598 Self::Mouse(event)
599 }
600}
601
602impl From<crate::input::InternalKeyEvent> for InternalEvent {
603 fn from(event: crate::input::InternalKeyEvent) -> Self {
604 Self::Key(event)
605 }
606}
607
608impl InternalEvent {
609 /// The public event this event corresponds to, if any.
610 ///
611 /// This is what the window event hook observes,
612 /// so that hooks only ever see events they could dispatch themselves.
613 /// Events without a public representation aren't reported:
614 /// gestures, touch and input method composition.
615 pub(crate) fn public_representation(&self) -> Option<WindowEvent> {
616 use crate::input::{BackendMouseEvent, KeyEventType};
617 use crate::lengths::logical_position_to_api;
618
619 match self {
620 Self::Mouse(event) => match event {
621 BackendMouseEvent::Pressed { position, button, .. } => {
622 Some(WindowEvent::PointerPressed {
623 position: logical_position_to_api(*position),
624 button: *button,
625 })
626 }
627 BackendMouseEvent::Released { position, button, .. } => {
628 Some(WindowEvent::PointerReleased {
629 position: logical_position_to_api(*position),
630 button: *button,
631 })
632 }
633 BackendMouseEvent::Moved { position, .. } => {
634 Some(WindowEvent::PointerMoved { position: logical_position_to_api(*position) })
635 }
636 BackendMouseEvent::Wheel { position, delta_x, delta_y, .. } => {
637 Some(WindowEvent::PointerScrolled {
638 position: logical_position_to_api(*position),
639 delta_x: *delta_x as f32,
640 delta_y: *delta_y as f32,
641 })
642 }
643 BackendMouseEvent::Exit => Some(WindowEvent::PointerExited),
644 BackendMouseEvent::PinchGesture { .. }
645 | BackendMouseEvent::RotationGesture { .. } => None,
646 },
647 Self::Key(event) => {
648 let text = event.key_event.text.clone();
649 match event.event_type {
650 KeyEventType::KeyPressed if event.key_event.repeat => {
651 Some(WindowEvent::KeyPressRepeated { text })
652 }
653 KeyEventType::KeyPressed => Some(WindowEvent::KeyPressed { text }),
654 KeyEventType::KeyReleased => Some(WindowEvent::KeyReleased { text }),
655 KeyEventType::UpdateComposition | KeyEventType::CommitComposition => None,
656 }
657 }
658 // There's no public touch event, and the pointer events the runtime synthesizes from
659 // a touch point carry a finger id that `WindowEvent` can't express.
660 Self::Touch { .. } => None,
661 }
662 }
663
664 /// The position of the pointer or finger for this event, if any.
665 fn position(&self) -> Option<LogicalPosition> {
666 match self {
667 Self::Mouse(event) => crate::input::MouseEvent::from(*event)
668 .position()
669 .map(crate::lengths::logical_position_to_api),
670 Self::Key(_) => None,
671 Self::Touch { position, .. } => {
672 Some(crate::lengths::logical_position_to_api(*position))
673 }
674 }
675 }
676}
677
678/**
679 * Test the animation tick is updated when a platform is set
680```rust
681use i_slint_core::platform::*;
682struct DummyBackend;
683impl Platform for DummyBackend {
684 fn create_window_adapter(
685 &self,
686 ) -> Result<std::rc::Rc<dyn WindowAdapter>, PlatformError> {
687 Err(PlatformError::Other("not implemented".into()))
688 }
689 fn duration_since_start(&self) -> core::time::Duration {
690 core::time::Duration::from_millis(100)
691 }
692}
693
694let start_time = i_slint_backend_testing::get_mocked_time();
695i_slint_core::platform::set_platform(Box::new(DummyBackend{}));
696let time_after_platform_init = i_slint_backend_testing::get_mocked_time();
697assert_ne!(time_after_platform_init, start_time);
698assert_eq!(time_after_platform_init, 100);
699```
700 */
701#[cfg(doctest)]
702const _ANIM_TICK_UPDATED_ON_PLATFORM_SET: () = ();