#![warn(missing_docs)]
#[cfg(target_has_atomic = "ptr")]
pub use crate::future::*;
use crate::graphics::{Rgba8Pixel, SharedPixelBuffer};
use crate::input::{KeyEventType, MouseEvent};
use crate::item_tree::ItemTreeVTable;
use crate::window::{WindowAdapter, WindowInner};
#[cfg(not(feature = "std"))]
use alloc::boxed::Box;
#[cfg(not(feature = "std"))]
use alloc::string::String;
#[derive(Debug, Default, Copy, Clone, PartialEq)]
#[repr(C)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct LogicalPosition {
pub x: f32,
pub y: f32,
}
impl LogicalPosition {
pub const fn new(x: f32, y: f32) -> Self {
Self { x, y }
}
pub fn from_physical(physical_pos: PhysicalPosition, scale_factor: f32) -> Self {
Self::new(physical_pos.x as f32 / scale_factor, physical_pos.y as f32 / scale_factor)
}
pub fn to_physical(&self, scale_factor: f32) -> PhysicalPosition {
PhysicalPosition::from_logical(*self, scale_factor)
}
pub(crate) fn to_euclid(self) -> crate::lengths::LogicalPoint {
[self.x as _, self.y as _].into()
}
pub(crate) fn from_euclid(p: crate::lengths::LogicalPoint) -> Self {
Self::new(p.x as _, p.y as _)
}
}
#[derive(Debug, Default, Copy, Clone, Eq, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct PhysicalPosition {
pub x: i32,
pub y: i32,
}
impl PhysicalPosition {
pub const fn new(x: i32, y: i32) -> Self {
Self { x, y }
}
pub fn from_logical(logical_pos: LogicalPosition, scale_factor: f32) -> Self {
Self::new((logical_pos.x * scale_factor) as i32, (logical_pos.y * scale_factor) as i32)
}
pub fn to_logical(&self, scale_factor: f32) -> LogicalPosition {
LogicalPosition::from_physical(*self, scale_factor)
}
#[cfg(feature = "ffi")]
pub(crate) fn to_euclid(&self) -> crate::graphics::euclid::default::Point2D<i32> {
[self.x, self.y].into()
}
#[cfg(feature = "ffi")]
pub(crate) fn from_euclid(p: crate::graphics::euclid::default::Point2D<i32>) -> Self {
Self::new(p.x as _, p.y as _)
}
}
#[derive(Clone, Debug, derive_more::From, PartialEq)]
pub enum WindowPosition {
Physical(PhysicalPosition),
Logical(LogicalPosition),
}
impl WindowPosition {
pub fn to_physical(&self, scale_factor: f32) -> PhysicalPosition {
match self {
WindowPosition::Physical(pos) => *pos,
WindowPosition::Logical(pos) => pos.to_physical(scale_factor),
}
}
}
#[repr(C)]
#[derive(Debug, Default, Copy, Clone, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct LogicalSize {
pub width: f32,
pub height: f32,
}
impl LogicalSize {
pub const fn new(width: f32, height: f32) -> Self {
Self { width, height }
}
pub fn from_physical(physical_size: PhysicalSize, scale_factor: f32) -> Self {
Self::new(
physical_size.width as f32 / scale_factor,
physical_size.height as f32 / scale_factor,
)
}
pub fn to_physical(&self, scale_factor: f32) -> PhysicalSize {
PhysicalSize::from_logical(*self, scale_factor)
}
pub(crate) fn to_euclid(self) -> crate::lengths::LogicalSize {
[self.width as _, self.height as _].into()
}
pub(crate) fn from_euclid(p: crate::lengths::LogicalSize) -> Self {
Self::new(p.width as _, p.height as _)
}
}
#[derive(Debug, Default, Copy, Clone, Eq, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct PhysicalSize {
pub width: u32,
pub height: u32,
}
impl PhysicalSize {
pub const fn new(width: u32, height: u32) -> Self {
Self { width, height }
}
pub fn from_logical(logical_size: LogicalSize, scale_factor: f32) -> Self {
Self::new(
(logical_size.width * scale_factor) as u32,
(logical_size.height * scale_factor) as u32,
)
}
pub fn to_logical(&self, scale_factor: f32) -> LogicalSize {
LogicalSize::from_physical(*self, scale_factor)
}
#[cfg(feature = "ffi")]
pub(crate) fn to_euclid(&self) -> crate::graphics::euclid::default::Size2D<u32> {
[self.width, self.height].into()
}
}
#[derive(Clone, Debug, derive_more::From, PartialEq)]
pub enum WindowSize {
Physical(PhysicalSize),
Logical(LogicalSize),
}
impl WindowSize {
pub fn to_physical(&self, scale_factor: f32) -> PhysicalSize {
match self {
WindowSize::Physical(size) => *size,
WindowSize::Logical(size) => size.to_physical(scale_factor),
}
}
pub fn to_logical(&self, scale_factor: f32) -> LogicalSize {
match self {
WindowSize::Physical(size) => size.to_logical(scale_factor),
WindowSize::Logical(size) => *size,
}
}
}
#[test]
fn logical_physical_pos() {
use crate::graphics::euclid::approxeq::ApproxEq;
let phys = PhysicalPosition::new(100, 50);
let logical = phys.to_logical(2.);
assert!(logical.x.approx_eq(&50.));
assert!(logical.y.approx_eq(&25.));
assert_eq!(logical.to_physical(2.), phys);
}
#[test]
fn logical_physical_size() {
use crate::graphics::euclid::approxeq::ApproxEq;
let phys = PhysicalSize::new(100, 50);
let logical = phys.to_logical(2.);
assert!(logical.width.approx_eq(&50.));
assert!(logical.height.approx_eq(&25.));
assert_eq!(logical.to_physical(2.), phys);
}
#[derive(Clone)]
#[non_exhaustive]
pub enum GraphicsAPI<'a> {
NativeOpenGL {
get_proc_address: &'a dyn Fn(&core::ffi::CStr) -> *const core::ffi::c_void,
},
WebGL {
canvas_element_id: &'a str,
context_type: &'a str,
},
}
impl<'a> core::fmt::Debug for GraphicsAPI<'a> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
GraphicsAPI::NativeOpenGL { .. } => write!(f, "GraphicsAPI::NativeOpenGL"),
GraphicsAPI::WebGL { context_type, .. } => {
write!(f, "GraphicsAPI::WebGL(context_type = {})", context_type)
}
}
}
}
#[derive(Debug, Clone)]
#[repr(u8)]
#[non_exhaustive]
pub enum RenderingState {
RenderingSetup,
BeforeRendering,
AfterRendering,
RenderingTeardown,
}
#[doc(hidden)]
pub trait RenderingNotifier {
fn notify(&mut self, state: RenderingState, graphics_api: &GraphicsAPI);
}
impl<F: FnMut(RenderingState, &GraphicsAPI)> RenderingNotifier for F {
fn notify(&mut self, state: RenderingState, graphics_api: &GraphicsAPI) {
self(state, graphics_api)
}
}
#[derive(Debug, Clone)]
#[repr(u8)]
#[non_exhaustive]
pub enum SetRenderingNotifierError {
Unsupported,
AlreadySet,
}
impl core::fmt::Display for SetRenderingNotifierError {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
Self::Unsupported => {
f.write_str("The rendering backend does not support rendering notifiers.")
}
Self::AlreadySet => f.write_str(
"There is already a rendering notifier set, multiple notifiers are not supported.",
),
}
}
}
#[cfg(feature = "std")]
impl std::error::Error for SetRenderingNotifierError {}
#[cfg(feature = "raw-window-handle-06")]
#[derive(Clone)]
enum WindowHandleInner {
HandleByAdapter(alloc::rc::Rc<dyn WindowAdapter>),
HandleByRcRWH {
window_handle_provider: alloc::rc::Rc<dyn raw_window_handle_06::HasWindowHandle>,
display_handle_provider: alloc::rc::Rc<dyn raw_window_handle_06::HasDisplayHandle>,
},
}
#[cfg(feature = "raw-window-handle-06")]
#[derive(Clone)]
pub struct WindowHandle {
inner: WindowHandleInner,
}
#[cfg(feature = "raw-window-handle-06")]
impl raw_window_handle_06::HasWindowHandle for WindowHandle {
fn window_handle<'a>(
&'a self,
) -> Result<raw_window_handle_06::WindowHandle<'a>, raw_window_handle_06::HandleError> {
match &self.inner {
WindowHandleInner::HandleByAdapter(adapter) => adapter.window_handle_06(),
WindowHandleInner::HandleByRcRWH { window_handle_provider, .. } => {
window_handle_provider.window_handle()
}
}
}
}
#[cfg(feature = "raw-window-handle-06")]
impl raw_window_handle_06::HasDisplayHandle for WindowHandle {
fn display_handle<'a>(
&'a self,
) -> Result<raw_window_handle_06::DisplayHandle<'a>, raw_window_handle_06::HandleError> {
match &self.inner {
WindowHandleInner::HandleByAdapter(adapter) => adapter.display_handle_06(),
WindowHandleInner::HandleByRcRWH { display_handle_provider, .. } => {
display_handle_provider.display_handle()
}
}
}
}
#[repr(transparent)]
pub struct Window(pub(crate) WindowInner);
#[derive(Copy, Clone, Debug, PartialEq, Default)]
#[repr(u8)]
pub enum CloseRequestResponse {
#[default]
HideWindow = 0,
KeepWindowShown = 1,
}
impl Window {
pub fn new(window_adapter_weak: alloc::rc::Weak<dyn WindowAdapter>) -> Self {
Self(WindowInner::new(window_adapter_weak))
}
pub fn show(&self) -> Result<(), PlatformError> {
self.0.show()
}
pub fn hide(&self) -> Result<(), PlatformError> {
self.0.hide()
}
pub fn set_rendering_notifier(
&self,
callback: impl FnMut(RenderingState, &GraphicsAPI) + 'static,
) -> Result<(), SetRenderingNotifierError> {
self.0.window_adapter().renderer().set_rendering_notifier(Box::new(callback))
}
pub fn on_close_requested(&self, callback: impl FnMut() -> CloseRequestResponse + 'static) {
self.0.on_close_requested(callback);
}
pub fn request_redraw(&self) {
self.0.window_adapter().request_redraw()
}
pub fn scale_factor(&self) -> f32 {
self.0.scale_factor()
}
pub fn position(&self) -> PhysicalPosition {
self.0.window_adapter().position().unwrap_or_default()
}
pub fn set_position(&self, position: impl Into<WindowPosition>) {
let position = position.into();
self.0.window_adapter().set_position(position)
}
pub fn size(&self) -> PhysicalSize {
self.0.window_adapter().size()
}
pub fn set_size(&self, size: impl Into<WindowSize>) {
let size = size.into();
crate::window::WindowAdapter::set_size(&*self.0.window_adapter(), size);
}
pub fn is_fullscreen(&self) -> bool {
self.0.is_fullscreen()
}
pub fn set_fullscreen(&self, fullscreen: bool) {
self.0.set_fullscreen(fullscreen);
}
pub fn is_maximized(&self) -> bool {
self.0.is_maximized()
}
pub fn set_maximized(&self, maximized: bool) {
self.0.set_maximized(maximized);
}
pub fn is_minimized(&self) -> bool {
self.0.is_minimized()
}
pub fn set_minimized(&self, minimized: bool) {
self.0.set_minimized(minimized);
}
pub fn dispatch_event(&self, event: crate::platform::WindowEvent) {
match event {
crate::platform::WindowEvent::PointerPressed { position, button } => {
self.0.process_mouse_input(MouseEvent::Pressed {
position: position.to_euclid().cast(),
button,
click_count: 0,
});
}
crate::platform::WindowEvent::PointerReleased { position, button } => {
self.0.process_mouse_input(MouseEvent::Released {
position: position.to_euclid().cast(),
button,
click_count: 0,
});
}
crate::platform::WindowEvent::PointerMoved { position } => {
self.0.process_mouse_input(MouseEvent::Moved {
position: position.to_euclid().cast(),
});
}
crate::platform::WindowEvent::PointerScrolled { position, delta_x, delta_y } => {
self.0.process_mouse_input(MouseEvent::Wheel {
position: position.to_euclid().cast(),
delta_x: delta_x as _,
delta_y: delta_y as _,
});
}
crate::platform::WindowEvent::PointerExited => {
self.0.process_mouse_input(MouseEvent::Exit)
}
crate::platform::WindowEvent::KeyPressed { text } => {
self.0.process_key_input(crate::input::KeyEvent {
text,
repeat: false,
event_type: KeyEventType::KeyPressed,
..Default::default()
})
}
crate::platform::WindowEvent::KeyPressRepeated { text } => {
self.0.process_key_input(crate::input::KeyEvent {
text,
repeat: true,
event_type: KeyEventType::KeyPressed,
..Default::default()
})
}
crate::platform::WindowEvent::KeyReleased { text } => {
self.0.process_key_input(crate::input::KeyEvent {
text,
event_type: KeyEventType::KeyReleased,
..Default::default()
})
}
crate::platform::WindowEvent::ScaleFactorChanged { scale_factor } => {
self.0.set_scale_factor(scale_factor);
}
crate::platform::WindowEvent::Resized { size } => {
self.0.set_window_item_geometry(size.to_euclid());
self.0
.window_adapter()
.renderer()
.resize(size.to_physical(self.scale_factor()))
.unwrap()
}
crate::platform::WindowEvent::CloseRequested => {
if self.0.request_close() {
self.hide().unwrap();
}
}
crate::platform::WindowEvent::WindowActiveChanged(bool) => self.0.set_active(bool),
}
}
pub fn has_active_animations(&self) -> bool {
crate::animations::CURRENT_ANIMATION_DRIVER.with(|driver| driver.has_active_animations())
}
pub fn is_visible(&self) -> bool {
self.0.is_visible()
}
#[cfg(feature = "raw-window-handle-06")]
pub fn window_handle(&self) -> WindowHandle {
let adapter = self.0.window_adapter();
if let Some((window_handle_provider, display_handle_provider)) =
adapter.internal(crate::InternalToken).and_then(|internal| {
internal.window_handle_06_rc().ok().zip(internal.display_handle_06_rc().ok())
})
{
WindowHandle {
inner: WindowHandleInner::HandleByRcRWH {
window_handle_provider,
display_handle_provider,
},
}
} else {
WindowHandle { inner: WindowHandleInner::HandleByAdapter(adapter) }
}
}
pub fn take_snapshot(&self) -> Result<SharedPixelBuffer<Rgba8Pixel>, PlatformError> {
self.0.window_adapter().renderer().take_snapshot()
}
}
pub use crate::SharedString;
#[i_slint_core_macros::slint_doc]
pub trait Global<'a, Component> {
fn get(component: &'a Component) -> Self;
}
pub trait ComponentHandle {
#[doc(hidden)]
type Inner;
fn as_weak(&self) -> Weak<Self>
where
Self: Sized;
#[must_use]
fn clone_strong(&self) -> Self;
#[doc(hidden)]
fn from_inner(_: vtable::VRc<ItemTreeVTable, Self::Inner>) -> Self;
fn show(&self) -> Result<(), PlatformError>;
fn hide(&self) -> Result<(), PlatformError>;
fn window(&self) -> &Window;
fn run(&self) -> Result<(), PlatformError>;
fn global<'a, T: Global<'a, Self>>(&'a self) -> T
where
Self: Sized;
}
mod weak_handle {
use super::*;
pub struct Weak<T: ComponentHandle> {
inner: vtable::VWeak<ItemTreeVTable, T::Inner>,
#[cfg(feature = "std")]
thread: std::thread::ThreadId,
}
impl<T: ComponentHandle> Default for Weak<T> {
fn default() -> Self {
Self {
inner: vtable::VWeak::default(),
#[cfg(feature = "std")]
thread: std::thread::current().id(),
}
}
}
impl<T: ComponentHandle> Clone for Weak<T> {
fn clone(&self) -> Self {
Self {
inner: self.inner.clone(),
#[cfg(feature = "std")]
thread: self.thread,
}
}
}
impl<T: ComponentHandle> Weak<T> {
#[doc(hidden)]
pub fn new(rc: &vtable::VRc<ItemTreeVTable, T::Inner>) -> Self {
Self {
inner: vtable::VRc::downgrade(rc),
#[cfg(feature = "std")]
thread: std::thread::current().id(),
}
}
pub fn upgrade(&self) -> Option<T>
where
T: ComponentHandle,
{
#[cfg(feature = "std")]
if std::thread::current().id() != self.thread {
return None;
}
self.inner.upgrade().map(T::from_inner)
}
#[track_caller]
pub fn unwrap(&self) -> T {
#[cfg(feature = "std")]
if std::thread::current().id() != self.thread {
panic!(
"Trying to upgrade a Weak from a different thread than the one it belongs to"
);
}
T::from_inner(self.inner.upgrade().expect("The Weak doesn't hold a valid component"))
}
pub(crate) fn inner(&self) -> vtable::VWeak<ItemTreeVTable, T::Inner> {
self.inner.clone()
}
#[cfg(any(feature = "std", feature = "unsafe-single-threaded"))]
pub fn upgrade_in_event_loop(
&self,
func: impl FnOnce(T) + Send + 'static,
) -> Result<(), EventLoopError>
where
T: 'static,
{
let weak_handle = self.clone();
super::invoke_from_event_loop(move || {
if let Some(h) = weak_handle.upgrade() {
func(h);
}
})
}
}
#[allow(unsafe_code)]
#[cfg(any(feature = "std", feature = "unsafe-single-threaded"))]
unsafe impl<T: ComponentHandle> Send for Weak<T> {}
}
pub use weak_handle::*;
pub fn invoke_from_event_loop(func: impl FnOnce() + Send + 'static) -> Result<(), EventLoopError> {
crate::platform::with_event_loop_proxy(|proxy| {
proxy
.ok_or(EventLoopError::NoEventLoopProvider)?
.invoke_from_event_loop(alloc::boxed::Box::new(func))
})
}
pub fn quit_event_loop() -> Result<(), EventLoopError> {
crate::platform::with_event_loop_proxy(|proxy| {
proxy.ok_or(EventLoopError::NoEventLoopProvider)?.quit_event_loop()
})
}
#[derive(Debug, Clone, Eq, PartialEq)]
#[non_exhaustive]
pub enum EventLoopError {
EventLoopTerminated,
NoEventLoopProvider,
}
impl core::fmt::Display for EventLoopError {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
EventLoopError::EventLoopTerminated => {
f.write_str("The event loop was already terminated")
}
EventLoopError::NoEventLoopProvider => {
f.write_str("The Slint platform does not provide an event loop")
}
}
}
}
#[cfg(feature = "std")]
impl std::error::Error for EventLoopError {}
#[derive(Debug)]
#[non_exhaustive]
pub enum PlatformError {
NoPlatform,
NoEventLoopProvider,
SetPlatformError(crate::platform::SetPlatformError),
Other(String),
#[cfg(feature = "std")]
OtherError(Box<dyn std::error::Error + Send + Sync>),
}
impl core::fmt::Display for PlatformError {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
PlatformError::NoPlatform => f.write_str(
"No default Slint platform was selected, and no Slint platform was initialized",
),
PlatformError::NoEventLoopProvider => {
f.write_str("The Slint platform does not provide an event loop")
}
PlatformError::SetPlatformError(_) => {
f.write_str("The Slint platform was initialized in another thread")
}
PlatformError::Other(str) => f.write_str(str),
#[cfg(feature = "std")]
PlatformError::OtherError(error) => error.fmt(f),
}
}
}
impl From<String> for PlatformError {
fn from(value: String) -> Self {
Self::Other(value)
}
}
impl From<&str> for PlatformError {
fn from(value: &str) -> Self {
Self::Other(value.into())
}
}
#[cfg(feature = "std")]
impl From<Box<dyn std::error::Error + Send + Sync>> for PlatformError {
fn from(error: Box<dyn std::error::Error + Send + Sync>) -> Self {
Self::OtherError(error)
}
}
#[cfg(feature = "std")]
impl std::error::Error for PlatformError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
PlatformError::OtherError(err) => Some(err.as_ref()),
_ => None,
}
}
}
#[test]
#[cfg(feature = "std")]
fn error_is_send() {
let _: Box<dyn std::error::Error + Send + Sync + 'static> = PlatformError::NoPlatform.into();
}
pub fn set_xdg_app_id(app_id: impl Into<SharedString>) -> Result<(), PlatformError> {
crate::context::with_global_context(
|| Err(crate::platform::PlatformError::NoPlatform),
|ctx| ctx.set_xdg_app_id(app_id.into()),
)
}