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
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
31/// Re-export of the winit crate.
32pub use winit;
33
34/// Internal type used by the winit backend for thread communication and window system updates.
35///
36/// See also [`EventLoopBuilder`]
37#[non_exhaustive]
38#[derive(Debug)]
39pub struct SlintEvent(CustomEvent);
40
41#[i_slint_core_macros::slint_doc]
42/// Convenience alias for the event loop builder used by Slint.
43///
44/// It can be used to configure the event loop with
45/// [`slint::BackendSelector::with_winit_event_loop_builder()`](slint:rust:slint/struct.BackendSelector.html#method.with_winit_event_loop_builder)
46pub type EventLoopBuilder = winit::event_loop::EventLoopBuilder<SlintEvent>;
47
48/// Returned by callbacks passed to [`Window::on_winit_window_event`](WinitWindowAccessor::on_winit_window_event)
49/// to determine if winit events should propagate to the Slint event loop.
50pub enum EventResult {
51    /// The winit event should propagate normally.
52    Propagate,
53    /// The winit event shouldn't be processed further.
54    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        // Got WindowEvent::Occluded
68        fn occluded(&self, _: bool) {}
69
70        fn suspend(&self) -> Result<(), PlatformError>;
71
72        // Got winit::Event::Resumed
73        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/// Use this trait to intercept events from winit.
180///
181/// It imitates [`winit::application::ApplicationHandler`] with two changes:
182///   - All functions are invoked before Slint sees them. Use the [`EventResult`] return value to
183///     optionally prevent Slint from seeing the event.
184///   - The [`Self::window_event()`] function has additional parameters to provide access to the Slint Window and
185///     Winit window, if applicable.
186#[allow(unused_variables)]
187pub trait CustomApplicationHandler {
188    /// Re-implement to intercept the [`ApplicationHandler::resumed()`](winit::application::ApplicationHandler::resumed()) event.
189    fn resumed(&mut self, _event_loop: &ActiveEventLoop) -> EventResult {
190        EventResult::Propagate
191    }
192
193    /// Re-implement to intercept the [`ApplicationHandler::window_event()`](winit::application::ApplicationHandler::window_event()) event.
194    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    /// Re-implement to intercept the [`ApplicationHandler::new_events()`](winit::application::ApplicationHandler::new_events()) event.
206    fn new_events(
207        &mut self,
208        event_loop: &ActiveEventLoop,
209        cause: winit::event::StartCause,
210    ) -> EventResult {
211        EventResult::Propagate
212    }
213
214    /// Re-implement to intercept the [`ApplicationHandler::device_event()`](winit::application::ApplicationHandler::device_event()) event.
215    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    /// Re-implement to intercept the [`ApplicationHandler::about_to_wait()`](winit::application::ApplicationHandler::about_to_wait()) event.
225    fn about_to_wait(&mut self, event_loop: &ActiveEventLoop) -> EventResult {
226        EventResult::Propagate
227    }
228
229    /// Re-implement to intercept the [`ApplicationHandler::suspended()`](winit::application::ApplicationHandler::suspended()) event.
230    fn suspended(&mut self, event_loop: &ActiveEventLoop) -> EventResult {
231        EventResult::Propagate
232    }
233
234    /// Re-implement to intercept the [`ApplicationHandler::exiting()`](winit::application::ApplicationHandler::exiting()) event.
235    fn exiting(&mut self, event_loop: &ActiveEventLoop) -> EventResult {
236        EventResult::Propagate
237    }
238
239    /// Re-implement to intercept the [`ApplicationHandler::memory_warning()`](winit::application::ApplicationHandler::memory_warning()) event.
240    fn memory_warning(&mut self, event_loop: &ActiveEventLoop) -> EventResult {
241        EventResult::Propagate
242    }
243}
244
245/// Use the BackendBuilder to configure the properties of the Winit Backend before creating it.
246/// Create the builder using [`Backend::builder()`], then configure it for example with [`Self::with_renderer_name`],
247/// and build the backend using [`Self::build`].
248pub 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    /// Configures this builder to require a renderer that supports the specified graphics API.
264    #[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    /// Configures this builder to use the specified renderer name when building the backend later.
271    /// Pass `renderer-software` for example to configure the backend to use the Slint software renderer.
272    #[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    /// Configures this builder to use the specified hook that will be called before a Window is created.
279    ///
280    /// It can be used to adjust settings of window that will be created.
281    ///
282    /// # Example
283    ///
284    /// ```rust,no_run
285    /// let mut backend = i_slint_backend_winit::Backend::builder()
286    ///     .with_window_attributes_hook(|attributes| attributes.with_content_protected(true))
287    ///     .build()
288    ///     .unwrap();
289    /// slint::platform::set_platform(Box::new(backend));
290    /// ```
291    #[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    /// Configures this builder to use the specified event loop builder when creating the event
301    /// loop during a subsequent call to [`Self::build`].
302    #[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    /// Configures this builder to enable or disable the default menu bar.
309    /// By default, the menu bar is provided by Slint. Set this to false
310    /// if you're providing your own menu bar.
311    /// Note that an application provided menu bar will be overriden by a `MenuBar`
312    /// declared in Slint code.
313    #[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    /// Configures this builder to spawn the event loop using [`winit::platform::web::EventLoopExtWebSys::spawn()`]
322    /// run `run_event_loop()` is called.
323    pub fn with_spawn_event_loop(mut self, enable: bool) -> Self {
324        self.spawn_event_loop = enable;
325        self
326    }
327
328    /// Configures this builder to use the specified [`CustomApplicationHandler`].
329    ///
330    /// This allow application developer to intercept events from winit.
331    /// Similar to [`winit::application::ApplicationHandler`].
332    #[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    /// Builds the backend with the parameters configured previously. Set the resulting backend
342    /// with `slint::platform::set_platform()`:
343    ///
344    /// # Example
345    ///
346    /// ```rust,no_run
347    /// let mut backend = i_slint_backend_winit::Backend::builder()
348    ///     .with_renderer_name("renderer-software")
349    ///     .build()
350    ///     .unwrap();
351    /// slint::platform::set_platform(Box::new(backend));
352    /// ```
353    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        // Never use winit's menu bar. Either we provide one ourselves with muda, or
359        // the user provides one.
360        #[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        // Initialize the winit event loop and propagate errors if for example `DISPLAY` or `WAYLAND_DISPLAY` isn't set.
367
368        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 a graphics API was requested, double check that it's GL. FemtoVG doesn't support Metal, etc.
380                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-27")]
389                    if matches!(_api, RequestedGraphicsAPI::WGPU27(..)) {
390                        return true;
391                    }
392                    false
393                }) {
394                    return Err(
395                        "The FemtoVG WGPU renderer only supports the WGPU27 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 a graphics API was requested, double check that it's GL.
408                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(
414                enable_skia_renderer,
415                any(feature = "unstable-wgpu-26", feature = "unstable-wgpu-27")
416            ))]
417            (Some("skia-wgpu"), maybe_graphics_api @ _) => {
418                if let Some(factory) = maybe_graphics_api.map_or_else(
419                    || {
420                        let result;
421                        cfg_if::cfg_if!(
422                            if #[cfg(feature = "unstable-wgpu-27")]
423                        {
424                            result = Some(
425                                renderer::skia::WinitSkiaRenderer::new_wgpu_27_suspended
426                                    as RendererFactoryFn,
427                            );
428                        } else {
429                             result = Some(
430                                renderer::skia::WinitSkiaRenderer::new_wgpu_26_suspended
431                                    as RendererFactoryFn,
432                            );
433                        }
434                        );
435                        result
436                    },
437                    |api| {
438                        #[cfg(feature = "unstable-wgpu-26")]
439                        if matches!(api, RequestedGraphicsAPI::WGPU26(..)) {
440                            return Some(
441                                renderer::skia::WinitSkiaRenderer::new_wgpu_26_suspended
442                                    as RendererFactoryFn,
443                            );
444                        }
445                        #[cfg(feature = "unstable-wgpu-27")]
446                        if matches!(api, RequestedGraphicsAPI::WGPU27(..)) {
447                            return Some(
448                                renderer::skia::WinitSkiaRenderer::new_wgpu_27_suspended
449                                    as RendererFactoryFn,
450                            );
451                        }
452                        None
453                    },
454                ) {
455                    factory
456                } else {
457                    return Err(
458                        format!("Skia with WGPU doesn't support non-WGPU graphics API").into()
459                    );
460                }
461            }
462            #[cfg(all(enable_skia_renderer, not(target_os = "android")))]
463            (Some("skia-software"), None) => {
464                renderer::skia::WinitSkiaRenderer::new_software_suspended
465            }
466            #[cfg(feature = "renderer-software")]
467            (Some("sw"), None) | (Some("software"), None) => {
468                renderer::sw::WinitSoftwareRenderer::new_suspended
469            }
470            (None, None) => default_renderer_factory,
471            (Some(renderer_name), _) => {
472                if self.allow_fallback {
473                    eprintln!(
474                        "slint winit: unrecognized renderer {renderer_name}, falling back to {DEFAULT_RENDERER_NAME}"
475                    );
476                    default_renderer_factory
477                } else {
478                    return Err(PlatformError::NoPlatform);
479                }
480            }
481            #[cfg(feature = "unstable-wgpu-26")]
482            (None, Some(RequestedGraphicsAPI::WGPU26(..))) => {
483                renderer::skia::WinitSkiaRenderer::new_wgpu_26_suspended
484            }
485            #[cfg(feature = "unstable-wgpu-27")]
486            (None, Some(RequestedGraphicsAPI::WGPU27(..))) => {
487                cfg_if::cfg_if! {
488                    if #[cfg(enable_skia_renderer)] {
489                        renderer::skia::WinitSkiaRenderer::new_wgpu_27_suspended
490                    } else {
491                        renderer::femtovg::WGPUFemtoVGRenderer::new_suspended
492                    }
493                }
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                        // If a graphics API was requested, double check that it's GL. FemtoVG doesn't support Metal, etc.
501                        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: RefCell<HashMap<winit::window::WindowId, Weak<WinitWindowAdapter>>>,
529    /// List of visible windows that have been created when without the event loop and
530    /// need to be mapped to a winit Window as soon as the event loop becomes active.
531    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    is_wayland: bool,
537}
538
539impl SharedBackendData {
540    fn new(
541        mut builder: EventLoopBuilder,
542        requested_graphics_api: Option<RequestedGraphicsAPI>,
543    ) -> Result<Self, PlatformError> {
544        #[cfg(not(target_arch = "wasm32"))]
545        use raw_window_handle::HasDisplayHandle;
546
547        #[cfg(all(unix, not(target_vendor = "apple")))]
548        {
549            #[cfg(feature = "wayland")]
550            {
551                use winit::platform::wayland::EventLoopBuilderExtWayland;
552                builder.with_any_thread(true);
553            }
554            #[cfg(feature = "x11")]
555            {
556                use winit::platform::x11::EventLoopBuilderExtX11;
557                builder.with_any_thread(true);
558
559                // Under WSL, the compositor sometimes crashes. Since we cannot reconnect after the compositor
560                // was restarted, the application panics. This does not happen when using XWayland. Therefore,
561                // when running under WSL, try to connect to X11 instead.
562                #[cfg(feature = "wayland")]
563                if std::fs::metadata("/proc/sys/fs/binfmt_misc/WSLInterop").is_ok()
564                    || std::fs::metadata("/run/WSL").is_ok()
565                {
566                    builder.with_x11();
567                }
568            }
569        }
570        #[cfg(target_family = "windows")]
571        {
572            use winit::platform::windows::EventLoopBuilderExtWindows;
573            builder.with_any_thread(true);
574        }
575
576        let event_loop =
577            builder.build().map_err(|e| format!("Error initializing winit event loop: {e}"))?;
578
579        cfg_if::cfg_if! {
580            if #[cfg(all(unix, not(target_vendor = "apple"), feature = "wayland"))] {
581                use winit::platform::wayland::EventLoopExtWayland;
582                let is_wayland = event_loop.is_wayland();
583            } else {
584                let is_wayland = false;
585            }
586        }
587
588        let event_loop_proxy = event_loop.create_proxy();
589        #[cfg(not(target_arch = "wasm32"))]
590        let clipboard = crate::clipboard::create_clipboard(
591            &event_loop
592                .display_handle()
593                .map_err(|display_err| PlatformError::OtherError(display_err.into()))?,
594        );
595        Ok(Self {
596            _requested_graphics_api: requested_graphics_api,
597            #[cfg(enable_skia_renderer)]
598            skia_context: i_slint_renderer_skia::SkiaSharedContext::default(),
599            active_windows: Default::default(),
600            inactive_windows: Default::default(),
601            #[cfg(not(target_arch = "wasm32"))]
602            clipboard: RefCell::new(clipboard),
603            not_running_event_loop: RefCell::new(Some(event_loop)),
604            event_loop_proxy,
605            is_wayland,
606        })
607    }
608
609    pub fn register_window(&self, id: winit::window::WindowId, window: Rc<WinitWindowAdapter>) {
610        self.active_windows.borrow_mut().insert(id, Rc::downgrade(&window));
611    }
612
613    pub fn register_inactive_window(&self, window: Rc<WinitWindowAdapter>) {
614        let window = Rc::downgrade(&window);
615        let mut inactive_windows = self.inactive_windows.borrow_mut();
616        if !inactive_windows.iter().any(|w| Weak::ptr_eq(w, &window)) {
617            inactive_windows.push(window);
618        }
619    }
620
621    pub fn unregister_window(&self, id: Option<winit::window::WindowId>) {
622        if let Some(id) = id {
623            self.active_windows.borrow_mut().remove(&id);
624        } else {
625            // Use this opportunity of a Window being removed to tidy up.
626            self.inactive_windows
627                .borrow_mut()
628                .retain(|inactive_weak_window| inactive_weak_window.strong_count() > 0)
629        }
630    }
631
632    pub fn create_inactive_windows(
633        &self,
634        event_loop: &winit::event_loop::ActiveEventLoop,
635    ) -> Result<(), PlatformError> {
636        let mut inactive_windows = self.inactive_windows.take();
637        let mut result = Ok(());
638        while let Some(window_weak) = inactive_windows.pop() {
639            if let Some(err) = window_weak.upgrade().and_then(|w| w.ensure_window(event_loop).err())
640            {
641                result = Err(err);
642                break;
643            }
644        }
645        self.inactive_windows.borrow_mut().extend(inactive_windows);
646        result
647    }
648
649    pub fn window_by_id(&self, id: winit::window::WindowId) -> Option<Rc<WinitWindowAdapter>> {
650        self.active_windows.borrow().get(&id).and_then(|weakref| weakref.upgrade())
651    }
652}
653
654type RendererFactoryFn =
655    fn(&Rc<SharedBackendData>) -> Result<Box<dyn WinitCompatibleRenderer>, PlatformError>;
656
657#[i_slint_core_macros::slint_doc]
658/// This struct implements the Slint Platform trait.
659/// Use this in conjunction with [`slint::platform::set_platform`](slint:rust:slint/platform/fn.set_platform.html) to initialize.
660/// Slint to use winit for all windowing system interaction.
661///
662/// ```rust,no_run
663/// use i_slint_backend_winit::Backend;
664/// slint::platform::set_platform(Box::new(Backend::new().unwrap()));
665/// ```
666pub struct Backend {
667    renderer_factory_fn: RendererFactoryFn,
668    event_loop_state: RefCell<Option<crate::event_loop::EventLoopState>>,
669    shared_data: Rc<SharedBackendData>,
670    custom_application_handler: RefCell<Option<Box<dyn crate::CustomApplicationHandler>>>,
671
672    /// This hook is called before a Window is created.
673    ///
674    /// It can be used to adjust settings of window that will be created
675    ///
676    /// See also [`BackendBuilder::with_window_attributes_hook`].
677    ///
678    /// # Example
679    ///
680    /// ```rust,no_run
681    /// let mut backend = i_slint_backend_winit::Backend::new().unwrap();
682    /// backend.window_attributes_hook = Some(Box::new(|attributes| attributes.with_content_protected(true)));
683    /// slint::platform::set_platform(Box::new(backend));
684    /// ```
685    pub window_attributes_hook:
686        Option<Box<dyn Fn(winit::window::WindowAttributes) -> winit::window::WindowAttributes>>,
687
688    #[cfg(all(muda, target_os = "macos"))]
689    muda_enable_default_menu_bar_bar: bool,
690
691    #[cfg(target_family = "wasm")]
692    spawn_event_loop: bool,
693}
694
695impl Backend {
696    #[i_slint_core_macros::slint_doc]
697    /// Creates a new winit backend with the default renderer that's compiled in.
698    ///
699    /// See the [backend documentation](slint:backends_and_renderers) for details on how to select the default renderer.
700    pub fn new() -> Result<Self, PlatformError> {
701        Self::builder().build()
702    }
703
704    #[i_slint_core_macros::slint_doc]
705    /// Creates a new winit backend with the renderer specified by name.
706    ///
707    /// See the [backend documentation](slint:backends_and_renderers) for details on how to select the default renderer.
708    ///
709    /// If the renderer name is `None` or the name is not recognized, the default renderer is selected.
710    pub fn new_with_renderer_by_name(renderer_name: Option<&str>) -> Result<Self, PlatformError> {
711        let mut builder = Self::builder();
712        if let Some(name) = renderer_name {
713            builder = builder.with_renderer_name(name.to_string());
714        }
715        builder.build()
716    }
717
718    /// Creates a new BackendBuilder for configuring aspects of the Winit backend before
719    /// setting it as the platform backend.
720    pub fn builder() -> BackendBuilder {
721        BackendBuilder {
722            allow_fallback: true,
723            requested_graphics_api: None,
724            window_attributes_hook: None,
725            renderer_name: None,
726            event_loop_builder: None,
727            #[cfg(all(muda, target_os = "macos"))]
728            muda_enable_default_menu_bar_bar: true,
729            #[cfg(target_family = "wasm")]
730            spawn_event_loop: false,
731            custom_application_handler: None,
732        }
733    }
734}
735
736impl i_slint_core::platform::Platform for Backend {
737    fn create_window_adapter(&self) -> Result<Rc<dyn WindowAdapter>, PlatformError> {
738        let mut attrs = WinitWindowAdapter::window_attributes()?;
739
740        if let Some(hook) = &self.window_attributes_hook {
741            attrs = hook(attrs);
742        }
743
744        let adapter = (self.renderer_factory_fn)(&self.shared_data).map_or_else(
745            |e| {
746                try_create_window_with_fallback_renderer(
747                    &self.shared_data,
748                    attrs.clone(),
749                    &self.shared_data.event_loop_proxy.clone(),
750                    #[cfg(all(muda, target_os = "macos"))]
751                    self.muda_enable_default_menu_bar_bar,
752                )
753                .ok_or_else(|| format!("Winit backend failed to find a suitable renderer: {e}"))
754            },
755            |renderer| {
756                Ok(WinitWindowAdapter::new(
757                    self.shared_data.clone(),
758                    renderer,
759                    attrs.clone(),
760                    #[cfg(any(enable_accesskit, muda))]
761                    self.shared_data.event_loop_proxy.clone(),
762                    #[cfg(all(muda, target_os = "macos"))]
763                    self.muda_enable_default_menu_bar_bar,
764                ))
765            },
766        )?;
767        Ok(adapter)
768    }
769
770    fn run_event_loop(&self) -> Result<(), PlatformError> {
771        let loop_state = self.event_loop_state.borrow_mut().take().unwrap_or_else(|| {
772            EventLoopState::new(self.shared_data.clone(), self.custom_application_handler.take())
773        });
774        #[cfg(target_family = "wasm")]
775        {
776            if self.spawn_event_loop {
777                return loop_state.spawn();
778            }
779        }
780        let new_state = loop_state.run()?;
781        *self.event_loop_state.borrow_mut() = Some(new_state);
782        Ok(())
783    }
784
785    #[cfg(all(not(target_arch = "wasm32"), not(ios_and_friends)))]
786    fn process_events(
787        &self,
788        timeout: core::time::Duration,
789        _: i_slint_core::InternalToken,
790    ) -> Result<core::ops::ControlFlow<()>, PlatformError> {
791        let loop_state = self.event_loop_state.borrow_mut().take().unwrap_or_else(|| {
792            EventLoopState::new(self.shared_data.clone(), self.custom_application_handler.take())
793        });
794        let (new_state, status) = loop_state.pump_events(Some(timeout))?;
795        *self.event_loop_state.borrow_mut() = Some(new_state);
796        match status {
797            winit::platform::pump_events::PumpStatus::Continue => {
798                Ok(core::ops::ControlFlow::Continue(()))
799            }
800            winit::platform::pump_events::PumpStatus::Exit(code) => {
801                if code == 0 {
802                    Ok(core::ops::ControlFlow::Break(()))
803                } else {
804                    Err(format!("Event loop exited with non-zero code {code}").into())
805                }
806            }
807        }
808    }
809
810    fn new_event_loop_proxy(&self) -> Option<Box<dyn EventLoopProxy>> {
811        struct Proxy(winit::event_loop::EventLoopProxy<SlintEvent>);
812        impl EventLoopProxy for Proxy {
813            fn quit_event_loop(&self) -> Result<(), EventLoopError> {
814                self.0
815                    .send_event(SlintEvent(CustomEvent::Exit))
816                    .map_err(|_| EventLoopError::EventLoopTerminated)
817            }
818
819            fn invoke_from_event_loop(
820                &self,
821                event: Box<dyn FnOnce() + Send>,
822            ) -> Result<(), EventLoopError> {
823                // Calling send_event is usually done by winit at the bottom of the stack,
824                // in event handlers, and thus winit might decide to process the event
825                // immediately within that stack.
826                // To prevent re-entrancy issues that might happen by getting the application
827                // event processed on top of the current stack, set winit in Poll mode so that
828                // events are queued and process on top of a clean stack during a requested animation
829                // frame a few moments later.
830                // This also allows batching multiple post_event calls and redraw their state changes
831                // all at once.
832                #[cfg(target_arch = "wasm32")]
833                self.0
834                    .send_event(SlintEvent(CustomEvent::WakeEventLoopWorkaround))
835                    .map_err(|_| EventLoopError::EventLoopTerminated)?;
836
837                self.0
838                    .send_event(SlintEvent(CustomEvent::UserEvent(event)))
839                    .map_err(|_| EventLoopError::EventLoopTerminated)
840            }
841        }
842        Some(Box::new(Proxy(self.shared_data.event_loop_proxy.clone())))
843    }
844
845    #[cfg(target_arch = "wasm32")]
846    fn set_clipboard_text(&self, text: &str, clipboard: i_slint_core::platform::Clipboard) {
847        crate::wasm_input_helper::set_clipboard_text(text.into(), clipboard);
848    }
849
850    #[cfg(not(target_arch = "wasm32"))]
851    fn set_clipboard_text(&self, text: &str, clipboard: i_slint_core::platform::Clipboard) {
852        let mut pair = self.shared_data.clipboard.borrow_mut();
853        if let Some(clipboard) = clipboard::select_clipboard(&mut pair, clipboard) {
854            clipboard.set_contents(text.into()).ok();
855        }
856    }
857
858    #[cfg(target_arch = "wasm32")]
859    fn clipboard_text(&self, clipboard: i_slint_core::platform::Clipboard) -> Option<String> {
860        crate::wasm_input_helper::get_clipboard_text(clipboard)
861    }
862
863    #[cfg(not(target_arch = "wasm32"))]
864    fn clipboard_text(&self, clipboard: i_slint_core::platform::Clipboard) -> Option<String> {
865        let mut pair = self.shared_data.clipboard.borrow_mut();
866        clipboard::select_clipboard(&mut pair, clipboard).and_then(|c| c.get_contents().ok())
867    }
868}
869
870mod private {
871    pub trait WinitWindowAccessorSealed {}
872}
873
874#[i_slint_core_macros::slint_doc]
875/// This helper trait can be used to obtain access to the [`winit::window::Window`] for a given
876/// [`slint::Window`](slint:rust:slint/struct.window).
877///
878/// Note that the association of a Slint window with a winit window relies on two factors:
879///
880/// - 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)
881///   with "winit" as argument.
882/// - The winit window must've been created. Windowing systems, and by extension winit, require that windows can only be properly
883///   created when certain conditions of the event loop are met. For example, on Android the application can't be suspended. Therefore,
884///   functions like [`Self::has_winit_window()`] or [`Self::with_winit_window()`] will only succeed when the event loop is active.
885///   This is typically the case when callbacks are invoked from the event loop, such as through timers, user input events, or when window
886///   receives events (see also [`Self::on_winit_window_event()`]).
887pub trait WinitWindowAccessor: private::WinitWindowAccessorSealed {
888    /// Returns true if a [`winit::window::Window`] exists for this window. This is the case if the window is
889    /// backed by this winit backend.
890    fn has_winit_window(&self) -> bool;
891    /// Invokes the specified callback with a reference to the [`winit::window::Window`] that exists for this Slint window
892    /// and returns `Some(T)`; otherwise `None`.
893    fn with_winit_window<T>(&self, callback: impl FnOnce(&winit::window::Window) -> T)
894        -> Option<T>;
895    /// Registers a window event filter callback for this Slint window.
896    ///
897    /// The callback is invoked in the winit event loop whenever a window event is received with a reference to the
898    /// [`slint::Window`](i_slint_core::api::Window) and the [`winit::event::WindowEvent`]. The return value of the
899    /// callback specifies whether Slint should handle this event.
900    ///
901    /// If this window [is not backed by winit](WinitWindowAccessor::has_winit_window), this function is a no-op.
902    fn on_winit_window_event(
903        &self,
904        callback: impl FnMut(&i_slint_core::api::Window, &winit::event::WindowEvent) -> EventResult
905            + 'static,
906    );
907
908    /// Returns a future that resolves to the [`winit::window::Window`] for this Slint window.
909    /// When the future is ready, the output it resolves to is either `Ok(Arc<winit::window::Window>)` if the window exists,
910    /// or an error if the window has been deleted in the meanwhile or isn't backed by the winit backend.
911    ///
912    /// ```rust,no_run
913    /// // Bring winit and accessor traits into scope.
914    /// use slint::winit_030::{WinitWindowAccessor, winit};
915    ///
916    /// slint::slint!{
917    ///     import { VerticalBox, Button } from "std-widgets.slint";
918    ///     export component HelloWorld inherits Window {
919    ///         callback clicked;
920    ///         VerticalBox {
921    ///             Text {
922    ///                 text: "hello world";
923    ///                 color: green;
924    ///             }
925    ///             Button {
926    ///                 text: "Click me";
927    ///                 clicked => { root.clicked(); }
928    ///             }
929    ///         }
930    ///     }
931    /// }
932    /// fn main() -> Result<(), Box<dyn std::error::Error>> {
933    ///     // Make sure the winit backed is selected:
934    ///    slint::BackendSelector::new()
935    ///        .backend_name("winit".into())
936    ///        .select()?;
937    ///
938    ///     let app = HelloWorld::new()?;
939    ///     let app_weak = app.as_weak();
940    ///
941    ///     slint::spawn_local(async move {
942    ///         let app = app_weak.unwrap();
943    ///         let winit_window = app.window().winit_window().await.unwrap();
944    ///         eprintln!("window id = {:#?}", winit_window.id());
945    ///     }).unwrap();
946    ///     app.run()?;
947    ///     Ok(())
948    /// }
949    /// ```
950    fn winit_window(
951        &self,
952    ) -> impl std::future::Future<Output = Result<Arc<winit::window::Window>, PlatformError>>;
953}
954
955impl WinitWindowAccessor for i_slint_core::api::Window {
956    fn has_winit_window(&self) -> bool {
957        i_slint_core::window::WindowInner::from_pub(self)
958            .window_adapter()
959            .internal(i_slint_core::InternalToken)
960            .and_then(|wa| wa.as_any().downcast_ref::<WinitWindowAdapter>())
961            .is_some_and(|adapter| adapter.winit_window().is_some())
962    }
963
964    fn with_winit_window<T>(
965        &self,
966        callback: impl FnOnce(&winit::window::Window) -> T,
967    ) -> Option<T> {
968        i_slint_core::window::WindowInner::from_pub(self)
969            .window_adapter()
970            .internal(i_slint_core::InternalToken)
971            .and_then(|wa| wa.as_any().downcast_ref::<WinitWindowAdapter>())
972            .and_then(|adapter| adapter.winit_window().map(|w| callback(&w)))
973    }
974
975    fn winit_window(
976        &self,
977    ) -> impl std::future::Future<Output = Result<Arc<winit::window::Window>, PlatformError>> {
978        Box::pin(async move {
979            let adapter_weak = i_slint_core::window::WindowInner::from_pub(self)
980                .window_adapter()
981                .internal(i_slint_core::InternalToken)
982                .and_then(|wa| wa.as_any().downcast_ref::<WinitWindowAdapter>())
983                .map(|wa| wa.self_weak.clone())
984                .ok_or_else(|| {
985                    PlatformError::OtherError(
986                        format!("Slint window is not backed by a Winit window adapter").into(),
987                    )
988                })?;
989            WinitWindowAdapter::async_winit_window(adapter_weak).await
990        })
991    }
992
993    fn on_winit_window_event(
994        &self,
995        mut callback: impl FnMut(&i_slint_core::api::Window, &winit::event::WindowEvent) -> EventResult
996            + 'static,
997    ) {
998        if let Some(adapter) = i_slint_core::window::WindowInner::from_pub(self)
999            .window_adapter()
1000            .internal(i_slint_core::InternalToken)
1001            .and_then(|wa| wa.as_any().downcast_ref::<WinitWindowAdapter>())
1002        {
1003            adapter
1004                .window_event_filter
1005                .set(Some(Box::new(move |window, event| callback(window, event))));
1006        }
1007    }
1008}
1009
1010impl private::WinitWindowAccessorSealed for i_slint_core::api::Window {}
1011
1012#[cfg(test)]
1013mod testui {
1014    slint::slint! {
1015        export component App inherits Window {
1016            Text { text: "Ok"; }
1017        }
1018    }
1019}
1020
1021// Sorry, can't test with rust test harness and multiple threads.
1022#[cfg(not(any(target_arch = "wasm32", target_vendor = "apple")))]
1023#[test]
1024fn test_window_accessor_and_rwh() {
1025    slint::platform::set_platform(Box::new(crate::Backend::new().unwrap())).unwrap();
1026
1027    use testui::*;
1028
1029    slint::spawn_local(async move {
1030        let app = App::new().unwrap();
1031        let slint_window = app.window();
1032
1033        assert!(!slint_window.has_winit_window());
1034
1035        // Show() won't immediately create the window, the event loop will have to
1036        // spin first.
1037        app.show().unwrap();
1038
1039        let result = slint_window.winit_window().await;
1040        assert!(result.is_ok(), "Failed to get winit window: {:?}", result.err());
1041        assert!(slint_window.has_winit_window());
1042        let handle = slint_window.window_handle();
1043        use raw_window_handle::{HasDisplayHandle, HasWindowHandle};
1044        assert!(handle.window_handle().is_ok());
1045        assert!(handle.display_handle().is_ok());
1046        slint::quit_event_loop().unwrap();
1047    })
1048    .unwrap();
1049
1050    slint::run_event_loop().unwrap();
1051}