Skip to main content

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: () = ();