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