Skip to main content

i_slint_backend_winit/
lib.rs

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#![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
35/// Re-export of the winit crate.
36pub use winit;
37
38/// Internal type used by the winit backend for thread communication and window system updates.
39///
40/// See also [`EventLoopBuilder`]
41#[non_exhaustive]
42#[derive(Debug)]
43pub struct SlintEvent(CustomEvent);
44
45#[i_slint_core_macros::slint_doc]
46/// Convenience alias for the event loop builder used by Slint.
47///
48/// It can be used to configure the event loop with
49/// [`slint::BackendSelector::with_winit_event_loop_builder()`](slint:rust:slint/struct.BackendSelector.html#method.with_winit_event_loop_builder)
50pub type EventLoopBuilder = winit::event_loop::EventLoopBuilder<SlintEvent>;
51
52/// Returned by callbacks passed to [`Window::on_winit_window_event`](WinitWindowAccessor::on_winit_window_event)
53/// to determine if winit events should propagate to the Slint event loop.
54pub enum EventResult {
55    /// The winit event should propagate normally.
56    Propagate,
57    /// The winit event shouldn't be processed further.
58    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        // Got WindowEvent::Occluded
72        fn occluded(&self, _: bool) {}
73
74        fn suspend(&self) -> Result<(), PlatformError>;
75
76        // Got winit::Event::Resumed
77        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/// Use this trait to intercept events from winit.
181///
182/// It imitates [`winit::application::ApplicationHandler`] with two changes:
183///   - All functions are invoked before Slint sees them. Use the [`EventResult`] return value to
184///     optionally prevent Slint from seeing the event.
185///   - The [`Self::window_event()`] function has additional parameters to provide access to the Slint Window and
186///     Winit window, if applicable.
187#[allow(unused_variables)]
188pub trait CustomApplicationHandler {
189    /// Re-implement to intercept the [`ApplicationHandler::resumed()`](winit::application::ApplicationHandler::resumed()) event.
190    fn resumed(&mut self, _event_loop: &ActiveEventLoop) -> EventResult {
191        EventResult::Propagate
192    }
193
194    /// Re-implement to intercept the [`ApplicationHandler::window_event()`](winit::application::ApplicationHandler::window_event()) event.
195    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    /// Re-implement to intercept the [`ApplicationHandler::new_events()`](winit::application::ApplicationHandler::new_events()) event.
207    fn new_events(
208        &mut self,
209        event_loop: &ActiveEventLoop,
210        cause: winit::event::StartCause,
211    ) -> EventResult {
212        EventResult::Propagate
213    }
214
215    /// Re-implement to intercept the [`ApplicationHandler::device_event()`](winit::application::ApplicationHandler::device_event()) event.
216    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    /// Re-implement to intercept the [`ApplicationHandler::about_to_wait()`](winit::application::ApplicationHandler::about_to_wait()) event.
226    fn about_to_wait(&mut self, event_loop: &ActiveEventLoop) -> EventResult {
227        EventResult::Propagate
228    }
229
230    /// Re-implement to intercept the [`ApplicationHandler::suspended()`](winit::application::ApplicationHandler::suspended()) event.
231    fn suspended(&mut self, event_loop: &ActiveEventLoop) -> EventResult {
232        EventResult::Propagate
233    }
234
235    /// Re-implement to intercept the [`ApplicationHandler::exiting()`](winit::application::ApplicationHandler::exiting()) event.
236    fn exiting(&mut self, event_loop: &ActiveEventLoop) -> EventResult {
237        EventResult::Propagate
238    }
239
240    /// Re-implement to intercept the [`ApplicationHandler::memory_warning()`](winit::application::ApplicationHandler::memory_warning()) event.
241    fn memory_warning(&mut self, event_loop: &ActiveEventLoop) -> EventResult {
242        EventResult::Propagate
243    }
244}
245
246/// Use the BackendBuilder to configure the properties of the Winit Backend before creating it.
247/// Create the builder using [`Backend::builder()`], then configure it for example with [`Self::with_renderer_name`],
248/// and build the backend using [`Self::build`].
249pub 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    /// Configures this builder to require a renderer that supports the specified graphics API.
265    #[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    /// Configures this builder to use the specified renderer name when building the backend later.
272    /// Pass `renderer-software` for example to configure the backend to use the Slint software renderer.
273    #[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    /// Configures this builder to use the specified hook that will be called before a Window is created.
280    ///
281    /// It can be used to adjust settings of window that will be created.
282    ///
283    /// # Example
284    ///
285    /// ```rust,no_run
286    /// let mut backend = i_slint_backend_winit::Backend::builder()
287    ///     .with_window_attributes_hook(|attributes| attributes.with_content_protected(true))
288    ///     .build()
289    ///     .unwrap();
290    /// slint::platform::set_platform(Box::new(backend));
291    /// ```
292    #[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    /// Configures this builder to use the specified event loop builder when creating the event
302    /// loop during a subsequent call to [`Self::build`].
303    #[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    /// Configures this builder to enable or disable the default menu bar.
310    /// By default, the menu bar is provided by Slint. Set this to false
311    /// if you're providing your own menu bar.
312    /// Note that an application provided menu bar will be overriden by a `MenuBar`
313    /// declared in Slint code.
314    #[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    /// Configures this builder to spawn the event loop using [`winit::platform::web::EventLoopExtWebSys::spawn()`]
323    /// run `run_event_loop()` is called.
324    pub fn with_spawn_event_loop(mut self, enable: bool) -> Self {
325        self.spawn_event_loop = enable;
326        self
327    }
328
329    /// Configures this builder to use the specified [`CustomApplicationHandler`].
330    ///
331    /// This allow application developer to intercept events from winit.
332    /// Similar to [`winit::application::ApplicationHandler`].
333    #[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    /// Builds the backend with the parameters configured previously. Set the resulting backend
343    /// with `slint::platform::set_platform()`:
344    ///
345    /// # Example
346    ///
347    /// ```rust,no_run
348    /// let mut backend = i_slint_backend_winit::Backend::builder()
349    ///     .with_renderer_name("renderer-software")
350    ///     .build()
351    ///     .unwrap();
352    /// slint::platform::set_platform(Box::new(backend));
353    /// ```
354    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        // Never use winit's menu bar. Either we provide one ourselves with muda, or
360        // the user provides one.
361        #[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        // Initialize the winit event loop and propagate errors if for example `DISPLAY` or `WAYLAND_DISPLAY` isn't set.
368
369        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 a graphics API was requested, double check that it's GL. FemtoVG doesn't support Metal, etc.
381                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 a graphics API was requested, double check that it's GL.
406                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                        // If a graphics API was requested, double check that it's GL. FemtoVG doesn't support Metal, etc.
499                        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    /// List of visible windows that have been created when without the event loop and
528    /// need to be mapped to a winit Window as soon as the event loop becomes active.
529    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                // Under WSL, the compositor sometimes crashes. Since we cannot reconnect after the compositor
561                // was restarted, the application panics. This does not happen when using XWayland. Therefore,
562                // when running under WSL, try to connect to X11 instead.
563                #[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            // Use this opportunity of a Window being removed to tidy up.
636            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]
668/// This struct implements the Slint Platform trait.
669/// Use this in conjunction with [`slint::platform::set_platform`](slint:rust:slint/platform/fn.set_platform.html) to initialize.
670/// Slint to use winit for all windowing system interaction.
671///
672/// ```rust,no_run
673/// use i_slint_backend_winit::Backend;
674/// slint::platform::set_platform(Box::new(Backend::new().unwrap()));
675/// ```
676pub 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    /// This hook is called before a Window is created.
683    ///
684    /// It can be used to adjust settings of window that will be created
685    ///
686    /// See also [`BackendBuilder::with_window_attributes_hook`].
687    ///
688    /// # Example
689    ///
690    /// ```rust,no_run
691    /// let mut backend = i_slint_backend_winit::Backend::new().unwrap();
692    /// backend.window_attributes_hook = Some(Box::new(|attributes| attributes.with_content_protected(true)));
693    /// slint::platform::set_platform(Box::new(backend));
694    /// ```
695    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    /// Creates a new winit backend with the default renderer that's compiled in.
708    ///
709    /// See the [backend documentation](slint:backends_and_renderers) for details on how to select the default renderer.
710    pub fn new() -> Result<Self, PlatformError> {
711        Self::builder().build()
712    }
713
714    #[i_slint_core_macros::slint_doc]
715    /// Creates a new winit backend with the renderer specified by name.
716    ///
717    /// See the [backend documentation](slint:backends_and_renderers) for details on how to select the default renderer.
718    ///
719    /// If the renderer name is `None` or the name is not recognized, the default renderer is selected.
720    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    /// Creates a new BackendBuilder for configuring aspects of the Winit backend before
729    /// setting it as the platform backend.
730    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                // Calling send_event is usually done by winit at the bottom of the stack,
834                // in event handlers, and thus winit might decide to process the event
835                // immediately within that stack.
836                // To prevent re-entrancy issues that might happen by getting the application
837                // event processed on top of the current stack, set winit in Poll mode so that
838                // events are queued and process on top of a clean stack during a requested animation
839                // frame a few moments later.
840                // This also allows batching multiple post_event calls and redraw their state changes
841                // all at once.
842                #[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]
885/// This helper trait can be used to obtain access to the [`winit::window::Window`] for a given
886/// [`slint::Window`](slint:rust:slint/struct.window).
887///
888/// Note that the association of a Slint window with a winit window relies on two factors:
889///
890/// - The winit backend must be in use. You can ensure this programmatically by calling [`slint::BackendSelector::backend_name()`](slint:rust:slint/struct.BackendSelector#method.backend_name)
891///   with "winit" as argument.
892/// - The winit window must've been created. Windowing systems, and by extension winit, require that windows can only be properly
893///   created when certain conditions of the event loop are met. For example, on Android the application can't be suspended. Therefore,
894///   functions like [`Self::has_winit_window()`] or [`Self::with_winit_window()`] will only succeed when the event loop is active.
895///   This is typically the case when callbacks are invoked from the event loop, such as through timers, user input events, or when window
896///   receives events (see also [`Self::on_winit_window_event()`]).
897pub trait WinitWindowAccessor: private::WinitWindowAccessorSealed {
898    /// Returns true if a [`winit::window::Window`] exists for this window. This is the case if the window is
899    /// backed by this winit backend.
900    fn has_winit_window(&self) -> bool;
901    /// Invokes the specified callback with a reference to the [`winit::window::Window`] that exists for this Slint window
902    /// and returns `Some(T)`; otherwise `None`.
903    fn with_winit_window<T>(&self, callback: impl FnOnce(&winit::window::Window) -> T)
904    -> Option<T>;
905    /// Registers a window event filter callback for this Slint window.
906    ///
907    /// The callback is invoked in the winit event loop whenever a window event is received with a reference to the
908    /// [`slint::Window`](i_slint_core::api::Window) and the [`winit::event::WindowEvent`]. The return value of the
909    /// callback specifies whether Slint should handle this event.
910    ///
911    /// If this window [is not backed by winit](WinitWindowAccessor::has_winit_window), this function is a no-op.
912    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    /// Returns a future that resolves to the [`winit::window::Window`] for this Slint window.
919    /// When the future is ready, the output it resolves to is either `Ok(Arc<winit::window::Window>)` if the window exists,
920    /// or an error if the window has been deleted in the meanwhile or isn't backed by the winit backend.
921    ///
922    /// ```rust,no_run
923    /// // Bring winit and accessor traits into scope.
924    /// use slint::winit_030::{WinitWindowAccessor, winit};
925    ///
926    /// slint::slint!{
927    ///     import { VerticalBox, Button } from "std-widgets.slint";
928    ///     export component HelloWorld inherits Window {
929    ///         callback clicked;
930    ///         VerticalBox {
931    ///             Text {
932    ///                 text: "hello world";
933    ///                 color: green;
934    ///             }
935    ///             Button {
936    ///                 text: "Click me";
937    ///                 clicked => { root.clicked(); }
938    ///             }
939    ///         }
940    ///     }
941    /// }
942    /// fn main() -> Result<(), Box<dyn std::error::Error>> {
943    ///     // Make sure the winit backed is selected:
944    ///    slint::BackendSelector::new()
945    ///        .backend_name("winit".into())
946    ///        .select()?;
947    ///
948    ///     let app = HelloWorld::new()?;
949    ///     let app_weak = app.as_weak();
950    ///
951    ///     slint::spawn_local(async move {
952    ///         let app = app_weak.unwrap();
953    ///         let winit_window = app.window().winit_window().await.unwrap();
954    ///         eprintln!("window id = {:#?}", winit_window.id());
955    ///     }).unwrap();
956    ///     app.run()?;
957    ///     Ok(())
958    /// }
959    /// ```
960    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// Sorry, can't test with rust test harness and multiple threads.
1032#[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        // Show() won't immediately create the window, the event loop will have to
1046        // spin first.
1047        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}