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