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
8extern crate alloc;
9
10use event_loop::{CustomEvent, EventLoopState};
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;
20use std::sync::Arc;
21use winit::event_loop::ActiveEventLoop;
22
23#[cfg(not(target_arch = "wasm32"))]
24mod clipboard;
25mod drag_resize_window;
26mod winitwindowadapter;
27use winitwindowadapter::*;
28pub(crate) mod event_loop;
29mod frame_throttle;
30
31pub use winit;
33
34#[non_exhaustive]
38#[derive(Debug)]
39pub struct SlintEvent(CustomEvent);
40
41#[i_slint_core_macros::slint_doc]
42pub type EventLoopBuilder = winit::event_loop::EventLoopBuilder<SlintEvent>;
47
48pub enum EventResult {
51 Propagate,
53 PreventDefault,
55}
56
57mod renderer {
58 use std::sync::Arc;
59
60 use i_slint_core::platform::PlatformError;
61 use winit::event_loop::ActiveEventLoop;
62
63 pub trait WinitCompatibleRenderer {
64 fn render(&self, window: &i_slint_core::api::Window) -> Result<(), PlatformError>;
65
66 fn as_core_renderer(&self) -> &dyn i_slint_core::renderer::Renderer;
67 fn occluded(&self, _: bool) {}
69
70 fn suspend(&self) -> Result<(), PlatformError>;
71
72 fn resume(
74 &self,
75 active_event_loop: &ActiveEventLoop,
76 window_attributes: winit::window::WindowAttributes,
77 ) -> Result<Arc<winit::window::Window>, PlatformError>;
78 }
79
80 #[cfg(any(
81 all(feature = "renderer-femtovg", supports_opengl),
82 feature = "renderer-femtovg-wgpu"
83 ))]
84 pub(crate) mod femtovg;
85 #[cfg(enable_skia_renderer)]
86 pub(crate) mod skia;
87
88 #[cfg(feature = "renderer-software")]
89 pub(crate) mod sw;
90}
91
92#[cfg(enable_accesskit)]
93mod accesskit;
94#[cfg(muda)]
95mod muda;
96#[cfg(not(use_winit_theme))]
97mod xdg_color_scheme;
98
99#[cfg(target_arch = "wasm32")]
100pub(crate) mod wasm_input_helper;
101
102cfg_if::cfg_if! {
103 if #[cfg(enable_femtovg_renderer)] {
104 const DEFAULT_RENDERER_NAME: &str = "FemtoVG";
105 } else if #[cfg(enable_skia_renderer)] {
106 const DEFAULT_RENDERER_NAME: &'static str = "Skia";
107 } else if #[cfg(feature = "renderer-software")] {
108 const DEFAULT_RENDERER_NAME: &'static str = "Software";
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
114fn default_renderer_factory(
115 shared_backend_data: &Rc<SharedBackendData>,
116) -> Result<Box<dyn WinitCompatibleRenderer>, PlatformError> {
117 cfg_if::cfg_if! {
118 if #[cfg(enable_skia_renderer)] {
119 renderer::skia::WinitSkiaRenderer::new_suspended(shared_backend_data)
120 } else if #[cfg(feature = "renderer-femtovg-wgpu")] {
121 renderer::femtovg::WGPUFemtoVGRenderer::new_suspended(shared_backend_data)
122 } else if #[cfg(all(feature = "renderer-femtovg", supports_opengl))] {
123 renderer::femtovg::GlutinFemtoVGRenderer::new_suspended(shared_backend_data)
124 } else if #[cfg(feature = "renderer-software")] {
125 renderer::sw::WinitSoftwareRenderer::new_suspended(shared_backend_data)
126 } else {
127 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`");
128 }
129 }
130}
131
132fn try_create_window_with_fallback_renderer(
133 shared_backend_data: &Rc<SharedBackendData>,
134 attrs: winit::window::WindowAttributes,
135 _proxy: &winit::event_loop::EventLoopProxy<SlintEvent>,
136 #[cfg(all(muda, target_os = "macos"))] muda_enable_default_menu_bar: bool,
137) -> Option<Rc<WinitWindowAdapter>> {
138 [
139 #[cfg(any(
140 feature = "renderer-skia",
141 feature = "renderer-skia-opengl",
142 feature = "renderer-skia-vulkan"
143 ))]
144 renderer::skia::WinitSkiaRenderer::new_suspended,
145 #[cfg(feature = "renderer-femtovg-wgpu")]
146 renderer::femtovg::WGPUFemtoVGRenderer::new_suspended,
147 #[cfg(all(
148 feature = "renderer-femtovg",
149 supports_opengl,
150 not(feature = "renderer-femtovg-wgpu")
151 ))]
152 renderer::femtovg::GlutinFemtoVGRenderer::new_suspended,
153 #[cfg(feature = "renderer-software")]
154 renderer::sw::WinitSoftwareRenderer::new_suspended,
155 ]
156 .into_iter()
157 .find_map(|renderer_factory| {
158 Some(WinitWindowAdapter::new(
159 shared_backend_data.clone(),
160 renderer_factory(&shared_backend_data).ok()?,
161 attrs.clone(),
162 #[cfg(any(enable_accesskit, muda))]
163 _proxy.clone(),
164 #[cfg(all(muda, target_os = "macos"))]
165 muda_enable_default_menu_bar,
166 ))
167 })
168}
169
170#[doc(hidden)]
171pub type NativeWidgets = ();
172#[doc(hidden)]
173pub type NativeGlobals = ();
174#[doc(hidden)]
175pub const HAS_NATIVE_STYLE: bool = false;
176#[doc(hidden)]
177pub mod native_widgets {}
178
179#[allow(unused_variables)]
187pub trait CustomApplicationHandler {
188 fn resumed(&mut self, _event_loop: &ActiveEventLoop) -> EventResult {
190 EventResult::Propagate
191 }
192
193 fn window_event(
195 &mut self,
196 event_loop: &ActiveEventLoop,
197 window_id: winit::window::WindowId,
198 winit_window: Option<&winit::window::Window>,
199 slint_window: Option<&i_slint_core::api::Window>,
200 event: &winit::event::WindowEvent,
201 ) -> EventResult {
202 EventResult::Propagate
203 }
204
205 fn new_events(
207 &mut self,
208 event_loop: &ActiveEventLoop,
209 cause: winit::event::StartCause,
210 ) -> EventResult {
211 EventResult::Propagate
212 }
213
214 fn device_event(
216 &mut self,
217 event_loop: &ActiveEventLoop,
218 device_id: winit::event::DeviceId,
219 event: winit::event::DeviceEvent,
220 ) -> EventResult {
221 EventResult::Propagate
222 }
223
224 fn about_to_wait(&mut self, event_loop: &ActiveEventLoop) -> EventResult {
226 EventResult::Propagate
227 }
228
229 fn suspended(&mut self, event_loop: &ActiveEventLoop) -> EventResult {
231 EventResult::Propagate
232 }
233
234 fn exiting(&mut self, event_loop: &ActiveEventLoop) -> EventResult {
236 EventResult::Propagate
237 }
238
239 fn memory_warning(&mut self, event_loop: &ActiveEventLoop) -> EventResult {
241 EventResult::Propagate
242 }
243}
244
245pub struct BackendBuilder {
249 allow_fallback: bool,
250 requested_graphics_api: Option<RequestedGraphicsAPI>,
251 window_attributes_hook:
252 Option<Box<dyn Fn(winit::window::WindowAttributes) -> winit::window::WindowAttributes>>,
253 renderer_name: Option<String>,
254 event_loop_builder: Option<EventLoopBuilder>,
255 #[cfg(all(muda, target_os = "macos"))]
256 muda_enable_default_menu_bar_bar: bool,
257 #[cfg(target_family = "wasm")]
258 spawn_event_loop: bool,
259 custom_application_handler: Option<Box<dyn CustomApplicationHandler>>,
260}
261
262impl BackendBuilder {
263 #[must_use]
265 pub fn request_graphics_api(mut self, graphics_api: RequestedGraphicsAPI) -> Self {
266 self.requested_graphics_api = Some(graphics_api);
267 self
268 }
269
270 #[must_use]
273 pub fn with_renderer_name(mut self, name: impl Into<String>) -> Self {
274 self.renderer_name = Some(name.into());
275 self
276 }
277
278 #[must_use]
292 pub fn with_window_attributes_hook(
293 mut self,
294 hook: impl Fn(winit::window::WindowAttributes) -> winit::window::WindowAttributes + 'static,
295 ) -> Self {
296 self.window_attributes_hook = Some(Box::new(hook));
297 self
298 }
299
300 #[must_use]
303 pub fn with_event_loop_builder(mut self, event_loop_builder: EventLoopBuilder) -> Self {
304 self.event_loop_builder = Some(event_loop_builder);
305 self
306 }
307
308 #[must_use]
314 #[cfg(all(muda, target_os = "macos"))]
315 pub fn with_default_menu_bar(mut self, enable: bool) -> Self {
316 self.muda_enable_default_menu_bar_bar = enable;
317 self
318 }
319
320 #[cfg(target_family = "wasm")]
321 pub fn with_spawn_event_loop(mut self, enable: bool) -> Self {
324 self.spawn_event_loop = enable;
325 self
326 }
327
328 #[must_use]
333 pub fn with_custom_application_handler(
334 mut self,
335 handler: Box<dyn CustomApplicationHandler + 'static>,
336 ) -> Self {
337 self.custom_application_handler = Some(handler);
338 self
339 }
340
341 pub fn build(self) -> Result<Backend, PlatformError> {
354 #[allow(unused_mut)]
355 let mut event_loop_builder =
356 self.event_loop_builder.unwrap_or_else(winit::event_loop::EventLoop::with_user_event);
357
358 #[cfg(all(feature = "muda", target_os = "macos"))]
361 winit::platform::macos::EventLoopBuilderExtMacOS::with_default_menu(
362 &mut event_loop_builder,
363 false,
364 );
365
366 let shared_data = Rc::new(SharedBackendData::new(
369 event_loop_builder,
370 self.requested_graphics_api.clone(),
371 )?);
372
373 let renderer_factory_fn = match (
374 self.renderer_name.as_deref(),
375 self.requested_graphics_api.as_ref(),
376 ) {
377 #[cfg(all(feature = "renderer-femtovg", supports_opengl))]
378 (Some("gl"), maybe_graphics_api) | (Some("femtovg"), maybe_graphics_api) => {
379 if let Some(api) = maybe_graphics_api {
381 i_slint_core::graphics::RequestedOpenGLVersion::try_from(api)?;
382 }
383 renderer::femtovg::GlutinFemtoVGRenderer::new_suspended
384 }
385 #[cfg(feature = "renderer-femtovg-wgpu")]
386 (Some("femtovg-wgpu"), maybe_graphics_api) => {
387 if !maybe_graphics_api.is_some_and(|_api| {
388 #[cfg(feature = "unstable-wgpu-26")]
389 if matches!(_api, RequestedGraphicsAPI::WGPU26(..)) {
390 return true;
391 }
392 false
393 }) {
394 return Err(
395 "The FemtoVG WGPU renderer only supports the WGPU26 graphics API selection"
396 .into(),
397 );
398 }
399 renderer::femtovg::WGPUFemtoVGRenderer::new_suspended
400 }
401 #[cfg(enable_skia_renderer)]
402 (Some("skia"), maybe_graphics_api) => {
403 renderer::skia::WinitSkiaRenderer::factory_for_graphics_api(maybe_graphics_api)?
404 }
405 #[cfg(all(enable_skia_renderer, supports_opengl))]
406 (Some("skia-opengl"), maybe_graphics_api @ _) => {
407 if let Some(api) = maybe_graphics_api {
409 i_slint_core::graphics::RequestedOpenGLVersion::try_from(api)?;
410 }
411 renderer::skia::WinitSkiaRenderer::new_opengl_suspended
412 }
413 #[cfg(all(enable_skia_renderer, feature = "unstable-wgpu-26"))]
414 (Some("skia-wgpu"), maybe_graphics_api @ _) => {
415 if !maybe_graphics_api
416 .map_or(true, |api| !matches!(api, RequestedGraphicsAPI::WGPU26(..)))
417 {
418 return Err(
419 format!("Skia with WGPU doesn't support non-WGPU graphics API").into()
420 );
421 }
422 renderer::skia::WinitSkiaRenderer::new_wgpu_26_suspended
423 }
424 #[cfg(all(enable_skia_renderer, not(target_os = "android")))]
425 (Some("skia-software"), None) => {
426 renderer::skia::WinitSkiaRenderer::new_software_suspended
427 }
428 #[cfg(feature = "renderer-software")]
429 (Some("sw"), None) | (Some("software"), None) => {
430 renderer::sw::WinitSoftwareRenderer::new_suspended
431 }
432 (None, None) => default_renderer_factory,
433 (Some(renderer_name), _) => {
434 if self.allow_fallback {
435 eprintln!(
436 "slint winit: unrecognized renderer {renderer_name}, falling back to {DEFAULT_RENDERER_NAME}"
437 );
438 default_renderer_factory
439 } else {
440 return Err(PlatformError::NoPlatform);
441 }
442 }
443 #[cfg(feature = "unstable-wgpu-26")]
444 (None, Some(RequestedGraphicsAPI::WGPU26(..))) => {
445 cfg_if::cfg_if! {
446 if #[cfg(enable_skia_renderer)] {
447 renderer::skia::WinitSkiaRenderer::new_wgpu_26_suspended
448 } else {
449 renderer::femtovg::WGPUFemtoVGRenderer::new_suspended
450 }
451 }
452 }
453 (None, Some(_requested_graphics_api)) => {
454 cfg_if::cfg_if! {
455 if #[cfg(enable_skia_renderer)] {
456 renderer::skia::WinitSkiaRenderer::factory_for_graphics_api(Some(_requested_graphics_api))?
457 } else if #[cfg(all(feature = "renderer-femtovg", supports_opengl))] {
458 i_slint_core::graphics::RequestedOpenGLVersion::try_from(_requested_graphics_api)?;
460 renderer::femtovg::GlutinFemtoVGRenderer::new_suspended
461 } else {
462 return Err(format!("Graphics API use requested by the compile-time enabled renderers don't support that").into())
463 }
464 }
465 }
466 };
467
468 Ok(Backend {
469 renderer_factory_fn,
470 event_loop_state: Default::default(),
471 window_attributes_hook: self.window_attributes_hook,
472 shared_data,
473 #[cfg(all(muda, target_os = "macos"))]
474 muda_enable_default_menu_bar_bar: self.muda_enable_default_menu_bar_bar,
475 #[cfg(target_family = "wasm")]
476 spawn_event_loop: self.spawn_event_loop,
477 custom_application_handler: self.custom_application_handler.into(),
478 })
479 }
480}
481
482pub(crate) struct SharedBackendData {
483 _requested_graphics_api: Option<RequestedGraphicsAPI>,
484 #[cfg(enable_skia_renderer)]
485 skia_context: i_slint_renderer_skia::SkiaSharedContext,
486 active_windows: RefCell<HashMap<winit::window::WindowId, Weak<WinitWindowAdapter>>>,
487 inactive_windows: RefCell<Vec<Weak<WinitWindowAdapter>>>,
490 #[cfg(not(target_arch = "wasm32"))]
491 clipboard: std::cell::RefCell<clipboard::ClipboardPair>,
492 not_running_event_loop: RefCell<Option<winit::event_loop::EventLoop<SlintEvent>>>,
493 event_loop_proxy: winit::event_loop::EventLoopProxy<SlintEvent>,
494 is_wayland: bool,
495}
496
497impl SharedBackendData {
498 fn new(
499 mut builder: EventLoopBuilder,
500 requested_graphics_api: Option<RequestedGraphicsAPI>,
501 ) -> Result<Self, PlatformError> {
502 #[cfg(not(target_arch = "wasm32"))]
503 use raw_window_handle::HasDisplayHandle;
504
505 #[cfg(all(unix, not(target_vendor = "apple")))]
506 {
507 #[cfg(feature = "wayland")]
508 {
509 use winit::platform::wayland::EventLoopBuilderExtWayland;
510 builder.with_any_thread(true);
511 }
512 #[cfg(feature = "x11")]
513 {
514 use winit::platform::x11::EventLoopBuilderExtX11;
515 builder.with_any_thread(true);
516
517 #[cfg(feature = "wayland")]
521 if std::fs::metadata("/proc/sys/fs/binfmt_misc/WSLInterop").is_ok()
522 || std::fs::metadata("/run/WSL").is_ok()
523 {
524 builder.with_x11();
525 }
526 }
527 }
528 #[cfg(target_family = "windows")]
529 {
530 use winit::platform::windows::EventLoopBuilderExtWindows;
531 builder.with_any_thread(true);
532 }
533
534 let event_loop =
535 builder.build().map_err(|e| format!("Error initializing winit event loop: {e}"))?;
536
537 cfg_if::cfg_if! {
538 if #[cfg(all(unix, not(target_vendor = "apple"), feature = "wayland"))] {
539 use winit::platform::wayland::EventLoopExtWayland;
540 let is_wayland = event_loop.is_wayland();
541 } else {
542 let is_wayland = false;
543 }
544 }
545
546 let event_loop_proxy = event_loop.create_proxy();
547 #[cfg(not(target_arch = "wasm32"))]
548 let clipboard = crate::clipboard::create_clipboard(
549 &event_loop
550 .display_handle()
551 .map_err(|display_err| PlatformError::OtherError(display_err.into()))?,
552 );
553 Ok(Self {
554 _requested_graphics_api: requested_graphics_api,
555 #[cfg(enable_skia_renderer)]
556 skia_context: i_slint_renderer_skia::SkiaSharedContext::default(),
557 active_windows: Default::default(),
558 inactive_windows: Default::default(),
559 #[cfg(not(target_arch = "wasm32"))]
560 clipboard: RefCell::new(clipboard),
561 not_running_event_loop: RefCell::new(Some(event_loop)),
562 event_loop_proxy,
563 is_wayland,
564 })
565 }
566
567 pub fn register_window(&self, id: winit::window::WindowId, window: Rc<WinitWindowAdapter>) {
568 self.active_windows.borrow_mut().insert(id, Rc::downgrade(&window));
569 }
570
571 pub fn register_inactive_window(&self, window: Rc<WinitWindowAdapter>) {
572 let window = Rc::downgrade(&window);
573 let mut inactive_windows = self.inactive_windows.borrow_mut();
574 if !inactive_windows.iter().any(|w| Weak::ptr_eq(w, &window)) {
575 inactive_windows.push(window);
576 }
577 }
578
579 pub fn unregister_window(&self, id: Option<winit::window::WindowId>) {
580 if let Some(id) = id {
581 self.active_windows.borrow_mut().remove(&id);
582 } else {
583 self.inactive_windows
585 .borrow_mut()
586 .retain(|inactive_weak_window| inactive_weak_window.strong_count() > 0)
587 }
588 }
589
590 pub fn create_inactive_windows(
591 &self,
592 event_loop: &winit::event_loop::ActiveEventLoop,
593 ) -> Result<(), PlatformError> {
594 let mut inactive_windows = self.inactive_windows.take();
595 let mut result = Ok(());
596 while let Some(window_weak) = inactive_windows.pop() {
597 if let Some(err) = window_weak.upgrade().and_then(|w| w.ensure_window(event_loop).err())
598 {
599 result = Err(err);
600 break;
601 }
602 }
603 self.inactive_windows.borrow_mut().extend(inactive_windows);
604 result
605 }
606
607 pub fn window_by_id(&self, id: winit::window::WindowId) -> Option<Rc<WinitWindowAdapter>> {
608 self.active_windows.borrow().get(&id).and_then(|weakref| weakref.upgrade())
609 }
610}
611
612#[i_slint_core_macros::slint_doc]
613pub struct Backend {
622 renderer_factory_fn:
623 fn(&Rc<SharedBackendData>) -> Result<Box<dyn WinitCompatibleRenderer>, PlatformError>,
624 event_loop_state: RefCell<Option<crate::event_loop::EventLoopState>>,
625 shared_data: Rc<SharedBackendData>,
626 custom_application_handler: RefCell<Option<Box<dyn crate::CustomApplicationHandler>>>,
627
628 pub window_attributes_hook:
642 Option<Box<dyn Fn(winit::window::WindowAttributes) -> winit::window::WindowAttributes>>,
643
644 #[cfg(all(muda, target_os = "macos"))]
645 muda_enable_default_menu_bar_bar: bool,
646
647 #[cfg(target_family = "wasm")]
648 spawn_event_loop: bool,
649}
650
651impl Backend {
652 #[i_slint_core_macros::slint_doc]
653 pub fn new() -> Result<Self, PlatformError> {
657 Self::builder().build()
658 }
659
660 #[i_slint_core_macros::slint_doc]
661 pub fn new_with_renderer_by_name(renderer_name: Option<&str>) -> Result<Self, PlatformError> {
667 let mut builder = Self::builder();
668 if let Some(name) = renderer_name {
669 builder = builder.with_renderer_name(name.to_string());
670 }
671 builder.build()
672 }
673
674 pub fn builder() -> BackendBuilder {
677 BackendBuilder {
678 allow_fallback: true,
679 requested_graphics_api: None,
680 window_attributes_hook: None,
681 renderer_name: None,
682 event_loop_builder: None,
683 #[cfg(all(muda, target_os = "macos"))]
684 muda_enable_default_menu_bar_bar: true,
685 #[cfg(target_family = "wasm")]
686 spawn_event_loop: false,
687 custom_application_handler: None,
688 }
689 }
690}
691
692impl i_slint_core::platform::Platform for Backend {
693 fn create_window_adapter(&self) -> Result<Rc<dyn WindowAdapter>, PlatformError> {
694 let mut attrs = WinitWindowAdapter::window_attributes()?;
695
696 if let Some(hook) = &self.window_attributes_hook {
697 attrs = hook(attrs);
698 }
699
700 let adapter = (self.renderer_factory_fn)(&self.shared_data).map_or_else(
701 |e| {
702 try_create_window_with_fallback_renderer(
703 &self.shared_data,
704 attrs.clone(),
705 &self.shared_data.event_loop_proxy.clone(),
706 #[cfg(all(muda, target_os = "macos"))]
707 self.muda_enable_default_menu_bar_bar,
708 )
709 .ok_or_else(|| format!("Winit backend failed to find a suitable renderer: {e}"))
710 },
711 |renderer| {
712 Ok(WinitWindowAdapter::new(
713 self.shared_data.clone(),
714 renderer,
715 attrs.clone(),
716 #[cfg(any(enable_accesskit, muda))]
717 self.shared_data.event_loop_proxy.clone(),
718 #[cfg(all(muda, target_os = "macos"))]
719 self.muda_enable_default_menu_bar_bar,
720 ))
721 },
722 )?;
723 Ok(adapter)
724 }
725
726 fn run_event_loop(&self) -> Result<(), PlatformError> {
727 let loop_state = self.event_loop_state.borrow_mut().take().unwrap_or_else(|| {
728 EventLoopState::new(self.shared_data.clone(), self.custom_application_handler.take())
729 });
730 #[cfg(target_family = "wasm")]
731 {
732 if self.spawn_event_loop {
733 return loop_state.spawn();
734 }
735 }
736 let new_state = loop_state.run()?;
737 *self.event_loop_state.borrow_mut() = Some(new_state);
738 Ok(())
739 }
740
741 #[cfg(all(not(target_arch = "wasm32"), not(ios_and_friends)))]
742 fn process_events(
743 &self,
744 timeout: core::time::Duration,
745 _: i_slint_core::InternalToken,
746 ) -> Result<core::ops::ControlFlow<()>, PlatformError> {
747 let loop_state = self.event_loop_state.borrow_mut().take().unwrap_or_else(|| {
748 EventLoopState::new(self.shared_data.clone(), self.custom_application_handler.take())
749 });
750 let (new_state, status) = loop_state.pump_events(Some(timeout))?;
751 *self.event_loop_state.borrow_mut() = Some(new_state);
752 match status {
753 winit::platform::pump_events::PumpStatus::Continue => {
754 Ok(core::ops::ControlFlow::Continue(()))
755 }
756 winit::platform::pump_events::PumpStatus::Exit(code) => {
757 if code == 0 {
758 Ok(core::ops::ControlFlow::Break(()))
759 } else {
760 Err(format!("Event loop exited with non-zero code {code}").into())
761 }
762 }
763 }
764 }
765
766 fn new_event_loop_proxy(&self) -> Option<Box<dyn EventLoopProxy>> {
767 struct Proxy(winit::event_loop::EventLoopProxy<SlintEvent>);
768 impl EventLoopProxy for Proxy {
769 fn quit_event_loop(&self) -> Result<(), EventLoopError> {
770 self.0
771 .send_event(SlintEvent(CustomEvent::Exit))
772 .map_err(|_| EventLoopError::EventLoopTerminated)
773 }
774
775 fn invoke_from_event_loop(
776 &self,
777 event: Box<dyn FnOnce() + Send>,
778 ) -> Result<(), EventLoopError> {
779 #[cfg(target_arch = "wasm32")]
789 self.0
790 .send_event(SlintEvent(CustomEvent::WakeEventLoopWorkaround))
791 .map_err(|_| EventLoopError::EventLoopTerminated)?;
792
793 self.0
794 .send_event(SlintEvent(CustomEvent::UserEvent(event)))
795 .map_err(|_| EventLoopError::EventLoopTerminated)
796 }
797 }
798 Some(Box::new(Proxy(self.shared_data.event_loop_proxy.clone())))
799 }
800
801 #[cfg(target_arch = "wasm32")]
802 fn set_clipboard_text(&self, text: &str, clipboard: i_slint_core::platform::Clipboard) {
803 crate::wasm_input_helper::set_clipboard_text(text.into(), clipboard);
804 }
805
806 #[cfg(not(target_arch = "wasm32"))]
807 fn set_clipboard_text(&self, text: &str, clipboard: i_slint_core::platform::Clipboard) {
808 let mut pair = self.shared_data.clipboard.borrow_mut();
809 if let Some(clipboard) = clipboard::select_clipboard(&mut pair, clipboard) {
810 clipboard.set_contents(text.into()).ok();
811 }
812 }
813
814 #[cfg(target_arch = "wasm32")]
815 fn clipboard_text(&self, clipboard: i_slint_core::platform::Clipboard) -> Option<String> {
816 crate::wasm_input_helper::get_clipboard_text(clipboard)
817 }
818
819 #[cfg(not(target_arch = "wasm32"))]
820 fn clipboard_text(&self, clipboard: i_slint_core::platform::Clipboard) -> Option<String> {
821 let mut pair = self.shared_data.clipboard.borrow_mut();
822 clipboard::select_clipboard(&mut pair, clipboard).and_then(|c| c.get_contents().ok())
823 }
824}
825
826mod private {
827 pub trait WinitWindowAccessorSealed {}
828}
829
830#[i_slint_core_macros::slint_doc]
831pub trait WinitWindowAccessor: private::WinitWindowAccessorSealed {
844 fn has_winit_window(&self) -> bool;
847 fn with_winit_window<T>(&self, callback: impl FnOnce(&winit::window::Window) -> T)
850 -> Option<T>;
851 fn on_winit_window_event(
859 &self,
860 callback: impl FnMut(&i_slint_core::api::Window, &winit::event::WindowEvent) -> EventResult
861 + 'static,
862 );
863
864 fn winit_window(
907 &self,
908 ) -> impl std::future::Future<Output = Result<Arc<winit::window::Window>, PlatformError>>;
909}
910
911impl WinitWindowAccessor for i_slint_core::api::Window {
912 fn has_winit_window(&self) -> bool {
913 i_slint_core::window::WindowInner::from_pub(self)
914 .window_adapter()
915 .internal(i_slint_core::InternalToken)
916 .and_then(|wa| wa.as_any().downcast_ref::<WinitWindowAdapter>())
917 .is_some_and(|adapter| adapter.winit_window().is_some())
918 }
919
920 fn with_winit_window<T>(
921 &self,
922 callback: impl FnOnce(&winit::window::Window) -> T,
923 ) -> Option<T> {
924 i_slint_core::window::WindowInner::from_pub(self)
925 .window_adapter()
926 .internal(i_slint_core::InternalToken)
927 .and_then(|wa| wa.as_any().downcast_ref::<WinitWindowAdapter>())
928 .and_then(|adapter| adapter.winit_window().map(|w| callback(&w)))
929 }
930
931 fn winit_window(
932 &self,
933 ) -> impl std::future::Future<Output = Result<Arc<winit::window::Window>, PlatformError>> {
934 Box::pin(async move {
935 let adapter_weak = i_slint_core::window::WindowInner::from_pub(self)
936 .window_adapter()
937 .internal(i_slint_core::InternalToken)
938 .and_then(|wa| wa.as_any().downcast_ref::<WinitWindowAdapter>())
939 .map(|wa| wa.self_weak.clone())
940 .ok_or_else(|| {
941 PlatformError::OtherError(
942 format!("Slint window is not backed by a Winit window adapter").into(),
943 )
944 })?;
945 WinitWindowAdapter::async_winit_window(adapter_weak).await
946 })
947 }
948
949 fn on_winit_window_event(
950 &self,
951 mut callback: impl FnMut(&i_slint_core::api::Window, &winit::event::WindowEvent) -> EventResult
952 + 'static,
953 ) {
954 if let Some(adapter) = i_slint_core::window::WindowInner::from_pub(self)
955 .window_adapter()
956 .internal(i_slint_core::InternalToken)
957 .and_then(|wa| wa.as_any().downcast_ref::<WinitWindowAdapter>())
958 {
959 adapter
960 .window_event_filter
961 .set(Some(Box::new(move |window, event| callback(window, event))));
962 }
963 }
964}
965
966impl private::WinitWindowAccessorSealed for i_slint_core::api::Window {}
967
968#[cfg(test)]
969mod testui {
970 slint::slint! {
971 export component App inherits Window {
972 Text { text: "Ok"; }
973 }
974 }
975}
976
977#[cfg(not(any(target_arch = "wasm32", target_vendor = "apple")))]
979#[test]
980fn test_window_accessor_and_rwh() {
981 slint::platform::set_platform(Box::new(crate::Backend::new().unwrap())).unwrap();
982
983 use testui::*;
984
985 slint::spawn_local(async move {
986 let app = App::new().unwrap();
987 let slint_window = app.window();
988
989 assert!(!slint_window.has_winit_window());
990
991 app.show().unwrap();
994
995 let result = slint_window.winit_window().await;
996 assert!(result.is_ok(), "Failed to get winit window: {:?}", result.err());
997 assert!(slint_window.has_winit_window());
998 let handle = slint_window.window_handle();
999 use raw_window_handle::{HasDisplayHandle, HasWindowHandle};
1000 assert!(handle.window_handle().is_ok());
1001 assert!(handle.display_handle().is_ok());
1002 slint::quit_event_loop().unwrap();
1003 })
1004 .unwrap();
1005
1006 slint::run_event_loop().unwrap();
1007}