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