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