#![warn(missing_docs)]
pub use crate::api::PlatformError;
use crate::api::{LogicalPosition, LogicalSize};
pub use crate::renderer::Renderer;
#[cfg(feature = "software-renderer")]
pub use crate::software_renderer;
#[cfg(all(not(feature = "std"), feature = "unsafe-single-threaded"))]
use crate::unsafe_single_threaded::OnceCell;
pub use crate::window::{LayoutConstraints, WindowAdapter, WindowProperties};
use crate::SharedString;
#[cfg(not(feature = "std"))]
use alloc::boxed::Box;
use alloc::rc::Rc;
#[cfg(not(feature = "std"))]
use alloc::string::String;
#[cfg(all(feature = "std", not(target_os = "android")))]
use once_cell::sync::OnceCell;
#[cfg(all(feature = "std", not(target_arch = "wasm32")))]
use std::time;
#[cfg(target_arch = "wasm32")]
use web_time as time;
pub trait Platform {
fn create_window_adapter(&self) -> Result<Rc<dyn WindowAdapter>, PlatformError>;
fn run_event_loop(&self) -> Result<(), PlatformError> {
Err(PlatformError::NoEventLoopProvider)
}
#[doc(hidden)]
fn process_events(
&self,
_timeout: core::time::Duration,
_: crate::InternalToken,
) -> Result<core::ops::ControlFlow<()>, PlatformError> {
Err(PlatformError::NoEventLoopProvider)
}
#[doc(hidden)]
#[deprecated(
note = "i-slint-core takes care of closing behavior. Application should call run_event_loop_until_quit"
)]
fn set_event_loop_quit_on_last_window_closed(&self, quit_on_last_window_closed: bool) {
assert!(!quit_on_last_window_closed);
crate::context::GLOBAL_CONTEXT
.with(|ctx| (*ctx.get().unwrap().0.window_count.borrow_mut()) += 1);
}
fn new_event_loop_proxy(&self) -> Option<Box<dyn EventLoopProxy>> {
None
}
fn duration_since_start(&self) -> core::time::Duration {
#[cfg(feature = "std")]
{
let the_beginning = *INITIAL_INSTANT.get_or_init(time::Instant::now);
time::Instant::now() - the_beginning
}
#[cfg(not(feature = "std"))]
unimplemented!("The platform abstraction must implement `duration_since_start`")
}
fn click_interval(&self) -> core::time::Duration {
core::time::Duration::from_millis(500)
}
fn set_clipboard_text(&self, _text: &str, _clipboard: Clipboard) {}
fn clipboard_text(&self, _clipboard: Clipboard) -> Option<String> {
None
}
fn debug_log(&self, _arguments: core::fmt::Arguments) {
crate::tests::default_debug_log(_arguments);
}
}
#[repr(u8)]
#[non_exhaustive]
#[derive(PartialEq, Clone, Default)]
pub enum Clipboard {
#[default]
DefaultClipboard = 0,
SelectionClipboard = 1,
}
pub trait EventLoopProxy: Send + Sync {
fn quit_event_loop(&self) -> Result<(), crate::api::EventLoopError>;
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<time::Instant> = once_cell::sync::OnceCell::new();
#[cfg(feature = "std")]
impl std::convert::From<crate::animations::Instant> for time::Instant {
fn from(our_instant: crate::animations::Instant) -> Self {
let the_beginning = *INITIAL_INSTANT.get_or_init(time::Instant::now);
the_beginning + core::time::Duration::from_millis(our_instant.0)
}
}
#[cfg(not(target_os = "android"))]
static EVENTLOOP_PROXY: OnceCell<Box<dyn EventLoopProxy + 'static>> = OnceCell::new();
#[cfg(target_os = "android")]
static EVENTLOOP_PROXY: std::sync::Mutex<Option<Box<dyn EventLoopProxy + 'static>>> =
std::sync::Mutex::new(None);
pub(crate) fn with_event_loop_proxy<R>(f: impl FnOnce(Option<&dyn EventLoopProxy>) -> R) -> R {
#[cfg(not(target_os = "android"))]
return f(EVENTLOOP_PROXY.get().map(core::ops::Deref::deref));
#[cfg(target_os = "android")]
return f(EVENTLOOP_PROXY.lock().unwrap().as_ref().map(core::ops::Deref::deref));
}
#[derive(Debug, Clone, PartialEq)]
#[repr(C)]
#[non_exhaustive]
pub enum SetPlatformError {
AlreadySet,
}
impl core::fmt::Display for SetPlatformError {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
SetPlatformError::AlreadySet => {
f.write_str("The platform has already been initialized.")
}
}
}
}
#[cfg(feature = "std")]
impl std::error::Error for SetPlatformError {}
pub fn set_platform(platform: Box<dyn Platform + 'static>) -> Result<(), SetPlatformError> {
crate::context::GLOBAL_CONTEXT.with(|instance| {
if instance.get().is_some() {
return Err(SetPlatformError::AlreadySet);
}
if let Some(proxy) = platform.new_event_loop_proxy() {
#[cfg(not(target_os = "android"))]
{
EVENTLOOP_PROXY.set(proxy).map_err(|_| SetPlatformError::AlreadySet)?;
}
#[cfg(target_os = "android")]
{
*EVENTLOOP_PROXY.lock().unwrap() = Some(proxy);
}
}
instance
.set(crate::SlintContext::new(platform))
.map_err(|_| SetPlatformError::AlreadySet)
.unwrap();
update_timers_and_animations();
Ok(())
})
}
pub fn update_timers_and_animations() {
crate::animations::update_animations();
crate::timers::TimerList::maybe_activate_timers(crate::animations::Instant::now());
crate::properties::ChangeTracker::run_change_handlers();
}
pub fn duration_until_next_timer_update() -> Option<core::time::Duration> {
crate::timers::TimerList::next_timeout().map(|timeout| {
let duration_since_start = crate::context::GLOBAL_CONTEXT
.with(|p| p.get().map(|p| p.platform().duration_since_start()))
.unwrap_or_default();
core::time::Duration::from_millis(
timeout.0.saturating_sub(duration_since_start.as_millis() as u64),
)
})
}
pub use crate::input::key_codes::Key;
pub use crate::input::PointerEventButton;
#[allow(missing_docs)]
#[derive(Debug, Clone, PartialEq)]
#[non_exhaustive]
#[repr(u32)]
pub enum WindowEvent {
PointerPressed {
position: LogicalPosition,
button: PointerEventButton,
},
PointerReleased {
position: LogicalPosition,
button: PointerEventButton,
},
PointerMoved { position: LogicalPosition },
PointerScrolled {
position: LogicalPosition,
delta_x: f32,
delta_y: f32,
},
PointerExited,
KeyPressed {
text: SharedString,
},
KeyPressRepeated {
text: SharedString,
},
KeyReleased {
text: SharedString,
},
ScaleFactorChanged {
scale_factor: f32,
},
Resized {
size: LogicalSize,
},
CloseRequested,
WindowActiveChanged(bool),
}
impl WindowEvent {
pub fn position(&self) -> Option<LogicalPosition> {
match self {
WindowEvent::PointerPressed { position, .. } => Some(*position),
WindowEvent::PointerReleased { position, .. } => Some(*position),
WindowEvent::PointerMoved { position } => Some(*position),
WindowEvent::PointerScrolled { position, .. } => Some(*position),
_ => None,
}
}
}
#[cfg(doctest)]
const _ANIM_TICK_UPDATED_ON_PLATFORM_SET: () = ();