slint/internal/backends/winit/winitwindowadapter.rs
Simon Hausmann 23c1989e59
Some checks failed
autofix.ci / format_fix (push) Has been cancelled
autofix.ci / lint_typecheck (push) Has been cancelled
CI / ffi_32bit_build (push) Has been cancelled
CI / updater_test (0.3.0) (push) Has been cancelled
CI / fmt_test (push) Has been cancelled
CI / esp-idf-quick (push) Has been cancelled
CI / android (push) Has been cancelled
CI / miri (push) Has been cancelled
CI / test-figma-inspector (push) Has been cancelled
CI / files-changed (push) Has been cancelled
CI / python_test (macos-14) (push) Has been cancelled
CI / python_test (ubuntu-22.04) (push) Has been cancelled
CI / build_and_test (--exclude ffmpeg --exclude gstreamer-player, macos-14, stable) (push) Has been cancelled
CI / build_and_test (--exclude ffmpeg --exclude gstreamer-player, windows-2022, 1.82) (push) Has been cancelled
CI / build_and_test (--exclude ffmpeg --exclude gstreamer-player, windows-2022, beta) (push) Has been cancelled
CI / build_and_test (--exclude ffmpeg --exclude gstreamer-player, windows-2022, stable) (push) Has been cancelled
CI / build_and_test (ubuntu-22.04, 1.82) (push) Has been cancelled
CI / build_and_test (ubuntu-22.04, nightly) (push) Has been cancelled
CI / node_test (macos-14) (push) Has been cancelled
CI / node_test (ubuntu-22.04) (push) Has been cancelled
CI / node_test (windows-2022) (push) Has been cancelled
CI / docs (push) Has been cancelled
CI / python_test (windows-2022) (push) Has been cancelled
CI / cpp_test_driver (macos-13) (push) Has been cancelled
CI / cpp_test_driver (ubuntu-22.04) (push) Has been cancelled
CI / cpp_test_driver (windows-2022) (push) Has been cancelled
CI / wasm (push) Has been cancelled
CI / cpp_cmake (macos-14, 1.82) (push) Has been cancelled
CI / cpp_cmake (ubuntu-22.04, stable) (push) Has been cancelled
CI / cpp_cmake (windows-2022, nightly) (push) Has been cancelled
CI / cpp_package_test (push) Has been cancelled
CI / wasm_demo (push) Has been cancelled
CI / tree-sitter (push) Has been cancelled
CI / vsce_build_test (push) Has been cancelled
CI / mcu (pico-st7789, thumbv6m-none-eabi) (push) Has been cancelled
CI / mcu (pico2-st7789, thumbv8m.main-none-eabihf) (push) Has been cancelled
CI / mcu (stm32h735g, thumbv7em-none-eabihf) (push) Has been cancelled
CI / mcu-embassy (push) Has been cancelled
winit: Fix preferred window size not being taken into account when it changes multiple times between window creation and the resize event received from the window manager
In this phase between the window being resized and receiving the resize event, the pending_requested_size variable tracks the value to be used in adjust_window_size_to_satisfy_constraints. Unfortunately,
it was never set.

Amends commit 1c73144b09.

Fixes #8452
2025-05-17 10:13:08 +02:00

1363 lines
54 KiB
Rust

