1 | // Copyright © SixtyFPS GmbH <info@slint.dev> |
2 | // SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-1.1 OR LicenseRef-Slint-commercial |
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; |
9 | #[cfg (target_arch = "wasm32" )] |
10 | use core::cell::RefCell; |
11 | use core::pin::Pin; |
12 | use std::rc::Rc; |
13 | #[cfg (target_arch = "wasm32" )] |
14 | use std::rc::Weak; |
15 | |
16 | #[cfg (target_arch = "wasm32" )] |
17 | use winit::platform::web::WindowExtWebSys; |
18 | |
19 | use crate::renderer::WinitCompatibleRenderer; |
20 | use const_field_offset::FieldOffsets; |
21 | |
22 | use corelib::item_tree::ItemTreeRc; |
23 | #[cfg (enable_accesskit)] |
24 | use corelib::item_tree::ItemTreeRef; |
25 | use corelib::items::MouseCursor; |
26 | #[cfg (enable_accesskit)] |
27 | use corelib::items::{ItemRc, ItemRef}; |
28 | |
29 | use corelib::api::PhysicalSize; |
30 | use corelib::layout::Orientation; |
31 | use corelib::lengths::LogicalLength; |
32 | use corelib::platform::{PlatformError, WindowEvent}; |
33 | use corelib::window::{WindowAdapter, WindowAdapterInternal, WindowInner}; |
34 | use corelib::Property; |
35 | use corelib::{graphics::*, Coord}; |
36 | use i_slint_core as corelib; |
37 | use once_cell::unsync::OnceCell; |
38 | use winit::window::WindowBuilder; |
39 | |
40 | fn position_to_winit(pos: &corelib::api::WindowPosition) -> winit::dpi::Position { |
41 | match pos { |
42 | corelib::api::WindowPosition::Logical(pos: &LogicalPosition) => { |
43 | winit::dpi::Position::new(position:winit::dpi::LogicalPosition::new(pos.x, pos.y)) |
44 | } |
45 | corelib::api::WindowPosition::Physical(pos: &PhysicalPosition) => { |
46 | winit::dpi::Position::new(position:winit::dpi::PhysicalPosition::new(pos.x, pos.y)) |
47 | } |
48 | } |
49 | } |
50 | |
51 | fn window_size_to_winit(size: &corelib::api::WindowSize) -> winit::dpi::Size { |
52 | match size { |
53 | corelib::api::WindowSize::Logical(size: &LogicalSize) => { |
54 | winit::dpi::Size::new(size:winit::dpi::LogicalSize::new(size.width, size.height)) |
55 | } |
56 | corelib::api::WindowSize::Physical(size: &PhysicalSize) => { |
57 | winit::dpi::Size::new(size:winit::dpi::PhysicalSize::new(size.width, size.height)) |
58 | } |
59 | } |
60 | } |
61 | |
62 | pub fn physical_size_to_slint(size: &winit::dpi::PhysicalSize<u32>) -> corelib::api::PhysicalSize { |
63 | corelib::api::PhysicalSize::new(size.width, size.height) |
64 | } |
65 | |
66 | fn icon_to_winit(icon: corelib::graphics::Image) -> Option<winit::window::Icon> { |
67 | let image_inner: &ImageInner = (&icon).into(); |
68 | |
69 | let pixel_buffer = match image_inner { |
70 | ImageInner::EmbeddedImage { buffer, .. } => buffer.clone(), |
71 | _ => return None, |
72 | }; |
73 | |
74 | // This could become a method in SharedPixelBuffer... |
75 | let rgba_pixels: Vec<u8> = match &pixel_buffer { |
76 | SharedImageBuffer::RGB8(pixels) => pixels |
77 | .as_bytes() |
78 | .chunks(3) |
79 | .flat_map(|rgb| IntoIterator::into_iter([rgb[0], rgb[1], rgb[2], 255])) |
80 | .collect(), |
81 | SharedImageBuffer::RGBA8(pixels) => pixels.as_bytes().to_vec(), |
82 | SharedImageBuffer::RGBA8Premultiplied(pixels) => pixels |
83 | .as_bytes() |
84 | .chunks(4) |
85 | .flat_map(|rgba| { |
86 | let alpha = rgba[3] as u32; |
87 | IntoIterator::into_iter(rgba) |
88 | .take(3) |
89 | .map(move |component| (*component as u32 * alpha / 255) as u8) |
90 | .chain(std::iter::once(alpha as u8)) |
91 | }) |
92 | .collect(), |
93 | }; |
94 | |
95 | winit::window::Icon::from_rgba(rgba_pixels, pixel_buffer.width(), pixel_buffer.height()).ok() |
96 | } |
97 | |
98 | fn window_is_resizable( |
99 | min_size: Option<corelib::api::LogicalSize>, |
100 | max_size: Option<corelib::api::LogicalSize>, |
101 | ) -> bool { |
102 | if let Some(( |
103 | corelib::api::LogicalSize { width: min_width: f32, height: min_height: f32, .. }, |
104 | corelib::api::LogicalSize { width: max_width: f32, height: max_height: f32, .. }, |
105 | )) = min_size.zip(max_size) |
106 | { |
107 | min_width < max_width || min_height < max_height |
108 | } else { |
109 | true |
110 | } |
111 | } |
112 | |
113 | /// GraphicsWindow is an implementation of the [WindowAdapter][`crate::eventloop::WindowAdapter`] trait. This is |
114 | /// typically instantiated by entry factory functions of the different graphics back ends. |
115 | pub struct WinitWindowAdapter { |
116 | window: OnceCell<corelib::api::Window>, |
117 | #[cfg (target_arch = "wasm32" )] |
118 | self_weak: Weak<Self>, |
119 | pending_redraw: Cell<bool>, |
120 | dark_color_scheme: OnceCell<Pin<Box<Property<bool>>>>, |
121 | constraints: Cell<corelib::window::LayoutConstraints>, |
122 | shown: Cell<bool>, |
123 | window_level: Cell<winit::window::WindowLevel>, |
124 | maximized: Cell<bool>, |
125 | minimized: Cell<bool>, |
126 | fullscreen: Cell<bool>, |
127 | |
128 | pub(crate) renderer: Box<dyn WinitCompatibleRenderer>, |
129 | /// We cache the size because winit_window.inner_size() can return different value between calls (eg, on X11) |
130 | /// And we wan see the newer value before the Resized event was received, leading to inconsistencies |
131 | size: Cell<PhysicalSize>, |
132 | |
133 | /// Whether the size has been set explicitly via `set_size` |
134 | has_explicit_size: Cell<bool>, |
135 | |
136 | #[cfg (target_arch = "wasm32" )] |
137 | virtual_keyboard_helper: RefCell<Option<super::wasm_input_helper::WasmInputHelper>>, |
138 | |
139 | #[cfg (enable_accesskit)] |
140 | pub accesskit_adapter: crate::accesskit::AccessKitAdapter, |
141 | |
142 | winit_window: Rc<winit::window::Window>, // Last field so that any previously provided window handles are still valid in the drop impl of the renderers, etc. |
143 | } |
144 | |
145 | impl WinitWindowAdapter { |
146 | /// Creates a new reference-counted instance. |
147 | pub(crate) fn new( |
148 | renderer: Box<dyn WinitCompatibleRenderer>, |
149 | winit_window: Rc<winit::window::Window>, |
150 | ) -> Rc<Self> { |
151 | let self_rc = Rc::new_cyclic(|self_weak| Self { |
152 | window: OnceCell::with_value(corelib::api::Window::new(self_weak.clone() as _)), |
153 | #[cfg (target_arch = "wasm32" )] |
154 | self_weak: self_weak.clone(), |
155 | pending_redraw: Default::default(), |
156 | dark_color_scheme: Default::default(), |
157 | constraints: Default::default(), |
158 | shown: Default::default(), |
159 | window_level: Default::default(), |
160 | maximized: Cell::default(), |
161 | minimized: Cell::default(), |
162 | fullscreen: Cell::default(), |
163 | winit_window: winit_window.clone(), |
164 | size: Default::default(), |
165 | has_explicit_size: Default::default(), |
166 | renderer, |
167 | #[cfg (target_arch = "wasm32" )] |
168 | virtual_keyboard_helper: Default::default(), |
169 | #[cfg (enable_accesskit)] |
170 | accesskit_adapter: crate::accesskit::AccessKitAdapter::new( |
171 | self_weak.clone(), |
172 | &winit_window, |
173 | ), |
174 | }); |
175 | |
176 | let id = self_rc.winit_window().id(); |
177 | crate::event_loop::register_window(id, (self_rc.clone()) as _); |
178 | |
179 | let scale_factor = std::env::var("SLINT_SCALE_FACTOR" ) |
180 | .ok() |
181 | .and_then(|x| x.parse::<f32>().ok()) |
182 | .filter(|f| *f > 0.) |
183 | .unwrap_or_else(|| self_rc.winit_window().scale_factor() as f32); |
184 | self_rc.window().dispatch_event(WindowEvent::ScaleFactorChanged { scale_factor }); |
185 | |
186 | self_rc |
187 | } |
188 | |
189 | fn renderer(&self) -> &dyn WinitCompatibleRenderer { |
190 | self.renderer.as_ref() |
191 | } |
192 | |
193 | pub(crate) fn window_builder( |
194 | #[cfg (target_arch = "wasm32" )] canvas_id: &str, |
195 | ) -> Result<WindowBuilder, PlatformError> { |
196 | let mut window_builder = WindowBuilder::new().with_transparent(true).with_visible(false); |
197 | |
198 | window_builder = window_builder.with_title("Slint Window" .to_string()); |
199 | |
200 | #[cfg (target_arch = "wasm32" )] |
201 | { |
202 | use winit::platform::web::WindowBuilderExtWebSys; |
203 | |
204 | use wasm_bindgen::JsCast; |
205 | |
206 | let html_canvas = web_sys::window() |
207 | .ok_or_else(|| "winit backend: Could not retrieve DOM window" .to_string())? |
208 | .document() |
209 | .ok_or_else(|| "winit backend: Could not retrieve DOM document" .to_string())? |
210 | .get_element_by_id(canvas_id) |
211 | .ok_or_else(|| { |
212 | format!( |
213 | "winit backend: Could not retrieve existing HTML Canvas element ' {}'" , |
214 | canvas_id |
215 | ) |
216 | })? |
217 | .dyn_into::<web_sys::HtmlCanvasElement>() |
218 | .map_err(|_| { |
219 | format!( |
220 | "winit backend: Specified DOM element ' {}' is not a HTML Canvas" , |
221 | canvas_id |
222 | ) |
223 | })?; |
224 | window_builder = window_builder |
225 | .with_canvas(Some(html_canvas)) |
226 | // Don't activate the window by default, as that will cause the page to scroll, |
227 | // ignoring any existing anchors. |
228 | .with_active(false) |
229 | }; |
230 | |
231 | Ok(window_builder) |
232 | } |
233 | |
234 | /// Draw the items of the specified `component` in the given window. |
235 | pub fn draw(&self) -> Result<(), PlatformError> { |
236 | if !self.shown.get() { |
237 | return Ok(()); // caller bug, doesn't make sense to call draw() when not shown |
238 | } |
239 | |
240 | self.pending_redraw.set(false); |
241 | |
242 | let renderer = self.renderer(); |
243 | renderer.render(self.window())?; |
244 | |
245 | Ok(()) |
246 | } |
247 | |
248 | fn with_window_handle(&self, callback: &mut dyn FnMut(&winit::window::Window)) { |
249 | callback(&self.winit_window()); |
250 | } |
251 | |
252 | pub fn winit_window(&self) -> Rc<winit::window::Window> { |
253 | self.winit_window.clone() |
254 | } |
255 | |
256 | #[cfg (target_arch = "wasm32" )] |
257 | pub fn input_method_focused(&self) -> bool { |
258 | match self.virtual_keyboard_helper.try_borrow() { |
259 | Ok(vkh) => vkh.as_ref().map_or(false, |h| h.has_focus()), |
260 | // the only location in which the virtual_keyboard_helper is mutably borrowed is from |
261 | // show_virtual_keyboard, which means we have the focus |
262 | Err(_) => true, |
263 | } |
264 | } |
265 | |
266 | #[cfg (not(target_arch = "wasm32" ))] |
267 | pub fn input_method_focused(&self) -> bool { |
268 | false |
269 | } |
270 | |
271 | // Requests for the window to be resized. Returns true if the window was resized immediately, |
272 | // or if it will be resized later (false). |
273 | fn resize_window(&self, size: winit::dpi::Size) -> Result<bool, PlatformError> { |
274 | if let Some(size) = self.winit_window().request_inner_size(size) { |
275 | // On wayland we might not get a WindowEvent::Resized, so resize the EGL surface right away. |
276 | self.resize_event(size)?; |
277 | Ok(true) |
278 | } else { |
279 | // None means that we'll get a `WindowEvent::Resized` later |
280 | Ok(false) |
281 | } |
282 | } |
283 | |
284 | pub fn resize_event(&self, size: winit::dpi::PhysicalSize<u32>) -> Result<(), PlatformError> { |
285 | // When a window is minimized on Windows, we get a move event to an off-screen position |
286 | // and a resize even with a zero size. Don't forward that, especially not to the renderer, |
287 | // which might panic when trying to create a zero-sized surface. |
288 | if size.width > 0 && size.height > 0 { |
289 | let physical_size = physical_size_to_slint(&size); |
290 | self.size.set(physical_size); |
291 | let scale_factor = WindowInner::from_pub(self.window()).scale_factor(); |
292 | self.window().dispatch_event(WindowEvent::Resized { |
293 | size: physical_size.to_logical(scale_factor), |
294 | }); |
295 | |
296 | // Workaround fox winit not sync'ing CSS size of the canvas (the size shown on the browser) |
297 | // with the width/height attribute (the size of the viewport/GL surface) |
298 | // If they're not in sync, the UI would be shown as scaled |
299 | #[cfg (target_arch = "wasm32" )] |
300 | if let Some(html_canvas) = self.winit_window.canvas() { |
301 | html_canvas.set_width(physical_size.width); |
302 | html_canvas.set_height(physical_size.height); |
303 | } |
304 | } |
305 | Ok(()) |
306 | } |
307 | |
308 | pub fn set_dark_color_scheme(&self, dark_mode: bool) { |
309 | self.dark_color_scheme |
310 | .get_or_init(|| Box::pin(Property::new(false))) |
311 | .as_ref() |
312 | .set(dark_mode) |
313 | } |
314 | |
315 | pub fn window_state_event(&self) { |
316 | if let Some(minimized) = self.winit_window.is_minimized() { |
317 | self.minimized.set(minimized); |
318 | if minimized != self.window().is_minimized() { |
319 | self.window().set_minimized(minimized); |
320 | } |
321 | } |
322 | |
323 | // The method winit::Window::is_maximized returns false when the window |
324 | // is minimized, even if it was previously maximized. We have to ensure |
325 | // that we only update the internal maximized state when the window is |
326 | // not minimized. Otherwise, the window would be restored in a |
327 | // non-maximized state even if it was maximized before being minimized. |
328 | let maximized = self.winit_window.is_maximized(); |
329 | if !self.window().is_minimized() { |
330 | self.maximized.set(maximized); |
331 | if maximized != self.window().is_maximized() { |
332 | self.window().set_maximized(maximized); |
333 | } |
334 | } |
335 | |
336 | // NOTE: Fullscreen overrides maximized so if both are true then the |
337 | // window will remain in fullscreen. Fullscreen must be false to switch |
338 | // to maximized. |
339 | let fullscreen = self.winit_window.fullscreen().is_some(); |
340 | if fullscreen != self.window().is_fullscreen() { |
341 | self.window().set_fullscreen(fullscreen); |
342 | } |
343 | } |
344 | } |
345 | |
346 | impl WindowAdapter for WinitWindowAdapter { |
347 | fn window(&self) -> &corelib::api::Window { |
348 | self.window.get().unwrap() |
349 | } |
350 | |
351 | fn renderer(&self) -> &dyn i_slint_core::renderer::Renderer { |
352 | self.renderer().as_core_renderer() |
353 | } |
354 | |
355 | fn set_visible(&self, visible: bool) -> Result<(), PlatformError> { |
356 | if visible == self.shown.get() { |
357 | return Ok(()); |
358 | } |
359 | |
360 | self.shown.set(visible); |
361 | if visible { |
362 | let winit_window = self.winit_window(); |
363 | |
364 | let runtime_window = WindowInner::from_pub(self.window()); |
365 | |
366 | let scale_factor = runtime_window.scale_factor() as f64; |
367 | |
368 | let component_rc = runtime_window.component(); |
369 | let component = ItemTreeRc::borrow_pin(&component_rc); |
370 | |
371 | let layout_info_h = component.as_ref().layout_info(Orientation::Horizontal); |
372 | if let Some(window_item) = runtime_window.window_item() { |
373 | // Setting the width to its preferred size before querying the vertical layout info |
374 | // is important in case the height depends on the width |
375 | window_item.width.set(LogicalLength::new(layout_info_h.preferred_bounded())); |
376 | } |
377 | let layout_info_v = component.as_ref().layout_info(Orientation::Vertical); |
378 | #[allow (unused_mut)] |
379 | let mut preferred_size = winit::dpi::LogicalSize::new( |
380 | layout_info_h.preferred_bounded(), |
381 | layout_info_v.preferred_bounded(), |
382 | ); |
383 | |
384 | #[cfg (target_arch = "wasm32" )] |
385 | if let Some(html_canvas) = winit_window.canvas() { |
386 | let existing_canvas_size = winit::dpi::LogicalSize::new( |
387 | html_canvas.client_width() as f32, |
388 | html_canvas.client_height() as f32, |
389 | ); |
390 | // Try to maintain the existing size of the canvas element, if any |
391 | if existing_canvas_size.width > 0. { |
392 | preferred_size.width = existing_canvas_size.width; |
393 | } |
394 | if existing_canvas_size.height > 0. { |
395 | preferred_size.height = existing_canvas_size.height; |
396 | } |
397 | } |
398 | |
399 | if winit_window.fullscreen().is_none() |
400 | && !self.has_explicit_size.get() |
401 | && preferred_size.width > 0 as Coord |
402 | && preferred_size.height > 0 as Coord |
403 | { |
404 | // use the Slint's window Scale factor to take in account the override |
405 | let size = preferred_size.to_physical::<u32>(scale_factor); |
406 | self.resize_window(size.into())?; |
407 | }; |
408 | |
409 | winit_window.set_visible(true); |
410 | |
411 | // Make sure the dark color scheme property is up-to-date, as it may have been queried earlier when |
412 | // the window wasn't mapped yet. |
413 | if let Some(dark_color_scheme_prop) = self.dark_color_scheme.get() { |
414 | if let Some(theme) = winit_window.theme() { |
415 | dark_color_scheme_prop.as_ref().set(theme == winit::window::Theme::Dark) |
416 | } |
417 | } |
418 | |
419 | // In wasm a request_redraw() issued before show() results in a draw() even when the window |
420 | // isn't visible, as opposed to regular windowing systems. The compensate for the lost draw, |
421 | // explicitly render the first frame on show(). |
422 | #[cfg (target_arch = "wasm32" )] |
423 | if self.pending_redraw.get() { |
424 | self.draw()?; |
425 | }; |
426 | |
427 | Ok(()) |
428 | } else { |
429 | self.winit_window().set_visible(false); |
430 | |
431 | /* FIXME: |
432 | if let Some(existing_blinker) = self.cursor_blinker.borrow().upgrade() { |
433 | existing_blinker.stop(); |
434 | }*/ |
435 | Ok(()) |
436 | } |
437 | } |
438 | |
439 | fn position(&self) -> Option<corelib::api::PhysicalPosition> { |
440 | match self.winit_window().outer_position() { |
441 | Ok(outer_position) => { |
442 | Some(corelib::api::PhysicalPosition::new(outer_position.x, outer_position.y)) |
443 | } |
444 | Err(_) => None, |
445 | } |
446 | } |
447 | |
448 | fn set_position(&self, position: corelib::api::WindowPosition) { |
449 | self.winit_window().set_outer_position(position_to_winit(&position)) |
450 | } |
451 | |
452 | fn set_size(&self, size: corelib::api::WindowSize) { |
453 | self.has_explicit_size.set(true); |
454 | // TODO: don't ignore error, propgate to caller |
455 | self.resize_window(window_size_to_winit(&size)).ok(); |
456 | } |
457 | |
458 | fn size(&self) -> corelib::api::PhysicalSize { |
459 | self.size.get() |
460 | } |
461 | |
462 | fn request_redraw(&self) { |
463 | if !self.pending_redraw.replace(true) { |
464 | self.winit_window.request_redraw() |
465 | } |
466 | } |
467 | |
468 | #[allow (clippy::unnecessary_cast)] // Coord is used! |
469 | fn update_window_properties(&self, properties: corelib::window::WindowProperties<'_>) { |
470 | let Some(window_item) = |
471 | self.window.get().and_then(|w| WindowInner::from_pub(w).window_item()) |
472 | else { |
473 | return; |
474 | }; |
475 | let window_item = window_item.as_pin_ref(); |
476 | |
477 | let winit_window = self.winit_window(); |
478 | |
479 | let mut width = window_item.width().get() as f32; |
480 | let mut height = window_item.height().get() as f32; |
481 | |
482 | let mut must_resize = false; |
483 | |
484 | winit_window.set_window_icon(icon_to_winit(window_item.icon())); |
485 | winit_window.set_title(&properties.title()); |
486 | winit_window |
487 | .set_decorations(!window_item.no_frame() || winit_window.fullscreen().is_some()); |
488 | let new_window_level = if window_item.always_on_top() { |
489 | winit::window::WindowLevel::AlwaysOnTop |
490 | } else { |
491 | winit::window::WindowLevel::Normal |
492 | }; |
493 | // Only change the window level if it changes, to avoid https://github.com/slint-ui/slint/issues/3280 |
494 | // (Ubuntu 20.04's window manager always bringing the window to the front on x11) |
495 | if self.window_level.replace(new_window_level) != new_window_level { |
496 | winit_window.set_window_level(new_window_level); |
497 | } |
498 | |
499 | if width <= 0. || height <= 0. { |
500 | must_resize = true; |
501 | |
502 | let winit_size = |
503 | winit_window.inner_size().to_logical(self.window().scale_factor() as f64); |
504 | |
505 | if width <= 0. { |
506 | width = winit_size.width; |
507 | } |
508 | if height <= 0. { |
509 | height = winit_size.height; |
510 | } |
511 | } |
512 | |
513 | let existing_size = self.size().to_logical(self.window().scale_factor()); |
514 | |
515 | if (existing_size.width - width).abs() > 1. || (existing_size.height - height).abs() > 1. { |
516 | // If we're in fullscreen state, don't try to resize the window but maintain the surface |
517 | // size we've been assigned to from the windowing system. Weston/Wayland don't like it |
518 | // when we create a surface that's bigger than the screen due to constraints (#532). |
519 | if winit_window.fullscreen().is_none() { |
520 | // TODO: don't ignore error, propgate to caller |
521 | let immediately_resized = self |
522 | .resize_window(winit::dpi::LogicalSize::new(width, height).into()) |
523 | .unwrap_or_default(); |
524 | if immediately_resized { |
525 | // The resize event was already dispatched |
526 | must_resize = false; |
527 | } |
528 | } |
529 | } |
530 | |
531 | if must_resize { |
532 | self.window().dispatch_event(WindowEvent::Resized { |
533 | size: i_slint_core::api::LogicalSize::new(width, height), |
534 | }); |
535 | } |
536 | |
537 | self.with_window_handle(&mut |winit_window| { |
538 | let m = properties.is_fullscreen(); |
539 | if m != self.fullscreen.get() { |
540 | if m { |
541 | if winit_window.fullscreen().is_none() { |
542 | winit_window |
543 | .set_fullscreen(Some(winit::window::Fullscreen::Borderless(None))); |
544 | } |
545 | } else { |
546 | winit_window.set_fullscreen(None); |
547 | } |
548 | } |
549 | |
550 | let m = properties.is_maximized(); |
551 | if m != self.maximized.get() { |
552 | self.maximized.set(m); |
553 | winit_window.set_maximized(m); |
554 | } |
555 | |
556 | let m = properties.is_minimized(); |
557 | if m != self.minimized.get() { |
558 | self.minimized.set(m); |
559 | winit_window.set_minimized(m); |
560 | } |
561 | |
562 | // If we're in fullscreen, don't try to resize the window but |
563 | // maintain the surface size we've been assigned to from the |
564 | // windowing system. Weston/Wayland don't like it when we create a |
565 | // surface that's bigger than the screen due to constraints (#532). |
566 | if winit_window.fullscreen().is_some() { |
567 | return; |
568 | } |
569 | |
570 | let new_constraints = properties.layout_constraints(); |
571 | if new_constraints == self.constraints.get() { |
572 | return; |
573 | } |
574 | |
575 | self.constraints.set(new_constraints); |
576 | |
577 | // Use our scale factor instead of winit's logical size to take a scale factor override into account. |
578 | let sf = self.window().scale_factor(); |
579 | |
580 | let into_size = |s: corelib::api::LogicalSize| -> winit::dpi::PhysicalSize<f32> { |
581 | winit::dpi::LogicalSize::new(s.width, s.height).to_physical(sf as f64) |
582 | }; |
583 | |
584 | let resizable = window_is_resizable(new_constraints.min, new_constraints.max); |
585 | // we must call set_resizable before setting the min and max size otherwise setting the min and max size don't work on X11 |
586 | winit_window.set_resizable(resizable); |
587 | let winit_min_inner = new_constraints.min.map(into_size); |
588 | winit_window.set_min_inner_size(winit_min_inner); |
589 | let winit_max_inner = new_constraints.max.map(into_size); |
590 | winit_window.set_max_inner_size(winit_max_inner); |
591 | |
592 | adjust_window_size_to_satisfy_constraints(self, winit_min_inner, winit_max_inner); |
593 | |
594 | // Auto-resize to the preferred size if users (SlintPad) requests it |
595 | #[cfg (target_arch = "wasm32" )] |
596 | if let Some(canvas) = winit_window.canvas() { |
597 | if canvas |
598 | .dataset() |
599 | .get("slintAutoResizeToPreferred" ) |
600 | .and_then(|val_str| val_str.parse().ok()) |
601 | .unwrap_or_default() |
602 | { |
603 | let pref_width = new_constraints.preferred.width; |
604 | let pref_height = new_constraints.preferred.height; |
605 | if pref_width > 0 as Coord || pref_height > 0 as Coord { |
606 | // TODO: don't ignore error, propgate to caller |
607 | self.resize_window( |
608 | winit::dpi::LogicalSize::new(pref_width, pref_height).into(), |
609 | ) |
610 | .ok(); |
611 | }; |
612 | } |
613 | } |
614 | }); |
615 | } |
616 | |
617 | fn internal(&self, _: corelib::InternalToken) -> Option<&dyn WindowAdapterInternal> { |
618 | Some(self) |
619 | } |
620 | } |
621 | |
622 | impl WindowAdapterInternal for WinitWindowAdapter { |
623 | fn set_mouse_cursor(&self, cursor: MouseCursor) { |
624 | let winit_cursor = match cursor { |
625 | MouseCursor::Default => winit::window::CursorIcon::Default, |
626 | MouseCursor::None => winit::window::CursorIcon::Default, |
627 | MouseCursor::Help => winit::window::CursorIcon::Help, |
628 | MouseCursor::Pointer => winit::window::CursorIcon::Pointer, |
629 | MouseCursor::Progress => winit::window::CursorIcon::Progress, |
630 | MouseCursor::Wait => winit::window::CursorIcon::Wait, |
631 | MouseCursor::Crosshair => winit::window::CursorIcon::Crosshair, |
632 | MouseCursor::Text => winit::window::CursorIcon::Text, |
633 | MouseCursor::Alias => winit::window::CursorIcon::Alias, |
634 | MouseCursor::Copy => winit::window::CursorIcon::Copy, |
635 | MouseCursor::Move => winit::window::CursorIcon::Move, |
636 | MouseCursor::NoDrop => winit::window::CursorIcon::NoDrop, |
637 | MouseCursor::NotAllowed => winit::window::CursorIcon::NotAllowed, |
638 | MouseCursor::Grab => winit::window::CursorIcon::Grab, |
639 | MouseCursor::Grabbing => winit::window::CursorIcon::Grabbing, |
640 | MouseCursor::ColResize => winit::window::CursorIcon::ColResize, |
641 | MouseCursor::RowResize => winit::window::CursorIcon::RowResize, |
642 | MouseCursor::NResize => winit::window::CursorIcon::NResize, |
643 | MouseCursor::EResize => winit::window::CursorIcon::EResize, |
644 | MouseCursor::SResize => winit::window::CursorIcon::SResize, |
645 | MouseCursor::WResize => winit::window::CursorIcon::WResize, |
646 | MouseCursor::NeResize => winit::window::CursorIcon::NeResize, |
647 | MouseCursor::NwResize => winit::window::CursorIcon::NwResize, |
648 | MouseCursor::SeResize => winit::window::CursorIcon::SeResize, |
649 | MouseCursor::SwResize => winit::window::CursorIcon::SwResize, |
650 | MouseCursor::EwResize => winit::window::CursorIcon::EwResize, |
651 | MouseCursor::NsResize => winit::window::CursorIcon::NsResize, |
652 | MouseCursor::NeswResize => winit::window::CursorIcon::NeswResize, |
653 | MouseCursor::NwseResize => winit::window::CursorIcon::NwseResize, |
654 | }; |
655 | self.with_window_handle(&mut |winit_window| { |
656 | winit_window.set_cursor_visible(cursor != MouseCursor::None); |
657 | winit_window.set_cursor_icon(winit_cursor); |
658 | }); |
659 | } |
660 | |
661 | fn input_method_request(&self, request: corelib::window::InputMethodRequest) { |
662 | #[cfg (not(target_arch = "wasm32" ))] |
663 | self.with_window_handle(&mut |winit_window| { |
664 | let props = match &request { |
665 | corelib::window::InputMethodRequest::Enable(props) => { |
666 | winit_window.set_ime_allowed(true); |
667 | props |
668 | } |
669 | corelib::window::InputMethodRequest::Disable => { |
670 | return winit_window.set_ime_allowed(false) |
671 | } |
672 | corelib::window::InputMethodRequest::Update(props) => props, |
673 | _ => return, |
674 | }; |
675 | winit_window.set_ime_purpose(match props.input_type { |
676 | corelib::items::InputType::Password => winit::window::ImePurpose::Password, |
677 | _ => winit::window::ImePurpose::Normal, |
678 | }); |
679 | winit_window.set_ime_cursor_area( |
680 | position_to_winit(&props.cursor_rect_origin.into()), |
681 | window_size_to_winit(&props.cursor_rect_size.into()), |
682 | ); |
683 | }); |
684 | |
685 | #[cfg (target_arch = "wasm32" )] |
686 | match request { |
687 | corelib::window::InputMethodRequest::Enable(..) => { |
688 | let mut vkh = self.virtual_keyboard_helper.borrow_mut(); |
689 | let Some(canvas) = self.winit_window().canvas() else { return }; |
690 | let h = vkh.get_or_insert_with(|| { |
691 | super::wasm_input_helper::WasmInputHelper::new(self.self_weak.clone(), canvas) |
692 | }); |
693 | h.show(); |
694 | } |
695 | corelib::window::InputMethodRequest::Disable => { |
696 | if let Some(h) = &*self.virtual_keyboard_helper.borrow() { |
697 | h.hide() |
698 | } |
699 | } |
700 | _ => {} |
701 | }; |
702 | } |
703 | |
704 | fn as_any(&self) -> &dyn std::any::Any { |
705 | self |
706 | } |
707 | |
708 | fn dark_color_scheme(&self) -> bool { |
709 | self.dark_color_scheme |
710 | .get_or_init(|| { |
711 | Box::pin(Property::new({ |
712 | self.winit_window() |
713 | .theme() |
714 | .map_or(false, |theme| theme == winit::window::Theme::Dark) |
715 | })) |
716 | }) |
717 | .as_ref() |
718 | .get() |
719 | } |
720 | |
721 | #[cfg (enable_accesskit)] |
722 | fn handle_focus_change(&self, _old: Option<ItemRc>, _new: Option<ItemRc>) { |
723 | self.accesskit_adapter.handle_focus_item_change(); |
724 | } |
725 | |
726 | #[cfg (enable_accesskit)] |
727 | fn register_item_tree(&self) { |
728 | self.accesskit_adapter.register_item_tree(); |
729 | } |
730 | |
731 | #[cfg (enable_accesskit)] |
732 | fn unregister_item_tree( |
733 | &self, |
734 | _component: ItemTreeRef, |
735 | _: &mut dyn Iterator<Item = Pin<ItemRef<'_>>>, |
736 | ) { |
737 | self.accesskit_adapter.unregister_item_tree(_component); |
738 | } |
739 | } |
740 | |
741 | impl Drop for WinitWindowAdapter { |
742 | fn drop(&mut self) { |
743 | crate::event_loop::unregister_window(self.winit_window().id()); |
744 | } |
745 | } |
746 | |
747 | #[derive (FieldOffsets)] |
748 | #[repr (C)] |
749 | #[pin] |
750 | struct WindowProperties { |
751 | scale_factor: Property<f32>, |
752 | } |
753 | |
754 | impl Default for WindowProperties { |
755 | fn default() -> Self { |
756 | Self { scale_factor: Property::new(1.0) } |
757 | } |
758 | } |
759 | |
760 | // Winit doesn't automatically resize the window to satisfy constraints. Qt does it though, and so do we here. |
761 | fn adjust_window_size_to_satisfy_constraints( |
762 | winit_window: &WinitWindowAdapter, |
763 | min_size: Option<winit::dpi::PhysicalSize<f32>>, |
764 | max_size: Option<winit::dpi::PhysicalSize<f32>>, |
765 | ) { |
766 | let mut window_size: PhysicalSize = winit_window.size(); |
767 | |
768 | if let Some(min_size: PhysicalSize) = min_size { |
769 | let min_size: PhysicalSize = min_size.cast(); |
770 | window_size.width = window_size.width.max(min_size.width); |
771 | window_size.height = window_size.height.max(min_size.height); |
772 | } |
773 | |
774 | if let Some(max_size: PhysicalSize) = max_size { |
775 | let max_size: PhysicalSize = max_size.cast(); |
776 | window_size.width = window_size.width.min(max_size.width); |
777 | window_size.height = window_size.height.min(max_size.height); |
778 | } |
779 | |
780 | if window_size != winit_window.size() { |
781 | // TODO: don't ignore error, propgate to caller |
782 | winit_windowResult |
783 | .resize_window( |
784 | size:winit::dpi::PhysicalSize::new(window_size.width, window_size.height).into(), |
785 | ) |
786 | .ok(); |
787 | } |
788 | } |
789 | |