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 | |