#![warn(missing_docs)]
use alloc::boxed::Box;
use alloc::string::String;
use crate::component::ComponentVTable;
use crate::input::{KeyEventType, KeyInputEvent, MouseEvent};
use crate::window::{WindowAdapter, WindowInner};
#[derive(Debug, Default, Copy, Clone, PartialEq)]
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()
}
}
#[derive(Debug, Default, Copy, Clone, Eq, PartialEq)]
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()
}
}
#[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.clone(),
WindowPosition::Logical(pos) => pos.to_physical(scale_factor),
}
}
}
#[derive(Debug, Default, Copy, Clone, PartialEq)]
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()
}
}
#[derive(Debug, Default, Copy, Clone, Eq, PartialEq)]
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.clone(),
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.clone(),
}
}
}
#[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(C)]
#[non_exhaustive]
pub enum RenderingState {
RenderingSetup,
BeforeRendering,
AfterRendering,
RenderingTeardown,
}
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(C)]
#[non_exhaustive]
pub enum SetRenderingNotifierError {
Unsupported,
AlreadySet,
}
#[repr(transparent)]
pub struct Window(pub(crate) WindowInner);
#[derive(Copy, Clone, Debug, PartialEq, Default)]
#[repr(C)]
pub enum CloseRequestResponse {
#[default]
HideWindow,
KeepWindowShown,
}
impl Window {
#[doc(hidden)]
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()
}
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.inner_size.get()
}
pub fn set_size(&self, size: impl Into<WindowSize>) {
let size = size.into();
let l = size.to_logical(self.scale_factor()).to_euclid();
let p = size.to_physical(self.scale_factor());
self.0.set_window_item_geometry(l);
if self.0.inner_size.replace(p) != p {
self.0.window_adapter().set_size(size);
}
}
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_y,
});
}
crate::platform::WindowEvent::PointerExited => {
self.0.process_mouse_input(MouseEvent::Exit)
}
crate::platform::WindowEvent::KeyPressed { text } => {
self.0.process_key_input(KeyInputEvent {
text: SharedString::from(text),
event_type: KeyEventType::KeyPressed,
..Default::default()
})
}
crate::platform::WindowEvent::KeyReleased { text } => {
self.0.process_key_input(KeyInputEvent {
text: SharedString::from(text),
event_type: KeyEventType::KeyReleased,
..Default::default()
})
}
}
}
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.window_adapter().is_visible()
}
}
pub use crate::SharedString;
#[doc = concat!("See also the [language documentation for global singletons](https://slint-ui.com/releases/", env!("CARGO_PKG_VERSION"), "/docs/slint/src/reference/globals.html) for more information.")]
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<ComponentVTable, 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<ComponentVTable, T::Inner>,
#[cfg(feature = "std")]
thread: std::thread::ThreadId,
}
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<ComponentVTable, 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)
}
pub fn unwrap(&self) -> T {
self.upgrade().unwrap()
}
#[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::event_loop_proxy()
.ok_or(EventLoopError::NoEventLoopProvider)?
.invoke_from_event_loop(alloc::boxed::Box::new(func))
}
pub fn quit_event_loop() -> Result<(), EventLoopError> {
crate::platform::event_loop_proxy()
.ok_or(EventLoopError::NoEventLoopProvider)?
.quit_event_loop()
}
#[derive(Debug, Clone, Eq, PartialEq)]
#[non_exhaustive]
pub enum EventLoopError {
EventLoopTerminated,
NoEventLoopProvider,
}
#[derive(Debug)]
#[non_exhaustive]
pub enum PlatformError {
NoPlatform,
NoEventLoopProvider,
SetPlatformError(crate::platform::SetPlatformError),
Other(String),
}
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 do 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),
}
}
}
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 std::error::Error for PlatformError {}
#[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);
}