Skip to main content

i_slint_backend_winit/
winitwindowadapter.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//! This module contains the GraphicsWindow that used to be within corelib.
5
6// cspell:ignore accesskit borderless corelib nesw webgl winit winsys xlib
7
8use core::cell::{Cell, RefCell};
9#[cfg(target_os = "macos")]
10use std::cell::OnceCell;
11use std::rc::Rc;
12use std::rc::Weak;
13use std::sync::Arc;
14
15use euclid::approxeq::ApproxEq;
16
17#[cfg(muda)]
18use i_slint_core::api::LogicalPosition;
19use i_slint_core::lengths::{PhysicalPx, ScaleFactor};
20use i_slint_core::renderer::DrawOutcome;
21use winit::event_loop::ActiveEventLoop;
22#[cfg(target_arch = "wasm32")]
23use winit::platform::web::WindowExtWebSys;
24#[cfg(target_family = "windows")]
25use winit::platform::windows::WindowExtWindows;
26
27#[cfg(muda)]
28use crate::muda::MudaType;
29use crate::renderer::WinitCompatibleRenderer;
30use crate::winit_compat::WindowSurfaceSizeExt;
31
32use corelib::item_tree::ItemTreeRc;
33#[cfg(enable_accesskit)]
34use corelib::item_tree::{ItemTreeRef, ItemTreeRefPin};
35use corelib::items::{ColorScheme, MouseCursor};
36#[cfg(enable_accesskit)]
37use corelib::items::{ItemRc, ItemRef};
38
39#[cfg(any(enable_accesskit, muda))]
40use crate::SlintEvent;
41use crate::{EventResult, SharedBackendData};
42use corelib::api::PhysicalSize;
43use corelib::layout::Orientation;
44use corelib::lengths::LogicalLength;
45use corelib::platform::{PlatformError, WindowEvent};
46use corelib::window::{WindowAdapter, WindowAdapterInternal, WindowInner};
47use corelib::{Coord, graphics::*};
48use i_slint_core::{self as corelib};
49#[cfg(any(enable_accesskit, muda))]
50use winit::event_loop::EventLoopProxy;
51use winit::window::{WindowAttributes, WindowButtons};
52
53pub(crate) fn position_to_winit(pos: &corelib::api::WindowPosition) -> winit::dpi::Position {
54    match pos {
55        corelib::api::WindowPosition::Logical(pos) => {
56            winit::dpi::Position::new(winit::dpi::LogicalPosition::new(pos.x, pos.y))
57        }
58        corelib::api::WindowPosition::Physical(pos) => {
59            winit::dpi::Position::new(winit::dpi::PhysicalPosition::new(pos.x, pos.y))
60        }
61    }
62}
63
64fn window_size_to_winit(size: &corelib::api::WindowSize) -> winit::dpi::Size {
65    match size {
66        corelib::api::WindowSize::Logical(size) => {
67            winit::dpi::Size::new(logical_size_to_winit(*size))
68        }
69        corelib::api::WindowSize::Physical(size) => {
70            winit::dpi::Size::new(physical_size_to_winit(*size))
71        }
72    }
73}
74
75pub fn physical_size_to_slint(size: &winit::dpi::PhysicalSize<u32>) -> corelib::api::PhysicalSize {
76    corelib::api::PhysicalSize::new(size.width, size.height)
77}
78
79fn logical_size_to_winit(s: i_slint_core::api::LogicalSize) -> winit::dpi::LogicalSize<f64> {
80    winit::dpi::LogicalSize::new(s.width as f64, s.height as f64)
81}
82
83fn physical_size_to_winit(size: PhysicalSize) -> winit::dpi::PhysicalSize<u32> {
84    winit::dpi::PhysicalSize::new(size.width, size.height)
85}
86
87fn filter_out_zero_width_or_height(
88    size: winit::dpi::LogicalSize<f64>,
89) -> winit::dpi::LogicalSize<f64> {
90    fn filter(v: f64) -> f64 {
91        if v.approx_eq(&0.) {
92            // Some width or height is better than zero
93            10.
94        } else {
95            v
96        }
97    }
98    winit::dpi::LogicalSize { width: filter(size.width), height: filter(size.height) }
99}
100
101fn apply_scale_factor_to_logical_sizes_in_attributes(
102    attributes: &mut WindowAttributes,
103    scale_factor: f64,
104) {
105    let fixup = |maybe_size: &mut Option<winit::dpi::Size>| {
106        if let Some(size) = maybe_size.as_mut() {
107            *size = winit::dpi::Size::Physical(size.to_physical::<u32>(scale_factor))
108        }
109    };
110
111    fixup(&mut attributes.inner_size);
112    fixup(&mut attributes.min_inner_size);
113    fixup(&mut attributes.max_inner_size);
114    fixup(&mut attributes.resize_increments);
115}
116
117fn icon_to_winit(
118    icon: corelib::graphics::Image,
119    size: euclid::Size2D<Coord, PhysicalPx>,
120) -> Option<winit::window::Icon> {
121    let image_inner: &ImageInner = (&icon).into();
122
123    let pixel_buffer = image_inner.render_to_buffer(Some(size.cast()))?;
124
125    // This could become a method in SharedPixelBuffer...
126    let rgba_pixels: Vec<u8> = match &pixel_buffer {
127        SharedImageBuffer::RGB8(pixels) => pixels
128            .as_bytes()
129            .chunks(3)
130            .flat_map(|rgb| IntoIterator::into_iter([rgb[0], rgb[1], rgb[2], 255]))
131            .collect(),
132        SharedImageBuffer::RGBA8(pixels) => pixels.as_bytes().to_vec(),
133        SharedImageBuffer::RGBA8Premultiplied(pixels) => pixels
134            .as_bytes()
135            .chunks(4)
136            .flat_map(|rgba| {
137                let alpha = rgba[3] as u32;
138                IntoIterator::into_iter(rgba)
139                    .take(3)
140                    .map(move |component| (*component as u32 * alpha / 255) as u8)
141                    .chain(std::iter::once(alpha as u8))
142            })
143            .collect(),
144    };
145
146    winit::window::Icon::from_rgba(rgba_pixels, pixel_buffer.width(), pixel_buffer.height()).ok()
147}
148
149fn window_is_resizable(
150    min_size: Option<corelib::api::LogicalSize>,
151    max_size: Option<corelib::api::LogicalSize>,
152) -> bool {
153    if let Some((
154        corelib::api::LogicalSize { width: min_width, height: min_height, .. },
155        corelib::api::LogicalSize { width: max_width, height: max_height, .. },
156    )) = min_size.zip(max_size)
157    {
158        min_width < max_width || min_height < max_height
159    } else {
160        true
161    }
162}
163
164#[allow(clippy::large_enum_variant)]
165enum WinitWindowOrNone {
166    HasWindow {
167        window: Arc<winit::window::Window>,
168        frame_throttle: Box<dyn crate::frame_throttle::FrameThrottle>,
169        #[cfg(enable_accesskit)]
170        accesskit_adapter: RefCell<crate::accesskit::AccessKitAdapter>,
171        #[cfg(muda)]
172        muda_adapter: RefCell<Option<crate::muda::MudaAdapter>>,
173        #[cfg(muda)]
174        context_menu_muda_adapter: RefCell<Option<crate::muda::MudaAdapter>>,
175        #[cfg(target_os = "ios")]
176        keyboard_curve_sampler: super::ios::KeyboardCurveSampler,
177        #[cfg(target_os = "ios")]
178        _color_scheme_observer: Option<super::ios::TraitChangeObserver>,
179        #[cfg(target_os = "ios")]
180        _font_size_observer: Option<super::ios::TraitChangeObserver>,
181    },
182    None(RefCell<WindowAttributes>),
183}
184
185impl WinitWindowOrNone {
186    fn as_window(&self) -> Option<Arc<winit::window::Window>> {
187        match self {
188            Self::HasWindow { window, .. } => Some(window.clone()),
189            Self::None { .. } => None,
190        }
191    }
192
193    fn set_window_icon(&self, icon: Option<winit::window::Icon>) {
194        match self {
195            Self::HasWindow { window, .. } => {
196                #[cfg(target_family = "windows")]
197                window.set_taskbar_icon(icon.as_ref().cloned());
198                window.set_window_icon(icon);
199            }
200            Self::None(attributes) => attributes.borrow_mut().window_icon = icon,
201        }
202    }
203
204    fn set_title(&self, title: &str) {
205        match self {
206            Self::HasWindow { window, .. } => window.set_title(title),
207            Self::None(attributes) => attributes.borrow_mut().title = title.into(),
208        }
209    }
210
211    fn set_decorations(&self, decorations: bool) {
212        match self {
213            Self::HasWindow { window, .. } => window.set_decorations(decorations),
214            Self::None(attributes) => attributes.borrow_mut().decorations = decorations,
215        }
216    }
217
218    fn fullscreen(&self) -> Option<winit::window::Fullscreen> {
219        match self {
220            Self::HasWindow { window, .. } => window.fullscreen(),
221            Self::None(attributes) => attributes.borrow().fullscreen.clone(),
222        }
223    }
224
225    fn set_fullscreen(&self, fullscreen: Option<winit::window::Fullscreen>) {
226        match self {
227            Self::HasWindow { window, .. } => window.set_fullscreen(fullscreen),
228            Self::None(attributes) => attributes.borrow_mut().fullscreen = fullscreen,
229        }
230    }
231
232    fn set_window_level(&self, level: winit::window::WindowLevel) {
233        match self {
234            Self::HasWindow { window, .. } => window.set_window_level(level),
235            Self::None(attributes) => attributes.borrow_mut().window_level = level,
236        }
237    }
238
239    fn set_visible(&self, visible: bool) {
240        match self {
241            Self::HasWindow { window, .. } => window.set_visible(visible),
242            Self::None(attributes) => attributes.borrow_mut().visible = visible,
243        }
244    }
245
246    fn set_maximized(&self, maximized: bool) {
247        match self {
248            Self::HasWindow { window, .. } => window.set_maximized(maximized),
249            Self::None(attributes) => attributes.borrow_mut().maximized = maximized,
250        }
251    }
252
253    fn set_minimized(&self, minimized: bool) {
254        match self {
255            Self::HasWindow { window, .. } => window.set_minimized(minimized),
256            Self::None(..) => { /* TODO: winit is missing attributes.borrow_mut().minimized = minimized*/
257            }
258        }
259    }
260
261    fn set_resizable(&self, resizable: bool) {
262        match self {
263            Self::HasWindow { window, .. } => {
264                window.set_resizable(resizable);
265            }
266            Self::None(attributes) => attributes.borrow_mut().resizable = resizable,
267        }
268    }
269
270    fn set_min_inner_size(
271        &self,
272        min_inner_size: Option<winit::dpi::LogicalSize<f64>>,
273        scale_factor: f64,
274    ) {
275        match self {
276            Self::HasWindow { window, .. } => {
277                // Store as physical size to make sure that our potentially overriding scale factor is applied.
278                window
279                    .set_min_inner_size(min_inner_size.map(|s| s.to_physical::<u32>(scale_factor)))
280            }
281            Self::None(attributes) => {
282                // Store as logical size, so that we can apply the real window scale factor later when it's known.
283                attributes.borrow_mut().min_inner_size = min_inner_size.map(|s| s.into());
284            }
285        }
286    }
287
288    fn set_max_inner_size(
289        &self,
290        max_inner_size: Option<winit::dpi::LogicalSize<f64>>,
291        scale_factor: f64,
292    ) {
293        match self {
294            Self::HasWindow { window, .. } => {
295                // Store as physical size to make sure that our potentially overriding scale factor is applied.
296                window
297                    .set_max_inner_size(max_inner_size.map(|s| s.to_physical::<u32>(scale_factor)))
298            }
299            Self::None(attributes) => {
300                // Store as logical size, so that we can apply the real window scale factor later when it's known.
301                attributes.borrow_mut().max_inner_size = max_inner_size.map(|s| s.into())
302            }
303        }
304    }
305}
306
307#[derive(Default, PartialEq, Clone, Copy)]
308pub(crate) enum WindowVisibility {
309    #[default]
310    Hidden,
311    /// This implies that we might resize the window the first time it's shown.
312    ShownFirstTime,
313    Shown,
314}
315
316/// GraphicsWindow is an implementation of the [WindowAdapter][`crate::eventloop::WindowAdapter`] trait. This is
317/// typically instantiated by entry factory functions of the different graphics back ends.
318pub struct WinitWindowAdapter {
319    pub shared_backend_data: Rc<SharedBackendData>,
320    window: corelib::api::Window,
321    pub(crate) self_weak: Weak<Self>,
322    pending_redraw: Cell<bool>,
323    constraints: Cell<corelib::window::LayoutConstraints>,
324    /// Indicates if the window is shown, from the perspective of the API user.
325    shown: Cell<WindowVisibility>,
326    window_level: Cell<winit::window::WindowLevel>,
327    maximized: Cell<bool>,
328    minimized: Cell<bool>,
329    fullscreen: Cell<bool>,
330
331    pub(crate) renderer: Box<dyn WinitCompatibleRenderer>,
332    /// We cache the size because winit_window.inner_size() can return different value between calls (eg, on X11)
333    /// And we wan see the newer value before the Resized event was received, leading to inconsistencies
334    size: Cell<PhysicalSize>,
335    /// We requested a size to be set, but we didn't get the resize event from winit yet
336    pending_requested_size: Cell<Option<winit::dpi::Size>>,
337
338    /// Whether the size has been set explicitly via `set_size`.
339    /// If that's the case, we should't resize to the preferred size in set_visible
340    has_explicit_size: Cell<bool>,
341
342    /// Indicate whether we've ever received a resize event from winit after showing the window.
343    pending_resize_event_after_show: Cell<bool>,
344
345    #[cfg(target_arch = "wasm32")]
346    virtual_keyboard_helper: RefCell<Option<super::wasm_input_helper::WasmInputHelper>>,
347
348    #[cfg(any(enable_accesskit, muda))]
349    event_loop_proxy: EventLoopProxy<SlintEvent>,
350
351    pub(crate) window_event_filter: Cell<
352        Option<Box<dyn FnMut(&corelib::api::Window, &winit::event::WindowEvent) -> EventResult>>,
353    >,
354
355    winit_window_or_none: RefCell<WinitWindowOrNone>,
356    window_existence_wakers: RefCell<Vec<core::task::Waker>>,
357
358    #[cfg(target_os = "macos")]
359    macos_color_observer: OnceCell<
360        objc2::rc::Retained<objc2::runtime::ProtocolObject<dyn objc2::runtime::NSObjectProtocol>>,
361    >,
362
363    #[cfg(muda)]
364    menubar: RefCell<Option<vtable::VRc<i_slint_core::menus::MenuVTable>>>,
365
366    #[cfg(muda)]
367    context_menu: RefCell<Option<vtable::VRc<i_slint_core::menus::MenuVTable>>>,
368
369    #[cfg(all(muda, target_os = "macos"))]
370    muda_enable_default_menu_bar: bool,
371
372    /// Winit's window_icon API has no way of checking if the window icon is
373    /// the same as a previously set one, so keep track of that here.
374    window_icon_cache_key: RefCell<Option<ImageCacheKey>>,
375}
376
377impl WinitWindowAdapter {
378    /// Creates a new reference-counted instance.
379    pub(crate) fn new(
380        shared_backend_data: Rc<SharedBackendData>,
381        renderer: Box<dyn WinitCompatibleRenderer>,
382        window_attributes: winit::window::WindowAttributes,
383        #[cfg(any(enable_accesskit, muda))] proxy: EventLoopProxy<SlintEvent>,
384        #[cfg(all(muda, target_os = "macos"))] muda_enable_default_menu_bar: bool,
385    ) -> Rc<Self> {
386        let self_rc = Rc::new_cyclic(|self_weak| Self {
387            shared_backend_data: shared_backend_data.clone(),
388            window: corelib::api::Window::new(self_weak.clone() as _),
389            self_weak: self_weak.clone(),
390            pending_redraw: Default::default(),
391            constraints: Default::default(),
392            shown: Default::default(),
393            window_level: Default::default(),
394            maximized: Cell::default(),
395            minimized: Cell::default(),
396            fullscreen: Cell::default(),
397            winit_window_or_none: RefCell::new(WinitWindowOrNone::None(window_attributes.into())),
398            window_existence_wakers: RefCell::new(Vec::default()),
399            size: Cell::default(),
400            pending_requested_size: Cell::new(None),
401            has_explicit_size: Default::default(),
402            pending_resize_event_after_show: Default::default(),
403            renderer,
404            #[cfg(target_arch = "wasm32")]
405            virtual_keyboard_helper: Default::default(),
406            #[cfg(any(enable_accesskit, muda))]
407            event_loop_proxy: proxy,
408            window_event_filter: Cell::new(None),
409            #[cfg(target_os = "macos")]
410            macos_color_observer: OnceCell::new(),
411            #[cfg(muda)]
412            menubar: Default::default(),
413            #[cfg(muda)]
414            context_menu: Default::default(),
415            #[cfg(all(muda, target_os = "macos"))]
416            muda_enable_default_menu_bar,
417            window_icon_cache_key: Default::default(),
418        });
419
420        self_rc.shared_backend_data.register_inactive_window((self_rc.clone()) as _);
421
422        self_rc
423    }
424
425    pub(crate) fn renderer(&self) -> &dyn WinitCompatibleRenderer {
426        self.renderer.as_ref()
427    }
428
429    pub fn ensure_window(
430        &self,
431        active_event_loop: &ActiveEventLoop,
432    ) -> Result<Arc<winit::window::Window>, PlatformError> {
433        #[allow(unused_mut)]
434        let mut window_attributes = match &*self.winit_window_or_none.borrow() {
435            WinitWindowOrNone::HasWindow { window, .. } => return Ok(window.clone()),
436            WinitWindowOrNone::None(attributes) => attributes.borrow().clone(),
437        };
438
439        #[cfg(all(unix, not(target_vendor = "apple")))]
440        {
441            if let Some(xdg_app_id) = WindowInner::from_pub(self.window()).xdg_app_id() {
442                #[cfg(feature = "wayland")]
443                {
444                    use winit::platform::wayland::WindowAttributesExtWayland;
445                    window_attributes = window_attributes.with_name(xdg_app_id.clone(), "");
446                }
447                #[cfg(feature = "x11")]
448                {
449                    use winit::platform::x11::WindowAttributesExtX11;
450                    window_attributes = window_attributes.with_name(xdg_app_id.clone(), "");
451                }
452            }
453        }
454
455        // Never show the window right away, as we
456        //  a) need to compute the correct size based on the scale factor before it's shown on the screen (handled by set_visible)
457        //  b) need to create the accesskit adapter before it's shown on the screen, as required by accesskit.
458        let show_after_creation = std::mem::replace(&mut window_attributes.visible, false);
459        let resizable = window_attributes.resizable;
460
461        let overriding_scale_factor = std::env::var("SLINT_SCALE_FACTOR")
462            .ok()
463            .and_then(|x| x.parse::<f32>().ok())
464            .filter(|f| *f > 0.);
465
466        if let Some(sf) = overriding_scale_factor {
467            apply_scale_factor_to_logical_sizes_in_attributes(&mut window_attributes, sf as f64)
468        }
469
470        // Work around issue with menu bar appearing translucent in fullscreen (#8793)
471        #[cfg(all(muda, target_os = "windows"))]
472        if self.menubar.borrow().is_some() {
473            window_attributes = window_attributes.with_transparent(false);
474        }
475
476        let winit_window = self.renderer.resume(active_event_loop, window_attributes)?;
477
478        // Push the host shell's color scheme and accent color to the SlintContext.
479        // With `xdg_desktop_settings` the backend-wide portal watcher (spawned in
480        // `Backend::bind_context`) is responsible for that; we only echo the
481        // current scheme to this fresh winit window so its CSDs render correctly.
482        // Otherwise winit exposes the system Light/Dark setting directly on the
483        // new window, and the OS-specific query yields the accent color.
484        cfg_if::cfg_if! {
485            if #[cfg(xdg_desktop_settings)] {
486                let scheme = WindowInner::from_pub(self.window()).context().color_scheme(None);
487                winit_window.set_theme(match scheme {
488                    ColorScheme::Dark => Some(winit::window::Theme::Dark),
489                    ColorScheme::Light => Some(winit::window::Theme::Light),
490                    ColorScheme::Unknown => None,
491                    _ => None,
492                });
493            } else {
494                let initial_scheme = winit_window.theme().map_or(ColorScheme::Unknown, |theme| match theme {
495                    winit::window::Theme::Dark => ColorScheme::Dark,
496                    winit::window::Theme::Light => ColorScheme::Light,
497                });
498                self.set_color_scheme(initial_scheme);
499                #[cfg(target_os = "macos")]
500                self.setup_macos_color_observer();
501                self.set_accent_color(Self::query_system_accent_color());
502            }
503        }
504
505        let scale_factor =
506            overriding_scale_factor.unwrap_or_else(|| winit_window.scale_factor() as f32);
507        self.window().try_dispatch_event(WindowEvent::ScaleFactorChanged { scale_factor })?;
508
509        #[cfg(target_os = "ios")]
510        let (content_view, keyboard_curve_self) = {
511            use objc2::Message as _;
512            use raw_window_handle::HasWindowHandle as _;
513
514            let raw_window_handle::RawWindowHandle::UiKit(window_handle) =
515                winit_window.window_handle().unwrap().as_raw()
516            else {
517                panic!()
518            };
519            let view = unsafe { &*(window_handle.ui_view.as_ptr() as *const objc2_ui_kit::UIView) }
520                .retain();
521            (view, self.self_weak.clone())
522        };
523
524        // winit doesn't surface iOS appearance, so query the view's trait
525        // collection directly; the matching live observers are installed below as
526        // part of the `HasWindow` variant so their lifetime is tied to the window.
527        #[cfg(target_os = "ios")]
528        {
529            self.set_color_scheme(crate::ios::current_color_scheme(&content_view));
530            self.set_platform_default_font_size(crate::ios::current_default_font_size(
531                &content_view,
532            ));
533        }
534
535        let frame_throttle = crate::frame_throttle::create_frame_throttle(
536            self.self_weak.clone(),
537            &winit_window,
538            self.shared_backend_data.is_wayland,
539        );
540
541        *self.winit_window_or_none.borrow_mut() = WinitWindowOrNone::HasWindow {
542            window: winit_window.clone(),
543            frame_throttle,
544            #[cfg(enable_accesskit)]
545            accesskit_adapter: crate::accesskit::AccessKitAdapter::new(
546                self.self_weak.clone(),
547                active_event_loop,
548                &winit_window,
549                self.event_loop_proxy.clone(),
550            )
551            .into(),
552            #[cfg(muda)]
553            muda_adapter: RefCell::new(None),
554            #[cfg(muda)]
555            context_menu_muda_adapter: None.into(),
556            #[cfg(target_os = "ios")]
557            keyboard_curve_sampler: super::ios::KeyboardCurveSampler::new(
558                &content_view,
559                move |rect| {
560                    if let Some(this) = keyboard_curve_self.upgrade() {
561                        use i_slint_core::api::{LogicalPosition, LogicalSize};
562
563                        this.window().set_virtual_keyboard(
564                            LogicalPosition::new(rect.origin.x as _, rect.origin.y as _),
565                            LogicalSize::new(rect.size.width as _, rect.size.height as _),
566                            i_slint_core::InternalToken,
567                        );
568                    }
569                },
570            ),
571            #[cfg(target_os = "ios")]
572            _color_scheme_observer: crate::ios::install_color_scheme_observer(
573                &content_view,
574                self.self_weak.clone(),
575            ),
576            #[cfg(target_os = "ios")]
577            _font_size_observer: crate::ios::install_font_size_observer(
578                &content_view,
579                self.self_weak.clone(),
580            ),
581        };
582
583        #[cfg(muda)]
584        {
585            let new_muda_adapter = self.menubar.borrow().as_ref().map(|menubar| {
586                crate::muda::MudaAdapter::setup(
587                    menubar,
588                    &winit_window,
589                    self.event_loop_proxy.clone(),
590                    self.self_weak.clone(),
591                )
592            });
593            match &*self.winit_window_or_none.borrow() {
594                WinitWindowOrNone::HasWindow { muda_adapter, .. } => {
595                    *muda_adapter.borrow_mut() = new_muda_adapter;
596                }
597                WinitWindowOrNone::None(_) => {
598                    // During muda menubar creation the winit window was destroyed again? Well then...
599                    // there's nothing to do for us :)
600                }
601            }
602        }
603
604        if show_after_creation {
605            self.shown.set(WindowVisibility::Hidden);
606            self.set_visibility(WindowVisibility::ShownFirstTime)?;
607        }
608
609        {
610            // Workaround for winit bug #2990
611            // Non-resizable windows can still contain a maximize button,
612            // so we'd have to additionally remove the button.
613            let mut buttons = winit_window.enabled_buttons();
614            buttons.set(WindowButtons::MAXIMIZE, resizable);
615            winit_window.set_enabled_buttons(buttons);
616        }
617
618        self.shared_backend_data
619            .register_window(winit_window.id(), (self.self_weak.upgrade().unwrap()) as _);
620
621        for waker in self.window_existence_wakers.take().into_iter() {
622            waker.wake();
623        }
624
625        Ok(winit_window)
626    }
627
628    pub(crate) fn suspend(&self) -> Result<(), PlatformError> {
629        let mut winit_window_or_none = self.winit_window_or_none.borrow_mut();
630        match *winit_window_or_none {
631            WinitWindowOrNone::HasWindow { ref window, .. } => {
632                self.renderer().suspend()?;
633
634                let last_window_rc = window.clone();
635
636                let mut attributes = Self::window_attributes().unwrap_or_default();
637                attributes.inner_size = Some(physical_size_to_winit(self.size.get()).into());
638                attributes.position = last_window_rc.outer_position().ok().map(|pos| pos.into());
639                *winit_window_or_none = WinitWindowOrNone::None(attributes.into());
640
641                if let Some(last_instance) = Arc::into_inner(last_window_rc) {
642                    // Note: Don't register the window in inactive_windows for re-creation later, as creating the window
643                    // on wayland implies making it visible. Unfortunately, winit won't allow creating a window on wayland
644                    // that's not visible.
645                    self.shared_backend_data.unregister_window(Some(last_instance.id()));
646                    drop(last_instance);
647                } else {
648                    i_slint_core::debug_log!(
649                        "Slint winit backend: request to hide window failed because references to the window still exist. This could be an application issue, make sure that there are no slint::WindowHandle instances left"
650                    );
651                }
652            }
653            WinitWindowOrNone::None(ref attributes) => {
654                attributes.borrow_mut().visible = false;
655            }
656        }
657
658        Ok(())
659    }
660
661    pub(crate) fn window_attributes() -> Result<WindowAttributes, PlatformError> {
662        let mut attrs = WindowAttributes::default().with_transparent(true).with_visible(false);
663
664        attrs = attrs.with_title("Slint Window".to_string());
665
666        #[cfg(target_arch = "wasm32")]
667        {
668            use winit::platform::web::WindowAttributesExtWebSys;
669
670            use wasm_bindgen::JsCast;
671
672            if let Some(html_canvas) = web_sys::window()
673                .ok_or_else(|| "winit backend: Could not retrieve DOM window".to_string())?
674                .document()
675                .ok_or_else(|| "winit backend: Could not retrieve DOM document".to_string())?
676                .get_element_by_id("canvas")
677                .and_then(|canvas_elem| canvas_elem.dyn_into::<web_sys::HtmlCanvasElement>().ok())
678            {
679                attrs = attrs
680                    .with_canvas(Some(html_canvas))
681                    // Don't activate the window by default, as that will cause the page to scroll,
682                    // ignoring any existing anchors.
683                    .with_active(false);
684            }
685        };
686
687        Ok(attrs)
688    }
689
690    /// Draw the items of the specified `component` in the given window.
691    pub fn draw(&self) -> Result<(), PlatformError> {
692        if matches!(self.shown.get(), WindowVisibility::Hidden) {
693            return Ok(()); // caller bug, doesn't make sense to call draw() when not shown
694        }
695
696        self.pending_redraw.set(false);
697
698        if let Some(winit_window) = self.winit_window_or_none.borrow().as_window() {
699            // on macOS we sometimes don't get a resize event after calling
700            // request_inner_size(), it returning None (promising a resize event), and then delivering RedrawRequested. To work around this,
701            // catch up here to ensure the renderer can resize the surface correctly.
702            // Note: On displays with a scale factor != 1, we get a scale factor change
703            // event and a resize event, so all is good.
704            if self.pending_resize_event_after_show.take() {
705                self.resize_event(winit_window.surface_size())?;
706            }
707        }
708
709        let renderer = self.renderer();
710        if !matches!(renderer.render(self.window())?, DrawOutcome::Success) {
711            // Frame was skipped (e.g. surface occluded). pending_redraw was already
712            // cleared above, so re-arm it so we try again.
713            self.request_redraw();
714        }
715
716        Ok(())
717    }
718
719    pub fn winit_window(&self) -> Option<Arc<winit::window::Window>> {
720        self.winit_window_or_none.borrow().as_window()
721    }
722
723    #[cfg(target_os = "ios")]
724    pub(crate) fn with_keyboard_curve_sampler<R>(
725        &self,
726        f: impl FnOnce(&super::ios::KeyboardCurveSampler) -> R,
727    ) -> Option<R> {
728        let winit_window_or_none = self.winit_window_or_none.borrow();
729        if let WinitWindowOrNone::HasWindow { keyboard_curve_sampler, .. } = &*winit_window_or_none
730        {
731            Some(f(keyboard_curve_sampler))
732        } else {
733            None
734        }
735    }
736
737    #[cfg(muda)]
738    pub fn rebuild_menubar(&self) {
739        let WinitWindowOrNone::HasWindow {
740            window: winit_window,
741            muda_adapter: maybe_muda_adapter,
742            ..
743        } = &*self.winit_window_or_none.borrow()
744        else {
745            return;
746        };
747        let mut maybe_muda_adapter = maybe_muda_adapter.borrow_mut();
748        let Some(muda_adapter) = maybe_muda_adapter.as_mut() else { return };
749        muda_adapter.rebuild_menu(winit_window, self.menubar.borrow().as_ref(), MudaType::Menubar);
750    }
751
752    #[cfg(muda)]
753    pub fn muda_event(&self, entry_id: usize, muda_type: MudaType) {
754        let Ok(maybe_muda_adapter) = std::cell::Ref::filter_map(
755            self.winit_window_or_none.borrow(),
756            |winit_window_or_none| match (winit_window_or_none, muda_type) {
757                (WinitWindowOrNone::HasWindow { muda_adapter, .. }, MudaType::Menubar) => {
758                    Some(muda_adapter)
759                }
760                (
761                    WinitWindowOrNone::HasWindow { context_menu_muda_adapter, .. },
762                    MudaType::Context,
763                ) => Some(context_menu_muda_adapter),
764                (WinitWindowOrNone::None(..), _) => None,
765            },
766        ) else {
767            return;
768        };
769        let maybe_muda_adapter = maybe_muda_adapter.borrow();
770        let Some(muda_adapter) = maybe_muda_adapter.as_ref() else { return };
771        let menu = match muda_type {
772            MudaType::Menubar => &self.menubar,
773            MudaType::Context => &self.context_menu,
774        };
775        let menu = menu.borrow();
776        let Some(menu) = menu.as_ref() else { return };
777        muda_adapter.invoke(menu, entry_id);
778    }
779
780    #[cfg(target_arch = "wasm32")]
781    pub fn input_method_focused(&self) -> bool {
782        match self.virtual_keyboard_helper.try_borrow() {
783            Ok(vkh) => vkh.as_ref().map_or(false, |h| h.has_focus()),
784            // the only location in which the virtual_keyboard_helper is mutably borrowed is from
785            // show_virtual_keyboard, which means we have the focus
786            Err(_) => true,
787        }
788    }
789
790    #[cfg(not(target_arch = "wasm32"))]
791    pub fn input_method_focused(&self) -> bool {
792        false
793    }
794
795    // Requests for the window to be resized. Returns true if the window was resized immediately,
796    // or if it will be resized later (false).
797    fn resize_window(&self, size: winit::dpi::Size) -> Result<bool, PlatformError> {
798        match &*self.winit_window_or_none.borrow() {
799            WinitWindowOrNone::HasWindow { window, .. } => {
800                if let Some(size) = window.request_inner_size(size) {
801                    // On wayland we might not get a WindowEvent::Resized, so resize the EGL surface right away.
802                    self.resize_event(size)?;
803                    Ok(true)
804                } else {
805                    self.pending_requested_size.set(size.into());
806                    // None means that we'll get a `WindowEvent::Resized` later
807                    Ok(false)
808                }
809            }
810            WinitWindowOrNone::None(attributes) => {
811                let scale_factor = self.window().scale_factor() as _;
812                // Avoid storing the physical size in the attributes. When creating a new window, we don't know the scale
813                // factor, so we've computed the desired size based on a factor of 1 and provided the physical size
814                // will be wrong when the window is created. So stick to a logical size.
815                attributes.borrow_mut().inner_size =
816                    Some(size.to_logical::<f64>(scale_factor).into());
817                self.resize_event(size.to_physical(scale_factor))?;
818                Ok(true)
819            }
820        }
821    }
822
823    pub fn resize_event(&self, size: winit::dpi::PhysicalSize<u32>) -> Result<(), PlatformError> {
824        self.pending_resize_event_after_show.set(false);
825        // When a window is minimized on Windows, we get a move event to an off-screen position
826        // and a resize even with a zero size. Don't forward that, especially not to the renderer,
827        // which might panic when trying to create a zero-sized surface.
828        if size.width > 0 && size.height > 0 {
829            let physical_size = physical_size_to_slint(&size);
830            self.size.set(physical_size);
831            self.pending_requested_size.set(None);
832            let scale_factor = WindowInner::from_pub(self.window()).scale_factor();
833
834            let size = physical_size.to_logical(scale_factor);
835            self.window().try_dispatch_event(WindowEvent::Resized { size })?;
836
837            WindowInner::from_pub(self.window())
838                .set_window_item_safe_area(self.safe_area_inset().to_logical(scale_factor));
839
840            // Workaround fox winit not sync'ing CSS size of the canvas (the size shown on the browser)
841            // with the width/height attribute (the size of the viewport/GL surface)
842            // If they're not in sync, the UI would be shown as scaled
843            #[cfg(target_arch = "wasm32")]
844            if let Some(html_canvas) = self
845                .winit_window_or_none
846                .borrow()
847                .as_window()
848                .and_then(|winit_window| winit_window.canvas())
849            {
850                html_canvas.set_width(physical_size.width);
851                html_canvas.set_height(physical_size.height);
852            }
853        }
854        Ok(())
855    }
856
857    pub fn set_accent_color(&self, color: Color) {
858        WindowInner::from_pub(self.window()).context().set_accent_color(color);
859    }
860
861    fn query_system_accent_color() -> Color {
862        cfg_if::cfg_if! {
863            if #[cfg(target_os = "windows")] {
864                use windows::Win32::Graphics::{
865                    Dwm::DwmGetColorizationColor,
866                    Gdi::{GetSysColor, COLOR_HIGHLIGHT},
867                };
868
869                let mut argb = 0u32;
870                let mut _opaque_blend = windows::core::BOOL::default();
871                if unsafe { DwmGetColorizationColor(&mut argb, &mut _opaque_blend) }.is_ok() {
872                    let a = ((argb >> 24) & 0xFF) as u8;
873                    let r = ((argb >> 16) & 0xFF) as u8;
874                    let g = ((argb >> 8) & 0xFF) as u8;
875                    let b = (argb & 0xFF) as u8;
876                    return Color::from_argb_u8(a, r, g, b);
877                }
878
879                let colorref = unsafe { GetSysColor(COLOR_HIGHLIGHT) };
880                let r = (colorref & 0xFF) as u8;
881                let g = ((colorref >> 8) & 0xFF) as u8;
882                let b = ((colorref >> 16) & 0xFF) as u8;
883                Color::from_argb_u8(255, r, g, b)
884            } else if #[cfg(target_os = "macos")] {
885                use objc2_app_kit::{NSColor, NSColorType};
886                let color = NSColor::controlAccentColor();
887                color.colorUsingType(NSColorType::ComponentBased).map(|c| {
888                    let r = c.redComponent() as f32;
889                    let g = c.greenComponent() as f32;
890                    let b = c.blueComponent() as f32;
891                    let a = c.alphaComponent() as f32;
892                    Color::from_argb_f32(a, r, g, b)
893                }).unwrap_or_default()
894            } else {
895                // Linux: set by XDG settings watcher; other platforms: not available
896                Color::default()
897            }
898        }
899    }
900
901    /// Re-query the system accent color. Called on theme changes.
902    pub fn update_accent_color(&self) {
903        let color = Self::query_system_accent_color();
904        if color != Color::default() {
905            self.set_accent_color(color);
906        }
907    }
908
909    pub fn set_color_scheme(&self, scheme: ColorScheme) {
910        WindowInner::from_pub(self.window()).context().set_color_scheme(scheme);
911
912        // Update the menubar theme
913        #[cfg(target_os = "windows")]
914        if let WinitWindowOrNone::HasWindow {
915            window: winit_window,
916            muda_adapter: maybe_muda_adapter,
917            ..
918        } = &*self.winit_window_or_none.borrow()
919        {
920            if let Some(muda_adapter) = maybe_muda_adapter.borrow().as_ref() {
921                muda_adapter.set_menubar_theme(&winit_window, scheme);
922            };
923        }
924
925        // Inform winit about the selected color theme, so that the window decoration is drawn correctly.
926        #[cfg(xdg_desktop_settings)]
927        if let Some(winit_window) = self.winit_window() {
928            winit_window.set_theme(match scheme {
929                ColorScheme::Unknown => None,
930                ColorScheme::Dark => Some(winit::window::Theme::Dark),
931                ColorScheme::Light => Some(winit::window::Theme::Light),
932                _ => None,
933            });
934        }
935    }
936
937    #[cfg(target_os = "ios")]
938    pub fn set_platform_default_font_size(&self, size: i_slint_core::lengths::LogicalLength) {
939        WindowInner::from_pub(self.window()).context().set_platform_default_font_size(Some(size));
940    }
941
942    pub fn window_state_event(&self) {
943        let Some(winit_window) = self.winit_window_or_none.borrow().as_window() else { return };
944
945        if let Some(minimized) = winit_window.is_minimized() {
946            self.minimized.set(minimized);
947            if minimized != self.window().is_minimized() {
948                self.window().set_minimized(minimized);
949            }
950        }
951
952        // The method winit::Window::is_maximized returns false when the window
953        // is minimized, even if it was previously maximized. We have to ensure
954        // that we only update the internal maximized state when the window is
955        // not minimized. Otherwise, the window would be restored in a
956        // non-maximized state even if it was maximized before being minimized.
957        let maximized = winit_window.is_maximized();
958        if !self.window().is_minimized() {
959            self.maximized.set(maximized);
960            if maximized != self.window().is_maximized() {
961                self.window().set_maximized(maximized);
962            }
963        }
964
965        // NOTE: Fullscreen overrides maximized so if both are true then the
966        // window will remain in fullscreen. Fullscreen must be false to switch
967        // to maximized.
968        let fullscreen = winit_window.fullscreen().is_some();
969        if fullscreen != self.window().is_fullscreen() {
970            self.window().set_fullscreen(fullscreen);
971        }
972    }
973
974    #[cfg(enable_accesskit)]
975    pub(crate) fn accesskit_adapter(
976        &self,
977    ) -> Option<std::cell::Ref<'_, RefCell<crate::accesskit::AccessKitAdapter>>> {
978        std::cell::Ref::filter_map(
979            self.winit_window_or_none.try_borrow().ok()?,
980            |wor: &WinitWindowOrNone| match wor {
981                WinitWindowOrNone::HasWindow { accesskit_adapter, .. } => Some(accesskit_adapter),
982                WinitWindowOrNone::None(..) => None,
983            },
984        )
985        .ok()
986    }
987
988    #[cfg(enable_accesskit)]
989    pub(crate) fn with_access_kit_adapter_from_weak_window_adapter(
990        self_weak: Weak<Self>,
991        callback: impl FnOnce(&RefCell<crate::accesskit::AccessKitAdapter>),
992    ) {
993        let Some(self_) = self_weak.upgrade() else { return };
994        let winit_window_or_none = self_.winit_window_or_none.borrow();
995        match &*winit_window_or_none {
996            WinitWindowOrNone::HasWindow { accesskit_adapter, .. } => callback(accesskit_adapter),
997            WinitWindowOrNone::None(..) => {}
998        }
999    }
1000
1001    /// Register an observer for macOS system color changes so that
1002    /// the accent color updates live when the user changes it in System Settings.
1003    #[cfg(target_os = "macos")]
1004    fn setup_macos_color_observer(&self) {
1005        let self_weak = self.self_weak.clone();
1006        let block =
1007            block2::RcBlock::new(move |_: core::ptr::NonNull<objc2_foundation::NSNotification>| {
1008                if let Some(adapter) = self_weak.upgrade() {
1009                    adapter.update_accent_color();
1010                }
1011            });
1012        let observer = unsafe {
1013            objc2_foundation::NSNotificationCenter::defaultCenter()
1014                .addObserverForName_object_queue_usingBlock(
1015                    Some(objc2_app_kit::NSSystemColorsDidChangeNotification),
1016                    None,
1017                    None,
1018                    &block,
1019                )
1020        };
1021        let _ = self.macos_color_observer.set(observer);
1022    }
1023
1024    pub fn activation_changed(&self, is_active: bool) -> Result<(), PlatformError> {
1025        let have_focus = is_active || self.input_method_focused();
1026        let slint_window = self.window();
1027        let runtime_window = WindowInner::from_pub(slint_window);
1028        // We don't render popups as separate windows yet, so treat
1029        // focus to be the same as being active.
1030        if have_focus != runtime_window.active() {
1031            slint_window.try_dispatch_event(
1032                corelib::platform::WindowEvent::WindowActiveChanged(have_focus),
1033            )?;
1034        }
1035
1036        #[cfg(all(muda, target_os = "macos"))]
1037        {
1038            if let WinitWindowOrNone::HasWindow { muda_adapter, .. } =
1039                &*self.winit_window_or_none.borrow()
1040            {
1041                if muda_adapter.borrow().is_none()
1042                    && self.muda_enable_default_menu_bar
1043                    && self.menubar.borrow().is_none()
1044                {
1045                    *muda_adapter.borrow_mut() =
1046                        Some(crate::muda::MudaAdapter::setup_default_menu_bar()?);
1047                }
1048
1049                if let Some(muda_adapter) = muda_adapter.borrow().as_ref() {
1050                    muda_adapter.window_activation_changed(is_active);
1051                }
1052            }
1053        }
1054
1055        Ok(())
1056    }
1057
1058    fn set_visibility(&self, visibility: WindowVisibility) -> Result<(), PlatformError> {
1059        if visibility == self.shown.get() {
1060            return Ok(());
1061        }
1062
1063        self.shown.set(visibility);
1064        self.pending_resize_event_after_show.set(!matches!(visibility, WindowVisibility::Hidden));
1065        self.pending_redraw.set(false);
1066        if matches!(visibility, WindowVisibility::ShownFirstTime | WindowVisibility::Shown) {
1067            let recreating_window = matches!(visibility, WindowVisibility::Shown);
1068
1069            let Some(winit_window) = self.winit_window() else {
1070                // Can't really show it on the screen, safe it in the attributes and try again later
1071                // by registering it for activation when we can.
1072                self.winit_window_or_none.borrow().set_visible(true);
1073                self.shared_backend_data
1074                    .register_inactive_window((self.self_weak.upgrade().unwrap()) as _);
1075                return Ok(());
1076            };
1077
1078            let runtime_window = WindowInner::from_pub(self.window());
1079
1080            let scale_factor = runtime_window.scale_factor() as f64;
1081
1082            let component_rc = runtime_window.component();
1083            let component = ItemTreeRc::borrow_pin(&component_rc);
1084
1085            let layout_info_h = component.as_ref().layout_info(Orientation::Horizontal);
1086            if let Some(window_item) = runtime_window.window_item() {
1087                // Setting the width to its preferred size before querying the vertical layout info
1088                // is important in case the height depends on the width
1089                window_item.width.set(LogicalLength::new(layout_info_h.preferred_bounded()));
1090            }
1091            let layout_info_v = component.as_ref().layout_info(Orientation::Vertical);
1092            #[allow(unused_mut)]
1093            let mut preferred_size = winit::dpi::LogicalSize::new(
1094                layout_info_h.preferred_bounded(),
1095                layout_info_v.preferred_bounded(),
1096            );
1097
1098            #[cfg(target_arch = "wasm32")]
1099            if let Some(html_canvas) = winit_window.canvas() {
1100                // Try to maintain the existing size of the canvas element, if any
1101                if !is_preferred_sized_canvas(&html_canvas)
1102                    && !canvas_has_explicit_size_set(&html_canvas)
1103                {
1104                    let existing_canvas_size = winit::dpi::LogicalSize::new(
1105                        html_canvas.client_width() as f32,
1106                        html_canvas.client_height() as f32,
1107                    );
1108                    preferred_size.width = existing_canvas_size.width;
1109
1110                    preferred_size.height = existing_canvas_size.height;
1111                }
1112            }
1113
1114            if winit_window.fullscreen().is_none()
1115                && !self.has_explicit_size.get()
1116                && preferred_size.width > 0 as Coord
1117                && preferred_size.height > 0 as Coord
1118                // Don't set the preferred size as the user may have resized the window
1119                && !recreating_window
1120            {
1121                // use the Slint's window Scale factor to take in account the override
1122                let size = preferred_size.to_physical::<u32>(scale_factor);
1123                self.resize_window(size.into())?;
1124            };
1125
1126            // Pre-render the first frame before mapping the window to avoid
1127            // a flash of uninitialized VRAM on X11 (no background_pixmap).
1128            if matches!(visibility, WindowVisibility::ShownFirstTime) {
1129                let _ = self.draw();
1130            }
1131
1132            winit_window.set_visible(true);
1133
1134            // Refresh the SlintContext color-scheme now that the window is mapped: on some platforms
1135            // `winit_window.theme()` only reports a real value once the window is shown.
1136            if let Some(theme) = winit_window.theme() {
1137                self.set_color_scheme(match theme {
1138                    winit::window::Theme::Dark => ColorScheme::Dark,
1139                    winit::window::Theme::Light => ColorScheme::Light,
1140                });
1141            }
1142
1143            // In wasm a request_redraw() issued before show() results in a draw() even when the window
1144            // isn't visible, as opposed to regular windowing systems. The compensate for the lost draw,
1145            // explicitly render the first frame on show().
1146            #[cfg(target_arch = "wasm32")]
1147            if self.pending_redraw.get() {
1148                self.draw()?;
1149            };
1150
1151            // On iOS making an already-created window visible doesn't generate a fresh
1152            // RedrawRequested. winit's one initial RedrawRequested is delivered while the window is
1153            // created (during `resumed`), so a window first shown later misses it and stays blank.
1154            #[cfg(ios_and_friends)]
1155            self.request_redraw();
1156
1157            Ok(())
1158        } else {
1159            // Wayland doesn't support hiding a window, only destroying it entirely.
1160            if self.winit_window_or_none.borrow().as_window().is_some_and(|winit_window| {
1161                use raw_window_handle::HasWindowHandle;
1162                winit_window.window_handle().is_ok_and(|h| {
1163                    matches!(h.as_raw(), raw_window_handle::RawWindowHandle::Wayland(..))
1164                }) || std::env::var_os("SLINT_DESTROY_WINDOW_ON_HIDE").is_some()
1165            }) {
1166                self.suspend()?;
1167                // Note: Don't register the window in inactive_windows for re-creation later, as creating the window
1168                // on wayland implies making it visible. Unfortunately, winit won't allow creating a window on wayland
1169                // that's not visible.
1170            } else {
1171                self.winit_window_or_none.borrow().set_visible(false);
1172            }
1173
1174            /* FIXME:
1175            if let Some(existing_blinker) = self.cursor_blinker.borrow().upgrade() {
1176                existing_blinker.stop();
1177            }*/
1178            Ok(())
1179        }
1180    }
1181
1182    pub(crate) fn visibility(&self) -> WindowVisibility {
1183        self.shown.get()
1184    }
1185
1186    pub(crate) fn pending_redraw(&self) -> bool {
1187        self.pending_redraw.get()
1188    }
1189
1190    pub async fn async_winit_window(
1191        self_weak: Weak<Self>,
1192    ) -> Result<Arc<winit::window::Window>, PlatformError> {
1193        std::future::poll_fn(move |context| {
1194            let Some(self_) = self_weak.upgrade() else {
1195                return std::task::Poll::Ready(Err(
1196                    "Unable to obtain winit window from destroyed window".to_string().into(),
1197                ));
1198            };
1199            match self_.winit_window() {
1200                Some(window) => std::task::Poll::Ready(Ok(window)),
1201                None => {
1202                    let waker = context.waker();
1203                    if !self_.window_existence_wakers.borrow().iter().any(|w| w.will_wake(waker)) {
1204                        self_.window_existence_wakers.borrow_mut().push(waker.clone());
1205                    }
1206                    std::task::Poll::Pending
1207                }
1208            }
1209        })
1210        .await
1211    }
1212}
1213
1214impl WindowAdapter for WinitWindowAdapter {
1215    fn window(&self) -> &corelib::api::Window {
1216        &self.window
1217    }
1218
1219    fn renderer(&self) -> &dyn i_slint_core::renderer::Renderer {
1220        self.renderer().as_core_renderer()
1221    }
1222
1223    fn set_visible(&self, visible: bool) -> Result<(), PlatformError> {
1224        self.set_visibility(if visible {
1225            WindowVisibility::Shown
1226        } else {
1227            WindowVisibility::Hidden
1228        })
1229    }
1230
1231    fn position(&self) -> Option<corelib::api::PhysicalPosition> {
1232        match &*self.winit_window_or_none.borrow() {
1233            WinitWindowOrNone::HasWindow { window, .. } => match window.outer_position() {
1234                Ok(outer_position) => {
1235                    Some(corelib::api::PhysicalPosition::new(outer_position.x, outer_position.y))
1236                }
1237                Err(_) => None,
1238            },
1239            WinitWindowOrNone::None(attributes) => {
1240                attributes.borrow().position.map(|pos| {
1241                    match pos {
1242                        winit::dpi::Position::Physical(phys_pos) => {
1243                            corelib::api::PhysicalPosition::new(phys_pos.x, phys_pos.y)
1244                        }
1245                        winit::dpi::Position::Logical(logical_pos) => {
1246                            // Best effort: Use the last known scale factor
1247                            corelib::api::LogicalPosition::new(
1248                                logical_pos.x as _,
1249                                logical_pos.y as _,
1250                            )
1251                            .to_physical(self.window().scale_factor())
1252                        }
1253                    }
1254                })
1255            }
1256        }
1257    }
1258
1259    fn set_position(&self, position: corelib::api::WindowPosition) {
1260        let winit_pos = position_to_winit(&position);
1261        match &*self.winit_window_or_none.borrow() {
1262            WinitWindowOrNone::HasWindow { window, .. } => window.set_outer_position(winit_pos),
1263            WinitWindowOrNone::None(attributes) => {
1264                attributes.borrow_mut().position = Some(winit_pos);
1265            }
1266        }
1267    }
1268
1269    fn set_size(&self, size: corelib::api::WindowSize) {
1270        self.has_explicit_size.set(true);
1271        // TODO: don't ignore error, propagate to caller
1272        self.resize_window(window_size_to_winit(&size)).ok();
1273    }
1274
1275    fn size(&self) -> corelib::api::PhysicalSize {
1276        self.size.get()
1277    }
1278
1279    fn request_redraw(&self) {
1280        if !self.pending_redraw.replace(true)
1281            && let WinitWindowOrNone::HasWindow { window, frame_throttle, .. } =
1282                &*self.winit_window_or_none.borrow()
1283        {
1284            frame_throttle.request_throttled_redraw(window);
1285        }
1286    }
1287
1288    #[allow(clippy::unnecessary_cast)] // Coord is used!
1289    fn update_window_properties(&self, properties: corelib::window::WindowProperties<'_>) {
1290        let Some(window_item) = WindowInner::from_pub(&self.window).window_item() else {
1291            return;
1292        };
1293        let window_item = window_item.as_pin_ref();
1294
1295        let winit_window_or_none = self.winit_window_or_none.borrow();
1296
1297        // Use our scale factor instead of winit's logical size to take a scale factor override into account.
1298        let sf = self.window().scale_factor();
1299
1300        // Update the icon only if it changes, to avoid flashing.
1301        let icon_image = window_item.icon();
1302        let icon_image_cache_key = ImageCacheKey::new((&icon_image).into());
1303        if *self.window_icon_cache_key.borrow() != icon_image_cache_key {
1304            *self.window_icon_cache_key.borrow_mut() = icon_image_cache_key;
1305            winit_window_or_none.set_window_icon(icon_to_winit(
1306                icon_image,
1307                i_slint_core::lengths::LogicalSize::new(64., 64.) * ScaleFactor::new(sf),
1308            ));
1309        }
1310        winit_window_or_none.set_title(&properties.title());
1311        winit_window_or_none.set_decorations(
1312            !window_item.no_frame() || winit_window_or_none.fullscreen().is_some(),
1313        );
1314
1315        let new_window_level = if window_item.always_on_top() {
1316            winit::window::WindowLevel::AlwaysOnTop
1317        } else {
1318            winit::window::WindowLevel::Normal
1319        };
1320        // Only change the window level if it changes, to avoid https://github.com/slint-ui/slint/issues/3280
1321        // (Ubuntu 20.04's window manager always bringing the window to the front on x11)
1322        if self.window_level.replace(new_window_level) != new_window_level {
1323            winit_window_or_none.set_window_level(new_window_level);
1324        }
1325
1326        let mut width = window_item.width().get() as f32;
1327        let mut height = window_item.height().get() as f32;
1328        let mut must_resize = false;
1329        let existing_size = self.size.get().to_logical(sf);
1330
1331        if width <= 0. || height <= 0. {
1332            must_resize = true;
1333            if width <= 0. {
1334                width = existing_size.width;
1335            }
1336            if height <= 0. {
1337                height = existing_size.height;
1338            }
1339        }
1340
1341        // Adjust the size of the window to the value of the width and height property (if these property are changed from .slint).
1342        // But not if there is a pending resize in flight as that resize will reset these properties back
1343        if ((existing_size.width - width).abs() > 1. || (existing_size.height - height).abs() > 1.)
1344            && self.pending_requested_size.get().is_none()
1345        {
1346            // If we're in fullscreen state, don't try to resize the window but maintain the surface
1347            // size we've been assigned to from the windowing system. Weston/Wayland don't like it
1348            // when we create a surface that's bigger than the screen due to constraints (#532).
1349            if winit_window_or_none.fullscreen().is_none() {
1350                // TODO: don't ignore error, propagate to caller
1351                let immediately_resized = self
1352                    .resize_window(winit::dpi::LogicalSize::new(width, height).into())
1353                    .unwrap_or_default();
1354                if immediately_resized {
1355                    // The resize event was already dispatched
1356                    must_resize = false;
1357                }
1358            }
1359        }
1360
1361        if must_resize {
1362            self.window()
1363                .try_dispatch_event(WindowEvent::Resized {
1364                    size: i_slint_core::api::LogicalSize::new(width, height),
1365                })
1366                .unwrap();
1367            WindowInner::from_pub(self.window())
1368                .set_window_item_safe_area(window_item.safe_area_insets());
1369        }
1370
1371        let m = properties.is_fullscreen();
1372        if m != self.fullscreen.get() {
1373            if m {
1374                if winit_window_or_none.fullscreen().is_none() {
1375                    winit_window_or_none
1376                        .set_fullscreen(Some(winit::window::Fullscreen::Borderless(None)));
1377                }
1378            } else {
1379                winit_window_or_none.set_fullscreen(None);
1380            }
1381            self.fullscreen.set(m);
1382        }
1383
1384        let m = properties.is_maximized();
1385        if m != self.maximized.get() {
1386            self.maximized.set(m);
1387            winit_window_or_none.set_maximized(m);
1388        }
1389
1390        let m = properties.is_minimized();
1391        if m != self.minimized.get() {
1392            self.minimized.set(m);
1393            winit_window_or_none.set_minimized(m);
1394        }
1395
1396        // If we're in fullscreen, don't try to resize the window but
1397        // maintain the surface size we've been assigned to from the
1398        // windowing system. Weston/Wayland don't like it when we create a
1399        // surface that's bigger than the screen due to constraints (#532).
1400        if winit_window_or_none.fullscreen().is_some() {
1401            return;
1402        }
1403
1404        let new_constraints = properties.layout_constraints();
1405        if new_constraints == self.constraints.get() {
1406            return;
1407        }
1408
1409        self.constraints.set(new_constraints);
1410
1411        let resizable = window_is_resizable(new_constraints.min, new_constraints.max);
1412        // we must call set_resizable before setting the min and max size otherwise setting the min and max size don't work on X11
1413        winit_window_or_none.set_resizable(resizable);
1414        // Important: Filter out (temporary?) zero width/heights, to avoid attempting to create a zero surface. For example, with wayland
1415        // the client-side rendering ends up passing a zero width/height to the renderer.
1416        let winit_min_inner =
1417            new_constraints.min.map(logical_size_to_winit).map(filter_out_zero_width_or_height);
1418        winit_window_or_none.set_min_inner_size(winit_min_inner, sf as f64);
1419        let winit_max_inner =
1420            new_constraints.max.map(logical_size_to_winit).map(filter_out_zero_width_or_height);
1421        winit_window_or_none.set_max_inner_size(winit_max_inner, sf as f64);
1422
1423        // On ios, etc. apps are fullscreen and need to be responsive.
1424        #[cfg(not(ios_and_friends))]
1425        adjust_window_size_to_satisfy_constraints(self, winit_min_inner, winit_max_inner);
1426
1427        // Auto-resize to the preferred size if users (SlintPad) requests it
1428        #[cfg(target_arch = "wasm32")]
1429        if let Some(canvas) =
1430            winit_window_or_none.as_window().and_then(|winit_window| winit_window.canvas())
1431        {
1432            if is_preferred_sized_canvas(&canvas) {
1433                let pref = new_constraints.preferred;
1434                if pref.width > 0 as Coord || pref.height > 0 as Coord {
1435                    // TODO: don't ignore error, propagate to caller
1436                    self.resize_window(logical_size_to_winit(pref).into()).ok();
1437                };
1438            }
1439        }
1440    }
1441
1442    fn internal(&self, _: corelib::InternalToken) -> Option<&dyn WindowAdapterInternal> {
1443        Some(self)
1444    }
1445}
1446
1447impl WindowAdapterInternal for WinitWindowAdapter {
1448    fn set_mouse_cursor(&self, cursor: MouseCursor) {
1449        let winit_cursor = match cursor {
1450            MouseCursor::Default => winit::window::CursorIcon::Default,
1451            MouseCursor::None => winit::window::CursorIcon::Default,
1452            MouseCursor::Help => winit::window::CursorIcon::Help,
1453            MouseCursor::Pointer => winit::window::CursorIcon::Pointer,
1454            MouseCursor::Progress => winit::window::CursorIcon::Progress,
1455            MouseCursor::Wait => winit::window::CursorIcon::Wait,
1456            MouseCursor::Crosshair => winit::window::CursorIcon::Crosshair,
1457            MouseCursor::Text => winit::window::CursorIcon::Text,
1458            MouseCursor::Alias => winit::window::CursorIcon::Alias,
1459            MouseCursor::Copy => winit::window::CursorIcon::Copy,
1460            MouseCursor::Move => winit::window::CursorIcon::Move,
1461            MouseCursor::NoDrop => winit::window::CursorIcon::NoDrop,
1462            MouseCursor::NotAllowed => winit::window::CursorIcon::NotAllowed,
1463            MouseCursor::Grab => winit::window::CursorIcon::Grab,
1464            MouseCursor::Grabbing => winit::window::CursorIcon::Grabbing,
1465            MouseCursor::ColResize => winit::window::CursorIcon::ColResize,
1466            MouseCursor::RowResize => winit::window::CursorIcon::RowResize,
1467            MouseCursor::NResize => winit::window::CursorIcon::NResize,
1468            MouseCursor::EResize => winit::window::CursorIcon::EResize,
1469            MouseCursor::SResize => winit::window::CursorIcon::SResize,
1470            MouseCursor::WResize => winit::window::CursorIcon::WResize,
1471            MouseCursor::NeResize => winit::window::CursorIcon::NeResize,
1472            MouseCursor::NwResize => winit::window::CursorIcon::NwResize,
1473            MouseCursor::SeResize => winit::window::CursorIcon::SeResize,
1474            MouseCursor::SwResize => winit::window::CursorIcon::SwResize,
1475            MouseCursor::EwResize => winit::window::CursorIcon::EwResize,
1476            MouseCursor::NsResize => winit::window::CursorIcon::NsResize,
1477            MouseCursor::NeswResize => winit::window::CursorIcon::NeswResize,
1478            MouseCursor::NwseResize => winit::window::CursorIcon::NwseResize,
1479            _ => winit::window::CursorIcon::Default,
1480        };
1481        if let Some(winit_window) = self.winit_window_or_none.borrow().as_window() {
1482            winit_window.set_cursor_visible(cursor != MouseCursor::None);
1483            winit_window.set_cursor(winit_cursor);
1484        }
1485    }
1486
1487    fn input_method_request(&self, request: corelib::window::InputMethodRequest) {
1488        #[cfg(not(target_arch = "wasm32"))]
1489        if let Some(winit_window) = self.winit_window_or_none.borrow().as_window() {
1490            let props = match &request {
1491                corelib::window::InputMethodRequest::Enable(props) => {
1492                    winit_window.set_ime_allowed(true);
1493                    props
1494                }
1495                corelib::window::InputMethodRequest::Disable => {
1496                    return winit_window.set_ime_allowed(false);
1497                }
1498                corelib::window::InputMethodRequest::Update(props) => props,
1499                _ => return,
1500            };
1501            winit_window.set_ime_purpose(match props.input_type {
1502                corelib::items::InputType::Password => winit::window::ImePurpose::Password,
1503                corelib::items::InputType::Text
1504                | corelib::items::InputType::Number
1505                | corelib::items::InputType::Decimal
1506                | corelib::items::InputType::Search
1507                | _ => winit::window::ImePurpose::Normal,
1508            });
1509            winit_window.set_ime_cursor_area(
1510                position_to_winit(&props.cursor_rect_origin.into()),
1511                window_size_to_winit(&props.cursor_rect_size.into()),
1512            );
1513        }
1514
1515        #[cfg(target_arch = "wasm32")]
1516        match request {
1517            corelib::window::InputMethodRequest::Enable(..) => {
1518                let mut vkh = self.virtual_keyboard_helper.borrow_mut();
1519                let Some(canvas) =
1520                    self.winit_window().and_then(|winit_window| winit_window.canvas())
1521                else {
1522                    return;
1523                };
1524                let h = vkh.get_or_insert_with(|| {
1525                    super::wasm_input_helper::WasmInputHelper::new(self.self_weak.clone(), canvas)
1526                });
1527                h.show();
1528            }
1529            corelib::window::InputMethodRequest::Disable => {
1530                if let Some(h) = &*self.virtual_keyboard_helper.borrow() {
1531                    h.hide()
1532                }
1533            }
1534            _ => {}
1535        };
1536    }
1537
1538    #[cfg(muda)]
1539    fn supports_native_menu_bar(&self) -> bool {
1540        true
1541    }
1542
1543    #[cfg(muda)]
1544    fn setup_menubar(&self, menubar: vtable::VRc<i_slint_core::menus::MenuVTable>) {
1545        self.menubar.replace(Some(menubar));
1546
1547        if let WinitWindowOrNone::HasWindow { muda_adapter, .. } =
1548            &*self.winit_window_or_none.borrow()
1549        {
1550            // On Windows, we must destroy the muda menu before re-creating a new one
1551            drop(muda_adapter.borrow_mut().take());
1552            muda_adapter.replace(Some(crate::muda::MudaAdapter::setup(
1553                self.menubar.borrow().as_ref().unwrap(),
1554                &self.winit_window().unwrap(),
1555                self.event_loop_proxy.clone(),
1556                self.self_weak.clone(),
1557            )));
1558        }
1559    }
1560
1561    #[cfg(muda)]
1562    fn show_native_popup_menu(
1563        &self,
1564        context_menu_item: vtable::VRc<i_slint_core::menus::MenuVTable>,
1565        position: LogicalPosition,
1566    ) -> bool {
1567        self.context_menu.replace(Some(context_menu_item));
1568
1569        if let WinitWindowOrNone::HasWindow { context_menu_muda_adapter, .. } =
1570            &*self.winit_window_or_none.borrow()
1571        {
1572            // On Windows, we must destroy the muda menu before re-creating a new one
1573            drop(context_menu_muda_adapter.borrow_mut().take());
1574            if let Some(new_adapter) = crate::muda::MudaAdapter::show_context_menu(
1575                self.context_menu.borrow().as_ref().unwrap(),
1576                &self.winit_window().unwrap(),
1577                position,
1578                self.event_loop_proxy.clone(),
1579            ) {
1580                context_menu_muda_adapter.replace(Some(new_adapter));
1581                return true;
1582            }
1583        }
1584        false
1585    }
1586
1587    #[cfg(enable_accesskit)]
1588    fn handle_focus_change(&self, _old: Option<ItemRc>, _new: Option<ItemRc>) {
1589        let Some(accesskit_adapter_cell) = self.accesskit_adapter() else { return };
1590        accesskit_adapter_cell.borrow_mut().handle_focus_item_change();
1591    }
1592
1593    #[cfg(enable_accesskit)]
1594    fn register_item_tree(&self, _: ItemTreeRefPin) {
1595        let Some(accesskit_adapter_cell) = self.accesskit_adapter() else { return };
1596        // If the accesskit_adapter is already borrowed, this means the new items were created when the tree was built and there is no need to re-visit them
1597        if let Ok(mut a) = accesskit_adapter_cell.try_borrow_mut() {
1598            a.reload_tree();
1599        };
1600    }
1601
1602    #[cfg(enable_accesskit)]
1603    fn unregister_item_tree(
1604        &self,
1605        component: ItemTreeRef,
1606        _: &mut dyn Iterator<Item = core::pin::Pin<ItemRef<'_>>>,
1607    ) {
1608        let Some(accesskit_adapter_cell) = self.accesskit_adapter() else { return };
1609        if let Ok(mut a) = accesskit_adapter_cell.try_borrow_mut() {
1610            a.unregister_item_tree(component);
1611        };
1612    }
1613
1614    #[cfg(feature = "raw-window-handle-06")]
1615    fn window_handle_06_rc(
1616        &self,
1617    ) -> Result<Arc<dyn raw_window_handle::HasWindowHandle>, raw_window_handle::HandleError> {
1618        Ok(self
1619            .winit_window_or_none
1620            .borrow()
1621            .as_window()
1622            .ok_or(raw_window_handle::HandleError::Unavailable)?)
1623    }
1624
1625    #[cfg(feature = "raw-window-handle-06")]
1626    fn display_handle_06_rc(
1627        &self,
1628    ) -> Result<Arc<dyn raw_window_handle::HasDisplayHandle>, raw_window_handle::HandleError> {
1629        Ok(self
1630            .winit_window_or_none
1631            .borrow()
1632            .as_window()
1633            .ok_or(raw_window_handle::HandleError::Unavailable)?)
1634    }
1635
1636    fn bring_to_front(&self) -> Result<(), PlatformError> {
1637        if let Some(winit_window) = self.winit_window_or_none.borrow().as_window() {
1638            winit_window.set_minimized(false);
1639            winit_window.focus_window();
1640        }
1641        Ok(())
1642    }
1643
1644    #[cfg(target_os = "ios")]
1645    fn safe_area_inset(&self) -> i_slint_core::lengths::PhysicalEdges {
1646        self.winit_window_or_none
1647            .borrow()
1648            .as_window()
1649            .and_then(|window| {
1650                let outer_position = window.outer_position().ok()?;
1651                let inner_position = window.inner_position().ok()?;
1652                let outer_size = window.outer_size();
1653                let inner_size = window.inner_size();
1654                Some(i_slint_core::lengths::PhysicalEdges::new(
1655                    inner_position.y - outer_position.y,
1656                    outer_size.height as i32
1657                        - (inner_size.height as i32)
1658                        - (inner_position.y - outer_position.y),
1659                    inner_position.x - outer_position.x,
1660                    outer_size.width as i32
1661                        - (inner_size.width as i32)
1662                        - (inner_position.x - outer_position.x),
1663                ))
1664            })
1665            .unwrap_or_default()
1666    }
1667}
1668
1669impl Drop for WinitWindowAdapter {
1670    fn drop(&mut self) {
1671        self.shared_backend_data.unregister_window(
1672            self.winit_window_or_none.borrow().as_window().map(|winit_window| winit_window.id()),
1673        );
1674
1675        #[cfg(target_os = "macos")]
1676        if let Some(observer) = self.macos_color_observer.get() {
1677            unsafe {
1678                objc2_foundation::NSNotificationCenter::defaultCenter()
1679                    .removeObserver((*observer).as_ref());
1680            }
1681        }
1682    }
1683}
1684
1685// Winit doesn't automatically resize the window to satisfy constraints. Qt does it though, and so do we here.
1686#[cfg(not(ios_and_friends))]
1687fn adjust_window_size_to_satisfy_constraints(
1688    adapter: &WinitWindowAdapter,
1689    min_size: Option<winit::dpi::LogicalSize<f64>>,
1690    max_size: Option<winit::dpi::LogicalSize<f64>>,
1691) {
1692    let sf = adapter.window().scale_factor() as f64;
1693    let current_size = adapter
1694        .pending_requested_size
1695        .get()
1696        .unwrap_or_else(|| {
1697            let existing_adapter_size = adapter.size.get();
1698            physical_size_to_winit(existing_adapter_size).into()
1699        })
1700        .to_logical::<f64>(sf);
1701
1702    let mut window_size = current_size;
1703    if let Some(min_size) = min_size {
1704        let min_size = min_size.cast();
1705        window_size.width = window_size.width.max(min_size.width);
1706        window_size.height = window_size.height.max(min_size.height);
1707    }
1708
1709    if let Some(max_size) = max_size {
1710        let max_size = max_size.cast();
1711        window_size.width = window_size.width.min(max_size.width);
1712        window_size.height = window_size.height.min(max_size.height);
1713    }
1714
1715    if window_size != current_size {
1716        // TODO: don't ignore error, propagate to caller
1717        adapter.resize_window(window_size.into()).ok();
1718    }
1719}
1720
1721#[cfg(target_family = "wasm")]
1722fn is_preferred_sized_canvas(canvas: &web_sys::HtmlCanvasElement) -> bool {
1723    canvas
1724        .dataset()
1725        .get("slintAutoResizeToPreferred")
1726        .and_then(|val_str| val_str.parse::<bool>().ok())
1727        .unwrap_or_default()
1728}
1729
1730#[cfg(target_family = "wasm")]
1731fn canvas_has_explicit_size_set(canvas: &web_sys::HtmlCanvasElement) -> bool {
1732    let style = canvas.style();
1733    if !style.get_property_value("width").unwrap_or_default().is_empty()
1734        || !style.get_property_value("height").unwrap_or_default().is_empty()
1735    {
1736        return true;
1737    }
1738
1739    let Some(window) = web_sys::window() else {
1740        return false;
1741    };
1742    let Some(computed_style) = window.get_computed_style(&canvas).ok().flatten() else {
1743        return false;
1744    };
1745
1746    computed_style.get_property_value("width").ok().as_deref() != Some("auto")
1747        || computed_style.get_property_value("height").ok().as_deref() != Some("auto")
1748}