Skip to main content

i_slint_backend_winit/
winitwindowadapter.rs

1// Copyright © SixtyFPS GmbH <info@slint.dev>
2// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0
3
4//! This module contains the GraphicsWindow that used to be within corelib.
5
6// cspell:ignore accesskit borderless corelib nesw webgl winit winsys xlib
7
8use core::cell::{Cell, RefCell};
9#[cfg(target_os = "macos")]
10use std::cell::OnceCell;
11use std::rc::Rc;
12use std::rc::Weak;
13use std::sync::Arc;
14
15use euclid::approxeq::ApproxEq;
16
17#[cfg(muda)]
18use i_slint_core::api::LogicalPosition;
19use i_slint_core::cursor::{MouseCursorInner, scaled_hotspot};
20use i_slint_core::lengths::{PhysicalPx, ScaleFactor};
21#[cfg(muda)]
22use i_slint_core::menus::MenuVTable;
23use i_slint_core::renderer::DrawOutcome;
24use winit::event_loop::ActiveEventLoop;
25#[cfg(target_arch = "wasm32")]
26use winit::platform::web::WindowExtWebSys;
27#[cfg(target_family = "windows")]
28use winit::platform::windows::WindowExtWindows;
29
30use crate::drag_resize_window::{handle_cursor_move_for_resize, handle_resize};
31#[cfg(muda)]
32use crate::muda::MudaType;
33use crate::renderer::WinitCompatibleRenderer;
34use crate::winit_compat::WindowSurfaceSizeExt;
35
36use corelib::SharedString;
37use corelib::input::{BackendMouseEvent, InternalKeyEvent, KeyEvent, KeyEventType};
38use corelib::item_tree::ItemTreeRc;
39#[cfg(enable_accesskit)]
40use corelib::item_tree::{ItemTreeRef, ItemTreeRefPin};
41use corelib::items::{BuiltInMouseCursor, ColorScheme, PointerEventButton};
42#[cfg(enable_accesskit)]
43use corelib::items::{ItemRc, ItemRef};
44
45#[cfg(any(enable_accesskit, muda))]
46use crate::SlintEvent;
47use crate::{EventResult, SharedBackendData};
48use corelib::api::PhysicalSize;
49use corelib::layout::Orientation;
50use corelib::lengths::{LogicalLength, LogicalPoint};
51use corelib::platform::{PlatformError, WindowEvent};
52use corelib::window::{WindowAdapter, WindowAdapterInternal, WindowInner};
53use corelib::{Coord, graphics::*};
54use i_slint_core::{self as corelib};
55use winit::event::WindowEvent as WinitWindowEvent;
56#[cfg(any(enable_accesskit, muda))]
57use winit::event_loop::EventLoopProxy;
58use winit::keyboard::Key;
59use winit::window::{
60    CustomCursor, CustomCursorSource, ResizeDirection, WindowAttributes, WindowButtons,
61};
62
63fn winit_touch_phase(phase: winit::event::TouchPhase) -> corelib::input::TouchPhase {
64    match phase {
65        winit::event::TouchPhase::Started => corelib::input::TouchPhase::Started,
66        winit::event::TouchPhase::Moved => corelib::input::TouchPhase::Moved,
67        winit::event::TouchPhase::Ended => corelib::input::TouchPhase::Ended,
68        winit::event::TouchPhase::Cancelled => corelib::input::TouchPhase::Cancelled,
69    }
70}
71
72pub(crate) fn position_to_winit(pos: &corelib::api::WindowPosition) -> winit::dpi::Position {
73    match pos {
74        corelib::api::WindowPosition::Logical(pos) => {
75            winit::dpi::Position::new(winit::dpi::LogicalPosition::new(pos.x, pos.y))
76        }
77        corelib::api::WindowPosition::Physical(pos) => {
78            winit::dpi::Position::new(winit::dpi::PhysicalPosition::new(pos.x, pos.y))
79        }
80    }
81}
82
83fn window_size_to_winit(size: &corelib::api::WindowSize) -> winit::dpi::Size {
84    match size {
85        corelib::api::WindowSize::Logical(size) => {
86            winit::dpi::Size::new(logical_size_to_winit(*size))
87        }
88        corelib::api::WindowSize::Physical(size) => {
89            winit::dpi::Size::new(physical_size_to_winit(*size))
90        }
91    }
92}
93
94pub fn physical_size_to_slint(size: &winit::dpi::PhysicalSize<u32>) -> corelib::api::PhysicalSize {
95    corelib::api::PhysicalSize::new(size.width, size.height)
96}
97
98fn logical_size_to_winit(s: i_slint_core::api::LogicalSize) -> winit::dpi::LogicalSize<f64> {
99    winit::dpi::LogicalSize::new(s.width as f64, s.height as f64)
100}
101
102fn physical_size_to_winit(size: PhysicalSize) -> winit::dpi::PhysicalSize<u32> {
103    winit::dpi::PhysicalSize::new(size.width, size.height)
104}
105
106fn filter_out_zero_width_or_height(
107    size: winit::dpi::LogicalSize<f64>,
108) -> winit::dpi::LogicalSize<f64> {
109    fn filter(v: f64) -> f64 {
110        if v.approx_eq(&0.) {
111            // Some width or height is better than zero
112            10.
113        } else {
114            v
115        }
116    }
117    winit::dpi::LogicalSize { width: filter(size.width), height: filter(size.height) }
118}
119
120/// The smallest integer logical size whose physical size is at least
121/// `logical * scale_factor`.
122///
123/// A requested logical size materializes as `round(logical * scale_factor)`
124/// physical pixels: winit converts logical sizes that way on every platform,
125/// and Wayland only accepts integer logical sizes in the first place. With a
126/// fractional scale factor the rounding can land below
127/// `logical * scale_factor`, making the window slightly smaller than requested
128/// and cutting off content measured to fit exactly.
129fn round_up_logical(logical: f64, scale_factor: f64) -> f64 {
130    // For positive x, round(x) = floor(x + 0.5), so the physical size reaches
131    // the target integer ceil(target) once result * scale_factor >= ceil(target) - 0.5.
132    let target = logical * scale_factor;
133    let result = logical.ceil().max(((target.ceil() - 0.5) / scale_factor).ceil());
134    // The division can land an ulp below the exact quotient, making the outer
135    // ceil() undershoot by one; verify against the actual rounding.
136    if (result * scale_factor).round() < target { result + 1. } else { result }
137}
138
139#[test]
140fn test_round_up_logical() {
141    assert_eq!(round_up_logical(228., 1.), 228.);
142    // 228 * 1.3 = 296.4 rounds down to 296 = 227.7 logical; 229 * 1.3 = 297.7
143    // rounds to 298 = 229.2 logical.
144    assert_eq!(round_up_logical(228., 1.3), 229.);
145    // 227.5 * 2 is exact after the ceil.
146    assert_eq!(round_up_logical(227.5, 2.), 228.);
147    // 12 * 0.2 = 2.4 rounds down to 2 physical; 13 * 0.2 = 2.6 rounds to 3.
148    assert_eq!(round_up_logical(12., 0.2), 13.);
149    // 21..=24 all round to 2 physical at scale 0.1; 25 * 0.1 = 2.5 rounds to 3.
150    assert_eq!(round_up_logical(21., 0.1), 25.);
151}
152
153/// Whether the platform assigns the window its size, so requesting one is pointless.
154///
155/// On iOS and friends the window covers whatever the system hands it, and winit's UIKit
156/// backend ignores resize requests but turns an initial size into the UIWindow's frame,
157/// confining the app to a corner of the screen.
158fn platform_dictates_window_size() -> bool {
159    cfg!(ios_and_friends)
160}
161
162fn apply_scale_factor_to_logical_sizes_in_attributes(
163    attributes: &mut WindowAttributes,
164    scale_factor: f64,
165) {
166    let fixup = |maybe_size: &mut Option<winit::dpi::Size>| {
167        if let Some(size) = maybe_size.as_mut() {
168            *size = winit::dpi::Size::Physical(size.to_physical::<u32>(scale_factor))
169        }
170    };
171
172    fixup(&mut attributes.inner_size);
173    fixup(&mut attributes.min_inner_size);
174    fixup(&mut attributes.max_inner_size);
175    fixup(&mut attributes.resize_increments);
176}
177
178fn icon_to_winit(
179    icon: corelib::graphics::Image,
180    size: euclid::Size2D<Coord, PhysicalPx>,
181) -> Option<winit::window::Icon> {
182    let image_inner: &ImageInner = (&icon).into();
183
184    let pixel_buffer = image_inner.render_to_buffer(Some(size.cast()))?;
185
186    // This could become a method in SharedPixelBuffer...
187    let rgba_pixels: Vec<u8> = match &pixel_buffer {
188        SharedImageBuffer::RGB8(pixels) => pixels
189            .as_bytes()
190            .chunks(3)
191            .flat_map(|rgb| IntoIterator::into_iter([rgb[0], rgb[1], rgb[2], 255]))
192            .collect(),
193        SharedImageBuffer::RGBA8(pixels) => pixels.as_bytes().to_vec(),
194        SharedImageBuffer::RGBA8Premultiplied(pixels) => pixels
195            .as_bytes()
196            .chunks(4)
197            .flat_map(|rgba| {
198                let alpha = rgba[3] as u32;
199                IntoIterator::into_iter(rgba)
200                    .take(3)
201                    .map(move |component| (*component as u32 * alpha / 255) as u8)
202                    .chain(std::iter::once(alpha as u8))
203            })
204            .collect(),
205    };
206
207    winit::window::Icon::from_rgba(rgba_pixels, pixel_buffer.width(), pixel_buffer.height()).ok()
208}
209
210fn window_is_resizable(
211    min_size: Option<corelib::api::LogicalSize>,
212    max_size: Option<corelib::api::LogicalSize>,
213) -> bool {
214    if let Some((
215        corelib::api::LogicalSize { width: min_width, height: min_height, .. },
216        corelib::api::LogicalSize { width: max_width, height: max_height, .. },
217    )) = min_size.zip(max_size)
218    {
219        min_width < max_width || min_height < max_height
220    } else {
221        true
222    }
223}
224
225#[allow(clippy::large_enum_variant)]
226enum WinitWindowOrNone {
227    HasWindow {
228        window: Arc<winit::window::Window>,
229        frame_throttle: Box<dyn crate::frame_throttle::FrameThrottle>,
230        #[cfg(enable_accesskit)]
231        accesskit_adapter: RefCell<crate::accesskit::AccessKitAdapter>,
232        #[cfg(muda)]
233        muda_adapter: RefCell<Option<crate::muda::MudaAdapter>>,
234        #[cfg(muda)]
235        context_menu_muda_adapter: RefCell<Option<crate::muda::MudaAdapter>>,
236        #[cfg(target_os = "ios")]
237        keyboard_curve_sampler: super::ios::KeyboardCurveSampler,
238        #[cfg(target_os = "ios")]
239        _color_scheme_observer: Option<super::ios::TraitChangeObserver>,
240        #[cfg(target_os = "ios")]
241        _font_size_observer: Option<super::ios::TraitChangeObserver>,
242    },
243    None(RefCell<WindowAttributes>),
244}
245
246impl WinitWindowOrNone {
247    fn as_window(&self) -> Option<Arc<winit::window::Window>> {
248        match self {
249            Self::HasWindow { window, .. } => Some(window.clone()),
250            Self::None { .. } => None,
251        }
252    }
253
254    fn set_window_icon(&self, icon: Option<winit::window::Icon>) {
255        match self {
256            Self::HasWindow { window, .. } => {
257                #[cfg(target_family = "windows")]
258                window.set_taskbar_icon(icon.as_ref().cloned());
259                window.set_window_icon(icon);
260            }
261            Self::None(attributes) => attributes.borrow_mut().window_icon = icon,
262        }
263    }
264
265    fn set_title(&self, title: &str) {
266        match self {
267            Self::HasWindow { window, .. } => window.set_title(title),
268            Self::None(attributes) => attributes.borrow_mut().title = title.into(),
269        }
270    }
271
272    fn set_decorations(&self, decorations: bool) {
273        match self {
274            Self::HasWindow { window, .. } => window.set_decorations(decorations),
275            Self::None(attributes) => attributes.borrow_mut().decorations = decorations,
276        }
277    }
278
279    fn fullscreen(&self) -> Option<winit::window::Fullscreen> {
280        match self {
281            Self::HasWindow { window, .. } => window.fullscreen(),
282            Self::None(attributes) => attributes.borrow().fullscreen.clone(),
283        }
284    }
285
286    fn set_fullscreen(&self, fullscreen: Option<winit::window::Fullscreen>) {
287        match self {
288            Self::HasWindow { window, .. } => window.set_fullscreen(fullscreen),
289            Self::None(attributes) => attributes.borrow_mut().fullscreen = fullscreen,
290        }
291    }
292
293    fn set_window_level(&self, level: winit::window::WindowLevel) {
294        match self {
295            Self::HasWindow { window, .. } => window.set_window_level(level),
296            Self::None(attributes) => attributes.borrow_mut().window_level = level,
297        }
298    }
299
300    fn set_visible(&self, visible: bool) {
301        match self {
302            Self::HasWindow { window, .. } => window.set_visible(visible),
303            Self::None(attributes) => attributes.borrow_mut().visible = visible,
304        }
305    }
306
307    fn set_maximized(&self, maximized: bool) {
308        match self {
309            Self::HasWindow { window, .. } => window.set_maximized(maximized),
310            Self::None(attributes) => attributes.borrow_mut().maximized = maximized,
311        }
312    }
313
314    fn set_minimized(&self, minimized: bool) {
315        match self {
316            Self::HasWindow { window, .. } => window.set_minimized(minimized),
317            Self::None(..) => { /* TODO: winit is missing attributes.borrow_mut().minimized = minimized*/
318            }
319        }
320    }
321
322    fn set_resizable(&self, resizable: bool) {
323        match self {
324            Self::HasWindow { window, .. } => {
325                window.set_resizable(resizable);
326            }
327            Self::None(attributes) => attributes.borrow_mut().resizable = resizable,
328        }
329    }
330
331    fn set_min_inner_size(
332        &self,
333        min_inner_size: Option<winit::dpi::LogicalSize<f64>>,
334        scale_factor: f64,
335    ) {
336        match self {
337            Self::HasWindow { window, .. } => {
338                // Store as physical size to make sure that our potentially overriding scale factor is applied.
339                window
340                    .set_min_inner_size(min_inner_size.map(|s| s.to_physical::<u32>(scale_factor)))
341            }
342            Self::None(attributes) => {
343                // Store as logical size, so that we can apply the real window scale factor later when it's known.
344                attributes.borrow_mut().min_inner_size = min_inner_size.map(|s| s.into());
345            }
346        }
347    }
348
349    fn set_max_inner_size(
350        &self,
351        max_inner_size: Option<winit::dpi::LogicalSize<f64>>,
352        scale_factor: f64,
353    ) {
354        match self {
355            Self::HasWindow { window, .. } => {
356                // Store as physical size to make sure that our potentially overriding scale factor is applied.
357                window
358                    .set_max_inner_size(max_inner_size.map(|s| s.to_physical::<u32>(scale_factor)))
359            }
360            Self::None(attributes) => {
361                // Store as logical size, so that we can apply the real window scale factor later when it's known.
362                attributes.borrow_mut().max_inner_size = max_inner_size.map(|s| s.into())
363            }
364        }
365    }
366}
367
368#[derive(Default, PartialEq, Clone, Copy)]
369pub(crate) enum WindowVisibility {
370    #[default]
371    Hidden,
372    /// This implies that we might resize the window the first time it's shown.
373    ShownFirstTime,
374    Shown,
375}
376
377/// GraphicsWindow is an implementation of the [WindowAdapter][`crate::eventloop::WindowAdapter`] trait. This is
378/// typically instantiated by entry factory functions of the different graphics back ends.
379pub struct WinitWindowAdapter {
380    pub shared_backend_data: Rc<SharedBackendData>,
381    window: corelib::api::Window,
382    pub(crate) self_weak: Weak<Self>,
383    pending_redraw: Cell<bool>,
384    constraints: Cell<corelib::window::LayoutConstraints>,
385    /// Indicates if the window is shown, from the perspective of the API user.
386    shown: Cell<WindowVisibility>,
387    window_level: Cell<winit::window::WindowLevel>,
388    maximized: Cell<bool>,
389    minimized: Cell<bool>,
390    fullscreen: Cell<bool>,
391    /// Mirrors the transparency the live window was given, so that a property update only
392    /// reaches the NSWindow when the value actually changes.
393    #[cfg(target_os = "macos")]
394    transparent: Cell<bool>,
395
396    pub(crate) renderer: Box<dyn WinitCompatibleRenderer>,
397    /// We cache the size because winit_window.inner_size() can return different value between calls (eg, on X11)
398    /// And we wan see the newer value before the Resized event was received, leading to inconsistencies
399    size: Cell<PhysicalSize>,
400    /// We requested a size to be set, but we didn't get the resize event from winit yet
401    pending_requested_size: Cell<Option<winit::dpi::Size>>,
402    /// A physical size requested before the window exists. Winit resolves it against the
403    /// scale factor it knows at creation, which on Wayland is 1 until the window is mapped,
404    /// so the size is applied again when the scale factor changes. A newer request or a
405    /// resize to another size drops it.
406    physical_size_before_scale_factor: Cell<Option<winit::dpi::PhysicalSize<u32>>>,
407
408    /// Whether the size has been set explicitly via `set_size`.
409    /// If that's the case, we should't resize to the preferred size in set_visible
410    has_explicit_size: Cell<bool>,
411
412    /// Indicate whether we've ever received a resize event from winit after showing the window.
413    pending_resize_event_after_show: Cell<bool>,
414
415    /// Whether the current winit window has presented a frame.
416    first_frame_presented: Cell<bool>,
417
418    #[cfg(target_arch = "wasm32")]
419    virtual_keyboard_helper: RefCell<Option<super::wasm_input_helper::WasmInputHelper>>,
420
421    /// Set while a shown window waits for its first frame, see [`crate::macos::RevealOnFirstFrame`].
422    #[cfg(target_os = "macos")]
423    reveal_on_first_frame: RefCell<Option<crate::macos::RevealOnFirstFrame>>,
424
425    #[cfg(any(enable_accesskit, muda))]
426    event_loop_proxy: EventLoopProxy<SlintEvent>,
427
428    pub(crate) window_event_filter: Cell<
429        Option<Box<dyn FnMut(&corelib::api::Window, &winit::event::WindowEvent) -> EventResult>>,
430    >,
431
432    winit_window_or_none: RefCell<WinitWindowOrNone>,
433    window_existence_wakers: RefCell<Vec<core::task::Waker>>,
434
435    #[cfg(target_os = "macos")]
436    macos_color_observer: OnceCell<
437        objc2::rc::Retained<objc2::runtime::ProtocolObject<dyn objc2::runtime::NSObjectProtocol>>,
438    >,
439
440    // The component owns the menu item tree, which reaches this adapter through the globals. Holding
441    // it weakly here keeps the adapter out of that ownership cycle.
442    #[cfg(muda)]
443    menubar_weak: RefCell<Option<vtable::VWeak<MenuVTable>>>,
444
445    #[cfg(muda)]
446    context_menu: RefCell<Option<vtable::VRc<MenuVTable>>>,
447
448    #[cfg(all(muda, target_os = "macos"))]
449    muda_enable_default_menu_bar: bool,
450
451    /// Winit's window_icon API has no way of checking if the window icon is
452    /// the same as a previously set one, so keep track of that here.
453    window_icon_cache_key: RefCell<Option<ImageCacheKey>>,
454
455    custom_cursor_source: Cell<Option<CustomCursorSource>>,
456
457    /// Last seen cursor position.
458    cursor_pos: Cell<LogicalPoint>,
459    /// Whether a *mouse* button is currently pressed. Touch input is handled
460    /// separately via `process_touch_input` and does not affect this flag.
461    pressed: Cell<bool>,
462    current_resize_direction: Cell<Option<ResizeDirection>>,
463    /// Allocates small i32 finger ids for winit's per-device u64 touch ids.
464    touch_finger_ids: RefCell<crate::touch_finger_id::TouchFingerIdAllocator>,
465}
466
467impl WinitWindowAdapter {
468    /// Creates a new reference-counted instance.
469    pub(crate) fn new(
470        shared_backend_data: Rc<SharedBackendData>,
471        renderer: Box<dyn WinitCompatibleRenderer>,
472        window_attributes: winit::window::WindowAttributes,
473        #[cfg(any(enable_accesskit, muda))] proxy: EventLoopProxy<SlintEvent>,
474        #[cfg(all(muda, target_os = "macos"))] muda_enable_default_menu_bar: bool,
475    ) -> Rc<Self> {
476        let self_rc = Rc::new_cyclic(|self_weak| Self {
477            shared_backend_data: shared_backend_data.clone(),
478            window: corelib::api::Window::new(self_weak.clone() as _),
479            self_weak: self_weak.clone(),
480            pending_redraw: Default::default(),
481            constraints: Default::default(),
482            shown: Default::default(),
483            window_level: Default::default(),
484            maximized: Cell::default(),
485            minimized: Cell::default(),
486            fullscreen: Cell::default(),
487            #[cfg(target_os = "macos")]
488            transparent: Cell::default(),
489            winit_window_or_none: RefCell::new(WinitWindowOrNone::None(window_attributes.into())),
490            window_existence_wakers: RefCell::new(Vec::default()),
491            size: Cell::default(),
492            pending_requested_size: Cell::new(None),
493            physical_size_before_scale_factor: Cell::new(None),
494            has_explicit_size: Default::default(),
495            pending_resize_event_after_show: Default::default(),
496            first_frame_presented: Default::default(),
497            renderer,
498            #[cfg(target_arch = "wasm32")]
499            virtual_keyboard_helper: Default::default(),
500            #[cfg(target_os = "macos")]
501            reveal_on_first_frame: Default::default(),
502            #[cfg(any(enable_accesskit, muda))]
503            event_loop_proxy: proxy,
504            window_event_filter: Cell::new(None),
505            #[cfg(target_os = "macos")]
506            macos_color_observer: OnceCell::new(),
507            #[cfg(muda)]
508            menubar_weak: Default::default(),
509            #[cfg(muda)]
510            context_menu: Default::default(),
511            #[cfg(all(muda, target_os = "macos"))]
512            muda_enable_default_menu_bar,
513            window_icon_cache_key: Default::default(),
514            custom_cursor_source: Cell::new(None),
515            cursor_pos: Default::default(),
516            pressed: Default::default(),
517            current_resize_direction: Default::default(),
518            touch_finger_ids: Default::default(),
519        });
520
521        self_rc.shared_backend_data.register_inactive_window((self_rc.clone()) as _);
522
523        self_rc
524    }
525
526    pub(crate) fn renderer(&self) -> &dyn WinitCompatibleRenderer {
527        self.renderer.as_ref()
528    }
529
530    /// The preferred logical size of the component, or None if it has no positive preferred size.
531    ///
532    /// The size is rounded up so that the physical window cannot end up smaller
533    /// than the component's preferred logical size (see [`round_up_logical`]):
534    /// content measured to fit it exactly (e.g. a wrapping FlexboxLayout whose
535    /// preferred width is precisely its one-line width) would get cut off.
536    fn preferred_size(&self) -> Option<winit::dpi::LogicalSize<Coord>> {
537        let runtime_window = WindowInner::from_pub(self.window());
538        let component_rc = runtime_window.try_component()?;
539        let component = ItemTreeRc::borrow_pin(&component_rc);
540        let scale_factor = runtime_window.scale_factor() as f64;
541        let layout_info_h = component.as_ref().layout_info(Orientation::Horizontal);
542        let width = round_up_logical(layout_info_h.preferred_bounded() as f64, scale_factor);
543        let layout_info_v = match runtime_window.window_item() {
544            // The height may depend on the width, so query it at the preferred width. Restore
545            // the width afterwards: it may hold a size set explicitly before the window is shown.
546            Some(window_item) => {
547                let current_width = window_item.as_pin_ref().width();
548                window_item.width.set(LogicalLength::new(width as Coord));
549                let layout_info_v = component.as_ref().layout_info(Orientation::Vertical);
550                window_item.width.set(current_width);
551                layout_info_v
552            }
553            None => component.as_ref().layout_info(Orientation::Vertical),
554        };
555        let height = round_up_logical(layout_info_v.preferred_bounded() as f64, scale_factor);
556        let size = winit::dpi::LogicalSize::new(width as Coord, height as Coord);
557        (size.width > 0 as Coord && size.height > 0 as Coord).then_some(size)
558    }
559
560    /// winit asks for a transparent window with `backgroundColor = clear`, and AppKit then
561    /// leaves the whole window frame unpainted, the native title bar included. So ask for
562    /// transparency only where it buys something: a translucent background has to blend with
563    /// what's behind the window, and a frameless one needs it so the rounded corners aren't
564    /// filled in.
565    #[cfg(target_os = "macos")]
566    fn wants_transparent(window_item: core::pin::Pin<&corelib::items::WindowItem>) -> bool {
567        !window_item.background().is_opaque() || window_item.no_frame()
568    }
569
570    pub fn ensure_window(
571        &self,
572        active_event_loop: &ActiveEventLoop,
573    ) -> Result<Arc<winit::window::Window>, PlatformError> {
574        #[allow(unused_mut)]
575        let mut window_attributes = match &*self.winit_window_or_none.borrow() {
576            WinitWindowOrNone::HasWindow { window, .. } => return Ok(window.clone()),
577            WinitWindowOrNone::None(attributes) => attributes.borrow().clone(),
578        };
579
580        #[cfg(all(unix, not(target_vendor = "apple")))]
581        {
582            if let Some(xdg_app_id) = WindowInner::from_pub(self.window()).xdg_app_id() {
583                #[cfg(feature = "wayland")]
584                {
585                    use winit::platform::wayland::WindowAttributesExtWayland;
586                    window_attributes = window_attributes.with_name(xdg_app_id.clone(), "");
587                }
588                #[cfg(feature = "x11")]
589                {
590                    use winit::platform::x11::WindowAttributesExtX11;
591                    window_attributes = window_attributes.with_name(xdg_app_id.clone(), "");
592                }
593            }
594        }
595
596        // Never show the window right away, as we
597        //  a) need to compute the correct size based on the scale factor before it's shown on the screen (handled by set_visible)
598        //  b) need to create the accesskit adapter before it's shown on the screen, as required by accesskit.
599        let show_after_creation = std::mem::replace(&mut window_attributes.visible, false);
600        let resizable = window_attributes.resizable;
601
602        let overriding_scale_factor = std::env::var("SLINT_SCALE_FACTOR")
603            .ok()
604            .and_then(|x| x.parse::<f32>().ok())
605            .filter(|f| *f > 0.);
606
607        if let Some(sf) = overriding_scale_factor {
608            apply_scale_factor_to_logical_sizes_in_attributes(&mut window_attributes, sf as f64)
609        }
610
611        // Work around issue with menu bar appearing translucent in fullscreen (#8793)
612        #[cfg(all(muda, target_os = "windows"))]
613        if self.menubar().is_some() {
614            window_attributes = window_attributes.with_transparent(false);
615        }
616
617        #[cfg(target_os = "macos")]
618        if let Some(window_item) = WindowInner::from_pub(self.window()).window_item() {
619            let transparent = Self::wants_transparent(window_item.as_pin_ref());
620            window_attributes = window_attributes.with_transparent(transparent);
621            self.transparent.set(transparent);
622        }
623
624        // Create the window at its preferred size: the renderer's surface is created together
625        // with the window, and on Wayland resizing it afterwards only takes effect after the
626        // next present, so the first frame would be rendered at the pre-show size.
627        if !platform_dictates_window_size()
628            && !self.has_explicit_size.get()
629            && window_attributes.fullscreen.is_none()
630            && let Some(preferred_size) = self.preferred_size()
631        {
632            window_attributes.inner_size = Some(preferred_size.into());
633        }
634
635        let winit_window =
636            self.renderer.resume(active_event_loop, window_attributes, self.self_weak.clone())?;
637        self.first_frame_presented.set(false);
638
639        // Push the host shell's color scheme and accent color to the SlintContext.
640        // With `xdg_desktop_settings` the backend-wide portal watcher (spawned in
641        // `Backend::bind_context`) is responsible for that; we only echo the
642        // current scheme to this fresh winit window so its CSDs render correctly.
643        // Otherwise winit exposes the system Light/Dark setting directly on the
644        // new window, and the OS-specific query yields the accent color.
645        cfg_if::cfg_if! {
646            if #[cfg(xdg_desktop_settings)] {
647                let scheme = WindowInner::from_pub(self.window()).context().color_scheme(None);
648                winit_window.set_theme(match scheme {
649                    ColorScheme::Dark => Some(winit::window::Theme::Dark),
650                    ColorScheme::Light => Some(winit::window::Theme::Light),
651                    ColorScheme::Unknown => None,
652                    _ => None,
653                });
654            } else {
655                let initial_scheme = winit_window.theme().map_or(ColorScheme::Unknown, |theme| match theme {
656                    winit::window::Theme::Dark => ColorScheme::Dark,
657                    winit::window::Theme::Light => ColorScheme::Light,
658                });
659                self.set_color_scheme(initial_scheme);
660                #[cfg(target_os = "macos")]
661                self.setup_macos_color_observer();
662                self.set_accent_color(Self::query_system_accent_color());
663            }
664        }
665
666        let scale_factor =
667            overriding_scale_factor.unwrap_or_else(|| winit_window.scale_factor() as f32);
668        self.window()
669            .dispatch_event_with_result(WindowEvent::ScaleFactorChanged { scale_factor })?;
670
671        #[cfg(target_os = "ios")]
672        let (content_view, keyboard_curve_self) = {
673            use objc2::Message as _;
674            use raw_window_handle::HasWindowHandle as _;
675
676            let raw_window_handle::RawWindowHandle::UiKit(window_handle) =
677                winit_window.window_handle().unwrap().as_raw()
678            else {
679                panic!()
680            };
681            let view = unsafe { &*(window_handle.ui_view.as_ptr() as *const objc2_ui_kit::UIView) }
682                .retain();
683            (view, self.self_weak.clone())
684        };
685
686        // A window created after UIKit connected the scene isn't covered by the
687        // scene delegate, so attach it here.
688        #[cfg(target_os = "ios")]
689        crate::ios::attach_window_to_scene(&content_view);
690
691        // winit doesn't surface iOS appearance, so query the view's trait
692        // collection directly; the matching live observers are installed below as
693        // part of the `HasWindow` variant so their lifetime is tied to the window.
694        #[cfg(target_os = "ios")]
695        {
696            self.set_color_scheme(crate::ios::current_color_scheme(&content_view));
697            self.set_platform_default_font_size(crate::ios::current_default_font_size(
698                &content_view,
699            ));
700        }
701
702        let frame_throttle = crate::frame_throttle::create_frame_throttle(
703            self.self_weak.clone(),
704            &winit_window,
705            self.shared_backend_data.is_wayland,
706        );
707
708        *self.winit_window_or_none.borrow_mut() = WinitWindowOrNone::HasWindow {
709            window: winit_window.clone(),
710            frame_throttle,
711            #[cfg(enable_accesskit)]
712            accesskit_adapter: crate::accesskit::AccessKitAdapter::new(
713                self.self_weak.clone(),
714                active_event_loop,
715                &winit_window,
716                self.event_loop_proxy.clone(),
717            )
718            .into(),
719            #[cfg(muda)]
720            muda_adapter: RefCell::new(None),
721            #[cfg(muda)]
722            context_menu_muda_adapter: None.into(),
723            #[cfg(target_os = "ios")]
724            keyboard_curve_sampler: super::ios::KeyboardCurveSampler::new(
725                &content_view,
726                move |rect| {
727                    if let Some(this) = keyboard_curve_self.upgrade() {
728                        use i_slint_core::api::{LogicalPosition, LogicalSize};
729
730                        this.window().set_virtual_keyboard(
731                            LogicalPosition::new(rect.origin.x as _, rect.origin.y as _),
732                            LogicalSize::new(rect.size.width as _, rect.size.height as _),
733                            i_slint_core::InternalToken,
734                        );
735                    }
736                },
737            ),
738            #[cfg(target_os = "ios")]
739            _color_scheme_observer: crate::ios::install_color_scheme_observer(
740                &content_view,
741                self.self_weak.clone(),
742            ),
743            #[cfg(target_os = "ios")]
744            _font_size_observer: crate::ios::install_font_size_observer(
745                &content_view,
746                self.self_weak.clone(),
747            ),
748        };
749
750        #[cfg(muda)]
751        {
752            let menubar = self.menubar();
753            let new_muda_adapter = menubar.as_ref().map(|menubar| {
754                crate::muda::MudaAdapter::setup(
755                    menubar,
756                    &winit_window,
757                    self.event_loop_proxy.clone(),
758                    self.self_weak.clone(),
759                )
760            });
761            match &*self.winit_window_or_none.borrow() {
762                WinitWindowOrNone::HasWindow { muda_adapter, .. } => {
763                    *muda_adapter.borrow_mut() = new_muda_adapter;
764                }
765                WinitWindowOrNone::None(_) => {
766                    // During muda menubar creation the winit window was destroyed again? Well then...
767                    // there's nothing to do for us :)
768                }
769            }
770        }
771
772        if show_after_creation {
773            self.shown.set(WindowVisibility::Hidden);
774            self.set_visibility(WindowVisibility::ShownFirstTime)?;
775        }
776
777        {
778            // Workaround for winit bug #2990
779            // Non-resizable windows can still contain a maximize button,
780            // so we'd have to additionally remove the button.
781            let mut buttons = winit_window.enabled_buttons();
782            buttons.set(WindowButtons::MAXIMIZE, resizable);
783            winit_window.set_enabled_buttons(buttons);
784        }
785
786        self.shared_backend_data
787            .register_window(winit_window.id(), (self.self_weak.upgrade().unwrap()) as _);
788
789        for waker in self.window_existence_wakers.take().into_iter() {
790            waker.wake();
791        }
792
793        Ok(winit_window)
794    }
795
796    pub(crate) fn suspend(&self) -> Result<(), PlatformError> {
797        let mut winit_window_or_none = self.winit_window_or_none.borrow_mut();
798        match *winit_window_or_none {
799            WinitWindowOrNone::HasWindow { ref window, .. } => {
800                self.renderer().suspend()?;
801
802                let last_window_rc = window.clone();
803
804                let mut attributes = Self::window_attributes().unwrap_or_default();
805                attributes.inner_size = Some(physical_size_to_winit(self.size.get()).into());
806                attributes.position = last_window_rc.outer_position().ok().map(|pos| pos.into());
807                *winit_window_or_none = WinitWindowOrNone::None(attributes.into());
808
809                if let Some(last_instance) = Arc::into_inner(last_window_rc) {
810                    // Note: Don't register the window in inactive_windows for re-creation later, as creating the window
811                    // on wayland implies making it visible. Unfortunately, winit won't allow creating a window on wayland
812                    // that's not visible.
813                    self.shared_backend_data.unregister_window(Some(last_instance.id()));
814                    drop(last_instance);
815                } else {
816                    i_slint_core::debug_log!(
817                        "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"
818                    );
819                }
820            }
821            WinitWindowOrNone::None(ref attributes) => {
822                attributes.borrow_mut().visible = false;
823            }
824        }
825
826        Ok(())
827    }
828
829    pub(crate) fn window_attributes() -> Result<WindowAttributes, PlatformError> {
830        let mut attrs = WindowAttributes::default().with_transparent(true).with_visible(false);
831
832        // Only until the component's own title reaches the window
833        attrs = attrs.with_title(i_slint_core::window::application_name().to_string());
834
835        #[cfg(target_arch = "wasm32")]
836        {
837            use winit::platform::web::WindowAttributesExtWebSys;
838
839            use wasm_bindgen::JsCast;
840
841            if let Some(html_canvas) = web_sys::window()
842                .ok_or_else(|| "winit backend: Could not retrieve DOM window".to_string())?
843                .document()
844                .ok_or_else(|| "winit backend: Could not retrieve DOM document".to_string())?
845                .get_element_by_id("canvas")
846                .and_then(|canvas_elem| canvas_elem.dyn_into::<web_sys::HtmlCanvasElement>().ok())
847            {
848                attrs = attrs
849                    .with_canvas(Some(html_canvas))
850                    // Don't activate the window by default, as that will cause the page to scroll,
851                    // ignoring any existing anchors.
852                    .with_active(false);
853            }
854        };
855
856        Ok(attrs)
857    }
858
859    /// Draw the items of the specified `component` in the given window.
860    pub fn draw(&self) -> Result<(), PlatformError> {
861        if matches!(self.shown.get(), WindowVisibility::Hidden) {
862            return Ok(()); // caller bug, doesn't make sense to call draw() when not shown
863        }
864
865        self.pending_redraw.set(false);
866
867        if let Some(winit_window) = self.winit_window_or_none.borrow().as_window() {
868            // on macOS we sometimes don't get a resize event after calling
869            // request_inner_size(), it returning None (promising a resize event), and then delivering RedrawRequested. To work around this,
870            // catch up here to ensure the renderer can resize the surface correctly.
871            // Note: On displays with a scale factor != 1, we get a scale factor change
872            // event and a resize event, so all is good.
873            if self.pending_resize_event_after_show.take() {
874                self.resize_event(winit_window.surface_size())?;
875            }
876        }
877
878        let renderer = self.renderer();
879        let outcome = renderer.render(self.window());
880        // A timeout or an error ends the wait as well, so that the window can't stay invisible.
881        #[cfg(target_os = "macos")]
882        if !matches!(outcome, Ok(DrawOutcome::Occluded | DrawOutcome::Skipped)) {
883            self.reveal_on_first_frame.take();
884        }
885        if matches!(outcome?, DrawOutcome::Success) {
886            self.first_frame_presented.set(true);
887        } else {
888            // Frame was skipped (e.g. surface occluded). pending_redraw was already
889            // cleared above, so re-arm it so we try again.
890            self.request_redraw();
891        }
892
893        Ok(())
894    }
895
896    pub fn winit_window(&self) -> Option<Arc<winit::window::Window>> {
897        self.winit_window_or_none.borrow().as_window()
898    }
899
900    #[cfg(target_os = "ios")]
901    pub(crate) fn with_keyboard_curve_sampler<R>(
902        &self,
903        f: impl FnOnce(&super::ios::KeyboardCurveSampler) -> R,
904    ) -> Option<R> {
905        let winit_window_or_none = self.winit_window_or_none.borrow();
906        if let WinitWindowOrNone::HasWindow { keyboard_curve_sampler, .. } = &*winit_window_or_none
907        {
908            Some(f(keyboard_curve_sampler))
909        } else {
910            None
911        }
912    }
913
914    #[cfg(muda)]
915    fn menubar(&self) -> Option<vtable::VRc<MenuVTable>> {
916        self.menubar_weak.borrow().as_ref().and_then(vtable::VWeak::upgrade)
917    }
918
919    #[cfg(muda)]
920    pub fn rebuild_menubar(&self) {
921        let WinitWindowOrNone::HasWindow {
922            window: winit_window,
923            muda_adapter: maybe_muda_adapter,
924            ..
925        } = &*self.winit_window_or_none.borrow()
926        else {
927            return;
928        };
929        let mut maybe_muda_adapter = maybe_muda_adapter.borrow_mut();
930        let Some(muda_adapter) = maybe_muda_adapter.as_mut() else { return };
931        let menubar = self.menubar();
932        muda_adapter.rebuild_menu(winit_window, menubar.as_ref(), MudaType::Menubar);
933    }
934
935    #[cfg(muda)]
936    pub fn muda_event(&self, entry_id: usize, muda_type: MudaType) {
937        let Ok(maybe_muda_adapter) = std::cell::Ref::filter_map(
938            self.winit_window_or_none.borrow(),
939            |winit_window_or_none| match (winit_window_or_none, muda_type) {
940                (WinitWindowOrNone::HasWindow { muda_adapter, .. }, MudaType::Menubar) => {
941                    Some(muda_adapter)
942                }
943                (
944                    WinitWindowOrNone::HasWindow { context_menu_muda_adapter, .. },
945                    MudaType::Context,
946                ) => Some(context_menu_muda_adapter),
947                (WinitWindowOrNone::None(..), _) => None,
948            },
949        ) else {
950            return;
951        };
952        let maybe_muda_adapter = maybe_muda_adapter.borrow();
953        let Some(muda_adapter) = maybe_muda_adapter.as_ref() else { return };
954        match muda_type {
955            MudaType::Menubar => {
956                let Some(menu) = self.menubar() else { return };
957                muda_adapter.invoke(&menu, entry_id);
958            }
959            MudaType::Context => {
960                let menu = self.context_menu.borrow();
961                let Some(menu) = menu.as_ref() else { return };
962                muda_adapter.invoke(menu, entry_id);
963            }
964        }
965    }
966
967    #[cfg(target_arch = "wasm32")]
968    pub fn input_method_focused(&self) -> bool {
969        match self.virtual_keyboard_helper.try_borrow() {
970            Ok(vkh) => vkh.as_ref().map_or(false, |h| h.has_focus()),
971            // the only location in which the virtual_keyboard_helper is mutably borrowed is from
972            // show_virtual_keyboard, which means we have the focus
973            Err(_) => true,
974        }
975    }
976
977    #[cfg(not(target_arch = "wasm32"))]
978    pub fn input_method_focused(&self) -> bool {
979        false
980    }
981
982    // Requests for the window to be resized. Returns true if the window was resized immediately,
983    // or if it will be resized later (false).
984    fn resize_window(&self, size: winit::dpi::Size) -> Result<bool, PlatformError> {
985        if platform_dictates_window_size() {
986            // The platform's size wins: re-announce it so the window item snaps back to it.
987            self.resize_event(physical_size_to_winit(self.size.get()))?;
988            return Ok(true);
989        }
990        match &*self.winit_window_or_none.borrow() {
991            WinitWindowOrNone::HasWindow { window, .. } => {
992                self.physical_size_before_scale_factor.set(None);
993                if let Some(size) = window.request_inner_size(size) {
994                    // On wayland we might not get a WindowEvent::Resized, so resize the EGL surface right away.
995                    self.resize_event(size)?;
996                    Ok(true)
997                } else {
998                    self.pending_requested_size.set(size.into());
999                    // None means that we'll get a `WindowEvent::Resized` later
1000                    Ok(false)
1001                }
1002            }
1003            WinitWindowOrNone::None(attributes) => {
1004                attributes.borrow_mut().inner_size = Some(size);
1005                if let winit::dpi::Size::Physical(physical) = size {
1006                    self.physical_size_before_scale_factor.set(Some(physical));
1007                }
1008                // The scale factor is not known yet: the resize event after creation corrects
1009                // the window item.
1010                let scale_factor = self.window().scale_factor() as _;
1011                self.resize_event(size.to_physical(scale_factor))?;
1012                Ok(true)
1013            }
1014        }
1015    }
1016
1017    pub fn resize_event(&self, size: winit::dpi::PhysicalSize<u32>) -> Result<(), PlatformError> {
1018        self.pending_resize_event_after_show.set(false);
1019        if self.physical_size_before_scale_factor.get().is_some_and(|requested| requested != size) {
1020            self.physical_size_before_scale_factor.set(None);
1021        }
1022        // When a window is minimized on Windows, we get a move event to an off-screen position
1023        // and a resize even with a zero size. Don't forward that, especially not to the renderer,
1024        // which might panic when trying to create a zero-sized surface.
1025        if size.width > 0 && size.height > 0 {
1026            let physical_size = physical_size_to_slint(&size);
1027            self.size.set(physical_size);
1028            self.pending_requested_size.set(None);
1029            let scale_factor = WindowInner::from_pub(self.window()).scale_factor();
1030
1031            let size = physical_size.to_logical(scale_factor);
1032            self.window().dispatch_event_with_result(WindowEvent::Resized { size })?;
1033
1034            WindowInner::from_pub(self.window())
1035                .set_window_item_safe_area(self.safe_area_inset().to_logical(scale_factor));
1036
1037            // Workaround fox winit not sync'ing CSS size of the canvas (the size shown on the browser)
1038            // with the width/height attribute (the size of the viewport/GL surface)
1039            // If they're not in sync, the UI would be shown as scaled
1040            #[cfg(target_arch = "wasm32")]
1041            if let Some(html_canvas) = self
1042                .winit_window_or_none
1043                .borrow()
1044                .as_window()
1045                .and_then(|winit_window| winit_window.canvas())
1046            {
1047                html_canvas.set_width(physical_size.width);
1048                html_canvas.set_height(physical_size.height);
1049            }
1050        }
1051        Ok(())
1052    }
1053
1054    pub fn set_accent_color(&self, color: Color) {
1055        WindowInner::from_pub(self.window()).context().set_accent_color(color);
1056    }
1057
1058    fn query_system_accent_color() -> Color {
1059        cfg_if::cfg_if! {
1060            if #[cfg(target_os = "windows")] {
1061                use windows::Win32::Graphics::{
1062                    Dwm::DwmGetColorizationColor,
1063                    Gdi::{GetSysColor, COLOR_HIGHLIGHT},
1064                };
1065
1066                let mut argb = 0u32;
1067                let mut _opaque_blend = windows::core::BOOL::default();
1068                if unsafe { DwmGetColorizationColor(&mut argb, &mut _opaque_blend) }.is_ok() {
1069                    let a = ((argb >> 24) & 0xFF) as u8;
1070                    let r = ((argb >> 16) & 0xFF) as u8;
1071                    let g = ((argb >> 8) & 0xFF) as u8;
1072                    let b = (argb & 0xFF) as u8;
1073                    return Color::from_argb_u8(a, r, g, b);
1074                }
1075
1076                let colorref = unsafe { GetSysColor(COLOR_HIGHLIGHT) };
1077                let r = (colorref & 0xFF) as u8;
1078                let g = ((colorref >> 8) & 0xFF) as u8;
1079                let b = ((colorref >> 16) & 0xFF) as u8;
1080                Color::from_argb_u8(255, r, g, b)
1081            } else if #[cfg(target_os = "macos")] {
1082                use objc2::ClassType;
1083                use objc2_app_kit::{NSColor, NSColorType};
1084                // controlAccentColor is only available on macOS 10.14 and later.
1085                // Probe for it so that older systems fall back to the default palette
1086                // instead of aborting with an unrecognized-selector exception.
1087                if !NSColor::class().responds_to(objc2::sel!(controlAccentColor)) {
1088                    return Color::default();
1089                }
1090                let color = NSColor::controlAccentColor();
1091                color.colorUsingType(NSColorType::ComponentBased).map(|c| {
1092                    let r = c.redComponent() as f32;
1093                    let g = c.greenComponent() as f32;
1094                    let b = c.blueComponent() as f32;
1095                    let a = c.alphaComponent() as f32;
1096                    Color::from_argb_f32(a, r, g, b)
1097                }).unwrap_or_default()
1098            } else if #[cfg(target_arch = "wasm32")] {
1099                query_wasm_accent_color()
1100            } else {
1101                // Linux: set by XDG settings watcher; other platforms: not available
1102                Color::default()
1103            }
1104        }
1105    }
1106
1107    /// Re-query the system accent color. Called on theme changes.
1108    pub fn update_accent_color(&self) {
1109        let color = Self::query_system_accent_color();
1110        if color != Color::default() {
1111            self.set_accent_color(color);
1112        }
1113    }
1114
1115    pub fn set_color_scheme(&self, scheme: ColorScheme) {
1116        WindowInner::from_pub(self.window()).context().set_color_scheme(scheme);
1117
1118        // Update the menubar theme
1119        #[cfg(all(target_os = "windows", muda))]
1120        if let WinitWindowOrNone::HasWindow {
1121            window: winit_window,
1122            muda_adapter: maybe_muda_adapter,
1123            ..
1124        } = &*self.winit_window_or_none.borrow()
1125        {
1126            if let Some(muda_adapter) = maybe_muda_adapter.borrow().as_ref() {
1127                muda_adapter.set_menubar_theme(&winit_window, scheme);
1128            };
1129        }
1130
1131        // Inform winit about the selected color theme, so that the window decoration is drawn correctly.
1132        #[cfg(xdg_desktop_settings)]
1133        if let Some(winit_window) = self.winit_window() {
1134            winit_window.set_theme(match scheme {
1135                ColorScheme::Unknown => None,
1136                ColorScheme::Dark => Some(winit::window::Theme::Dark),
1137                ColorScheme::Light => Some(winit::window::Theme::Light),
1138                _ => None,
1139            });
1140        }
1141    }
1142
1143    #[cfg(target_os = "ios")]
1144    pub fn set_platform_default_font_size(&self, size: i_slint_core::lengths::LogicalLength) {
1145        WindowInner::from_pub(self.window()).context().set_platform_default_font_size(Some(size));
1146    }
1147
1148    pub fn window_state_event(&self) {
1149        let Some(winit_window) = self.winit_window_or_none.borrow().as_window() else { return };
1150
1151        if let Some(minimized) = winit_window.is_minimized() {
1152            self.minimized.set(minimized);
1153            if minimized != self.window().is_minimized() {
1154                self.window().set_minimized(minimized);
1155            }
1156        }
1157
1158        // The method winit::Window::is_maximized returns false when the window
1159        // is minimized, even if it was previously maximized. We have to ensure
1160        // that we only update the internal maximized state when the window is
1161        // not minimized. Otherwise, the window would be restored in a
1162        // non-maximized state even if it was maximized before being minimized.
1163        let maximized = winit_window.is_maximized();
1164        if !self.window().is_minimized() {
1165            self.maximized.set(maximized);
1166            if maximized != self.window().is_maximized() {
1167                self.window().set_maximized(maximized);
1168            }
1169        }
1170
1171        // NOTE: Fullscreen overrides maximized so if both are true then the
1172        // window will remain in fullscreen. Fullscreen must be false to switch
1173        // to maximized.
1174        let fullscreen = winit_window.fullscreen().is_some();
1175        if fullscreen != self.window().is_fullscreen() {
1176            self.window().set_fullscreen(fullscreen);
1177        }
1178    }
1179
1180    #[cfg(enable_accesskit)]
1181    pub(crate) fn accesskit_adapter(
1182        &self,
1183    ) -> Option<std::cell::Ref<'_, RefCell<crate::accesskit::AccessKitAdapter>>> {
1184        std::cell::Ref::filter_map(
1185            self.winit_window_or_none.try_borrow().ok()?,
1186            |wor: &WinitWindowOrNone| match wor {
1187                WinitWindowOrNone::HasWindow { accesskit_adapter, .. } => Some(accesskit_adapter),
1188                WinitWindowOrNone::None(..) => None,
1189            },
1190        )
1191        .ok()
1192    }
1193
1194    #[cfg(enable_accesskit)]
1195    pub(crate) fn with_access_kit_adapter_from_weak_window_adapter(
1196        self_weak: Weak<Self>,
1197        callback: impl FnOnce(&RefCell<crate::accesskit::AccessKitAdapter>),
1198    ) {
1199        let Some(self_) = self_weak.upgrade() else { return };
1200        let winit_window_or_none = self_.winit_window_or_none.borrow();
1201        match &*winit_window_or_none {
1202            WinitWindowOrNone::HasWindow { accesskit_adapter, .. } => callback(accesskit_adapter),
1203            WinitWindowOrNone::None(..) => {}
1204        }
1205    }
1206
1207    /// Register an observer for macOS system color changes so that
1208    /// the accent color updates live when the user changes it in System Settings.
1209    #[cfg(target_os = "macos")]
1210    fn setup_macos_color_observer(&self) {
1211        let self_weak = self.self_weak.clone();
1212        let block =
1213            block2::RcBlock::new(move |_: core::ptr::NonNull<objc2_foundation::NSNotification>| {
1214                if let Some(adapter) = self_weak.upgrade() {
1215                    adapter.update_accent_color();
1216                }
1217            });
1218        let observer = unsafe {
1219            objc2_foundation::NSNotificationCenter::defaultCenter()
1220                .addObserverForName_object_queue_usingBlock(
1221                    Some(objc2_app_kit::NSSystemColorsDidChangeNotification),
1222                    None,
1223                    None,
1224                    &block,
1225                )
1226        };
1227        let _ = self.macos_color_observer.set(observer);
1228    }
1229
1230    pub fn activation_changed(&self, is_active: bool) -> Result<(), PlatformError> {
1231        let have_focus = is_active || self.input_method_focused();
1232        let slint_window = self.window();
1233        let runtime_window = WindowInner::from_pub(slint_window);
1234        // We don't render popups as separate windows yet, so treat
1235        // focus to be the same as being active.
1236        if have_focus != runtime_window.active() {
1237            slint_window.dispatch_event_with_result(
1238                corelib::platform::WindowEvent::WindowActiveChanged(have_focus),
1239            )?;
1240        }
1241
1242        #[cfg(all(muda, target_os = "macos"))]
1243        {
1244            if let WinitWindowOrNone::HasWindow { muda_adapter, .. } =
1245                &*self.winit_window_or_none.borrow()
1246            {
1247                if muda_adapter.borrow().is_none()
1248                    && self.muda_enable_default_menu_bar
1249                    && self.menubar().is_none()
1250                {
1251                    *muda_adapter.borrow_mut() =
1252                        Some(crate::muda::MudaAdapter::setup_default_menu_bar()?);
1253                }
1254
1255                if let Some(muda_adapter) = muda_adapter.borrow().as_ref() {
1256                    muda_adapter.window_activation_changed(is_active);
1257                }
1258            }
1259        }
1260
1261        Ok(())
1262    }
1263
1264    fn dispatch_internal_event(&self, event: impl Into<corelib::platform::InternalEvent>) {
1265        self.window().dispatch_event(WindowEvent::internal(event));
1266    }
1267
1268    /// Handles a winit window event for this window: applies the window event filter, feeds
1269    /// accesskit, updates the cursor and dispatches the corresponding Slint event.
1270    /// The event loop is needed to create the custom cursor.
1271    pub(crate) fn dispatch_winit_window_event(
1272        &self,
1273        event_loop: &ActiveEventLoop,
1274        winit_window: &winit::window::Window,
1275        event: WinitWindowEvent,
1276    ) -> Result<(), PlatformError> {
1277        if let Some(mut window_event_filter) = self.window_event_filter.take() {
1278            let event_result = window_event_filter(self.window(), &event);
1279            self.window_event_filter.set(Some(window_event_filter));
1280
1281            match event_result {
1282                EventResult::PreventDefault => return Ok(()),
1283                EventResult::Propagate => (),
1284            }
1285        }
1286
1287        #[cfg(enable_accesskit)]
1288        self.accesskit_adapter()
1289            .expect("internal error: accesskit adapter must exist when window exists")
1290            .borrow_mut()
1291            .process_event(winit_window, &event);
1292
1293        let runtime_window = WindowInner::from_pub(self.window());
1294        self.maybe_set_custom_cursor(event_loop, winit_window);
1295        if !matches!(
1296            event,
1297            WinitWindowEvent::CursorMoved { .. } | WinitWindowEvent::AxisMotion { .. }
1298        ) {
1299            self.shared_backend_data.flush_pending_mouse_move();
1300        }
1301
1302        match event {
1303            WinitWindowEvent::RedrawRequested => self.draw()?,
1304            WinitWindowEvent::Resized(size) => {
1305                let resized = self.resize_event(size);
1306
1307                // Entering fullscreen, maximizing or minimizing the window will
1308                // trigger a resize event. We need to update the internal window
1309                // state to match the actual window state. We simulate a "window
1310                // state event" since there is not an official event for it yet.
1311                // See: https://github.com/rust-windowing/winit/issues/2334
1312                self.window_state_event();
1313
1314                // Some platforms (e.g., Windows) may not emit an Occluded event when minimized,
1315                // so manually mark the window as occluded if its size is zero.
1316                #[cfg(target_os = "windows")]
1317                {
1318                    if size.width == 0 || size.height == 0 {
1319                        self.renderer.occluded(true);
1320                    }
1321                }
1322
1323                resized?;
1324            }
1325            WinitWindowEvent::CloseRequested => {
1326                self.window()
1327                    .dispatch_event_with_result(corelib::platform::WindowEvent::CloseRequested)?;
1328            }
1329            WinitWindowEvent::Focused(have_focus) => {
1330                // Work around https://github.com/rust-windowing/winit/issues/4371
1331                let have_focus =
1332                    if cfg!(target_os = "macos") { winit_window.has_focus() } else { have_focus };
1333                self.activation_changed(have_focus)?;
1334            }
1335
1336            WinitWindowEvent::KeyboardInput { event, is_synthetic, .. } => {
1337                let key_code = event.logical_key.clone();
1338                // For now: Match Qt's behavior of mapping command to control and control to meta (LWin/RWin).
1339                let swap_cmd_ctrl = i_slint_core::is_apple_platform();
1340
1341                let key_code = if swap_cmd_ctrl {
1342                    #[cfg_attr(slint_nightly_test, allow(non_exhaustive_omitted_patterns))]
1343                    match key_code {
1344                        winit::keyboard::Key::Named(winit::keyboard::NamedKey::Control) => {
1345                            winit::keyboard::Key::Named(winit::keyboard::NamedKey::Super)
1346                        }
1347                        winit::keyboard::Key::Named(winit::keyboard::NamedKey::Super) => {
1348                            winit::keyboard::Key::Named(winit::keyboard::NamedKey::Control)
1349                        }
1350                        code => code,
1351                    }
1352                } else {
1353                    key_code
1354                };
1355
1356                fn to_slint_key(event: &winit::event::KeyEvent, key_code: &Key) -> SharedString {
1357                    macro_rules! winit_key_to_char {
1358                        ($($char:literal # $name:ident # $($shifted:ident)? $(=> $($_muda:ident)? # $($_qt:ident)|* # $($winit:ident $(($pos:ident))?)|* # $($_xkb:ident)|* )? ;)*) => {
1359                            #[cfg_attr(slint_nightly_test, allow(non_exhaustive_omitted_patterns))]
1360                            match key_code {
1361                                $( $( $(
1362                                            winit::keyboard::Key::Named(winit::keyboard::NamedKey::$winit)
1363                                            $(if event.location == winit::keyboard::KeyLocation::$pos)?
1364                                            => $char.into(),
1365                                )* )? )*
1366                                    winit::keyboard::Key::Character(str) => str.as_str().into(),
1367                                _ => {
1368                                    if let Some(text) = &event.text {
1369                                        text.as_str().into()
1370                                    } else {
1371                                        "".into()
1372                                    }
1373                                }
1374                            }
1375                        }
1376                    }
1377                    i_slint_common::for_each_keys!(winit_key_to_char)
1378                }
1379                #[allow(unused_mut)]
1380                let mut text = to_slint_key(&event, &key_code);
1381
1382                #[cfg(target_os = "windows")]
1383                let text_without_modifiers = {
1384                    use winit::platform::modifier_supplement::KeyEventExtModifierSupplement;
1385
1386                    // On Windows, if Ctrl+Alt is pressed with a key that does not use
1387                    // AltGr for remapping, we need to fall back to the
1388                    // key_without_modifiers.
1389                    //
1390                    // See: https://github.com/rust-windowing/winit/issues/2945
1391                    //
1392                    // The text_without_modifiers also let's us disambiguate between a Ctrl+Alt
1393                    // combination used to imply AltGr or not.
1394                    // The latter case should be treated as a shortcut, the former should not.
1395                    let text_without_modifiers =
1396                        to_slint_key(&event, &event.key_without_modifiers());
1397                    // Skip the fallback for dead keys so the accent composes instead of being inserted.
1398                    if text.is_empty()
1399                        && !text_without_modifiers.is_empty()
1400                        && !matches!(event.logical_key, winit::keyboard::Key::Dead(_))
1401                    {
1402                        text = text_without_modifiers.clone();
1403                    }
1404                    text_without_modifiers
1405                };
1406
1407                if text.is_empty() {
1408                    // Failed to translate the key event
1409                    return Ok(());
1410                }
1411
1412                if is_synthetic {
1413                    // Synthetic event are sent when the focus is acquired, for all the keys currently pressed.
1414                    // Don't forward these keys other than modifiers to the app
1415                    use winit::keyboard::{Key::Named, NamedKey as N};
1416                    if !matches!(
1417                        key_code,
1418                        Named(N::Control | N::Shift | N::Super | N::Alt | N::AltGraph),
1419                    ) {
1420                        return Ok(());
1421                    }
1422                }
1423
1424                let event_type = match event.state {
1425                    winit::event::ElementState::Pressed => KeyEventType::KeyPressed,
1426                    winit::event::ElementState::Released => KeyEventType::KeyReleased,
1427                };
1428                let mut key_event = KeyEvent::default();
1429                key_event.text = text;
1430                key_event.repeat = event.repeat;
1431
1432                let event = InternalKeyEvent {
1433                    key_event,
1434                    event_type,
1435                    #[cfg(target_os = "windows")]
1436                    text_without_modifiers,
1437                    ..Default::default()
1438                };
1439
1440                self.dispatch_internal_event(event);
1441            }
1442            WinitWindowEvent::Ime(winit::event::Ime::Preedit(string, preedit_selection)) => {
1443                let event = InternalKeyEvent {
1444                    event_type: KeyEventType::UpdateComposition,
1445                    preedit_text: string.into(),
1446                    preedit_selection: preedit_selection.map(|e| e.0 as i32..e.1 as i32),
1447                    ..Default::default()
1448                };
1449                self.dispatch_internal_event(event);
1450            }
1451            WinitWindowEvent::Ime(winit::event::Ime::Commit(string)) => {
1452                let mut key_event = KeyEvent::default();
1453                key_event.text = string.into();
1454                let event = InternalKeyEvent {
1455                    event_type: KeyEventType::CommitComposition,
1456                    key_event,
1457                    ..Default::default()
1458                };
1459                self.dispatch_internal_event(event);
1460            }
1461            WinitWindowEvent::CursorMoved { position, .. } => {
1462                self.current_resize_direction.set(handle_cursor_move_for_resize(
1463                    winit_window,
1464                    position,
1465                    self.current_resize_direction.get(),
1466                    runtime_window
1467                        .window_item()
1468                        .map_or(Default::default(), |w| w.as_pin_ref().resize_border_width()),
1469                ));
1470                let position = position.to_logical(runtime_window.scale_factor() as f64);
1471                let cursor_pos = euclid::point2(position.x, position.y);
1472                self.cursor_pos.set(cursor_pos);
1473                // winit sends this event at a very high frequency, so coalesce the moves.
1474                self.shared_backend_data.buffer_mouse_move(&self.self_weak, cursor_pos);
1475            }
1476            WinitWindowEvent::CursorLeft { .. } => {
1477                // On the html canvas, we don't get the mouse move or release event when outside the canvas. So we have no choice but canceling the event
1478                if cfg!(target_arch = "wasm32") || !self.pressed.get() {
1479                    self.pressed.set(false);
1480                    self.dispatch_internal_event(BackendMouseEvent::Exit);
1481                }
1482            }
1483            WinitWindowEvent::MouseWheel { delta, phase, .. } => {
1484                let (delta_x, delta_y) = match delta {
1485                    winit::event::MouseScrollDelta::LineDelta(lx, ly) => (lx * 60., ly * 60.),
1486                    winit::event::MouseScrollDelta::PixelDelta(d) => {
1487                        let d = d.to_logical(runtime_window.scale_factor() as f64);
1488                        (d.x, d.y)
1489                    }
1490                };
1491                let phase = winit_touch_phase(phase);
1492                self.dispatch_internal_event(BackendMouseEvent::Wheel {
1493                    position: self.cursor_pos.get(),
1494                    delta_x,
1495                    delta_y,
1496                    phase,
1497                });
1498            }
1499            WinitWindowEvent::MouseInput { state, button, .. } => {
1500                let button = match button {
1501                    winit::event::MouseButton::Left => PointerEventButton::Left,
1502                    winit::event::MouseButton::Right => PointerEventButton::Right,
1503                    winit::event::MouseButton::Middle => PointerEventButton::Middle,
1504                    winit::event::MouseButton::Back => PointerEventButton::Back,
1505                    winit::event::MouseButton::Forward => PointerEventButton::Forward,
1506                    winit::event::MouseButton::Other(_) => PointerEventButton::Other,
1507                };
1508                let ev = match state {
1509                    winit::event::ElementState::Pressed => {
1510                        if button == PointerEventButton::Left
1511                            && self.current_resize_direction.get().is_some()
1512                        {
1513                            handle_resize(winit_window, self.current_resize_direction.get());
1514                            return Ok(());
1515                        }
1516
1517                        self.pressed.set(true);
1518                        BackendMouseEvent::Pressed {
1519                            position: self.cursor_pos.get(),
1520                            button,
1521                            click_count: 0,
1522                            touch_finger_id: 0,
1523                        }
1524                    }
1525                    winit::event::ElementState::Released => {
1526                        self.pressed.set(false);
1527                        BackendMouseEvent::Released {
1528                            position: self.cursor_pos.get(),
1529                            button,
1530                            click_count: 0,
1531                            touch_finger_id: 0,
1532                        }
1533                    }
1534                };
1535                self.dispatch_internal_event(ev);
1536            }
1537            WinitWindowEvent::Touch(touch) => {
1538                let location = touch.location.to_logical(runtime_window.scale_factor() as f64);
1539                let position = euclid::point2(location.x, location.y);
1540                let finger_id = match touch.phase {
1541                    winit::event::TouchPhase::Started | winit::event::TouchPhase::Moved => {
1542                        Some(self.touch_finger_ids.borrow_mut().id_for((touch.device_id, touch.id)))
1543                    }
1544                    winit::event::TouchPhase::Ended | winit::event::TouchPhase::Cancelled => {
1545                        self.touch_finger_ids.borrow_mut().take((touch.device_id, touch.id))
1546                    }
1547                };
1548                if let Some(finger_id) = finger_id {
1549                    self.dispatch_internal_event(corelib::platform::InternalEvent::Touch {
1550                        id: finger_id,
1551                        position,
1552                        phase: winit_touch_phase(touch.phase),
1553                    });
1554                }
1555            }
1556            WinitWindowEvent::ScaleFactorChanged { scale_factor, mut inner_size_writer } => {
1557                if std::env::var("SLINT_SCALE_FACTOR").is_err() {
1558                    self.window().dispatch_event_with_result(
1559                        corelib::platform::WindowEvent::ScaleFactorChanged {
1560                            scale_factor: scale_factor as f32,
1561                        },
1562                    )?;
1563                    if let Some(physical) = self.physical_size_before_scale_factor.take() {
1564                        inner_size_writer.request_inner_size(physical).ok();
1565                    }
1566                    // TODO: otherwise send a resize event or try to keep the logical size the same.
1567                }
1568            }
1569            WinitWindowEvent::ThemeChanged(theme) => {
1570                self.set_color_scheme(match theme {
1571                    winit::window::Theme::Dark => ColorScheme::Dark,
1572                    winit::window::Theme::Light => ColorScheme::Light,
1573                });
1574                self.update_accent_color();
1575            }
1576            WinitWindowEvent::Occluded(occluded) => {
1577                self.renderer.occluded(occluded);
1578
1579                // wgpu hands out no drawable while the window isn't visible, see
1580                // `macos::RevealOnFirstFrame`. Draw now instead of at the next display link tick.
1581                #[cfg(target_os = "macos")]
1582                if !occluded && self.pending_redraw.get() {
1583                    self.draw()?;
1584                }
1585
1586                // Same hack as in the Resized arm above, so that we handle Minimized changes
1587                self.window_state_event();
1588            }
1589            // Note: winit's PinchGesture does not carry a position; we use the last
1590            // known cursor position as the best available approximation. On macOS
1591            // trackpads, CursorMoved events typically precede gesture events.
1592            WinitWindowEvent::PinchGesture { delta, phase, .. } => {
1593                self.dispatch_internal_event(BackendMouseEvent::PinchGesture {
1594                    position: self.cursor_pos.get(),
1595                    delta: delta as f32,
1596                    phase: winit_touch_phase(phase),
1597                });
1598            }
1599            WinitWindowEvent::RotationGesture { delta, phase, .. } => {
1600                // macOS/winit: positive = counterclockwise. Negate to match
1601                // Slint convention (positive = clockwise).
1602                self.dispatch_internal_event(BackendMouseEvent::RotationGesture {
1603                    position: self.cursor_pos.get(),
1604                    delta: -delta,
1605                    phase: winit_touch_phase(phase),
1606                });
1607            }
1608
1609            WinitWindowEvent::AxisMotion { .. } => {
1610                // Ignored, but happens often and is also ignored for the purpose of bundling CursorMoved.
1611            }
1612            _ => {}
1613        }
1614
1615        Ok(())
1616    }
1617
1618    /// Sets the cursor to a custom source, if there is a new one.
1619    fn maybe_set_custom_cursor(
1620        &self,
1621        event_loop: &ActiveEventLoop,
1622        winit_window: &winit::window::Window,
1623    ) {
1624        if let Some(source) = self.custom_cursor_source.take() {
1625            winit_window.set_cursor(event_loop.create_custom_cursor(source));
1626        }
1627    }
1628
1629    fn set_visibility(&self, visibility: WindowVisibility) -> Result<(), PlatformError> {
1630        if visibility == self.shown.get() {
1631            return Ok(());
1632        }
1633
1634        self.shown.set(visibility);
1635        self.pending_resize_event_after_show.set(!matches!(visibility, WindowVisibility::Hidden));
1636        self.pending_redraw.set(false);
1637        if matches!(visibility, WindowVisibility::ShownFirstTime | WindowVisibility::Shown) {
1638            let recreating_window = matches!(visibility, WindowVisibility::Shown);
1639
1640            let Some(winit_window) = self.winit_window() else {
1641                // Can't really show it on the screen, safe it in the attributes and try again later
1642                // by registering it for activation when we can.
1643                self.winit_window_or_none.borrow().set_visible(true);
1644                self.shared_backend_data
1645                    .register_inactive_window((self.self_weak.upgrade().unwrap()) as _);
1646                return Ok(());
1647            };
1648
1649            let runtime_window = WindowInner::from_pub(self.window());
1650
1651            let scale_factor = runtime_window.scale_factor() as f64;
1652
1653            #[allow(unused_mut)]
1654            let mut preferred_size = self.preferred_size().unwrap_or_default();
1655
1656            #[cfg(target_arch = "wasm32")]
1657            if let Some(html_canvas) = winit_window.canvas() {
1658                // Try to maintain the existing size of the canvas element, if any
1659                if !is_preferred_sized_canvas(&html_canvas)
1660                    && !canvas_has_explicit_size_set(&html_canvas)
1661                {
1662                    let existing_canvas_size = winit::dpi::LogicalSize::new(
1663                        html_canvas.client_width() as f32,
1664                        html_canvas.client_height() as f32,
1665                    );
1666                    preferred_size.width = existing_canvas_size.width;
1667
1668                    preferred_size.height = existing_canvas_size.height;
1669                }
1670            }
1671
1672            if !platform_dictates_window_size()
1673                && winit_window.fullscreen().is_none()
1674                && !self.has_explicit_size.get()
1675                && preferred_size.width > 0 as Coord
1676                && preferred_size.height > 0 as Coord
1677                // Don't set the preferred size as the user may have resized the window
1678                && !recreating_window
1679            {
1680                // use the Slint's window Scale factor to take in account the override
1681                let size = preferred_size.to_physical::<u32>(scale_factor);
1682                self.resize_window(size.into())?;
1683            };
1684
1685            // Pre-render the first frame before mapping the window to avoid a flash of
1686            // uninitialized VRAM on X11 (no background_pixmap). Skipped on Wayland, where
1687            // rendering before the initial configure makes the compositor mis-size the window.
1688            if !self.first_frame_presented.get() && !self.shared_backend_data.is_wayland {
1689                let _ = self.draw();
1690                #[cfg(target_os = "macos")]
1691                if !self.first_frame_presented.get() {
1692                    *self.reveal_on_first_frame.borrow_mut() =
1693                        crate::macos::RevealOnFirstFrame::new(&winit_window);
1694                }
1695            }
1696
1697            winit_window.set_visible(true);
1698
1699            // Refresh the SlintContext color-scheme now that the window is mapped: on some platforms
1700            // `winit_window.theme()` only reports a real value once the window is shown.
1701            if let Some(theme) = winit_window.theme() {
1702                self.set_color_scheme(match theme {
1703                    winit::window::Theme::Dark => ColorScheme::Dark,
1704                    winit::window::Theme::Light => ColorScheme::Light,
1705                });
1706            }
1707
1708            // In wasm a request_redraw() issued before show() results in a draw() even when the window
1709            // isn't visible, as opposed to regular windowing systems. The compensate for the lost draw,
1710            // explicitly render the first frame on show().
1711            #[cfg(target_arch = "wasm32")]
1712            if self.pending_redraw.get() {
1713                self.draw()?;
1714            };
1715
1716            // On iOS making an already-created window visible doesn't generate a fresh
1717            // RedrawRequested. winit's one initial RedrawRequested is delivered while the window is
1718            // created (during `resumed`), so a window first shown later misses it and stays blank.
1719            #[cfg(ios_and_friends)]
1720            self.request_redraw();
1721
1722            Ok(())
1723        } else {
1724            // Release the context menu; it holds the menu item tree that keeps this adapter alive.
1725            #[cfg(muda)]
1726            self.context_menu.take();
1727
1728            // Wayland doesn't support hiding a window, only destroying it entirely.
1729            if self.winit_window_or_none.borrow().as_window().is_some_and(|winit_window| {
1730                use raw_window_handle::HasWindowHandle;
1731                winit_window.window_handle().is_ok_and(|h| {
1732                    matches!(h.as_raw(), raw_window_handle::RawWindowHandle::Wayland(..))
1733                }) || std::env::var_os("SLINT_DESTROY_WINDOW_ON_HIDE").is_some()
1734            }) {
1735                self.suspend()?;
1736                // Note: Don't register the window in inactive_windows for re-creation later, as creating the window
1737                // on wayland implies making it visible. Unfortunately, winit won't allow creating a window on wayland
1738                // that's not visible.
1739            } else {
1740                self.winit_window_or_none.borrow().set_visible(false);
1741            }
1742
1743            /* FIXME:
1744            if let Some(existing_blinker) = self.cursor_blinker.borrow().upgrade() {
1745                existing_blinker.stop();
1746            }*/
1747
1748            // After the window is ordered out, so that the reveal can't show it.
1749            #[cfg(target_os = "macos")]
1750            self.reveal_on_first_frame.take();
1751
1752            Ok(())
1753        }
1754    }
1755
1756    pub(crate) fn visibility(&self) -> WindowVisibility {
1757        self.shown.get()
1758    }
1759
1760    pub(crate) fn pending_redraw(&self) -> bool {
1761        self.pending_redraw.get()
1762    }
1763
1764    pub async fn async_winit_window(
1765        self_weak: Weak<Self>,
1766    ) -> Result<Arc<winit::window::Window>, PlatformError> {
1767        std::future::poll_fn(move |context| {
1768            let Some(self_) = self_weak.upgrade() else {
1769                return std::task::Poll::Ready(Err(
1770                    "Unable to obtain winit window from destroyed window".to_string().into(),
1771                ));
1772            };
1773            match self_.winit_window() {
1774                Some(window) => std::task::Poll::Ready(Ok(window)),
1775                None => {
1776                    let waker = context.waker();
1777                    if !self_.window_existence_wakers.borrow().iter().any(|w| w.will_wake(waker)) {
1778                        self_.window_existence_wakers.borrow_mut().push(waker.clone());
1779                    }
1780                    std::task::Poll::Pending
1781                }
1782            }
1783        })
1784        .await
1785    }
1786}
1787
1788impl WindowAdapter for WinitWindowAdapter {
1789    fn window(&self) -> &corelib::api::Window {
1790        &self.window
1791    }
1792
1793    fn renderer(&self) -> &dyn i_slint_core::renderer::Renderer {
1794        self.renderer().as_core_renderer()
1795    }
1796
1797    fn set_visible(&self, visible: bool) -> Result<(), PlatformError> {
1798        self.set_visibility(if visible {
1799            WindowVisibility::Shown
1800        } else {
1801            WindowVisibility::Hidden
1802        })
1803    }
1804
1805    fn position(&self) -> Option<corelib::api::PhysicalPosition> {
1806        match &*self.winit_window_or_none.borrow() {
1807            WinitWindowOrNone::HasWindow { window, .. } => match window.outer_position() {
1808                Ok(outer_position) => {
1809                    Some(corelib::api::PhysicalPosition::new(outer_position.x, outer_position.y))
1810                }
1811                Err(_) => None,
1812            },
1813            WinitWindowOrNone::None(attributes) => {
1814                attributes.borrow().position.map(|pos| {
1815                    match pos {
1816                        winit::dpi::Position::Physical(phys_pos) => {
1817                            corelib::api::PhysicalPosition::new(phys_pos.x, phys_pos.y)
1818                        }
1819                        winit::dpi::Position::Logical(logical_pos) => {
1820                            // Best effort: Use the last known scale factor
1821                            corelib::api::LogicalPosition::new(
1822                                logical_pos.x as _,
1823                                logical_pos.y as _,
1824                            )
1825                            .to_physical(self.window().scale_factor())
1826                        }
1827                    }
1828                })
1829            }
1830        }
1831    }
1832
1833    fn set_position(&self, position: corelib::api::WindowPosition) {
1834        let winit_pos = position_to_winit(&position);
1835        match &*self.winit_window_or_none.borrow() {
1836            WinitWindowOrNone::HasWindow { window, .. } => window.set_outer_position(winit_pos),
1837            WinitWindowOrNone::None(attributes) => {
1838                attributes.borrow_mut().position = Some(winit_pos);
1839            }
1840        }
1841    }
1842
1843    fn set_size(&self, size: corelib::api::WindowSize) {
1844        self.has_explicit_size.set(true);
1845        // TODO: don't ignore error, propagate to caller
1846        self.resize_window(window_size_to_winit(&size)).ok();
1847    }
1848
1849    fn size(&self) -> corelib::api::PhysicalSize {
1850        self.size.get()
1851    }
1852
1853    fn request_redraw(&self) {
1854        if !self.pending_redraw.replace(true)
1855            && let WinitWindowOrNone::HasWindow { window, frame_throttle, .. } =
1856                &*self.winit_window_or_none.borrow()
1857        {
1858            frame_throttle.request_throttled_redraw(window);
1859        }
1860    }
1861
1862    #[allow(clippy::unnecessary_cast)] // Coord is used!
1863    fn update_window_properties(&self, properties: corelib::window::WindowProperties<'_>) {
1864        let Some(window_item) = WindowInner::from_pub(&self.window).window_item() else {
1865            return;
1866        };
1867        let window_item = window_item.as_pin_ref();
1868
1869        let winit_window_or_none = self.winit_window_or_none.borrow();
1870
1871        // Use our scale factor instead of winit's logical size to take a scale factor override into account.
1872        let sf = self.window().scale_factor();
1873
1874        // Update the icon only if it changes, to avoid flashing.
1875        let icon_image = window_item.icon();
1876        let icon_image_cache_key = ImageCacheKey::new((&icon_image).into());
1877        if *self.window_icon_cache_key.borrow() != icon_image_cache_key {
1878            *self.window_icon_cache_key.borrow_mut() = icon_image_cache_key;
1879            winit_window_or_none.set_window_icon(icon_to_winit(
1880                icon_image,
1881                i_slint_core::lengths::LogicalSize::new(64., 64.) * ScaleFactor::new(sf),
1882            ));
1883        }
1884        winit_window_or_none.set_title(&properties.title());
1885        winit_window_or_none.set_decorations(
1886            !window_item.no_frame() || winit_window_or_none.fullscreen().is_some(),
1887        );
1888
1889        // Follow a background brush that changes while the window is up. The renderer has to
1890        // come along: its surface keeps or discards the scene's alpha to match the window.
1891        #[cfg(target_os = "macos")]
1892        if let WinitWindowOrNone::HasWindow { window, .. } = &*winit_window_or_none {
1893            let transparent = Self::wants_transparent(window_item);
1894            if self.transparent.replace(transparent) != transparent {
1895                window.set_transparent(transparent);
1896                if let Err(err) = self.renderer.set_transparent(transparent) {
1897                    i_slint_core::debug_log!("Error adjusting the surface transparency: {err}");
1898                }
1899            }
1900        }
1901
1902        let new_window_level = if window_item.always_on_top() {
1903            winit::window::WindowLevel::AlwaysOnTop
1904        } else {
1905            winit::window::WindowLevel::Normal
1906        };
1907        // Only change the window level if it changes, to avoid https://github.com/slint-ui/slint/issues/3280
1908        // (Ubuntu 20.04's window manager always bringing the window to the front on x11)
1909        if self.window_level.replace(new_window_level) != new_window_level {
1910            winit_window_or_none.set_window_level(new_window_level);
1911        }
1912
1913        let mut width = window_item.width().get() as f32;
1914        let mut height = window_item.height().get() as f32;
1915        let mut must_resize = false;
1916        let existing_size = self.size.get().to_logical(sf);
1917
1918        if width <= 0. || height <= 0. {
1919            must_resize = true;
1920            if width <= 0. {
1921                width = existing_size.width;
1922            }
1923            if height <= 0. {
1924                height = existing_size.height;
1925            }
1926        }
1927
1928        // Adjust the size of the window to the value of the width and height property (if these property are changed from .slint).
1929        // But not if there is a pending resize in flight as that resize will reset these properties back
1930        if ((existing_size.width - width).abs() > 1. || (existing_size.height - height).abs() > 1.)
1931            && self.pending_requested_size.get().is_none()
1932            // Nor while the item still holds a physical size set before the window existed as
1933            // its logical size: the scale factor to convert it is not known yet.
1934            && self.physical_size_before_scale_factor.get().is_none_or(|requested| {
1935                requested.width as f32 != width || requested.height as f32 != height
1936            })
1937        {
1938            // If we're in fullscreen state, don't try to resize the window but maintain the surface
1939            // size we've been assigned to from the windowing system. Weston/Wayland don't like it
1940            // when we create a surface that's bigger than the screen due to constraints (#532).
1941            if winit_window_or_none.fullscreen().is_none() {
1942                // TODO: don't ignore error, propagate to caller
1943                let immediately_resized = self
1944                    .resize_window(winit::dpi::LogicalSize::new(width, height).into())
1945                    .unwrap_or_default();
1946                if immediately_resized {
1947                    // The resize event was already dispatched
1948                    must_resize = false;
1949                }
1950            }
1951        }
1952
1953        if must_resize {
1954            self.window()
1955                .dispatch_event_with_result(WindowEvent::Resized {
1956                    size: i_slint_core::api::LogicalSize::new(width, height),
1957                })
1958                .unwrap();
1959            WindowInner::from_pub(self.window())
1960                .set_window_item_safe_area(window_item.safe_area_insets());
1961        }
1962
1963        let m = properties.is_fullscreen();
1964        if m != self.fullscreen.get() {
1965            if m {
1966                if winit_window_or_none.fullscreen().is_none() {
1967                    winit_window_or_none
1968                        .set_fullscreen(Some(winit::window::Fullscreen::Borderless(None)));
1969                }
1970            } else {
1971                winit_window_or_none.set_fullscreen(None);
1972            }
1973            self.fullscreen.set(m);
1974        }
1975
1976        let m = properties.is_maximized();
1977        if m != self.maximized.get() {
1978            self.maximized.set(m);
1979            winit_window_or_none.set_maximized(m);
1980        }
1981
1982        let m = properties.is_minimized();
1983        if m != self.minimized.get() {
1984            self.minimized.set(m);
1985            winit_window_or_none.set_minimized(m);
1986        }
1987
1988        // If we're in fullscreen, don't try to resize the window but
1989        // maintain the surface size we've been assigned to from the
1990        // windowing system. Weston/Wayland don't like it when we create a
1991        // surface that's bigger than the screen due to constraints (#532).
1992        if winit_window_or_none.fullscreen().is_some() {
1993            return;
1994        }
1995
1996        let new_constraints = properties.layout_constraints();
1997        if new_constraints == self.constraints.get() {
1998            return;
1999        }
2000
2001        self.constraints.set(new_constraints);
2002
2003        let resizable = window_is_resizable(new_constraints.min, new_constraints.max);
2004        // we must call set_resizable before setting the min and max size otherwise setting the min and max size don't work on X11
2005        winit_window_or_none.set_resizable(resizable);
2006        // Important: Filter out (temporary?) zero width/heights, to avoid attempting to create a zero surface. For example, with wayland
2007        // the client-side rendering ends up passing a zero width/height to the renderer.
2008        let winit_min_inner =
2009            new_constraints.min.map(logical_size_to_winit).map(filter_out_zero_width_or_height);
2010        winit_window_or_none.set_min_inner_size(winit_min_inner, sf as f64);
2011        let winit_max_inner =
2012            new_constraints.max.map(logical_size_to_winit).map(filter_out_zero_width_or_height);
2013        winit_window_or_none.set_max_inner_size(winit_max_inner, sf as f64);
2014
2015        if !platform_dictates_window_size() {
2016            adjust_window_size_to_satisfy_constraints(self, winit_min_inner, winit_max_inner);
2017        }
2018
2019        // Auto-resize to the preferred size if users (SlintPad) requests it
2020        #[cfg(target_arch = "wasm32")]
2021        if let Some(canvas) =
2022            winit_window_or_none.as_window().and_then(|winit_window| winit_window.canvas())
2023        {
2024            if is_preferred_sized_canvas(&canvas) {
2025                let pref = new_constraints.preferred;
2026                if pref.width > 0 as Coord || pref.height > 0 as Coord {
2027                    // TODO: don't ignore error, propagate to caller
2028                    self.resize_window(logical_size_to_winit(pref).into()).ok();
2029                };
2030            }
2031        }
2032    }
2033
2034    fn internal(&self, _: corelib::InternalToken) -> Option<&dyn WindowAdapterInternal> {
2035        Some(self)
2036    }
2037}
2038
2039impl WindowAdapterInternal for WinitWindowAdapter {
2040    fn start_window_move(&self) {
2041        if let Some(winit_window) = self.winit_window_or_none.borrow().as_window() {
2042            let _ = winit_window.drag_window();
2043        }
2044    }
2045
2046    fn set_mouse_cursor(&self, cursor: MouseCursorInner) {
2047        let winit_cursor = match &cursor {
2048            MouseCursorInner::BuiltIn(cursor) => Some(match cursor {
2049                BuiltInMouseCursor::Default => winit::window::CursorIcon::Default,
2050                BuiltInMouseCursor::None => winit::window::CursorIcon::Default,
2051                BuiltInMouseCursor::Help => winit::window::CursorIcon::Help,
2052                BuiltInMouseCursor::Pointer => winit::window::CursorIcon::Pointer,
2053                BuiltInMouseCursor::Progress => winit::window::CursorIcon::Progress,
2054                BuiltInMouseCursor::Wait => winit::window::CursorIcon::Wait,
2055                BuiltInMouseCursor::Crosshair => winit::window::CursorIcon::Crosshair,
2056                BuiltInMouseCursor::Text => winit::window::CursorIcon::Text,
2057                BuiltInMouseCursor::Alias => winit::window::CursorIcon::Alias,
2058                BuiltInMouseCursor::Copy => winit::window::CursorIcon::Copy,
2059                BuiltInMouseCursor::Move => winit::window::CursorIcon::Move,
2060                BuiltInMouseCursor::NoDrop => winit::window::CursorIcon::NoDrop,
2061                BuiltInMouseCursor::NotAllowed => winit::window::CursorIcon::NotAllowed,
2062                BuiltInMouseCursor::Grab => winit::window::CursorIcon::Grab,
2063                BuiltInMouseCursor::Grabbing => winit::window::CursorIcon::Grabbing,
2064                BuiltInMouseCursor::ColResize => winit::window::CursorIcon::ColResize,
2065                BuiltInMouseCursor::RowResize => winit::window::CursorIcon::RowResize,
2066                BuiltInMouseCursor::NResize => winit::window::CursorIcon::NResize,
2067                BuiltInMouseCursor::EResize => winit::window::CursorIcon::EResize,
2068                BuiltInMouseCursor::SResize => winit::window::CursorIcon::SResize,
2069                BuiltInMouseCursor::WResize => winit::window::CursorIcon::WResize,
2070                BuiltInMouseCursor::NeResize => winit::window::CursorIcon::NeResize,
2071                BuiltInMouseCursor::NwResize => winit::window::CursorIcon::NwResize,
2072                BuiltInMouseCursor::SeResize => winit::window::CursorIcon::SeResize,
2073                BuiltInMouseCursor::SwResize => winit::window::CursorIcon::SwResize,
2074                BuiltInMouseCursor::EwResize => winit::window::CursorIcon::EwResize,
2075                BuiltInMouseCursor::NsResize => winit::window::CursorIcon::NsResize,
2076                BuiltInMouseCursor::NeswResize => winit::window::CursorIcon::NeswResize,
2077                BuiltInMouseCursor::NwseResize => winit::window::CursorIcon::NwseResize,
2078                _ => winit::window::CursorIcon::Default,
2079            }),
2080            MouseCursorInner::CustomMouseCursor { image, hotspot_x, hotspot_y } => {
2081                // Render scalable sources (SVG) at the display resolution so the cursor stays crisp.
2082                let scale = self.window().scale_factor();
2083                let source_size = image.size();
2084                let target_size = IntSize::new(
2085                    (source_size.width as f32 * scale) as u32,
2086                    (source_size.height as f32 * scale) as u32,
2087                );
2088                if let Some(rgba8) =
2089                    i_slint_core::graphics::image_to_rgba8_with_target_size(image, target_size)
2090                {
2091                    let (width, height) = (rgba8.width(), rgba8.height());
2092                    // winit rejects a hotspot that lies outside the image, so clamp it inside.
2093                    let source = CustomCursor::from_rgba(
2094                        rgba8.as_bytes(),
2095                        width as u16,
2096                        height as u16,
2097                        scaled_hotspot(*hotspot_x, source_size.width, width) as u16,
2098                        scaled_hotspot(*hotspot_y, source_size.height, height) as u16,
2099                    );
2100
2101                    // Custom cursors have to be set during the event loop
2102                    self.custom_cursor_source.set(source.ok());
2103                }
2104                None
2105            }
2106            _ => None,
2107        };
2108        if let Some(winit_window) = self.winit_window_or_none.borrow().as_window() {
2109            winit_window
2110                .set_cursor_visible(cursor != MouseCursorInner::BuiltIn(BuiltInMouseCursor::None));
2111
2112            if let Some(cursor) = winit_cursor {
2113                winit_window.set_cursor(cursor);
2114            }
2115        }
2116    }
2117
2118    fn input_method_request(&self, request: corelib::window::InputMethodRequest) {
2119        #[cfg(not(target_arch = "wasm32"))]
2120        if let Some(winit_window) = self.winit_window_or_none.borrow().as_window() {
2121            let props = match &request {
2122                corelib::window::InputMethodRequest::Enable(props) => {
2123                    winit_window.set_ime_allowed(true);
2124                    props
2125                }
2126                corelib::window::InputMethodRequest::Disable => {
2127                    return winit_window.set_ime_allowed(false);
2128                }
2129                corelib::window::InputMethodRequest::Update(props) => props,
2130                _ => return,
2131            };
2132            winit_window.set_ime_purpose(match props.input_type {
2133                corelib::items::InputType::Password => winit::window::ImePurpose::Password,
2134                corelib::items::InputType::Text
2135                | corelib::items::InputType::Number
2136                | corelib::items::InputType::Decimal
2137                | corelib::items::InputType::Search
2138                | _ => winit::window::ImePurpose::Normal,
2139            });
2140            winit_window.set_ime_cursor_area(
2141                position_to_winit(&props.cursor_rect_origin.into()),
2142                window_size_to_winit(&props.cursor_rect_size.into()),
2143            );
2144        }
2145
2146        #[cfg(target_arch = "wasm32")]
2147        match request {
2148            corelib::window::InputMethodRequest::Enable(..) => {
2149                let mut vkh = self.virtual_keyboard_helper.borrow_mut();
2150                let Some(canvas) =
2151                    self.winit_window().and_then(|winit_window| winit_window.canvas())
2152                else {
2153                    return;
2154                };
2155                let h = vkh.get_or_insert_with(|| {
2156                    super::wasm_input_helper::WasmInputHelper::new(self.self_weak.clone(), canvas)
2157                });
2158                h.show();
2159            }
2160            corelib::window::InputMethodRequest::Disable => {
2161                if let Some(h) = &*self.virtual_keyboard_helper.borrow() {
2162                    h.hide()
2163                }
2164            }
2165            _ => {}
2166        };
2167    }
2168
2169    #[cfg(muda)]
2170    fn supports_native_menu_bar(&self) -> bool {
2171        !crate::muda::is_disabled()
2172    }
2173
2174    #[cfg(muda)]
2175    fn setup_menubar(&self, menubar: vtable::VRc<MenuVTable>) {
2176        self.menubar_weak.replace(Some(vtable::VRc::downgrade(&menubar)));
2177
2178        if let WinitWindowOrNone::HasWindow { muda_adapter, .. } =
2179            &*self.winit_window_or_none.borrow()
2180        {
2181            // On Windows, we must destroy the muda menu before re-creating a new one
2182            drop(muda_adapter.borrow_mut().take());
2183            muda_adapter.replace(Some(crate::muda::MudaAdapter::setup(
2184                &menubar,
2185                &self.winit_window().unwrap(),
2186                self.event_loop_proxy.clone(),
2187                self.self_weak.clone(),
2188            )));
2189        }
2190    }
2191
2192    #[cfg(muda)]
2193    fn show_native_popup_menu(
2194        &self,
2195        context_menu_item: vtable::VRc<MenuVTable>,
2196        position: LogicalPosition,
2197    ) -> bool {
2198        if crate::muda::is_disabled() {
2199            return false;
2200        }
2201
2202        // Set before showing: on Windows the activation event can arrive before this returns.
2203        self.context_menu.replace(Some(context_menu_item));
2204
2205        if let WinitWindowOrNone::HasWindow { context_menu_muda_adapter, .. } =
2206            &*self.winit_window_or_none.borrow()
2207        {
2208            // On Windows, we must destroy the muda menu before re-creating a new one
2209            drop(context_menu_muda_adapter.borrow_mut().take());
2210            if let Some(new_adapter) = crate::muda::MudaAdapter::show_context_menu(
2211                self.context_menu.borrow().as_ref().unwrap(),
2212                &self.winit_window().unwrap(),
2213                position,
2214                self.event_loop_proxy.clone(),
2215            ) {
2216                context_menu_muda_adapter.replace(Some(new_adapter));
2217                return true;
2218            }
2219        }
2220
2221        // No native menu shown; release it so it doesn't keep the adapter alive.
2222        self.context_menu.take();
2223        false
2224    }
2225
2226    #[cfg(enable_accesskit)]
2227    fn handle_focus_change(&self, _old: Option<ItemRc>, _new: Option<ItemRc>) {
2228        let Some(accesskit_adapter_cell) = self.accesskit_adapter() else { return };
2229        if let Ok(mut a) = accesskit_adapter_cell.try_borrow_mut() {
2230            a.handle_focus_item_change();
2231        }
2232    }
2233
2234    #[cfg(enable_accesskit)]
2235    fn register_item_tree(&self, _: ItemTreeRefPin) {
2236        let Some(accesskit_adapter_cell) = self.accesskit_adapter() else { return };
2237        // 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
2238        if let Ok(mut a) = accesskit_adapter_cell.try_borrow_mut() {
2239            a.reload_tree();
2240        };
2241    }
2242
2243    #[cfg(enable_accesskit)]
2244    fn unregister_item_tree(
2245        &self,
2246        component: ItemTreeRef,
2247        _: &mut dyn Iterator<Item = core::pin::Pin<ItemRef<'_>>>,
2248    ) {
2249        let Some(accesskit_adapter_cell) = self.accesskit_adapter() else { return };
2250        if let Ok(mut a) = accesskit_adapter_cell.try_borrow_mut() {
2251            a.unregister_item_tree(component);
2252        };
2253    }
2254
2255    #[cfg(feature = "raw-window-handle-06")]
2256    fn window_handle_06_rc(
2257        &self,
2258    ) -> Result<Arc<dyn raw_window_handle::HasWindowHandle>, raw_window_handle::HandleError> {
2259        Ok(self
2260            .winit_window_or_none
2261            .borrow()
2262            .as_window()
2263            .ok_or(raw_window_handle::HandleError::Unavailable)?)
2264    }
2265
2266    #[cfg(feature = "raw-window-handle-06")]
2267    fn display_handle_06_rc(
2268        &self,
2269    ) -> Result<Arc<dyn raw_window_handle::HasDisplayHandle>, raw_window_handle::HandleError> {
2270        Ok(self
2271            .winit_window_or_none
2272            .borrow()
2273            .as_window()
2274            .ok_or(raw_window_handle::HandleError::Unavailable)?)
2275    }
2276
2277    fn bring_to_front(&self) -> Result<(), PlatformError> {
2278        if let Some(winit_window) = self.winit_window_or_none.borrow().as_window() {
2279            winit_window.set_minimized(false);
2280            winit_window.focus_window();
2281        }
2282        Ok(())
2283    }
2284
2285    #[cfg(target_os = "ios")]
2286    fn safe_area_inset(&self) -> i_slint_core::lengths::PhysicalEdges {
2287        self.winit_window_or_none
2288            .borrow()
2289            .as_window()
2290            .and_then(|window| {
2291                let outer_position = window.outer_position().ok()?;
2292                let inner_position = window.inner_position().ok()?;
2293                let outer_size = window.outer_size();
2294                let inner_size = window.inner_size();
2295                Some(i_slint_core::lengths::PhysicalEdges::new(
2296                    inner_position.y - outer_position.y,
2297                    outer_size.height as i32
2298                        - (inner_size.height as i32)
2299                        - (inner_position.y - outer_position.y),
2300                    inner_position.x - outer_position.x,
2301                    outer_size.width as i32
2302                        - (inner_size.width as i32)
2303                        - (inner_position.x - outer_position.x),
2304                ))
2305            })
2306            .unwrap_or_default()
2307    }
2308}
2309
2310impl Drop for WinitWindowAdapter {
2311    fn drop(&mut self) {
2312        self.shared_backend_data.unregister_window(
2313            self.winit_window_or_none.borrow().as_window().map(|winit_window| winit_window.id()),
2314        );
2315
2316        #[cfg(target_os = "macos")]
2317        if let Some(observer) = self.macos_color_observer.get() {
2318            unsafe {
2319                objc2_foundation::NSNotificationCenter::defaultCenter()
2320                    .removeObserver((*observer).as_ref());
2321            }
2322        }
2323    }
2324}
2325
2326// Winit doesn't automatically resize the window to satisfy constraints. Qt does it though, and so do we here.
2327fn adjust_window_size_to_satisfy_constraints(
2328    adapter: &WinitWindowAdapter,
2329    min_size: Option<winit::dpi::LogicalSize<f64>>,
2330    max_size: Option<winit::dpi::LogicalSize<f64>>,
2331) {
2332    let sf = adapter.window().scale_factor() as f64;
2333    let current_size = adapter
2334        .pending_requested_size
2335        .get()
2336        .unwrap_or_else(|| {
2337            let existing_adapter_size = adapter.size.get();
2338            physical_size_to_winit(existing_adapter_size).into()
2339        })
2340        .to_logical::<f64>(sf);
2341
2342    let mut window_size = current_size;
2343    if let Some(max_size) = max_size {
2344        let max_size = max_size.cast();
2345        window_size.width = window_size.width.min(max_size.width);
2346        window_size.height = window_size.height.min(max_size.height);
2347    }
2348
2349    // After the max clamp, so that a minimum above a fractional maximum wins
2350    // (staying below the minimum would cut off content, e.g. for a min == max
2351    // window). Raise a dimension whenever the physical rounding would land
2352    // below the minimum, not only when its logical value is below it (see
2353    // `round_up_logical`).
2354    if let Some(min_size) = min_size {
2355        let min_size = min_size.cast::<f64>();
2356        if (window_size.width * sf).round() < min_size.width * sf {
2357            window_size.width = round_up_logical(min_size.width, sf);
2358        }
2359        if (window_size.height * sf).round() < min_size.height * sf {
2360            window_size.height = round_up_logical(min_size.height, sf);
2361        }
2362    }
2363
2364    if window_size != current_size {
2365        // TODO: don't ignore error, propagate to caller
2366        adapter.resize_window(window_size.into()).ok();
2367    }
2368}
2369
2370#[cfg(target_arch = "wasm32")]
2371fn query_wasm_accent_color() -> Color {
2372    (|| {
2373        use wasm_bindgen::JsCast;
2374        let window = web_sys::window()?;
2375        let document = window.document()?;
2376        let element = document.create_element("span").ok()?;
2377        let html_element: &web_sys::HtmlElement = element.dyn_ref()?;
2378        html_element.style().set_property("color", "AccentColor").ok()?;
2379        // If the browser doesn't support AccentColor, the property won't be set
2380        if html_element.style().get_property_value("color").ok()?.is_empty() {
2381            return None;
2382        }
2383        html_element.style().set_property("display", "none").ok()?;
2384        document.body()?.append_child(&element).ok()?;
2385        let color_str =
2386            window.get_computed_style(&element).ok()??.get_property_value("color").ok()?;
2387        element.remove();
2388        // Parse "rgb(r, g, b)" computed color string
2389        let inner = color_str.strip_prefix("rgb(")?.strip_suffix(')')?;
2390        let mut parts = inner.split(',').map(|p| p.trim().parse::<u8>().ok());
2391        Some(Color::from_argb_u8(255, parts.next()??, parts.next()??, parts.next()??))
2392    })()
2393    .unwrap_or_default()
2394}
2395
2396#[cfg(target_family = "wasm")]
2397fn is_preferred_sized_canvas(canvas: &web_sys::HtmlCanvasElement) -> bool {
2398    canvas
2399        .dataset()
2400        .get("slintAutoResizeToPreferred")
2401        .and_then(|val_str| val_str.parse::<bool>().ok())
2402        .unwrap_or_default()
2403}
2404
2405#[cfg(target_family = "wasm")]
2406fn canvas_has_explicit_size_set(canvas: &web_sys::HtmlCanvasElement) -> bool {
2407    let style = canvas.style();
2408    if !style.get_property_value("width").unwrap_or_default().is_empty()
2409        || !style.get_property_value("height").unwrap_or_default().is_empty()
2410    {
2411        return true;
2412    }
2413
2414    let Some(window) = web_sys::window() else {
2415        return false;
2416    };
2417    let Some(computed_style) = window.get_computed_style(&canvas).ok().flatten() else {
2418        return false;
2419    };
2420
2421    computed_style.get_property_value("width").ok().as_deref() != Some("auto")
2422        || computed_style.get_property_value("height").ok().as_deref() != Some("auto")
2423}