1use core::cell::{Cell, RefCell};
9#[cfg(target_os = "macos")]
10use std::cell::OnceCell;
11use std::rc::Rc;
12use std::rc::Weak;
13use std::sync::Arc;
14
15use euclid::approxeq::ApproxEq;
16
17#[cfg(muda)]
18use i_slint_core::api::LogicalPosition;
19use i_slint_core::cursor::{MouseCursorInner, scaled_hotspot};
20use i_slint_core::lengths::{PhysicalPx, ScaleFactor};
21#[cfg(muda)]
22use i_slint_core::menus::MenuVTable;
23use i_slint_core::renderer::DrawOutcome;
24use winit::event_loop::ActiveEventLoop;
25#[cfg(target_arch = "wasm32")]
26use winit::platform::web::WindowExtWebSys;
27#[cfg(target_family = "windows")]
28use winit::platform::windows::WindowExtWindows;
29
30use crate::drag_resize_window::{handle_cursor_move_for_resize, handle_resize};
31#[cfg(muda)]
32use crate::muda::MudaType;
33use crate::renderer::WinitCompatibleRenderer;
34use crate::winit_compat::WindowSurfaceSizeExt;
35
36use corelib::SharedString;
37use corelib::input::{BackendMouseEvent, InternalKeyEvent, KeyEvent, KeyEventType};
38use corelib::item_tree::ItemTreeRc;
39#[cfg(enable_accesskit)]
40use corelib::item_tree::{ItemTreeRef, ItemTreeRefPin};
41use corelib::items::{BuiltInMouseCursor, ColorScheme, PointerEventButton};
42#[cfg(enable_accesskit)]
43use corelib::items::{ItemRc, ItemRef};
44
45#[cfg(any(enable_accesskit, muda))]
46use crate::SlintEvent;
47use crate::{EventResult, SharedBackendData};
48use corelib::api::PhysicalSize;
49use corelib::layout::Orientation;
50use corelib::lengths::{LogicalLength, LogicalPoint};
51use corelib::platform::{PlatformError, WindowEvent};
52use corelib::window::{WindowAdapter, WindowAdapterInternal, WindowInner};
53use corelib::{Coord, graphics::*};
54use i_slint_core::{self as corelib};
55use winit::event::WindowEvent as WinitWindowEvent;
56#[cfg(any(enable_accesskit, muda))]
57use winit::event_loop::EventLoopProxy;
58use winit::keyboard::Key;
59use winit::window::{
60 CustomCursor, CustomCursorSource, ResizeDirection, WindowAttributes, WindowButtons,
61};
62
63fn winit_touch_phase(phase: winit::event::TouchPhase) -> corelib::input::TouchPhase {
64 match phase {
65 winit::event::TouchPhase::Started => corelib::input::TouchPhase::Started,
66 winit::event::TouchPhase::Moved => corelib::input::TouchPhase::Moved,
67 winit::event::TouchPhase::Ended => corelib::input::TouchPhase::Ended,
68 winit::event::TouchPhase::Cancelled => corelib::input::TouchPhase::Cancelled,
69 }
70}
71
72pub(crate) fn position_to_winit(pos: &corelib::api::WindowPosition) -> winit::dpi::Position {
73 match pos {
74 corelib::api::WindowPosition::Logical(pos) => {
75 winit::dpi::Position::new(winit::dpi::LogicalPosition::new(pos.x, pos.y))
76 }
77 corelib::api::WindowPosition::Physical(pos) => {
78 winit::dpi::Position::new(winit::dpi::PhysicalPosition::new(pos.x, pos.y))
79 }
80 }
81}
82
83fn window_size_to_winit(size: &corelib::api::WindowSize) -> winit::dpi::Size {
84 match size {
85 corelib::api::WindowSize::Logical(size) => {
86 winit::dpi::Size::new(logical_size_to_winit(*size))
87 }
88 corelib::api::WindowSize::Physical(size) => {
89 winit::dpi::Size::new(physical_size_to_winit(*size))
90 }
91 }
92}
93
94pub fn physical_size_to_slint(size: &winit::dpi::PhysicalSize<u32>) -> corelib::api::PhysicalSize {
95 corelib::api::PhysicalSize::new(size.width, size.height)
96}
97
98fn logical_size_to_winit(s: i_slint_core::api::LogicalSize) -> winit::dpi::LogicalSize<f64> {
99 winit::dpi::LogicalSize::new(s.width as f64, s.height as f64)
100}
101
102fn physical_size_to_winit(size: PhysicalSize) -> winit::dpi::PhysicalSize<u32> {
103 winit::dpi::PhysicalSize::new(size.width, size.height)
104}
105
106fn filter_out_zero_width_or_height(
107 size: winit::dpi::LogicalSize<f64>,
108) -> winit::dpi::LogicalSize<f64> {
109 fn filter(v: f64) -> f64 {
110 if v.approx_eq(&0.) {
111 10.
113 } else {
114 v
115 }
116 }
117 winit::dpi::LogicalSize { width: filter(size.width), height: filter(size.height) }
118}
119
120fn round_up_logical(logical: f64, scale_factor: f64) -> f64 {
130 let target = logical * scale_factor;
133 let result = logical.ceil().max(((target.ceil() - 0.5) / scale_factor).ceil());
134 if (result * scale_factor).round() < target { result + 1. } else { result }
137}
138
139#[test]
140fn test_round_up_logical() {
141 assert_eq!(round_up_logical(228., 1.), 228.);
142 assert_eq!(round_up_logical(228., 1.3), 229.);
145 assert_eq!(round_up_logical(227.5, 2.), 228.);
147 assert_eq!(round_up_logical(12., 0.2), 13.);
149 assert_eq!(round_up_logical(21., 0.1), 25.);
151}
152
153fn platform_dictates_window_size() -> bool {
159 cfg!(ios_and_friends)
160}
161
162fn apply_scale_factor_to_logical_sizes_in_attributes(
163 attributes: &mut WindowAttributes,
164 scale_factor: f64,
165) {
166 let fixup = |maybe_size: &mut Option<winit::dpi::Size>| {
167 if let Some(size) = maybe_size.as_mut() {
168 *size = winit::dpi::Size::Physical(size.to_physical::<u32>(scale_factor))
169 }
170 };
171
172 fixup(&mut attributes.inner_size);
173 fixup(&mut attributes.min_inner_size);
174 fixup(&mut attributes.max_inner_size);
175 fixup(&mut attributes.resize_increments);
176}
177
178fn icon_to_winit(
179 icon: corelib::graphics::Image,
180 size: euclid::Size2D<Coord, PhysicalPx>,
181) -> Option<winit::window::Icon> {
182 let image_inner: &ImageInner = (&icon).into();
183
184 let pixel_buffer = image_inner.render_to_buffer(Some(size.cast()))?;
185
186 let rgba_pixels: Vec<u8> = match &pixel_buffer {
188 SharedImageBuffer::RGB8(pixels) => pixels
189 .as_bytes()
190 .chunks(3)
191 .flat_map(|rgb| IntoIterator::into_iter([rgb[0], rgb[1], rgb[2], 255]))
192 .collect(),
193 SharedImageBuffer::RGBA8(pixels) => pixels.as_bytes().to_vec(),
194 SharedImageBuffer::RGBA8Premultiplied(pixels) => pixels
195 .as_bytes()
196 .chunks(4)
197 .flat_map(|rgba| {
198 let alpha = rgba[3] as u32;
199 IntoIterator::into_iter(rgba)
200 .take(3)
201 .map(move |component| (*component as u32 * alpha / 255) as u8)
202 .chain(std::iter::once(alpha as u8))
203 })
204 .collect(),
205 };
206
207 winit::window::Icon::from_rgba(rgba_pixels, pixel_buffer.width(), pixel_buffer.height()).ok()
208}
209
210fn window_is_resizable(
211 min_size: Option<corelib::api::LogicalSize>,
212 max_size: Option<corelib::api::LogicalSize>,
213) -> bool {
214 if let Some((
215 corelib::api::LogicalSize { width: min_width, height: min_height, .. },
216 corelib::api::LogicalSize { width: max_width, height: max_height, .. },
217 )) = min_size.zip(max_size)
218 {
219 min_width < max_width || min_height < max_height
220 } else {
221 true
222 }
223}
224
225#[allow(clippy::large_enum_variant)]
226enum WinitWindowOrNone {
227 HasWindow {
228 window: Arc<winit::window::Window>,
229 frame_throttle: Box<dyn crate::frame_throttle::FrameThrottle>,
230 #[cfg(enable_accesskit)]
231 accesskit_adapter: RefCell<crate::accesskit::AccessKitAdapter>,
232 #[cfg(muda)]
233 muda_adapter: RefCell<Option<crate::muda::MudaAdapter>>,
234 #[cfg(muda)]
235 context_menu_muda_adapter: RefCell<Option<crate::muda::MudaAdapter>>,
236 #[cfg(target_os = "ios")]
237 keyboard_curve_sampler: super::ios::KeyboardCurveSampler,
238 #[cfg(target_os = "ios")]
239 _color_scheme_observer: Option<super::ios::TraitChangeObserver>,
240 #[cfg(target_os = "ios")]
241 _font_size_observer: Option<super::ios::TraitChangeObserver>,
242 },
243 None(RefCell<WindowAttributes>),
244}
245
246impl WinitWindowOrNone {
247 fn as_window(&self) -> Option<Arc<winit::window::Window>> {
248 match self {
249 Self::HasWindow { window, .. } => Some(window.clone()),
250 Self::None { .. } => None,
251 }
252 }
253
254 fn set_window_icon(&self, icon: Option<winit::window::Icon>) {
255 match self {
256 Self::HasWindow { window, .. } => {
257 #[cfg(target_family = "windows")]
258 window.set_taskbar_icon(icon.as_ref().cloned());
259 window.set_window_icon(icon);
260 }
261 Self::None(attributes) => attributes.borrow_mut().window_icon = icon,
262 }
263 }
264
265 fn set_title(&self, title: &str) {
266 match self {
267 Self::HasWindow { window, .. } => window.set_title(title),
268 Self::None(attributes) => attributes.borrow_mut().title = title.into(),
269 }
270 }
271
272 fn set_decorations(&self, decorations: bool) {
273 match self {
274 Self::HasWindow { window, .. } => window.set_decorations(decorations),
275 Self::None(attributes) => attributes.borrow_mut().decorations = decorations,
276 }
277 }
278
279 fn fullscreen(&self) -> Option<winit::window::Fullscreen> {
280 match self {
281 Self::HasWindow { window, .. } => window.fullscreen(),
282 Self::None(attributes) => attributes.borrow().fullscreen.clone(),
283 }
284 }
285
286 fn set_fullscreen(&self, fullscreen: Option<winit::window::Fullscreen>) {
287 match self {
288 Self::HasWindow { window, .. } => window.set_fullscreen(fullscreen),
289 Self::None(attributes) => attributes.borrow_mut().fullscreen = fullscreen,
290 }
291 }
292
293 fn set_window_level(&self, level: winit::window::WindowLevel) {
294 match self {
295 Self::HasWindow { window, .. } => window.set_window_level(level),
296 Self::None(attributes) => attributes.borrow_mut().window_level = level,
297 }
298 }
299
300 fn set_visible(&self, visible: bool) {
301 match self {
302 Self::HasWindow { window, .. } => window.set_visible(visible),
303 Self::None(attributes) => attributes.borrow_mut().visible = visible,
304 }
305 }
306
307 fn set_maximized(&self, maximized: bool) {
308 match self {
309 Self::HasWindow { window, .. } => window.set_maximized(maximized),
310 Self::None(attributes) => attributes.borrow_mut().maximized = maximized,
311 }
312 }
313
314 fn set_minimized(&self, minimized: bool) {
315 match self {
316 Self::HasWindow { window, .. } => window.set_minimized(minimized),
317 Self::None(..) => { }
319 }
320 }
321
322 fn set_resizable(&self, resizable: bool) {
323 match self {
324 Self::HasWindow { window, .. } => {
325 window.set_resizable(resizable);
326 }
327 Self::None(attributes) => attributes.borrow_mut().resizable = resizable,
328 }
329 }
330
331 fn set_min_inner_size(
332 &self,
333 min_inner_size: Option<winit::dpi::LogicalSize<f64>>,
334 scale_factor: f64,
335 ) {
336 match self {
337 Self::HasWindow { window, .. } => {
338 window
340 .set_min_inner_size(min_inner_size.map(|s| s.to_physical::<u32>(scale_factor)))
341 }
342 Self::None(attributes) => {
343 attributes.borrow_mut().min_inner_size = min_inner_size.map(|s| s.into());
345 }
346 }
347 }
348
349 fn set_max_inner_size(
350 &self,
351 max_inner_size: Option<winit::dpi::LogicalSize<f64>>,
352 scale_factor: f64,
353 ) {
354 match self {
355 Self::HasWindow { window, .. } => {
356 window
358 .set_max_inner_size(max_inner_size.map(|s| s.to_physical::<u32>(scale_factor)))
359 }
360 Self::None(attributes) => {
361 attributes.borrow_mut().max_inner_size = max_inner_size.map(|s| s.into())
363 }
364 }
365 }
366}
367
368#[derive(Default, PartialEq, Clone, Copy)]
369pub(crate) enum WindowVisibility {
370 #[default]
371 Hidden,
372 ShownFirstTime,
374 Shown,
375}
376
377pub struct WinitWindowAdapter {
380 pub shared_backend_data: Rc<SharedBackendData>,
381 window: corelib::api::Window,
382 pub(crate) self_weak: Weak<Self>,
383 pending_redraw: Cell<bool>,
384 constraints: Cell<corelib::window::LayoutConstraints>,
385 shown: Cell<WindowVisibility>,
387 window_level: Cell<winit::window::WindowLevel>,
388 maximized: Cell<bool>,
389 minimized: Cell<bool>,
390 fullscreen: Cell<bool>,
391 #[cfg(target_os = "macos")]
394 transparent: Cell<bool>,
395
396 pub(crate) renderer: Box<dyn WinitCompatibleRenderer>,
397 size: Cell<PhysicalSize>,
400 pending_requested_size: Cell<Option<winit::dpi::Size>>,
402 physical_size_before_scale_factor: Cell<Option<winit::dpi::PhysicalSize<u32>>>,
407
408 has_explicit_size: Cell<bool>,
411
412 pending_resize_event_after_show: Cell<bool>,
414
415 first_frame_presented: Cell<bool>,
417
418 #[cfg(target_arch = "wasm32")]
419 virtual_keyboard_helper: RefCell<Option<super::wasm_input_helper::WasmInputHelper>>,
420
421 #[cfg(target_os = "macos")]
423 reveal_on_first_frame: RefCell<Option<crate::macos::RevealOnFirstFrame>>,
424
425 #[cfg(any(enable_accesskit, muda))]
426 event_loop_proxy: EventLoopProxy<SlintEvent>,
427
428 pub(crate) window_event_filter: Cell<
429 Option<Box<dyn FnMut(&corelib::api::Window, &winit::event::WindowEvent) -> EventResult>>,
430 >,
431
432 winit_window_or_none: RefCell<WinitWindowOrNone>,
433 window_existence_wakers: RefCell<Vec<core::task::Waker>>,
434
435 #[cfg(target_os = "macos")]
436 macos_color_observer: OnceCell<
437 objc2::rc::Retained<objc2::runtime::ProtocolObject<dyn objc2::runtime::NSObjectProtocol>>,
438 >,
439
440 #[cfg(muda)]
443 menubar_weak: RefCell<Option<vtable::VWeak<MenuVTable>>>,
444
445 #[cfg(muda)]
446 context_menu: RefCell<Option<vtable::VRc<MenuVTable>>>,
447
448 #[cfg(all(muda, target_os = "macos"))]
449 muda_enable_default_menu_bar: bool,
450
451 window_icon_cache_key: RefCell<Option<ImageCacheKey>>,
454
455 custom_cursor_source: Cell<Option<CustomCursorSource>>,
456
457 cursor_pos: Cell<LogicalPoint>,
459 pressed: Cell<bool>,
462 current_resize_direction: Cell<Option<ResizeDirection>>,
463 touch_finger_ids: RefCell<crate::touch_finger_id::TouchFingerIdAllocator>,
465}
466
467impl WinitWindowAdapter {
468 pub(crate) fn new(
470 shared_backend_data: Rc<SharedBackendData>,
471 renderer: Box<dyn WinitCompatibleRenderer>,
472 window_attributes: winit::window::WindowAttributes,
473 #[cfg(any(enable_accesskit, muda))] proxy: EventLoopProxy<SlintEvent>,
474 #[cfg(all(muda, target_os = "macos"))] muda_enable_default_menu_bar: bool,
475 ) -> Rc<Self> {
476 let self_rc = Rc::new_cyclic(|self_weak| Self {
477 shared_backend_data: shared_backend_data.clone(),
478 window: corelib::api::Window::new(self_weak.clone() as _),
479 self_weak: self_weak.clone(),
480 pending_redraw: Default::default(),
481 constraints: Default::default(),
482 shown: Default::default(),
483 window_level: Default::default(),
484 maximized: Cell::default(),
485 minimized: Cell::default(),
486 fullscreen: Cell::default(),
487 #[cfg(target_os = "macos")]
488 transparent: Cell::default(),
489 winit_window_or_none: RefCell::new(WinitWindowOrNone::None(window_attributes.into())),
490 window_existence_wakers: RefCell::new(Vec::default()),
491 size: Cell::default(),
492 pending_requested_size: Cell::new(None),
493 physical_size_before_scale_factor: Cell::new(None),
494 has_explicit_size: Default::default(),
495 pending_resize_event_after_show: Default::default(),
496 first_frame_presented: Default::default(),
497 renderer,
498 #[cfg(target_arch = "wasm32")]
499 virtual_keyboard_helper: Default::default(),
500 #[cfg(target_os = "macos")]
501 reveal_on_first_frame: Default::default(),
502 #[cfg(any(enable_accesskit, muda))]
503 event_loop_proxy: proxy,
504 window_event_filter: Cell::new(None),
505 #[cfg(target_os = "macos")]
506 macos_color_observer: OnceCell::new(),
507 #[cfg(muda)]
508 menubar_weak: Default::default(),
509 #[cfg(muda)]
510 context_menu: Default::default(),
511 #[cfg(all(muda, target_os = "macos"))]
512 muda_enable_default_menu_bar,
513 window_icon_cache_key: Default::default(),
514 custom_cursor_source: Cell::new(None),
515 cursor_pos: Default::default(),
516 pressed: Default::default(),
517 current_resize_direction: Default::default(),
518 touch_finger_ids: Default::default(),
519 });
520
521 self_rc.shared_backend_data.register_inactive_window((self_rc.clone()) as _);
522
523 self_rc
524 }
525
526 pub(crate) fn renderer(&self) -> &dyn WinitCompatibleRenderer {
527 self.renderer.as_ref()
528 }
529
530 fn preferred_size(&self) -> Option<winit::dpi::LogicalSize<Coord>> {
537 let runtime_window = WindowInner::from_pub(self.window());
538 let component_rc = runtime_window.try_component()?;
539 let component = ItemTreeRc::borrow_pin(&component_rc);
540 let scale_factor = runtime_window.scale_factor() as f64;
541 let layout_info_h = component.as_ref().layout_info(Orientation::Horizontal);
542 let width = round_up_logical(layout_info_h.preferred_bounded() as f64, scale_factor);
543 let layout_info_v = match runtime_window.window_item() {
544 Some(window_item) => {
547 let current_width = window_item.as_pin_ref().width();
548 window_item.width.set(LogicalLength::new(width as Coord));
549 let layout_info_v = component.as_ref().layout_info(Orientation::Vertical);
550 window_item.width.set(current_width);
551 layout_info_v
552 }
553 None => component.as_ref().layout_info(Orientation::Vertical),
554 };
555 let height = round_up_logical(layout_info_v.preferred_bounded() as f64, scale_factor);
556 let size = winit::dpi::LogicalSize::new(width as Coord, height as Coord);
557 (size.width > 0 as Coord && size.height > 0 as Coord).then_some(size)
558 }
559
560 #[cfg(target_os = "macos")]
566 fn wants_transparent(window_item: core::pin::Pin<&corelib::items::WindowItem>) -> bool {
567 !window_item.background().is_opaque() || window_item.no_frame()
568 }
569
570 pub fn ensure_window(
571 &self,
572 active_event_loop: &ActiveEventLoop,
573 ) -> Result<Arc<winit::window::Window>, PlatformError> {
574 #[allow(unused_mut)]
575 let mut window_attributes = match &*self.winit_window_or_none.borrow() {
576 WinitWindowOrNone::HasWindow { window, .. } => return Ok(window.clone()),
577 WinitWindowOrNone::None(attributes) => attributes.borrow().clone(),
578 };
579
580 #[cfg(all(unix, not(target_vendor = "apple")))]
581 {
582 if let Some(xdg_app_id) = WindowInner::from_pub(self.window()).xdg_app_id() {
583 #[cfg(feature = "wayland")]
584 {
585 use winit::platform::wayland::WindowAttributesExtWayland;
586 window_attributes = window_attributes.with_name(xdg_app_id.clone(), "");
587 }
588 #[cfg(feature = "x11")]
589 {
590 use winit::platform::x11::WindowAttributesExtX11;
591 window_attributes = window_attributes.with_name(xdg_app_id.clone(), "");
592 }
593 }
594 }
595
596 let show_after_creation = std::mem::replace(&mut window_attributes.visible, false);
600 let resizable = window_attributes.resizable;
601
602 let overriding_scale_factor = std::env::var("SLINT_SCALE_FACTOR")
603 .ok()
604 .and_then(|x| x.parse::<f32>().ok())
605 .filter(|f| *f > 0.);
606
607 if let Some(sf) = overriding_scale_factor {
608 apply_scale_factor_to_logical_sizes_in_attributes(&mut window_attributes, sf as f64)
609 }
610
611 #[cfg(all(muda, target_os = "windows"))]
613 if self.menubar().is_some() {
614 window_attributes = window_attributes.with_transparent(false);
615 }
616
617 #[cfg(target_os = "macos")]
618 if let Some(window_item) = WindowInner::from_pub(self.window()).window_item() {
619 let transparent = Self::wants_transparent(window_item.as_pin_ref());
620 window_attributes = window_attributes.with_transparent(transparent);
621 self.transparent.set(transparent);
622 }
623
624 if !platform_dictates_window_size()
628 && !self.has_explicit_size.get()
629 && window_attributes.fullscreen.is_none()
630 && let Some(preferred_size) = self.preferred_size()
631 {
632 window_attributes.inner_size = Some(preferred_size.into());
633 }
634
635 let winit_window =
636 self.renderer.resume(active_event_loop, window_attributes, self.self_weak.clone())?;
637 self.first_frame_presented.set(false);
638
639 cfg_if::cfg_if! {
646 if #[cfg(xdg_desktop_settings)] {
647 let scheme = WindowInner::from_pub(self.window()).context().color_scheme(None);
648 winit_window.set_theme(match scheme {
649 ColorScheme::Dark => Some(winit::window::Theme::Dark),
650 ColorScheme::Light => Some(winit::window::Theme::Light),
651 ColorScheme::Unknown => None,
652 _ => None,
653 });
654 } else {
655 let initial_scheme = winit_window.theme().map_or(ColorScheme::Unknown, |theme| match theme {
656 winit::window::Theme::Dark => ColorScheme::Dark,
657 winit::window::Theme::Light => ColorScheme::Light,
658 });
659 self.set_color_scheme(initial_scheme);
660 #[cfg(target_os = "macos")]
661 self.setup_macos_color_observer();
662 self.set_accent_color(Self::query_system_accent_color());
663 }
664 }
665
666 let scale_factor =
667 overriding_scale_factor.unwrap_or_else(|| winit_window.scale_factor() as f32);
668 self.window()
669 .dispatch_event_with_result(WindowEvent::ScaleFactorChanged { scale_factor })?;
670
671 #[cfg(target_os = "ios")]
672 let (content_view, keyboard_curve_self) = {
673 use objc2::Message as _;
674 use raw_window_handle::HasWindowHandle as _;
675
676 let raw_window_handle::RawWindowHandle::UiKit(window_handle) =
677 winit_window.window_handle().unwrap().as_raw()
678 else {
679 panic!()
680 };
681 let view = unsafe { &*(window_handle.ui_view.as_ptr() as *const objc2_ui_kit::UIView) }
682 .retain();
683 (view, self.self_weak.clone())
684 };
685
686 #[cfg(target_os = "ios")]
689 crate::ios::attach_window_to_scene(&content_view);
690
691 #[cfg(target_os = "ios")]
695 {
696 self.set_color_scheme(crate::ios::current_color_scheme(&content_view));
697 self.set_platform_default_font_size(crate::ios::current_default_font_size(
698 &content_view,
699 ));
700 }
701
702 let frame_throttle = crate::frame_throttle::create_frame_throttle(
703 self.self_weak.clone(),
704 &winit_window,
705 self.shared_backend_data.is_wayland,
706 );
707
708 *self.winit_window_or_none.borrow_mut() = WinitWindowOrNone::HasWindow {
709 window: winit_window.clone(),
710 frame_throttle,
711 #[cfg(enable_accesskit)]
712 accesskit_adapter: crate::accesskit::AccessKitAdapter::new(
713 self.self_weak.clone(),
714 active_event_loop,
715 &winit_window,
716 self.event_loop_proxy.clone(),
717 )
718 .into(),
719 #[cfg(muda)]
720 muda_adapter: RefCell::new(None),
721 #[cfg(muda)]
722 context_menu_muda_adapter: None.into(),
723 #[cfg(target_os = "ios")]
724 keyboard_curve_sampler: super::ios::KeyboardCurveSampler::new(
725 &content_view,
726 move |rect| {
727 if let Some(this) = keyboard_curve_self.upgrade() {
728 use i_slint_core::api::{LogicalPosition, LogicalSize};
729
730 this.window().set_virtual_keyboard(
731 LogicalPosition::new(rect.origin.x as _, rect.origin.y as _),
732 LogicalSize::new(rect.size.width as _, rect.size.height as _),
733 i_slint_core::InternalToken,
734 );
735 }
736 },
737 ),
738 #[cfg(target_os = "ios")]
739 _color_scheme_observer: crate::ios::install_color_scheme_observer(
740 &content_view,
741 self.self_weak.clone(),
742 ),
743 #[cfg(target_os = "ios")]
744 _font_size_observer: crate::ios::install_font_size_observer(
745 &content_view,
746 self.self_weak.clone(),
747 ),
748 };
749
750 #[cfg(muda)]
751 {
752 let menubar = self.menubar();
753 let new_muda_adapter = menubar.as_ref().map(|menubar| {
754 crate::muda::MudaAdapter::setup(
755 menubar,
756 &winit_window,
757 self.event_loop_proxy.clone(),
758 self.self_weak.clone(),
759 )
760 });
761 match &*self.winit_window_or_none.borrow() {
762 WinitWindowOrNone::HasWindow { muda_adapter, .. } => {
763 *muda_adapter.borrow_mut() = new_muda_adapter;
764 }
765 WinitWindowOrNone::None(_) => {
766 }
769 }
770 }
771
772 if show_after_creation {
773 self.shown.set(WindowVisibility::Hidden);
774 self.set_visibility(WindowVisibility::ShownFirstTime)?;
775 }
776
777 {
778 let mut buttons = winit_window.enabled_buttons();
782 buttons.set(WindowButtons::MAXIMIZE, resizable);
783 winit_window.set_enabled_buttons(buttons);
784 }
785
786 self.shared_backend_data
787 .register_window(winit_window.id(), (self.self_weak.upgrade().unwrap()) as _);
788
789 for waker in self.window_existence_wakers.take().into_iter() {
790 waker.wake();
791 }
792
793 Ok(winit_window)
794 }
795
796 pub(crate) fn suspend(&self) -> Result<(), PlatformError> {
797 let mut winit_window_or_none = self.winit_window_or_none.borrow_mut();
798 match *winit_window_or_none {
799 WinitWindowOrNone::HasWindow { ref window, .. } => {
800 self.renderer().suspend()?;
801
802 let last_window_rc = window.clone();
803
804 let mut attributes = Self::window_attributes().unwrap_or_default();
805 attributes.inner_size = Some(physical_size_to_winit(self.size.get()).into());
806 attributes.position = last_window_rc.outer_position().ok().map(|pos| pos.into());
807 *winit_window_or_none = WinitWindowOrNone::None(attributes.into());
808
809 if let Some(last_instance) = Arc::into_inner(last_window_rc) {
810 self.shared_backend_data.unregister_window(Some(last_instance.id()));
814 drop(last_instance);
815 } else {
816 i_slint_core::debug_log!(
817 "Slint winit backend: request to hide window failed because references to the window still exist. This could be an application issue, make sure that there are no slint::WindowHandle instances left"
818 );
819 }
820 }
821 WinitWindowOrNone::None(ref attributes) => {
822 attributes.borrow_mut().visible = false;
823 }
824 }
825
826 Ok(())
827 }
828
829 pub(crate) fn window_attributes() -> Result<WindowAttributes, PlatformError> {
830 let mut attrs = WindowAttributes::default().with_transparent(true).with_visible(false);
831
832 attrs = attrs.with_title(i_slint_core::window::application_name().to_string());
834
835 #[cfg(target_arch = "wasm32")]
836 {
837 use winit::platform::web::WindowAttributesExtWebSys;
838
839 use wasm_bindgen::JsCast;
840
841 if let Some(html_canvas) = web_sys::window()
842 .ok_or_else(|| "winit backend: Could not retrieve DOM window".to_string())?
843 .document()
844 .ok_or_else(|| "winit backend: Could not retrieve DOM document".to_string())?
845 .get_element_by_id("canvas")
846 .and_then(|canvas_elem| canvas_elem.dyn_into::<web_sys::HtmlCanvasElement>().ok())
847 {
848 attrs = attrs
849 .with_canvas(Some(html_canvas))
850 .with_active(false);
853 }
854 };
855
856 Ok(attrs)
857 }
858
859 pub fn draw(&self) -> Result<(), PlatformError> {
861 if matches!(self.shown.get(), WindowVisibility::Hidden) {
862 return Ok(()); }
864
865 self.pending_redraw.set(false);
866
867 if let Some(winit_window) = self.winit_window_or_none.borrow().as_window() {
868 if self.pending_resize_event_after_show.take() {
874 self.resize_event(winit_window.surface_size())?;
875 }
876 }
877
878 let renderer = self.renderer();
879 let outcome = renderer.render(self.window());
880 #[cfg(target_os = "macos")]
882 if !matches!(outcome, Ok(DrawOutcome::Occluded | DrawOutcome::Skipped)) {
883 self.reveal_on_first_frame.take();
884 }
885 if matches!(outcome?, DrawOutcome::Success) {
886 self.first_frame_presented.set(true);
887 } else {
888 self.request_redraw();
891 }
892
893 Ok(())
894 }
895
896 pub fn winit_window(&self) -> Option<Arc<winit::window::Window>> {
897 self.winit_window_or_none.borrow().as_window()
898 }
899
900 #[cfg(target_os = "ios")]
901 pub(crate) fn with_keyboard_curve_sampler<R>(
902 &self,
903 f: impl FnOnce(&super::ios::KeyboardCurveSampler) -> R,
904 ) -> Option<R> {
905 let winit_window_or_none = self.winit_window_or_none.borrow();
906 if let WinitWindowOrNone::HasWindow { keyboard_curve_sampler, .. } = &*winit_window_or_none
907 {
908 Some(f(keyboard_curve_sampler))
909 } else {
910 None
911 }
912 }
913
914 #[cfg(muda)]
915 fn menubar(&self) -> Option<vtable::VRc<MenuVTable>> {
916 self.menubar_weak.borrow().as_ref().and_then(vtable::VWeak::upgrade)
917 }
918
919 #[cfg(muda)]
920 pub fn rebuild_menubar(&self) {
921 let WinitWindowOrNone::HasWindow {
922 window: winit_window,
923 muda_adapter: maybe_muda_adapter,
924 ..
925 } = &*self.winit_window_or_none.borrow()
926 else {
927 return;
928 };
929 let mut maybe_muda_adapter = maybe_muda_adapter.borrow_mut();
930 let Some(muda_adapter) = maybe_muda_adapter.as_mut() else { return };
931 let menubar = self.menubar();
932 muda_adapter.rebuild_menu(winit_window, menubar.as_ref(), MudaType::Menubar);
933 }
934
935 #[cfg(muda)]
936 pub fn muda_event(&self, entry_id: usize, muda_type: MudaType) {
937 let Ok(maybe_muda_adapter) = std::cell::Ref::filter_map(
938 self.winit_window_or_none.borrow(),
939 |winit_window_or_none| match (winit_window_or_none, muda_type) {
940 (WinitWindowOrNone::HasWindow { muda_adapter, .. }, MudaType::Menubar) => {
941 Some(muda_adapter)
942 }
943 (
944 WinitWindowOrNone::HasWindow { context_menu_muda_adapter, .. },
945 MudaType::Context,
946 ) => Some(context_menu_muda_adapter),
947 (WinitWindowOrNone::None(..), _) => None,
948 },
949 ) else {
950 return;
951 };
952 let maybe_muda_adapter = maybe_muda_adapter.borrow();
953 let Some(muda_adapter) = maybe_muda_adapter.as_ref() else { return };
954 match muda_type {
955 MudaType::Menubar => {
956 let Some(menu) = self.menubar() else { return };
957 muda_adapter.invoke(&menu, entry_id);
958 }
959 MudaType::Context => {
960 let menu = self.context_menu.borrow();
961 let Some(menu) = menu.as_ref() else { return };
962 muda_adapter.invoke(menu, entry_id);
963 }
964 }
965 }
966
967 #[cfg(target_arch = "wasm32")]
968 pub fn input_method_focused(&self) -> bool {
969 match self.virtual_keyboard_helper.try_borrow() {
970 Ok(vkh) => vkh.as_ref().map_or(false, |h| h.has_focus()),
971 Err(_) => true,
974 }
975 }
976
977 #[cfg(not(target_arch = "wasm32"))]
978 pub fn input_method_focused(&self) -> bool {
979 false
980 }
981
982 fn resize_window(&self, size: winit::dpi::Size) -> Result<bool, PlatformError> {
985 if platform_dictates_window_size() {
986 self.resize_event(physical_size_to_winit(self.size.get()))?;
988 return Ok(true);
989 }
990 match &*self.winit_window_or_none.borrow() {
991 WinitWindowOrNone::HasWindow { window, .. } => {
992 self.physical_size_before_scale_factor.set(None);
993 if let Some(size) = window.request_inner_size(size) {
994 self.resize_event(size)?;
996 Ok(true)
997 } else {
998 self.pending_requested_size.set(size.into());
999 Ok(false)
1001 }
1002 }
1003 WinitWindowOrNone::None(attributes) => {
1004 attributes.borrow_mut().inner_size = Some(size);
1005 if let winit::dpi::Size::Physical(physical) = size {
1006 self.physical_size_before_scale_factor.set(Some(physical));
1007 }
1008 let scale_factor = self.window().scale_factor() as _;
1011 self.resize_event(size.to_physical(scale_factor))?;
1012 Ok(true)
1013 }
1014 }
1015 }
1016
1017 pub fn resize_event(&self, size: winit::dpi::PhysicalSize<u32>) -> Result<(), PlatformError> {
1018 self.pending_resize_event_after_show.set(false);
1019 if self.physical_size_before_scale_factor.get().is_some_and(|requested| requested != size) {
1020 self.physical_size_before_scale_factor.set(None);
1021 }
1022 if size.width > 0 && size.height > 0 {
1026 let physical_size = physical_size_to_slint(&size);
1027 self.size.set(physical_size);
1028 self.pending_requested_size.set(None);
1029 let scale_factor = WindowInner::from_pub(self.window()).scale_factor();
1030
1031 let size = physical_size.to_logical(scale_factor);
1032 self.window().dispatch_event_with_result(WindowEvent::Resized { size })?;
1033
1034 WindowInner::from_pub(self.window())
1035 .set_window_item_safe_area(self.safe_area_inset().to_logical(scale_factor));
1036
1037 #[cfg(target_arch = "wasm32")]
1041 if let Some(html_canvas) = self
1042 .winit_window_or_none
1043 .borrow()
1044 .as_window()
1045 .and_then(|winit_window| winit_window.canvas())
1046 {
1047 html_canvas.set_width(physical_size.width);
1048 html_canvas.set_height(physical_size.height);
1049 }
1050 }
1051 Ok(())
1052 }
1053
1054 pub fn set_accent_color(&self, color: Color) {
1055 WindowInner::from_pub(self.window()).context().set_accent_color(color);
1056 }
1057
1058 fn query_system_accent_color() -> Color {
1059 cfg_if::cfg_if! {
1060 if #[cfg(target_os = "windows")] {
1061 use windows::Win32::Graphics::{
1062 Dwm::DwmGetColorizationColor,
1063 Gdi::{GetSysColor, COLOR_HIGHLIGHT},
1064 };
1065
1066 let mut argb = 0u32;
1067 let mut _opaque_blend = windows::core::BOOL::default();
1068 if unsafe { DwmGetColorizationColor(&mut argb, &mut _opaque_blend) }.is_ok() {
1069 let a = ((argb >> 24) & 0xFF) as u8;
1070 let r = ((argb >> 16) & 0xFF) as u8;
1071 let g = ((argb >> 8) & 0xFF) as u8;
1072 let b = (argb & 0xFF) as u8;
1073 return Color::from_argb_u8(a, r, g, b);
1074 }
1075
1076 let colorref = unsafe { GetSysColor(COLOR_HIGHLIGHT) };
1077 let r = (colorref & 0xFF) as u8;
1078 let g = ((colorref >> 8) & 0xFF) as u8;
1079 let b = ((colorref >> 16) & 0xFF) as u8;
1080 Color::from_argb_u8(255, r, g, b)
1081 } else if #[cfg(target_os = "macos")] {
1082 use objc2::ClassType;
1083 use objc2_app_kit::{NSColor, NSColorType};
1084 if !NSColor::class().responds_to(objc2::sel!(controlAccentColor)) {
1088 return Color::default();
1089 }
1090 let color = NSColor::controlAccentColor();
1091 color.colorUsingType(NSColorType::ComponentBased).map(|c| {
1092 let r = c.redComponent() as f32;
1093 let g = c.greenComponent() as f32;
1094 let b = c.blueComponent() as f32;
1095 let a = c.alphaComponent() as f32;
1096 Color::from_argb_f32(a, r, g, b)
1097 }).unwrap_or_default()
1098 } else if #[cfg(target_arch = "wasm32")] {
1099 query_wasm_accent_color()
1100 } else {
1101 Color::default()
1103 }
1104 }
1105 }
1106
1107 pub fn update_accent_color(&self) {
1109 let color = Self::query_system_accent_color();
1110 if color != Color::default() {
1111 self.set_accent_color(color);
1112 }
1113 }
1114
1115 pub fn set_color_scheme(&self, scheme: ColorScheme) {
1116 WindowInner::from_pub(self.window()).context().set_color_scheme(scheme);
1117
1118 #[cfg(all(target_os = "windows", muda))]
1120 if let WinitWindowOrNone::HasWindow {
1121 window: winit_window,
1122 muda_adapter: maybe_muda_adapter,
1123 ..
1124 } = &*self.winit_window_or_none.borrow()
1125 {
1126 if let Some(muda_adapter) = maybe_muda_adapter.borrow().as_ref() {
1127 muda_adapter.set_menubar_theme(&winit_window, scheme);
1128 };
1129 }
1130
1131 #[cfg(xdg_desktop_settings)]
1133 if let Some(winit_window) = self.winit_window() {
1134 winit_window.set_theme(match scheme {
1135 ColorScheme::Unknown => None,
1136 ColorScheme::Dark => Some(winit::window::Theme::Dark),
1137 ColorScheme::Light => Some(winit::window::Theme::Light),
1138 _ => None,
1139 });
1140 }
1141 }
1142
1143 #[cfg(target_os = "ios")]
1144 pub fn set_platform_default_font_size(&self, size: i_slint_core::lengths::LogicalLength) {
1145 WindowInner::from_pub(self.window()).context().set_platform_default_font_size(Some(size));
1146 }
1147
1148 pub fn window_state_event(&self) {
1149 let Some(winit_window) = self.winit_window_or_none.borrow().as_window() else { return };
1150
1151 if let Some(minimized) = winit_window.is_minimized() {
1152 self.minimized.set(minimized);
1153 if minimized != self.window().is_minimized() {
1154 self.window().set_minimized(minimized);
1155 }
1156 }
1157
1158 let maximized = winit_window.is_maximized();
1164 if !self.window().is_minimized() {
1165 self.maximized.set(maximized);
1166 if maximized != self.window().is_maximized() {
1167 self.window().set_maximized(maximized);
1168 }
1169 }
1170
1171 let fullscreen = winit_window.fullscreen().is_some();
1175 if fullscreen != self.window().is_fullscreen() {
1176 self.window().set_fullscreen(fullscreen);
1177 }
1178 }
1179
1180 #[cfg(enable_accesskit)]
1181 pub(crate) fn accesskit_adapter(
1182 &self,
1183 ) -> Option<std::cell::Ref<'_, RefCell<crate::accesskit::AccessKitAdapter>>> {
1184 std::cell::Ref::filter_map(
1185 self.winit_window_or_none.try_borrow().ok()?,
1186 |wor: &WinitWindowOrNone| match wor {
1187 WinitWindowOrNone::HasWindow { accesskit_adapter, .. } => Some(accesskit_adapter),
1188 WinitWindowOrNone::None(..) => None,
1189 },
1190 )
1191 .ok()
1192 }
1193
1194 #[cfg(enable_accesskit)]
1195 pub(crate) fn with_access_kit_adapter_from_weak_window_adapter(
1196 self_weak: Weak<Self>,
1197 callback: impl FnOnce(&RefCell<crate::accesskit::AccessKitAdapter>),
1198 ) {
1199 let Some(self_) = self_weak.upgrade() else { return };
1200 let winit_window_or_none = self_.winit_window_or_none.borrow();
1201 match &*winit_window_or_none {
1202 WinitWindowOrNone::HasWindow { accesskit_adapter, .. } => callback(accesskit_adapter),
1203 WinitWindowOrNone::None(..) => {}
1204 }
1205 }
1206
1207 #[cfg(target_os = "macos")]
1210 fn setup_macos_color_observer(&self) {
1211 let self_weak = self.self_weak.clone();
1212 let block =
1213 block2::RcBlock::new(move |_: core::ptr::NonNull<objc2_foundation::NSNotification>| {
1214 if let Some(adapter) = self_weak.upgrade() {
1215 adapter.update_accent_color();
1216 }
1217 });
1218 let observer = unsafe {
1219 objc2_foundation::NSNotificationCenter::defaultCenter()
1220 .addObserverForName_object_queue_usingBlock(
1221 Some(objc2_app_kit::NSSystemColorsDidChangeNotification),
1222 None,
1223 None,
1224 &block,
1225 )
1226 };
1227 let _ = self.macos_color_observer.set(observer);
1228 }
1229
1230 pub fn activation_changed(&self, is_active: bool) -> Result<(), PlatformError> {
1231 let have_focus = is_active || self.input_method_focused();
1232 let slint_window = self.window();
1233 let runtime_window = WindowInner::from_pub(slint_window);
1234 if have_focus != runtime_window.active() {
1237 slint_window.dispatch_event_with_result(
1238 corelib::platform::WindowEvent::WindowActiveChanged(have_focus),
1239 )?;
1240 }
1241
1242 #[cfg(all(muda, target_os = "macos"))]
1243 {
1244 if let WinitWindowOrNone::HasWindow { muda_adapter, .. } =
1245 &*self.winit_window_or_none.borrow()
1246 {
1247 if muda_adapter.borrow().is_none()
1248 && self.muda_enable_default_menu_bar
1249 && self.menubar().is_none()
1250 {
1251 *muda_adapter.borrow_mut() =
1252 Some(crate::muda::MudaAdapter::setup_default_menu_bar()?);
1253 }
1254
1255 if let Some(muda_adapter) = muda_adapter.borrow().as_ref() {
1256 muda_adapter.window_activation_changed(is_active);
1257 }
1258 }
1259 }
1260
1261 Ok(())
1262 }
1263
1264 fn dispatch_internal_event(&self, event: impl Into<corelib::platform::InternalEvent>) {
1265 self.window().dispatch_event(WindowEvent::internal(event));
1266 }
1267
1268 pub(crate) fn dispatch_winit_window_event(
1272 &self,
1273 event_loop: &ActiveEventLoop,
1274 winit_window: &winit::window::Window,
1275 event: WinitWindowEvent,
1276 ) -> Result<(), PlatformError> {
1277 if let Some(mut window_event_filter) = self.window_event_filter.take() {
1278 let event_result = window_event_filter(self.window(), &event);
1279 self.window_event_filter.set(Some(window_event_filter));
1280
1281 match event_result {
1282 EventResult::PreventDefault => return Ok(()),
1283 EventResult::Propagate => (),
1284 }
1285 }
1286
1287 #[cfg(enable_accesskit)]
1288 self.accesskit_adapter()
1289 .expect("internal error: accesskit adapter must exist when window exists")
1290 .borrow_mut()
1291 .process_event(winit_window, &event);
1292
1293 let runtime_window = WindowInner::from_pub(self.window());
1294 self.maybe_set_custom_cursor(event_loop, winit_window);
1295 if !matches!(
1296 event,
1297 WinitWindowEvent::CursorMoved { .. } | WinitWindowEvent::AxisMotion { .. }
1298 ) {
1299 self.shared_backend_data.flush_pending_mouse_move();
1300 }
1301
1302 match event {
1303 WinitWindowEvent::RedrawRequested => self.draw()?,
1304 WinitWindowEvent::Resized(size) => {
1305 let resized = self.resize_event(size);
1306
1307 self.window_state_event();
1313
1314 #[cfg(target_os = "windows")]
1317 {
1318 if size.width == 0 || size.height == 0 {
1319 self.renderer.occluded(true);
1320 }
1321 }
1322
1323 resized?;
1324 }
1325 WinitWindowEvent::CloseRequested => {
1326 self.window()
1327 .dispatch_event_with_result(corelib::platform::WindowEvent::CloseRequested)?;
1328 }
1329 WinitWindowEvent::Focused(have_focus) => {
1330 let have_focus =
1332 if cfg!(target_os = "macos") { winit_window.has_focus() } else { have_focus };
1333 self.activation_changed(have_focus)?;
1334 }
1335
1336 WinitWindowEvent::KeyboardInput { event, is_synthetic, .. } => {
1337 let key_code = event.logical_key.clone();
1338 let swap_cmd_ctrl = i_slint_core::is_apple_platform();
1340
1341 let key_code = if swap_cmd_ctrl {
1342 #[cfg_attr(slint_nightly_test, allow(non_exhaustive_omitted_patterns))]
1343 match key_code {
1344 winit::keyboard::Key::Named(winit::keyboard::NamedKey::Control) => {
1345 winit::keyboard::Key::Named(winit::keyboard::NamedKey::Super)
1346 }
1347 winit::keyboard::Key::Named(winit::keyboard::NamedKey::Super) => {
1348 winit::keyboard::Key::Named(winit::keyboard::NamedKey::Control)
1349 }
1350 code => code,
1351 }
1352 } else {
1353 key_code
1354 };
1355
1356 fn to_slint_key(event: &winit::event::KeyEvent, key_code: &Key) -> SharedString {
1357 macro_rules! winit_key_to_char {
1358 ($($char:literal # $name:ident # $($shifted:ident)? $(=> $($_muda:ident)? # $($_qt:ident)|* # $($winit:ident $(($pos:ident))?)|* # $($_xkb:ident)|* )? ;)*) => {
1359 #[cfg_attr(slint_nightly_test, allow(non_exhaustive_omitted_patterns))]
1360 match key_code {
1361 $( $( $(
1362 winit::keyboard::Key::Named(winit::keyboard::NamedKey::$winit)
1363 $(if event.location == winit::keyboard::KeyLocation::$pos)?
1364 => $char.into(),
1365 )* )? )*
1366 winit::keyboard::Key::Character(str) => str.as_str().into(),
1367 _ => {
1368 if let Some(text) = &event.text {
1369 text.as_str().into()
1370 } else {
1371 "".into()
1372 }
1373 }
1374 }
1375 }
1376 }
1377 i_slint_common::for_each_keys!(winit_key_to_char)
1378 }
1379 #[allow(unused_mut)]
1380 let mut text = to_slint_key(&event, &key_code);
1381
1382 #[cfg(target_os = "windows")]
1383 let text_without_modifiers = {
1384 use winit::platform::modifier_supplement::KeyEventExtModifierSupplement;
1385
1386 let text_without_modifiers =
1396 to_slint_key(&event, &event.key_without_modifiers());
1397 if text.is_empty()
1399 && !text_without_modifiers.is_empty()
1400 && !matches!(event.logical_key, winit::keyboard::Key::Dead(_))
1401 {
1402 text = text_without_modifiers.clone();
1403 }
1404 text_without_modifiers
1405 };
1406
1407 if text.is_empty() {
1408 return Ok(());
1410 }
1411
1412 if is_synthetic {
1413 use winit::keyboard::{Key::Named, NamedKey as N};
1416 if !matches!(
1417 key_code,
1418 Named(N::Control | N::Shift | N::Super | N::Alt | N::AltGraph),
1419 ) {
1420 return Ok(());
1421 }
1422 }
1423
1424 let event_type = match event.state {
1425 winit::event::ElementState::Pressed => KeyEventType::KeyPressed,
1426 winit::event::ElementState::Released => KeyEventType::KeyReleased,
1427 };
1428 let mut key_event = KeyEvent::default();
1429 key_event.text = text;
1430 key_event.repeat = event.repeat;
1431
1432 let event = InternalKeyEvent {
1433 key_event,
1434 event_type,
1435 #[cfg(target_os = "windows")]
1436 text_without_modifiers,
1437 ..Default::default()
1438 };
1439
1440 self.dispatch_internal_event(event);
1441 }
1442 WinitWindowEvent::Ime(winit::event::Ime::Preedit(string, preedit_selection)) => {
1443 let event = InternalKeyEvent {
1444 event_type: KeyEventType::UpdateComposition,
1445 preedit_text: string.into(),
1446 preedit_selection: preedit_selection.map(|e| e.0 as i32..e.1 as i32),
1447 ..Default::default()
1448 };
1449 self.dispatch_internal_event(event);
1450 }
1451 WinitWindowEvent::Ime(winit::event::Ime::Commit(string)) => {
1452 let mut key_event = KeyEvent::default();
1453 key_event.text = string.into();
1454 let event = InternalKeyEvent {
1455 event_type: KeyEventType::CommitComposition,
1456 key_event,
1457 ..Default::default()
1458 };
1459 self.dispatch_internal_event(event);
1460 }
1461 WinitWindowEvent::CursorMoved { position, .. } => {
1462 self.current_resize_direction.set(handle_cursor_move_for_resize(
1463 winit_window,
1464 position,
1465 self.current_resize_direction.get(),
1466 runtime_window
1467 .window_item()
1468 .map_or(Default::default(), |w| w.as_pin_ref().resize_border_width()),
1469 ));
1470 let position = position.to_logical(runtime_window.scale_factor() as f64);
1471 let cursor_pos = euclid::point2(position.x, position.y);
1472 self.cursor_pos.set(cursor_pos);
1473 self.shared_backend_data.buffer_mouse_move(&self.self_weak, cursor_pos);
1475 }
1476 WinitWindowEvent::CursorLeft { .. } => {
1477 if cfg!(target_arch = "wasm32") || !self.pressed.get() {
1479 self.pressed.set(false);
1480 self.dispatch_internal_event(BackendMouseEvent::Exit);
1481 }
1482 }
1483 WinitWindowEvent::MouseWheel { delta, phase, .. } => {
1484 let (delta_x, delta_y) = match delta {
1485 winit::event::MouseScrollDelta::LineDelta(lx, ly) => (lx * 60., ly * 60.),
1486 winit::event::MouseScrollDelta::PixelDelta(d) => {
1487 let d = d.to_logical(runtime_window.scale_factor() as f64);
1488 (d.x, d.y)
1489 }
1490 };
1491 let phase = winit_touch_phase(phase);
1492 self.dispatch_internal_event(BackendMouseEvent::Wheel {
1493 position: self.cursor_pos.get(),
1494 delta_x,
1495 delta_y,
1496 phase,
1497 });
1498 }
1499 WinitWindowEvent::MouseInput { state, button, .. } => {
1500 let button = match button {
1501 winit::event::MouseButton::Left => PointerEventButton::Left,
1502 winit::event::MouseButton::Right => PointerEventButton::Right,
1503 winit::event::MouseButton::Middle => PointerEventButton::Middle,
1504 winit::event::MouseButton::Back => PointerEventButton::Back,
1505 winit::event::MouseButton::Forward => PointerEventButton::Forward,
1506 winit::event::MouseButton::Other(_) => PointerEventButton::Other,
1507 };
1508 let ev = match state {
1509 winit::event::ElementState::Pressed => {
1510 if button == PointerEventButton::Left
1511 && self.current_resize_direction.get().is_some()
1512 {
1513 handle_resize(winit_window, self.current_resize_direction.get());
1514 return Ok(());
1515 }
1516
1517 self.pressed.set(true);
1518 BackendMouseEvent::Pressed {
1519 position: self.cursor_pos.get(),
1520 button,
1521 click_count: 0,
1522 touch_finger_id: 0,
1523 }
1524 }
1525 winit::event::ElementState::Released => {
1526 self.pressed.set(false);
1527 BackendMouseEvent::Released {
1528 position: self.cursor_pos.get(),
1529 button,
1530 click_count: 0,
1531 touch_finger_id: 0,
1532 }
1533 }
1534 };
1535 self.dispatch_internal_event(ev);
1536 }
1537 WinitWindowEvent::Touch(touch) => {
1538 let location = touch.location.to_logical(runtime_window.scale_factor() as f64);
1539 let position = euclid::point2(location.x, location.y);
1540 let finger_id = match touch.phase {
1541 winit::event::TouchPhase::Started | winit::event::TouchPhase::Moved => {
1542 Some(self.touch_finger_ids.borrow_mut().id_for((touch.device_id, touch.id)))
1543 }
1544 winit::event::TouchPhase::Ended | winit::event::TouchPhase::Cancelled => {
1545 self.touch_finger_ids.borrow_mut().take((touch.device_id, touch.id))
1546 }
1547 };
1548 if let Some(finger_id) = finger_id {
1549 self.dispatch_internal_event(corelib::platform::InternalEvent::Touch {
1550 id: finger_id,
1551 position,
1552 phase: winit_touch_phase(touch.phase),
1553 });
1554 }
1555 }
1556 WinitWindowEvent::ScaleFactorChanged { scale_factor, mut inner_size_writer } => {
1557 if std::env::var("SLINT_SCALE_FACTOR").is_err() {
1558 self.window().dispatch_event_with_result(
1559 corelib::platform::WindowEvent::ScaleFactorChanged {
1560 scale_factor: scale_factor as f32,
1561 },
1562 )?;
1563 if let Some(physical) = self.physical_size_before_scale_factor.take() {
1564 inner_size_writer.request_inner_size(physical).ok();
1565 }
1566 }
1568 }
1569 WinitWindowEvent::ThemeChanged(theme) => {
1570 self.set_color_scheme(match theme {
1571 winit::window::Theme::Dark => ColorScheme::Dark,
1572 winit::window::Theme::Light => ColorScheme::Light,
1573 });
1574 self.update_accent_color();
1575 }
1576 WinitWindowEvent::Occluded(occluded) => {
1577 self.renderer.occluded(occluded);
1578
1579 #[cfg(target_os = "macos")]
1582 if !occluded && self.pending_redraw.get() {
1583 self.draw()?;
1584 }
1585
1586 self.window_state_event();
1588 }
1589 WinitWindowEvent::PinchGesture { delta, phase, .. } => {
1593 self.dispatch_internal_event(BackendMouseEvent::PinchGesture {
1594 position: self.cursor_pos.get(),
1595 delta: delta as f32,
1596 phase: winit_touch_phase(phase),
1597 });
1598 }
1599 WinitWindowEvent::RotationGesture { delta, phase, .. } => {
1600 self.dispatch_internal_event(BackendMouseEvent::RotationGesture {
1603 position: self.cursor_pos.get(),
1604 delta: -delta,
1605 phase: winit_touch_phase(phase),
1606 });
1607 }
1608
1609 WinitWindowEvent::AxisMotion { .. } => {
1610 }
1612 _ => {}
1613 }
1614
1615 Ok(())
1616 }
1617
1618 fn maybe_set_custom_cursor(
1620 &self,
1621 event_loop: &ActiveEventLoop,
1622 winit_window: &winit::window::Window,
1623 ) {
1624 if let Some(source) = self.custom_cursor_source.take() {
1625 winit_window.set_cursor(event_loop.create_custom_cursor(source));
1626 }
1627 }
1628
1629 fn set_visibility(&self, visibility: WindowVisibility) -> Result<(), PlatformError> {
1630 if visibility == self.shown.get() {
1631 return Ok(());
1632 }
1633
1634 self.shown.set(visibility);
1635 self.pending_resize_event_after_show.set(!matches!(visibility, WindowVisibility::Hidden));
1636 self.pending_redraw.set(false);
1637 if matches!(visibility, WindowVisibility::ShownFirstTime | WindowVisibility::Shown) {
1638 let recreating_window = matches!(visibility, WindowVisibility::Shown);
1639
1640 let Some(winit_window) = self.winit_window() else {
1641 self.winit_window_or_none.borrow().set_visible(true);
1644 self.shared_backend_data
1645 .register_inactive_window((self.self_weak.upgrade().unwrap()) as _);
1646 return Ok(());
1647 };
1648
1649 let runtime_window = WindowInner::from_pub(self.window());
1650
1651 let scale_factor = runtime_window.scale_factor() as f64;
1652
1653 #[allow(unused_mut)]
1654 let mut preferred_size = self.preferred_size().unwrap_or_default();
1655
1656 #[cfg(target_arch = "wasm32")]
1657 if let Some(html_canvas) = winit_window.canvas() {
1658 if !is_preferred_sized_canvas(&html_canvas)
1660 && !canvas_has_explicit_size_set(&html_canvas)
1661 {
1662 let existing_canvas_size = winit::dpi::LogicalSize::new(
1663 html_canvas.client_width() as f32,
1664 html_canvas.client_height() as f32,
1665 );
1666 preferred_size.width = existing_canvas_size.width;
1667
1668 preferred_size.height = existing_canvas_size.height;
1669 }
1670 }
1671
1672 if !platform_dictates_window_size()
1673 && winit_window.fullscreen().is_none()
1674 && !self.has_explicit_size.get()
1675 && preferred_size.width > 0 as Coord
1676 && preferred_size.height > 0 as Coord
1677 && !recreating_window
1679 {
1680 let size = preferred_size.to_physical::<u32>(scale_factor);
1682 self.resize_window(size.into())?;
1683 };
1684
1685 if !self.first_frame_presented.get() && !self.shared_backend_data.is_wayland {
1689 let _ = self.draw();
1690 #[cfg(target_os = "macos")]
1691 if !self.first_frame_presented.get() {
1692 *self.reveal_on_first_frame.borrow_mut() =
1693 crate::macos::RevealOnFirstFrame::new(&winit_window);
1694 }
1695 }
1696
1697 winit_window.set_visible(true);
1698
1699 if let Some(theme) = winit_window.theme() {
1702 self.set_color_scheme(match theme {
1703 winit::window::Theme::Dark => ColorScheme::Dark,
1704 winit::window::Theme::Light => ColorScheme::Light,
1705 });
1706 }
1707
1708 #[cfg(target_arch = "wasm32")]
1712 if self.pending_redraw.get() {
1713 self.draw()?;
1714 };
1715
1716 #[cfg(ios_and_friends)]
1720 self.request_redraw();
1721
1722 Ok(())
1723 } else {
1724 #[cfg(muda)]
1726 self.context_menu.take();
1727
1728 if self.winit_window_or_none.borrow().as_window().is_some_and(|winit_window| {
1730 use raw_window_handle::HasWindowHandle;
1731 winit_window.window_handle().is_ok_and(|h| {
1732 matches!(h.as_raw(), raw_window_handle::RawWindowHandle::Wayland(..))
1733 }) || std::env::var_os("SLINT_DESTROY_WINDOW_ON_HIDE").is_some()
1734 }) {
1735 self.suspend()?;
1736 } else {
1740 self.winit_window_or_none.borrow().set_visible(false);
1741 }
1742
1743 #[cfg(target_os = "macos")]
1750 self.reveal_on_first_frame.take();
1751
1752 Ok(())
1753 }
1754 }
1755
1756 pub(crate) fn visibility(&self) -> WindowVisibility {
1757 self.shown.get()
1758 }
1759
1760 pub(crate) fn pending_redraw(&self) -> bool {
1761 self.pending_redraw.get()
1762 }
1763
1764 pub async fn async_winit_window(
1765 self_weak: Weak<Self>,
1766 ) -> Result<Arc<winit::window::Window>, PlatformError> {
1767 std::future::poll_fn(move |context| {
1768 let Some(self_) = self_weak.upgrade() else {
1769 return std::task::Poll::Ready(Err(
1770 "Unable to obtain winit window from destroyed window".to_string().into(),
1771 ));
1772 };
1773 match self_.winit_window() {
1774 Some(window) => std::task::Poll::Ready(Ok(window)),
1775 None => {
1776 let waker = context.waker();
1777 if !self_.window_existence_wakers.borrow().iter().any(|w| w.will_wake(waker)) {
1778 self_.window_existence_wakers.borrow_mut().push(waker.clone());
1779 }
1780 std::task::Poll::Pending
1781 }
1782 }
1783 })
1784 .await
1785 }
1786}
1787
1788impl WindowAdapter for WinitWindowAdapter {
1789 fn window(&self) -> &corelib::api::Window {
1790 &self.window
1791 }
1792
1793 fn renderer(&self) -> &dyn i_slint_core::renderer::Renderer {
1794 self.renderer().as_core_renderer()
1795 }
1796
1797 fn set_visible(&self, visible: bool) -> Result<(), PlatformError> {
1798 self.set_visibility(if visible {
1799 WindowVisibility::Shown
1800 } else {
1801 WindowVisibility::Hidden
1802 })
1803 }
1804
1805 fn position(&self) -> Option<corelib::api::PhysicalPosition> {
1806 match &*self.winit_window_or_none.borrow() {
1807 WinitWindowOrNone::HasWindow { window, .. } => match window.outer_position() {
1808 Ok(outer_position) => {
1809 Some(corelib::api::PhysicalPosition::new(outer_position.x, outer_position.y))
1810 }
1811 Err(_) => None,
1812 },
1813 WinitWindowOrNone::None(attributes) => {
1814 attributes.borrow().position.map(|pos| {
1815 match pos {
1816 winit::dpi::Position::Physical(phys_pos) => {
1817 corelib::api::PhysicalPosition::new(phys_pos.x, phys_pos.y)
1818 }
1819 winit::dpi::Position::Logical(logical_pos) => {
1820 corelib::api::LogicalPosition::new(
1822 logical_pos.x as _,
1823 logical_pos.y as _,
1824 )
1825 .to_physical(self.window().scale_factor())
1826 }
1827 }
1828 })
1829 }
1830 }
1831 }
1832
1833 fn set_position(&self, position: corelib::api::WindowPosition) {
1834 let winit_pos = position_to_winit(&position);
1835 match &*self.winit_window_or_none.borrow() {
1836 WinitWindowOrNone::HasWindow { window, .. } => window.set_outer_position(winit_pos),
1837 WinitWindowOrNone::None(attributes) => {
1838 attributes.borrow_mut().position = Some(winit_pos);
1839 }
1840 }
1841 }
1842
1843 fn set_size(&self, size: corelib::api::WindowSize) {
1844 self.has_explicit_size.set(true);
1845 self.resize_window(window_size_to_winit(&size)).ok();
1847 }
1848
1849 fn size(&self) -> corelib::api::PhysicalSize {
1850 self.size.get()
1851 }
1852
1853 fn request_redraw(&self) {
1854 if !self.pending_redraw.replace(true)
1855 && let WinitWindowOrNone::HasWindow { window, frame_throttle, .. } =
1856 &*self.winit_window_or_none.borrow()
1857 {
1858 frame_throttle.request_throttled_redraw(window);
1859 }
1860 }
1861
1862 #[allow(clippy::unnecessary_cast)] fn update_window_properties(&self, properties: corelib::window::WindowProperties<'_>) {
1864 let Some(window_item) = WindowInner::from_pub(&self.window).window_item() else {
1865 return;
1866 };
1867 let window_item = window_item.as_pin_ref();
1868
1869 let winit_window_or_none = self.winit_window_or_none.borrow();
1870
1871 let sf = self.window().scale_factor();
1873
1874 let icon_image = window_item.icon();
1876 let icon_image_cache_key = ImageCacheKey::new((&icon_image).into());
1877 if *self.window_icon_cache_key.borrow() != icon_image_cache_key {
1878 *self.window_icon_cache_key.borrow_mut() = icon_image_cache_key;
1879 winit_window_or_none.set_window_icon(icon_to_winit(
1880 icon_image,
1881 i_slint_core::lengths::LogicalSize::new(64., 64.) * ScaleFactor::new(sf),
1882 ));
1883 }
1884 winit_window_or_none.set_title(&properties.title());
1885 winit_window_or_none.set_decorations(
1886 !window_item.no_frame() || winit_window_or_none.fullscreen().is_some(),
1887 );
1888
1889 #[cfg(target_os = "macos")]
1892 if let WinitWindowOrNone::HasWindow { window, .. } = &*winit_window_or_none {
1893 let transparent = Self::wants_transparent(window_item);
1894 if self.transparent.replace(transparent) != transparent {
1895 window.set_transparent(transparent);
1896 if let Err(err) = self.renderer.set_transparent(transparent) {
1897 i_slint_core::debug_log!("Error adjusting the surface transparency: {err}");
1898 }
1899 }
1900 }
1901
1902 let new_window_level = if window_item.always_on_top() {
1903 winit::window::WindowLevel::AlwaysOnTop
1904 } else {
1905 winit::window::WindowLevel::Normal
1906 };
1907 if self.window_level.replace(new_window_level) != new_window_level {
1910 winit_window_or_none.set_window_level(new_window_level);
1911 }
1912
1913 let mut width = window_item.width().get() as f32;
1914 let mut height = window_item.height().get() as f32;
1915 let mut must_resize = false;
1916 let existing_size = self.size.get().to_logical(sf);
1917
1918 if width <= 0. || height <= 0. {
1919 must_resize = true;
1920 if width <= 0. {
1921 width = existing_size.width;
1922 }
1923 if height <= 0. {
1924 height = existing_size.height;
1925 }
1926 }
1927
1928 if ((existing_size.width - width).abs() > 1. || (existing_size.height - height).abs() > 1.)
1931 && self.pending_requested_size.get().is_none()
1932 && self.physical_size_before_scale_factor.get().is_none_or(|requested| {
1935 requested.width as f32 != width || requested.height as f32 != height
1936 })
1937 {
1938 if winit_window_or_none.fullscreen().is_none() {
1942 let immediately_resized = self
1944 .resize_window(winit::dpi::LogicalSize::new(width, height).into())
1945 .unwrap_or_default();
1946 if immediately_resized {
1947 must_resize = false;
1949 }
1950 }
1951 }
1952
1953 if must_resize {
1954 self.window()
1955 .dispatch_event_with_result(WindowEvent::Resized {
1956 size: i_slint_core::api::LogicalSize::new(width, height),
1957 })
1958 .unwrap();
1959 WindowInner::from_pub(self.window())
1960 .set_window_item_safe_area(window_item.safe_area_insets());
1961 }
1962
1963 let m = properties.is_fullscreen();
1964 if m != self.fullscreen.get() {
1965 if m {
1966 if winit_window_or_none.fullscreen().is_none() {
1967 winit_window_or_none
1968 .set_fullscreen(Some(winit::window::Fullscreen::Borderless(None)));
1969 }
1970 } else {
1971 winit_window_or_none.set_fullscreen(None);
1972 }
1973 self.fullscreen.set(m);
1974 }
1975
1976 let m = properties.is_maximized();
1977 if m != self.maximized.get() {
1978 self.maximized.set(m);
1979 winit_window_or_none.set_maximized(m);
1980 }
1981
1982 let m = properties.is_minimized();
1983 if m != self.minimized.get() {
1984 self.minimized.set(m);
1985 winit_window_or_none.set_minimized(m);
1986 }
1987
1988 if winit_window_or_none.fullscreen().is_some() {
1993 return;
1994 }
1995
1996 let new_constraints = properties.layout_constraints();
1997 if new_constraints == self.constraints.get() {
1998 return;
1999 }
2000
2001 self.constraints.set(new_constraints);
2002
2003 let resizable = window_is_resizable(new_constraints.min, new_constraints.max);
2004 winit_window_or_none.set_resizable(resizable);
2006 let winit_min_inner =
2009 new_constraints.min.map(logical_size_to_winit).map(filter_out_zero_width_or_height);
2010 winit_window_or_none.set_min_inner_size(winit_min_inner, sf as f64);
2011 let winit_max_inner =
2012 new_constraints.max.map(logical_size_to_winit).map(filter_out_zero_width_or_height);
2013 winit_window_or_none.set_max_inner_size(winit_max_inner, sf as f64);
2014
2015 if !platform_dictates_window_size() {
2016 adjust_window_size_to_satisfy_constraints(self, winit_min_inner, winit_max_inner);
2017 }
2018
2019 #[cfg(target_arch = "wasm32")]
2021 if let Some(canvas) =
2022 winit_window_or_none.as_window().and_then(|winit_window| winit_window.canvas())
2023 {
2024 if is_preferred_sized_canvas(&canvas) {
2025 let pref = new_constraints.preferred;
2026 if pref.width > 0 as Coord || pref.height > 0 as Coord {
2027 self.resize_window(logical_size_to_winit(pref).into()).ok();
2029 };
2030 }
2031 }
2032 }
2033
2034 fn internal(&self, _: corelib::InternalToken) -> Option<&dyn WindowAdapterInternal> {
2035 Some(self)
2036 }
2037}
2038
2039impl WindowAdapterInternal for WinitWindowAdapter {
2040 fn start_window_move(&self) {
2041 if let Some(winit_window) = self.winit_window_or_none.borrow().as_window() {
2042 let _ = winit_window.drag_window();
2043 }
2044 }
2045
2046 fn set_mouse_cursor(&self, cursor: MouseCursorInner) {
2047 let winit_cursor = match &cursor {
2048 MouseCursorInner::BuiltIn(cursor) => Some(match cursor {
2049 BuiltInMouseCursor::Default => winit::window::CursorIcon::Default,
2050 BuiltInMouseCursor::None => winit::window::CursorIcon::Default,
2051 BuiltInMouseCursor::Help => winit::window::CursorIcon::Help,
2052 BuiltInMouseCursor::Pointer => winit::window::CursorIcon::Pointer,
2053 BuiltInMouseCursor::Progress => winit::window::CursorIcon::Progress,
2054 BuiltInMouseCursor::Wait => winit::window::CursorIcon::Wait,
2055 BuiltInMouseCursor::Crosshair => winit::window::CursorIcon::Crosshair,
2056 BuiltInMouseCursor::Text => winit::window::CursorIcon::Text,
2057 BuiltInMouseCursor::Alias => winit::window::CursorIcon::Alias,
2058 BuiltInMouseCursor::Copy => winit::window::CursorIcon::Copy,
2059 BuiltInMouseCursor::Move => winit::window::CursorIcon::Move,
2060 BuiltInMouseCursor::NoDrop => winit::window::CursorIcon::NoDrop,
2061 BuiltInMouseCursor::NotAllowed => winit::window::CursorIcon::NotAllowed,
2062 BuiltInMouseCursor::Grab => winit::window::CursorIcon::Grab,
2063 BuiltInMouseCursor::Grabbing => winit::window::CursorIcon::Grabbing,
2064 BuiltInMouseCursor::ColResize => winit::window::CursorIcon::ColResize,
2065 BuiltInMouseCursor::RowResize => winit::window::CursorIcon::RowResize,
2066 BuiltInMouseCursor::NResize => winit::window::CursorIcon::NResize,
2067 BuiltInMouseCursor::EResize => winit::window::CursorIcon::EResize,
2068 BuiltInMouseCursor::SResize => winit::window::CursorIcon::SResize,
2069 BuiltInMouseCursor::WResize => winit::window::CursorIcon::WResize,
2070 BuiltInMouseCursor::NeResize => winit::window::CursorIcon::NeResize,
2071 BuiltInMouseCursor::NwResize => winit::window::CursorIcon::NwResize,
2072 BuiltInMouseCursor::SeResize => winit::window::CursorIcon::SeResize,
2073 BuiltInMouseCursor::SwResize => winit::window::CursorIcon::SwResize,
2074 BuiltInMouseCursor::EwResize => winit::window::CursorIcon::EwResize,
2075 BuiltInMouseCursor::NsResize => winit::window::CursorIcon::NsResize,
2076 BuiltInMouseCursor::NeswResize => winit::window::CursorIcon::NeswResize,
2077 BuiltInMouseCursor::NwseResize => winit::window::CursorIcon::NwseResize,
2078 _ => winit::window::CursorIcon::Default,
2079 }),
2080 MouseCursorInner::CustomMouseCursor { image, hotspot_x, hotspot_y } => {
2081 let scale = self.window().scale_factor();
2083 let source_size = image.size();
2084 let target_size = IntSize::new(
2085 (source_size.width as f32 * scale) as u32,
2086 (source_size.height as f32 * scale) as u32,
2087 );
2088 if let Some(rgba8) =
2089 i_slint_core::graphics::image_to_rgba8_with_target_size(image, target_size)
2090 {
2091 let (width, height) = (rgba8.width(), rgba8.height());
2092 let source = CustomCursor::from_rgba(
2094 rgba8.as_bytes(),
2095 width as u16,
2096 height as u16,
2097 scaled_hotspot(*hotspot_x, source_size.width, width) as u16,
2098 scaled_hotspot(*hotspot_y, source_size.height, height) as u16,
2099 );
2100
2101 self.custom_cursor_source.set(source.ok());
2103 }
2104 None
2105 }
2106 _ => None,
2107 };
2108 if let Some(winit_window) = self.winit_window_or_none.borrow().as_window() {
2109 winit_window
2110 .set_cursor_visible(cursor != MouseCursorInner::BuiltIn(BuiltInMouseCursor::None));
2111
2112 if let Some(cursor) = winit_cursor {
2113 winit_window.set_cursor(cursor);
2114 }
2115 }
2116 }
2117
2118 fn input_method_request(&self, request: corelib::window::InputMethodRequest) {
2119 #[cfg(not(target_arch = "wasm32"))]
2120 if let Some(winit_window) = self.winit_window_or_none.borrow().as_window() {
2121 let props = match &request {
2122 corelib::window::InputMethodRequest::Enable(props) => {
2123 winit_window.set_ime_allowed(true);
2124 props
2125 }
2126 corelib::window::InputMethodRequest::Disable => {
2127 return winit_window.set_ime_allowed(false);
2128 }
2129 corelib::window::InputMethodRequest::Update(props) => props,
2130 _ => return,
2131 };
2132 winit_window.set_ime_purpose(match props.input_type {
2133 corelib::items::InputType::Password => winit::window::ImePurpose::Password,
2134 corelib::items::InputType::Text
2135 | corelib::items::InputType::Number
2136 | corelib::items::InputType::Decimal
2137 | corelib::items::InputType::Search
2138 | _ => winit::window::ImePurpose::Normal,
2139 });
2140 winit_window.set_ime_cursor_area(
2141 position_to_winit(&props.cursor_rect_origin.into()),
2142 window_size_to_winit(&props.cursor_rect_size.into()),
2143 );
2144 }
2145
2146 #[cfg(target_arch = "wasm32")]
2147 match request {
2148 corelib::window::InputMethodRequest::Enable(..) => {
2149 let mut vkh = self.virtual_keyboard_helper.borrow_mut();
2150 let Some(canvas) =
2151 self.winit_window().and_then(|winit_window| winit_window.canvas())
2152 else {
2153 return;
2154 };
2155 let h = vkh.get_or_insert_with(|| {
2156 super::wasm_input_helper::WasmInputHelper::new(self.self_weak.clone(), canvas)
2157 });
2158 h.show();
2159 }
2160 corelib::window::InputMethodRequest::Disable => {
2161 if let Some(h) = &*self.virtual_keyboard_helper.borrow() {
2162 h.hide()
2163 }
2164 }
2165 _ => {}
2166 };
2167 }
2168
2169 #[cfg(muda)]
2170 fn supports_native_menu_bar(&self) -> bool {
2171 !crate::muda::is_disabled()
2172 }
2173
2174 #[cfg(muda)]
2175 fn setup_menubar(&self, menubar: vtable::VRc<MenuVTable>) {
2176 self.menubar_weak.replace(Some(vtable::VRc::downgrade(&menubar)));
2177
2178 if let WinitWindowOrNone::HasWindow { muda_adapter, .. } =
2179 &*self.winit_window_or_none.borrow()
2180 {
2181 drop(muda_adapter.borrow_mut().take());
2183 muda_adapter.replace(Some(crate::muda::MudaAdapter::setup(
2184 &menubar,
2185 &self.winit_window().unwrap(),
2186 self.event_loop_proxy.clone(),
2187 self.self_weak.clone(),
2188 )));
2189 }
2190 }
2191
2192 #[cfg(muda)]
2193 fn show_native_popup_menu(
2194 &self,
2195 context_menu_item: vtable::VRc<MenuVTable>,
2196 position: LogicalPosition,
2197 ) -> bool {
2198 if crate::muda::is_disabled() {
2199 return false;
2200 }
2201
2202 self.context_menu.replace(Some(context_menu_item));
2204
2205 if let WinitWindowOrNone::HasWindow { context_menu_muda_adapter, .. } =
2206 &*self.winit_window_or_none.borrow()
2207 {
2208 drop(context_menu_muda_adapter.borrow_mut().take());
2210 if let Some(new_adapter) = crate::muda::MudaAdapter::show_context_menu(
2211 self.context_menu.borrow().as_ref().unwrap(),
2212 &self.winit_window().unwrap(),
2213 position,
2214 self.event_loop_proxy.clone(),
2215 ) {
2216 context_menu_muda_adapter.replace(Some(new_adapter));
2217 return true;
2218 }
2219 }
2220
2221 self.context_menu.take();
2223 false
2224 }
2225
2226 #[cfg(enable_accesskit)]
2227 fn handle_focus_change(&self, _old: Option<ItemRc>, _new: Option<ItemRc>) {
2228 let Some(accesskit_adapter_cell) = self.accesskit_adapter() else { return };
2229 if let Ok(mut a) = accesskit_adapter_cell.try_borrow_mut() {
2230 a.handle_focus_item_change();
2231 }
2232 }
2233
2234 #[cfg(enable_accesskit)]
2235 fn register_item_tree(&self, _: ItemTreeRefPin) {
2236 let Some(accesskit_adapter_cell) = self.accesskit_adapter() else { return };
2237 if let Ok(mut a) = accesskit_adapter_cell.try_borrow_mut() {
2239 a.reload_tree();
2240 };
2241 }
2242
2243 #[cfg(enable_accesskit)]
2244 fn unregister_item_tree(
2245 &self,
2246 component: ItemTreeRef,
2247 _: &mut dyn Iterator<Item = core::pin::Pin<ItemRef<'_>>>,
2248 ) {
2249 let Some(accesskit_adapter_cell) = self.accesskit_adapter() else { return };
2250 if let Ok(mut a) = accesskit_adapter_cell.try_borrow_mut() {
2251 a.unregister_item_tree(component);
2252 };
2253 }
2254
2255 #[cfg(feature = "raw-window-handle-06")]
2256 fn window_handle_06_rc(
2257 &self,
2258 ) -> Result<Arc<dyn raw_window_handle::HasWindowHandle>, raw_window_handle::HandleError> {
2259 Ok(self
2260 .winit_window_or_none
2261 .borrow()
2262 .as_window()
2263 .ok_or(raw_window_handle::HandleError::Unavailable)?)
2264 }
2265
2266 #[cfg(feature = "raw-window-handle-06")]
2267 fn display_handle_06_rc(
2268 &self,
2269 ) -> Result<Arc<dyn raw_window_handle::HasDisplayHandle>, raw_window_handle::HandleError> {
2270 Ok(self
2271 .winit_window_or_none
2272 .borrow()
2273 .as_window()
2274 .ok_or(raw_window_handle::HandleError::Unavailable)?)
2275 }
2276
2277 fn bring_to_front(&self) -> Result<(), PlatformError> {
2278 if let Some(winit_window) = self.winit_window_or_none.borrow().as_window() {
2279 winit_window.set_minimized(false);
2280 winit_window.focus_window();
2281 }
2282 Ok(())
2283 }
2284
2285 #[cfg(target_os = "ios")]
2286 fn safe_area_inset(&self) -> i_slint_core::lengths::PhysicalEdges {
2287 self.winit_window_or_none
2288 .borrow()
2289 .as_window()
2290 .and_then(|window| {
2291 let outer_position = window.outer_position().ok()?;
2292 let inner_position = window.inner_position().ok()?;
2293 let outer_size = window.outer_size();
2294 let inner_size = window.inner_size();
2295 Some(i_slint_core::lengths::PhysicalEdges::new(
2296 inner_position.y - outer_position.y,
2297 outer_size.height as i32
2298 - (inner_size.height as i32)
2299 - (inner_position.y - outer_position.y),
2300 inner_position.x - outer_position.x,
2301 outer_size.width as i32
2302 - (inner_size.width as i32)
2303 - (inner_position.x - outer_position.x),
2304 ))
2305 })
2306 .unwrap_or_default()
2307 }
2308}
2309
2310impl Drop for WinitWindowAdapter {
2311 fn drop(&mut self) {
2312 self.shared_backend_data.unregister_window(
2313 self.winit_window_or_none.borrow().as_window().map(|winit_window| winit_window.id()),
2314 );
2315
2316 #[cfg(target_os = "macos")]
2317 if let Some(observer) = self.macos_color_observer.get() {
2318 unsafe {
2319 objc2_foundation::NSNotificationCenter::defaultCenter()
2320 .removeObserver((*observer).as_ref());
2321 }
2322 }
2323 }
2324}
2325
2326fn adjust_window_size_to_satisfy_constraints(
2328 adapter: &WinitWindowAdapter,
2329 min_size: Option<winit::dpi::LogicalSize<f64>>,
2330 max_size: Option<winit::dpi::LogicalSize<f64>>,
2331) {
2332 let sf = adapter.window().scale_factor() as f64;
2333 let current_size = adapter
2334 .pending_requested_size
2335 .get()
2336 .unwrap_or_else(|| {
2337 let existing_adapter_size = adapter.size.get();
2338 physical_size_to_winit(existing_adapter_size).into()
2339 })
2340 .to_logical::<f64>(sf);
2341
2342 let mut window_size = current_size;
2343 if let Some(max_size) = max_size {
2344 let max_size = max_size.cast();
2345 window_size.width = window_size.width.min(max_size.width);
2346 window_size.height = window_size.height.min(max_size.height);
2347 }
2348
2349 if let Some(min_size) = min_size {
2355 let min_size = min_size.cast::<f64>();
2356 if (window_size.width * sf).round() < min_size.width * sf {
2357 window_size.width = round_up_logical(min_size.width, sf);
2358 }
2359 if (window_size.height * sf).round() < min_size.height * sf {
2360 window_size.height = round_up_logical(min_size.height, sf);
2361 }
2362 }
2363
2364 if window_size != current_size {
2365 adapter.resize_window(window_size.into()).ok();
2367 }
2368}
2369
2370#[cfg(target_arch = "wasm32")]
2371fn query_wasm_accent_color() -> Color {
2372 (|| {
2373 use wasm_bindgen::JsCast;
2374 let window = web_sys::window()?;
2375 let document = window.document()?;
2376 let element = document.create_element("span").ok()?;
2377 let html_element: &web_sys::HtmlElement = element.dyn_ref()?;
2378 html_element.style().set_property("color", "AccentColor").ok()?;
2379 if html_element.style().get_property_value("color").ok()?.is_empty() {
2381 return None;
2382 }
2383 html_element.style().set_property("display", "none").ok()?;
2384 document.body()?.append_child(&element).ok()?;
2385 let color_str =
2386 window.get_computed_style(&element).ok()??.get_property_value("color").ok()?;
2387 element.remove();
2388 let inner = color_str.strip_prefix("rgb(")?.strip_suffix(')')?;
2390 let mut parts = inner.split(',').map(|p| p.trim().parse::<u8>().ok());
2391 Some(Color::from_argb_u8(255, parts.next()??, parts.next()??, parts.next()??))
2392 })()
2393 .unwrap_or_default()
2394}
2395
2396#[cfg(target_family = "wasm")]
2397fn is_preferred_sized_canvas(canvas: &web_sys::HtmlCanvasElement) -> bool {
2398 canvas
2399 .dataset()
2400 .get("slintAutoResizeToPreferred")
2401 .and_then(|val_str| val_str.parse::<bool>().ok())
2402 .unwrap_or_default()
2403}
2404
2405#[cfg(target_family = "wasm")]
2406fn canvas_has_explicit_size_set(canvas: &web_sys::HtmlCanvasElement) -> bool {
2407 let style = canvas.style();
2408 if !style.get_property_value("width").unwrap_or_default().is_empty()
2409 || !style.get_property_value("height").unwrap_or_default().is_empty()
2410 {
2411 return true;
2412 }
2413
2414 let Some(window) = web_sys::window() else {
2415 return false;
2416 };
2417 let Some(computed_style) = window.get_computed_style(&canvas).ok().flatten() else {
2418 return false;
2419 };
2420
2421 computed_style.get_property_value("width").ok().as_deref() != Some("auto")
2422 || computed_style.get_property_value("height").ok().as_deref() != Some("auto")
2423}