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#![doc = include_str!("README.md")]
5#![doc(html_logo_url = "https://slint.dev/logo/slint-logo-square-light.svg")]
6#![warn(missing_docs)]
7
8extern crate alloc;
9
10use event_loop::{CustomEvent, EventLoopState, NotRunningEventLoop};
11use i_slint_core::api::EventLoopError;
12use i_slint_core::graphics::RequestedGraphicsAPI;
13use i_slint_core::platform::{EventLoopProxy, PlatformError};
14use i_slint_core::window::WindowAdapter;
15use renderer::WinitCompatibleRenderer;
16use std::cell::RefCell;
17use std::collections::HashMap;
18use std::rc::Rc;
19use std::rc::Weak;
20
21#[cfg(not(target_arch = "wasm32"))]
22mod clipboard;
23mod drag_resize_window;
24mod winitwindowadapter;
25use winitwindowadapter::*;
26pub(crate) mod event_loop;
27
28/// Re-export of the winit crate.
29pub use winit;
30
31/// Internal type used by the winit backend for thread communication and window system updates.
32#[non_exhaustive]
33#[derive(Debug)]
34pub struct SlintUserEvent(CustomEvent);
35
36/// Returned by callbacks passed to [`Window::on_winit_window_event`](WinitWindowAccessor::on_winit_window_event)
37/// to determine if winit events should propagate to the Slint event loop.
38pub enum WinitWindowEventResult {
39 /// The winit event should propagate normally.
40 Propagate,
41 /// The winit event shouldn't be processed further.
42 PreventDefault,
43}
44
45mod renderer {
46 use std::rc::Rc;
47
48 use i_slint_core::{graphics::RequestedGraphicsAPI, platform::PlatformError};
49
50 pub trait WinitCompatibleRenderer {
51 fn render(&self, window: &i_slint_core::api::Window) -> Result<(), PlatformError>;
52
53 fn as_core_renderer(&self) -> &dyn i_slint_core::renderer::Renderer;
54 // Got WindowEvent::Occluded
55 fn occluded(&self, _: bool) {}
56
57 fn suspend(&self) -> Result<(), PlatformError>;
58
59 // Got winit::Event::Resumed
60 fn resume(
61 &self,
62 event_loop: &dyn crate::event_loop::EventLoopInterface,
63 window_attributes: winit::window::WindowAttributes,
64 requested_graphics_api: Option<RequestedGraphicsAPI>,
65 ) -> Result<Rc<winit::window::Window>, PlatformError>;
66
67 fn is_suspended(&self) -> bool;
68 }
69
70 #[cfg(feature = "renderer-femtovg")]
71 pub(crate) mod femtovg;
72 #[cfg(enable_skia_renderer)]
73 pub(crate) mod skia;
74
75 #[cfg(feature = "renderer-software")]
76 pub(crate) mod sw;
77}
78
79#[cfg(enable_accesskit)]
80mod accesskit;
81#[cfg(muda)]
82mod muda;
83#[cfg(not(use_winit_theme))]
84mod xdg_color_scheme;
85
86#[cfg(target_arch = "wasm32")]
87pub(crate) mod wasm_input_helper;
88
89cfg_if::cfg_if! {
90 if #[cfg(feature = "renderer-femtovg")] {
91 const DEFAULT_RENDERER_NAME: &str = "FemtoVG";
92 } else if #[cfg(enable_skia_renderer)] {
93 const DEFAULT_RENDERER_NAME: &'static str = "Skia";
94 } else if #[cfg(feature = "renderer-software")] {
95 const DEFAULT_RENDERER_NAME: &'static str = "Software";
96 } else {
97 compile_error!("Please select a feature to build with the winit backend: `renderer-femtovg`, `renderer-skia`, `renderer-skia-opengl`, `renderer-skia-vulkan` or `renderer-software`");
98 }
99}
100
101fn default_renderer_factory() -> Box<dyn WinitCompatibleRenderer> {
102 cfg_if::cfg_if! {
103 if #[cfg(enable_skia_renderer)] {
104 renderer::skia::WinitSkiaRenderer::new_suspended()
105 } else if #[cfg(feature = "renderer-femtovg")] {
106 renderer::femtovg::GlutinFemtoVGRenderer::new_suspended()
107 } else if #[cfg(feature = "renderer-software")] {
108 renderer::sw::WinitSoftwareRenderer::new_suspended()
109 } else {
110 compile_error!("Please select a feature to build with the winit backend: `renderer-femtovg`, `renderer-skia`, `renderer-skia-opengl`, `renderer-skia-vulkan` or `renderer-software`");
111 }
112 }
113}
114
115fn try_create_window_with_fallback_renderer(
116 shared_backend_data: &Rc<SharedBackendData>,
117 attrs: winit::window::WindowAttributes,
118 _proxy: &winit::event_loop::EventLoopProxy<SlintUserEvent>,
119 #[cfg(all(muda, target_os = "macos"))] muda_enable_default_menu_bar: bool,
120) -> Option<Rc<WinitWindowAdapter>> {
121 [
122 #[cfg(any(
123 feature = "renderer-skia",
124 feature = "renderer-skia-opengl",
125 feature = "renderer-skia-vulkan"
126 ))]
127 renderer::skia::WinitSkiaRenderer::new_suspended,
128 #[cfg(feature = "renderer-femtovg")]
129 renderer::femtovg::GlutinFemtoVGRenderer::new_suspended,
130 #[cfg(feature = "renderer-software")]
131 renderer::sw::WinitSoftwareRenderer::new_suspended,
132 ]
133 .into_iter()
134 .find_map(|renderer_factory| {
135 WinitWindowAdapter::new(
136 shared_backend_data.clone(),
137 renderer_factory(),
138 attrs.clone(),
139 None,
140 #[cfg(any(enable_accesskit, muda))]
141 _proxy.clone(),
142 #[cfg(all(muda, target_os = "macos"))]
143 muda_enable_default_menu_bar,
144 )
145 .ok()
146 })
147}
148
149#[doc(hidden)]
150pub type NativeWidgets = ();
151#[doc(hidden)]
152pub type NativeGlobals = ();
153#[doc(hidden)]
154pub const HAS_NATIVE_STYLE: bool = false;
155#[doc(hidden)]
156pub mod native_widgets {}
157
158/// Use the BackendBuilder to configure the properties of the Winit Backend before creating it.
159/// Create the builder using [`Backend::builder()`], then configure it for example with [`Self::with_renderer_name`],
160/// and build the backend using [`Self::build`].
161pub struct BackendBuilder {
162 allow_fallback: bool,
163 requested_graphics_api: Option<RequestedGraphicsAPI>,
164 window_attributes_hook:
165 Option<Box<dyn Fn(winit::window::WindowAttributes) -> winit::window::WindowAttributes>>,
166 renderer_name: Option<String>,
167 event_loop_builder: Option<winit::event_loop::EventLoopBuilder<SlintUserEvent>>,
168 #[cfg(all(muda, target_os = "macos"))]
169 muda_enable_default_menu_bar_bar: bool,
170 #[cfg(target_family = "wasm")]
171 spawn_event_loop: bool,
172}
173
174impl BackendBuilder {
175 /// Configures this builder to require a renderer that supports the specified graphics API.
176 #[must_use]
177 pub fn request_graphics_api(mut self, graphics_api: RequestedGraphicsAPI) -> Self {
178 self.requested_graphics_api = Some(graphics_api);
179 self
180 }
181
182 /// Configures this builder to use the specified renderer name when building the backend later.
183 /// Pass `renderer-software` for example to configure the backend to use the Slint software renderer.
184 #[must_use]
185 pub fn with_renderer_name(mut self, name: impl Into<String>) -> Self {
186 self.renderer_name = Some(name.into());
187 self
188 }
189
190 /// Configures this builder to use the specified hook that will be called before a Window is created.
191 ///
192 /// It can be used to adjust settings of window that will be created.
193 ///
194 /// # Example
195 ///
196 /// ```rust,no_run
197 /// let mut backend = i_slint_backend_winit::Backend::builder()
198 /// .with_window_attributes_hook(|attributes| attributes.with_content_protected(true))
199 /// .build()
200 /// .unwrap();
201 /// slint::platform::set_platform(Box::new(backend));
202 /// ```
203 #[must_use]
204 pub fn with_window_attributes_hook(
205 mut self,
206 hook: impl Fn(winit::window::WindowAttributes) -> winit::window::WindowAttributes + 'static,
207 ) -> Self {
208 self.window_attributes_hook = Some(Box::new(hook));
209 self
210 }
211
212 /// Configures this builder to use the specified event loop builder when creating the event
213 /// loop during a subsequent call to [`Self::build`].
214 #[must_use]
215 pub fn with_event_loop_builder(
216 mut self,
217 event_loop_builder: winit::event_loop::EventLoopBuilder<SlintUserEvent>,
218 ) -> Self {
219 self.event_loop_builder = Some(event_loop_builder);
220 self
221 }
222
223 /// Configures this builder to enable or disable the default menu bar.
224 /// By default, the menu bar is provided by Slint. Set this to false
225 /// if you're providing your own menu bar.
226 /// Note that an application provided menu bar will be overriden by a `MenuBar`
227 /// declared in Slint code.
228 #[must_use]
229 #[cfg(all(muda, target_os = "macos"))]
230 pub fn with_default_menu_bar(mut self, enable: bool) -> Self {
231 self.muda_enable_default_menu_bar_bar = enable;
232 self
233 }
234
235 #[cfg(target_family = "wasm")]
236 /// Configures this builder to spawn the event loop using [`winit::platform::web::EventLoopExtWebSys::spawn()`]
237 /// run `run_event_loop()` is called.
238 pub fn with_spawn_event_loop(mut self, enable: bool) -> Self {
239 self.spawn_event_loop = enable;
240 self
241 }
242
243 /// Builds the backend with the parameters configured previously. Set the resulting backend
244 /// with `slint::platform::set_platform()`:
245 ///
246 /// # Example
247 ///
248 /// ```rust,no_run
249 /// let mut backend = i_slint_backend_winit::Backend::builder()
250 /// .with_renderer_name("renderer-software")
251 /// .build()
252 /// .unwrap();
253 /// slint::platform::set_platform(Box::new(backend));
254 /// ```
255 pub fn build(self) -> Result<Backend, PlatformError> {
256 #[allow(unused_mut)]
257 let mut event_loop_builder =
258 self.event_loop_builder.unwrap_or_else(winit::event_loop::EventLoop::with_user_event);
259
260 // Never use winit's menu bar. Either we provide one ourselves with muda, or
261 // the user provides one.
262 #[cfg(all(feature = "muda", target_os = "macos"))]
263 winit::platform::macos::EventLoopBuilderExtMacOS::with_default_menu(
264 &mut event_loop_builder,
265 false,
266 );
267
268 // Initialize the winit event loop and propagate errors if for example `DISPLAY` or `WAYLAND_DISPLAY` isn't set.
269
270 let shared_data = Rc::new(SharedBackendData::new(event_loop_builder)?);
271
272 let renderer_factory_fn = match (
273 self.renderer_name.as_deref(),
274 self.requested_graphics_api.as_ref(),
275 ) {
276 #[cfg(feature = "renderer-femtovg")]
277 (Some("gl"), maybe_graphics_api) | (Some("femtovg"), maybe_graphics_api) => {
278 // If a graphics API was requested, double check that it's GL. FemtoVG doesn't support Metal, etc.
279 if let Some(api) = maybe_graphics_api {
280 i_slint_core::graphics::RequestedOpenGLVersion::try_from(api.clone())?;
281 }
282 renderer::femtovg::GlutinFemtoVGRenderer::new_suspended
283 }
284 #[cfg(enable_skia_renderer)]
285 (Some("skia"), maybe_graphics_api) => {
286 renderer::skia::WinitSkiaRenderer::factory_for_graphics_api(maybe_graphics_api)?
287 }
288 #[cfg(all(enable_skia_renderer, supports_opengl))]
289 (Some("skia-opengl"), maybe_graphics_api @ _) => {
290 // If a graphics API was requested, double check that it's GL. FemtoVG doesn't support Metal, etc.
291 if let Some(api) = maybe_graphics_api {
292 i_slint_core::graphics::RequestedOpenGLVersion::try_from(api.clone())?;
293 }
294 renderer::skia::WinitSkiaRenderer::new_opengl_suspended
295 }
296 #[cfg(all(enable_skia_renderer, not(target_os = "android")))]
297 (Some("skia-software"), None) => {
298 renderer::skia::WinitSkiaRenderer::new_software_suspended
299 }
300 #[cfg(feature = "renderer-software")]
301 (Some("sw"), None) | (Some("software"), None) => {
302 renderer::sw::WinitSoftwareRenderer::new_suspended
303 }
304 (None, None) => default_renderer_factory,
305 (Some(renderer_name), _) => {
306 if self.allow_fallback {
307 eprintln!(
308 "slint winit: unrecognized renderer {renderer_name}, falling back to {DEFAULT_RENDERER_NAME}"
309 );
310 default_renderer_factory
311 } else {
312 return Err(PlatformError::NoPlatform);
313 }
314 }
315 (None, Some(_requested_graphics_api)) => {
316 cfg_if::cfg_if! {
317 if #[cfg(enable_skia_renderer)] {
318 renderer::skia::WinitSkiaRenderer::factory_for_graphics_api(Some(_requested_graphics_api))?
319 } else if #[cfg(feature = "renderer-femtovg")] {
320 // If a graphics API was requested, double check that it's GL. FemtoVG doesn't support Metal, etc.
321 i_slint_core::graphics::RequestedOpenGLVersion::try_from(_requested_graphics_api.clone())?;
322 renderer::femtovg::GlutinFemtoVGRenderer::new_suspended
323 } else {
324 return Err(format!("Graphics API use requested by the compile-time enabled renderers don't support that").into())
325 }
326 }
327 }
328 };
329
330 Ok(Backend {
331 requested_graphics_api: self.requested_graphics_api,
332 renderer_factory_fn,
333 event_loop_state: Default::default(),
334 window_attributes_hook: self.window_attributes_hook,
335 shared_data,
336 #[cfg(all(muda, target_os = "macos"))]
337 muda_enable_default_menu_bar_bar: self.muda_enable_default_menu_bar_bar,
338 #[cfg(target_family = "wasm")]
339 spawn_event_loop: self.spawn_event_loop,
340 })
341 }
342}
343
344pub(crate) struct SharedBackendData {
345 active_windows: RefCell<HashMap<winit::window::WindowId, Weak<WinitWindowAdapter>>>,
346 #[cfg(not(target_arch = "wasm32"))]
347 clipboard: std::cell::RefCell<clipboard::ClipboardPair>,
348 not_running_event_loop: RefCell<Option<crate::event_loop::NotRunningEventLoop>>,
349 event_loop_proxy: winit::event_loop::EventLoopProxy<SlintUserEvent>,
350}
351
352impl SharedBackendData {
353 fn new(
354 builder: winit::event_loop::EventLoopBuilder<SlintUserEvent>,
355 ) -> Result<Self, PlatformError> {
356 #[cfg(not(target_arch = "wasm32"))]
357 use raw_window_handle::HasDisplayHandle;
358
359 let nre = NotRunningEventLoop::new(builder)?;
360 let event_loop_proxy = nre.instance.create_proxy();
361 #[cfg(not(target_arch = "wasm32"))]
362 let clipboard = crate::clipboard::create_clipboard(
363 &nre.instance
364 .display_handle()
365 .map_err(|display_err| PlatformError::OtherError(display_err.into()))?,
366 );
367 Ok(Self {
368 active_windows: Default::default(),
369 #[cfg(not(target_arch = "wasm32"))]
370 clipboard: RefCell::new(clipboard),
371 not_running_event_loop: RefCell::new(Some(nre)),
372 event_loop_proxy,
373 })
374 }
375
376 pub(crate) fn with_event_loop<T>(
377 &self,
378 callback: impl FnOnce(
379 &dyn crate::event_loop::EventLoopInterface,
380 ) -> Result<T, Box<dyn std::error::Error + Send + Sync>>,
381 ) -> Result<T, Box<dyn std::error::Error + Send + Sync>> {
382 if crate::event_loop::CURRENT_WINDOW_TARGET.is_set() {
383 crate::event_loop::CURRENT_WINDOW_TARGET.with(|current_target| callback(current_target))
384 } else {
385 match self.not_running_event_loop.borrow().as_ref() {
386 Some(event_loop) => callback(event_loop),
387 None => {
388 Err(PlatformError::from("Event loop functions called without event loop")
389 .into())
390 }
391 }
392 }
393 }
394
395 pub fn register_window(&self, id: winit::window::WindowId, window: Rc<WinitWindowAdapter>) {
396 self.active_windows.borrow_mut().insert(id, Rc::downgrade(&window));
397 }
398
399 pub fn unregister_window(&self, id: winit::window::WindowId) {
400 self.active_windows.borrow_mut().remove(&id);
401 }
402
403 pub fn window_by_id(&self, id: winit::window::WindowId) -> Option<Rc<WinitWindowAdapter>> {
404 self.active_windows.borrow().get(&id).and_then(|weakref| weakref.upgrade())
405 }
406}
407
408#[i_slint_core_macros::slint_doc]
409/// This struct implements the Slint Platform trait.
410/// Use this in conjunction with [`slint::platform::set_platform`](slint:rust:slint/platform/fn.set_platform.html) to initialize.
411/// Slint to use winit for all windowing system interaction.
412///
413/// ```rust,no_run
414/// use i_slint_backend_winit::Backend;
415/// slint::platform::set_platform(Box::new(Backend::new().unwrap()));
416/// ```
417pub struct Backend {
418 requested_graphics_api: Option<RequestedGraphicsAPI>,
419 renderer_factory_fn: fn() -> Box<dyn WinitCompatibleRenderer>,
420 event_loop_state: std::cell::RefCell<Option<crate::event_loop::EventLoopState>>,
421 shared_data: Rc<SharedBackendData>,
422
423 /// This hook is called before a Window is created.
424 ///
425 /// It can be used to adjust settings of window that will be created
426 ///
427 /// See also [`BackendBuilder::with_window_attributes_hook`].
428 ///
429 /// # Example
430 ///
431 /// ```rust,no_run
432 /// let mut backend = i_slint_backend_winit::Backend::new().unwrap();
433 /// backend.window_attributes_hook = Some(Box::new(|attributes| attributes.with_content_protected(true)));
434 /// slint::platform::set_platform(Box::new(backend));
435 /// ```
436 pub window_attributes_hook:
437 Option<Box<dyn Fn(winit::window::WindowAttributes) -> winit::window::WindowAttributes>>,
438
439 #[cfg(all(muda, target_os = "macos"))]
440 muda_enable_default_menu_bar_bar: bool,
441
442 #[cfg(target_family = "wasm")]
443 spawn_event_loop: bool,
444}
445
446impl Backend {
447 #[i_slint_core_macros::slint_doc]
448 /// Creates a new winit backend with the default renderer that's compiled in.
449 ///
450 /// See the [backend documentation](slint:backends_and_renderers) for details on how to select the default renderer.
451 pub fn new() -> Result<Self, PlatformError> {
452 Self::builder().build()
453 }
454
455 #[i_slint_core_macros::slint_doc]
456 /// Creates a new winit backend with the renderer specified by name.
457 ///
458 /// See the [backend documentation](slint:backends_and_renderers) for details on how to select the default renderer.
459 ///
460 /// If the renderer name is `None` or the name is not recognized, the default renderer is selected.
461 pub fn new_with_renderer_by_name(renderer_name: Option<&str>) -> Result<Self, PlatformError> {
462 let mut builder = Self::builder();
463 if let Some(name) = renderer_name {
464 builder = builder.with_renderer_name(name.to_string());
465 }
466 builder.build()
467 }
468
469 /// Creates a new BackendBuilder for configuring aspects of the Winit backend before
470 /// setting it as the platform backend.
471 pub fn builder() -> BackendBuilder {
472 BackendBuilder {
473 allow_fallback: true,
474 requested_graphics_api: None,
475 window_attributes_hook: None,
476 renderer_name: None,
477 event_loop_builder: None,
478 #[cfg(all(muda, target_os = "macos"))]
479 muda_enable_default_menu_bar_bar: true,
480 #[cfg(target_family = "wasm")]
481 spawn_event_loop: false,
482 }
483 }
484}
485
486impl i_slint_core::platform::Platform for Backend {
487 fn create_window_adapter(&self) -> Result<Rc<dyn WindowAdapter>, PlatformError> {
488 let mut attrs = WinitWindowAdapter::window_attributes()?;
489
490 if let Some(hook) = &self.window_attributes_hook {
491 attrs = hook(attrs);
492 }
493
494 let adapter = WinitWindowAdapter::new(
495 self.shared_data.clone(),
496 (self.renderer_factory_fn)(),
497 attrs.clone(),
498 self.requested_graphics_api.clone(),
499 #[cfg(any(enable_accesskit, muda))]
500 self.shared_data.event_loop_proxy.clone(),
501 #[cfg(all(muda, target_os = "macos"))]
502 self.muda_enable_default_menu_bar_bar,
503 )
504 .or_else(|e| {
505 try_create_window_with_fallback_renderer(
506 &self.shared_data,
507 attrs,
508 &self.shared_data.event_loop_proxy.clone(),
509 #[cfg(all(muda, target_os = "macos"))]
510 self.muda_enable_default_menu_bar_bar,
511 )
512 .ok_or_else(|| format!("Winit backend failed to find a suitable renderer: {e}"))
513 })?;
514 Ok(adapter)
515 }
516
517 fn run_event_loop(&self) -> Result<(), PlatformError> {
518 let loop_state = self
519 .event_loop_state
520 .borrow_mut()
521 .take()
522 .unwrap_or_else(|| EventLoopState::new(self.shared_data.clone()));
523 #[cfg(target_family = "wasm")]
524 {
525 if self.spawn_event_loop {
526 return loop_state.spawn();
527 }
528 }
529 let new_state = loop_state.run()?;
530 *self.event_loop_state.borrow_mut() = Some(new_state);
531 Ok(())
532 }
533
534 #[cfg(all(not(target_arch = "wasm32"), not(target_os = "ios")))]
535 fn process_events(
536 &self,
537 timeout: core::time::Duration,
538 _: i_slint_core::InternalToken,
539 ) -> Result<core::ops::ControlFlow<()>, PlatformError> {
540 let loop_state = self
541 .event_loop_state
542 .borrow_mut()
543 .take()
544 .unwrap_or_else(|| EventLoopState::new(self.shared_data.clone()));
545 let (new_state, status) = loop_state.pump_events(Some(timeout))?;
546 *self.event_loop_state.borrow_mut() = Some(new_state);
547 match status {
548 winit::platform::pump_events::PumpStatus::Continue => {
549 Ok(core::ops::ControlFlow::Continue(()))
550 }
551 winit::platform::pump_events::PumpStatus::Exit(code) => {
552 if code == 0 {
553 Ok(core::ops::ControlFlow::Break(()))
554 } else {
555 Err(format!("Event loop exited with non-zero code {code}").into())
556 }
557 }
558 }
559 }
560
561 fn new_event_loop_proxy(&self) -> Option<Box<dyn EventLoopProxy>> {
562 struct Proxy(winit::event_loop::EventLoopProxy<SlintUserEvent>);
563 impl EventLoopProxy for Proxy {
564 fn quit_event_loop(&self) -> Result<(), EventLoopError> {
565 self.0
566 .send_event(SlintUserEvent(CustomEvent::Exit))
567 .map_err(|_| EventLoopError::EventLoopTerminated)
568 }
569
570 fn invoke_from_event_loop(
571 &self,
572 event: Box<dyn FnOnce() + Send>,
573 ) -> Result<(), EventLoopError> {
574 // Calling send_event is usually done by winit at the bottom of the stack,
575 // in event handlers, and thus winit might decide to process the event
576 // immediately within that stack.
577 // To prevent re-entrancy issues that might happen by getting the application
578 // event processed on top of the current stack, set winit in Poll mode so that
579 // events are queued and process on top of a clean stack during a requested animation
580 // frame a few moments later.
581 // This also allows batching multiple post_event calls and redraw their state changes
582 // all at once.
583 #[cfg(target_arch = "wasm32")]
584 self.0
585 .send_event(SlintUserEvent(CustomEvent::WakeEventLoopWorkaround))
586 .map_err(|_| EventLoopError::EventLoopTerminated)?;
587
588 self.0
589 .send_event(SlintUserEvent(CustomEvent::UserEvent(event)))
590 .map_err(|_| EventLoopError::EventLoopTerminated)
591 }
592 }
593 Some(Box::new(Proxy(self.shared_data.event_loop_proxy.clone())))
594 }
595
596 #[cfg(target_arch = "wasm32")]
597 fn set_clipboard_text(&self, text: &str, clipboard: i_slint_core::platform::Clipboard) {
598 crate::wasm_input_helper::set_clipboard_text(text.into(), clipboard);
599 }
600
601 #[cfg(not(target_arch = "wasm32"))]
602 fn set_clipboard_text(&self, text: &str, clipboard: i_slint_core::platform::Clipboard) {
603 let mut pair = self.shared_data.clipboard.borrow_mut();
604 if let Some(clipboard) = clipboard::select_clipboard(&mut pair, clipboard) {
605 clipboard.set_contents(text.into()).ok();
606 }
607 }
608
609 #[cfg(target_arch = "wasm32")]
610 fn clipboard_text(&self, clipboard: i_slint_core::platform::Clipboard) -> Option<String> {
611 crate::wasm_input_helper::get_clipboard_text(clipboard)
612 }
613
614 #[cfg(not(target_arch = "wasm32"))]
615 fn clipboard_text(&self, clipboard: i_slint_core::platform::Clipboard) -> Option<String> {
616 let mut pair = self.shared_data.clipboard.borrow_mut();
617 clipboard::select_clipboard(&mut pair, clipboard).and_then(|c| c.get_contents().ok())
618 }
619}
620
621mod private {
622 pub trait WinitWindowAccessorSealed {}
623}
624
625#[i_slint_core_macros::slint_doc]
626/// This helper trait can be used to obtain access to the [`winit::window::Window`] for a given
627/// [`slint::Window`](slint:rust:slint/struct.window).")]
628pub trait WinitWindowAccessor: private::WinitWindowAccessorSealed {
629 /// Returns true if a [`winit::window::Window`] exists for this window. This is the case if the window is
630 /// backed by this winit backend.
631 fn has_winit_window(&self) -> bool;
632 /// Invokes the specified callback with a reference to the [`winit::window::Window`] that exists for this Slint window
633 /// and returns `Some(T)`; otherwise `None`.
634 fn with_winit_window<T>(&self, callback: impl FnOnce(&winit::window::Window) -> T)
635 -> Option<T>;
636 /// Registers a window event filter callback for this Slint window.
637 ///
638 /// The callback is invoked in the winit event loop whenever a window event is received with a reference to the
639 /// [`slint::Window`](i_slint_core::api::Window) and the [`winit::event::WindowEvent`]. The return value of the
640 /// callback specifies whether Slint should handle this event.
641 ///
642 /// If this window [is not backed by winit](WinitWindowAccessor::has_winit_window), this function is a no-op.
643 fn on_winit_window_event(
644 &self,
645 callback: impl FnMut(&i_slint_core::api::Window, &winit::event::WindowEvent) -> WinitWindowEventResult
646 + 'static,
647 );
648
649 /// Creates a non Slint aware window with winit
650 fn create_winit_window(
651 &self,
652 window_attributes: winit::window::WindowAttributes,
653 ) -> Result<winit::window::Window, winit::error::OsError>;
654}
655
656impl WinitWindowAccessor for i_slint_core::api::Window {
657 fn has_winit_window(&self) -> bool {
658 i_slint_core::window::WindowInner::from_pub(self)
659 .window_adapter()
660 .internal(i_slint_core::InternalToken)
661 .and_then(|wa| wa.as_any().downcast_ref::<WinitWindowAdapter>())
662 .is_some_and(|adapter| adapter.winit_window().is_some())
663 }
664
665 fn with_winit_window<T>(
666 &self,
667 callback: impl FnOnce(&winit::window::Window) -> T,
668 ) -> Option<T> {
669 i_slint_core::window::WindowInner::from_pub(self)
670 .window_adapter()
671 .internal(i_slint_core::InternalToken)
672 .and_then(|wa| wa.as_any().downcast_ref::<WinitWindowAdapter>())
673 .and_then(|adapter| adapter.winit_window().map(|w| callback(&w)))
674 }
675
676 fn on_winit_window_event(
677 &self,
678 mut callback: impl FnMut(&i_slint_core::api::Window, &winit::event::WindowEvent) -> WinitWindowEventResult
679 + 'static,
680 ) {
681 if let Some(adapter) = i_slint_core::window::WindowInner::from_pub(self)
682 .window_adapter()
683 .internal(i_slint_core::InternalToken)
684 .and_then(|wa| wa.as_any().downcast_ref::<WinitWindowAdapter>())
685 {
686 adapter
687 .window_event_filter
688 .set(Some(Box::new(move |window, event| callback(window, event))));
689 }
690 }
691
692 /// Creates a non Slint aware window with winit
693 fn create_winit_window(
694 &self,
695 window_attributes: winit::window::WindowAttributes,
696 ) -> Result<winit::window::Window, winit::error::OsError> {
697 i_slint_core::window::WindowInner::from_pub(self)
698 .window_adapter()
699 .internal(i_slint_core::InternalToken)
700 .unwrap()
701 .as_any()
702 .downcast_ref::<WinitWindowAdapter>()
703 .unwrap()
704 .shared_backend_data
705 .with_event_loop(|eli| Ok(eli.create_window(window_attributes)))
706 .unwrap()
707 }
708}
709
710impl private::WinitWindowAccessorSealed for i_slint_core::api::Window {}
711
712#[cfg(test)]
713mod testui {
714 slint::slint! {
715 export component App inherits Window {
716 Text { text: "Ok"; }
717 }
718 }
719}
720
721// Sorry, can't test with rust test harness and multiple threads.
722#[cfg(not(any(target_arch = "wasm32", target_os = "macos", target_os = "ios")))]
723#[test]
724fn test_window_accessor_and_rwh() {
725 slint::platform::set_platform(Box::new(crate::Backend::new().unwrap())).unwrap();
726
727 use testui::*;
728 let app = App::new().unwrap();
729 let slint_window = app.window();
730 assert!(slint_window.has_winit_window());
731 let handle = slint_window.window_handle();
732 use raw_window_handle::{HasDisplayHandle, HasWindowHandle};
733 assert!(handle.window_handle().is_ok());
734 assert!(handle.display_handle().is_ok());
735}
736

Provided by KDAB

Privacy Policy