1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
// Copyright © SixtyFPS GmbH <info@slint-ui.com>
// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-commercial

/*!
The backend is the abstraction for crates that need to do the actual drawing and event loop
*/

#![warn(missing_docs)]

pub use crate::software_renderer;
#[cfg(all(not(feature = "std"), feature = "unsafe-single-threaded"))]
use crate::unsafe_single_threaded::{thread_local, OnceCell};
pub use crate::window::WindowAdapter;
use alloc::boxed::Box;
use alloc::rc::Rc;
use alloc::string::String;
#[cfg(feature = "std")]
use once_cell::sync::OnceCell;

/// This trait defines the interface between Slint and platform APIs typically provided by operating and windowing systems.
pub trait Platform {
    /// Instantiate a window for a component.
    fn create_window_adapter(&self) -> Rc<dyn WindowAdapter>;

    /// Spins an event loop and renders the visible windows.
    fn run_event_loop(&self) {
        unimplemented!("The backend does not implement running an eventloop")
    }

    /// Specify if the event loop should quit quen the last window is closed.
    /// The default behavior is `true`.
    /// When this is set to `false`, the event loop must keep running until
    /// [`slint::quit_event_loop()`](crate::api::quit_event_loop()) is called
    #[doc(hidden)]
    fn set_event_loop_quit_on_last_window_closed(&self, _quit_on_last_window_closed: bool) {
        unimplemented!("The backend does not implement event loop quit behaviors")
    }

    /// Return an [`EventLoopProxy`] that can be used to send event to the event loop
    ///
    /// If this function returns `None` (the default implementation), then it will
    /// not be possible to send event to the event loop and the function
    /// [`slint::invoke_from_event_loop()`](crate::api::invoke_from_event_loop) and
    /// [`slint::quit_event_loop()`](crate::api::quit_event_loop) will panic
    fn new_event_loop_proxy(&self) -> Option<Box<dyn EventLoopProxy>> {
        None
    }

    /// Returns the current time as a monotonic duration since the start of the program
    ///
    /// This is used by the animations and timer to compute the elapsed time.
    ///
    /// When the `std` feature is enabled, this function is implemented in terms of
    /// [`std::time::Instant::now()`], but on `#![no_std]` platform, this function must
    /// be implemented.
    fn duration_since_start(&self) -> core::time::Duration {
        #[cfg(feature = "std")]
        {
            let the_beginning = *INITIAL_INSTANT.get_or_init(instant::Instant::now);
            instant::Instant::now() - the_beginning
        }
        #[cfg(not(feature = "std"))]
        unimplemented!("The platform abstraction must implement `duration_since_start`")
    }

    /// Sends the given text into the system clipboard
    fn set_clipboard_text(&self, _text: &str) {}
    /// Returns a copy of text stored in the system clipboard, if any.
    fn clipboard_text(&self) -> Option<String> {
        None
    }

    /// This function is called when debug() is used in .slint files. The implementation
    /// should direct the output to some developer visible terminal. The default implementation
    /// uses stderr if available, or `console.log` when targeting wasm.
    fn debug_log(&self, _arguments: core::fmt::Arguments) {
        crate::tests::default_debug_log(_arguments);
    }
}

/// Trait that is returned by the [`Platform::new_event_loop_proxy`]
///
/// This are the implementation details for the function that may need to
/// communicate with the eventloop from different thread
pub trait EventLoopProxy: Send + Sync {
    /// Exits the event loop.
    ///
    /// This is what is called by [`slint::quit_event_loop()`](crate::api::quit_event_loop)
    fn quit_event_loop(&self) -> Result<(), crate::api::EventLoopError>;

    /// Invoke the function from the event loop.
    ///
    /// This is what is called by [`slint::invoke_from_event_loop()`](crate::api::invoke_from_event_loop)
    fn invoke_from_event_loop(
        &self,
        event: Box<dyn FnOnce() + Send>,
    ) -> Result<(), crate::api::EventLoopError>;
}

#[cfg(feature = "std")]
static INITIAL_INSTANT: once_cell::sync::OnceCell<instant::Instant> =
    once_cell::sync::OnceCell::new();

#[cfg(feature = "std")]
impl std::convert::From<crate::animations::Instant> for instant::Instant {
    fn from(our_instant: crate::animations::Instant) -> Self {
        let the_beginning = *INITIAL_INSTANT.get_or_init(instant::Instant::now);
        the_beginning + core::time::Duration::from_millis(our_instant.0)
    }
}

thread_local! {
    /// Internal: Singleton of the platform abstraction.
    pub(crate) static PLATFORM_INSTANCE : once_cell::unsync::OnceCell<Box<dyn Platform>>
        = once_cell::unsync::OnceCell::new()
}
static EVENTLOOP_PROXY: OnceCell<Box<dyn EventLoopProxy + 'static>> = OnceCell::new();

pub(crate) fn event_loop_proxy() -> Option<&'static dyn EventLoopProxy> {
    EVENTLOOP_PROXY.get().map(core::ops::Deref::deref)
}

/// This enum describes the different error scenarios that may occur when [`set_platform`]
/// fails.
#[derive(Debug, Clone)]
#[repr(C)]
#[non_exhaustive]
pub enum SetPlatformError {
    /// The platform has been initialized in an earlier call to [`set_platform`].
    AlreadySet,
}

/// Set the Slint platform abstraction.
///
/// If the platform abstraction was already set this will return `Err`.
pub fn set_platform(platform: Box<dyn Platform + 'static>) -> Result<(), SetPlatformError> {
    PLATFORM_INSTANCE.with(|instance| {
        if instance.get().is_some() {
            return Err(SetPlatformError::AlreadySet);
        }
        if let Some(proxy) = platform.new_event_loop_proxy() {
            EVENTLOOP_PROXY.set(proxy).map_err(|_| SetPlatformError::AlreadySet)?
        }
        instance.set(platform.into()).map_err(|_| SetPlatformError::AlreadySet).unwrap();
        Ok(())
    })
}

/// Call this function to update and potentially activate any pending timers, as well
/// as advance the state of any active animtaions.
///
/// This function should be called before rendering or processing input event, at the
/// beginning of each event loop iteration.
pub fn update_timers_and_animations() {
    crate::timers::TimerList::maybe_activate_timers(crate::animations::Instant::now());
    crate::animations::update_animations();
}

/// Returns the duration before the next timer is expected to be activated. This is the
/// largest amount of time that you can wait before calling [`update_timers_and_animations()`].
///
/// Call this in your own event loop implementation to know how long the current thread can
/// go to sleep. Note that this does not take currently activate animations into account.
/// Only go to sleep if [`Window::has_active_animations()`](crate::api::Window::has_active_animations())
/// returns false.
pub fn duration_until_next_timer_update() -> Option<core::time::Duration> {
    crate::timers::TimerList::next_timeout().map(|timeout| {
        let duration_since_start = crate::platform::PLATFORM_INSTANCE
            .with(|p| p.get().map(|p| p.duration_since_start()))
            .unwrap_or_default();
        core::time::Duration::from_millis(
            timeout.0.saturating_sub(duration_since_start.as_millis() as u64),
        )
    })
}