1 | // Copyright © SixtyFPS GmbH <info@slint.dev> |
2 | // SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-1.1 OR LicenseRef-Slint-commercial |
3 | |
4 | /*! |
5 | This module contains types that are public and re-exported in the slint-rs as well as the slint-interpreter crate as public API. |
6 | */ |
7 | |
8 | #![warn (missing_docs)] |
9 | |
10 | #[cfg (target_has_atomic = "ptr" )] |
11 | pub use crate::future::*; |
12 | use crate::input::{KeyEventType, MouseEvent}; |
13 | use crate::item_tree::ItemTreeVTable; |
14 | use crate::window::{WindowAdapter, WindowInner}; |
15 | #[cfg (not(feature = "std" ))] |
16 | use alloc::boxed::Box; |
17 | #[cfg (not(feature = "std" ))] |
18 | use alloc::string::String; |
19 | |
20 | /// A position represented in the coordinate space of logical pixels. That is the space before applying |
21 | /// a display device specific scale factor. |
22 | #[derive (Debug, Default, Copy, Clone, PartialEq)] |
23 | #[repr (C)] |
24 | pub struct LogicalPosition { |
25 | /// The x coordinate. |
26 | pub x: f32, |
27 | /// The y coordinate. |
28 | pub y: f32, |
29 | } |
30 | |
31 | impl LogicalPosition { |
32 | /// Construct a new logical position from the given x and y coordinates, that are assumed to be |
33 | /// in the logical coordinate space. |
34 | pub const fn new(x: f32, y: f32) -> Self { |
35 | Self { x, y } |
36 | } |
37 | |
38 | /// Convert a given physical position to a logical position by dividing the coordinates with the |
39 | /// specified scale factor. |
40 | pub fn from_physical(physical_pos: PhysicalPosition, scale_factor: f32) -> Self { |
41 | Self::new(physical_pos.x as f32 / scale_factor, physical_pos.y as f32 / scale_factor) |
42 | } |
43 | |
44 | /// Convert this logical position to a physical position by multiplying the coordinates with the |
45 | /// specified scale factor. |
46 | pub fn to_physical(&self, scale_factor: f32) -> PhysicalPosition { |
47 | PhysicalPosition::from_logical(*self, scale_factor) |
48 | } |
49 | |
50 | pub(crate) fn to_euclid(self) -> crate::lengths::LogicalPoint { |
51 | [self.x as _, self.y as _].into() |
52 | } |
53 | pub(crate) fn from_euclid(p: crate::lengths::LogicalPoint) -> Self { |
54 | Self::new(p.x as _, p.y as _) |
55 | } |
56 | } |
57 | |
58 | /// A position represented in the coordinate space of physical device pixels. That is the space after applying |
59 | /// a display device specific scale factor to pixels from the logical coordinate space. |
60 | #[derive (Debug, Default, Copy, Clone, Eq, PartialEq)] |
61 | pub struct PhysicalPosition { |
62 | /// The x coordinate. |
63 | pub x: i32, |
64 | /// The y coordinate. |
65 | pub y: i32, |
66 | } |
67 | |
68 | impl PhysicalPosition { |
69 | /// Construct a new physical position from the given x and y coordinates, that are assumed to be |
70 | /// in the physical coordinate space. |
71 | pub const fn new(x: i32, y: i32) -> Self { |
72 | Self { x, y } |
73 | } |
74 | |
75 | /// Convert a given logical position to a physical position by multiplying the coordinates with the |
76 | /// specified scale factor. |
77 | pub fn from_logical(logical_pos: LogicalPosition, scale_factor: f32) -> Self { |
78 | Self::new((logical_pos.x * scale_factor) as i32, (logical_pos.y * scale_factor) as i32) |
79 | } |
80 | |
81 | /// Convert this physical position to a logical position by dividing the coordinates with the |
82 | /// specified scale factor. |
83 | pub fn to_logical(&self, scale_factor: f32) -> LogicalPosition { |
84 | LogicalPosition::from_physical(*self, scale_factor) |
85 | } |
86 | |
87 | #[cfg (feature = "ffi" )] |
88 | pub(crate) fn to_euclid(&self) -> crate::graphics::euclid::default::Point2D<i32> { |
89 | [self.x, self.y].into() |
90 | } |
91 | |
92 | #[cfg (feature = "ffi" )] |
93 | pub(crate) fn from_euclid(p: crate::graphics::euclid::default::Point2D<i32>) -> Self { |
94 | Self::new(p.x as _, p.y as _) |
95 | } |
96 | } |
97 | |
98 | /// The position of the window in either physical or logical pixels. This is used |
99 | /// with [`Window::set_position`]. |
100 | #[derive (Clone, Debug, derive_more::From, PartialEq)] |
101 | pub enum WindowPosition { |
102 | /// The position in physical pixels. |
103 | Physical(PhysicalPosition), |
104 | /// The position in logical pixels. |
105 | Logical(LogicalPosition), |
106 | } |
107 | |
108 | impl WindowPosition { |
109 | /// Turn the `WindowPosition` into a `PhysicalPosition`. |
110 | pub fn to_physical(&self, scale_factor: f32) -> PhysicalPosition { |
111 | match self { |
112 | WindowPosition::Physical(pos: &PhysicalPosition) => *pos, |
113 | WindowPosition::Logical(pos: &LogicalPosition) => pos.to_physical(scale_factor), |
114 | } |
115 | } |
116 | } |
117 | |
118 | /// A size represented in the coordinate space of logical pixels. That is the space before applying |
119 | /// a display device specific scale factor. |
120 | #[repr (C)] |
121 | #[derive (Debug, Default, Copy, Clone, PartialEq)] |
122 | pub struct LogicalSize { |
123 | /// The width in logical pixels. |
124 | pub width: f32, |
125 | /// The height in logical. |
126 | pub height: f32, |
127 | } |
128 | |
129 | impl LogicalSize { |
130 | /// Construct a new logical size from the given width and height values, that are assumed to be |
131 | /// in the logical coordinate space. |
132 | pub const fn new(width: f32, height: f32) -> Self { |
133 | Self { width, height } |
134 | } |
135 | |
136 | /// Convert a given physical size to a logical size by dividing width and height by the |
137 | /// specified scale factor. |
138 | pub fn from_physical(physical_size: PhysicalSize, scale_factor: f32) -> Self { |
139 | Self::new( |
140 | physical_size.width as f32 / scale_factor, |
141 | physical_size.height as f32 / scale_factor, |
142 | ) |
143 | } |
144 | |
145 | /// Convert this logical size to a physical size by multiplying width and height with the |
146 | /// specified scale factor. |
147 | pub fn to_physical(&self, scale_factor: f32) -> PhysicalSize { |
148 | PhysicalSize::from_logical(*self, scale_factor) |
149 | } |
150 | |
151 | pub(crate) fn to_euclid(self) -> crate::lengths::LogicalSize { |
152 | [self.width as _, self.height as _].into() |
153 | } |
154 | |
155 | pub(crate) fn from_euclid(p: crate::lengths::LogicalSize) -> Self { |
156 | Self::new(p.width as _, p.height as _) |
157 | } |
158 | } |
159 | |
160 | /// A size represented in the coordinate space of physical device pixels. That is the space after applying |
161 | /// a display device specific scale factor to pixels from the logical coordinate space. |
162 | #[derive (Debug, Default, Copy, Clone, Eq, PartialEq)] |
163 | pub struct PhysicalSize { |
164 | /// The width in physical pixels. |
165 | pub width: u32, |
166 | /// The height in physical pixels; |
167 | pub height: u32, |
168 | } |
169 | |
170 | impl PhysicalSize { |
171 | /// Construct a new physical size from the width and height values, that are assumed to be |
172 | /// in the physical coordinate space. |
173 | pub const fn new(width: u32, height: u32) -> Self { |
174 | Self { width, height } |
175 | } |
176 | |
177 | /// Convert a given logical size to a physical size by multiplying width and height with the |
178 | /// specified scale factor. |
179 | pub fn from_logical(logical_size: LogicalSize, scale_factor: f32) -> Self { |
180 | Self::new( |
181 | (logical_size.width * scale_factor) as u32, |
182 | (logical_size.height * scale_factor) as u32, |
183 | ) |
184 | } |
185 | |
186 | /// Convert this physical size to a logical size by dividing width and height by the |
187 | /// specified scale factor. |
188 | pub fn to_logical(&self, scale_factor: f32) -> LogicalSize { |
189 | LogicalSize::from_physical(*self, scale_factor) |
190 | } |
191 | |
192 | #[cfg (feature = "ffi" )] |
193 | pub(crate) fn to_euclid(&self) -> crate::graphics::euclid::default::Size2D<u32> { |
194 | [self.width, self.height].into() |
195 | } |
196 | } |
197 | |
198 | /// The size of a window represented in either physical or logical pixels. This is used |
199 | /// with [`Window::set_size`]. |
200 | #[derive (Clone, Debug, derive_more::From, PartialEq)] |
201 | pub enum WindowSize { |
202 | /// The size in physical pixels. |
203 | Physical(PhysicalSize), |
204 | /// The size in logical screen pixels. |
205 | Logical(LogicalSize), |
206 | } |
207 | |
208 | impl WindowSize { |
209 | /// Turn the `WindowSize` into a `PhysicalSize`. |
210 | pub fn to_physical(&self, scale_factor: f32) -> PhysicalSize { |
211 | match self { |
212 | WindowSize::Physical(size: &PhysicalSize) => *size, |
213 | WindowSize::Logical(size: &LogicalSize) => size.to_physical(scale_factor), |
214 | } |
215 | } |
216 | |
217 | /// Turn the `WindowSize` into a `LogicalSize`. |
218 | pub fn to_logical(&self, scale_factor: f32) -> LogicalSize { |
219 | match self { |
220 | WindowSize::Physical(size: &PhysicalSize) => size.to_logical(scale_factor), |
221 | WindowSize::Logical(size: &LogicalSize) => *size, |
222 | } |
223 | } |
224 | } |
225 | |
226 | #[test ] |
227 | fn logical_physical_pos() { |
228 | use crate::graphics::euclid::approxeq::ApproxEq; |
229 | |
230 | let phys: PhysicalPosition = PhysicalPosition::new(x:100, y:50); |
231 | let logical: LogicalPosition = phys.to_logical(scale_factor:2.); |
232 | assert!(logical.x.approx_eq(&50.)); |
233 | assert!(logical.y.approx_eq(&25.)); |
234 | |
235 | assert_eq!(logical.to_physical(2.), phys); |
236 | } |
237 | |
238 | #[test ] |
239 | fn logical_physical_size() { |
240 | use crate::graphics::euclid::approxeq::ApproxEq; |
241 | |
242 | let phys: PhysicalSize = PhysicalSize::new(width:100, height:50); |
243 | let logical: LogicalSize = phys.to_logical(scale_factor:2.); |
244 | assert!(logical.width.approx_eq(&50.)); |
245 | assert!(logical.height.approx_eq(&25.)); |
246 | |
247 | assert_eq!(logical.to_physical(2.), phys); |
248 | } |
249 | |
250 | /// This enum describes a low-level access to specific graphics APIs used |
251 | /// by the renderer. |
252 | #[derive (Clone)] |
253 | #[non_exhaustive ] |
254 | pub enum GraphicsAPI<'a> { |
255 | /// The rendering is done using OpenGL. |
256 | NativeOpenGL { |
257 | /// Use this function pointer to obtain access to the OpenGL implementation - similar to `eglGetProcAddress`. |
258 | get_proc_address: &'a dyn Fn(&core::ffi::CStr) -> *const core::ffi::c_void, |
259 | }, |
260 | /// The rendering is done on a HTML Canvas element using WebGL. |
261 | WebGL { |
262 | /// The DOM element id of the HTML Canvas element used for rendering. |
263 | canvas_element_id: &'a str, |
264 | /// The drawing context type used on the HTML Canvas element for rendering. This is the argument to the |
265 | /// `getContext` function on the HTML Canvas element. |
266 | context_type: &'a str, |
267 | }, |
268 | } |
269 | |
270 | impl<'a> core::fmt::Debug for GraphicsAPI<'a> { |
271 | fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { |
272 | match self { |
273 | GraphicsAPI::NativeOpenGL { .. } => write!(f, "GraphicsAPI::NativeOpenGL" ), |
274 | GraphicsAPI::WebGL { context_type: &&str, .. } => { |
275 | write!(f, "GraphicsAPI::WebGL(context_type = {})" , context_type) |
276 | } |
277 | } |
278 | } |
279 | } |
280 | |
281 | /// This enum describes the different rendering states, that will be provided |
282 | /// to the parameter of the callback for `set_rendering_notifier` on the `slint::Window`. |
283 | #[derive (Debug, Clone)] |
284 | #[repr (u8)] |
285 | #[non_exhaustive ] |
286 | pub enum RenderingState { |
287 | /// The window has been created and the graphics adapter/context initialized. When OpenGL |
288 | /// is used for rendering, the context will be current. |
289 | RenderingSetup, |
290 | /// The scene of items is about to be rendered. When OpenGL |
291 | /// is used for rendering, the context will be current. |
292 | BeforeRendering, |
293 | /// The scene of items was rendered, but the back buffer was not sent for display presentation |
294 | /// yet (for example GL swap buffers). When OpenGL is used for rendering, the context will be current. |
295 | AfterRendering, |
296 | /// The window will be destroyed and/or graphics resources need to be released due to other |
297 | /// constraints. |
298 | RenderingTeardown, |
299 | } |
300 | |
301 | /// Internal trait that's used to map rendering state callbacks to either a Rust-API provided |
302 | /// impl FnMut or a struct that invokes a C callback and implements Drop to release the closure |
303 | /// on the C++ side. |
304 | pub trait RenderingNotifier { |
305 | /// Called to notify that rendering has reached a certain state. |
306 | fn notify(&mut self, state: RenderingState, graphics_api: &GraphicsAPI); |
307 | } |
308 | |
309 | impl<F: FnMut(RenderingState, &GraphicsAPI)> RenderingNotifier for F { |
310 | fn notify(&mut self, state: RenderingState, graphics_api: &GraphicsAPI) { |
311 | self(state, graphics_api) |
312 | } |
313 | } |
314 | |
315 | /// This enum describes the different error scenarios that may occur when the application |
316 | /// registers a rendering notifier on a `slint::Window`. |
317 | #[derive (Debug, Clone)] |
318 | #[repr (u8)] |
319 | #[non_exhaustive ] |
320 | pub enum SetRenderingNotifierError { |
321 | /// The rendering backend does not support rendering notifiers. |
322 | Unsupported, |
323 | /// There is already a rendering notifier set, multiple notifiers are not supported. |
324 | AlreadySet, |
325 | } |
326 | |
327 | /// This type represents a window towards the windowing system, that's used to render the |
328 | /// scene of a component. It provides API to control windowing system specific aspects such |
329 | /// as the position on the screen. |
330 | #[repr (transparent)] |
331 | pub struct Window(pub(crate) WindowInner); |
332 | |
333 | /// This enum describes whether a Window is allowed to be hidden when the user tries to close the window. |
334 | /// It is the return type of the callback provided to [Window::on_close_requested]. |
335 | #[derive (Copy, Clone, Debug, PartialEq, Default)] |
336 | #[repr (u8)] |
337 | pub enum CloseRequestResponse { |
338 | /// The Window will be hidden (default action) |
339 | #[default] |
340 | HideWindow = 0, |
341 | /// The close request is rejected and the window will be kept shown. |
342 | KeepWindowShown = 1, |
343 | } |
344 | |
345 | impl Window { |
346 | /// Create a new window from a window adapter |
347 | /// |
348 | /// You only need to create the window yourself when you create a [`WindowAdapter`] from |
349 | /// [`Platform::create_window_adapter`](crate::platform::Platform::create_window_adapter) |
350 | /// |
351 | /// Since the window adapter must own the Window, this function is meant to be used with |
352 | /// [`Rc::new_cyclic`](alloc::rc::Rc::new_cyclic) |
353 | /// |
354 | /// # Example |
355 | /// ```rust |
356 | /// use std::rc::Rc; |
357 | /// use slint::platform::{WindowAdapter, Renderer}; |
358 | /// use slint::{Window, PhysicalSize}; |
359 | /// struct MyWindowAdapter { |
360 | /// window: Window, |
361 | /// //... |
362 | /// } |
363 | /// impl WindowAdapter for MyWindowAdapter { |
364 | /// fn window(&self) -> &Window { &self.window } |
365 | /// fn size(&self) -> PhysicalSize { unimplemented!() } |
366 | /// fn renderer(&self) -> &dyn Renderer { unimplemented!() } |
367 | /// } |
368 | /// |
369 | /// fn create_window_adapter() -> Rc<dyn WindowAdapter> { |
370 | /// Rc::<MyWindowAdapter>::new_cyclic(|weak| { |
371 | /// MyWindowAdapter { |
372 | /// window: Window::new(weak.clone()), |
373 | /// //... |
374 | /// } |
375 | /// }) |
376 | /// } |
377 | /// ``` |
378 | pub fn new(window_adapter_weak: alloc::rc::Weak<dyn WindowAdapter>) -> Self { |
379 | Self(WindowInner::new(window_adapter_weak)) |
380 | } |
381 | |
382 | /// Shows the window on the screen. An additional strong reference on the |
383 | /// associated component is maintained while the window is visible. |
384 | /// |
385 | /// Call [`Self::hide()`] to make the window invisible again, and drop the additional |
386 | /// strong reference. |
387 | pub fn show(&self) -> Result<(), PlatformError> { |
388 | self.0.show() |
389 | } |
390 | |
391 | /// Hides the window, so that it is not visible anymore. The additional strong |
392 | /// reference on the associated component, that was created when [`Self::show()`] was called, is |
393 | /// dropped. |
394 | pub fn hide(&self) -> Result<(), PlatformError> { |
395 | self.0.hide() |
396 | } |
397 | |
398 | /// This function allows registering a callback that's invoked during the different phases of |
399 | /// rendering. This allows custom rendering on top or below of the scene. |
400 | pub fn set_rendering_notifier( |
401 | &self, |
402 | callback: impl FnMut(RenderingState, &GraphicsAPI) + 'static, |
403 | ) -> Result<(), SetRenderingNotifierError> { |
404 | self.0.window_adapter().renderer().set_rendering_notifier(Box::new(callback)) |
405 | } |
406 | |
407 | /// This function allows registering a callback that's invoked when the user tries to close a window. |
408 | /// The callback has to return a [CloseRequestResponse]. |
409 | pub fn on_close_requested(&self, callback: impl FnMut() -> CloseRequestResponse + 'static) { |
410 | self.0.on_close_requested(callback); |
411 | } |
412 | |
413 | /// This function issues a request to the windowing system to redraw the contents of the window. |
414 | pub fn request_redraw(&self) { |
415 | self.0.window_adapter().request_redraw() |
416 | } |
417 | |
418 | /// This function returns the scale factor that allows converting between logical and |
419 | /// physical pixels. |
420 | pub fn scale_factor(&self) -> f32 { |
421 | self.0.scale_factor() |
422 | } |
423 | |
424 | /// Returns the position of the window on the screen, in physical screen coordinates and including |
425 | /// a window frame (if present). |
426 | pub fn position(&self) -> PhysicalPosition { |
427 | self.0.window_adapter().position().unwrap_or_default() |
428 | } |
429 | |
430 | /// Sets the position of the window on the screen, in physical screen coordinates and including |
431 | /// a window frame (if present). |
432 | /// Note that on some windowing systems, such as Wayland, this functionality is not available. |
433 | pub fn set_position(&self, position: impl Into<WindowPosition>) { |
434 | let position = position.into(); |
435 | self.0.window_adapter().set_position(position) |
436 | } |
437 | |
438 | /// Returns the size of the window on the screen, in physical screen coordinates and excluding |
439 | /// a window frame (if present). |
440 | pub fn size(&self) -> PhysicalSize { |
441 | self.0.window_adapter().size() |
442 | } |
443 | |
444 | /// Resizes the window to the specified size on the screen, in physical pixels and excluding |
445 | /// a window frame (if present). |
446 | pub fn set_size(&self, size: impl Into<WindowSize>) { |
447 | let size = size.into(); |
448 | crate::window::WindowAdapter::set_size(&*self.0.window_adapter(), size); |
449 | } |
450 | |
451 | /// Returns if the window is currently fullscreen |
452 | pub fn is_fullscreen(&self) -> bool { |
453 | self.0.is_fullscreen() |
454 | } |
455 | |
456 | /// Set or unset the window to display fullscreen. |
457 | pub fn set_fullscreen(&self, fullscreen: bool) { |
458 | self.0.set_fullscreen(fullscreen); |
459 | } |
460 | |
461 | /// Returns if the window is currently maximized |
462 | pub fn is_maximized(&self) -> bool { |
463 | self.0.is_maximized() |
464 | } |
465 | |
466 | /// Maximize or unmaximize the window. |
467 | pub fn set_maximized(&self, maximized: bool) { |
468 | self.0.set_maximized(maximized); |
469 | } |
470 | |
471 | /// Returns if the window is currently minimized |
472 | pub fn is_minimized(&self) -> bool { |
473 | self.0.is_minimized() |
474 | } |
475 | |
476 | /// Minimize or unminimze the window. |
477 | pub fn set_minimized(&self, minimized: bool) { |
478 | self.0.set_minimized(minimized); |
479 | } |
480 | |
481 | /// Dispatch a window event to the scene. |
482 | /// |
483 | /// Use this when you're implementing your own backend and want to forward user input events. |
484 | /// |
485 | /// Any position fields in the event must be in the logical pixel coordinate system relative to |
486 | /// the top left corner of the window. |
487 | // TODO: Return a Result<(), PlatformError> |
488 | pub fn dispatch_event(&self, event: crate::platform::WindowEvent) { |
489 | match event { |
490 | crate::platform::WindowEvent::PointerPressed { position, button } => { |
491 | self.0.process_mouse_input(MouseEvent::Pressed { |
492 | position: position.to_euclid().cast(), |
493 | button, |
494 | click_count: 0, |
495 | }); |
496 | } |
497 | crate::platform::WindowEvent::PointerReleased { position, button } => { |
498 | self.0.process_mouse_input(MouseEvent::Released { |
499 | position: position.to_euclid().cast(), |
500 | button, |
501 | click_count: 0, |
502 | }); |
503 | } |
504 | crate::platform::WindowEvent::PointerMoved { position } => { |
505 | self.0.process_mouse_input(MouseEvent::Moved { |
506 | position: position.to_euclid().cast(), |
507 | }); |
508 | } |
509 | crate::platform::WindowEvent::PointerScrolled { position, delta_x, delta_y } => { |
510 | self.0.process_mouse_input(MouseEvent::Wheel { |
511 | position: position.to_euclid().cast(), |
512 | delta_x: delta_x as _, |
513 | delta_y: delta_y as _, |
514 | }); |
515 | } |
516 | crate::platform::WindowEvent::PointerExited => { |
517 | self.0.process_mouse_input(MouseEvent::Exit) |
518 | } |
519 | |
520 | crate::platform::WindowEvent::KeyPressed { text } => { |
521 | self.0.process_key_input(crate::input::KeyEvent { |
522 | text, |
523 | repeat: false, |
524 | event_type: KeyEventType::KeyPressed, |
525 | ..Default::default() |
526 | }) |
527 | } |
528 | crate::platform::WindowEvent::KeyPressRepeated { text } => { |
529 | self.0.process_key_input(crate::input::KeyEvent { |
530 | text, |
531 | repeat: true, |
532 | event_type: KeyEventType::KeyPressed, |
533 | ..Default::default() |
534 | }) |
535 | } |
536 | crate::platform::WindowEvent::KeyReleased { text } => { |
537 | self.0.process_key_input(crate::input::KeyEvent { |
538 | text, |
539 | event_type: KeyEventType::KeyReleased, |
540 | ..Default::default() |
541 | }) |
542 | } |
543 | crate::platform::WindowEvent::ScaleFactorChanged { scale_factor } => { |
544 | self.0.set_scale_factor(scale_factor); |
545 | } |
546 | crate::platform::WindowEvent::Resized { size } => { |
547 | self.0.set_window_item_geometry(size.to_euclid()); |
548 | self.0 |
549 | .window_adapter() |
550 | .renderer() |
551 | .resize(size.to_physical(self.scale_factor())) |
552 | .unwrap() |
553 | } |
554 | crate::platform::WindowEvent::CloseRequested => { |
555 | if self.0.request_close() { |
556 | self.hide().unwrap(); |
557 | } |
558 | } |
559 | crate::platform::WindowEvent::WindowActiveChanged(bool) => self.0.set_active(bool), |
560 | } |
561 | } |
562 | |
563 | /// Returns true if there is an animation currently active on any property in the Window; false otherwise. |
564 | pub fn has_active_animations(&self) -> bool { |
565 | // TODO make it really per window. |
566 | crate::animations::CURRENT_ANIMATION_DRIVER.with(|driver| driver.has_active_animations()) |
567 | } |
568 | |
569 | /// Returns the visibility state of the window. This function can return false even if you previously called show() |
570 | /// on it, for example if the user minimized the window. |
571 | pub fn is_visible(&self) -> bool { |
572 | self.0.is_visible() |
573 | } |
574 | } |
575 | |
576 | pub use crate::SharedString; |
577 | |
578 | /// This trait is used to obtain references to global singletons exported in `.slint` |
579 | /// markup. Alternatively, you can use [`ComponentHandle::global`] to obtain access. |
580 | /// |
581 | /// This trait is implemented by the compiler for each global singleton that's exported. |
582 | /// |
583 | /// # Example |
584 | /// The following example of `.slint` markup defines a global singleton called `Palette`, exports |
585 | /// it and modifies it from Rust code: |
586 | /// ```rust |
587 | /// # i_slint_backend_testing::init(); |
588 | /// slint::slint!{ |
589 | /// export global Palette { |
590 | /// in property<color> foreground-color; |
591 | /// in property<color> background-color; |
592 | /// } |
593 | /// |
594 | /// export component App inherits Window { |
595 | /// background: Palette.background-color; |
596 | /// Text { |
597 | /// text: "Hello" ; |
598 | /// color: Palette.foreground-color; |
599 | /// } |
600 | /// // ... |
601 | /// } |
602 | /// } |
603 | /// let app = App::new().unwrap(); |
604 | /// app.global::<Palette>().set_background_color(slint::Color::from_rgb_u8(0, 0, 0)); |
605 | /// |
606 | /// // alternate way to access the global singleton: |
607 | /// Palette::get(&app).set_foreground_color(slint::Color::from_rgb_u8(255, 255, 255)); |
608 | /// ``` |
609 | /// |
610 | #[doc = concat!("See also the [language documentation for global singletons](https://slint.dev/releases/" , env!("CARGO_PKG_VERSION" ), "/docs/slint/src/reference/globals.html) for more information." )] |
611 | /// |
612 | /// **Note:** Only globals that are exported or re-exported from the main .slint file will |
613 | /// be exposed in the API |
614 | pub trait Global<'a, Component> { |
615 | /// Returns a reference that's tied to the life time of the provided component. |
616 | fn get(component: &'a Component) -> Self; |
617 | } |
618 | |
619 | /// This trait describes the common public API of a strongly referenced Slint component. |
620 | /// It allows creating strongly-referenced clones, a conversion into/ a weak pointer as well |
621 | /// as other convenience functions. |
622 | /// |
623 | /// This trait is implemented by the [generated component](index.html#generated-components) |
624 | pub trait ComponentHandle { |
625 | /// The type of the generated component. |
626 | #[doc (hidden)] |
627 | type Inner; |
628 | /// Returns a new weak pointer. |
629 | fn as_weak(&self) -> Weak<Self> |
630 | where |
631 | Self: Sized; |
632 | |
633 | /// Returns a clone of this handle that's a strong reference. |
634 | #[must_use ] |
635 | fn clone_strong(&self) -> Self; |
636 | |
637 | /// Internal function used when upgrading a weak reference to a strong one. |
638 | #[doc (hidden)] |
639 | fn from_inner(_: vtable::VRc<ItemTreeVTable, Self::Inner>) -> Self; |
640 | |
641 | /// Convenience function for [`crate::Window::show()`](struct.Window.html#method.show). |
642 | /// This shows the window on the screen and maintains an extra strong reference while |
643 | /// the window is visible. To react to events from the windowing system, such as draw |
644 | /// requests or mouse/touch input, it is still necessary to spin the event loop, |
645 | /// using [`crate::run_event_loop`](fn.run_event_loop.html). |
646 | fn show(&self) -> Result<(), PlatformError>; |
647 | |
648 | /// Convenience function for [`crate::Window::hide()`](struct.Window.html#method.hide). |
649 | /// Hides the window, so that it is not visible anymore. The additional strong reference |
650 | /// on the associated component, that was created when show() was called, is dropped. |
651 | fn hide(&self) -> Result<(), PlatformError>; |
652 | |
653 | /// Returns the Window associated with this component. The window API can be used |
654 | /// to control different aspects of the integration into the windowing system, |
655 | /// such as the position on the screen. |
656 | fn window(&self) -> &Window; |
657 | |
658 | /// This is a convenience function that first calls [`Self::show`], followed by [`crate::run_event_loop()`](fn.run_event_loop.html) |
659 | /// and [`Self::hide`]. |
660 | fn run(&self) -> Result<(), PlatformError>; |
661 | |
662 | /// This function provides access to instances of global singletons exported in `.slint`. |
663 | /// See [`Global`] for an example how to export and access globals from `.slint` markup. |
664 | fn global<'a, T: Global<'a, Self>>(&'a self) -> T |
665 | where |
666 | Self: Sized; |
667 | } |
668 | |
669 | mod weak_handle { |
670 | |
671 | use super::*; |
672 | |
673 | /// Struct that's used to hold weak references of a [Slint component](index.html#generated-components) |
674 | /// |
675 | /// In order to create a Weak, you should use [`ComponentHandle::as_weak`]. |
676 | /// |
677 | /// Strong references should not be captured by the functions given to a lambda, |
678 | /// as this would produce a reference loop and leak the component. |
679 | /// Instead, the callback function should capture a weak component. |
680 | /// |
681 | /// The Weak component also implement `Send` and can be send to another thread. |
682 | /// but the upgrade function will only return a valid component from the same thread |
683 | /// as the one it has been created from. |
684 | /// This is useful to use with [`invoke_from_event_loop()`] or [`Self::upgrade_in_event_loop()`]. |
685 | pub struct Weak<T: ComponentHandle> { |
686 | inner: vtable::VWeak<ItemTreeVTable, T::Inner>, |
687 | #[cfg (feature = "std" )] |
688 | thread: std::thread::ThreadId, |
689 | } |
690 | |
691 | impl<T: ComponentHandle> Default for Weak<T> { |
692 | fn default() -> Self { |
693 | Self { |
694 | inner: vtable::VWeak::default(), |
695 | #[cfg (feature = "std" )] |
696 | thread: std::thread::current().id(), |
697 | } |
698 | } |
699 | } |
700 | |
701 | impl<T: ComponentHandle> Clone for Weak<T> { |
702 | fn clone(&self) -> Self { |
703 | Self { |
704 | inner: self.inner.clone(), |
705 | #[cfg (feature = "std" )] |
706 | thread: self.thread, |
707 | } |
708 | } |
709 | } |
710 | |
711 | impl<T: ComponentHandle> Weak<T> { |
712 | #[doc (hidden)] |
713 | pub fn new(rc: &vtable::VRc<ItemTreeVTable, T::Inner>) -> Self { |
714 | Self { |
715 | inner: vtable::VRc::downgrade(rc), |
716 | #[cfg (feature = "std" )] |
717 | thread: std::thread::current().id(), |
718 | } |
719 | } |
720 | |
721 | /// Returns a new strongly referenced component if some other instance still |
722 | /// holds a strong reference. Otherwise, returns None. |
723 | /// |
724 | /// This also returns None if the current thread is not the thread that created |
725 | /// the component |
726 | pub fn upgrade(&self) -> Option<T> |
727 | where |
728 | T: ComponentHandle, |
729 | { |
730 | #[cfg (feature = "std" )] |
731 | if std::thread::current().id() != self.thread { |
732 | return None; |
733 | } |
734 | self.inner.upgrade().map(T::from_inner) |
735 | } |
736 | |
737 | /// Convenience function that returns a new strongly referenced component if |
738 | /// some other instance still holds a strong reference and the current thread |
739 | /// is the thread that created this component. |
740 | /// Otherwise, this function panics. |
741 | #[track_caller ] |
742 | pub fn unwrap(&self) -> T { |
743 | #[cfg (feature = "std" )] |
744 | if std::thread::current().id() != self.thread { |
745 | panic!( |
746 | "Trying to upgrade a Weak from a different thread than the one it belongs to" |
747 | ); |
748 | } |
749 | T::from_inner(self.inner.upgrade().expect("The Weak doesn't hold a valid component" )) |
750 | } |
751 | |
752 | /// A helper function to allow creation on `component_factory::Component` from |
753 | /// a `ComponentHandle` |
754 | pub(crate) fn inner(&self) -> vtable::VWeak<ItemTreeVTable, T::Inner> { |
755 | self.inner.clone() |
756 | } |
757 | |
758 | /// Convenience function that combines [`invoke_from_event_loop()`] with [`Self::upgrade()`] |
759 | /// |
760 | /// The given functor will be added to an internal queue and will wake the event loop. |
761 | /// On the next iteration of the event loop, the functor will be executed with a `T` as an argument. |
762 | /// |
763 | /// If the component was dropped because there are no more strong reference to the component, |
764 | /// the functor will not be called. |
765 | /// |
766 | /// # Example |
767 | /// ```rust |
768 | /// # i_slint_backend_testing::init(); |
769 | /// slint::slint! { export component MyApp inherits Window { in property <int> foo; /* ... */ } } |
770 | /// let handle = MyApp::new().unwrap(); |
771 | /// let handle_weak = handle.as_weak(); |
772 | /// let thread = std::thread::spawn(move || { |
773 | /// // ... Do some computation in the thread |
774 | /// let foo = 42; |
775 | /// # assert!(handle_weak.upgrade().is_none()); // note that upgrade fails in a thread |
776 | /// # return; // don't upgrade_in_event_loop in our examples |
777 | /// // now forward the data to the main thread using upgrade_in_event_loop |
778 | /// handle_weak.upgrade_in_event_loop(move |handle| handle.set_foo(foo)); |
779 | /// }); |
780 | /// # thread.join().unwrap(); return; // don't run the event loop in examples |
781 | /// handle.run().unwrap(); |
782 | /// ``` |
783 | #[cfg (any(feature = "std" , feature = "unsafe-single-threaded" ))] |
784 | pub fn upgrade_in_event_loop( |
785 | &self, |
786 | func: impl FnOnce(T) + Send + 'static, |
787 | ) -> Result<(), EventLoopError> |
788 | where |
789 | T: 'static, |
790 | { |
791 | let weak_handle = self.clone(); |
792 | super::invoke_from_event_loop(move || { |
793 | if let Some(h) = weak_handle.upgrade() { |
794 | func(h); |
795 | } |
796 | }) |
797 | } |
798 | } |
799 | |
800 | // Safety: we make sure in upgrade that the thread is the proper one, |
801 | // and the VWeak only use atomic pointer so it is safe to clone and drop in another thread |
802 | #[allow (unsafe_code)] |
803 | #[cfg (any(feature = "std" , feature = "unsafe-single-threaded" ))] |
804 | unsafe impl<T: ComponentHandle> Send for Weak<T> {} |
805 | } |
806 | |
807 | pub use weak_handle::*; |
808 | |
809 | /// Adds the specified function to an internal queue, notifies the event loop to wake up. |
810 | /// Once woken up, any queued up functors will be invoked. |
811 | /// |
812 | /// This function is thread-safe and can be called from any thread, including the one |
813 | /// running the event loop. The provided functors will only be invoked from the thread |
814 | /// that started the event loop. |
815 | /// |
816 | /// You can use this to set properties or use any other Slint APIs from other threads, |
817 | /// by collecting the code in a functor and queuing it up for invocation within the event loop. |
818 | /// |
819 | /// See also [`Weak::upgrade_in_event_loop`] |
820 | /// |
821 | /// # Example |
822 | /// ```rust |
823 | /// slint::slint! { export component MyApp inherits Window { in property <int> foo; /* ... */ } } |
824 | /// # i_slint_backend_testing::init(); |
825 | /// let handle = MyApp::new().unwrap(); |
826 | /// let handle_weak = handle.as_weak(); |
827 | /// # return; // don't run the event loop in examples |
828 | /// let thread = std::thread::spawn(move || { |
829 | /// // ... Do some computation in the thread |
830 | /// let foo = 42; |
831 | /// // now forward the data to the main thread using invoke_from_event_loop |
832 | /// let handle_copy = handle_weak.clone(); |
833 | /// slint::invoke_from_event_loop(move || handle_copy.unwrap().set_foo(foo)); |
834 | /// }); |
835 | /// handle.run().unwrap(); |
836 | /// ``` |
837 | pub fn invoke_from_event_loop(func: impl FnOnce() + Send + 'static) -> Result<(), EventLoopError> { |
838 | crate::platform::event_loop_proxy() |
839 | .ok_or(EventLoopError::NoEventLoopProvider)? |
840 | .invoke_from_event_loop(event:alloc::boxed::Box::new(func)) |
841 | } |
842 | |
843 | /// Schedules the main event loop for termination. This function is meant |
844 | /// to be called from callbacks triggered by the UI. After calling the function, |
845 | /// it will return immediately and once control is passed back to the event loop, |
846 | /// the initial call to `slint::run_event_loop()` will return. |
847 | /// |
848 | /// This function can be called from any thread |
849 | pub fn quit_event_loop() -> Result<(), EventLoopError> { |
850 | crate&dyn EventLoopProxy::platform::event_loop_proxy() |
851 | .ok_or(err:EventLoopError::NoEventLoopProvider)? |
852 | .quit_event_loop() |
853 | } |
854 | |
855 | #[derive (Debug, Clone, Eq, PartialEq)] |
856 | #[non_exhaustive ] |
857 | /// Error returned from the [`invoke_from_event_loop()`] and [`quit_event_loop()`] function |
858 | pub enum EventLoopError { |
859 | /// The event could not be sent because the event loop was terminated already |
860 | EventLoopTerminated, |
861 | /// The event could not be sent because the Slint platform abstraction was not yet initialized, |
862 | /// or the platform does not support event loop. |
863 | NoEventLoopProvider, |
864 | } |
865 | |
866 | impl core::fmt::Display for EventLoopError { |
867 | fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { |
868 | match self { |
869 | EventLoopError::EventLoopTerminated => { |
870 | f.write_str(data:"The event loop was already terminated" ) |
871 | } |
872 | EventLoopError::NoEventLoopProvider => { |
873 | f.write_str(data:"The Slint platform does not provide an event loop" ) |
874 | } |
875 | } |
876 | } |
877 | } |
878 | |
879 | /// The platform encountered a fatal error. |
880 | /// |
881 | /// This error typically indicates an issue with initialization or connecting to the windowing system. |
882 | /// |
883 | /// This can be constructed from a `String`: |
884 | /// ```rust |
885 | /// use slint::platform::PlatformError; |
886 | /// PlatformError::from(format!("Could not load resource {}" , 1234)); |
887 | /// ``` |
888 | #[derive (Debug)] |
889 | #[non_exhaustive ] |
890 | pub enum PlatformError { |
891 | /// No default platform was selected, or no platform could be initialized. |
892 | /// |
893 | /// If you encounter this error, make sure to either selected trough the `backend-*` cargo features flags, |
894 | /// or call [`platform::set_platform()`](crate::platform::set_platform) |
895 | /// before running the event loop |
896 | NoPlatform, |
897 | /// The Slint Platform does not provide an event loop. |
898 | /// |
899 | /// The [`Platform::run_event_loop`](crate::platform::Platform::run_event_loop) |
900 | /// is not implemented for the current platform. |
901 | NoEventLoopProvider, |
902 | |
903 | /// There is already a platform set from another thread. |
904 | SetPlatformError(crate::platform::SetPlatformError), |
905 | |
906 | /// Another platform-specific error occurred |
907 | Other(String), |
908 | /// Another platform-specific error occurred. |
909 | #[cfg (feature = "std" )] |
910 | OtherError(Box<dyn std::error::Error + Send + Sync>), |
911 | } |
912 | |
913 | impl core::fmt::Display for PlatformError { |
914 | fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { |
915 | match self { |
916 | PlatformError::NoPlatform => f.write_str( |
917 | data:"No default Slint platform was selected, and no Slint platform was initialized" , |
918 | ), |
919 | PlatformError::NoEventLoopProvider => { |
920 | f.write_str(data:"The Slint platform does not provide an event loop" ) |
921 | } |
922 | PlatformError::SetPlatformError(_) => { |
923 | f.write_str(data:"The Slint platform was initialized in another thread" ) |
924 | } |
925 | PlatformError::Other(str: &String) => f.write_str(data:str), |
926 | #[cfg (feature = "std" )] |
927 | PlatformError::OtherError(error: &Box) => error.fmt(f), |
928 | } |
929 | } |
930 | } |
931 | |
932 | impl From<String> for PlatformError { |
933 | fn from(value: String) -> Self { |
934 | Self::Other(value) |
935 | } |
936 | } |
937 | impl From<&str> for PlatformError { |
938 | fn from(value: &str) -> Self { |
939 | Self::Other(value.into()) |
940 | } |
941 | } |
942 | |
943 | #[cfg (feature = "std" )] |
944 | impl From<Box<dyn std::error::Error + Send + Sync>> for PlatformError { |
945 | fn from(error: Box<dyn std::error::Error + Send + Sync>) -> Self { |
946 | Self::OtherError(error) |
947 | } |
948 | } |
949 | |
950 | #[cfg (feature = "std" )] |
951 | impl std::error::Error for PlatformError { |
952 | fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { |
953 | match self { |
954 | PlatformError::OtherError(err: &Box) => Some(err.as_ref()), |
955 | _ => None, |
956 | } |
957 | } |
958 | } |
959 | |
960 | #[test ] |
961 | #[cfg (feature = "std" )] |
962 | fn error_is_send() { |
963 | let _: Box<dyn std::error::Error + Send + Sync + 'static> = PlatformError::NoPlatform.into(); |
964 | } |
965 | |