1#![doc = include_str!("README.md")]
5#![doc(html_logo_url = "https://slint.dev/logo/slint-logo-square-light.svg")]
6#![warn(missing_docs)]
7#![cfg_attr(slint_nightly_test, feature(non_exhaustive_omitted_patterns_lint))]
8#![cfg_attr(slint_nightly_test, warn(non_exhaustive_omitted_patterns))]
9
10extern crate alloc;
11
12use event_loop::{CustomEvent, EventLoopState};
13use i_slint_core::api::EventLoopError;
14use i_slint_core::graphics::RequestedGraphicsAPI;
15use i_slint_core::platform::{EventLoopProxy, PlatformError};
16use i_slint_core::window::WindowAdapter;
17use renderer::WinitCompatibleRenderer;
18use std::cell::RefCell;
19use std::collections::HashMap;
20use std::rc::Rc;
21use std::rc::Weak;
22use std::sync::Arc;
23use winit::event_loop::ActiveEventLoop;
24
25#[cfg(not(target_arch = "wasm32"))]
26mod clipboard;
27mod drag_resize_window;
28mod winitwindowadapter;
29use winitwindowadapter::*;
30pub(crate) mod event_loop;
31mod frame_throttle;
32#[cfg(target_os = "ios")]
33mod ios;
34
35pub use winit;
37
38#[non_exhaustive]
42#[derive(Debug)]
43pub struct SlintEvent(CustomEvent);
44
45#[i_slint_core_macros::slint_doc]
46pub type EventLoopBuilder = winit::event_loop::EventLoopBuilder<SlintEvent>;
51
52pub enum EventResult {
55 Propagate,
57 PreventDefault,
59}
60
61mod renderer {
62 use std::sync::Arc;
63
64 use i_slint_core::platform::PlatformError;
65 use winit::event_loop::ActiveEventLoop;
66
67 pub trait WinitCompatibleRenderer: std::any::Any {
68 fn render(&self, window: &i_slint_core::api::Window) -> Result<(), PlatformError>;
69
70 fn as_core_renderer(&self) -> &dyn i_slint_core::renderer::Renderer;
71 fn occluded(&self, _: bool) {}
73
74 fn suspend(&self) -> Result<(), PlatformError>;
75
76 fn resume(
78 &self,
79 active_event_loop: &ActiveEventLoop,
80 window_attributes: winit::window::WindowAttributes,
81 ) -> Result<Arc<winit::window::Window>, PlatformError>;
82 }
83
84 #[cfg(enable_femtovg_renderer)]
85 pub(crate) mod femtovg;
86 #[cfg(enable_skia_renderer)]
87 pub(crate) mod skia;
88
89 #[cfg(feature = "renderer-software")]
90 pub(crate) mod sw;
91}
92
93#[cfg(enable_accesskit)]
94mod accesskit;
95#[cfg(muda)]
96mod muda;
97#[cfg(not(use_winit_theme))]
98mod xdg_color_scheme;
99
100#[cfg(target_arch = "wasm32")]
101pub(crate) mod wasm_input_helper;
102
103cfg_if::cfg_if! {
104 if #[cfg(enable_femtovg_renderer)] {
105 const DEFAULT_RENDERER_NAME: &str = "FemtoVG";
106 } else if #[cfg(enable_skia_renderer)] {
107 const DEFAULT_RENDERER_NAME: &'static str = "Skia";
108 } else if #[cfg(feature = "renderer-software")] {
109 const DEFAULT_RENDERER_NAME: &'static str = "Software";
110 } else {
111 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`");
112 }
113}
114
115fn default_renderer_factory(
116 shared_backend_data: &Rc<SharedBackendData>,
117) -> Result<Box<dyn WinitCompatibleRenderer>, PlatformError> {
118 cfg_if::cfg_if! {
119 if #[cfg(enable_skia_renderer)] {
120 renderer::skia::WinitSkiaRenderer::new_suspended(shared_backend_data)
121 } else if #[cfg(feature = "renderer-femtovg-wgpu")] {
122 renderer::femtovg::WGPUFemtoVGRenderer::new_suspended(shared_backend_data)
123 } else if #[cfg(all(feature = "renderer-femtovg", supports_opengl))] {
124 renderer::femtovg::GlutinFemtoVGRenderer::new_suspended(shared_backend_data)
125 } else if #[cfg(feature = "renderer-software")] {
126 renderer::sw::WinitSoftwareRenderer::new_suspended(shared_backend_data)
127 } else {
128 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`");
129 }
130 }
131}
132
133fn try_create_window_with_fallback_renderer(
134 shared_backend_data: &Rc<SharedBackendData>,
135 attrs: winit::window::WindowAttributes,
136 _proxy: &winit::event_loop::EventLoopProxy<SlintEvent>,
137 #[cfg(all(muda, target_os = "macos"))] muda_enable_default_menu_bar: bool,
138) -> Option<Rc<WinitWindowAdapter>> {
139 [
140 #[cfg(any(
141 feature = "renderer-skia",
142 feature = "renderer-skia-opengl",
143 feature = "renderer-skia-vulkan"
144 ))]
145 renderer::skia::WinitSkiaRenderer::new_suspended,
146 #[cfg(feature = "renderer-femtovg-wgpu")]
147 renderer::femtovg::WGPUFemtoVGRenderer::new_suspended,
148 #[cfg(all(
149 feature = "renderer-femtovg",
150 supports_opengl,
151 not(feature = "renderer-femtovg-wgpu")
152 ))]
153 renderer::femtovg::GlutinFemtoVGRenderer::new_suspended,
154 #[cfg(feature = "renderer-software")]
155 renderer::sw::WinitSoftwareRenderer::new_suspended,
156 ]
157 .into_iter()
158 .find_map(|renderer_factory| {
159 Some(WinitWindowAdapter::new(
160 shared_backend_data.clone(),
161 renderer_factory(&shared_backend_data).ok()?,
162 attrs.clone(),
163 #[cfg(any(enable_accesskit, muda))]
164 _proxy.clone(),
165 #[cfg(all(muda, target_os = "macos"))]
166 muda_enable_default_menu_bar,
167 ))
168 })
169}
170
171#[doc(hidden)]
172pub type NativeWidgets = ();
173#[doc(hidden)]
174pub type NativeGlobals = ();
175#[doc(hidden)]
176pub const HAS_NATIVE_STYLE: bool = false;
177#[doc(hidden)]
178pub mod native_widgets {}
179
180#[allow(unused_variables)]
188pub trait CustomApplicationHandler {
189 fn resumed(&mut self, _event_loop: &ActiveEventLoop) -> EventResult {
191 EventResult::Propagate
192 }
193
194 fn window_event(
196 &mut self,
197 event_loop: &ActiveEventLoop,
198 window_id: winit::window::WindowId,
199 winit_window: Option<&winit::window::Window>,
200 slint_window: Option<&i_slint_core::api::Window>,
201 event: &winit::event::WindowEvent,
202 ) -> EventResult {
203 EventResult::Propagate
204 }
205
206 fn new_events(
208 &mut self,
209 event_loop: &ActiveEventLoop,
210 cause: winit::event::StartCause,
211 ) -> EventResult {
212 EventResult::Propagate
213 }
214
215 fn device_event(
217 &mut self,
218 event_loop: &ActiveEventLoop,
219 device_id: winit::event::DeviceId,
220 event: winit::event::DeviceEvent,
221 ) -> EventResult {
222 EventResult::Propagate
223 }
224
225 fn about_to_wait(&mut self, event_loop: &ActiveEventLoop) -> EventResult {
227 EventResult::Propagate
228 }
229
230 fn suspended(&mut self, event_loop: &ActiveEventLoop) -> EventResult {
232 EventResult::Propagate
233 }
234
235 fn exiting(&mut self, event_loop: &ActiveEventLoop) -> EventResult {
237 EventResult::Propagate
238 }
239
240 fn memory_warning(&mut self, event_loop: &ActiveEventLoop) -> EventResult {
242 EventResult::Propagate
243 }
244}
245
246pub struct BackendBuilder {
250 allow_fallback: bool,
251 requested_graphics_api: Option<RequestedGraphicsAPI>,
252 window_attributes_hook:
253 Option<Box<dyn Fn(winit::window::WindowAttributes) -> winit::window::WindowAttributes>>,
254 renderer_name: Option<String>,
255 event_loop_builder: Option<EventLoopBuilder>,
256 #[cfg(all(muda, target_os = "macos"))]
257 muda_enable_default_menu_bar_bar: bool,
258 #[cfg(target_family = "wasm")]
259 spawn_event_loop: bool,
260 custom_application_handler: Option<Box<dyn CustomApplicationHandler>>,
261}
262
263impl BackendBuilder {
264 #[must_use]
266 pub fn request_graphics_api(mut self, graphics_api: RequestedGraphicsAPI) -> Self {
267 self.requested_graphics_api = Some(graphics_api);
268 self
269 }
270
271 #[must_use]
274 pub fn with_renderer_name(mut self, name: impl Into<String>) -> Self {
275 self.renderer_name = Some(name.into());
276 self
277 }
278
279 #[must_use]
293 pub fn with_window_attributes_hook(
294 mut self,
295 hook: impl Fn(winit::window::WindowAttributes) -> winit::window::WindowAttributes + 'static,
296 ) -> Self {
297 self.window_attributes_hook = Some(Box::new(hook));
298 self
299 }
300
301 #[must_use]
304 pub fn with_event_loop_builder(mut self, event_loop_builder: EventLoopBuilder) -> Self {
305 self.event_loop_builder = Some(event_loop_builder);
306 self
307 }
308
309 #[must_use]
315 #[cfg(all(muda, target_os = "macos"))]
316 pub fn with_default_menu_bar(mut self, enable: bool) -> Self {
317 self.muda_enable_default_menu_bar_bar = enable;
318 self
319 }
320
321 #[cfg(target_family = "wasm")]
322 pub fn with_spawn_event_loop(mut self, enable: bool) -> Self {
325 self.spawn_event_loop = enable;
326 self
327 }
328
329 #[must_use]
334 pub fn with_custom_application_handler(
335 mut self,
336 handler: Box<dyn CustomApplicationHandler + 'static>,
337 ) -> Self {
338 self.custom_application_handler = Some(handler);
339 self
340 }
341
342 pub fn build(self) -> Result<Backend, PlatformError> {
355 #[allow(unused_mut)]
356 let mut event_loop_builder =
357 self.event_loop_builder.unwrap_or_else(winit::event_loop::EventLoop::with_user_event);
358
359 #[cfg(all(feature = "muda", target_os = "macos"))]
362 winit::platform::macos::EventLoopBuilderExtMacOS::with_default_menu(
363 &mut event_loop_builder,
364 false,
365 );
366
367 let shared_data = Rc::new(SharedBackendData::new(
370 event_loop_builder,
371 self.requested_graphics_api.clone(),
372 )?);
373
374 let renderer_factory_fn = match (
375 self.renderer_name.as_deref(),
376 self.requested_graphics_api.as_ref(),
377 ) {
378 #[cfg(all(feature = "renderer-femtovg", supports_opengl))]
379 (Some("gl"), maybe_graphics_api) | (Some("femtovg"), maybe_graphics_api) => {
380 if let Some(api) = maybe_graphics_api {
382 i_slint_core::graphics::RequestedOpenGLVersion::try_from(api)?;
383 }
384 renderer::femtovg::GlutinFemtoVGRenderer::new_suspended
385 }
386 #[cfg(feature = "renderer-femtovg-wgpu")]
387 (Some("femtovg-wgpu"), maybe_graphics_api) => {
388 if let Some(_api) = maybe_graphics_api {
389 #[cfg(feature = "unstable-wgpu-28")]
390 if !matches!(_api, RequestedGraphicsAPI::WGPU28(..)) {
391 return Err(
392 "The FemtoVG WGPU renderer only supports the WGPU28 graphics API selection"
393 .into(),
394 );
395 }
396 }
397 renderer::femtovg::WGPUFemtoVGRenderer::new_suspended
398 }
399 #[cfg(enable_skia_renderer)]
400 (Some("skia"), maybe_graphics_api) => {
401 renderer::skia::WinitSkiaRenderer::factory_for_graphics_api(maybe_graphics_api)?
402 }
403 #[cfg(all(enable_skia_renderer, supports_opengl))]
404 (Some("skia-opengl"), maybe_graphics_api @ _) => {
405 if let Some(api) = maybe_graphics_api {
407 i_slint_core::graphics::RequestedOpenGLVersion::try_from(api)?;
408 }
409 renderer::skia::WinitSkiaRenderer::new_opengl_suspended
410 }
411 #[cfg(all(
412 enable_skia_renderer,
413 any(feature = "unstable-wgpu-27", feature = "unstable-wgpu-28")
414 ))]
415 (Some("skia-wgpu"), maybe_graphics_api @ _) => {
416 if let Some(factory) = maybe_graphics_api.map_or_else(
417 || {
418 let result;
419 cfg_if::cfg_if!(
420 if #[cfg(feature = "unstable-wgpu-28")]
421 {
422 result = Some(
423 renderer::skia::WinitSkiaRenderer::new_wgpu_28_suspended
424 as RendererFactoryFn,
425 );
426 } else {
427 result = Some(
428 renderer::skia::WinitSkiaRenderer::new_wgpu_27_suspended
429 as RendererFactoryFn,
430 );
431 }
432 );
433 result
434 },
435 |api| {
436 #[cfg(feature = "unstable-wgpu-27")]
437 if matches!(api, RequestedGraphicsAPI::WGPU27(..)) {
438 return Some(
439 renderer::skia::WinitSkiaRenderer::new_wgpu_27_suspended
440 as RendererFactoryFn,
441 );
442 }
443 #[cfg(feature = "unstable-wgpu-28")]
444 if matches!(api, RequestedGraphicsAPI::WGPU28(..)) {
445 return Some(
446 renderer::skia::WinitSkiaRenderer::new_wgpu_28_suspended
447 as RendererFactoryFn,
448 );
449 }
450 None
451 },
452 ) {
453 factory
454 } else {
455 return Err(
456 format!("Skia with WGPU doesn't support non-WGPU graphics API").into()
457 );
458 }
459 }
460 #[cfg(all(enable_skia_renderer, not(target_os = "android")))]
461 (Some("skia-software"), None) => {
462 renderer::skia::WinitSkiaRenderer::new_software_suspended
463 }
464 #[cfg(feature = "renderer-software")]
465 (Some("sw"), None) | (Some("software"), None) => {
466 renderer::sw::WinitSoftwareRenderer::new_suspended
467 }
468 (None, None) => default_renderer_factory,
469 (Some(renderer_name), _) => {
470 if self.allow_fallback {
471 eprintln!(
472 "slint winit: unrecognized renderer {renderer_name}, falling back to {DEFAULT_RENDERER_NAME}"
473 );
474 default_renderer_factory
475 } else {
476 return Err(PlatformError::NoPlatform);
477 }
478 }
479 #[cfg(feature = "unstable-wgpu-28")]
480 (None, Some(RequestedGraphicsAPI::WGPU28(..))) => {
481 cfg_if::cfg_if! {
482 if #[cfg(enable_skia_renderer)] {
483 renderer::skia::WinitSkiaRenderer::new_wgpu_28_suspended
484 } else {
485 renderer::femtovg::WGPUFemtoVGRenderer::new_suspended
486 }
487 }
488 }
489 #[cfg(all(enable_skia_renderer, feature = "unstable-wgpu-27"))]
490 (None, Some(RequestedGraphicsAPI::WGPU27(..))) => {
491 renderer::skia::WinitSkiaRenderer::new_wgpu_27_suspended
492 }
493 (None, Some(_requested_graphics_api)) => {
494 cfg_if::cfg_if! {
495 if #[cfg(enable_skia_renderer)] {
496 renderer::skia::WinitSkiaRenderer::factory_for_graphics_api(Some(_requested_graphics_api))?
497 } else if #[cfg(all(feature = "renderer-femtovg", supports_opengl))] {
498 i_slint_core::graphics::RequestedOpenGLVersion::try_from(_requested_graphics_api)?;
500 renderer::femtovg::GlutinFemtoVGRenderer::new_suspended
501 } else {
502 return Err(format!("Graphics API use requested by the compile-time enabled renderers don't support that").into())
503 }
504 }
505 }
506 };
507
508 Ok(Backend {
509 renderer_factory_fn,
510 event_loop_state: Default::default(),
511 window_attributes_hook: self.window_attributes_hook,
512 shared_data,
513 #[cfg(all(muda, target_os = "macos"))]
514 muda_enable_default_menu_bar_bar: self.muda_enable_default_menu_bar_bar,
515 #[cfg(target_family = "wasm")]
516 spawn_event_loop: self.spawn_event_loop,
517 custom_application_handler: self.custom_application_handler.into(),
518 })
519 }
520}
521
522pub(crate) struct SharedBackendData {
523 _requested_graphics_api: Option<RequestedGraphicsAPI>,
524 #[cfg(enable_skia_renderer)]
525 skia_context: i_slint_renderer_skia::SkiaSharedContext,
526 active_windows: Rc<RefCell<HashMap<winit::window::WindowId, Weak<WinitWindowAdapter>>>>,
527 inactive_windows: RefCell<Vec<Weak<WinitWindowAdapter>>>,
530 #[cfg(not(target_arch = "wasm32"))]
531 clipboard: std::cell::RefCell<clipboard::ClipboardPair>,
532 not_running_event_loop: RefCell<Option<winit::event_loop::EventLoop<SlintEvent>>>,
533 event_loop_proxy: winit::event_loop::EventLoopProxy<SlintEvent>,
534 is_wayland: bool,
535 #[cfg(target_os = "ios")]
536 #[allow(unused)]
537 keyboard_notifications: ios::KeyboardNotifications,
538}
539
540impl SharedBackendData {
541 fn new(
542 mut builder: EventLoopBuilder,
543 requested_graphics_api: Option<RequestedGraphicsAPI>,
544 ) -> Result<Self, PlatformError> {
545 #[cfg(not(target_arch = "wasm32"))]
546 use raw_window_handle::HasDisplayHandle;
547
548 #[cfg(all(unix, not(target_vendor = "apple")))]
549 {
550 #[cfg(feature = "wayland")]
551 {
552 use winit::platform::wayland::EventLoopBuilderExtWayland;
553 builder.with_any_thread(true);
554 }
555 #[cfg(feature = "x11")]
556 {
557 use winit::platform::x11::EventLoopBuilderExtX11;
558 builder.with_any_thread(true);
559
560 #[cfg(feature = "wayland")]
564 if std::fs::metadata("/proc/sys/fs/binfmt_misc/WSLInterop").is_ok()
565 || std::fs::metadata("/run/WSL").is_ok()
566 {
567 builder.with_x11();
568 }
569 }
570 }
571 #[cfg(target_family = "windows")]
572 {
573 use winit::platform::windows::EventLoopBuilderExtWindows;
574 builder.with_any_thread(true);
575 }
576
577 let event_loop =
578 builder.build().map_err(|e| format!("Error initializing winit event loop: {e}"))?;
579
580 cfg_if::cfg_if! {
581 if #[cfg(all(unix, not(target_vendor = "apple"), feature = "wayland"))] {
582 use winit::platform::wayland::EventLoopExtWayland;
583 let is_wayland = event_loop.is_wayland();
584 } else {
585 let is_wayland = false;
586 }
587 }
588
589 let active_windows =
590 Rc::<RefCell<HashMap<winit::window::WindowId, Weak<WinitWindowAdapter>>>>::default();
591
592 #[cfg(target_os = "ios")]
593 let keyboard_notifications =
594 ios::register_keyboard_notifications(Rc::downgrade(&active_windows));
595
596 let event_loop_proxy = event_loop.create_proxy();
597 #[cfg(not(target_arch = "wasm32"))]
598 let clipboard = crate::clipboard::create_clipboard(
599 &event_loop
600 .display_handle()
601 .map_err(|display_err| PlatformError::OtherError(display_err.into()))?,
602 );
603 Ok(Self {
604 _requested_graphics_api: requested_graphics_api,
605 #[cfg(enable_skia_renderer)]
606 skia_context: i_slint_renderer_skia::SkiaSharedContext::default(),
607 active_windows,
608 inactive_windows: Default::default(),
609 #[cfg(not(target_arch = "wasm32"))]
610 clipboard: RefCell::new(clipboard),
611 not_running_event_loop: RefCell::new(Some(event_loop)),
612 event_loop_proxy,
613 is_wayland,
614 #[cfg(target_os = "ios")]
615 keyboard_notifications,
616 })
617 }
618
619 pub fn register_window(&self, id: winit::window::WindowId, window: Rc<WinitWindowAdapter>) {
620 self.active_windows.borrow_mut().insert(id, Rc::downgrade(&window));
621 }
622
623 pub fn register_inactive_window(&self, window: Rc<WinitWindowAdapter>) {
624 let window = Rc::downgrade(&window);
625 let mut inactive_windows = self.inactive_windows.borrow_mut();
626 if !inactive_windows.iter().any(|w| Weak::ptr_eq(w, &window)) {
627 inactive_windows.push(window);
628 }
629 }
630
631 pub fn unregister_window(&self, id: Option<winit::window::WindowId>) {
632 if let Some(id) = id {
633 self.active_windows.borrow_mut().remove(&id);
634 } else {
635 self.inactive_windows
637 .borrow_mut()
638 .retain(|inactive_weak_window| inactive_weak_window.strong_count() > 0)
639 }
640 }
641
642 pub fn create_inactive_windows(
643 &self,
644 event_loop: &winit::event_loop::ActiveEventLoop,
645 ) -> Result<(), PlatformError> {
646 let mut inactive_windows = self.inactive_windows.take();
647 let mut result = Ok(());
648 while let Some(window_weak) = inactive_windows.pop() {
649 if let Some(err) = window_weak.upgrade().and_then(|w| w.ensure_window(event_loop).err())
650 {
651 result = Err(err);
652 break;
653 }
654 }
655 self.inactive_windows.borrow_mut().extend(inactive_windows);
656 result
657 }
658
659 pub fn window_by_id(&self, id: winit::window::WindowId) -> Option<Rc<WinitWindowAdapter>> {
660 self.active_windows.borrow().get(&id).and_then(|weakref| weakref.upgrade())
661 }
662}
663
664type RendererFactoryFn =
665 fn(&Rc<SharedBackendData>) -> Result<Box<dyn WinitCompatibleRenderer>, PlatformError>;
666
667#[i_slint_core_macros::slint_doc]
668pub struct Backend {
677 renderer_factory_fn: RendererFactoryFn,
678 event_loop_state: RefCell<Option<crate::event_loop::EventLoopState>>,
679 shared_data: Rc<SharedBackendData>,
680 custom_application_handler: RefCell<Option<Box<dyn crate::CustomApplicationHandler>>>,
681
682 pub window_attributes_hook:
696 Option<Box<dyn Fn(winit::window::WindowAttributes) -> winit::window::WindowAttributes>>,
697
698 #[cfg(all(muda, target_os = "macos"))]
699 muda_enable_default_menu_bar_bar: bool,
700
701 #[cfg(target_family = "wasm")]
702 spawn_event_loop: bool,
703}
704
705impl Backend {
706 #[i_slint_core_macros::slint_doc]
707 pub fn new() -> Result<Self, PlatformError> {
711 Self::builder().build()
712 }
713
714 #[i_slint_core_macros::slint_doc]
715 pub fn new_with_renderer_by_name(renderer_name: Option<&str>) -> Result<Self, PlatformError> {
721 let mut builder = Self::builder();
722 if let Some(name) = renderer_name {
723 builder = builder.with_renderer_name(name.to_string());
724 }
725 builder.build()
726 }
727
728 pub fn builder() -> BackendBuilder {
731 BackendBuilder {
732 allow_fallback: true,
733 requested_graphics_api: None,
734 window_attributes_hook: None,
735 renderer_name: None,
736 event_loop_builder: None,
737 #[cfg(all(muda, target_os = "macos"))]
738 muda_enable_default_menu_bar_bar: true,
739 #[cfg(target_family = "wasm")]
740 spawn_event_loop: false,
741 custom_application_handler: None,
742 }
743 }
744}
745
746impl i_slint_core::platform::Platform for Backend {
747 fn create_window_adapter(&self) -> Result<Rc<dyn WindowAdapter>, PlatformError> {
748 let mut attrs = WinitWindowAdapter::window_attributes()?;
749
750 if let Some(hook) = &self.window_attributes_hook {
751 attrs = hook(attrs);
752 }
753
754 let adapter = (self.renderer_factory_fn)(&self.shared_data).map_or_else(
755 |e| {
756 try_create_window_with_fallback_renderer(
757 &self.shared_data,
758 attrs.clone(),
759 &self.shared_data.event_loop_proxy.clone(),
760 #[cfg(all(muda, target_os = "macos"))]
761 self.muda_enable_default_menu_bar_bar,
762 )
763 .ok_or_else(|| format!("Winit backend failed to find a suitable renderer: {e}"))
764 },
765 |renderer| {
766 Ok(WinitWindowAdapter::new(
767 self.shared_data.clone(),
768 renderer,
769 attrs.clone(),
770 #[cfg(any(enable_accesskit, muda))]
771 self.shared_data.event_loop_proxy.clone(),
772 #[cfg(all(muda, target_os = "macos"))]
773 self.muda_enable_default_menu_bar_bar,
774 ))
775 },
776 )?;
777 Ok(adapter)
778 }
779
780 fn run_event_loop(&self) -> Result<(), PlatformError> {
781 let loop_state = self.event_loop_state.borrow_mut().take().unwrap_or_else(|| {
782 EventLoopState::new(self.shared_data.clone(), self.custom_application_handler.take())
783 });
784 #[cfg(target_family = "wasm")]
785 {
786 if self.spawn_event_loop {
787 return loop_state.spawn();
788 }
789 }
790 let new_state = loop_state.run()?;
791 *self.event_loop_state.borrow_mut() = Some(new_state);
792 Ok(())
793 }
794
795 #[cfg(all(not(target_arch = "wasm32"), not(ios_and_friends)))]
796 fn process_events(
797 &self,
798 timeout: core::time::Duration,
799 _: i_slint_core::InternalToken,
800 ) -> Result<core::ops::ControlFlow<()>, PlatformError> {
801 let loop_state = self.event_loop_state.borrow_mut().take().unwrap_or_else(|| {
802 EventLoopState::new(self.shared_data.clone(), self.custom_application_handler.take())
803 });
804 let (new_state, status) = loop_state.pump_events(Some(timeout))?;
805 *self.event_loop_state.borrow_mut() = Some(new_state);
806 match status {
807 winit::platform::pump_events::PumpStatus::Continue => {
808 Ok(core::ops::ControlFlow::Continue(()))
809 }
810 winit::platform::pump_events::PumpStatus::Exit(code) => {
811 if code == 0 {
812 Ok(core::ops::ControlFlow::Break(()))
813 } else {
814 Err(format!("Event loop exited with non-zero code {code}").into())
815 }
816 }
817 }
818 }
819
820 fn new_event_loop_proxy(&self) -> Option<Box<dyn EventLoopProxy>> {
821 struct Proxy(winit::event_loop::EventLoopProxy<SlintEvent>);
822 impl EventLoopProxy for Proxy {
823 fn quit_event_loop(&self) -> Result<(), EventLoopError> {
824 self.0
825 .send_event(SlintEvent(CustomEvent::Exit))
826 .map_err(|_| EventLoopError::EventLoopTerminated)
827 }
828
829 fn invoke_from_event_loop(
830 &self,
831 event: Box<dyn FnOnce() + Send>,
832 ) -> Result<(), EventLoopError> {
833 #[cfg(target_arch = "wasm32")]
843 self.0
844 .send_event(SlintEvent(CustomEvent::WakeEventLoopWorkaround))
845 .map_err(|_| EventLoopError::EventLoopTerminated)?;
846
847 self.0
848 .send_event(SlintEvent(CustomEvent::UserEvent(event)))
849 .map_err(|_| EventLoopError::EventLoopTerminated)
850 }
851 }
852 Some(Box::new(Proxy(self.shared_data.event_loop_proxy.clone())))
853 }
854
855 #[cfg(target_arch = "wasm32")]
856 fn set_clipboard_text(&self, text: &str, clipboard: i_slint_core::platform::Clipboard) {
857 crate::wasm_input_helper::set_clipboard_text(text.into(), clipboard);
858 }
859
860 #[cfg(not(target_arch = "wasm32"))]
861 fn set_clipboard_text(&self, text: &str, clipboard: i_slint_core::platform::Clipboard) {
862 let mut pair = self.shared_data.clipboard.borrow_mut();
863 if let Some(clipboard) = clipboard::select_clipboard(&mut pair, clipboard) {
864 clipboard.set_contents(text.into()).ok();
865 }
866 }
867
868 #[cfg(target_arch = "wasm32")]
869 fn clipboard_text(&self, clipboard: i_slint_core::platform::Clipboard) -> Option<String> {
870 crate::wasm_input_helper::get_clipboard_text(clipboard)
871 }
872
873 #[cfg(not(target_arch = "wasm32"))]
874 fn clipboard_text(&self, clipboard: i_slint_core::platform::Clipboard) -> Option<String> {
875 let mut pair = self.shared_data.clipboard.borrow_mut();
876 clipboard::select_clipboard(&mut pair, clipboard).and_then(|c| c.get_contents().ok())
877 }
878}
879
880mod private {
881 pub trait WinitWindowAccessorSealed {}
882}
883
884#[i_slint_core_macros::slint_doc]
885pub trait WinitWindowAccessor: private::WinitWindowAccessorSealed {
898 fn has_winit_window(&self) -> bool;
901 fn with_winit_window<T>(&self, callback: impl FnOnce(&winit::window::Window) -> T)
904 -> Option<T>;
905 fn on_winit_window_event(
913 &self,
914 callback: impl FnMut(&i_slint_core::api::Window, &winit::event::WindowEvent) -> EventResult
915 + 'static,
916 );
917
918 fn winit_window(
961 &self,
962 ) -> impl std::future::Future<Output = Result<Arc<winit::window::Window>, PlatformError>>;
963}
964
965impl WinitWindowAccessor for i_slint_core::api::Window {
966 fn has_winit_window(&self) -> bool {
967 i_slint_core::window::WindowInner::from_pub(self)
968 .window_adapter()
969 .internal(i_slint_core::InternalToken)
970 .and_then(|wa| (wa as &dyn core::any::Any).downcast_ref::<WinitWindowAdapter>())
971 .is_some_and(|adapter| adapter.winit_window().is_some())
972 }
973
974 fn with_winit_window<T>(
975 &self,
976 callback: impl FnOnce(&winit::window::Window) -> T,
977 ) -> Option<T> {
978 i_slint_core::window::WindowInner::from_pub(self)
979 .window_adapter()
980 .internal(i_slint_core::InternalToken)
981 .and_then(|wa| (wa as &dyn core::any::Any).downcast_ref::<WinitWindowAdapter>())
982 .and_then(|adapter| adapter.winit_window().map(|w| callback(&w)))
983 }
984
985 fn winit_window(
986 &self,
987 ) -> impl std::future::Future<Output = Result<Arc<winit::window::Window>, PlatformError>> {
988 Box::pin(async move {
989 let adapter_weak = i_slint_core::window::WindowInner::from_pub(self)
990 .window_adapter()
991 .internal(i_slint_core::InternalToken)
992 .and_then(|wa| (wa as &dyn core::any::Any).downcast_ref::<WinitWindowAdapter>())
993 .map(|wa| wa.self_weak.clone())
994 .ok_or_else(|| {
995 PlatformError::OtherError(
996 format!("Slint window is not backed by a Winit window adapter").into(),
997 )
998 })?;
999 WinitWindowAdapter::async_winit_window(adapter_weak).await
1000 })
1001 }
1002
1003 fn on_winit_window_event(
1004 &self,
1005 mut callback: impl FnMut(&i_slint_core::api::Window, &winit::event::WindowEvent) -> EventResult
1006 + 'static,
1007 ) {
1008 if let Some(adapter) = i_slint_core::window::WindowInner::from_pub(self)
1009 .window_adapter()
1010 .internal(i_slint_core::InternalToken)
1011 .and_then(|wa| (wa as &dyn core::any::Any).downcast_ref::<WinitWindowAdapter>())
1012 {
1013 adapter
1014 .window_event_filter
1015 .set(Some(Box::new(move |window, event| callback(window, event))));
1016 }
1017 }
1018}
1019
1020impl private::WinitWindowAccessorSealed for i_slint_core::api::Window {}
1021
1022#[cfg(test)]
1023mod testui {
1024 slint::slint! {
1025 export component App inherits Window {
1026 Text { text: "Ok"; }
1027 }
1028 }
1029}
1030
1031#[cfg(not(any(target_arch = "wasm32", target_vendor = "apple")))]
1033#[test]
1034fn test_window_accessor_and_rwh() {
1035 slint::platform::set_platform(Box::new(crate::Backend::new().unwrap())).unwrap();
1036
1037 use testui::*;
1038
1039 slint::spawn_local(async move {
1040 let app = App::new().unwrap();
1041 let slint_window = app.window();
1042
1043 assert!(!slint_window.has_winit_window());
1044
1045 app.show().unwrap();
1048
1049 let result = slint_window.winit_window().await;
1050 assert!(result.is_ok(), "Failed to get winit window: {:?}", result.err());
1051 assert!(slint_window.has_winit_window());
1052 let handle = slint_window.window_handle();
1053 use raw_window_handle::{HasDisplayHandle, HasWindowHandle};
1054 assert!(handle.window_handle().is_ok());
1055 assert!(handle.display_handle().is_ok());
1056 slint::quit_event_loop().unwrap();
1057 })
1058 .unwrap();
1059
1060 slint::run_event_loop().unwrap();
1061}