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 std::sync::atomic::AtomicUsize;
24use winit::event_loop::ActiveEventLoop;
25
26#[cfg(not(target_arch = "wasm32"))]
27mod clipboard;
28mod drag_resize_window;
29mod winitwindowadapter;
30use winitwindowadapter::*;
31pub(crate) mod event_loop;
32mod frame_throttle;
33#[cfg(target_os = "ios")]
34mod ios;
35
36pub use winit;
38
39#[non_exhaustive]
43#[derive(Debug)]
44pub struct SlintEvent(CustomEvent);
45
46#[i_slint_core_macros::slint_doc]
47pub type EventLoopBuilder = winit::event_loop::EventLoopBuilder<SlintEvent>;
52
53pub enum EventResult {
56 Propagate,
58 PreventDefault,
60}
61
62mod renderer {
63 use std::sync::Arc;
64
65 use i_slint_core::platform::PlatformError;
66 use winit::event_loop::ActiveEventLoop;
67
68 pub trait WinitCompatibleRenderer: std::any::Any {
69 fn render(&self, window: &i_slint_core::api::Window) -> Result<(), PlatformError>;
70
71 fn as_core_renderer(&self) -> &dyn i_slint_core::renderer::Renderer;
72 fn occluded(&self, _: bool) {}
74
75 fn suspend(&self) -> Result<(), PlatformError>;
76
77 fn resume(
79 &self,
80 active_event_loop: &ActiveEventLoop,
81 window_attributes: winit::window::WindowAttributes,
82 ) -> Result<Arc<winit::window::Window>, PlatformError>;
83 }
84
85 #[cfg(enable_femtovg_renderer)]
86 pub(crate) mod femtovg;
87 #[cfg(enable_skia_renderer)]
88 pub(crate) mod skia;
89
90 #[cfg(feature = "renderer-software")]
91 pub(crate) mod sw;
92}
93
94#[cfg(enable_accesskit)]
95mod accesskit;
96#[cfg(muda)]
97mod muda;
98#[cfg(not(use_winit_theme))]
99mod xdg_color_scheme;
100
101#[cfg(target_arch = "wasm32")]
102pub(crate) mod wasm_input_helper;
103
104cfg_if::cfg_if! {
105 if #[cfg(enable_femtovg_renderer)] {
106 const DEFAULT_RENDERER_NAME: &str = "FemtoVG";
107 } else if #[cfg(enable_skia_renderer)] {
108 const DEFAULT_RENDERER_NAME: &str = "Skia";
109 } else if #[cfg(feature = "renderer-software")] {
110 const DEFAULT_RENDERER_NAME: &str = "Software";
111 } else {
112 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`");
113 }
114}
115
116fn default_renderer_factory(
117 shared_backend_data: &Rc<SharedBackendData>,
118) -> Result<Box<dyn WinitCompatibleRenderer>, PlatformError> {
119 cfg_if::cfg_if! {
120 if #[cfg(enable_skia_renderer)] {
121 renderer::skia::WinitSkiaRenderer::new_suspended(shared_backend_data)
122 } else if #[cfg(feature = "renderer-femtovg-wgpu")] {
123 renderer::femtovg::WGPUFemtoVGRenderer::new_suspended(shared_backend_data)
124 } else if #[cfg(all(feature = "renderer-femtovg", supports_opengl))] {
125 renderer::femtovg::GlutinFemtoVGRenderer::new_suspended(shared_backend_data)
126 } else if #[cfg(feature = "renderer-software")] {
127 renderer::sw::WinitSoftwareRenderer::new_suspended(shared_backend_data)
128 } else {
129 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`");
130 }
131 }
132}
133
134fn try_create_window_with_fallback_renderer(
135 shared_backend_data: &Rc<SharedBackendData>,
136 attrs: winit::window::WindowAttributes,
137 _proxy: &winit::event_loop::EventLoopProxy<SlintEvent>,
138 #[cfg(all(muda, target_os = "macos"))] muda_enable_default_menu_bar: bool,
139) -> Option<Rc<WinitWindowAdapter>> {
140 [
141 #[cfg(any(
142 feature = "renderer-skia",
143 feature = "renderer-skia-opengl",
144 feature = "renderer-skia-vulkan"
145 ))]
146 renderer::skia::WinitSkiaRenderer::new_suspended,
147 #[cfg(feature = "renderer-femtovg-wgpu")]
148 renderer::femtovg::WGPUFemtoVGRenderer::new_suspended,
149 #[cfg(all(
150 feature = "renderer-femtovg",
151 supports_opengl,
152 not(feature = "renderer-femtovg-wgpu")
153 ))]
154 renderer::femtovg::GlutinFemtoVGRenderer::new_suspended,
155 #[cfg(feature = "renderer-software")]
156 renderer::sw::WinitSoftwareRenderer::new_suspended,
157 ]
158 .into_iter()
159 .find_map(|renderer_factory| {
160 Some(WinitWindowAdapter::new(
161 shared_backend_data.clone(),
162 renderer_factory(shared_backend_data).ok()?,
163 attrs.clone(),
164 #[cfg(any(enable_accesskit, muda))]
165 _proxy.clone(),
166 #[cfg(all(muda, target_os = "macos"))]
167 muda_enable_default_menu_bar,
168 ))
169 })
170}
171
172#[doc(hidden)]
173pub type NativeWidgets = ();
174#[doc(hidden)]
175pub type NativeGlobals = ();
176#[doc(hidden)]
177pub const HAS_NATIVE_STYLE: bool = false;
178#[doc(hidden)]
179pub mod native_widgets {}
180
181#[allow(unused_variables)]
189pub trait CustomApplicationHandler {
190 fn resumed(&mut self, _event_loop: &ActiveEventLoop) -> EventResult {
192 EventResult::Propagate
193 }
194
195 fn window_event(
197 &mut self,
198 event_loop: &ActiveEventLoop,
199 window_id: winit::window::WindowId,
200 winit_window: Option<&winit::window::Window>,
201 slint_window: Option<&i_slint_core::api::Window>,
202 event: &winit::event::WindowEvent,
203 ) -> EventResult {
204 EventResult::Propagate
205 }
206
207 fn new_events(
209 &mut self,
210 event_loop: &ActiveEventLoop,
211 cause: winit::event::StartCause,
212 ) -> EventResult {
213 EventResult::Propagate
214 }
215
216 fn device_event(
218 &mut self,
219 event_loop: &ActiveEventLoop,
220 device_id: winit::event::DeviceId,
221 event: winit::event::DeviceEvent,
222 ) -> EventResult {
223 EventResult::Propagate
224 }
225
226 fn about_to_wait(&mut self, event_loop: &ActiveEventLoop) -> EventResult {
228 EventResult::Propagate
229 }
230
231 fn suspended(&mut self, event_loop: &ActiveEventLoop) -> EventResult {
233 EventResult::Propagate
234 }
235
236 fn exiting(&mut self, event_loop: &ActiveEventLoop) -> EventResult {
238 EventResult::Propagate
239 }
240
241 fn memory_warning(&mut self, event_loop: &ActiveEventLoop) -> EventResult {
243 EventResult::Propagate
244 }
245}
246
247pub struct BackendBuilder {
251 allow_fallback: bool,
252 requested_graphics_api: Option<RequestedGraphicsAPI>,
253 window_attributes_hook:
254 Option<Box<dyn Fn(winit::window::WindowAttributes) -> winit::window::WindowAttributes>>,
255 renderer_name: Option<String>,
256 event_loop_builder: Option<EventLoopBuilder>,
257 #[cfg(all(muda, target_os = "macos"))]
258 muda_enable_default_menu_bar_bar: bool,
259 #[cfg(target_family = "wasm")]
260 spawn_event_loop: bool,
261 custom_application_handler: Option<Box<dyn CustomApplicationHandler>>,
262}
263
264impl BackendBuilder {
265 #[must_use]
267 pub fn request_graphics_api(mut self, graphics_api: RequestedGraphicsAPI) -> Self {
268 self.requested_graphics_api = Some(graphics_api);
269 self
270 }
271
272 #[must_use]
275 pub fn with_renderer_name(mut self, name: impl Into<String>) -> Self {
276 self.renderer_name = Some(name.into());
277 self
278 }
279
280 #[must_use]
294 pub fn with_window_attributes_hook(
295 mut self,
296 hook: impl Fn(winit::window::WindowAttributes) -> winit::window::WindowAttributes + 'static,
297 ) -> Self {
298 self.window_attributes_hook = Some(Box::new(hook));
299 self
300 }
301
302 #[must_use]
305 pub fn with_event_loop_builder(mut self, event_loop_builder: EventLoopBuilder) -> Self {
306 self.event_loop_builder = Some(event_loop_builder);
307 self
308 }
309
310 #[must_use]
316 #[cfg(all(muda, target_os = "macos"))]
317 pub fn with_default_menu_bar(mut self, enable: bool) -> Self {
318 self.muda_enable_default_menu_bar_bar = enable;
319 self
320 }
321
322 #[cfg(target_family = "wasm")]
323 pub fn with_spawn_event_loop(mut self, enable: bool) -> Self {
326 self.spawn_event_loop = enable;
327 self
328 }
329
330 #[must_use]
335 pub fn with_custom_application_handler(
336 mut self,
337 handler: Box<dyn CustomApplicationHandler + 'static>,
338 ) -> Self {
339 self.custom_application_handler = Some(handler);
340 self
341 }
342
343 pub fn build(self) -> Result<Backend, PlatformError> {
356 #[allow(unused_mut)]
357 let mut event_loop_builder =
358 self.event_loop_builder.unwrap_or_else(winit::event_loop::EventLoop::with_user_event);
359
360 #[cfg(all(feature = "muda", target_os = "macos"))]
363 winit::platform::macos::EventLoopBuilderExtMacOS::with_default_menu(
364 &mut event_loop_builder,
365 false,
366 );
367
368 let shared_data = Rc::new(SharedBackendData::new(
371 event_loop_builder,
372 self.requested_graphics_api.clone(),
373 )?);
374
375 let renderer_factory_fn = match (
376 self.renderer_name.as_deref(),
377 self.requested_graphics_api.as_ref(),
378 ) {
379 #[cfg(all(feature = "renderer-femtovg", supports_opengl))]
380 (Some("gl"), maybe_graphics_api) | (Some("femtovg"), maybe_graphics_api) => {
381 if let Some(api) = maybe_graphics_api {
383 i_slint_core::graphics::RequestedOpenGLVersion::try_from(api)?;
384 }
385 renderer::femtovg::GlutinFemtoVGRenderer::new_suspended
386 }
387 #[cfg(feature = "renderer-femtovg-wgpu")]
388 (Some("femtovg-wgpu"), maybe_graphics_api) => {
389 if let Some(_api) = maybe_graphics_api {
390 #[cfg(feature = "unstable-wgpu-28")]
391 if !matches!(_api, RequestedGraphicsAPI::WGPU28(..)) {
392 return Err(
393 "The FemtoVG WGPU renderer only supports the WGPU28 graphics API selection"
394 .into(),
395 );
396 }
397 }
398 renderer::femtovg::WGPUFemtoVGRenderer::new_suspended
399 }
400 #[cfg(enable_skia_renderer)]
401 (Some("skia"), maybe_graphics_api) => {
402 renderer::skia::WinitSkiaRenderer::factory_for_graphics_api(maybe_graphics_api)?
403 }
404 #[cfg(all(enable_skia_renderer, supports_opengl))]
405 (Some("skia-opengl"), maybe_graphics_api) => {
406 if let Some(api) = maybe_graphics_api {
408 i_slint_core::graphics::RequestedOpenGLVersion::try_from(api)?;
409 }
410 renderer::skia::WinitSkiaRenderer::new_opengl_suspended
411 }
412 #[cfg(all(
413 enable_skia_renderer,
414 any(feature = "unstable-wgpu-27", feature = "unstable-wgpu-28")
415 ))]
416 (Some("skia-wgpu"), maybe_graphics_api) => {
417 if let Some(factory) = maybe_graphics_api.map_or_else(
418 || {
419 cfg_if::cfg_if!(
420 if #[cfg(feature = "unstable-wgpu-28")]
421 {
422 let result = Some(
423 renderer::skia::WinitSkiaRenderer::new_wgpu_28_suspended
424 as RendererFactoryFn,
425 );
426 } else {
427 let 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("Skia with WGPU doesn't support non-WGPU graphics API"
456 .to_string()
457 .into());
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 if #[cfg(feature = "renderer-femtovg-wgpu")] {
485 renderer::femtovg::WGPUFemtoVGRenderer::new_suspended
486 } else {
487 return Err("unstable-wgpu-28 was enabled but no renderer was selected. Please select either renderer-skia* or renderer-femtovg-wgpu".into())
488 }
489 }
490 }
491 #[cfg(all(enable_skia_renderer, feature = "unstable-wgpu-27"))]
492 (None, Some(RequestedGraphicsAPI::WGPU27(..))) => {
493 renderer::skia::WinitSkiaRenderer::new_wgpu_27_suspended
494 }
495 (None, Some(_requested_graphics_api)) => {
496 cfg_if::cfg_if! {
497 if #[cfg(enable_skia_renderer)] {
498 renderer::skia::WinitSkiaRenderer::factory_for_graphics_api(Some(_requested_graphics_api))?
499 } else if #[cfg(all(feature = "renderer-femtovg", supports_opengl))] {
500 i_slint_core::graphics::RequestedOpenGLVersion::try_from(_requested_graphics_api)?;
502 renderer::femtovg::GlutinFemtoVGRenderer::new_suspended
503 } else {
504 return Err(format!("Graphics API use requested by the compile-time enabled renderers don't support that").into())
505 }
506 }
507 }
508 };
509
510 Ok(Backend {
511 renderer_factory_fn,
512 event_loop_state: Default::default(),
513 window_attributes_hook: self.window_attributes_hook,
514 shared_data,
515 #[cfg(all(muda, target_os = "macos"))]
516 muda_enable_default_menu_bar_bar: self.muda_enable_default_menu_bar_bar,
517 #[cfg(target_family = "wasm")]
518 spawn_event_loop: self.spawn_event_loop,
519 custom_application_handler: self.custom_application_handler.into(),
520 })
521 }
522}
523
524pub(crate) struct SharedBackendData {
525 _requested_graphics_api: Option<RequestedGraphicsAPI>,
526 #[cfg(enable_skia_renderer)]
527 skia_context: i_slint_renderer_skia::SkiaSharedContext,
528 active_windows: Rc<RefCell<HashMap<winit::window::WindowId, Weak<WinitWindowAdapter>>>>,
529 inactive_windows: RefCell<Vec<Weak<WinitWindowAdapter>>>,
532 #[cfg(not(target_arch = "wasm32"))]
533 clipboard: std::cell::RefCell<clipboard::ClipboardPair>,
534 not_running_event_loop: RefCell<Option<winit::event_loop::EventLoop<SlintEvent>>>,
535 event_loop_proxy: winit::event_loop::EventLoopProxy<SlintEvent>,
536 event_loop_generation: Arc<AtomicUsize>,
539 is_wayland: bool,
540 #[cfg(target_os = "ios")]
541 #[allow(unused)]
542 keyboard_notifications: ios::KeyboardNotifications,
543}
544
545impl SharedBackendData {
546 fn new(
547 mut builder: EventLoopBuilder,
548 requested_graphics_api: Option<RequestedGraphicsAPI>,
549 ) -> Result<Self, PlatformError> {
550 #[cfg(not(target_arch = "wasm32"))]
551 use raw_window_handle::HasDisplayHandle;
552
553 #[cfg(all(unix, not(target_vendor = "apple")))]
554 {
555 #[cfg(feature = "wayland")]
556 {
557 use winit::platform::wayland::EventLoopBuilderExtWayland;
558 builder.with_any_thread(true);
559 }
560 #[cfg(feature = "x11")]
561 {
562 use winit::platform::x11::EventLoopBuilderExtX11;
563 builder.with_any_thread(true);
564
565 #[cfg(feature = "wayland")]
569 if std::fs::metadata("/proc/sys/fs/binfmt_misc/WSLInterop").is_ok()
570 || std::fs::metadata("/run/WSL").is_ok()
571 {
572 builder.with_x11();
573 }
574 }
575 }
576 #[cfg(target_family = "windows")]
577 {
578 use winit::platform::windows::EventLoopBuilderExtWindows;
579 builder.with_any_thread(true);
580 }
581
582 let event_loop =
583 builder.build().map_err(|e| format!("Error initializing winit event loop: {e}"))?;
584
585 #[cfg(target_os = "macos")]
586 Self::disable_macos_automatic_shortcut_localization();
587
588 cfg_if::cfg_if! {
589 if #[cfg(all(unix, not(target_vendor = "apple"), feature = "wayland"))] {
590 use winit::platform::wayland::EventLoopExtWayland;
591 let is_wayland = event_loop.is_wayland();
592 } else {
593 let is_wayland = false;
594 }
595 }
596
597 let active_windows =
598 Rc::<RefCell<HashMap<winit::window::WindowId, Weak<WinitWindowAdapter>>>>::default();
599
600 #[cfg(target_os = "ios")]
601 let keyboard_notifications =
602 ios::register_keyboard_notifications(Rc::downgrade(&active_windows));
603
604 let event_loop_proxy = event_loop.create_proxy();
605 #[cfg(not(target_arch = "wasm32"))]
606 let clipboard = crate::clipboard::create_clipboard(
607 &event_loop
608 .display_handle()
609 .map_err(|display_err| PlatformError::OtherError(display_err.into()))?,
610 );
611 Ok(Self {
612 _requested_graphics_api: requested_graphics_api,
613 #[cfg(enable_skia_renderer)]
614 skia_context: i_slint_renderer_skia::SkiaSharedContext::default(),
615 active_windows,
616 inactive_windows: Default::default(),
617 #[cfg(not(target_arch = "wasm32"))]
618 clipboard: RefCell::new(clipboard),
619 not_running_event_loop: RefCell::new(Some(event_loop)),
620 event_loop_proxy,
621 event_loop_generation: Default::default(),
622 is_wayland,
623 #[cfg(target_os = "ios")]
624 keyboard_notifications,
625 })
626 }
627
628 #[cfg(target_os = "macos")]
636 fn disable_macos_automatic_shortcut_localization() {
637 use objc2::runtime::{AnyClass, AnyObject, Bool, Imp, Sel};
638 use objc2::sel;
639
640 unsafe extern "C-unwind" fn should_not_localize(
641 _this: *mut AnyObject,
642 _cmd: Sel,
643 _app: *mut AnyObject,
644 ) -> Bool {
645 Bool::NO
646 }
647
648 let sel = sel!(applicationShouldAutomaticallyLocalizeKeyEquivalents:);
649 if let Some(cls) = AnyClass::get(c"WinitApplicationDelegate")
650 && cls.instance_method(sel).is_none()
651 {
652 unsafe {
653 objc2::ffi::class_addMethod(
654 (cls as *const AnyClass).cast_mut(),
655 sel,
656 core::mem::transmute::<
657 unsafe extern "C-unwind" fn(*mut AnyObject, Sel, *mut AnyObject) -> Bool,
658 Imp,
659 >(should_not_localize),
660 c"B@:@".as_ptr(),
661 );
662 }
663 }
664 }
665
666 pub fn register_window(&self, id: winit::window::WindowId, window: Rc<WinitWindowAdapter>) {
667 self.active_windows.borrow_mut().insert(id, Rc::downgrade(&window));
668 }
669
670 pub fn register_inactive_window(&self, window: Rc<WinitWindowAdapter>) {
671 let window = Rc::downgrade(&window);
672 let mut inactive_windows = self.inactive_windows.borrow_mut();
673 if !inactive_windows.iter().any(|w| Weak::ptr_eq(w, &window)) {
674 inactive_windows.push(window);
675 }
676 }
677
678 pub fn unregister_window(&self, id: Option<winit::window::WindowId>) {
679 if let Some(id) = id {
680 self.active_windows.borrow_mut().remove(&id);
681 } else {
682 self.inactive_windows
684 .borrow_mut()
685 .retain(|inactive_weak_window| inactive_weak_window.strong_count() > 0)
686 }
687 }
688
689 pub fn create_inactive_windows(
690 &self,
691 event_loop: &winit::event_loop::ActiveEventLoop,
692 ) -> Result<(), PlatformError> {
693 let mut inactive_windows = self.inactive_windows.take();
694 let mut result = Ok(());
695 while let Some(window_weak) = inactive_windows.pop() {
696 if let Some(err) = window_weak.upgrade().and_then(|w| w.ensure_window(event_loop).err())
697 {
698 result = Err(err);
699 break;
700 }
701 }
702 self.inactive_windows.borrow_mut().extend(inactive_windows);
703 result
704 }
705
706 pub fn window_by_id(&self, id: winit::window::WindowId) -> Option<Rc<WinitWindowAdapter>> {
707 self.active_windows.borrow().get(&id).and_then(|weakref| weakref.upgrade())
708 }
709}
710
711type RendererFactoryFn =
712 fn(&Rc<SharedBackendData>) -> Result<Box<dyn WinitCompatibleRenderer>, PlatformError>;
713
714#[i_slint_core_macros::slint_doc]
715pub struct Backend {
724 renderer_factory_fn: RendererFactoryFn,
725 event_loop_state: RefCell<Option<crate::event_loop::EventLoopState>>,
726 shared_data: Rc<SharedBackendData>,
727 custom_application_handler: RefCell<Option<Box<dyn crate::CustomApplicationHandler>>>,
728
729 pub window_attributes_hook:
743 Option<Box<dyn Fn(winit::window::WindowAttributes) -> winit::window::WindowAttributes>>,
744
745 #[cfg(all(muda, target_os = "macos"))]
746 muda_enable_default_menu_bar_bar: bool,
747
748 #[cfg(target_family = "wasm")]
749 spawn_event_loop: bool,
750}
751
752impl Backend {
753 #[i_slint_core_macros::slint_doc]
754 pub fn new() -> Result<Self, PlatformError> {
758 Self::builder().build()
759 }
760
761 #[i_slint_core_macros::slint_doc]
762 pub fn new_with_renderer_by_name(renderer_name: Option<&str>) -> Result<Self, PlatformError> {
768 let mut builder = Self::builder();
769 if let Some(name) = renderer_name {
770 builder = builder.with_renderer_name(name.to_string());
771 }
772 builder.build()
773 }
774
775 pub fn builder() -> BackendBuilder {
778 BackendBuilder {
779 allow_fallback: true,
780 requested_graphics_api: None,
781 window_attributes_hook: None,
782 renderer_name: None,
783 event_loop_builder: None,
784 #[cfg(all(muda, target_os = "macos"))]
785 muda_enable_default_menu_bar_bar: true,
786 #[cfg(target_family = "wasm")]
787 spawn_event_loop: false,
788 custom_application_handler: None,
789 }
790 }
791}
792
793impl i_slint_core::platform::Platform for Backend {
794 fn create_window_adapter(&self) -> Result<Rc<dyn WindowAdapter>, PlatformError> {
795 let mut attrs = WinitWindowAdapter::window_attributes()?;
796
797 if let Some(hook) = &self.window_attributes_hook {
798 attrs = hook(attrs);
799 }
800
801 let adapter = (self.renderer_factory_fn)(&self.shared_data).map_or_else(
802 |e| {
803 try_create_window_with_fallback_renderer(
804 &self.shared_data,
805 attrs.clone(),
806 &self.shared_data.event_loop_proxy.clone(),
807 #[cfg(all(muda, target_os = "macos"))]
808 self.muda_enable_default_menu_bar_bar,
809 )
810 .ok_or_else(|| format!("Winit backend failed to find a suitable renderer: {e}"))
811 },
812 |renderer| {
813 Ok(WinitWindowAdapter::new(
814 self.shared_data.clone(),
815 renderer,
816 attrs.clone(),
817 #[cfg(any(enable_accesskit, muda))]
818 self.shared_data.event_loop_proxy.clone(),
819 #[cfg(all(muda, target_os = "macos"))]
820 self.muda_enable_default_menu_bar_bar,
821 ))
822 },
823 )?;
824 Ok(adapter)
825 }
826
827 fn run_event_loop(&self) -> Result<(), PlatformError> {
828 let loop_state = self.event_loop_state.borrow_mut().take().unwrap_or_else(|| {
829 EventLoopState::new(self.shared_data.clone(), self.custom_application_handler.take())
830 });
831 #[cfg(target_family = "wasm")]
832 {
833 if self.spawn_event_loop {
834 return loop_state.spawn();
835 }
836 }
837 self.shared_data.event_loop_generation.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
839 let new_state = loop_state.run()?;
840 *self.event_loop_state.borrow_mut() = Some(new_state);
841 Ok(())
842 }
843
844 #[cfg(all(not(target_arch = "wasm32"), not(ios_and_friends)))]
845 fn process_events(
846 &self,
847 timeout: core::time::Duration,
848 _: i_slint_core::InternalToken,
849 ) -> Result<core::ops::ControlFlow<()>, PlatformError> {
850 let loop_state = self.event_loop_state.borrow_mut().take().unwrap_or_else(|| {
851 EventLoopState::new(self.shared_data.clone(), self.custom_application_handler.take())
852 });
853 let (new_state, status) = loop_state.pump_events(Some(timeout))?;
854 *self.event_loop_state.borrow_mut() = Some(new_state);
855 match status {
856 winit::platform::pump_events::PumpStatus::Continue => {
857 Ok(core::ops::ControlFlow::Continue(()))
858 }
859 winit::platform::pump_events::PumpStatus::Exit(code) => {
860 if code == 0 {
861 Ok(core::ops::ControlFlow::Break(()))
862 } else {
863 Err(format!("Event loop exited with non-zero code {code}").into())
864 }
865 }
866 }
867 }
868
869 fn new_event_loop_proxy(&self) -> Option<Box<dyn EventLoopProxy>> {
870 struct Proxy(winit::event_loop::EventLoopProxy<SlintEvent>, Arc<AtomicUsize>);
871 impl EventLoopProxy for Proxy {
872 fn quit_event_loop(&self) -> Result<(), EventLoopError> {
873 let generation = self.1.load(std::sync::atomic::Ordering::Relaxed);
874 self.0
875 .send_event(SlintEvent(CustomEvent::Exit(generation)))
876 .map_err(|_| EventLoopError::EventLoopTerminated)
877 }
878
879 fn invoke_from_event_loop(
880 &self,
881 event: Box<dyn FnOnce() + Send>,
882 ) -> Result<(), EventLoopError> {
883 #[cfg(target_arch = "wasm32")]
893 self.0
894 .send_event(SlintEvent(CustomEvent::WakeEventLoopWorkaround))
895 .map_err(|_| EventLoopError::EventLoopTerminated)?;
896
897 self.0
898 .send_event(SlintEvent(CustomEvent::UserEvent(event)))
899 .map_err(|_| EventLoopError::EventLoopTerminated)
900 }
901 }
902 Some(Box::new(Proxy(
903 self.shared_data.event_loop_proxy.clone(),
904 Arc::clone(&self.shared_data.event_loop_generation),
905 )))
906 }
907
908 #[cfg(target_arch = "wasm32")]
909 fn set_clipboard_text(&self, text: &str, clipboard: i_slint_core::platform::Clipboard) {
910 crate::wasm_input_helper::set_clipboard_text(text.into(), clipboard);
911 }
912
913 #[cfg(not(target_arch = "wasm32"))]
914 fn set_clipboard_text(&self, text: &str, clipboard: i_slint_core::platform::Clipboard) {
915 let mut pair = self.shared_data.clipboard.borrow_mut();
916 if let Some(clipboard) = clipboard::select_clipboard(&mut pair, clipboard) {
917 clipboard.set_contents(text.into()).ok();
918 }
919 }
920
921 #[cfg(target_arch = "wasm32")]
922 fn clipboard_text(&self, clipboard: i_slint_core::platform::Clipboard) -> Option<String> {
923 crate::wasm_input_helper::get_clipboard_text(clipboard)
924 }
925
926 #[cfg(not(target_arch = "wasm32"))]
927 fn clipboard_text(&self, clipboard: i_slint_core::platform::Clipboard) -> Option<String> {
928 let mut pair = self.shared_data.clipboard.borrow_mut();
929 clipboard::select_clipboard(&mut pair, clipboard).and_then(|c| c.get_contents().ok())
930 }
931
932 fn open_url(&self, url: &str) -> Result<(), i_slint_core::platform::PlatformError> {
933 webbrowser::open(url).map_err(|e| {
934 i_slint_core::platform::PlatformError::Other(format!("Failed to open URL: {e}"))
935 })
936 }
937}
938
939mod private {
940 pub trait WinitWindowAccessorSealed {}
941}
942
943#[i_slint_core_macros::slint_doc]
944pub trait WinitWindowAccessor: private::WinitWindowAccessorSealed {
957 fn has_winit_window(&self) -> bool;
960 fn with_winit_window<T>(&self, callback: impl FnOnce(&winit::window::Window) -> T)
963 -> Option<T>;
964 fn on_winit_window_event(
972 &self,
973 callback: impl FnMut(&i_slint_core::api::Window, &winit::event::WindowEvent) -> EventResult
974 + 'static,
975 );
976
977 fn winit_window(
1020 &self,
1021 ) -> impl std::future::Future<Output = Result<Arc<winit::window::Window>, PlatformError>>;
1022}
1023
1024impl WinitWindowAccessor for i_slint_core::api::Window {
1025 fn has_winit_window(&self) -> bool {
1026 i_slint_core::window::WindowInner::from_pub(self)
1027 .window_adapter()
1028 .internal(i_slint_core::InternalToken)
1029 .and_then(|wa| (wa as &dyn core::any::Any).downcast_ref::<WinitWindowAdapter>())
1030 .is_some_and(|adapter| adapter.winit_window().is_some())
1031 }
1032
1033 fn with_winit_window<T>(
1034 &self,
1035 callback: impl FnOnce(&winit::window::Window) -> T,
1036 ) -> Option<T> {
1037 i_slint_core::window::WindowInner::from_pub(self)
1038 .window_adapter()
1039 .internal(i_slint_core::InternalToken)
1040 .and_then(|wa| (wa as &dyn core::any::Any).downcast_ref::<WinitWindowAdapter>())
1041 .and_then(|adapter| adapter.winit_window().map(|w| callback(&w)))
1042 }
1043
1044 fn winit_window(
1045 &self,
1046 ) -> impl std::future::Future<Output = Result<Arc<winit::window::Window>, PlatformError>> {
1047 Box::pin(async move {
1048 let adapter_weak = i_slint_core::window::WindowInner::from_pub(self)
1049 .window_adapter()
1050 .internal(i_slint_core::InternalToken)
1051 .and_then(|wa| (wa as &dyn core::any::Any).downcast_ref::<WinitWindowAdapter>())
1052 .map(|wa| wa.self_weak.clone())
1053 .ok_or_else(|| {
1054 PlatformError::OtherError(
1055 "Slint window is not backed by a Winit window adapter".to_string().into(),
1056 )
1057 })?;
1058 WinitWindowAdapter::async_winit_window(adapter_weak).await
1059 })
1060 }
1061
1062 fn on_winit_window_event(
1063 &self,
1064 mut callback: impl FnMut(&i_slint_core::api::Window, &winit::event::WindowEvent) -> EventResult
1065 + 'static,
1066 ) {
1067 if let Some(adapter) = i_slint_core::window::WindowInner::from_pub(self)
1068 .window_adapter()
1069 .internal(i_slint_core::InternalToken)
1070 .and_then(|wa| (wa as &dyn core::any::Any).downcast_ref::<WinitWindowAdapter>())
1071 {
1072 adapter
1073 .window_event_filter
1074 .set(Some(Box::new(move |window, event| callback(window, event))));
1075 }
1076 }
1077}
1078
1079impl private::WinitWindowAccessorSealed for i_slint_core::api::Window {}
1080
1081#[cfg(test)]
1082mod testui {
1083 slint::slint! {
1084 export component App inherits Window {
1085 Text { text: "Ok"; }
1086 }
1087 }
1088}
1089
1090#[cfg(not(any(target_arch = "wasm32", target_vendor = "apple")))]
1092#[test]
1093fn test_window_accessor_and_rwh() {
1094 slint::platform::set_platform(Box::new(crate::Backend::new().unwrap())).unwrap();
1095
1096 use testui::*;
1097
1098 slint::spawn_local(async move {
1099 let app = App::new().unwrap();
1100 let slint_window = app.window();
1101
1102 assert!(!slint_window.has_winit_window());
1103
1104 app.show().unwrap();
1107
1108 let result = slint_window.winit_window().await;
1109 assert!(result.is_ok(), "Failed to get winit window: {:?}", result.err());
1110 assert!(slint_window.has_winit_window());
1111 let handle = slint_window.window_handle();
1112 use raw_window_handle::{HasDisplayHandle, HasWindowHandle};
1113 assert!(handle.window_handle().is_ok());
1114 assert!(handle.display_handle().is_ok());
1115 slint::quit_event_loop().unwrap();
1116 })
1117 .unwrap();
1118
1119 slint::run_event_loop().unwrap();
1120}