// Copyright © SixtyFPS GmbH <info@slint.dev>
// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0
//! This module contains the GraphicsWindow that used to be within corelib.
// cspell:ignore accesskit borderless corelib nesw webgl winit winsys xlib
use core::cell::{Cell, RefCell};
use core::pin::Pin;
use std::rc::Rc;
use std::rc::Weak;
use std::sync::Arc;
use i_slint_core::lengths::{PhysicalPx, ScaleFactor};
use winit::event_loop::ActiveEventLoop;
#[cfg(target_arch = "wasm32")]
use winit::platform::web::WindowExtWebSys;
#[cfg(target_family = "windows")]
use winit::platform::windows::WindowExtWindows;
use crate::renderer::WinitCompatibleRenderer;
use corelib::item_tree::ItemTreeRc;
#[cfg(enable_accesskit)]
use corelib::item_tree::ItemTreeRef;
use corelib::items::{ColorScheme, MouseCursor};
#[cfg(enable_accesskit)]
use corelib::items::{ItemRc, ItemRef};
#[cfg(any(enable_accesskit, muda))]
use crate::SlintEvent;
use crate::{SharedBackendData, WinitWindowEventResult};
use corelib::api::PhysicalSize;
use corelib::layout::Orientation;
use corelib::lengths::LogicalLength;
use corelib::platform::{PlatformError, WindowEvent};
use corelib::window::{WindowAdapter, WindowAdapterInternal, WindowInner};
use corelib::Property;
use corelib::{graphics::*, Coord};
use i_slint_core::{self as corelib, graphics::RequestedGraphicsAPI};
use std::cell::OnceCell;
#[cfg(any(enable_accesskit, muda))]
use winit::event_loop::EventLoopProxy;
use winit::window::{WindowAttributes, WindowButtons};
fn position_to_winit(pos: &corelib::api::WindowPosition) -> winit::dpi::Position {
match pos {
corelib::api::WindowPosition::Logical(pos) => {
winit::dpi::Position::new(winit::dpi::LogicalPosition::new(pos.x, pos.y))
}
corelib::api::WindowPosition::Physical(pos) => {
winit::dpi::Position::new(winit::dpi::PhysicalPosition::new(pos.x, pos.y))
}
}
}
fn window_size_to_winit(size: &corelib::api::WindowSize) -> winit::dpi::Size {
match size {
corelib::api::WindowSize::Logical(size) => {
winit::dpi::Size::new(logical_size_to_winit(*size))
}
corelib::api::WindowSize::Physical(size) => {
winit::dpi::Size::new(physical_size_to_winit(*size))
}
}
}
pub fn physical_size_to_slint(size: &winit::dpi::PhysicalSize<u32>) -> corelib::api::PhysicalSize {
corelib::api::PhysicalSize::new(size.width, size.height)
}
fn logical_size_to_winit(s: i_slint_core::api::LogicalSize) -> winit::dpi::LogicalSize<f32> {
winit::dpi::LogicalSize::new(s.width, s.height)
}
fn physical_size_to_winit(size: PhysicalSize) -> winit::dpi::PhysicalSize<u32> {
winit::dpi::PhysicalSize::new(size.width, size.height)
}
fn icon_to_winit(
icon: corelib::graphics::Image,
size: euclid::Size2D<Coord, PhysicalPx>,
) -> Option<winit::window::Icon> {
let image_inner: &ImageInner = (&icon).into();
let Some(pixel_buffer) = image_inner.render_to_buffer(Some(size.cast())) else {
return None;
};
// This could become a method in SharedPixelBuffer...
let rgba_pixels: Vec<u8> = match &pixel_buffer {
SharedImageBuffer::RGB8(pixels) => pixels
.as_bytes()
.chunks(3)
.flat_map(|rgb| IntoIterator::into_iter([rgb[0], rgb[1], rgb[2], 255]))
.collect(),
SharedImageBuffer::RGBA8(pixels) => pixels.as_bytes().to_vec(),
SharedImageBuffer::RGBA8Premultiplied(pixels) => pixels
.as_bytes()
.chunks(4)
.flat_map(|rgba| {
let alpha = rgba[3] as u32;
IntoIterator::into_iter(rgba)
.take(3)
.map(move |component| (*component as u32 * alpha / 255) as u8)
.chain(std::iter::once(alpha as u8))
})
.collect(),
};
winit::window::Icon::from_rgba(rgba_pixels, pixel_buffer.width(), pixel_buffer.height()).ok()
}
fn window_is_resizable(
min_size: Option<corelib::api::LogicalSize>,
max_size: Option<corelib::api::LogicalSize>,
) -> bool {
if let Some((
corelib::api::LogicalSize { width: min_width, height: min_height, .. },
corelib::api::LogicalSize { width: max_width, height: max_height, .. },
)) = min_size.zip(max_size)
{
min_width < max_width || min_height < max_height
} else {
true
}
}
enum WinitWindowOrNone {
HasWindow {
window: Arc<winit::window::Window>,
#[cfg(enable_accesskit)]
accesskit_adapter: RefCell<crate::accesskit::AccessKitAdapter>,
#[cfg(muda)]
muda_adapter: RefCell<Option<crate::muda::MudaAdapter>>,
},
None(RefCell<WindowAttributes>),
}
impl WinitWindowOrNone {
fn as_window(&self) -> Option<Arc<winit::window::Window>> {
match self {
Self::HasWindow { window, .. } => Some(window.clone()),
Self::None { .. } => None,
}
}
fn set_window_icon(&self, icon: Option<winit::window::Icon>) {
match self {
Self::HasWindow { window, .. } => {
#[cfg(target_family = "windows")]
window.set_taskbar_icon(icon.as_ref().cloned());
window.set_window_icon(icon);
}
Self::None(attributes) => attributes.borrow_mut().window_icon = icon,
}
}
fn set_title(&self, title: &str) {
match self {
Self::HasWindow { window, .. } => window.set_title(title),
Self::None(attributes) => attributes.borrow_mut().title = title.into(),
}
}
fn set_decorations(&self, decorations: bool) {
match self {
Self::HasWindow { window, .. } => window.set_decorations(decorations),
Self::None(attributes) => attributes.borrow_mut().decorations = decorations,
}
}
fn fullscreen(&self) -> Option<winit::window::Fullscreen> {
match self {
Self::HasWindow { window, .. } => window.fullscreen(),
Self::None(attributes) => attributes.borrow().fullscreen.clone(),
}
}
fn set_fullscreen(&self, fullscreen: Option<winit::window::Fullscreen>) {
match self {
Self::HasWindow { window, .. } => window.set_fullscreen(fullscreen),
Self::None(attributes) => attributes.borrow_mut().fullscreen = fullscreen,
}
}
fn set_window_level(&self, level: winit::window::WindowLevel) {
match self {
Self::HasWindow { window, .. } => window.set_window_level(level),
Self::None(attributes) => attributes.borrow_mut().window_level = level,
}
}
fn set_visible(&self, visible: bool) {
match self {
Self::HasWindow { window, .. } => window.set_visible(visible),
Self::None(attributes) => attributes.borrow_mut().visible = visible,
}
}
fn set_maximized(&self, maximized: bool) {
match self {
Self::HasWindow { window, .. } => window.set_maximized(maximized),
Self::None(attributes) => attributes.borrow_mut().maximized = maximized,
}
}
fn set_minimized(&self, minimized: bool) {
match self {
Self::HasWindow { window, .. } => window.set_minimized(minimized),
Self::None(..) => { /* TODO: winit is missing attributes.borrow_mut().minimized = minimized*/
}
}
}
fn set_resizable(&self, resizable: bool) {
match self {
Self::HasWindow { window, .. } => {
window.set_resizable(resizable);
// Workaround for winit bug #2990
// Non-resizable windows can still contain a maximize button,
// so we'd have to additionally remove the button.
let mut buttons = window.enabled_buttons();
buttons.set(WindowButtons::MAXIMIZE, resizable);
window.set_enabled_buttons(buttons);
}
Self::None(attributes) => attributes.borrow_mut().resizable = resizable,
}
}
fn set_min_inner_size<S: Into<winit::dpi::Size>>(&self, min_inner_size: Option<S>) {
match self {
Self::HasWindow { window, .. } => window.set_min_inner_size(min_inner_size),
Self::None(attributes) => {
attributes.borrow_mut().min_inner_size = min_inner_size.map(|s| s.into());
}
}
}
fn set_max_inner_size<S: Into<winit::dpi::Size>>(&self, max_inner_size: Option<S>) {
match self {
Self::HasWindow { window, .. } => window.set_max_inner_size(max_inner_size),
Self::None(attributes) => {
attributes.borrow_mut().max_inner_size = max_inner_size.map(|s| s.into())
}
}
}
}
#[derive(Default, PartialEq, Clone, Copy)]
enum WindowVisibility {
#[default]
Hidden,
/// This implies that we might resize the window the first time it's shown.
ShownFirstTime,
Shown,
}
/// GraphicsWindow is an implementation of the [WindowAdapter][`crate::eventloop::WindowAdapter`] trait. This is
/// typically instantiated by entry factory functions of the different graphics back ends.
pub struct WinitWindowAdapter {
pub shared_backend_data: Rc<SharedBackendData>,
window: OnceCell<corelib::api::Window>,
self_weak: Weak<Self>,
pending_redraw: Cell<bool>,
color_scheme: OnceCell<Pin<Box<Property<ColorScheme>>>>,
constraints: Cell<corelib::window::LayoutConstraints>,
/// Indicates if the window is shown, from the perspective of the API user.
shown: Cell<WindowVisibility>,
window_level: Cell<winit::window::WindowLevel>,
maximized: Cell<bool>,
minimized: Cell<bool>,
fullscreen: Cell<bool>,
pub(crate) renderer: Box<dyn WinitCompatibleRenderer>,
requested_graphics_api: Option<RequestedGraphicsAPI>,
/// We cache the size because winit_window.inner_size() can return different value between calls (eg, on X11)
/// And we wan see the newer value before the Resized event was received, leading to inconsistencies
size: Cell<PhysicalSize>,
/// We requested a size to be set, but we didn't get the resize event from winit yet
pending_requested_size: Cell<Option<winit::dpi::Size>>,
/// Whether the size has been set explicitly via `set_size`.
/// If that's the case, we should't resize to the preferred size in set_visible
has_explicit_size: Cell<bool>,
/// Indicate whether we've ever received a resize event from winit after showing the window.
pending_resize_event_after_show: Cell<bool>,
#[cfg(target_arch = "wasm32")]
virtual_keyboard_helper: RefCell<Option<super::wasm_input_helper::WasmInputHelper>>,
#[cfg(any(enable_accesskit, muda))]
event_loop_proxy: EventLoopProxy<SlintEvent>,
pub(crate) window_event_filter: Cell<
Option<
Box<
dyn FnMut(
&corelib::api::Window,
&winit::event::WindowEvent,
) -> WinitWindowEventResult,
>,
>,
>,
winit_window_or_none: RefCell<WinitWindowOrNone>,
#[cfg(not(use_winit_theme))]
xdg_settings_watcher: RefCell<Option<i_slint_core::future::JoinHandle<()>>>,
#[cfg(muda)]
menubar: RefCell<Option<vtable::VBox<i_slint_core::menus::MenuVTable>>>,
#[cfg(all(muda, target_os = "macos"))]
muda_enable_default_menu_bar: bool,
/// Winit's window_icon API has no way of checking if the window icon is
/// the same as a previously set one, so keep track of that here.
window_icon_cache_key: RefCell<Option<ImageCacheKey>>,
}
impl WinitWindowAdapter {
/// Creates a new reference-counted instance.
pub(crate) fn new(
shared_backend_data: Rc<SharedBackendData>,
renderer: Box<dyn WinitCompatibleRenderer>,
window_attributes: winit::window::WindowAttributes,
requested_graphics_api: Option<RequestedGraphicsAPI>,
#[cfg(any(enable_accesskit, muda))] proxy: EventLoopProxy<SlintEvent>,
#[cfg(all(muda, target_os = "macos"))] muda_enable_default_menu_bar: bool,
) -> Result<Rc<Self>, PlatformError> {
let self_rc = Rc::new_cyclic(|self_weak| Self {
shared_backend_data,
window: OnceCell::from(corelib::api::Window::new(self_weak.clone() as _)),
self_weak: self_weak.clone(),
pending_redraw: Default::default(),
color_scheme: Default::default(),
constraints: Default::default(),
shown: Default::default(),
window_level: Default::default(),
maximized: Cell::default(),
minimized: Cell::default(),
fullscreen: Cell::default(),
winit_window_or_none: RefCell::new(WinitWindowOrNone::None(window_attributes.into())),
size: Cell::default(),
pending_requested_size: Cell::new(None),
has_explicit_size: Default::default(),
pending_resize_event_after_show: Default::default(),
renderer,
requested_graphics_api,
#[cfg(target_arch = "wasm32")]
virtual_keyboard_helper: Default::default(),
#[cfg(any(enable_accesskit, muda))]
event_loop_proxy: proxy,
window_event_filter: Cell::new(None),
#[cfg(not(use_winit_theme))]
xdg_settings_watcher: Default::default(),
#[cfg(muda)]
menubar: Default::default(),
#[cfg(all(muda, target_os = "macos"))]
muda_enable_default_menu_bar,
window_icon_cache_key: Default::default(),
});
self_rc.shared_backend_data.register_inactive_window((self_rc.clone()) as _);
Ok(self_rc)
}
fn renderer(&self) -> &dyn WinitCompatibleRenderer {
self.renderer.as_ref()
}
pub fn ensure_window(
&self,
active_event_loop: &ActiveEventLoop,
) -> Result<Arc<winit::window::Window>, PlatformError> {
#[allow(unused_mut)]
let mut window_attributes = match &*self.winit_window_or_none.borrow() {
WinitWindowOrNone::HasWindow { window, .. } => return Ok(window.clone()),
WinitWindowOrNone::None(attributes) => attributes.borrow().clone(),
};
#[cfg(all(unix, not(target_vendor = "apple")))]
{
if let Some(xdg_app_id) = WindowInner::from_pub(self.window()).xdg_app_id() {
#[cfg(feature = "wayland")]
{
use winit::platform::wayland::WindowAttributesExtWayland;
window_attributes = window_attributes.with_name(xdg_app_id.clone(), "");
}
#[cfg(feature = "x11")]
{
use winit::platform::x11::WindowAttributesExtX11;
window_attributes = window_attributes.with_name(xdg_app_id.clone(), "");
}
}
}
let mut winit_window_or_none = self.winit_window_or_none.borrow_mut();
// Never show the window right away, as we
// a) need to compute the correct size based on the scale factor before it's shown on the screen (handled by set_visible)
// b) need to create the accesskit adapter before it's shown on the screen, as required by accesskit.
let show_after_creation = std::mem::replace(&mut window_attributes.visible, false);
let winit_window = self.renderer.resume(
active_event_loop,
window_attributes,
self.requested_graphics_api.clone(),
)?;
let scale_factor = std::env::var("SLINT_SCALE_FACTOR")
.ok()
.and_then(|x| x.parse::<f32>().ok())
.filter(|f| *f > 0.)
.unwrap_or_else(|| winit_window.scale_factor() as f32);
self.window().try_dispatch_event(WindowEvent::ScaleFactorChanged { scale_factor })?;
*winit_window_or_none = WinitWindowOrNone::HasWindow {
window: winit_window.clone(),
#[cfg(enable_accesskit)]
accesskit_adapter: crate::accesskit::AccessKitAdapter::new(
self.self_weak.clone(),
active_event_loop,
&winit_window,
self.event_loop_proxy.clone(),
)
.into(),
#[cfg(muda)]
muda_adapter: self
.menubar
.borrow()
.as_ref()
.map(|menubar| {
crate::muda::MudaAdapter::setup(
menubar,
&winit_window,
self.event_loop_proxy.clone(),
self.self_weak.clone(),
)
})
.into(),
};
drop(winit_window_or_none);
if show_after_creation {
self.shown.set(WindowVisibility::Hidden);
self.set_visibility(WindowVisibility::ShownFirstTime)?;
}
self.shared_backend_data
.register_window(winit_window.id(), (self.self_weak.upgrade().unwrap()) as _);
Ok(winit_window)
}
fn suspend(&self) -> Result<(), PlatformError> {
let mut winit_window_or_none = self.winit_window_or_none.borrow_mut();
match *winit_window_or_none {
WinitWindowOrNone::HasWindow { ref window, .. } => {
self.renderer().suspend()?;
let last_window_rc = window.clone();
let mut attributes = Self::window_attributes().unwrap_or_default();
attributes.inner_size = Some(physical_size_to_winit(self.size.get()).into());
attributes.position = last_window_rc.outer_position().ok().map(|pos| pos.into());
*winit_window_or_none = WinitWindowOrNone::None(attributes.into());
if let Some(last_instance) = Arc::into_inner(last_window_rc) {
self.shared_backend_data.unregister_window(Some(last_instance.id()));
drop(last_instance);
} else {
i_slint_core::debug_log!(
"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"
);
}
}
WinitWindowOrNone::None(ref attributes) => {
attributes.borrow_mut().visible = false;
}
}
Ok(())
}
pub(crate) fn window_attributes() -> Result<WindowAttributes, PlatformError> {
let mut attrs = WindowAttributes::default().with_transparent(true).with_visible(false);
attrs = attrs.with_title("Slint Window".to_string());
#[cfg(target_arch = "wasm32")]
{
use winit::platform::web::WindowAttributesExtWebSys;
use wasm_bindgen::JsCast;
if let Some(html_canvas) = web_sys::window()
.ok_or_else(|| "winit backend: Could not retrieve DOM window".to_string())?
.document()
.ok_or_else(|| "winit backend: Could not retrieve DOM document".to_string())?
.get_element_by_id("canvas")
.and_then(|canvas_elem| canvas_elem.dyn_into::<web_sys::HtmlCanvasElement>().ok())
{
attrs = attrs
.with_canvas(Some(html_canvas))
// Don't activate the window by default, as that will cause the page to scroll,
// ignoring any existing anchors.
.with_active(false);
}
};
Ok(attrs)
}
/// Draw the items of the specified `component` in the given window.
pub fn draw(&self) -> Result<(), PlatformError> {
if matches!(self.shown.get(), WindowVisibility::Hidden) {
return Ok(()); // caller bug, doesn't make sense to call draw() when not shown
}
self.pending_redraw.set(false);
if let Some(winit_window) = self.winit_window_or_none.borrow().as_window() {
// on macOS we sometimes don't get a resize event after calling
// request_inner_size(), it returning None (promising a resize event), and then delivering RedrawRequested. To work around this,
// catch up here to ensure the renderer can resize the surface correctly.
// Note: On displays with a scale factor != 1, we get a scale factor change
// event and a resize event, so all is good.
if self.pending_resize_event_after_show.take() {
self.resize_event(winit_window.inner_size())?;
}
}
let renderer = self.renderer();
renderer.render(self.window())?;
Ok(())
}
pub fn winit_window(&self) -> Option<Arc<winit::window::Window>> {
self.winit_window_or_none.borrow().as_window()
}
#[cfg(muda)]
pub fn rebuild_menubar(&self) {
let WinitWindowOrNone::HasWindow {
window: winit_window,
muda_adapter: maybe_muda_adapter,
..
} = &*self.winit_window_or_none.borrow()
else {
return;
};
let mut maybe_muda_adapter = maybe_muda_adapter.borrow_mut();
let Some(muda_adapter) = maybe_muda_adapter.as_mut() else { return };
muda_adapter.rebuild_menu(&winit_window, self.menubar.borrow().as_ref());
}
#[cfg(muda)]
pub fn muda_event(&self, entry_id: usize) {
let Ok(maybe_muda_adapter) = std::cell::Ref::filter_map(
self.winit_window_or_none.borrow(),
|winit_window_or_none| match winit_window_or_none {
WinitWindowOrNone::HasWindow { muda_adapter, .. } => Some(muda_adapter),
WinitWindowOrNone::None(..) => None,
},
) else {
return;
};
let maybe_muda_adapter = maybe_muda_adapter.borrow();
let Some(muda_adapter) = maybe_muda_adapter.as_ref() else { return };
let menubar = self.menubar.borrow();
let Some(menubar) = menubar.as_ref() else { return };
muda_adapter.invoke(menubar, entry_id);
}
#[cfg(target_arch = "wasm32")]
pub fn input_method_focused(&self) -> bool {
match self.virtual_keyboard_helper.try_borrow() {
Ok(vkh) => vkh.as_ref().map_or(false, |h| h.has_focus()),
// the only location in which the virtual_keyboard_helper is mutably borrowed is from
// show_virtual_keyboard, which means we have the focus
Err(_) => true,
}
}
#[cfg(not(target_arch = "wasm32"))]
pub fn input_method_focused(&self) -> bool {
false
}
// Requests for the window to be resized. Returns true if the window was resized immediately,
// or if it will be resized later (false).
fn resize_window(&self, size: winit::dpi::Size) -> Result<bool, PlatformError> {
match &*self.winit_window_or_none.borrow() {
WinitWindowOrNone::HasWindow { window, .. } => {
if let Some(size) = window.request_inner_size(size) {
// On wayland we might not get a WindowEvent::Resized, so resize the EGL surface right away.
self.resize_event(size)?;
Ok(true)
} else {
self.pending_requested_size.set(size.into());
// None means that we'll get a `WindowEvent::Resized` later
Ok(false)
}
}
WinitWindowOrNone::None(attributes) => {
let scale_factor = self.window().scale_factor() as _;
// Avoid storing the physical size in the attributes. When creating a new window, we don't know the scale
// factor, so we've computed the desired size based on a factor of 1 and provided the physical size
// will be wrong when the window is created. So stick to a logical size.
attributes.borrow_mut().inner_size =
Some(size.to_logical::<f64>(scale_factor).into());
self.resize_event(size.to_physical(scale_factor))?;
Ok(true)
}
}
}
pub fn resize_event(&self, size: winit::dpi::PhysicalSize<u32>) -> Result<(), PlatformError> {
self.pending_resize_event_after_show.set(false);
// When a window is minimized on Windows, we get a move event to an off-screen position
// and a resize even with a zero size. Don't forward that, especially not to the renderer,
// which might panic when trying to create a zero-sized surface.
if size.width > 0 && size.height > 0 {
let physical_size = physical_size_to_slint(&size);
self.size.set(physical_size);
self.pending_requested_size.set(None);
let scale_factor = WindowInner::from_pub(self.window()).scale_factor();
self.window().try_dispatch_event(WindowEvent::Resized {
size: physical_size.to_logical(scale_factor),
})?;
// Workaround fox winit not sync'ing CSS size of the canvas (the size shown on the browser)
// with the width/height attribute (the size of the viewport/GL surface)
// If they're not in sync, the UI would be shown as scaled
#[cfg(target_arch = "wasm32")]
if let Some(html_canvas) = self
.winit_window_or_none
.borrow()
.as_window()
.and_then(|winit_window| winit_window.canvas())
{
html_canvas.set_width(physical_size.width);
html_canvas.set_height(physical_size.height);
}
}
Ok(())
}
pub fn set_color_scheme(&self, scheme: ColorScheme) {
self.color_scheme
.get_or_init(|| Box::pin(Property::new(ColorScheme::Unknown)))
.as_ref()
.set(scheme);
// Inform winit about the selected color theme, so that the window decoration is drawn correctly.
#[cfg(not(use_winit_theme))]
if let Some(winit_window) = self.winit_window() {
winit_window.set_theme(match scheme {
ColorScheme::Unknown => None,
ColorScheme::Dark => Some(winit::window::Theme::Dark),
ColorScheme::Light => Some(winit::window::Theme::Light),
});
}
}
pub fn window_state_event(&self) {
let Some(winit_window) = self.winit_window_or_none.borrow().as_window() else { return };
if let Some(minimized) = winit_window.is_minimized() {
self.minimized.set(minimized);
if minimized != self.window().is_minimized() {
self.window().set_minimized(minimized);
}
}
// The method winit::Window::is_maximized returns false when the window
// is minimized, even if it was previously maximized. We have to ensure
// that we only update the internal maximized state when the window is
// not minimized. Otherwise, the window would be restored in a
// non-maximized state even if it was maximized before being minimized.
let maximized = winit_window.is_maximized();
if !self.window().is_minimized() {
self.maximized.set(maximized);
if maximized != self.window().is_maximized() {
self.window().set_maximized(maximized);
}
}
// NOTE: Fullscreen overrides maximized so if both are true then the
// window will remain in fullscreen. Fullscreen must be false to switch
// to maximized.
let fullscreen = winit_window.fullscreen().is_some();
if fullscreen != self.window().is_fullscreen() {
self.window().set_fullscreen(fullscreen);
}
}
#[cfg(enable_accesskit)]
pub(crate) fn accesskit_adapter(
&self,
) -> Option<std::cell::Ref<'_, RefCell<crate::accesskit::AccessKitAdapter>>> {
std::cell::Ref::filter_map(
self.winit_window_or_none.try_borrow().ok()?,
|wor: &WinitWindowOrNone| match wor {
WinitWindowOrNone::HasWindow { accesskit_adapter, .. } => Some(accesskit_adapter),
WinitWindowOrNone::None(..) => None,
},
)
.ok()
}
#[cfg(enable_accesskit)]
pub(crate) fn with_access_kit_adapter_from_weak_window_adapter(
self_weak: Weak<Self>,
callback: impl FnOnce(&RefCell<crate::accesskit::AccessKitAdapter>),
) {
let Some(self_) = self_weak.upgrade() else { return };
let winit_window_or_none = self_.winit_window_or_none.borrow();
match &*winit_window_or_none {
WinitWindowOrNone::HasWindow { accesskit_adapter, .. } => callback(accesskit_adapter),
WinitWindowOrNone::None(..) => {}
}
}
#[cfg(not(use_winit_theme))]
fn spawn_xdg_settings_watcher(&self) -> Option<i_slint_core::future::JoinHandle<()>> {
let window_inner = WindowInner::from_pub(self.window());
let self_weak = self.self_weak.clone();
window_inner
.context()
.spawn_local(async move {
if let Err(err) = crate::xdg_color_scheme::watch(self_weak).await {
i_slint_core::debug_log!("Error watching for xdg color schemes: {}", err);
}
})
.ok()
}
pub fn activation_changed(&self, is_active: bool) -> Result<(), PlatformError> {
let have_focus = is_active || self.input_method_focused();
let slint_window = self.window();
let runtime_window = WindowInner::from_pub(slint_window);
// We don't render popups as separate windows yet, so treat
// focus to be the same as being active.
if have_focus != runtime_window.active() {
slint_window.try_dispatch_event(
corelib::platform::WindowEvent::WindowActiveChanged(have_focus),
)?;
}
#[cfg(all(muda, target_os = "macos"))]
{
if let WinitWindowOrNone::HasWindow { muda_adapter, .. } =
&*self.winit_window_or_none.borrow()
{
if muda_adapter.borrow().is_none()
&& self.muda_enable_default_menu_bar
&& self.menubar.borrow().is_none()
{
*muda_adapter.borrow_mut() =
Some(crate::muda::MudaAdapter::setup_default_menu_bar()?);
}
if let Some(muda_adapter) = muda_adapter.borrow().as_ref() {
muda_adapter.window_activation_changed(is_active);
}
}
}
Ok(())
}
fn set_visibility(&self, visibility: WindowVisibility) -> Result<(), PlatformError> {
if visibility == self.shown.get() {
return Ok(());
}
self.shown.set(visibility);
self.pending_resize_event_after_show.set(!matches!(visibility, WindowVisibility::Hidden));
self.pending_redraw.set(false);
if matches!(visibility, WindowVisibility::ShownFirstTime | WindowVisibility::Shown) {
let recreating_window = matches!(visibility, WindowVisibility::Shown);
let Some(winit_window) = self.winit_window() else {
// Can't really show it on the screen, safe it in the attributes and try again later.
self.winit_window_or_none.borrow().set_visible(true);
return Ok(());
};
let runtime_window = WindowInner::from_pub(self.window());
let scale_factor = runtime_window.scale_factor() as f64;
let component_rc = runtime_window.component();
let component = ItemTreeRc::borrow_pin(&component_rc);
let layout_info_h = component.as_ref().layout_info(Orientation::Horizontal);
if let Some(window_item) = runtime_window.window_item() {
// Setting the width to its preferred size before querying the vertical layout info
// is important in case the height depends on the width
window_item.width.set(LogicalLength::new(layout_info_h.preferred_bounded()));
}
let layout_info_v = component.as_ref().layout_info(Orientation::Vertical);
#[allow(unused_mut)]
let mut preferred_size = winit::dpi::LogicalSize::new(
layout_info_h.preferred_bounded(),
layout_info_v.preferred_bounded(),
);
#[cfg(target_arch = "wasm32")]
if let Some(html_canvas) = winit_window.canvas() {
let existing_canvas_size = winit::dpi::LogicalSize::new(
html_canvas.client_width() as f32,
html_canvas.client_height() as f32,
);
// Try to maintain the existing size of the canvas element, if any
if existing_canvas_size.width > 0. {
preferred_size.width = existing_canvas_size.width;
}
if existing_canvas_size.height > 0. {
preferred_size.height = existing_canvas_size.height;
}
}
if winit_window.fullscreen().is_none()
&& !self.has_explicit_size.get()
&& preferred_size.width > 0 as Coord
&& preferred_size.height > 0 as Coord
// Don't set the preferred size as the user may have resized the window
&& !recreating_window
{
// use the Slint's window Scale factor to take in account the override
let size = preferred_size.to_physical::<u32>(scale_factor);
self.resize_window(size.into())?;
};
winit_window.set_visible(true);
// Make sure the dark color scheme property is up-to-date, as it may have been queried earlier when
// the window wasn't mapped yet.
if let Some(color_scheme_prop) = self.color_scheme.get() {
if let Some(theme) = winit_window.theme() {
color_scheme_prop.as_ref().set(match theme {
winit::window::Theme::Dark => ColorScheme::Dark,
winit::window::Theme::Light => ColorScheme::Light,
})
}
}
// In wasm a request_redraw() issued before show() results in a draw() even when the window
// isn't visible, as opposed to regular windowing systems. The compensate for the lost draw,
// explicitly render the first frame on show().
#[cfg(target_arch = "wasm32")]
if self.pending_redraw.get() {
self.draw()?;
};
Ok(())
} else {
// Wayland doesn't support hiding a window, only destroying it entirely.
if self.winit_window_or_none.borrow().as_window().is_some_and(|winit_window| {
use raw_window_handle::HasWindowHandle;
winit_window.window_handle().is_ok_and(|h| {
matches!(h.as_raw(), raw_window_handle::RawWindowHandle::Wayland(..))
}) || std::env::var_os("SLINT_DESTROY_WINDOW_ON_HIDE").is_some()
}) {
self.suspend()?;
} else {
self.winit_window_or_none.borrow().set_visible(false);
}
/* FIXME:
if let Some(existing_blinker) = self.cursor_blinker.borrow().upgrade() {
existing_blinker.stop();
}*/
Ok(())
}
}
}
impl WindowAdapter for WinitWindowAdapter {
fn window(&self) -> &corelib::api::Window {
self.window.get().unwrap()
}
fn renderer(&self) -> &dyn i_slint_core::renderer::Renderer {
self.renderer().as_core_renderer()
}
fn set_visible(&self, visible: bool) -> Result<(), PlatformError> {
self.set_visibility(if visible {
WindowVisibility::Shown
} else {
WindowVisibility::Hidden
})
}
fn position(&self) -> Option<corelib::api::PhysicalPosition> {
match &*self.winit_window_or_none.borrow() {
WinitWindowOrNone::HasWindow { window, .. } => match window.outer_position() {
Ok(outer_position) => {
Some(corelib::api::PhysicalPosition::new(outer_position.x, outer_position.y))
}
Err(_) => None,
},
WinitWindowOrNone::None(attributes) => {
attributes.borrow().position.map(|pos| {
match pos {
winit::dpi::Position::Physical(phys_pos) => {
corelib::api::PhysicalPosition::new(phys_pos.x, phys_pos.y)
}
winit::dpi::Position::Logical(logical_pos) => {
// Best effort: Use the last known scale factor
corelib::api::LogicalPosition::new(
logical_pos.x as _,
logical_pos.y as _,
)
.to_physical(self.window().scale_factor())
}
}
})
}
}
}
fn set_position(&self, position: corelib::api::WindowPosition) {
let winit_pos = position_to_winit(&position);
match &*self.winit_window_or_none.borrow() {
WinitWindowOrNone::HasWindow { window, .. } => window.set_outer_position(winit_pos),
WinitWindowOrNone::None(attributes) => {
attributes.borrow_mut().position = Some(winit_pos);
}
}
}
fn set_size(&self, size: corelib::api::WindowSize) {
self.has_explicit_size.set(true);
// TODO: don't ignore error, propgate to caller
self.resize_window(window_size_to_winit(&size)).ok();
}
fn size(&self) -> corelib::api::PhysicalSize {
self.size.get()
}
fn request_redraw(&self) {
if !self.pending_redraw.replace(true) {
if let Some(window) = self.winit_window_or_none.borrow().as_window() {
window.request_redraw()
}
}
}
#[allow(clippy::unnecessary_cast)] // Coord is used!
fn update_window_properties(&self, properties: corelib::window::WindowProperties<'_>) {
let Some(window_item) =
self.window.get().and_then(|w| WindowInner::from_pub(w).window_item())
else {
return;
};
let window_item = window_item.as_pin_ref();
let winit_window_or_none = self.winit_window_or_none.borrow();
// Use our scale factor instead of winit's logical size to take a scale factor override into account.
let sf = self.window().scale_factor();
// Update the icon only if it changes, to avoid flashing.
let icon_image = window_item.icon();
let icon_image_cache_key = ImageCacheKey::new((&icon_image).into());
if *self.window_icon_cache_key.borrow() != icon_image_cache_key {
*self.window_icon_cache_key.borrow_mut() = icon_image_cache_key;
winit_window_or_none.set_window_icon(icon_to_winit(
icon_image,
i_slint_core::lengths::LogicalSize::new(64., 64.) * ScaleFactor::new(sf),
));
}
winit_window_or_none.set_title(&properties.title());
winit_window_or_none.set_decorations(
!window_item.no_frame() || winit_window_or_none.fullscreen().is_some(),
);
let new_window_level = if window_item.always_on_top() {
winit::window::WindowLevel::AlwaysOnTop
} else {
winit::window::WindowLevel::Normal
};
// Only change the window level if it changes, to avoid https://github.com/slint-ui/slint/issues/3280
// (Ubuntu 20.04's window manager always bringing the window to the front on x11)
if self.window_level.replace(new_window_level) != new_window_level {
winit_window_or_none.set_window_level(new_window_level);
}
let mut width = window_item.width().get() as f32;
let mut height = window_item.height().get() as f32;
let mut must_resize = false;
let existing_size = self.size.get().to_logical(sf);
if width <= 0. || height <= 0. {
must_resize = true;
if width <= 0. {
width = existing_size.width;
}
if height <= 0. {
height = existing_size.height;
}
}
// Adjust the size of the window to the value of the width and height property (if these property are changed from .slint).
// But not if there is a pending resize in flight as that resize will reset these properties back
if ((existing_size.width - width).abs() > 1. || (existing_size.height - height).abs() > 1.)
&& self.pending_requested_size.get().is_none()
{
// If we're in fullscreen state, don't try to resize the window but maintain the surface
// size we've been assigned to from the windowing system. Weston/Wayland don't like it
// when we create a surface that's bigger than the screen due to constraints (#532).
if winit_window_or_none.fullscreen().is_none() {
// TODO: don't ignore error, propgate to caller
let immediately_resized = self
.resize_window(winit::dpi::LogicalSize::new(width, height).into())
.unwrap_or_default();
if immediately_resized {
// The resize event was already dispatched
must_resize = false;
}
}
}
if must_resize {
self.window()
.try_dispatch_event(WindowEvent::Resized {
size: i_slint_core::api::LogicalSize::new(width, height),
})
.unwrap();
}
let m = properties.is_fullscreen();
if m != self.fullscreen.get() {
if m {
if winit_window_or_none.fullscreen().is_none() {
winit_window_or_none
.set_fullscreen(Some(winit::window::Fullscreen::Borderless(None)));
}
} else {
winit_window_or_none.set_fullscreen(None);
}
self.fullscreen.set(m);
}
let m = properties.is_maximized();
if m != self.maximized.get() {
self.maximized.set(m);
winit_window_or_none.set_maximized(m);
}
let m = properties.is_minimized();
if m != self.minimized.get() {
self.minimized.set(m);
winit_window_or_none.set_minimized(m);
}
// If we're in fullscreen, don't try to resize the window but
// maintain the surface size we've been assigned to from the
// windowing system. Weston/Wayland don't like it when we create a
// surface that's bigger than the screen due to constraints (#532).
if winit_window_or_none.fullscreen().is_some() {
return;
}
let new_constraints = properties.layout_constraints();
if new_constraints == self.constraints.get() {
return;
}
self.constraints.set(new_constraints);
let into_size = |s: corelib::api::LogicalSize| -> winit::dpi::PhysicalSize<f32> {
logical_size_to_winit(s).to_physical(sf as f64)
};
let resizable = window_is_resizable(new_constraints.min, new_constraints.max);
// we must call set_resizable before setting the min and max size otherwise setting the min and max size don't work on X11
winit_window_or_none.set_resizable(resizable);
let winit_min_inner = new_constraints.min.map(into_size);
winit_window_or_none.set_min_inner_size(winit_min_inner);
let winit_max_inner = new_constraints.max.map(into_size);
winit_window_or_none.set_max_inner_size(winit_max_inner);
// On ios, etc. apps are fullscreen and need to be responsive.
#[cfg(not(ios_and_friends))]
adjust_window_size_to_satisfy_constraints(self, winit_min_inner, winit_max_inner);
// Auto-resize to the preferred size if users (SlintPad) requests it
#[cfg(target_arch = "wasm32")]
if let Some(canvas) =
winit_window_or_none.as_window().and_then(|winit_window| winit_window.canvas())
{
if canvas
.dataset()
.get("slintAutoResizeToPreferred")
.and_then(|val_str| val_str.parse().ok())
.unwrap_or_default()
{
let pref = new_constraints.preferred;
if pref.width > 0 as Coord || pref.height > 0 as Coord {
// TODO: don't ignore error, propgate to caller
self.resize_window(logical_size_to_winit(pref).into()).ok();
};
}
}
}
fn internal(&self, _: corelib::InternalToken) -> Option<&dyn WindowAdapterInternal> {
Some(self)
}
}
impl WindowAdapterInternal for WinitWindowAdapter {
fn set_mouse_cursor(&self, cursor: MouseCursor) {
let winit_cursor = match cursor {
MouseCursor::Default => winit::window::CursorIcon::Default,
MouseCursor::None => winit::window::CursorIcon::Default,
MouseCursor::Help => winit::window::CursorIcon::Help,
MouseCursor::Pointer => winit::window::CursorIcon::Pointer,
MouseCursor::Progress => winit::window::CursorIcon::Progress,
MouseCursor::Wait => winit::window::CursorIcon::Wait,
MouseCursor::Crosshair => winit::window::CursorIcon::Crosshair,
MouseCursor::Text => winit::window::CursorIcon::Text,
MouseCursor::Alias => winit::window::CursorIcon::Alias,
MouseCursor::Copy => winit::window::CursorIcon::Copy,
MouseCursor::Move => winit::window::CursorIcon::Move,
MouseCursor::NoDrop => winit::window::CursorIcon::NoDrop,
MouseCursor::NotAllowed => winit::window::CursorIcon::NotAllowed,
MouseCursor::Grab => winit::window::CursorIcon::Grab,
MouseCursor::Grabbing => winit::window::CursorIcon::Grabbing,
MouseCursor::ColResize => winit::window::CursorIcon::ColResize,
MouseCursor::RowResize => winit::window::CursorIcon::RowResize,
MouseCursor::NResize => winit::window::CursorIcon::NResize,
MouseCursor::EResize => winit::window::CursorIcon::EResize,
MouseCursor::SResize => winit::window::CursorIcon::SResize,
MouseCursor::WResize => winit::window::CursorIcon::WResize,
MouseCursor::NeResize => winit::window::CursorIcon::NeResize,
MouseCursor::NwResize => winit::window::CursorIcon::NwResize,
MouseCursor::SeResize => winit::window::CursorIcon::SeResize,
MouseCursor::SwResize => winit::window::CursorIcon::SwResize,
MouseCursor::EwResize => winit::window::CursorIcon::EwResize,
MouseCursor::NsResize => winit::window::CursorIcon::NsResize,
MouseCursor::NeswResize => winit::window::CursorIcon::NeswResize,
MouseCursor::NwseResize => winit::window::CursorIcon::NwseResize,
};
if let Some(winit_window) = self.winit_window_or_none.borrow().as_window() {
winit_window.set_cursor_visible(cursor != MouseCursor::None);
winit_window.set_cursor(winit_cursor);
}
}
fn input_method_request(&self, request: corelib::window::InputMethodRequest) {
#[cfg(not(target_arch = "wasm32"))]
if let Some(winit_window) = self.winit_window_or_none.borrow().as_window() {
let props = match &request {
corelib::window::InputMethodRequest::Enable(props) => {
winit_window.set_ime_allowed(true);
props
}
corelib::window::InputMethodRequest::Disable => {
return winit_window.set_ime_allowed(false);
}
corelib::window::InputMethodRequest::Update(props) => props,
_ => return,
};
winit_window.set_ime_purpose(match props.input_type {
corelib::items::InputType::Password => winit::window::ImePurpose::Password,
_ => winit::window::ImePurpose::Normal,
});
winit_window.set_ime_cursor_area(
position_to_winit(&props.cursor_rect_origin.into()),
window_size_to_winit(&props.cursor_rect_size.into()),
);
}
#[cfg(target_arch = "wasm32")]
match request {
corelib::window::InputMethodRequest::Enable(..) => {
let mut vkh = self.virtual_keyboard_helper.borrow_mut();
let Some(canvas) =
self.winit_window().and_then(|winit_window| winit_window.canvas())
else {
return;
};
let h = vkh.get_or_insert_with(|| {
super::wasm_input_helper::WasmInputHelper::new(self.self_weak.clone(), canvas)
});
h.show();
}
corelib::window::InputMethodRequest::Disable => {
if let Some(h) = &*self.virtual_keyboard_helper.borrow() {
h.hide()
}
}
_ => {}
};
}
fn as_any(&self) -> &dyn std::any::Any {
self
}
fn color_scheme(&self) -> ColorScheme {
self.color_scheme
.get_or_init(|| {
Box::pin(Property::new({
cfg_if::cfg_if! {
if #[cfg(use_winit_theme)] {
self.winit_window_or_none
.borrow()
.as_window()
.and_then(|window| window.theme())
.map_or(ColorScheme::Unknown, |theme| match theme {
winit::window::Theme::Dark => ColorScheme::Dark,
winit::window::Theme::Light => ColorScheme::Light,
})
} else {
if let Some(old_watch) = self.xdg_settings_watcher.replace(self.spawn_xdg_settings_watcher()) {
old_watch.abort()
}
ColorScheme::Unknown
}
}
}))
})
.as_ref()
.get()
}
#[cfg(muda)]
fn supports_native_menu_bar(&self) -> bool {
true
}
#[cfg(muda)]
fn setup_menubar(&self, menubar: vtable::VBox<i_slint_core::menus::MenuVTable>) {
self.menubar.replace(Some(menubar));
if let WinitWindowOrNone::HasWindow { muda_adapter, .. } =
&*self.winit_window_or_none.borrow()
{
// On Windows, we must destroy the muda menu before re-creating a new one
drop(muda_adapter.borrow_mut().take());
muda_adapter.replace(Some(crate::muda::MudaAdapter::setup(
self.menubar.borrow().as_ref().unwrap(),
&self.winit_window().unwrap(),
self.event_loop_proxy.clone(),
self.self_weak.clone(),
)));
}
}
#[cfg(enable_accesskit)]
fn handle_focus_change(&self, _old: Option<ItemRc>, _new: Option<ItemRc>) {
let Some(accesskit_adapter_cell) = self.accesskit_adapter() else { return };
accesskit_adapter_cell.borrow_mut().handle_focus_item_change();
}
#[cfg(enable_accesskit)]
fn register_item_tree(&self) {
let Some(accesskit_adapter_cell) = self.accesskit_adapter() else { return };
// 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
if let Ok(mut a) = accesskit_adapter_cell.try_borrow_mut() {
a.reload_tree();
};
}
#[cfg(enable_accesskit)]
fn unregister_item_tree(
&self,
component: ItemTreeRef,
_: &mut dyn Iterator<Item = Pin<ItemRef<'_>>>,
) {
let Some(accesskit_adapter_cell) = self.accesskit_adapter() else { return };
if let Ok(mut a) = accesskit_adapter_cell.try_borrow_mut() {
a.unregister_item_tree(component);
};
}
#[cfg(feature = "raw-window-handle-06")]
fn window_handle_06_rc(
&self,
) -> Result<Arc<dyn raw_window_handle::HasWindowHandle>, raw_window_handle::HandleError> {
self.winit_window_or_none
.borrow()
.as_window()
.map_or(Err(raw_window_handle::HandleError::Unavailable), |window| Ok(window))
}
#[cfg(feature = "raw-window-handle-06")]
fn display_handle_06_rc(
&self,
) -> Result<Arc<dyn raw_window_handle::HasDisplayHandle>, raw_window_handle::HandleError> {
self.winit_window_or_none
.borrow()
.as_window()
.map_or(Err(raw_window_handle::HandleError::Unavailable), |window| Ok(window))
}
fn bring_to_front(&self) -> Result<(), PlatformError> {
if let Some(winit_window) = self.winit_window_or_none.borrow().as_window() {
winit_window.set_minimized(false);
winit_window.focus_window();
}
Ok(())
}
}
impl Drop for WinitWindowAdapter {
fn drop(&mut self) {
self.shared_backend_data.unregister_window(
self.winit_window_or_none.borrow().as_window().map(|winit_window| winit_window.id()),
);
#[cfg(not(use_winit_theme))]
if let Some(xdg_watch_future) = self.xdg_settings_watcher.take() {
xdg_watch_future.abort();
}
}
}
// Winit doesn't automatically resize the window to satisfy constraints. Qt does it though, and so do we here.
#[cfg(not(ios_and_friends))]
fn adjust_window_size_to_satisfy_constraints(
adapter: &WinitWindowAdapter,
min_size: Option<winit::dpi::PhysicalSize<f32>>,
max_size: Option<winit::dpi::PhysicalSize<f32>>,
) {
let current_size = adapter
.pending_requested_size
.get()
.map(|s| s.to_physical(adapter.window().scale_factor() as f64))
.unwrap_or_else(|| physical_size_to_winit(adapter.size.get()));
let mut window_size = current_size;
if let Some(min_size) = min_size {
let min_size = min_size.cast();
window_size.width = window_size.width.max(min_size.width);
window_size.height = window_size.height.max(min_size.height);
}
if let Some(max_size) = max_size {
let max_size = max_size.cast();
window_size.width = window_size.width.min(max_size.width);
window_size.height = window_size.height.min(max_size.height);
}
if window_size != current_size {
// TODO: don't ignore error, propgate to caller
adapter.resize_window(window_size.into()).ok();
}
}