| 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 | |
| 8 | use core::cell::{Cell, RefCell}; |
| 9 | use core::pin::Pin; |
| 10 | use std::rc::Rc; |
| 11 | use std::rc::Weak; |
| 12 | |
| 13 | #[cfg (target_arch = "wasm32" )] |
| 14 | use winit::platform::web::WindowExtWebSys; |
| 15 | #[cfg (target_family = "windows" )] |
| 16 | use winit::platform::windows::WindowExtWindows; |
| 17 | |
| 18 | use crate::renderer::WinitCompatibleRenderer; |
| 19 | |
| 20 | use corelib::item_tree::ItemTreeRc; |
| 21 | #[cfg (enable_accesskit)] |
| 22 | use corelib::item_tree::ItemTreeRef; |
| 23 | use corelib::items::{ColorScheme, MouseCursor}; |
| 24 | #[cfg (enable_accesskit)] |
| 25 | use corelib::items::{ItemRc, ItemRef}; |
| 26 | |
| 27 | #[cfg (any(enable_accesskit, muda))] |
| 28 | use crate::SlintUserEvent; |
| 29 | use crate::{SharedBackendData, WinitWindowEventResult}; |
| 30 | use corelib::api::PhysicalSize; |
| 31 | use corelib::layout::Orientation; |
| 32 | use corelib::lengths::LogicalLength; |
| 33 | use corelib::platform::{PlatformError, WindowEvent}; |
| 34 | use corelib::window::{WindowAdapter, WindowAdapterInternal, WindowInner}; |
| 35 | use corelib::Property; |
| 36 | use corelib::{graphics::*, Coord}; |
| 37 | use i_slint_core::{self as corelib, graphics::RequestedGraphicsAPI}; |
| 38 | use std::cell::OnceCell; |
| 39 | #[cfg (any(enable_accesskit, muda))] |
| 40 | use winit::event_loop::EventLoopProxy; |
| 41 | use winit::window::{WindowAttributes, WindowButtons}; |
| 42 | |
| 43 | fn position_to_winit(pos: &corelib::api::WindowPosition) -> winit::dpi::Position { |
| 44 | match pos { |
| 45 | corelib::api::WindowPosition::Logical(pos: &LogicalPosition) => { |
| 46 | winit::dpi::Position::new(position:winit::dpi::LogicalPosition::new(pos.x, pos.y)) |
| 47 | } |
| 48 | corelib::api::WindowPosition::Physical(pos: &PhysicalPosition) => { |
| 49 | winit::dpi::Position::new(position:winit::dpi::PhysicalPosition::new(pos.x, pos.y)) |
| 50 | } |
| 51 | } |
| 52 | } |
| 53 | |
| 54 | fn window_size_to_winit(size: &corelib::api::WindowSize) -> winit::dpi::Size { |
| 55 | match size { |
| 56 | corelib::api::WindowSize::Logical(size: &LogicalSize) => { |
| 57 | winit::dpi::Size::new(size:logical_size_to_winit(*size)) |
| 58 | } |
| 59 | corelib::api::WindowSize::Physical(size: &PhysicalSize) => { |
| 60 | winit::dpi::Size::new(size:physical_size_to_winit(*size)) |
| 61 | } |
| 62 | } |
| 63 | } |
| 64 | |
| 65 | pub fn physical_size_to_slint(size: &winit::dpi::PhysicalSize<u32>) -> corelib::api::PhysicalSize { |
| 66 | corelib::api::PhysicalSize::new(size.width, size.height) |
| 67 | } |
| 68 | |
| 69 | fn logical_size_to_winit(s: i_slint_core::api::LogicalSize) -> winit::dpi::LogicalSize<f32> { |
| 70 | winit::dpi::LogicalSize::new(s.width, s.height) |
| 71 | } |
| 72 | |
| 73 | fn physical_size_to_winit(size: PhysicalSize) -> winit::dpi::PhysicalSize<u32> { |
| 74 | winit::dpi::PhysicalSize::new(size.width, size.height) |
| 75 | } |
| 76 | |
| 77 | fn icon_to_winit(icon: corelib::graphics::Image) -> Option<winit::window::Icon> { |
| 78 | let image_inner: &ImageInner = (&icon).into(); |
| 79 | |
| 80 | let pixel_buffer = match image_inner { |
| 81 | ImageInner::EmbeddedImage { buffer, .. } => buffer.clone(), |
| 82 | _ => return None, |
| 83 | }; |
| 84 | |
| 85 | // This could become a method in SharedPixelBuffer... |
| 86 | let rgba_pixels: Vec<u8> = match &pixel_buffer { |
| 87 | SharedImageBuffer::RGB8(pixels) => pixels |
| 88 | .as_bytes() |
| 89 | .chunks(3) |
| 90 | .flat_map(|rgb| IntoIterator::into_iter([rgb[0], rgb[1], rgb[2], 255])) |
| 91 | .collect(), |
| 92 | SharedImageBuffer::RGBA8(pixels) => pixels.as_bytes().to_vec(), |
| 93 | SharedImageBuffer::RGBA8Premultiplied(pixels) => pixels |
| 94 | .as_bytes() |
| 95 | .chunks(4) |
| 96 | .flat_map(|rgba| { |
| 97 | let alpha = rgba[3] as u32; |
| 98 | IntoIterator::into_iter(rgba) |
| 99 | .take(3) |
| 100 | .map(move |component| (*component as u32 * alpha / 255) as u8) |
| 101 | .chain(std::iter::once(alpha as u8)) |
| 102 | }) |
| 103 | .collect(), |
| 104 | }; |
| 105 | |
| 106 | winit::window::Icon::from_rgba(rgba_pixels, pixel_buffer.width(), pixel_buffer.height()).ok() |
| 107 | } |
| 108 | |
| 109 | fn window_is_resizable( |
| 110 | min_size: Option<corelib::api::LogicalSize>, |
| 111 | max_size: Option<corelib::api::LogicalSize>, |
| 112 | ) -> bool { |
| 113 | if let Some(( |
| 114 | corelib::api::LogicalSize { width: min_width: f32, height: min_height: f32, .. }, |
| 115 | corelib::api::LogicalSize { width: max_width: f32, height: max_height: f32, .. }, |
| 116 | )) = min_size.zip(max_size) |
| 117 | { |
| 118 | min_width < max_width || min_height < max_height |
| 119 | } else { |
| 120 | true |
| 121 | } |
| 122 | } |
| 123 | |
| 124 | enum WinitWindowOrNone { |
| 125 | HasWindow { |
| 126 | window: Rc<winit::window::Window>, |
| 127 | #[cfg (enable_accesskit)] |
| 128 | accesskit_adapter: RefCell<crate::accesskit::AccessKitAdapter>, |
| 129 | #[cfg (muda)] |
| 130 | muda_adapter: RefCell<Option<crate::muda::MudaAdapter>>, |
| 131 | }, |
| 132 | None(RefCell<WindowAttributes>), |
| 133 | } |
| 134 | |
| 135 | impl WinitWindowOrNone { |
| 136 | fn as_window(&self) -> Option<Rc<winit::window::Window>> { |
| 137 | match self { |
| 138 | Self::HasWindow { window, .. } => Some(window.clone()), |
| 139 | Self::None { .. } => None, |
| 140 | } |
| 141 | } |
| 142 | |
| 143 | fn set_window_icon(&self, icon: Option<winit::window::Icon>) { |
| 144 | match self { |
| 145 | Self::HasWindow { window, .. } => { |
| 146 | #[cfg (target_family = "windows" )] |
| 147 | window.set_taskbar_icon(icon.as_ref().cloned()); |
| 148 | window.set_window_icon(icon); |
| 149 | } |
| 150 | Self::None(attributes) => attributes.borrow_mut().window_icon = icon, |
| 151 | } |
| 152 | } |
| 153 | |
| 154 | fn set_title(&self, title: &str) { |
| 155 | match self { |
| 156 | Self::HasWindow { window, .. } => window.set_title(title), |
| 157 | Self::None(attributes) => attributes.borrow_mut().title = title.into(), |
| 158 | } |
| 159 | } |
| 160 | |
| 161 | fn set_decorations(&self, decorations: bool) { |
| 162 | match self { |
| 163 | Self::HasWindow { window, .. } => window.set_decorations(decorations), |
| 164 | Self::None(attributes) => attributes.borrow_mut().decorations = decorations, |
| 165 | } |
| 166 | } |
| 167 | |
| 168 | fn fullscreen(&self) -> Option<winit::window::Fullscreen> { |
| 169 | match self { |
| 170 | Self::HasWindow { window, .. } => window.fullscreen(), |
| 171 | Self::None(attributes) => attributes.borrow().fullscreen.clone(), |
| 172 | } |
| 173 | } |
| 174 | |
| 175 | fn set_fullscreen(&self, fullscreen: Option<winit::window::Fullscreen>) { |
| 176 | match self { |
| 177 | Self::HasWindow { window, .. } => window.set_fullscreen(fullscreen), |
| 178 | Self::None(attributes) => attributes.borrow_mut().fullscreen = fullscreen, |
| 179 | } |
| 180 | } |
| 181 | |
| 182 | fn set_window_level(&self, level: winit::window::WindowLevel) { |
| 183 | match self { |
| 184 | Self::HasWindow { window, .. } => window.set_window_level(level), |
| 185 | Self::None(attributes) => attributes.borrow_mut().window_level = level, |
| 186 | } |
| 187 | } |
| 188 | |
| 189 | fn set_visible(&self, visible: bool) { |
| 190 | match self { |
| 191 | Self::HasWindow { window, .. } => window.set_visible(visible), |
| 192 | Self::None(attributes) => attributes.borrow_mut().visible = visible, |
| 193 | } |
| 194 | } |
| 195 | |
| 196 | fn set_maximized(&self, maximized: bool) { |
| 197 | match self { |
| 198 | Self::HasWindow { window, .. } => window.set_maximized(maximized), |
| 199 | Self::None(attributes) => attributes.borrow_mut().maximized = maximized, |
| 200 | } |
| 201 | } |
| 202 | |
| 203 | fn set_minimized(&self, minimized: bool) { |
| 204 | match self { |
| 205 | Self::HasWindow { window, .. } => window.set_minimized(minimized), |
| 206 | Self::None(..) => { /* TODO: winit is missing attributes.borrow_mut().minimized = minimized*/ |
| 207 | } |
| 208 | } |
| 209 | } |
| 210 | |
| 211 | fn set_resizable(&self, resizable: bool) { |
| 212 | match self { |
| 213 | Self::HasWindow { window, .. } => { |
| 214 | window.set_resizable(resizable); |
| 215 | |
| 216 | // Workaround for winit bug #2990 |
| 217 | // Non-resizable windows can still contain a maximize button, |
| 218 | // so we'd have to additionally remove the button. |
| 219 | let mut buttons = window.enabled_buttons(); |
| 220 | buttons.set(WindowButtons::MAXIMIZE, resizable); |
| 221 | window.set_enabled_buttons(buttons); |
| 222 | } |
| 223 | Self::None(attributes) => attributes.borrow_mut().resizable = resizable, |
| 224 | } |
| 225 | } |
| 226 | |
| 227 | fn set_min_inner_size<S: Into<winit::dpi::Size>>(&self, min_inner_size: Option<S>) { |
| 228 | match self { |
| 229 | Self::HasWindow { window, .. } => window.set_min_inner_size(min_inner_size), |
| 230 | Self::None(attributes) => { |
| 231 | attributes.borrow_mut().min_inner_size = min_inner_size.map(|s| s.into()); |
| 232 | } |
| 233 | } |
| 234 | } |
| 235 | |
| 236 | fn set_max_inner_size<S: Into<winit::dpi::Size>>(&self, max_inner_size: Option<S>) { |
| 237 | match self { |
| 238 | Self::HasWindow { window, .. } => window.set_max_inner_size(max_inner_size), |
| 239 | Self::None(attributes) => { |
| 240 | attributes.borrow_mut().max_inner_size = max_inner_size.map(|s| s.into()) |
| 241 | } |
| 242 | } |
| 243 | } |
| 244 | } |
| 245 | |
| 246 | /// GraphicsWindow is an implementation of the [WindowAdapter][`crate::eventloop::WindowAdapter`] trait. This is |
| 247 | /// typically instantiated by entry factory functions of the different graphics back ends. |
| 248 | pub struct WinitWindowAdapter { |
| 249 | pub shared_backend_data: Rc<SharedBackendData>, |
| 250 | window: OnceCell<corelib::api::Window>, |
| 251 | self_weak: Weak<Self>, |
| 252 | pending_redraw: Cell<bool>, |
| 253 | color_scheme: OnceCell<Pin<Box<Property<ColorScheme>>>>, |
| 254 | constraints: Cell<corelib::window::LayoutConstraints>, |
| 255 | shown: Cell<bool>, |
| 256 | window_level: Cell<winit::window::WindowLevel>, |
| 257 | maximized: Cell<bool>, |
| 258 | minimized: Cell<bool>, |
| 259 | fullscreen: Cell<bool>, |
| 260 | |
| 261 | pub(crate) renderer: Box<dyn WinitCompatibleRenderer>, |
| 262 | requested_graphics_api: Option<RequestedGraphicsAPI>, |
| 263 | /// We cache the size because winit_window.inner_size() can return different value between calls (eg, on X11) |
| 264 | /// And we wan see the newer value before the Resized event was received, leading to inconsistencies |
| 265 | size: Cell<PhysicalSize>, |
| 266 | /// We requested a size to be set, but we didn't get the resize event from winit yet |
| 267 | pending_requested_size: Cell<Option<winit::dpi::Size>>, |
| 268 | |
| 269 | /// Whether the size has been set explicitly via `set_size`. |
| 270 | /// If that's the case, we should't resize to the preferred size in set_visible |
| 271 | has_explicit_size: Cell<bool>, |
| 272 | |
| 273 | /// Indicate whether we've ever received a resize event from winit after showing the window. |
| 274 | pending_resize_event_after_show: Cell<bool>, |
| 275 | |
| 276 | #[cfg (target_arch = "wasm32" )] |
| 277 | virtual_keyboard_helper: RefCell<Option<super::wasm_input_helper::WasmInputHelper>>, |
| 278 | |
| 279 | #[cfg (any(enable_accesskit, muda))] |
| 280 | event_loop_proxy: EventLoopProxy<SlintUserEvent>, |
| 281 | |
| 282 | pub(crate) window_event_filter: Cell< |
| 283 | Option< |
| 284 | Box< |
| 285 | dyn FnMut( |
| 286 | &corelib::api::Window, |
| 287 | &winit::event::WindowEvent, |
| 288 | ) -> WinitWindowEventResult, |
| 289 | >, |
| 290 | >, |
| 291 | >, |
| 292 | |
| 293 | winit_window_or_none: RefCell<WinitWindowOrNone>, |
| 294 | |
| 295 | #[cfg (not(use_winit_theme))] |
| 296 | xdg_settings_watcher: RefCell<Option<i_slint_core::future::JoinHandle<()>>>, |
| 297 | |
| 298 | #[cfg (muda)] |
| 299 | menubar: RefCell<Option<vtable::VBox<i_slint_core::menus::MenuVTable>>>, |
| 300 | |
| 301 | #[cfg (all(muda, target_os = "macos" ))] |
| 302 | muda_enable_default_menu_bar: bool, |
| 303 | } |
| 304 | |
| 305 | impl WinitWindowAdapter { |
| 306 | /// Creates a new reference-counted instance. |
| 307 | pub(crate) fn new( |
| 308 | shared_backend_data: Rc<SharedBackendData>, |
| 309 | renderer: Box<dyn WinitCompatibleRenderer>, |
| 310 | window_attributes: winit::window::WindowAttributes, |
| 311 | requested_graphics_api: Option<RequestedGraphicsAPI>, |
| 312 | #[cfg (any(enable_accesskit, muda))] proxy: EventLoopProxy<SlintUserEvent>, |
| 313 | #[cfg (all(muda, target_os = "macos" ))] muda_enable_default_menu_bar: bool, |
| 314 | ) -> Result<Rc<Self>, PlatformError> { |
| 315 | let self_rc = Rc::new_cyclic(|self_weak| Self { |
| 316 | shared_backend_data, |
| 317 | window: OnceCell::from(corelib::api::Window::new(self_weak.clone() as _)), |
| 318 | self_weak: self_weak.clone(), |
| 319 | pending_redraw: Default::default(), |
| 320 | color_scheme: Default::default(), |
| 321 | constraints: Default::default(), |
| 322 | shown: Default::default(), |
| 323 | window_level: Default::default(), |
| 324 | maximized: Cell::default(), |
| 325 | minimized: Cell::default(), |
| 326 | fullscreen: Cell::default(), |
| 327 | winit_window_or_none: RefCell::new(WinitWindowOrNone::None(window_attributes.into())), |
| 328 | size: Cell::default(), |
| 329 | pending_requested_size: Cell::new(None), |
| 330 | has_explicit_size: Default::default(), |
| 331 | pending_resize_event_after_show: Default::default(), |
| 332 | renderer, |
| 333 | requested_graphics_api, |
| 334 | #[cfg (target_arch = "wasm32" )] |
| 335 | virtual_keyboard_helper: Default::default(), |
| 336 | #[cfg (any(enable_accesskit, muda))] |
| 337 | event_loop_proxy: proxy, |
| 338 | window_event_filter: Cell::new(None), |
| 339 | #[cfg (not(use_winit_theme))] |
| 340 | xdg_settings_watcher: Default::default(), |
| 341 | #[cfg (muda)] |
| 342 | menubar: Default::default(), |
| 343 | #[cfg (all(muda, target_os = "macos" ))] |
| 344 | muda_enable_default_menu_bar, |
| 345 | }); |
| 346 | |
| 347 | let winit_window = self_rc |
| 348 | .shared_backend_data |
| 349 | .with_event_loop(|event_loop| Ok(self_rc.ensure_window(event_loop)?))?; |
| 350 | debug_assert!(!self_rc.renderer.is_suspended()); |
| 351 | self_rc.size.set(physical_size_to_slint(&winit_window.inner_size())); |
| 352 | |
| 353 | let id = winit_window.id(); |
| 354 | self_rc.shared_backend_data.register_window(id, (self_rc.clone()) as _); |
| 355 | |
| 356 | let scale_factor = std::env::var("SLINT_SCALE_FACTOR" ) |
| 357 | .ok() |
| 358 | .and_then(|x| x.parse::<f32>().ok()) |
| 359 | .filter(|f| *f > 0.) |
| 360 | .unwrap_or_else(|| winit_window.scale_factor() as f32); |
| 361 | self_rc.window().try_dispatch_event(WindowEvent::ScaleFactorChanged { scale_factor })?; |
| 362 | |
| 363 | Ok(self_rc) |
| 364 | } |
| 365 | |
| 366 | fn renderer(&self) -> &dyn WinitCompatibleRenderer { |
| 367 | self.renderer.as_ref() |
| 368 | } |
| 369 | |
| 370 | pub fn ensure_window( |
| 371 | &self, |
| 372 | event_loop: &dyn crate::event_loop::EventLoopInterface, |
| 373 | ) -> Result<Rc<winit::window::Window>, PlatformError> { |
| 374 | #[allow (unused_mut)] |
| 375 | let mut window_attributes = match &*self.winit_window_or_none.borrow() { |
| 376 | WinitWindowOrNone::HasWindow { window, .. } => return Ok(window.clone()), |
| 377 | WinitWindowOrNone::None(attributes) => attributes.borrow().clone(), |
| 378 | }; |
| 379 | |
| 380 | #[cfg (all(unix, not(target_vendor = "apple" )))] |
| 381 | { |
| 382 | if let Some(xdg_app_id) = WindowInner::from_pub(self.window()).xdg_app_id() { |
| 383 | #[cfg (feature = "wayland" )] |
| 384 | { |
| 385 | use winit::platform::wayland::WindowAttributesExtWayland; |
| 386 | window_attributes = window_attributes.with_name(xdg_app_id.clone(), "" ); |
| 387 | } |
| 388 | #[cfg (feature = "x11" )] |
| 389 | { |
| 390 | use winit::platform::x11::WindowAttributesExtX11; |
| 391 | window_attributes = window_attributes.with_name(xdg_app_id.clone(), "" ); |
| 392 | } |
| 393 | } |
| 394 | } |
| 395 | |
| 396 | let mut winit_window_or_none = self.winit_window_or_none.borrow_mut(); |
| 397 | |
| 398 | let winit_window = self.renderer.resume( |
| 399 | event_loop, |
| 400 | window_attributes, |
| 401 | self.requested_graphics_api.clone(), |
| 402 | )?; |
| 403 | |
| 404 | *winit_window_or_none = WinitWindowOrNone::HasWindow { |
| 405 | window: winit_window.clone(), |
| 406 | #[cfg (enable_accesskit)] |
| 407 | accesskit_adapter: crate::accesskit::AccessKitAdapter::new( |
| 408 | self.self_weak.clone(), |
| 409 | &winit_window, |
| 410 | self.event_loop_proxy.clone(), |
| 411 | ) |
| 412 | .into(), |
| 413 | #[cfg (muda)] |
| 414 | muda_adapter: self |
| 415 | .menubar |
| 416 | .borrow() |
| 417 | .as_ref() |
| 418 | .map(|menubar| { |
| 419 | crate::muda::MudaAdapter::setup( |
| 420 | menubar, |
| 421 | &self.winit_window().unwrap(), |
| 422 | self.event_loop_proxy.clone(), |
| 423 | self.self_weak.clone(), |
| 424 | ) |
| 425 | }) |
| 426 | .into(), |
| 427 | }; |
| 428 | |
| 429 | self.shared_backend_data |
| 430 | .register_window(winit_window.id(), (self.self_weak.upgrade().unwrap()) as _); |
| 431 | |
| 432 | Ok(winit_window) |
| 433 | } |
| 434 | |
| 435 | fn suspend(&self) -> Result<(), PlatformError> { |
| 436 | let mut winit_window_or_none = self.winit_window_or_none.borrow_mut(); |
| 437 | match *winit_window_or_none { |
| 438 | WinitWindowOrNone::HasWindow { ref window, .. } => { |
| 439 | self.renderer().suspend()?; |
| 440 | |
| 441 | let last_window_rc = window.clone(); |
| 442 | |
| 443 | let mut attributes = Self::window_attributes().unwrap_or_default(); |
| 444 | attributes.inner_size = Some(physical_size_to_winit(self.size.get()).into()); |
| 445 | attributes.position = last_window_rc.outer_position().ok().map(|pos| pos.into()); |
| 446 | *winit_window_or_none = WinitWindowOrNone::None(attributes.into()); |
| 447 | |
| 448 | if let Some(last_instance) = Rc::into_inner(last_window_rc) { |
| 449 | self.shared_backend_data.unregister_window(last_instance.id()); |
| 450 | drop(last_instance); |
| 451 | } else { |
| 452 | i_slint_core::debug_log!( |
| 453 | "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" |
| 454 | ); |
| 455 | } |
| 456 | } |
| 457 | WinitWindowOrNone::None(ref attributes) => { |
| 458 | attributes.borrow_mut().visible = false; |
| 459 | } |
| 460 | } |
| 461 | |
| 462 | Ok(()) |
| 463 | } |
| 464 | |
| 465 | pub(crate) fn window_attributes() -> Result<WindowAttributes, PlatformError> { |
| 466 | let mut attrs = WindowAttributes::default().with_transparent(true).with_visible(false); |
| 467 | |
| 468 | attrs = attrs.with_title("Slint Window" .to_string()); |
| 469 | |
| 470 | #[cfg (target_arch = "wasm32" )] |
| 471 | { |
| 472 | use winit::platform::web::WindowAttributesExtWebSys; |
| 473 | |
| 474 | use wasm_bindgen::JsCast; |
| 475 | |
| 476 | if let Some(html_canvas) = web_sys::window() |
| 477 | .ok_or_else(|| "winit backend: Could not retrieve DOM window" .to_string())? |
| 478 | .document() |
| 479 | .ok_or_else(|| "winit backend: Could not retrieve DOM document" .to_string())? |
| 480 | .get_element_by_id("canvas" ) |
| 481 | .and_then(|canvas_elem| canvas_elem.dyn_into::<web_sys::HtmlCanvasElement>().ok()) |
| 482 | { |
| 483 | attrs = attrs |
| 484 | .with_canvas(Some(html_canvas)) |
| 485 | // Don't activate the window by default, as that will cause the page to scroll, |
| 486 | // ignoring any existing anchors. |
| 487 | .with_active(false); |
| 488 | } |
| 489 | }; |
| 490 | |
| 491 | Ok(attrs) |
| 492 | } |
| 493 | |
| 494 | /// Draw the items of the specified `component` in the given window. |
| 495 | pub fn draw(&self) -> Result<(), PlatformError> { |
| 496 | if !self.shown.get() { |
| 497 | return Ok(()); // caller bug, doesn't make sense to call draw() when not shown |
| 498 | } |
| 499 | |
| 500 | self.pending_redraw.set(false); |
| 501 | |
| 502 | if let Some(winit_window) = self.winit_window_or_none.borrow().as_window() { |
| 503 | // on macOS we sometimes don't get a resize event after calling |
| 504 | // request_inner_size(), it returning None (promising a resize event), and then delivering RedrawRequested. To work around this, |
| 505 | // catch up here to ensure the renderer can resize the surface correctly. |
| 506 | // Note: On displays with a scale factor != 1, we get a scale factor change |
| 507 | // event and a resize event, so all is good. |
| 508 | if self.pending_resize_event_after_show.take() { |
| 509 | self.resize_event(winit_window.inner_size())?; |
| 510 | } |
| 511 | } |
| 512 | |
| 513 | let renderer = self.renderer(); |
| 514 | renderer.render(self.window())?; |
| 515 | |
| 516 | Ok(()) |
| 517 | } |
| 518 | |
| 519 | pub fn winit_window(&self) -> Option<Rc<winit::window::Window>> { |
| 520 | self.winit_window_or_none.borrow().as_window() |
| 521 | } |
| 522 | |
| 523 | #[cfg (muda)] |
| 524 | pub fn rebuild_menubar(&self) { |
| 525 | let WinitWindowOrNone::HasWindow { |
| 526 | window: winit_window, |
| 527 | muda_adapter: maybe_muda_adapter, |
| 528 | .. |
| 529 | } = &*self.winit_window_or_none.borrow() |
| 530 | else { |
| 531 | return; |
| 532 | }; |
| 533 | let mut maybe_muda_adapter = maybe_muda_adapter.borrow_mut(); |
| 534 | let Some(muda_adapter) = maybe_muda_adapter.as_mut() else { return }; |
| 535 | muda_adapter.rebuild_menu(&winit_window, self.menubar.borrow().as_ref()); |
| 536 | } |
| 537 | |
| 538 | #[cfg (muda)] |
| 539 | pub fn muda_event(&self, entry_id: usize) { |
| 540 | let Ok(maybe_muda_adapter) = std::cell::Ref::filter_map( |
| 541 | self.winit_window_or_none.borrow(), |
| 542 | |winit_window_or_none| match winit_window_or_none { |
| 543 | WinitWindowOrNone::HasWindow { muda_adapter, .. } => Some(muda_adapter), |
| 544 | WinitWindowOrNone::None(..) => None, |
| 545 | }, |
| 546 | ) else { |
| 547 | return; |
| 548 | }; |
| 549 | let maybe_muda_adapter = maybe_muda_adapter.borrow(); |
| 550 | let Some(muda_adapter) = maybe_muda_adapter.as_ref() else { return }; |
| 551 | let menubar = self.menubar.borrow(); |
| 552 | let Some(menubar) = menubar.as_ref() else { return }; |
| 553 | muda_adapter.invoke(menubar, entry_id); |
| 554 | } |
| 555 | |
| 556 | #[cfg (target_arch = "wasm32" )] |
| 557 | pub fn input_method_focused(&self) -> bool { |
| 558 | match self.virtual_keyboard_helper.try_borrow() { |
| 559 | Ok(vkh) => vkh.as_ref().map_or(false, |h| h.has_focus()), |
| 560 | // the only location in which the virtual_keyboard_helper is mutably borrowed is from |
| 561 | // show_virtual_keyboard, which means we have the focus |
| 562 | Err(_) => true, |
| 563 | } |
| 564 | } |
| 565 | |
| 566 | #[cfg (not(target_arch = "wasm32" ))] |
| 567 | pub fn input_method_focused(&self) -> bool { |
| 568 | false |
| 569 | } |
| 570 | |
| 571 | // Requests for the window to be resized. Returns true if the window was resized immediately, |
| 572 | // or if it will be resized later (false). |
| 573 | fn resize_window(&self, size: winit::dpi::Size) -> Result<bool, PlatformError> { |
| 574 | match &*self.winit_window_or_none.borrow() { |
| 575 | WinitWindowOrNone::HasWindow { window, .. } => { |
| 576 | if let Some(size) = window.request_inner_size(size) { |
| 577 | // On wayland we might not get a WindowEvent::Resized, so resize the EGL surface right away. |
| 578 | self.resize_event(size)?; |
| 579 | Ok(true) |
| 580 | } else { |
| 581 | // None means that we'll get a `WindowEvent::Resized` later |
| 582 | Ok(false) |
| 583 | } |
| 584 | } |
| 585 | WinitWindowOrNone::None(attributes) => { |
| 586 | attributes.borrow_mut().inner_size = Some(size); |
| 587 | self.resize_event(size.to_physical(self.window().scale_factor() as _))?; |
| 588 | Ok(true) |
| 589 | } |
| 590 | } |
| 591 | } |
| 592 | |
| 593 | pub fn resize_event(&self, size: winit::dpi::PhysicalSize<u32>) -> Result<(), PlatformError> { |
| 594 | self.pending_resize_event_after_show.set(false); |
| 595 | // When a window is minimized on Windows, we get a move event to an off-screen position |
| 596 | // and a resize even with a zero size. Don't forward that, especially not to the renderer, |
| 597 | // which might panic when trying to create a zero-sized surface. |
| 598 | if size.width > 0 && size.height > 0 { |
| 599 | let physical_size = physical_size_to_slint(&size); |
| 600 | self.size.set(physical_size); |
| 601 | let scale_factor = WindowInner::from_pub(self.window()).scale_factor(); |
| 602 | self.window().try_dispatch_event(WindowEvent::Resized { |
| 603 | size: physical_size.to_logical(scale_factor), |
| 604 | })?; |
| 605 | |
| 606 | // Workaround fox winit not sync'ing CSS size of the canvas (the size shown on the browser) |
| 607 | // with the width/height attribute (the size of the viewport/GL surface) |
| 608 | // If they're not in sync, the UI would be shown as scaled |
| 609 | #[cfg (target_arch = "wasm32" )] |
| 610 | if let Some(html_canvas) = self |
| 611 | .winit_window_or_none |
| 612 | .borrow() |
| 613 | .as_window() |
| 614 | .and_then(|winit_window| winit_window.canvas()) |
| 615 | { |
| 616 | html_canvas.set_width(physical_size.width); |
| 617 | html_canvas.set_height(physical_size.height); |
| 618 | } |
| 619 | } |
| 620 | Ok(()) |
| 621 | } |
| 622 | |
| 623 | pub fn set_color_scheme(&self, scheme: ColorScheme) { |
| 624 | self.color_scheme |
| 625 | .get_or_init(|| Box::pin(Property::new(ColorScheme::Unknown))) |
| 626 | .as_ref() |
| 627 | .set(scheme); |
| 628 | // Inform winit about the selected color theme, so that the window decoration is drawn correctly. |
| 629 | #[cfg (not(use_winit_theme))] |
| 630 | if let Some(winit_window) = self.winit_window() { |
| 631 | winit_window.set_theme(match scheme { |
| 632 | ColorScheme::Unknown => None, |
| 633 | ColorScheme::Dark => Some(winit::window::Theme::Dark), |
| 634 | ColorScheme::Light => Some(winit::window::Theme::Light), |
| 635 | }); |
| 636 | } |
| 637 | } |
| 638 | |
| 639 | pub fn window_state_event(&self) { |
| 640 | let Some(winit_window) = self.winit_window_or_none.borrow().as_window() else { return }; |
| 641 | |
| 642 | if let Some(minimized) = winit_window.is_minimized() { |
| 643 | self.minimized.set(minimized); |
| 644 | if minimized != self.window().is_minimized() { |
| 645 | self.window().set_minimized(minimized); |
| 646 | } |
| 647 | } |
| 648 | |
| 649 | // The method winit::Window::is_maximized returns false when the window |
| 650 | // is minimized, even if it was previously maximized. We have to ensure |
| 651 | // that we only update the internal maximized state when the window is |
| 652 | // not minimized. Otherwise, the window would be restored in a |
| 653 | // non-maximized state even if it was maximized before being minimized. |
| 654 | let maximized = winit_window.is_maximized(); |
| 655 | if !self.window().is_minimized() { |
| 656 | self.maximized.set(maximized); |
| 657 | if maximized != self.window().is_maximized() { |
| 658 | self.window().set_maximized(maximized); |
| 659 | } |
| 660 | } |
| 661 | |
| 662 | // NOTE: Fullscreen overrides maximized so if both are true then the |
| 663 | // window will remain in fullscreen. Fullscreen must be false to switch |
| 664 | // to maximized. |
| 665 | let fullscreen = winit_window.fullscreen().is_some(); |
| 666 | if fullscreen != self.window().is_fullscreen() { |
| 667 | self.window().set_fullscreen(fullscreen); |
| 668 | } |
| 669 | } |
| 670 | |
| 671 | #[cfg (enable_accesskit)] |
| 672 | pub(crate) fn accesskit_adapter( |
| 673 | &self, |
| 674 | ) -> Option<std::cell::Ref<'_, RefCell<crate::accesskit::AccessKitAdapter>>> { |
| 675 | std::cell::Ref::filter_map(self.winit_window_or_none.borrow(), |wor: &WinitWindowOrNone| { |
| 676 | match wor { |
| 677 | WinitWindowOrNone::HasWindow { accesskit_adapter, .. } => Some(accesskit_adapter), |
| 678 | WinitWindowOrNone::None(..) => None, |
| 679 | } |
| 680 | }) |
| 681 | .ok() |
| 682 | } |
| 683 | |
| 684 | #[cfg (enable_accesskit)] |
| 685 | pub(crate) fn with_access_kit_adapter_from_weak_window_adapter( |
| 686 | self_weak: Weak<Self>, |
| 687 | callback: impl FnOnce(&RefCell<crate::accesskit::AccessKitAdapter>), |
| 688 | ) { |
| 689 | let Some(self_) = self_weak.upgrade() else { return }; |
| 690 | let winit_window_or_none = self_.winit_window_or_none.borrow(); |
| 691 | match &*winit_window_or_none { |
| 692 | WinitWindowOrNone::HasWindow { accesskit_adapter, .. } => callback(accesskit_adapter), |
| 693 | WinitWindowOrNone::None(..) => {} |
| 694 | } |
| 695 | } |
| 696 | |
| 697 | #[cfg (not(use_winit_theme))] |
| 698 | fn spawn_xdg_settings_watcher(&self) -> Option<i_slint_core::future::JoinHandle<()>> { |
| 699 | let window_inner = WindowInner::from_pub(self.window()); |
| 700 | let self_weak = self.self_weak.clone(); |
| 701 | window_inner |
| 702 | .context() |
| 703 | .spawn_local(async move { |
| 704 | if let Err(err) = crate::xdg_color_scheme::watch(self_weak).await { |
| 705 | i_slint_core::debug_log!("Error watching for xdg color schemes: {}" , err); |
| 706 | } |
| 707 | }) |
| 708 | .ok() |
| 709 | } |
| 710 | |
| 711 | pub fn activation_changed(&self, is_active: bool) -> Result<(), PlatformError> { |
| 712 | let have_focus = is_active || self.input_method_focused(); |
| 713 | let slint_window = self.window(); |
| 714 | let runtime_window = WindowInner::from_pub(slint_window); |
| 715 | // We don't render popups as separate windows yet, so treat |
| 716 | // focus to be the same as being active. |
| 717 | if have_focus != runtime_window.active() { |
| 718 | slint_window.try_dispatch_event( |
| 719 | corelib::platform::WindowEvent::WindowActiveChanged(have_focus), |
| 720 | )?; |
| 721 | } |
| 722 | |
| 723 | #[cfg (all(muda, target_os = "macos" ))] |
| 724 | { |
| 725 | if let WinitWindowOrNone::HasWindow { muda_adapter, .. } = |
| 726 | &*self.winit_window_or_none.borrow() |
| 727 | { |
| 728 | if muda_adapter.borrow().is_none() |
| 729 | && self.muda_enable_default_menu_bar |
| 730 | && self.menubar.borrow().is_none() |
| 731 | { |
| 732 | *muda_adapter.borrow_mut() = |
| 733 | Some(crate::muda::MudaAdapter::setup_default_menu_bar()?); |
| 734 | } |
| 735 | |
| 736 | if let Some(muda_adapter) = muda_adapter.borrow().as_ref() { |
| 737 | muda_adapter.window_activation_changed(is_active); |
| 738 | } |
| 739 | } |
| 740 | } |
| 741 | |
| 742 | Ok(()) |
| 743 | } |
| 744 | } |
| 745 | |
| 746 | impl WindowAdapter for WinitWindowAdapter { |
| 747 | fn window(&self) -> &corelib::api::Window { |
| 748 | self.window.get().unwrap() |
| 749 | } |
| 750 | |
| 751 | fn renderer(&self) -> &dyn i_slint_core::renderer::Renderer { |
| 752 | self.renderer().as_core_renderer() |
| 753 | } |
| 754 | |
| 755 | fn set_visible(&self, visible: bool) -> Result<(), PlatformError> { |
| 756 | if visible == self.shown.get() { |
| 757 | return Ok(()); |
| 758 | } |
| 759 | |
| 760 | self.shown.set(visible); |
| 761 | self.pending_resize_event_after_show.set(visible); |
| 762 | self.pending_redraw.set(false); |
| 763 | if visible { |
| 764 | let recreating_window = self.winit_window_or_none.borrow().as_window().is_none(); |
| 765 | |
| 766 | let winit_window = self |
| 767 | .shared_backend_data |
| 768 | .with_event_loop(|event_loop| Ok(self.ensure_window(event_loop)?))?; |
| 769 | |
| 770 | let runtime_window = WindowInner::from_pub(self.window()); |
| 771 | |
| 772 | let scale_factor = runtime_window.scale_factor() as f64; |
| 773 | |
| 774 | let component_rc = runtime_window.component(); |
| 775 | let component = ItemTreeRc::borrow_pin(&component_rc); |
| 776 | |
| 777 | let layout_info_h = component.as_ref().layout_info(Orientation::Horizontal); |
| 778 | if let Some(window_item) = runtime_window.window_item() { |
| 779 | // Setting the width to its preferred size before querying the vertical layout info |
| 780 | // is important in case the height depends on the width |
| 781 | window_item.width.set(LogicalLength::new(layout_info_h.preferred_bounded())); |
| 782 | } |
| 783 | let layout_info_v = component.as_ref().layout_info(Orientation::Vertical); |
| 784 | #[allow (unused_mut)] |
| 785 | let mut preferred_size = winit::dpi::LogicalSize::new( |
| 786 | layout_info_h.preferred_bounded(), |
| 787 | layout_info_v.preferred_bounded(), |
| 788 | ); |
| 789 | |
| 790 | #[cfg (target_arch = "wasm32" )] |
| 791 | if let Some(html_canvas) = winit_window.canvas() { |
| 792 | let existing_canvas_size = winit::dpi::LogicalSize::new( |
| 793 | html_canvas.client_width() as f32, |
| 794 | html_canvas.client_height() as f32, |
| 795 | ); |
| 796 | // Try to maintain the existing size of the canvas element, if any |
| 797 | if existing_canvas_size.width > 0. { |
| 798 | preferred_size.width = existing_canvas_size.width; |
| 799 | } |
| 800 | if existing_canvas_size.height > 0. { |
| 801 | preferred_size.height = existing_canvas_size.height; |
| 802 | } |
| 803 | } |
| 804 | |
| 805 | if winit_window.fullscreen().is_none() |
| 806 | && !self.has_explicit_size.get() |
| 807 | && preferred_size.width > 0 as Coord |
| 808 | && preferred_size.height > 0 as Coord |
| 809 | // Don't set the preferred size as the user may have resized the window |
| 810 | && !recreating_window |
| 811 | { |
| 812 | // use the Slint's window Scale factor to take in account the override |
| 813 | let size = preferred_size.to_physical::<u32>(scale_factor); |
| 814 | self.resize_window(size.into())?; |
| 815 | }; |
| 816 | |
| 817 | winit_window.set_visible(true); |
| 818 | |
| 819 | // Make sure the dark color scheme property is up-to-date, as it may have been queried earlier when |
| 820 | // the window wasn't mapped yet. |
| 821 | if let Some(color_scheme_prop) = self.color_scheme.get() { |
| 822 | if let Some(theme) = winit_window.theme() { |
| 823 | color_scheme_prop.as_ref().set(match theme { |
| 824 | winit::window::Theme::Dark => ColorScheme::Dark, |
| 825 | winit::window::Theme::Light => ColorScheme::Light, |
| 826 | }) |
| 827 | } |
| 828 | } |
| 829 | |
| 830 | // In wasm a request_redraw() issued before show() results in a draw() even when the window |
| 831 | // isn't visible, as opposed to regular windowing systems. The compensate for the lost draw, |
| 832 | // explicitly render the first frame on show(). |
| 833 | #[cfg (target_arch = "wasm32" )] |
| 834 | if self.pending_redraw.get() { |
| 835 | self.draw()?; |
| 836 | }; |
| 837 | |
| 838 | Ok(()) |
| 839 | } else { |
| 840 | // Wayland doesn't support hiding a window, only destroying it entirely. |
| 841 | if self.winit_window_or_none.borrow().as_window().is_some_and(|winit_window| { |
| 842 | use raw_window_handle::HasWindowHandle; |
| 843 | winit_window.window_handle().is_ok_and(|h| { |
| 844 | matches!(h.as_raw(), raw_window_handle::RawWindowHandle::Wayland(..)) |
| 845 | }) || std::env::var_os("SLINT_DESTROY_WINDOW_ON_HIDE" ).is_some() |
| 846 | }) { |
| 847 | self.suspend()?; |
| 848 | } else { |
| 849 | self.winit_window_or_none.borrow().set_visible(false); |
| 850 | } |
| 851 | |
| 852 | /* FIXME: |
| 853 | if let Some(existing_blinker) = self.cursor_blinker.borrow().upgrade() { |
| 854 | existing_blinker.stop(); |
| 855 | }*/ |
| 856 | Ok(()) |
| 857 | } |
| 858 | } |
| 859 | |
| 860 | fn position(&self) -> Option<corelib::api::PhysicalPosition> { |
| 861 | match &*self.winit_window_or_none.borrow() { |
| 862 | WinitWindowOrNone::HasWindow { window, .. } => match window.outer_position() { |
| 863 | Ok(outer_position) => { |
| 864 | Some(corelib::api::PhysicalPosition::new(outer_position.x, outer_position.y)) |
| 865 | } |
| 866 | Err(_) => None, |
| 867 | }, |
| 868 | WinitWindowOrNone::None(attributes) => { |
| 869 | attributes.borrow().position.map(|pos| { |
| 870 | match pos { |
| 871 | winit::dpi::Position::Physical(phys_pos) => { |
| 872 | corelib::api::PhysicalPosition::new(phys_pos.x, phys_pos.y) |
| 873 | } |
| 874 | winit::dpi::Position::Logical(logical_pos) => { |
| 875 | // Best effort: Use the last known scale factor |
| 876 | corelib::api::LogicalPosition::new( |
| 877 | logical_pos.x as _, |
| 878 | logical_pos.y as _, |
| 879 | ) |
| 880 | .to_physical(self.window().scale_factor()) |
| 881 | } |
| 882 | } |
| 883 | }) |
| 884 | } |
| 885 | } |
| 886 | } |
| 887 | |
| 888 | fn set_position(&self, position: corelib::api::WindowPosition) { |
| 889 | let winit_pos = position_to_winit(&position); |
| 890 | match &*self.winit_window_or_none.borrow() { |
| 891 | WinitWindowOrNone::HasWindow { window, .. } => window.set_outer_position(winit_pos), |
| 892 | WinitWindowOrNone::None(attributes) => { |
| 893 | attributes.borrow_mut().position = Some(winit_pos); |
| 894 | } |
| 895 | } |
| 896 | } |
| 897 | |
| 898 | fn set_size(&self, size: corelib::api::WindowSize) { |
| 899 | self.has_explicit_size.set(true); |
| 900 | // TODO: don't ignore error, propgate to caller |
| 901 | self.resize_window(window_size_to_winit(&size)).ok(); |
| 902 | } |
| 903 | |
| 904 | fn size(&self) -> corelib::api::PhysicalSize { |
| 905 | self.size.get() |
| 906 | } |
| 907 | |
| 908 | fn request_redraw(&self) { |
| 909 | if !self.pending_redraw.replace(true) { |
| 910 | if let Some(window) = self.winit_window_or_none.borrow().as_window() { |
| 911 | window.request_redraw() |
| 912 | } |
| 913 | } |
| 914 | } |
| 915 | |
| 916 | #[allow (clippy::unnecessary_cast)] // Coord is used! |
| 917 | fn update_window_properties(&self, properties: corelib::window::WindowProperties<'_>) { |
| 918 | let Some(window_item) = |
| 919 | self.window.get().and_then(|w| WindowInner::from_pub(w).window_item()) |
| 920 | else { |
| 921 | return; |
| 922 | }; |
| 923 | let window_item = window_item.as_pin_ref(); |
| 924 | |
| 925 | let winit_window_or_none = self.winit_window_or_none.borrow(); |
| 926 | |
| 927 | winit_window_or_none.set_window_icon(icon_to_winit(window_item.icon())); |
| 928 | winit_window_or_none.set_title(&properties.title()); |
| 929 | winit_window_or_none.set_decorations( |
| 930 | !window_item.no_frame() || winit_window_or_none.fullscreen().is_some(), |
| 931 | ); |
| 932 | |
| 933 | let new_window_level = if window_item.always_on_top() { |
| 934 | winit::window::WindowLevel::AlwaysOnTop |
| 935 | } else { |
| 936 | winit::window::WindowLevel::Normal |
| 937 | }; |
| 938 | // Only change the window level if it changes, to avoid https://github.com/slint-ui/slint/issues/3280 |
| 939 | // (Ubuntu 20.04's window manager always bringing the window to the front on x11) |
| 940 | if self.window_level.replace(new_window_level) != new_window_level { |
| 941 | winit_window_or_none.set_window_level(new_window_level); |
| 942 | } |
| 943 | |
| 944 | // Use our scale factor instead of winit's logical size to take a scale factor override into account. |
| 945 | let sf = self.window().scale_factor(); |
| 946 | |
| 947 | let mut width = window_item.width().get() as f32; |
| 948 | let mut height = window_item.height().get() as f32; |
| 949 | let mut must_resize = false; |
| 950 | let existing_size = self.size.get().to_logical(sf); |
| 951 | |
| 952 | if width <= 0. || height <= 0. { |
| 953 | must_resize = true; |
| 954 | if width <= 0. { |
| 955 | width = existing_size.width; |
| 956 | } |
| 957 | if height <= 0. { |
| 958 | height = existing_size.height; |
| 959 | } |
| 960 | } |
| 961 | |
| 962 | // Adjust the size of the window to the value of the width and height property (if these property are changed from .slint). |
| 963 | // But not if there is a pending resize in flight as that resize will reset these properties back |
| 964 | if ((existing_size.width - width).abs() > 1. || (existing_size.height - height).abs() > 1.) |
| 965 | && self.pending_requested_size.get().is_none() |
| 966 | { |
| 967 | // If we're in fullscreen state, don't try to resize the window but maintain the surface |
| 968 | // size we've been assigned to from the windowing system. Weston/Wayland don't like it |
| 969 | // when we create a surface that's bigger than the screen due to constraints (#532). |
| 970 | if winit_window_or_none.fullscreen().is_none() { |
| 971 | // TODO: don't ignore error, propgate to caller |
| 972 | let immediately_resized = self |
| 973 | .resize_window(winit::dpi::LogicalSize::new(width, height).into()) |
| 974 | .unwrap_or_default(); |
| 975 | if immediately_resized { |
| 976 | // The resize event was already dispatched |
| 977 | must_resize = false; |
| 978 | } |
| 979 | } |
| 980 | } |
| 981 | |
| 982 | if must_resize { |
| 983 | self.window() |
| 984 | .try_dispatch_event(WindowEvent::Resized { |
| 985 | size: i_slint_core::api::LogicalSize::new(width, height), |
| 986 | }) |
| 987 | .unwrap(); |
| 988 | } |
| 989 | |
| 990 | let m = properties.is_fullscreen(); |
| 991 | if m != self.fullscreen.get() { |
| 992 | if m { |
| 993 | if winit_window_or_none.fullscreen().is_none() { |
| 994 | winit_window_or_none |
| 995 | .set_fullscreen(Some(winit::window::Fullscreen::Borderless(None))); |
| 996 | } |
| 997 | } else { |
| 998 | winit_window_or_none.set_fullscreen(None); |
| 999 | } |
| 1000 | self.fullscreen.set(m); |
| 1001 | } |
| 1002 | |
| 1003 | let m = properties.is_maximized(); |
| 1004 | if m != self.maximized.get() { |
| 1005 | self.maximized.set(m); |
| 1006 | winit_window_or_none.set_maximized(m); |
| 1007 | } |
| 1008 | |
| 1009 | let m = properties.is_minimized(); |
| 1010 | if m != self.minimized.get() { |
| 1011 | self.minimized.set(m); |
| 1012 | winit_window_or_none.set_minimized(m); |
| 1013 | } |
| 1014 | |
| 1015 | // If we're in fullscreen, don't try to resize the window but |
| 1016 | // maintain the surface size we've been assigned to from the |
| 1017 | // windowing system. Weston/Wayland don't like it when we create a |
| 1018 | // surface that's bigger than the screen due to constraints (#532). |
| 1019 | if winit_window_or_none.fullscreen().is_some() { |
| 1020 | return; |
| 1021 | } |
| 1022 | |
| 1023 | let new_constraints = properties.layout_constraints(); |
| 1024 | if new_constraints == self.constraints.get() { |
| 1025 | return; |
| 1026 | } |
| 1027 | |
| 1028 | self.constraints.set(new_constraints); |
| 1029 | |
| 1030 | let into_size = |s: corelib::api::LogicalSize| -> winit::dpi::PhysicalSize<f32> { |
| 1031 | logical_size_to_winit(s).to_physical(sf as f64) |
| 1032 | }; |
| 1033 | |
| 1034 | let resizable = window_is_resizable(new_constraints.min, new_constraints.max); |
| 1035 | // we must call set_resizable before setting the min and max size otherwise setting the min and max size don't work on X11 |
| 1036 | winit_window_or_none.set_resizable(resizable); |
| 1037 | let winit_min_inner = new_constraints.min.map(into_size); |
| 1038 | winit_window_or_none.set_min_inner_size(winit_min_inner); |
| 1039 | let winit_max_inner = new_constraints.max.map(into_size); |
| 1040 | winit_window_or_none.set_max_inner_size(winit_max_inner); |
| 1041 | |
| 1042 | adjust_window_size_to_satisfy_constraints(self, winit_min_inner, winit_max_inner); |
| 1043 | |
| 1044 | // Auto-resize to the preferred size if users (SlintPad) requests it |
| 1045 | #[cfg (target_arch = "wasm32" )] |
| 1046 | if let Some(canvas) = |
| 1047 | winit_window_or_none.as_window().and_then(|winit_window| winit_window.canvas()) |
| 1048 | { |
| 1049 | if canvas |
| 1050 | .dataset() |
| 1051 | .get("slintAutoResizeToPreferred" ) |
| 1052 | .and_then(|val_str| val_str.parse().ok()) |
| 1053 | .unwrap_or_default() |
| 1054 | { |
| 1055 | let pref = new_constraints.preferred; |
| 1056 | if pref.width > 0 as Coord || pref.height > 0 as Coord { |
| 1057 | // TODO: don't ignore error, propgate to caller |
| 1058 | self.resize_window(logical_size_to_winit(pref).into()).ok(); |
| 1059 | }; |
| 1060 | } |
| 1061 | } |
| 1062 | } |
| 1063 | |
| 1064 | fn internal(&self, _: corelib::InternalToken) -> Option<&dyn WindowAdapterInternal> { |
| 1065 | Some(self) |
| 1066 | } |
| 1067 | } |
| 1068 | |
| 1069 | impl WindowAdapterInternal for WinitWindowAdapter { |
| 1070 | fn set_mouse_cursor(&self, cursor: MouseCursor) { |
| 1071 | let winit_cursor = match cursor { |
| 1072 | MouseCursor::Default => winit::window::CursorIcon::Default, |
| 1073 | MouseCursor::None => winit::window::CursorIcon::Default, |
| 1074 | MouseCursor::Help => winit::window::CursorIcon::Help, |
| 1075 | MouseCursor::Pointer => winit::window::CursorIcon::Pointer, |
| 1076 | MouseCursor::Progress => winit::window::CursorIcon::Progress, |
| 1077 | MouseCursor::Wait => winit::window::CursorIcon::Wait, |
| 1078 | MouseCursor::Crosshair => winit::window::CursorIcon::Crosshair, |
| 1079 | MouseCursor::Text => winit::window::CursorIcon::Text, |
| 1080 | MouseCursor::Alias => winit::window::CursorIcon::Alias, |
| 1081 | MouseCursor::Copy => winit::window::CursorIcon::Copy, |
| 1082 | MouseCursor::Move => winit::window::CursorIcon::Move, |
| 1083 | MouseCursor::NoDrop => winit::window::CursorIcon::NoDrop, |
| 1084 | MouseCursor::NotAllowed => winit::window::CursorIcon::NotAllowed, |
| 1085 | MouseCursor::Grab => winit::window::CursorIcon::Grab, |
| 1086 | MouseCursor::Grabbing => winit::window::CursorIcon::Grabbing, |
| 1087 | MouseCursor::ColResize => winit::window::CursorIcon::ColResize, |
| 1088 | MouseCursor::RowResize => winit::window::CursorIcon::RowResize, |
| 1089 | MouseCursor::NResize => winit::window::CursorIcon::NResize, |
| 1090 | MouseCursor::EResize => winit::window::CursorIcon::EResize, |
| 1091 | MouseCursor::SResize => winit::window::CursorIcon::SResize, |
| 1092 | MouseCursor::WResize => winit::window::CursorIcon::WResize, |
| 1093 | MouseCursor::NeResize => winit::window::CursorIcon::NeResize, |
| 1094 | MouseCursor::NwResize => winit::window::CursorIcon::NwResize, |
| 1095 | MouseCursor::SeResize => winit::window::CursorIcon::SeResize, |
| 1096 | MouseCursor::SwResize => winit::window::CursorIcon::SwResize, |
| 1097 | MouseCursor::EwResize => winit::window::CursorIcon::EwResize, |
| 1098 | MouseCursor::NsResize => winit::window::CursorIcon::NsResize, |
| 1099 | MouseCursor::NeswResize => winit::window::CursorIcon::NeswResize, |
| 1100 | MouseCursor::NwseResize => winit::window::CursorIcon::NwseResize, |
| 1101 | }; |
| 1102 | if let Some(winit_window) = self.winit_window_or_none.borrow().as_window() { |
| 1103 | winit_window.set_cursor_visible(cursor != MouseCursor::None); |
| 1104 | winit_window.set_cursor(winit_cursor); |
| 1105 | } |
| 1106 | } |
| 1107 | |
| 1108 | fn input_method_request(&self, request: corelib::window::InputMethodRequest) { |
| 1109 | #[cfg (not(target_arch = "wasm32" ))] |
| 1110 | if let Some(winit_window) = self.winit_window_or_none.borrow().as_window() { |
| 1111 | let props = match &request { |
| 1112 | corelib::window::InputMethodRequest::Enable(props) => { |
| 1113 | winit_window.set_ime_allowed(true); |
| 1114 | props |
| 1115 | } |
| 1116 | corelib::window::InputMethodRequest::Disable => { |
| 1117 | return winit_window.set_ime_allowed(false); |
| 1118 | } |
| 1119 | corelib::window::InputMethodRequest::Update(props) => props, |
| 1120 | _ => return, |
| 1121 | }; |
| 1122 | winit_window.set_ime_purpose(match props.input_type { |
| 1123 | corelib::items::InputType::Password => winit::window::ImePurpose::Password, |
| 1124 | _ => winit::window::ImePurpose::Normal, |
| 1125 | }); |
| 1126 | winit_window.set_ime_cursor_area( |
| 1127 | position_to_winit(&props.cursor_rect_origin.into()), |
| 1128 | window_size_to_winit(&props.cursor_rect_size.into()), |
| 1129 | ); |
| 1130 | } |
| 1131 | |
| 1132 | #[cfg (target_arch = "wasm32" )] |
| 1133 | match request { |
| 1134 | corelib::window::InputMethodRequest::Enable(..) => { |
| 1135 | let mut vkh = self.virtual_keyboard_helper.borrow_mut(); |
| 1136 | let Some(canvas) = |
| 1137 | self.winit_window().and_then(|winit_window| winit_window.canvas()) |
| 1138 | else { |
| 1139 | return; |
| 1140 | }; |
| 1141 | let h = vkh.get_or_insert_with(|| { |
| 1142 | super::wasm_input_helper::WasmInputHelper::new(self.self_weak.clone(), canvas) |
| 1143 | }); |
| 1144 | h.show(); |
| 1145 | } |
| 1146 | corelib::window::InputMethodRequest::Disable => { |
| 1147 | if let Some(h) = &*self.virtual_keyboard_helper.borrow() { |
| 1148 | h.hide() |
| 1149 | } |
| 1150 | } |
| 1151 | _ => {} |
| 1152 | }; |
| 1153 | } |
| 1154 | |
| 1155 | fn as_any(&self) -> &dyn std::any::Any { |
| 1156 | self |
| 1157 | } |
| 1158 | |
| 1159 | fn color_scheme(&self) -> ColorScheme { |
| 1160 | self.color_scheme |
| 1161 | .get_or_init(|| { |
| 1162 | Box::pin(Property::new({ |
| 1163 | cfg_if::cfg_if! { |
| 1164 | if #[cfg(use_winit_theme)] { |
| 1165 | self.winit_window_or_none |
| 1166 | .borrow() |
| 1167 | .as_window() |
| 1168 | .and_then(|window| window.theme()) |
| 1169 | .map_or(ColorScheme::Unknown, |theme| match theme { |
| 1170 | winit::window::Theme::Dark => ColorScheme::Dark, |
| 1171 | winit::window::Theme::Light => ColorScheme::Light, |
| 1172 | }) |
| 1173 | } else { |
| 1174 | if let Some(old_watch) = self.xdg_settings_watcher.replace(self.spawn_xdg_settings_watcher()) { |
| 1175 | old_watch.abort() |
| 1176 | } |
| 1177 | ColorScheme::Unknown |
| 1178 | } |
| 1179 | } |
| 1180 | })) |
| 1181 | }) |
| 1182 | .as_ref() |
| 1183 | .get() |
| 1184 | } |
| 1185 | |
| 1186 | #[cfg (muda)] |
| 1187 | fn supports_native_menu_bar(&self) -> bool { |
| 1188 | true |
| 1189 | } |
| 1190 | |
| 1191 | #[cfg (muda)] |
| 1192 | fn setup_menubar(&self, menubar: vtable::VBox<i_slint_core::menus::MenuVTable>) { |
| 1193 | self.menubar.replace(Some(menubar)); |
| 1194 | |
| 1195 | if let WinitWindowOrNone::HasWindow { muda_adapter, .. } = |
| 1196 | &*self.winit_window_or_none.borrow() |
| 1197 | { |
| 1198 | // On Windows, we must destroy the muda menu before re-creating a new one |
| 1199 | drop(muda_adapter.borrow_mut().take()); |
| 1200 | muda_adapter.replace(Some(crate::muda::MudaAdapter::setup( |
| 1201 | self.menubar.borrow().as_ref().unwrap(), |
| 1202 | &self.winit_window().unwrap(), |
| 1203 | self.event_loop_proxy.clone(), |
| 1204 | self.self_weak.clone(), |
| 1205 | ))); |
| 1206 | } |
| 1207 | } |
| 1208 | |
| 1209 | #[cfg (enable_accesskit)] |
| 1210 | fn handle_focus_change(&self, _old: Option<ItemRc>, _new: Option<ItemRc>) { |
| 1211 | let Some(accesskit_adapter_cell) = self.accesskit_adapter() else { return }; |
| 1212 | accesskit_adapter_cell.borrow_mut().handle_focus_item_change(); |
| 1213 | } |
| 1214 | |
| 1215 | #[cfg (enable_accesskit)] |
| 1216 | fn register_item_tree(&self) { |
| 1217 | let Some(accesskit_adapter_cell) = self.accesskit_adapter() else { return }; |
| 1218 | // 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 |
| 1219 | if let Ok(mut a) = accesskit_adapter_cell.try_borrow_mut() { |
| 1220 | a.reload_tree(); |
| 1221 | }; |
| 1222 | } |
| 1223 | |
| 1224 | #[cfg (enable_accesskit)] |
| 1225 | fn unregister_item_tree( |
| 1226 | &self, |
| 1227 | component: ItemTreeRef, |
| 1228 | _: &mut dyn Iterator<Item = Pin<ItemRef<'_>>>, |
| 1229 | ) { |
| 1230 | let Some(accesskit_adapter_cell) = self.accesskit_adapter() else { return }; |
| 1231 | if let Ok(mut a) = accesskit_adapter_cell.try_borrow_mut() { |
| 1232 | a.unregister_item_tree(component); |
| 1233 | }; |
| 1234 | } |
| 1235 | |
| 1236 | #[cfg (feature = "raw-window-handle-06" )] |
| 1237 | fn window_handle_06_rc( |
| 1238 | &self, |
| 1239 | ) -> Result<Rc<dyn raw_window_handle::HasWindowHandle>, raw_window_handle::HandleError> { |
| 1240 | self.winit_window_or_none |
| 1241 | .borrow() |
| 1242 | .as_window() |
| 1243 | .map_or(Err(raw_window_handle::HandleError::Unavailable), |window| Ok(window)) |
| 1244 | } |
| 1245 | |
| 1246 | #[cfg (feature = "raw-window-handle-06" )] |
| 1247 | fn display_handle_06_rc( |
| 1248 | &self, |
| 1249 | ) -> Result<Rc<dyn raw_window_handle::HasDisplayHandle>, raw_window_handle::HandleError> { |
| 1250 | self.winit_window_or_none |
| 1251 | .borrow() |
| 1252 | .as_window() |
| 1253 | .map_or(Err(raw_window_handle::HandleError::Unavailable), |window| Ok(window)) |
| 1254 | } |
| 1255 | |
| 1256 | fn bring_to_front(&self) -> Result<(), PlatformError> { |
| 1257 | if let Some(winit_window) = self.winit_window_or_none.borrow().as_window() { |
| 1258 | winit_window.set_minimized(false); |
| 1259 | winit_window.focus_window(); |
| 1260 | } |
| 1261 | Ok(()) |
| 1262 | } |
| 1263 | } |
| 1264 | |
| 1265 | impl Drop for WinitWindowAdapter { |
| 1266 | fn drop(&mut self) { |
| 1267 | if let Some(winit_window: Rc) = self.winit_window_or_none.borrow().as_window() { |
| 1268 | self.shared_backend_data.unregister_window(winit_window.id()); |
| 1269 | } |
| 1270 | |
| 1271 | #[cfg (not(use_winit_theme))] |
| 1272 | if let Some(xdg_watch_future: JoinHandle<()>) = self.xdg_settings_watcher.take() { |
| 1273 | xdg_watch_future.abort(); |
| 1274 | } |
| 1275 | } |
| 1276 | } |
| 1277 | |
| 1278 | // Winit doesn't automatically resize the window to satisfy constraints. Qt does it though, and so do we here. |
| 1279 | fn adjust_window_size_to_satisfy_constraints( |
| 1280 | adapter: &WinitWindowAdapter, |
| 1281 | min_size: Option<winit::dpi::PhysicalSize<f32>>, |
| 1282 | max_size: Option<winit::dpi::PhysicalSize<f32>>, |
| 1283 | ) { |
| 1284 | let current_size = adapter |
| 1285 | .pending_requested_size |
| 1286 | .get() |
| 1287 | .map(|s| s.to_physical(adapter.window().scale_factor() as f64)) |
| 1288 | .unwrap_or_else(|| physical_size_to_winit(adapter.size.get())); |
| 1289 | |
| 1290 | let mut window_size = current_size; |
| 1291 | if let Some(min_size) = min_size { |
| 1292 | let min_size = min_size.cast(); |
| 1293 | window_size.width = window_size.width.max(min_size.width); |
| 1294 | window_size.height = window_size.height.max(min_size.height); |
| 1295 | } |
| 1296 | |
| 1297 | if let Some(max_size) = max_size { |
| 1298 | let max_size = max_size.cast(); |
| 1299 | window_size.width = window_size.width.min(max_size.width); |
| 1300 | window_size.height = window_size.height.min(max_size.height); |
| 1301 | } |
| 1302 | |
| 1303 | if window_size != current_size { |
| 1304 | // TODO: don't ignore error, propgate to caller |
| 1305 | adapter.resize_window(window_size.into()).ok(); |
| 1306 | } |
| 1307 | } |
| 1308 | |