Skip to main content

i_slint_core/
window.rs

1// Copyright © SixtyFPS GmbH <info@slint.dev>
2// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0
3
4// cSpell: ignore backtab componentrc datastructure subelements unmaximized unminimized
5
6#![warn(missing_docs)]
7//! Exposed Window API
8use crate::api::{
9    CloseRequestResponse, LogicalPosition, LogicalSize, PhysicalPosition, PhysicalSize,
10    PlatformError, Window, WindowPosition, WindowSize,
11};
12use crate::cursor::MouseCursorInner;
13use crate::input::{
14    BackendDragEvent, ClickState, DragData, FocusEvent, FocusReason, InternalKeyEvent,
15    KeyEventResult, KeyEventType, Keys, MouseEvent, MouseInputState, PointerEventButton,
16    TextCursorBlinker, TouchPhase, TouchState, key_codes,
17};
18use crate::item_tree::{
19    ItemRc, ItemTreeRc, ItemTreeRef, ItemTreeRefPin, ItemTreeVTable, ItemTreeWeak, ItemWeak,
20    ParentItemTraversalMode,
21};
22use crate::items::{
23    BuiltInMouseCursor, InputMethodHints, InputType, ItemRef, MenuEntry, PopupClosePolicy,
24};
25use crate::lengths::{LogicalLength, LogicalPoint, LogicalRect, LogicalVector, SizeLengths};
26use crate::menus::MenuVTable;
27use crate::properties::{ChangeTracker, Property, PropertyTracker};
28use crate::renderer::Renderer;
29use crate::{Callback, Coord, SharedString, SharedVector};
30use alloc::boxed::Box;
31use alloc::rc::{Rc, Weak};
32use alloc::vec::Vec;
33use core::cell::{Cell, RefCell};
34use core::num::NonZeroU32;
35use core::pin::Pin;
36use euclid::num::Zero;
37use vtable::{VRc, VRcMapped};
38
39pub mod popup;
40
41fn next_focus_item(item: ItemRc) -> ItemRc {
42    item.next_focus_item()
43}
44
45fn previous_focus_item(item: ItemRc) -> ItemRc {
46    item.previous_focus_item()
47}
48
49/// The window kind when creating a new child window
50#[repr(C)]
51pub enum WindowKind {
52    /// Tooltip
53    ToolTip,
54    /// Popup Window
55    Popup,
56    /// Popup Menu
57    Menu,
58}
59
60/// This trait represents the adaptation layer between the [`Window`] API and then
61/// windowing specific window representation, such as a Win32 `HWND` handle or a `wayland_surface_t`.
62///
63/// Implement this trait to establish the link between the two, and pass messages in both
64/// directions:
65///
66/// - When receiving messages from the windowing system about state changes, such as the window being resized,
67///   the user requested the window to be closed, input being received, etc. you need to create a
68///   [`WindowEvent`](crate::platform::WindowEvent) and send it to Slint via [`Window::dispatch_event_with_result()`].
69///
70/// - Slint sends requests to change visibility, position, size, etc. via functions such as [`Self::set_visible`],
71///   [`Self::set_size`], [`Self::set_position`], or [`Self::update_window_properties()`]. Re-implement these functions
72///   and delegate the requests to the windowing system.
73///
74/// If the implementation of this bi-directional message passing protocol is incomplete, the user may
75/// experience unexpected behavior, or the intention of the developer calling functions on the [`Window`]
76/// API may not be fulfilled.
77///
78/// Your implementation must hold a renderer, such as `SoftwareRenderer` or `FemtoVGRenderer`.
79/// In the [`Self::renderer()`] function, you must return a reference to it.
80///
81/// It is also required to hold a [`Window`] and return a reference to it in your
82/// implementation of [`Self::window()`].
83///
84/// See also `slint::platform::software_renderer::MinimalSoftwareWindow`
85/// for a minimal implementation of this trait using the software renderer
86pub trait WindowAdapter {
87    /// Returns the window API.
88    fn window(&self) -> &Window;
89
90    /// Show the window if the argument is true, hide otherwise.
91    fn set_visible(&self, _visible: bool) -> Result<(), PlatformError> {
92        Ok(())
93    }
94
95    /// Returns the position of the window on the screen, in physical screen coordinates and including
96    /// a window frame (if present).
97    ///
98    /// The default implementation returns `None`
99    ///
100    /// Called from [`Window::position()`]
101    fn position(&self) -> Option<PhysicalPosition> {
102        None
103    }
104    /// Sets the position of the window on the screen, in physical screen coordinates and including
105    /// a window frame (if present).
106    ///
107    /// The default implementation does nothing
108    ///
109    /// Called from [`Window::set_position()`]
110    fn set_position(&self, _position: WindowPosition) {}
111
112    /// Request a new size for the window to the specified size on the screen, in physical or logical pixels
113    /// and excluding a window frame (if present).
114    ///
115    /// This is called from [`Window::set_size()`]
116    ///
117    /// The default implementation does nothing
118    ///
119    /// This function should sent the size to the Windowing system. If the window size actually changes, you
120    /// should dispatch a [`WindowEvent::Resized`](crate::platform::WindowEvent::Resized) using
121    /// [`Window::dispatch_event()`] to propagate the new size to the slint view
122    fn set_size(&self, _size: WindowSize) {}
123
124    /// Return the size of the Window on the screen
125    fn size(&self) -> PhysicalSize;
126
127    /// Issues a request to the windowing system to re-render the contents of the window.
128    ///
129    /// This request is typically asynchronous.
130    /// It is called when a property that was used during window rendering is marked as dirty.
131    ///
132    /// An implementation should repaint the window in a subsequent iteration of the event loop,
133    /// throttled to the screen refresh rate if possible.
134    /// It is important not to query any Slint properties to avoid introducing a dependency loop in the properties,
135    /// including the use of the render function, which itself queries properties.
136    ///
137    /// See also [`Window::request_redraw()`]
138    fn request_redraw(&self) {}
139
140    /// Return the renderer.
141    ///
142    /// The `Renderer` trait is an internal trait that you are not expected to implement.
143    /// In your implementation you should return a reference to an instance of one of the renderers provided by Slint.
144    fn renderer(&self) -> &dyn Renderer;
145
146    /// Re-implement this function to update the properties such as window title or layout constraints.
147    ///
148    /// This function is called before `set_visible(true)`, and will be called again when the properties
149    /// that were queried on the last call are changed. If you do not query any properties, it may not
150    /// be called again.
151    fn update_window_properties(&self, _properties: WindowProperties<'_>) {}
152
153    #[doc(hidden)]
154    fn internal(&self, _: crate::InternalToken) -> Option<&dyn WindowAdapterInternal> {
155        None
156    }
157
158    /// Re-implement this to support exposing raw window handles (version 0.6).
159    #[cfg(feature = "raw-window-handle-06")]
160    fn window_handle_06(
161        &self,
162    ) -> Result<raw_window_handle_06::WindowHandle<'_>, raw_window_handle_06::HandleError> {
163        Err(raw_window_handle_06::HandleError::NotSupported)
164    }
165
166    /// Re-implement this to support exposing raw display handles (version 0.6).
167    #[cfg(feature = "raw-window-handle-06")]
168    fn display_handle_06(
169        &self,
170    ) -> Result<raw_window_handle_06::DisplayHandle<'_>, raw_window_handle_06::HandleError> {
171        Err(raw_window_handle_06::HandleError::NotSupported)
172    }
173}
174
175/// What a `DragArea` offers to start a native (OS-level) drag, passed to
176/// [`WindowAdapterInternal::start_drag`].
177///
178/// A read-only view: the backend reads the payload, allowed actions, and drag image; the source
179/// item and other routing data stay in the core.
180#[derive(Clone)]
181#[doc(hidden)]
182pub struct DragRequest {
183    pub(crate) data: crate::data_transfer::DataTransfer,
184    pub(crate) allowed: crate::items::AllowedDragActions,
185    pub(crate) drag_image: crate::graphics::Image,
186    pub(crate) drag_image_offset: euclid::default::Vector2D<i32>,
187}
188
189impl DragRequest {
190    /// The data being transferred.
191    pub fn data(&self) -> &crate::data_transfer::DataTransfer {
192        &self.data
193    }
194    /// The set of actions the drag source permits.
195    pub fn allowed_actions(&self) -> crate::items::AllowedDragActions {
196        self.allowed
197    }
198    /// The image to show under the cursor while dragging.
199    pub fn drag_image(&self) -> &crate::graphics::Image {
200        &self.drag_image
201    }
202    /// The offset of the drag image relative to the cursor, in pixels.
203    pub fn drag_image_offset(&self) -> euclid::default::Vector2D<i32> {
204        self.drag_image_offset
205    }
206}
207
208/// A drag a `DragArea` started, tracked by the core while in flight.
209/// The backend sees only the [`DragRequest`]; the source and seed position stay here to report
210/// completion and to arm the in-window fallback.
211#[derive(Clone)]
212pub(crate) struct NativePendingDrag {
213    pub(crate) request: DragRequest,
214    /// The `DragArea` that initiated the drag.
215    pub(crate) source: ItemWeak,
216    /// The pointer position that crossed the drag threshold, used to seed the in-window drag.
217    pub(crate) seed_position: LogicalPosition,
218}
219
220/// Implementation details behind [`WindowAdapter`], but since this
221/// trait is not exported in the public API, it is not possible for the
222/// users to call or re-implement these functions.
223// TODO: add events for window receiving and loosing focus
224#[doc(hidden)]
225pub trait WindowAdapterInternal: core::any::Any {
226    /// This function is called by the generated code when a component and therefore its tree of items are created.
227    fn register_item_tree(&self, _: ItemTreeRefPin) {}
228
229    /// This function is called by the generated code when a component and therefore its tree of items are destroyed. The
230    /// implementation typically uses this to free the underlying graphics resources.
231    fn unregister_item_tree(
232        &self,
233        _component: ItemTreeRef,
234        _items: &mut dyn Iterator<Item = Pin<ItemRef<'_>>>,
235    ) {
236    }
237
238    /// Get the parent window adapter of this window adapter
239    fn get_parent(&self) -> Option<Rc<dyn WindowAdapter>> {
240        None
241    }
242
243    /// Create a window for a popup.
244    /// This function will create only the window adapter but does not show the popup it self
245    /// Use this window adapter to create a new popup window and show it with `show_popup()`
246    ///
247    /// If this function return None (the default implementation), then the
248    /// popup will be rendered within the window itself.
249    fn create_child_window_adapter(
250        &self,
251        _window_kind: WindowKind,
252    ) -> Option<Rc<dyn WindowAdapter>> {
253        None
254    }
255
256    /// Set the mouse cursor
257    // TODO: Make the enum public and make public
258    fn set_mouse_cursor(&self, _cursor: MouseCursorInner) {}
259
260    /// This method allow editable input field to communicate with the platform about input methods
261    fn input_method_request(&self, _: InputMethodRequest) {}
262
263    /// Handle focus change
264    // used for accessibility
265    fn handle_focus_change(&self, _old: Option<ItemRc>, _new: Option<ItemRc>) {}
266
267    /// Returns whether we can have a native menu bar
268    fn supports_native_menu_bar(&self) -> bool {
269        false
270    }
271
272    fn setup_menubar(&self, _menubar: vtable::VRc<MenuVTable>) {}
273
274    fn show_native_popup_menu(
275        &self,
276        _context_menu_item: vtable::VRc<MenuVTable>,
277        _position: LogicalPosition,
278    ) -> bool {
279        false
280    }
281
282    /// Re-implement this to support exposing raw window handles (version 0.6).
283    #[cfg(all(feature = "std", feature = "raw-window-handle-06"))]
284    fn window_handle_06_rc(
285        &self,
286    ) -> Result<
287        std::sync::Arc<dyn raw_window_handle_06::HasWindowHandle>,
288        raw_window_handle_06::HandleError,
289    > {
290        Err(raw_window_handle_06::HandleError::NotSupported)
291    }
292
293    /// Re-implement this to support exposing raw display handles (version 0.6).
294    #[cfg(all(feature = "std", feature = "raw-window-handle-06"))]
295    fn display_handle_06_rc(
296        &self,
297    ) -> Result<
298        std::sync::Arc<dyn raw_window_handle_06::HasDisplayHandle>,
299        raw_window_handle_06::HandleError,
300    > {
301        Err(raw_window_handle_06::HandleError::NotSupported)
302    }
303
304    /// Brings the window to the front and focuses it.
305    fn bring_to_front(&self) -> Result<(), PlatformError> {
306        Ok(())
307    }
308
309    /// Return the inset of the safe area of the Window in physical pixels.
310    /// This is necessary to avoid overlapping system UI such as notches or system bars.
311    fn safe_area_inset(&self) -> crate::lengths::PhysicalEdges {
312        Default::default()
313    }
314
315    /// Start a native (OS-level) drag-and-drop operation.
316    ///
317    /// Returns `true` if the backend took the drag over (it may defer the actual start).
318    /// Returns `false` (the default) when native drag is unsupported; the caller then arms the
319    /// in-window drag.
320    ///
321    /// On completion the backend calls [`WindowInner::report_drag_finished`]
322    /// (`DragAction::None` if cancelled), or [`WindowInner::start_in_window_drag`] if a native
323    /// start fails after returning `true`.
324    fn start_drag(&self, _request: &DragRequest) -> bool {
325        false
326    }
327
328    /// Ask the windowing system to start an interactive, user-driven move of the window,
329    /// as if the user had dragged the window's title bar.
330    ///
331    /// This is called while the user holds a mouse button pressed.
332    /// The default implementation does nothing; backends without support ignore the request.
333    fn start_window_move(&self) {}
334}
335
336/// This is the parameter from [`WindowAdapterInternal::input_method_request()`] which lets the editable text input field
337/// communicate with the platform about input methods.
338#[non_exhaustive]
339#[derive(Debug, Clone)]
340pub enum InputMethodRequest {
341    /// Enables the input method with the specified properties.
342    Enable(InputMethodProperties),
343    /// Updates the input method with new properties.
344    Update(InputMethodProperties),
345    /// Disables the input method.
346    Disable,
347}
348
349/// This struct holds properties related to an input method.
350#[non_exhaustive]
351#[derive(Clone, Default, Debug)]
352pub struct InputMethodProperties {
353    /// The text surrounding the cursor.
354    ///
355    /// This field does not include pre-edit text or composition.
356    pub text: SharedString,
357    /// The position of the cursor in bytes within the `text`.
358    pub cursor_position: usize,
359    /// When there is a selection, this is the position of the second anchor
360    /// for the beginning (or the end) of the selection.
361    pub anchor_position: Option<usize>,
362    /// The current value of the pre-edit text as known by the input method.
363    /// This is the text currently being edited but not yet committed.
364    /// When empty, there is no pre-edit text.
365    pub preedit_text: SharedString,
366    /// When the `preedit_text` is not empty, this is the offset of the pre-edit within the `text`.
367    pub preedit_offset: usize,
368    /// The top-left corner of the cursor rectangle in window coordinates.
369    pub cursor_rect_origin: LogicalPosition,
370    /// The size of the cursor rectangle.
371    pub cursor_rect_size: crate::api::LogicalSize,
372    /// The position of the anchor (bottom). Only meaningful if anchor_position is Some
373    pub anchor_point: LogicalPosition,
374    /// The type of input for the text edit.
375    pub input_type: InputType,
376    /// The hints for the input method for the text edit.
377    pub input_method_hints: InputMethodHints,
378    /// The clip rect in window coordinates
379    pub clip_rect: Option<LogicalRect>,
380}
381
382/// This struct describes layout constraints of a resizable element, such as a window.
383#[non_exhaustive]
384#[derive(Copy, Clone, Debug, PartialEq, Default)]
385pub struct LayoutConstraints {
386    /// The minimum size.
387    pub min: Option<crate::api::LogicalSize>,
388    /// The maximum size.
389    pub max: Option<crate::api::LogicalSize>,
390    /// The preferred size.
391    pub preferred: crate::api::LogicalSize,
392}
393
394/// This struct contains getters that provide access to properties of the `Window`
395/// element, and is used with [`WindowAdapter::update_window_properties`].
396pub struct WindowProperties<'a>(&'a WindowInner);
397
398impl WindowProperties<'_> {
399    /// Returns the Window's title
400    pub fn title(&self) -> SharedString {
401        self.0.window_item().map(|w| w.as_pin_ref().title()).unwrap_or_default()
402    }
403
404    /// The background color or brush of the Window
405    pub fn background(&self) -> crate::Brush {
406        self.0
407            .window_item()
408            .map(|w: VRcMapped<ItemTreeVTable, crate::items::WindowItem>| {
409                w.as_pin_ref().background()
410            })
411            .unwrap_or_default()
412    }
413
414    /// Returns the layout constraints of the window
415    pub fn layout_constraints(&self) -> LayoutConstraints {
416        let component = self.0.component();
417        let component = ItemTreeRc::borrow_pin(&component);
418        let h = component.as_ref().layout_info(crate::layout::Orientation::Horizontal);
419        let v = component.as_ref().layout_info(crate::layout::Orientation::Vertical);
420        let (min, max) = crate::layout::min_max_size_for_layout_constraints(h, v);
421        LayoutConstraints {
422            min,
423            max,
424            preferred: crate::api::LogicalSize::new(
425                h.preferred_bounded() as f32,
426                v.preferred_bounded() as f32,
427            ),
428        }
429    }
430
431    /// Returns true if the window should be shown fullscreen; false otherwise.
432    #[deprecated(note = "Please use `is_fullscreen` instead")]
433    pub fn fullscreen(&self) -> bool {
434        self.is_fullscreen()
435    }
436
437    /// Returns true if the window should be shown fullscreen; false otherwise.
438    pub fn is_fullscreen(&self) -> bool {
439        self.0.is_fullscreen()
440    }
441
442    /// true if the window is in a maximized state, otherwise false
443    pub fn is_maximized(&self) -> bool {
444        self.0.is_maximized()
445    }
446
447    /// true if the window is in a minimized state, otherwise false
448    pub fn is_minimized(&self) -> bool {
449        self.0.is_minimized()
450    }
451}
452
453struct WindowPropertiesTracker {
454    window_adapter_weak: Weak<dyn WindowAdapter>,
455}
456
457impl crate::properties::PropertyDirtyHandler for WindowPropertiesTracker {
458    fn notify(self: Pin<&Self>) {
459        let win = self.window_adapter_weak.clone();
460        let Some(adapter) = win.upgrade() else { return };
461        WindowInner::from_pub(adapter.window()).context().single_shot(
462            Default::default(),
463            move || {
464                if let Some(window_adapter) = win.upgrade() {
465                    WindowInner::from_pub(window_adapter.window()).update_window_properties();
466                };
467            },
468        )
469    }
470}
471
472pub(crate) struct PopupWindowPropertiesTracker {
473    /// Weak reference to the parent window that owns the active_popups list
474    parent_window_adapter_weak: Weak<dyn WindowAdapter>,
475    /// ID of the popup this tracker belongs to, used to re-evaluate after notification
476    popup_id: NonZeroU32,
477}
478
479impl crate::properties::PropertyDirtyHandler for PopupWindowPropertiesTracker {
480    fn notify(self: Pin<&Self>) {
481        let parent = self.parent_window_adapter_weak.clone();
482        let popup_id = self.popup_id;
483        let Some(parent_adapter) = parent.upgrade() else { return };
484        // Use a timer here, so if we change multiple properties at the same time not multiple notifications are send
485        // This timer will delay for the next evaluation
486        WindowInner::from_pub(parent_adapter.window()).context().single_shot(
487            Default::default(),
488            move || {
489                if let Some(parent_adapter) = parent.upgrade() {
490                    WindowInner::from_pub(parent_adapter.window())
491                        .update_popup_properties(popup_id);
492                }
493            },
494        );
495    }
496}
497
498struct WindowRedrawTracker {
499    window_adapter_weak: Weak<dyn WindowAdapter>,
500}
501
502impl crate::properties::PropertyDirtyHandler for WindowRedrawTracker {
503    fn notify(self: Pin<&Self>) {
504        if let Some(window_adapter) = self.window_adapter_weak.upgrade() {
505            window_adapter.request_redraw();
506        };
507    }
508}
509
510/// This enum describes the different ways a popup can be rendered by the back-end.
511pub enum PopupWindowLocation {
512    /// The popup is rendered in its own top-level window that is know to the windowing system.
513    TopLevel(Rc<dyn WindowAdapter>),
514    /// The popup is rendered as an embedded child window at the given position.
515    ChildWindow(LogicalPoint),
516}
517
518/// This structure defines a graphical element that is designed to pop up from the surrounding
519/// UI content, for example to show a context menu.
520pub struct PopupWindow {
521    /// The ID of the associated popup.
522    pub popup_id: NonZeroU32,
523    /// The location defines where the pop up is rendered.
524    pub location: PopupWindowLocation,
525    /// The component that is responsible for providing the popup content.
526    pub component: ItemTreeRc,
527    /// Defines the close behavior of the popup.
528    pub close_policy: PopupClosePolicy,
529    /// the item that had the focus in the parent window when the popup was opened
530    focus_item_in_parent: ItemWeak,
531    /// The item from where the Popup was invoked from
532    pub parent_item: ItemWeak,
533    /// Overlay tooltip: no focus steal, unclamped placement, skipped in main mouse routing.
534    /// Context / popup menu: participates in menu-chain hit testing and cascading close.
535    pub window_kind: WindowKind,
536    /// Callback that returns the current desired logical position of the popup.
537    /// Called during re-evaluation of the position tracker to re-subscribe to dependencies.
538    /// IMPORTANT: This position is relative to the parent
539    position_access: Box<dyn Fn() -> LogicalPosition>,
540    /// Keeps the parent component's `PopupWindow::is-open` property in sync. Provided to
541    /// [`WindowInner::show_popup`], invoked with `true` when the popup is shown and with `false` when
542    /// this `PopupWindow` is dropped (see the `Drop` impl below). It is a no-op for popups whose
543    /// parent does not read `is-open` (menus and tooltips).
544    is_open_setter: Box<dyn Fn(bool)>,
545    // tracks all relevant properties and reacts on changes
546    properties_tracker: Pin<Box<PropertyTracker<true, PopupWindowPropertiesTracker>>>,
547}
548
549impl Drop for PopupWindow {
550    fn drop(&mut self) {
551        // Dropping the `PopupWindow` is the single choke point that every close path funnels through
552        // (click-outside, selection, programmatic `close()`, sibling replacement, window change,
553        // Escape, and tearing down the window itself), so flip the parent's `is-open` back to false
554        // here rather than in any individual close function.
555        (self.is_open_setter)(false);
556    }
557}
558
559#[pin_project::pin_project]
560struct WindowPinnedFields {
561    #[pin]
562    redraw_tracker: PropertyTracker<false, WindowRedrawTracker>,
563    /// Gets dirty when the layout restrictions, or some other property of the windows change
564    #[pin]
565    window_properties_tracker: PropertyTracker<true, WindowPropertiesTracker>,
566    #[pin]
567    scale_factor: Property<f32>,
568    #[pin]
569    active: Property<bool>,
570    #[pin]
571    text_input_focused: Property<bool>,
572    #[pin]
573    menubar_shortcuts: Property<SharedVector<MenuEntry>>,
574}
575
576/// The outcome of dispatching a [`MouseEvent`] through [`WindowInner::process_mouse_input`].
577#[derive(Copy, Clone, Debug)]
578pub(crate) struct MouseDispatchResult {
579    /// For `MouseEvent::DragMove` / `MouseEvent::Drop` events, the action negotiated with
580    /// the accepting `DropArea` (or `None` if no `DropArea` accepted). Always `None` for
581    /// other event kinds.
582    pub drag_action: Option<crate::items::DragAction>,
583    /// `true` if an item consumed the event (`EventAccepted`, `GrabMouse`, `StartDrag`, or
584    /// a `DropArea` accepting a drag/drop). `false` if the event fell through without a taker.
585    pub accepted: bool,
586}
587
588/// The program a path names, without its directory or the Windows `.exe` suffix.
589/// `None` for a path that names no file, or an empty name.
590#[cfg(feature = "std")]
591fn program_name(path: &std::path::Path) -> Option<SharedString> {
592    // A Windows program is called "foo", not "foo.exe"
593    let is_exe = path.extension().is_some_and(|ext| ext.eq_ignore_ascii_case("exe"));
594    let name = if is_exe { path.file_stem()? } else { path.file_name()? };
595    let name = name.to_string_lossy();
596    (!name.is_empty()).then(|| name.as_ref().into())
597}
598
599/// The name of the running program, used as the window title when the application doesn't set one.
600///
601/// It comes from argument zero, which is also what winit derives the X11 `WM_CLASS` from, so the
602/// two agree even when the program is started through a symlink.
603/// Empty where the platform names the program neither way, such as on the web.
604pub fn application_name() -> SharedString {
605    #[cfg(feature = "std")]
606    {
607        static NAME: std::sync::LazyLock<SharedString> = std::sync::LazyLock::new(|| {
608            std::env::args_os()
609                .next()
610                .and_then(|arg| program_name(std::path::Path::new(&arg)))
611                .or_else(|| std::env::current_exe().ok().as_deref().and_then(program_name))
612                .unwrap_or_default()
613        });
614        NAME.clone()
615    }
616    #[cfg(not(feature = "std"))]
617    SharedString::default()
618}
619
620crate::thread_local! {
621    static DEFAULT_WINDOW_TITLE: core::cell::RefCell<Option<SharedString>> = Default::default();
622}
623
624/// Override what [`default_window_title`] returns, for this thread.
625///
626/// A tool that hosts someone else's component, like the Slint viewer, says here what its windows
627/// are called.
628/// A window reads the title when it first applies its properties, so this only reaches windows
629/// that have yet to be shown.
630pub fn set_default_window_title(title: SharedString) {
631    DEFAULT_WINDOW_TITLE.with(|slot| slot.replace(Some(title)));
632}
633
634/// The title a window shows when the application doesn't set one: [`set_default_window_title`]
635/// if a host called it on this thread, otherwise the application name.
636///
637/// This is what the compiler binds `Window.title` to, through
638/// `BuiltinFunction::DefaultWindowTitle`.
639pub fn default_window_title() -> SharedString {
640    DEFAULT_WINDOW_TITLE.with(|slot| slot.borrow().clone()).unwrap_or_else(application_name)
641}
642
643/// Inner datastructure for the [`crate::api::Window`]
644pub struct WindowInner {
645    window_adapter_weak: Weak<dyn WindowAdapter>,
646    component: RefCell<ItemTreeWeak>,
647    /// When the window is visible, keep a strong reference
648    strong_component_ref: RefCell<Option<ItemTreeRc>>,
649    mouse_input_state: Cell<MouseInputState>,
650    touch_state: RefCell<TouchState>,
651
652    /// ItemRC that currently have the focus (possibly an instance of TextInput)
653    pub focus_item: RefCell<crate::item_tree::ItemWeak>,
654    focus_item_visibility_tracker: ChangeTracker,
655    focus_item_position_tracker: ChangeTracker,
656    /// The last text that was sent to the input method
657    pub(crate) last_ime_text: RefCell<SharedString>,
658    /// Don't let ComponentContainers's instantiation change the focus.
659    /// This is a workaround for a recursion when instantiating ComponentContainer because the
660    /// init code for the component might have code that sets the focus, but we don't want that
661    /// for the ComponentContainer
662    pub(crate) prevent_focus_change: Cell<bool>,
663    cursor_blinker: RefCell<pin_weak::rc::PinWeak<crate::input::TextCursorBlinker>>,
664
665    pinned_fields: Pin<Box<WindowPinnedFields>>,
666
667    menubar: RefCell<Option<vtable::VWeak<MenuVTable>>>,
668
669    /// Stack of currently active popups
670    pub active_popups: RefCell<Vec<PopupWindow>>,
671    next_popup_id: Cell<NonZeroU32>,
672    had_popup_on_press: Cell<bool>,
673    close_requested: Callback<(), CloseRequestResponse>,
674    click_state: ClickState,
675    ctx: core::cell::OnceCell<crate::SlintContext>,
676    /// The native drag we started, if one is in flight.
677    /// It holds the source and seed position to report completion and to arm the in-window
678    /// fallback, and lets a drop back onto this same window restore the source's `DataTransfer`:
679    /// the OS round-trip can't carry in-app `user_data`.
680    native_drag: RefCell<Option<NativePendingDrag>>,
681}
682
683impl Drop for WindowInner {
684    fn drop(&mut self) {
685        if let Some(existing_blinker) = self.cursor_blinker.borrow().upgrade() {
686            existing_blinker.stop();
687        }
688    }
689}
690
691impl WindowInner {
692    /// Create a new instance of the window, given the window_adapter factory fn
693    pub fn new(window_adapter_weak: Weak<dyn WindowAdapter>) -> Self {
694        #![allow(unused_mut)]
695
696        let mut window_properties_tracker =
697            PropertyTracker::new_with_dirty_handler(WindowPropertiesTracker {
698                window_adapter_weak: window_adapter_weak.clone(),
699            });
700
701        let mut redraw_tracker = PropertyTracker::new_with_dirty_handler(WindowRedrawTracker {
702            window_adapter_weak: window_adapter_weak.clone(),
703        });
704
705        #[cfg(slint_debug_property)]
706        {
707            window_properties_tracker
708                .set_debug_name("i_slint_core::Window::window_properties_tracker".into());
709            redraw_tracker.set_debug_name("i_slint_core::Window::redraw_tracker".into());
710        }
711
712        Self {
713            window_adapter_weak,
714            component: Default::default(),
715            strong_component_ref: Default::default(),
716            mouse_input_state: Default::default(),
717            touch_state: Default::default(),
718            pinned_fields: Box::pin(WindowPinnedFields {
719                redraw_tracker,
720                window_properties_tracker,
721                scale_factor: Property::new_named(1., "i_slint_core::Window::scale_factor"),
722                active: Property::new_named(false, "i_slint_core::Window::active"),
723                text_input_focused: Property::new_named(
724                    false,
725                    "i_slint_core::Window::text_input_focused",
726                ),
727                menubar_shortcuts: Property::new_named(
728                    SharedVector::default(),
729                    "i_slint_core::Window::menubar_shortcuts",
730                ),
731            }),
732            focus_item: Default::default(),
733            focus_item_visibility_tracker: Default::default(),
734            focus_item_position_tracker: Default::default(),
735            last_ime_text: Default::default(),
736            cursor_blinker: Default::default(),
737            active_popups: Default::default(),
738            next_popup_id: Cell::new(NonZeroU32::MIN),
739            had_popup_on_press: Default::default(),
740            close_requested: Default::default(),
741            click_state: ClickState::default(),
742            prevent_focus_change: Default::default(),
743            ctx: Default::default(),
744            menubar: Default::default(),
745            native_drag: Default::default(),
746        }
747    }
748
749    /// Associates this window with the specified component. Further event handling and rendering, etc. will be
750    /// done with that component.
751    pub fn set_component(&self, component: &ItemTreeRc) {
752        self.close_all_popups();
753        self.focus_item_visibility_tracker.clear();
754        self.focus_item_position_tracker.clear();
755        self.focus_item.replace(Default::default());
756        self.mouse_input_state.replace(Default::default());
757        self.touch_state.replace(Default::default());
758        self.component.replace(ItemTreeRc::downgrade(component));
759        self.pinned_fields.window_properties_tracker.set_dirty(); // component changed, layout constraints for sure must be re-calculated
760        let window_adapter = self.window_adapter();
761        window_adapter.renderer().set_window_adapter(&window_adapter);
762        let scale_factor = self.scale_factor();
763        self.set_window_item_geometry(window_adapter.size().to_logical(scale_factor).to_euclid());
764        let inset = window_adapter
765            .internal(crate::InternalToken)
766            .map(|internal| internal.safe_area_inset())
767            .unwrap_or_default();
768        self.set_window_item_safe_area(inset.to_logical(scale_factor));
769        window_adapter.request_redraw();
770        let weak = Rc::downgrade(&window_adapter);
771        self.context().single_shot(Default::default(), move || {
772            if let Some(window_adapter) = weak.upgrade() {
773                WindowInner::from_pub(window_adapter.window()).update_window_properties();
774            }
775        })
776    }
777
778    /// return the component.
779    /// Panics if it wasn't set.
780    pub fn component(&self) -> ItemTreeRc {
781        self.component.borrow().upgrade().unwrap()
782    }
783
784    /// returns the component or None if it isn't set.
785    pub fn try_component(&self) -> Option<ItemTreeRc> {
786        self.component.borrow().upgrade()
787    }
788
789    /// Walk the component tree and every active popup to materialize every
790    /// Repeater, Conditional and ComponentContainer.  Runs change handlers
791    /// and the instantiation pass in a loop because init callbacks may set
792    /// properties that trigger change handlers, and change handlers may
793    /// make new conditionals/repeaters dirty.
794    pub fn ensure_tree_instantiated(&self) {
795        // Instantiation runs first so that ListView's ensure_updated_listview
796        // sees the model property before any change handler can reset it.
797        for _ in 0..10 {
798            let mut changed = false;
799            if let Some(component) = self.try_component() {
800                changed |= crate::item_tree::ensure_item_tree_instantiated(&component);
801            }
802            for popup in self.active_popups.borrow().iter() {
803                changed |= crate::item_tree::ensure_item_tree_instantiated(&popup.component);
804            }
805            changed |= crate::properties::ChangeTracker::run_change_handlers_once();
806            if !changed {
807                return;
808            }
809        }
810        crate::debug_log!("Slint: long callback/instantiation chain detected");
811    }
812
813    /// Returns a slice of the active popups.
814    pub fn active_popups(&self) -> core::cell::Ref<'_, [PopupWindow]> {
815        core::cell::Ref::map(self.active_popups.borrow(), |v| v.as_slice())
816    }
817
818    /// Receive a mouse event and pass it to the items of the component to
819    /// change their state.
820    ///
821    /// This is the runtime's entry point for pointer input.
822    /// Backends don't call it directly, they dispatch [`crate::platform::InternalEvent::Mouse`]
823    /// through [`crate::api::Window::dispatch_event_with_result()`],
824    /// so that every event they deliver takes the same path and is observed by the window event hook.
825    ///
826    /// Returns `None` when there is no component to dispatch to; otherwise returns a
827    /// [`MouseDispatchResult`] carrying:
828    /// - `accepted`: whether an item consumed the event, and
829    /// - `drag_action`: for `DragMove`/`Drop` events, the negotiated
830    ///   [`DragAction`](crate::items::DragAction) (or `None` if no `DropArea` accepted).
831    ///
832    /// Note: when a drag is in flight, the runtime rewrites a `Released` into either a
833    /// `Drop` (if a `DropArea` had previously accepted the matching `DragMove`) or an
834    /// `Exit` (if not). The reported `accepted` reflects the rewritten event, so a
835    /// `Released` that completes a drop on a non-accepting target reports `accepted = false`.
836    pub(crate) fn process_mouse_input(&self, mut event: MouseEvent) -> Option<MouseDispatchResult> {
837        crate::animations::update_animations(crate::animations::Instant::now(self.context()));
838
839        let item_tree = self.try_component()?;
840        self.ensure_tree_instantiated();
841
842        // If the focused item became invisible (e.g. a TabWidget switched away from
843        // the tab holding it), drop the focus so that input methods get torn down.
844        // The key-event handler does the same, but a tab is switched with a pointer
845        // tap, not a key press, so it must also happen here.
846        if self.focus_item.borrow().upgrade().is_some_and(|i| !i.is_visible()) {
847            self.take_focus_item(&FocusEvent::FocusOut(FocusReason::TabNavigation));
848        }
849
850        // handle multiple press release
851        event = self.click_state.check_repeat(event, self.context());
852
853        let window_adapter = self.window_adapter();
854        let mut mouse_input_state = self.mouse_input_state.take();
855
856        let was_dragging = mouse_input_state.drag_data.is_some();
857        let old_cursor = core::mem::replace(
858            &mut mouse_input_state.cursor,
859            MouseCursorInner::BuiltIn(BuiltInMouseCursor::Default),
860        );
861
862        // drag-finished firing is deferred until after dispatch so the DropArea has had
863        // a chance to fire its own `dropped` callback first; that callback returns the
864        // final action, which the runtime then forwards to the source.
865        let mut pending_drag_finished: Option<(
866            crate::item_tree::ItemWeak,
867            Option<crate::item_tree::ItemWeak>,
868        )> = None;
869
870        if let Some(DragData { event: mut drop_event, allowed }) =
871            mouse_input_state.drag_data.clone()
872        {
873            match &event {
874                MouseEvent::Released { position, button: PointerEventButton::Left, .. } => {
875                    mouse_input_state.drag_data = None;
876                    let source = mouse_input_state.drag_source.take();
877                    if let Some(target_weak) = mouse_input_state.drop_target.take() {
878                        // Seed `proposed-action` for the dropped callback with the action the
879                        // target last chose during hover; the callback's return value will
880                        // become the final action reported to the source.
881                        let hovered = target_weak
882                            .upgrade()
883                            .and_then(|t| t.downcast::<crate::items::DropArea>())
884                            .map(|d| d.as_pin_ref().current_action())
885                            .unwrap_or(crate::items::DragAction::None);
886                        drop_event.proposed_action = hovered;
887                        drop_event.position = crate::lengths::logical_position_to_api(*position);
888                        event = MouseEvent::Drop { event: drop_event, allowed };
889                        if let Some(s) = source {
890                            pending_drag_finished = Some((s, Some(target_weak)));
891                        }
892                    } else {
893                        // No DropArea accepted the most recent DragMove. Tear the drag
894                        // down via Exit instead of converting to Drop so a non-accepting
895                        // DropArea under the cursor doesn't fire `dropped`, and so the
896                        // underlying Release doesn't reach hit-tested items as a
897                        // spurious click.
898                        event = MouseEvent::Exit;
899                        if let Some(s) = source {
900                            pending_drag_finished = Some((s, None));
901                        }
902                    }
903                }
904                MouseEvent::Moved { position, .. } => {
905                    drop_event.position = crate::lengths::logical_position_to_api(*position);
906                    // Recompute the proposed action from current modifier state so the target's
907                    // `can-drop` callback sees an up-to-date `event.proposed-action`.
908                    drop_event.proposed_action = crate::items::compute_proposed_action(
909                        self.context().0.modifiers.get().into(),
910                        allowed,
911                    );
912                    // Mirror the position and proposed action into the persistent state so the
913                    // renderer can place the drag-image overlay without re-deriving the cursor
914                    // location, and so a subsequent synthetic Moved (e.g. fired from a modifier
915                    // key press) starts from the right position.
916                    if let Some(d) = mouse_input_state.drag_data.as_mut() {
917                        d.event.position = drop_event.position;
918                        d.event.proposed_action = drop_event.proposed_action;
919                    }
920                    mouse_input_state.cursor =
921                        MouseCursorInner::BuiltIn(BuiltInMouseCursor::NoDrop);
922                    event = MouseEvent::DragMove { event: drop_event, allowed };
923                }
924                MouseEvent::Exit => {
925                    mouse_input_state.drag_data = None;
926                    mouse_input_state.drop_target = None;
927                    if let Some(s) = mouse_input_state.drag_source.take() {
928                        pending_drag_finished = Some((s, None));
929                    }
930                }
931                _ => {}
932            }
933        } else if let MouseEvent::DragMove { event, .. } | MouseEvent::Drop { event, .. } =
934            &mut event
935        {
936            // An incoming native drag while our own is in flight: the same operation looping
937            // back onto the source window. Restore the full source data so a same-window drop
938            // sees the `user_data` the OS round-trip dropped.
939            if let Some(pending) = self.native_drag.borrow().as_ref() {
940                event.data = pending.request.data.clone();
941            }
942        }
943
944        let pressed_event = matches!(event, MouseEvent::Pressed { .. });
945        let released_event = matches!(event, MouseEvent::Released { .. });
946        let had_delay = mouse_input_state.has_delayed_event();
947
948        let last_top_item = mouse_input_state.top_item_including_delayed();
949        if released_event {
950            mouse_input_state =
951                crate::input::process_delayed_event(&window_adapter, mouse_input_state);
952        }
953
954        let parent_adapter = window_adapter
955            .internal(crate::InternalToken)
956            .and_then(|internal| internal.get_parent())
957            .unwrap_or_else(|| window_adapter.clone());
958        let active_popups = &WindowInner::from_pub(parent_adapter.window()).active_popups;
959        let native_popup_index = active_popups.borrow().iter().position(|p| {
960            if let PopupWindowLocation::TopLevel(wa) = &p.location {
961                Rc::ptr_eq(wa, &window_adapter)
962            } else {
963                false
964            }
965        });
966
967        if pressed_event {
968            self.had_popup_on_press.set(!active_popups.borrow().is_empty());
969        }
970
971        let mut popup_to_close = active_popups.borrow().last().and_then(|popup| {
972            let mouse_inside_popup = || {
973                if let PopupWindowLocation::ChildWindow(coordinates) = &popup.location {
974                    event.position().is_none_or(|pos| {
975                        ItemTreeRc::borrow_pin(&popup.component)
976                            .as_ref()
977                            .item_geometry(0)
978                            .contains(pos - coordinates.to_vector())
979                    })
980                } else {
981                    native_popup_index.is_some_and(|idx| idx == active_popups.borrow().len() - 1)
982                        && event.position().is_none_or(|pos| {
983                            ItemTreeRc::borrow_pin(&item_tree)
984                                .as_ref()
985                                .item_geometry(0)
986                                .contains(pos)
987                        })
988                }
989            };
990            match popup.close_policy {
991                PopupClosePolicy::CloseOnClick => {
992                    let mouse_inside_popup = mouse_inside_popup();
993                    (mouse_inside_popup && released_event && self.had_popup_on_press.get())
994                        || (!mouse_inside_popup && pressed_event)
995                }
996                PopupClosePolicy::CloseOnClickOutside => !mouse_inside_popup() && pressed_event,
997                PopupClosePolicy::NoAutoClose => false,
998            }
999            .then_some(popup.popup_id)
1000        });
1001
1002        let grab_result =
1003            crate::input::handle_mouse_grab(&event, &window_adapter, &mut mouse_input_state);
1004        let grab_accepted = grab_result.accepted;
1005
1006        let mut dispatch_accepted = false;
1007        mouse_input_state = if let Some(mut event) = grab_result.event {
1008            // The grab handler may have fired callbacks that modified models or
1009            // other state, so materialize any pending repeater/conditional
1010            // changes before hit-testing with the returned event.
1011            self.ensure_tree_instantiated();
1012            let mut item_tree = self.component.borrow().upgrade();
1013            let mut offset = LogicalPoint::default();
1014            let mut menubar_item = None;
1015            for (idx, popup) in active_popups.borrow().iter().enumerate().rev() {
1016                if matches!(popup.window_kind, WindowKind::ToolTip) {
1017                    continue;
1018                }
1019                item_tree = None;
1020                menubar_item = None;
1021                if let PopupWindowLocation::ChildWindow(coordinates) = &popup.location {
1022                    let geom = ItemTreeRc::borrow_pin(&popup.component).as_ref().item_geometry(0);
1023                    let mouse_inside_popup = event
1024                        .position()
1025                        .is_none_or(|pos| geom.contains(pos - coordinates.to_vector()));
1026                    if mouse_inside_popup {
1027                        item_tree = Some(popup.component.clone());
1028                        offset = *coordinates;
1029                        break;
1030                    }
1031                } else if native_popup_index.is_some_and(|i| i == idx) {
1032                    item_tree = self.component.borrow().upgrade();
1033                    break;
1034                }
1035
1036                if !matches!(popup.window_kind, WindowKind::Menu) {
1037                    break;
1038                } else if popup_to_close.is_some() {
1039                    // clicking outside of a popup menu should close all the menus
1040                    popup_to_close = Some(popup.popup_id);
1041                }
1042
1043                menubar_item = popup.parent_item.upgrade();
1044            }
1045
1046            let root = match menubar_item {
1047                None => item_tree.map(|item_tree| ItemRc::new_root(item_tree.clone())),
1048                Some(menubar_item) => {
1049                    event.translate(
1050                        menubar_item
1051                            .map_to_item_tree(Default::default(), &self.component())
1052                            .to_vector(),
1053                    );
1054                    menubar_item.parent_item(ParentItemTraversalMode::StopAtPopups)
1055                }
1056            };
1057
1058            if let Some(root) = root {
1059                event.translate(-offset.to_vector());
1060                let crate::input::MouseInputResult { mut state, accepted } =
1061                    crate::input::process_mouse_input(
1062                        root,
1063                        &event,
1064                        &window_adapter,
1065                        mouse_input_state,
1066                    );
1067                state.offset = offset;
1068                dispatch_accepted = accepted;
1069                state
1070            } else {
1071                // When outside, send exit event
1072                let mut new_input_state = MouseInputState::default();
1073                crate::input::send_exit_events(
1074                    &mouse_input_state,
1075                    &mut new_input_state,
1076                    event.position(),
1077                    &window_adapter,
1078                );
1079                new_input_state
1080            }
1081        } else {
1082            mouse_input_state
1083        };
1084
1085        let accepted = dispatch_accepted | grab_accepted;
1086
1087        if last_top_item != mouse_input_state.top_item_including_delayed() {
1088            self.click_state.reset();
1089            self.click_state.check_repeat(event, self.context());
1090        }
1091
1092        if !had_delay && mouse_input_state.has_delayed_event() {
1093            // A delay was just set up, preserve the old cursor
1094            mouse_input_state.cursor = old_cursor;
1095        } else if old_cursor != mouse_input_state.cursor
1096            && let Some(window_adapter) = window_adapter.internal(crate::InternalToken)
1097        {
1098            window_adapter.set_mouse_cursor(mouse_input_state.cursor.clone());
1099        }
1100
1101        let is_dragging = mouse_input_state.drag_data.is_some();
1102        let drag_action = mouse_input_state.drop_target_action();
1103        self.mouse_input_state.set(mouse_input_state);
1104
1105        // The drag-image overlay follows the cursor and lives outside any item tree, so
1106        // partial renderers won't otherwise know to repaint it on mouse motion or after
1107        // the drag ends. `render_drag_image_overlay` marks its painted rect dirty itself,
1108        // we just need to schedule the redraw.
1109        if was_dragging || is_dragging {
1110            window_adapter.request_redraw();
1111        }
1112
1113        if pending_drag_finished.is_some() {
1114            // A drag ended in-window (including after a native start fell back), so drop the
1115            // stash.
1116            self.native_drag.borrow_mut().take();
1117        }
1118        if let Some((source_weak, target_weak)) = pending_drag_finished
1119            && let Some(source) = source_weak.upgrade()
1120            && let Some(drag_area) = source.downcast::<crate::items::DragArea>()
1121        {
1122            // The action `dropped` returned is now sitting on the target's `current_action`.
1123            // For a cancelled drag (no target) we just report None.
1124            let target = target_weak
1125                .and_then(|w| w.upgrade())
1126                .and_then(|i| i.downcast::<crate::items::DropArea>());
1127            let action = target
1128                .as_ref()
1129                .map(|d| d.as_pin_ref().current_action())
1130                .unwrap_or(crate::items::DragAction::None);
1131            drag_area.as_pin_ref().finish_drag(action);
1132            // The drag is over: reset the target's `current_action` so it matches
1133            // `has_drag` and the docstring ("none when no drag is hovering").
1134            if let Some(target) = target {
1135                target.as_pin_ref().current_action.set(crate::items::DragAction::None);
1136            }
1137        }
1138
1139        if let Some(popup_id) = popup_to_close {
1140            WindowInner::from_pub(parent_adapter.window()).close_popup(popup_id);
1141        }
1142
1143        self.ensure_tree_instantiated();
1144
1145        Some(MouseDispatchResult { drag_action, accepted })
1146    }
1147
1148    /// Dispatch a drag and drop event.
1149    /// Returns the action negotiated with the accepting `DropArea`, or `None` when none accepted.
1150    ///
1151    /// Drag and drop is the one kind of input that backends don't deliver through
1152    /// [`crate::api::Window::dispatch_event_with_result()`]:
1153    /// they need the negotiated action back, which [`crate::platform::WindowEventDispatchResult`] can't express,
1154    /// and a drag leaving the window isn't the pointer leaving the window.
1155    /// [`BackendDragEvent`] keeps this entry point to drag and drop,
1156    /// so that nothing else bypasses the window event hook.
1157    pub fn process_drag_event(&self, event: BackendDragEvent) -> Option<crate::items::DragAction> {
1158        self.process_mouse_input(event.into()).and_then(|result| result.drag_action)
1159    }
1160
1161    /// Remember (or clear) the in-flight native drag, so a backend can report completion or fall
1162    /// back, and a drop back onto this window can restore the data. Set by `offer_native_drag`.
1163    pub(crate) fn set_native_drag(&self, drag: Option<NativePendingDrag>) {
1164        *self.native_drag.borrow_mut() = drag;
1165    }
1166
1167    /// Report that the in-flight native drag finished with `action`.
1168    ///
1169    /// Backends call this when the OS drag completes (`DragAction::None` if cancelled); the
1170    /// source `DragArea` clears `dragging` and fires `drag-finished`.
1171    pub fn report_drag_finished(&self, action: crate::items::DragAction) {
1172        let Some(pending) = self.native_drag.borrow_mut().take() else {
1173            return;
1174        };
1175        if let Some(drag_area) =
1176            pending.source.upgrade().and_then(|i| i.downcast::<crate::items::DragArea>())
1177        {
1178            drag_area.as_pin_ref().finish_drag(action);
1179        }
1180    }
1181
1182    /// Fall back to the in-window drag for the in-flight native drag.
1183    ///
1184    /// Backends call this when a native start fails after taking the drag over; subsequent mouse
1185    /// moves then drive `DragMove`/`Drop` and the drag-image overlay, in-process.
1186    pub fn start_in_window_drag(&self) {
1187        let (source, seed_position) = {
1188            let native_drag = self.native_drag.borrow();
1189            let Some(drag) = native_drag.as_ref() else {
1190                return;
1191            };
1192            (drag.source.clone(), drag.seed_position)
1193        };
1194        let Some(drag_area) = source.upgrade().and_then(|i| i.downcast::<crate::items::DragArea>())
1195        else {
1196            return;
1197        };
1198        let mut state = self.mouse_input_state.take();
1199        state.arm_in_window_drag(drag_area.as_pin_ref(), source, seed_position);
1200        self.mouse_input_state.set(state);
1201        self.window_adapter().request_redraw();
1202    }
1203
1204    /// Receive a raw touch event from a backend and either forward it as a mouse
1205    /// event (single finger) or synthesize `PinchGesture`/`RotationGesture` events
1206    /// (two fingers), producing the same events as platform gesture recognition.
1207    ///
1208    /// `position` must be in **logical coordinates** (i.e., already divided by the
1209    /// scale factor). Passing physical coordinates will produce incorrect gesture
1210    /// geometry and hit-testing.
1211    ///
1212    /// `drag_action` is taken from the *last* sub-event (including `None`), because
1213    /// `drag_action` reflects the current drop-target negotiation, not a per-event
1214    /// verdict to aggregate. For touch sequences that never produce a `DragMove`/`Drop`
1215    /// (the common case), this stays `None` throughout.
1216    pub(crate) fn process_touch_input(
1217        &self,
1218        id: i32,
1219        position: LogicalPoint,
1220        phase: TouchPhase,
1221    ) -> Option<MouseDispatchResult> {
1222        let events = self.touch_state.borrow_mut().process(id, position, phase);
1223        let mut aggregate: Option<MouseDispatchResult> = None;
1224        for event in events.into_iter() {
1225            if let Some(r) = self.process_mouse_input(event) {
1226                let agg = aggregate
1227                    .get_or_insert(MouseDispatchResult { drag_action: None, accepted: false });
1228                agg.accepted |= r.accepted;
1229                agg.drag_action = r.drag_action;
1230            }
1231        }
1232        aggregate
1233    }
1234
1235    /// Called by the input code's internal timer to send an event that was delayed
1236    pub(crate) fn process_delayed_event(&self) {
1237        self.mouse_input_state.set(crate::input::process_delayed_event(
1238            &self.window_adapter(),
1239            self.mouse_input_state.take(),
1240        ));
1241    }
1242
1243    /// Receive a key event and pass it to the items of the component to
1244    /// change their state.
1245    ///
1246    /// Arguments:
1247    /// * `event`: The key event received by the windowing system.
1248    pub(crate) fn process_key_input(
1249        &self,
1250        mut internal_key_event: InternalKeyEvent,
1251    ) -> crate::input::KeyEventResult {
1252        self.ensure_tree_instantiated();
1253        // NFC-normalize the event text so that shortcut matching works consistently
1254        // regardless of the composed/decomposed form the backend provides
1255        // (e.g. é as U+00E9 vs e + U+0301).
1256        // Note: icu_normalizer is currently only enabled if parley is enabled
1257        #[cfg(feature = "shared-parley")]
1258        {
1259            let normalizer = icu_normalizer::ComposingNormalizer::new_nfc();
1260            let normalized = normalizer.normalize(&internal_key_event.key_event.text);
1261            // Only replace the event text if normalization actually changed it,
1262            // to avoid unnecessary allocations.
1263            if let alloc::borrow::Cow::Owned(normalized) = normalized {
1264                internal_key_event.key_event.text = normalized.into();
1265            }
1266        }
1267
1268        if let Some(updated_modifier) = self.context().0.modifiers.get().state_update(
1269            internal_key_event.event_type == KeyEventType::KeyPressed,
1270            &internal_key_event.key_event.text,
1271        ) {
1272            // Updates the key modifiers depending on the key code and pressed state.
1273            self.context().0.modifiers.set(updated_modifier);
1274
1275            // If a drag is in flight, synthesize a Moved at the last drag position so
1276            // the new modifier state flows into `event.proposed-action` and the target's
1277            // `can-drop` re-runs — letting the user change copy/move/link with Ctrl/Shift
1278            // without having to move the mouse first.
1279            let drag_pos = {
1280                let state = self.mouse_input_state.take();
1281                let pos = state.drag_data.as_ref().map(|d| d.event.position);
1282                self.mouse_input_state.replace(state);
1283                pos
1284            };
1285            if let Some(pos) = drag_pos {
1286                self.process_mouse_input(MouseEvent::Moved {
1287                    position: crate::lengths::logical_point_from_api(pos),
1288                    touch_finger_id: 0,
1289                });
1290            }
1291        }
1292
1293        internal_key_event.key_event.modifiers =
1294            self.context().0.modifiers.get().modifiers_for(&internal_key_event);
1295
1296        // Emulate macOS menubar behavior: The OS consumes the event before it reaches any
1297        // Slint widgets. Therefore we process the menubar shortcuts here first and abort event
1298        // propagation if a shortcut matches.
1299        if self.process_menubar_shortcuts(&internal_key_event) == KeyEventResult::EventAccepted {
1300            self.ensure_tree_instantiated();
1301            return crate::input::KeyEventResult::EventAccepted;
1302        }
1303
1304        let mut item = self.focus_item.borrow().clone().upgrade();
1305
1306        if item.as_ref().is_some_and(|i| !i.is_visible()) {
1307            // Reset the focus... not great, but better than keeping it.
1308            self.take_focus_item(&FocusEvent::FocusOut(FocusReason::TabNavigation));
1309            item = None;
1310        }
1311
1312        let item_list = {
1313            let mut tmp = Vec::new();
1314            let mut item = item.clone();
1315
1316            while let Some(i) = item {
1317                tmp.push(i.clone());
1318                item = i.parent_item(ParentItemTraversalMode::StopAtPopups);
1319            }
1320
1321            tmp
1322        };
1323
1324        // Check capture_key_event (going from window to focused item):
1325        for i in item_list.iter().rev() {
1326            if i.borrow().as_ref().capture_key_event(&internal_key_event, &self.window_adapter(), i)
1327                == crate::input::KeyEventResult::EventAccepted
1328            {
1329                self.ensure_tree_instantiated();
1330                return crate::input::KeyEventResult::EventAccepted;
1331            }
1332        }
1333
1334        drop(item_list);
1335
1336        // Deliver key_event (to focused item, going up towards the window):
1337        while let Some(focus_item) = item {
1338            if focus_item.borrow().as_ref().key_event(
1339                &internal_key_event,
1340                &self.window_adapter(),
1341                &focus_item,
1342            ) == crate::input::KeyEventResult::EventAccepted
1343            {
1344                self.ensure_tree_instantiated();
1345                return crate::input::KeyEventResult::EventAccepted;
1346            }
1347            item = focus_item.parent_item(ParentItemTraversalMode::StopAtPopups);
1348        }
1349
1350        // Make Tab/Backtab handle keyboard focus
1351        let extra_mod = internal_key_event.key_event.modifiers.control
1352            || internal_key_event.key_event.modifiers.meta
1353            || internal_key_event.key_event.modifiers.alt;
1354        if internal_key_event.key_event.text.starts_with(key_codes::Tab)
1355            && !internal_key_event.key_event.modifiers.shift
1356            && !extra_mod
1357            && internal_key_event.event_type == KeyEventType::KeyPressed
1358        {
1359            self.focus_next_item();
1360            self.ensure_tree_instantiated();
1361            return crate::input::KeyEventResult::EventAccepted;
1362        } else if (internal_key_event.key_event.text.starts_with(key_codes::Backtab)
1363            || (internal_key_event.key_event.text.starts_with(key_codes::Tab)
1364                && internal_key_event.key_event.modifiers.shift))
1365            && internal_key_event.event_type == KeyEventType::KeyPressed
1366            && !extra_mod
1367        {
1368            self.focus_previous_item();
1369            self.ensure_tree_instantiated();
1370            return crate::input::KeyEventResult::EventAccepted;
1371        } else if internal_key_event.event_type == KeyEventType::KeyPressed
1372            && internal_key_event.key_event.text.starts_with(key_codes::Escape)
1373        {
1374            // Closes top most popup on ESC key pressed when policy is not no-auto-close
1375
1376            // Try to get the parent window in case `self` is the popup itself
1377            let mut adapter = self.window_adapter();
1378            let item_tree = self.component();
1379            let mut a = None;
1380            ItemTreeRc::borrow_pin(&item_tree).as_ref().window_adapter(false, &mut a);
1381            if let Some(a) = a {
1382                adapter = a;
1383            }
1384            let window = WindowInner::from_pub(adapter.window());
1385
1386            let close_on_escape = if let Some(popup) = window.active_popups.borrow().last() {
1387                popup.close_policy == PopupClosePolicy::CloseOnClick
1388                    || popup.close_policy == PopupClosePolicy::CloseOnClickOutside
1389            } else {
1390                false
1391            };
1392
1393            if close_on_escape {
1394                window.close_top_popup();
1395            }
1396            self.ensure_tree_instantiated();
1397            return crate::input::KeyEventResult::EventAccepted;
1398        }
1399
1400        self.ensure_tree_instantiated();
1401        crate::input::KeyEventResult::EventIgnored
1402    }
1403
1404    fn process_menubar_shortcuts(
1405        &self,
1406        internal_key_event: &InternalKeyEvent,
1407    ) -> crate::input::KeyEventResult {
1408        let event_type = internal_key_event.event_type;
1409        let menubar = self.menubar.borrow().as_ref().and_then(vtable::VWeak::upgrade);
1410
1411        if (event_type == KeyEventType::KeyReleased || event_type == KeyEventType::KeyPressed)
1412            && let Some(menubar) = menubar
1413        {
1414            let shortcuts = self.pinned_fields.as_ref().project_ref().menubar_shortcuts.get();
1415            let mut matches = shortcuts
1416                .into_iter()
1417                .filter(|entry| entry.shortcut.matches(&internal_key_event.key_event));
1418            if let Some(entry) = matches.next() {
1419                if internal_key_event.event_type == KeyEventType::KeyPressed {
1420                    VRc::borrow(&menubar).activate(&entry);
1421                    if matches.next().is_some() {
1422                        crate::debug_log!(
1423                            "Warning: Ambiguous menubar shortcut: {}",
1424                            entry.shortcut
1425                        );
1426                    }
1427                }
1428                return crate::input::KeyEventResult::EventAccepted;
1429            }
1430        }
1431        crate::input::KeyEventResult::EventIgnored
1432    }
1433
1434    /// Installs a binding on the specified property that's toggled whenever the text cursor is supposed to be visible or not.
1435    pub fn set_cursor_blink_binding(&self, prop: &crate::Property<bool>) {
1436        let existing_blinker = self.cursor_blinker.borrow().clone();
1437
1438        let blinker = existing_blinker.upgrade().unwrap_or_else(|| {
1439            let new_blinker = TextCursorBlinker::new();
1440            *self.cursor_blinker.borrow_mut() =
1441                pin_weak::rc::PinWeak::downgrade(new_blinker.clone());
1442            new_blinker
1443        });
1444
1445        let ctx = self.context();
1446        TextCursorBlinker::set_binding(blinker, prop, ctx, ctx.platform().cursor_flash_cycle());
1447    }
1448
1449    /// Sets the focus to the item pointed to by item_ptr. This will remove the focus from any
1450    /// currently focused item. If set_focus is false, the focus is cleared.
1451    pub fn set_focus_item(&self, new_focus_item: &ItemRc, set_focus: bool, reason: FocusReason) {
1452        if self.prevent_focus_change.get() {
1453            return;
1454        }
1455
1456        let popup_wa = self.active_popups.borrow().last().and_then(|p| match &p.location {
1457            PopupWindowLocation::TopLevel(wa) => Some(wa.clone()),
1458            PopupWindowLocation::ChildWindow(..) => None,
1459        });
1460        if let Some(popup_wa) = popup_wa {
1461            // Set the focus item on the popup's Window instead
1462            popup_wa.window().0.set_focus_item(new_focus_item, set_focus, reason);
1463            return;
1464        }
1465
1466        let current_focus_item = self.focus_item.borrow().clone();
1467        if let Some(current_focus_item_rc) = current_focus_item.upgrade() {
1468            if set_focus {
1469                if current_focus_item_rc == *new_focus_item {
1470                    // don't send focus out and in even to the same item if focus doesn't change
1471                    return;
1472                }
1473            } else if current_focus_item_rc != *new_focus_item {
1474                // can't clear focus unless called with currently focused item.
1475                return;
1476            }
1477        }
1478
1479        let old = self.take_focus_item(&FocusEvent::FocusOut(reason));
1480        let new = if set_focus {
1481            self.move_focus(new_focus_item.clone(), next_focus_item, reason)
1482        } else {
1483            None
1484        };
1485        let window_adapter = self.window_adapter();
1486        if let Some(window_adapter) = window_adapter.internal(crate::InternalToken) {
1487            window_adapter.handle_focus_change(old, new);
1488        }
1489    }
1490
1491    /// Take the focus_item out of this Window
1492    ///
1493    /// This sends the event which must be either FocusOut or WindowLostFocus for popups
1494    fn take_focus_item(&self, event: &FocusEvent) -> Option<ItemRc> {
1495        self.focus_item_visibility_tracker.clear();
1496        self.focus_item_position_tracker.clear();
1497        let focus_item = self.focus_item.take();
1498        assert!(matches!(event, FocusEvent::FocusOut(_)));
1499
1500        if let Some(focus_item_rc) = focus_item.upgrade() {
1501            focus_item_rc.borrow().as_ref().focus_event(
1502                event,
1503                &self.window_adapter(),
1504                &focus_item_rc,
1505            );
1506            Some(focus_item_rc)
1507        } else {
1508            None
1509        }
1510    }
1511
1512    /// Publish the new focus_item to this Window and return the FocusEventResult
1513    ///
1514    /// This sends a FocusIn event!
1515    fn publish_focus_item(
1516        &self,
1517        item: &Option<ItemRc>,
1518        reason: FocusReason,
1519    ) -> crate::input::FocusEventResult {
1520        match item {
1521            Some(item) => {
1522                *self.focus_item.borrow_mut() = item.downgrade();
1523                let result = item.borrow().as_ref().focus_event(
1524                    &FocusEvent::FocusIn(reason),
1525                    &self.window_adapter(),
1526                    item,
1527                );
1528                // Reveal offscreen item when it gains focus
1529                if result == crate::input::FocusEventResult::FocusAccepted {
1530                    self.track_focus_item(item);
1531                    item.try_scroll_into_visible();
1532                }
1533
1534                result
1535            }
1536            None => {
1537                self.focus_item_visibility_tracker.clear();
1538                self.focus_item_position_tracker.clear();
1539                *self.focus_item.borrow_mut() = Default::default();
1540                crate::input::FocusEventResult::FocusAccepted // We were removing the focus, treat that as OK
1541            }
1542        }
1543    }
1544
1545    fn track_focus_item(&self, item: &ItemRc) {
1546        // Track visibility
1547        let visibility_clips = item.visibility_clips();
1548        self.focus_item_visibility_tracker.init(
1549            (item.downgrade(), self.window_adapter_weak.clone(), visibility_clips),
1550            |(_, _, visibility_clips)| {
1551                visibility_clips
1552                    .iter()
1553                    .all(|clip| clip.upgrade().is_some_and(|clip| !clip.as_pin_ref().clip()))
1554            },
1555            |(item, window_adapter, _), visible| {
1556                if *visible {
1557                    return;
1558                }
1559                let Some(item) = item.upgrade() else { return };
1560                let Some(window_adapter) = window_adapter.upgrade() else { return };
1561                WindowInner::from_pub(window_adapter.window()).set_focus_item(
1562                    &item,
1563                    false,
1564                    FocusReason::Programmatic,
1565                );
1566            },
1567        );
1568
1569        // Track position
1570        if item.downcast::<crate::items::TextInput>().is_some() {
1571            self.focus_item_position_tracker.init(
1572                (item.downgrade(), self.window_adapter_weak.clone()),
1573                |(item, _)| {
1574                    let Some(item) = item.upgrade() else { return Default::default() };
1575                    Some(item.map_to_native_window(item.geometry().origin))
1576                },
1577                |(item, window_adapter), _| {
1578                    let (Some(item), Some(window_adapter)) =
1579                        (item.upgrade(), window_adapter.upgrade())
1580                    else {
1581                        return;
1582                    };
1583                    if let Some(text_input) = item.downcast::<crate::items::TextInput>() {
1584                        text_input.as_pin_ref().update_ime(&window_adapter, &item);
1585                    }
1586                },
1587            );
1588        }
1589    }
1590
1591    fn move_focus(
1592        &self,
1593        start_item: ItemRc,
1594        forward: impl Fn(ItemRc) -> ItemRc,
1595        reason: FocusReason,
1596    ) -> Option<ItemRc> {
1597        let mut current_item = start_item.clone();
1598        // The walk normally comes back to `start_item`, but ends in a cycle that misses it
1599        // when the tree changed under it, e.g. when the focus was on a removed item.
1600        // Comparing against a checkpoint (Brent's cycle detection) still terminates then.
1601        let mut checkpoint = start_item.clone();
1602        let mut steps = 0usize;
1603        let mut next_checkpoint = 1usize;
1604
1605        loop {
1606            let can_receive_focus = match reason {
1607                FocusReason::Programmatic => true,
1608                FocusReason::TabNavigation => current_item.is_visible_or_clipped_by_flickable(),
1609                _ => current_item.is_visible(),
1610            };
1611            if can_receive_focus
1612                && self.publish_focus_item(&Some(current_item.clone()), reason)
1613                    == crate::input::FocusEventResult::FocusAccepted
1614            {
1615                return Some(current_item); // Item was just published.
1616            }
1617            current_item = forward(current_item);
1618
1619            if current_item == start_item || current_item == checkpoint {
1620                return None; // Nothing to do: We took the focus_item already
1621            }
1622            steps += 1;
1623            if steps == next_checkpoint {
1624                checkpoint = current_item.clone();
1625                steps = 0;
1626                next_checkpoint *= 2;
1627            }
1628        }
1629    }
1630
1631    /// Move keyboard focus to the next item
1632    pub fn focus_next_item(&self) {
1633        let start_item = self
1634            .take_focus_item(&FocusEvent::FocusOut(FocusReason::TabNavigation))
1635            .map(next_focus_item)
1636            .unwrap_or_else(|| {
1637                ItemRc::new(
1638                    self.active_popups
1639                        .borrow()
1640                        .last()
1641                        .map_or_else(|| self.component(), |p| p.component.clone()),
1642                    0,
1643                )
1644            });
1645        let end_item =
1646            self.move_focus(start_item.clone(), next_focus_item, FocusReason::TabNavigation);
1647        let window_adapter = self.window_adapter();
1648        if let Some(window_adapter) = window_adapter.internal(crate::InternalToken) {
1649            window_adapter.handle_focus_change(Some(start_item), end_item);
1650        }
1651    }
1652
1653    /// Move keyboard focus to the previous item.
1654    pub fn focus_previous_item(&self) {
1655        let start_item = previous_focus_item(
1656            self.take_focus_item(&FocusEvent::FocusOut(FocusReason::TabNavigation)).unwrap_or_else(
1657                || {
1658                    ItemRc::new(
1659                        self.active_popups
1660                            .borrow()
1661                            .last()
1662                            .map_or_else(|| self.component(), |p| p.component.clone()),
1663                        0,
1664                    )
1665                },
1666            ),
1667        );
1668        let end_item =
1669            self.move_focus(start_item.clone(), previous_focus_item, FocusReason::TabNavigation);
1670        let window_adapter = self.window_adapter();
1671        if let Some(window_adapter) = window_adapter.internal(crate::InternalToken) {
1672            window_adapter.handle_focus_change(Some(start_item), end_item);
1673        }
1674    }
1675
1676    /// Marks the window to be the active window. This typically coincides with the keyboard
1677    /// focus. One exception though is when a popup is shown, in which case the window may
1678    /// remain active but temporarily loose focus to the popup.
1679    ///
1680    /// This results in WindowFocusReceived and WindowFocusLost events.
1681    pub fn set_active(&self, have_focus: bool) {
1682        self.pinned_fields.as_ref().project_ref().active.set(have_focus);
1683
1684        let event = if have_focus {
1685            FocusEvent::FocusIn(FocusReason::WindowActivation)
1686        } else {
1687            FocusEvent::FocusOut(FocusReason::WindowActivation)
1688        };
1689
1690        if let Some(focus_item) = self.focus_item.borrow().upgrade() {
1691            focus_item.borrow().as_ref().focus_event(&event, &self.window_adapter(), &focus_item);
1692        }
1693
1694        // If we lost focus due to for example a global shortcut, then when we regain focus
1695        // should not assume that the modifiers are in the same state.
1696        if !have_focus {
1697            self.context().0.modifiers.take();
1698        }
1699    }
1700
1701    /// Returns true of the window is the active window. That typically implies having the
1702    /// keyboard focus, except when a popup is shown and temporarily takes the focus.
1703    pub fn active(&self) -> bool {
1704        self.pinned_fields.as_ref().project_ref().active.get()
1705    }
1706
1707    /// If the component's root item is a Window element, then this function synchronizes its properties, such as the title
1708    /// for example, with the properties known to the windowing system.
1709    pub fn update_window_properties(&self) {
1710        let window_adapter = self.window_adapter();
1711
1712        // No `if !dirty { return; }` check here because the backend window may be newly mapped and not up-to-date, so force
1713        // an evaluation.
1714        self.pinned_fields
1715            .as_ref()
1716            .project_ref()
1717            .window_properties_tracker
1718            .evaluate_as_dependency_root(|| {
1719                window_adapter.update_window_properties(WindowProperties(self));
1720            });
1721    }
1722
1723    /// Re-evaluates the position tracker for the popup with the given ID, re-subscribing to its
1724    /// property dependencies so subsequent changes continue to trigger notifications.
1725    fn update_popup_properties(&self, popup_id: NonZeroU32) {
1726        let offset = {
1727            let active_popups = self.active_popups.borrow();
1728            let Some(popup) = active_popups.iter().find(|p| p.popup_id == popup_id) else { return };
1729            if let Some(parent) = popup.parent_item.clone().upgrade() {
1730                parent.map_to_native_window(
1731                    parent.geometry().origin + (popup.position_access)().to_euclid().to_vector(),
1732                )
1733            } else {
1734                LogicalPoint::zero()
1735            }
1736        };
1737        let mut active_popups = self.active_popups.borrow_mut();
1738        let Some(popup) = active_popups.iter_mut().find(|p| p.popup_id == popup_id) else { return };
1739        match &mut popup.location {
1740            PopupWindowLocation::ChildWindow(old_location) => {
1741                let (old_popup_region, new_popup_region) =
1742                    popup.properties_tracker.as_ref().evaluate_as_dependency_root(|| {
1743                        let component = ItemTreeRc::borrow_pin(&popup.component);
1744                        let root_item = component.as_ref().get_item_ref(0);
1745                        let window_item =
1746                            ItemRef::downcast_pin::<crate::items::WindowItem>(root_item)
1747                                .expect("Popup component is a Window item");
1748                        // Access the properties to set them as dependencies
1749                        let old_popup_region = LogicalRect::new(
1750                            *old_location,
1751                            crate::lengths::LogicalSize::new(
1752                                window_item.width().0,
1753                                window_item.height().0,
1754                            ),
1755                        );
1756
1757                        let width = {
1758                            let layout_info_h = component
1759                                .as_ref()
1760                                .layout_info(crate::layout::Orientation::Horizontal);
1761                            let w = layout_info_h
1762                                .preferred
1763                                .max(layout_info_h.min)
1764                                .min(layout_info_h.max);
1765                            window_item.width.set(LogicalLength::new(w));
1766                            w
1767                        };
1768
1769                        let height = {
1770                            let layout_info_v = component
1771                                .as_ref()
1772                                .layout_info(crate::layout::Orientation::Vertical);
1773                            let h = layout_info_v
1774                                .preferred
1775                                .max(layout_info_v.min)
1776                                .min(layout_info_v.max);
1777                            window_item.height.set(LogicalLength::new(h));
1778                            h
1779                        };
1780
1781                        let clip_region = Some(LogicalRect::new(
1782                            LogicalPoint::new(0.0 as crate::Coord, 0.0 as crate::Coord),
1783                            self.window_adapter()
1784                                .size()
1785                                .to_logical(self.scale_factor())
1786                                .to_euclid(),
1787                        ));
1788
1789                        let new_region_clipped = popup::place_popup(
1790                            popup::Placement::Fixed(LogicalRect::new(
1791                                offset,
1792                                crate::lengths::LogicalSize::new(width, height),
1793                            )),
1794                            &clip_region,
1795                        );
1796
1797                        (old_popup_region, new_region_clipped)
1798                    });
1799
1800                self.window_adapter().request_redraw();
1801
1802                // Set new location
1803                *old_location = new_popup_region.origin;
1804
1805                if let Some(adapter) = self.window_adapter_weak.upgrade() {
1806                    if !old_popup_region.is_empty() {
1807                        adapter.renderer().mark_dirty_region(old_popup_region.into());
1808                    }
1809
1810                    if !new_popup_region.is_empty() {
1811                        adapter.renderer().mark_dirty_region(new_popup_region.into());
1812                    }
1813                    adapter.request_redraw();
1814                }
1815            }
1816            PopupWindowLocation::TopLevel(adapter) => {
1817                // The size is already tracked in the windowadapter
1818                let mut new_position: Option<LogicalPosition> = None;
1819                popup.properties_tracker.as_ref().evaluate_as_dependency_root(|| {
1820                    (popup.position_access)(); // Dummy access to track position changes
1821                    new_position = Some(LogicalPosition::from_euclid(offset));
1822                });
1823                if let Some(pos) = new_position {
1824                    adapter.window().set_position(pos);
1825                }
1826            }
1827        }
1828    }
1829
1830    /// Calls the render_components to render the main component and any sub-window components, tracked by a
1831    /// property dependency tracker.
1832    ///
1833    /// The closure also receives a `post_render` callback. The renderer must invoke it
1834    /// once with its `ItemRenderer` after walking the components but before flushing,
1835    /// so the runtime can draw overlays that sit on top of the scene without being part
1836    /// of any item tree.
1837    ///
1838    /// Returns None if no component is set yet.
1839    pub fn draw_contents<T>(
1840        &self,
1841        render_components: impl FnOnce(
1842            &[(ItemTreeWeak, LogicalPoint)],
1843            &dyn Fn(&mut dyn crate::item_rendering::ItemRenderer),
1844        ) -> T,
1845    ) -> Option<T> {
1846        crate::properties::evaluate_no_tracking(|| self.ensure_tree_instantiated());
1847        #[cfg(feature = "shared-parley")]
1848        if let Some(cache) = self.window_adapter().renderer().text_layout_cache() {
1849            cache.begin_frame();
1850        }
1851        let component_weak = ItemTreeRc::downgrade(&self.try_component()?);
1852        let post_render = |renderer: &mut dyn crate::item_rendering::ItemRenderer| {
1853            self.render_drag_image_overlay(renderer);
1854        };
1855        Some(self.pinned_fields.as_ref().project_ref().redraw_tracker.evaluate_as_dependency_root(
1856            || {
1857                if !self
1858                    .active_popups
1859                    .borrow()
1860                    .iter()
1861                    .any(|p| matches!(p.location, PopupWindowLocation::ChildWindow(..)))
1862                {
1863                    render_components(&[(component_weak, LogicalPoint::default())], &post_render)
1864                } else {
1865                    let borrow = self.active_popups.borrow();
1866                    let mut item_trees = Vec::with_capacity(borrow.len() + 1);
1867                    item_trees.push((component_weak, LogicalPoint::default()));
1868                    for popup in borrow.iter() {
1869                        // If the popup is not a real window and does not have its own coordinate system.
1870                        // We have to draw the popup and consider the location for subelements because everything must
1871                        // be rendered relative to the main window position
1872                        if let PopupWindowLocation::ChildWindow(location) = &popup.location {
1873                            item_trees.push((ItemTreeRc::downgrade(&popup.component), *location));
1874                        }
1875                    }
1876                    drop(borrow);
1877                    render_components(&item_trees, &post_render)
1878                }
1879            },
1880        ))
1881    }
1882
1883    /// Draws the source `DragArea`'s `drag-image` under the cursor when a drag is in flight.
1884    /// No-op when no drag is active or the source has no image set.
1885    ///
1886    /// Marks the painted rect dirty for partial renderers, so the next frame clears the area
1887    /// before redrawing — same trick the linuxkms cursor injection uses, no per-frame state needed.
1888    fn render_drag_image_overlay(
1889        &self,
1890        item_renderer: &mut dyn crate::item_rendering::ItemRenderer,
1891    ) {
1892        let state = self.mouse_input_state.take();
1893        let cursor = state.drag_data.as_ref().map(|d| d.event.position);
1894        let source = state.drag_source.as_ref().and_then(|w| w.upgrade());
1895        self.mouse_input_state.set(state);
1896
1897        let (Some(cursor), Some(source)) = (cursor, source) else { return };
1898        let Some(drag_area) = source.downcast::<crate::items::DragArea>() else { return };
1899        let drag_area = drag_area.as_pin_ref();
1900        let image = drag_area.drag_image();
1901        let size = crate::lengths::LogicalSize::from_untyped(image.size().cast());
1902        if size.is_empty() {
1903            return;
1904        }
1905        let cursor = crate::lengths::logical_point_from_api(cursor);
1906        let offset = LogicalVector::new(
1907            drag_area.drag_image_offset_x() as Coord,
1908            drag_area.drag_image_offset_y() as Coord,
1909        );
1910        let top_left = cursor - offset;
1911
1912        item_renderer.save_state();
1913        item_renderer.translate(top_left.to_vector());
1914        item_renderer.draw_image_direct(image);
1915        item_renderer.restore_state();
1916
1917        self.window_adapter().renderer().mark_dirty_region(LogicalRect::new(top_left, size).into());
1918    }
1919
1920    /// Registers the window with the windowing system, in order to render the component's items and react
1921    /// to input events once the event loop spins.
1922    pub fn show(&self) -> Result<(), PlatformError> {
1923        if let Some(component) = self.try_component() {
1924            let was_visible = self.strong_component_ref.replace(Some(component)).is_some();
1925            if !was_visible {
1926                self.context().acquire_keepalive();
1927            }
1928        }
1929
1930        self.ensure_tree_instantiated();
1931        self.update_window_properties();
1932        self.window_adapter().set_visible(true)?;
1933        // Make sure that the window's inner size is in sync with the root window item's
1934        // width/height.
1935        let size = self.window_adapter().size();
1936        let scale_factor = self.scale_factor();
1937        self.set_window_item_geometry(size.to_logical(scale_factor).to_euclid());
1938        let inset = self
1939            .window_adapter()
1940            .internal(crate::InternalToken)
1941            .map(|internal| internal.safe_area_inset())
1942            .unwrap_or_default();
1943        self.set_window_item_safe_area(inset.to_logical(scale_factor));
1944        self.window_adapter().renderer().resize(size).unwrap();
1945        if let Some(hook) = self.context().0.window_shown_hook.borrow_mut().as_mut() {
1946            hook(&self.window_adapter());
1947        }
1948        Ok(())
1949    }
1950
1951    /// De-registers the window with the windowing system.
1952    pub fn hide(&self) -> Result<(), PlatformError> {
1953        let result = self.window_adapter().set_visible(false);
1954        let was_visible = self.strong_component_ref.borrow_mut().take().is_some();
1955        if was_visible {
1956            self.context().release_keepalive();
1957        }
1958        result
1959    }
1960
1961    /// Return whether the platform supports native menu bars
1962    pub fn supports_native_menu_bar(&self) -> bool {
1963        self.window_adapter()
1964            .internal(crate::InternalToken)
1965            .is_some_and(|x| x.supports_native_menu_bar())
1966    }
1967
1968    /// Setup the native menu bar
1969    pub fn setup_menubar(&self, menubar: vtable::VRc<MenuVTable>) {
1970        if let Some(x) = self.window_adapter().internal(crate::InternalToken) {
1971            x.setup_menubar(menubar);
1972        }
1973    }
1974
1975    /// Setup the shortcuts for the menubar
1976    /// Note: We still register the same shortcuts if the native menubar is active.
1977    /// Generally, the native menubar should capture the shortcuts first,
1978    /// but in case it doesn't, the window can still match them manually.
1979    pub fn setup_menubar_shortcuts(&self, menubar: VRc<MenuVTable>) {
1980        *self.menubar.borrow_mut() = Some(VRc::downgrade(&menubar));
1981        let weak = VRc::downgrade(&menubar);
1982        self.pinned_fields.menubar_shortcuts.set_binding(move || {
1983            fn flatten_menu(
1984                root: vtable::VRef<'_, MenuVTable>,
1985                parent: Option<&MenuEntry>,
1986            ) -> SharedVector<MenuEntry> {
1987                let mut menu_entries = Default::default();
1988                root.sub_menu(parent, &mut menu_entries);
1989
1990                let mut result = menu_entries.clone();
1991
1992                for entry in menu_entries {
1993                    result.extend(flatten_menu(root, Some(&entry)));
1994                }
1995                result
1996            }
1997
1998            let Some(menubar) = weak.upgrade() else {
1999                return SharedVector::default();
2000            };
2001            flatten_menu(VRc::borrow(&menubar), None)
2002                .into_iter()
2003                .filter(|entry| entry.enabled && entry.shortcut != Keys::default())
2004                .collect()
2005        });
2006    }
2007    /// Create a new popup window adapter
2008    /// This window adapter can be used on a popup component and shown with show_popup()
2009    pub fn create_child_window_adapter(&self, kind: WindowKind) -> Option<Rc<dyn WindowAdapter>> {
2010        self.window_adapter()
2011            .internal(crate::InternalToken)
2012            .and_then(|s| s.create_child_window_adapter(kind))
2013    }
2014
2015    /// Show a popup at the given position relative to the `parent_item` and returns its ID.
2016    /// The returned ID will always be non-zero.
2017    ///
2018    /// `is_open_setter` keeps the parent component's `PopupWindow::is-open` property in sync with this
2019    /// popup: it is invoked immediately with `true`, and again with `false` when the popup is closed
2020    /// through any path (the `Drop` impl of [`PopupWindow`] handles the `false`). Pass a no-op closure
2021    /// for popups (such as menus) that do not expose `is-open`.
2022    pub fn show_popup(
2023        &self,
2024        popup_componentrc: &ItemTreeRc,
2025        popup_access_position: Box<dyn Fn() -> LogicalPosition>,
2026        close_policy: PopupClosePolicy,
2027        parent_item: &ItemRc,
2028        window_kind: WindowKind,
2029        is_open_setter: Box<dyn Fn(bool)>,
2030    ) -> NonZeroU32 {
2031        // Popups live in their own ItemTree, which was invisible to any
2032        // earlier instantiation pass; materialize it before the layout queries below.
2033        crate::item_tree::ensure_item_tree_instantiated(popup_componentrc);
2034        let position = parent_item.map_to_native_window(
2035            parent_item.geometry().origin + popup_access_position().to_euclid().to_vector(),
2036        );
2037        let popup_component = ItemTreeRc::borrow_pin(popup_componentrc);
2038        let popup_root = popup_component.as_ref().get_item_ref(0);
2039
2040        let (mut w, mut h) = if let Some(window_item) =
2041            ItemRef::downcast_pin::<crate::items::WindowItem>(popup_root)
2042        {
2043            (window_item.width(), window_item.height())
2044        } else {
2045            (LogicalLength::zero(), LogicalLength::zero())
2046        };
2047
2048        let layout_info_h =
2049            popup_component.as_ref().layout_info(crate::layout::Orientation::Horizontal);
2050        let layout_info_v =
2051            popup_component.as_ref().layout_info(crate::layout::Orientation::Vertical);
2052
2053        if w <= LogicalLength::zero() {
2054            w = LogicalLength::new(layout_info_h.preferred);
2055        }
2056        if h <= LogicalLength::zero() {
2057            h = LogicalLength::new(layout_info_v.preferred);
2058        }
2059        w = w.max(LogicalLength::new(layout_info_h.min)).min(LogicalLength::new(layout_info_h.max));
2060        h = h.max(LogicalLength::new(layout_info_v.min)).min(LogicalLength::new(layout_info_v.max));
2061
2062        let size = crate::lengths::LogicalSize::from_lengths(w, h);
2063
2064        if let Some(window_item) = ItemRef::downcast_pin(popup_root) {
2065            let width_property =
2066                crate::items::WindowItem::FIELD_OFFSETS.width().apply_pin(window_item);
2067            let height_property =
2068                crate::items::WindowItem::FIELD_OFFSETS.height().apply_pin(window_item);
2069            width_property.set(size.width_length());
2070            height_property.set(size.height_length());
2071        };
2072
2073        let popup_id = self.next_popup_id.get();
2074        self.next_popup_id.set(popup_id.checked_add(1).unwrap());
2075        let parent_window_adapter_weak = Rc::downgrade(&self.window_adapter());
2076
2077        // Close active popups before creating a new one.
2078        let siblings: Vec<_> = self
2079            .active_popups
2080            .borrow()
2081            .iter()
2082            .filter(|p| p.parent_item == parent_item.downgrade())
2083            .map(|p| p.popup_id)
2084            .collect();
2085
2086        for sibling in siblings {
2087            self.close_popup(sibling);
2088        }
2089
2090        let root_of = |mut item_tree: ItemTreeRc| loop {
2091            if ItemRc::new_root(item_tree.clone()).downcast::<crate::items::WindowItem>().is_some()
2092            {
2093                return item_tree;
2094            }
2095            let mut r = crate::item_tree::ItemWeak::default();
2096            ItemTreeRc::borrow_pin(&item_tree).as_ref().parent_node(&mut r);
2097            match r.upgrade() {
2098                None => return item_tree,
2099                Some(x) => item_tree = x.item_tree().clone(),
2100            }
2101        };
2102
2103        let parent_root_item_tree = root_of(parent_item.item_tree().clone());
2104        let parent_window_adapter = if let Some(parent_popup) = self
2105            .active_popups
2106            .borrow()
2107            .iter()
2108            .find(|p| ItemTreeRc::ptr_eq(&p.component, &parent_root_item_tree))
2109        {
2110            // Popup in a popup
2111            match &parent_popup.location {
2112                PopupWindowLocation::TopLevel(wa) => wa.clone(),
2113                PopupWindowLocation::ChildWindow(_) => self.window_adapter(),
2114            }
2115        } else {
2116            self.window_adapter()
2117        };
2118
2119        let popup_window_adapter = {
2120            let mut popup_window_adapter = None;
2121            ItemTreeRc::borrow_pin(popup_componentrc)
2122                .as_ref()
2123                .window_adapter(false, &mut popup_window_adapter);
2124            popup_window_adapter.expect("It must be there because we set the global")
2125        };
2126
2127        // If the window adapter of the popup window and the parent window are equal, create a ChildWindow
2128        // because we weren't able to create a dedicated popup adapter (for example if the backend does not support it).
2129        let (location, properties_tracker) =
2130            if Rc::ptr_eq(&parent_window_adapter, &popup_window_adapter) {
2131                // Tooltips may extend past the window (e.g. above/left of the anchor); do not clamp.
2132                let clip_region = Some(LogicalRect::new(
2133                    LogicalPoint::new(0.0 as crate::Coord, 0.0 as crate::Coord),
2134                    self.window_adapter().size().to_logical(self.scale_factor()).to_euclid(),
2135                ));
2136                let rect = popup::place_popup(
2137                    popup::Placement::Fixed(LogicalRect::new(position, size)),
2138                    &clip_region,
2139                );
2140                self.window_adapter().request_redraw();
2141                (
2142                    PopupWindowLocation::ChildWindow(rect.origin),
2143                    Box::pin(PropertyTracker::new_with_dirty_handler(
2144                        PopupWindowPropertiesTracker {
2145                            parent_window_adapter_weak: parent_window_adapter_weak.clone(),
2146                            popup_id,
2147                        },
2148                    )),
2149                )
2150            } else {
2151                let popup_window = popup_window_adapter.window();
2152                WindowInner::from_pub(popup_window).set_component(popup_componentrc);
2153                popup_window.set_position(LogicalPosition::from_euclid(position));
2154                popup_window.set_size(WindowSize::Logical(LogicalSize::from_euclid(size)));
2155
2156                popup_window_adapter.set_visible(true).expect("unable to show popup window");
2157                (
2158                    PopupWindowLocation::TopLevel(popup_window_adapter),
2159                    Box::pin(PropertyTracker::new_with_dirty_handler(
2160                        PopupWindowPropertiesTracker {
2161                            parent_window_adapter_weak: parent_window_adapter_weak.clone(),
2162                            popup_id,
2163                        },
2164                    )),
2165                )
2166            };
2167
2168        let focus_item = if matches!(window_kind, WindowKind::ToolTip) {
2169            Default::default()
2170        } else {
2171            self.take_focus_item(&FocusEvent::FocusOut(FocusReason::PopupActivation))
2172                .map(|item| item.downgrade())
2173                .unwrap_or_default()
2174        };
2175
2176        // Reflect the freshly shown popup in the parent's `is-open` property; the matching `false` is
2177        // emitted when the stored `PopupWindow` is dropped (see its `Drop` impl), which every close
2178        // path funnels through. Called before the popup is stored so we do not hold a borrow on
2179        // `active_popups` while running user-provided code.
2180        is_open_setter(true);
2181
2182        self.active_popups.borrow_mut().push(PopupWindow {
2183            popup_id,
2184            location,
2185            component: popup_componentrc.clone(),
2186            close_policy,
2187            focus_item_in_parent: focus_item,
2188            parent_item: parent_item.downgrade(),
2189            window_kind,
2190            position_access: popup_access_position,
2191            is_open_setter,
2192            properties_tracker,
2193        });
2194
2195        self.update_popup_properties(popup_id);
2196
2197        popup_id
2198    }
2199
2200    /// Attempt to show a native popup menu
2201    ///
2202    /// context_menu_item is an instance of a ContextMenu
2203    ///
2204    /// Returns false if the native platform doesn't support it
2205    pub fn show_native_popup_menu(
2206        &self,
2207        context_menu_item: vtable::VRc<MenuVTable>,
2208        position: LogicalPosition,
2209        parent_item: &ItemRc,
2210    ) -> bool {
2211        if let Some(x) = self.window_adapter().internal(crate::InternalToken) {
2212            let position = parent_item.map_to_native_window(
2213                parent_item.geometry().origin + position.to_euclid().to_vector(),
2214            );
2215            let position = crate::lengths::logical_position_to_api(position);
2216            x.show_native_popup_menu(context_menu_item, position)
2217        } else {
2218            false
2219        }
2220    }
2221
2222    // Close the popup associated with the given popup window.
2223    // The parent's `is-open` property is reset to false when `current_popup` is dropped (see the
2224    // `Drop` impl for `PopupWindow`), which every close path eventually does.
2225    fn close_popup_impl(&self, current_popup: &PopupWindow) {
2226        match &current_popup.location {
2227            PopupWindowLocation::ChildWindow(offset) => {
2228                // Refresh the area that was previously covered by the popup.
2229                let popup_region = crate::properties::evaluate_no_tracking(|| {
2230                    let popup_component = ItemTreeRc::borrow_pin(&current_popup.component);
2231                    popup_component.as_ref().item_geometry(0)
2232                })
2233                .translate(offset.to_vector());
2234
2235                if !popup_region.is_empty() {
2236                    let window_adapter = self.window_adapter();
2237                    window_adapter.renderer().mark_dirty_region(popup_region.into());
2238                    window_adapter.request_redraw();
2239                }
2240            }
2241            PopupWindowLocation::TopLevel(adapter) => {
2242                let _ = adapter.set_visible(false);
2243            }
2244        }
2245        if let Some(focus) = current_popup.focus_item_in_parent.upgrade() {
2246            self.set_focus_item(&focus, true, FocusReason::PopupActivation);
2247        }
2248    }
2249
2250    /// Removes the popup matching the given ID.
2251    pub fn close_popup(&self, popup_id: NonZeroU32) {
2252        let mut active_popups = self.active_popups.borrow_mut();
2253        let maybe_index = active_popups.iter().position(|popup| popup.popup_id == popup_id);
2254
2255        if let Some(popup_index) = maybe_index {
2256            let p = active_popups.remove(popup_index);
2257            drop(active_popups);
2258            self.close_popup_impl(&p);
2259            if matches!(p.window_kind, WindowKind::Menu) {
2260                // close all sub-menus
2261                while self
2262                    .active_popups
2263                    .borrow()
2264                    .get(popup_index)
2265                    .is_some_and(|p| matches!(p.window_kind, WindowKind::Menu))
2266                {
2267                    let p = self.active_popups.borrow_mut().remove(popup_index);
2268                    self.close_popup_impl(&p);
2269                }
2270            }
2271        }
2272    }
2273
2274    /// Close all active popups.
2275    pub fn close_all_popups(&self) {
2276        for popup in self.active_popups.take() {
2277            self.close_popup_impl(&popup);
2278        }
2279    }
2280
2281    /// Close the top-most popup.
2282    pub fn close_top_popup(&self) {
2283        let popup = self.active_popups.borrow_mut().pop();
2284        if let Some(popup) = popup {
2285            self.close_popup_impl(&popup);
2286        }
2287    }
2288
2289    /// Returns the scale factor set on the window, as provided by the windowing system.
2290    pub fn scale_factor(&self) -> f32 {
2291        self.pinned_fields.as_ref().project_ref().scale_factor.get()
2292    }
2293
2294    /// Sets the scale factor for the window. This is set by the backend or for testing.
2295    pub(crate) fn set_scale_factor(&self, factor: f32) {
2296        if !self.pinned_fields.scale_factor.is_constant() {
2297            self.pinned_fields.scale_factor.set(factor)
2298        }
2299    }
2300
2301    /// Sets the scale factor for the window.
2302    /// From that point on, the scale factor is constant and cannot be changed anymore.
2303    pub fn set_const_scale_factor(&self, factor: f32) {
2304        if !self.pinned_fields.scale_factor.is_constant() {
2305            self.pinned_fields.scale_factor.set(factor);
2306            self.pinned_fields.scale_factor.set_constant();
2307        }
2308    }
2309
2310    /// Reads the global property `TextInputInterface.text-input-focused`
2311    pub fn text_input_focused(&self) -> bool {
2312        self.pinned_fields.as_ref().project_ref().text_input_focused.get()
2313    }
2314
2315    /// Sets the global property `TextInputInterface.text-input-focused`
2316    pub fn set_text_input_focused(&self, value: bool) {
2317        if !value && let Some(window_adapter) = self.window_adapter().internal(crate::InternalToken)
2318        {
2319            window_adapter.input_method_request(InputMethodRequest::Disable);
2320        }
2321        self.pinned_fields.text_input_focused.set(value)
2322    }
2323
2324    /// Returns true if the window is visible
2325    pub fn is_visible(&self) -> bool {
2326        self.strong_component_ref.borrow().is_some()
2327    }
2328
2329    /// Returns the window item that is the first item in the component. When Some()
2330    /// is returned, it's guaranteed to be safe to downcast to `WindowItem`.
2331    pub fn window_item_rc(&self) -> Option<ItemRc> {
2332        self.try_component().and_then(|component_rc| {
2333            let item_rc = ItemRc::new_root(component_rc);
2334            if item_rc.downcast::<crate::items::WindowItem>().is_some() {
2335                Some(item_rc)
2336            } else {
2337                None
2338            }
2339        })
2340    }
2341
2342    /// Returns the window item that is the first item in the component.
2343    pub fn window_item(&self) -> Option<VRcMapped<ItemTreeVTable, crate::items::WindowItem>> {
2344        self.try_component().and_then(|component_rc| {
2345            ItemRc::new_root(component_rc).downcast::<crate::items::WindowItem>()
2346        })
2347    }
2348
2349    /// Sets the size of the window item. This method is typically called in response to receiving a
2350    /// window resize event from the windowing system.
2351    pub(crate) fn set_window_item_geometry(&self, size: crate::lengths::LogicalSize) {
2352        if let Some(component_rc) = self.try_component() {
2353            let component = ItemTreeRc::borrow_pin(&component_rc);
2354            let root_item = component.as_ref().get_item_ref(0);
2355            if let Some(window_item) = ItemRef::downcast_pin::<crate::items::WindowItem>(root_item)
2356            {
2357                window_item.width.set(size.width_length());
2358                window_item.height.set(size.height_length());
2359            }
2360        }
2361    }
2362
2363    /// The safe area of the window has changed.
2364    pub fn set_window_item_safe_area(&self, inset: crate::lengths::LogicalEdges) {
2365        if let Some(component_rc) = self.try_component() {
2366            let component = ItemTreeRc::borrow_pin(&component_rc);
2367            let root_item = component.as_ref().get_item_ref(0);
2368            if let Some(window_item) = ItemRef::downcast_pin::<crate::items::WindowItem>(root_item)
2369            {
2370                window_item.safe_area_insets.set(inset);
2371            }
2372        }
2373    }
2374
2375    pub(crate) fn set_window_item_virtual_keyboard(
2376        &self,
2377        origin: crate::lengths::LogicalPoint,
2378        size: crate::lengths::LogicalSize,
2379    ) {
2380        let Some(component_rc) = self.try_component() else {
2381            return;
2382        };
2383        let component = ItemTreeRc::borrow_pin(&component_rc);
2384        let root_item = component.as_ref().get_item_ref(0);
2385        let Some(window_item) = ItemRef::downcast_pin::<crate::items::WindowItem>(root_item) else {
2386            return;
2387        };
2388        window_item.virtual_keyboard_position.set(origin);
2389        window_item.virtual_keyboard_size.set(size);
2390        if let Some(focus_item) = self.focus_item.borrow().upgrade() {
2391            focus_item.try_scroll_into_visible();
2392        }
2393    }
2394
2395    // Get geometry of the virtual keyboard if available
2396    pub(crate) fn window_item_virtual_keyboard(
2397        &self,
2398    ) -> Option<(crate::lengths::LogicalPoint, crate::lengths::LogicalSize)> {
2399        let component_rc = self.try_component()?;
2400        let component = ItemTreeRc::borrow_pin(&component_rc);
2401        let root_item = component.as_ref().get_item_ref(0);
2402        let window_item = ItemRef::downcast_pin::<crate::items::WindowItem>(root_item)?;
2403        let keyboard_size = window_item.virtual_keyboard_size();
2404        if keyboard_size.width == 0. as Coord || keyboard_size.height == 0. as Coord {
2405            None
2406        } else {
2407            Some((window_item.virtual_keyboard_position(), keyboard_size))
2408        }
2409    }
2410
2411    /// Sets the close_requested callback. The callback will be run when the user tries to close a window.
2412    pub fn on_close_requested(&self, mut callback: impl FnMut() -> CloseRequestResponse + 'static) {
2413        self.close_requested.set_handler(move |()| callback());
2414    }
2415
2416    /// Runs the close_requested callback.
2417    /// If the callback returns KeepWindowShown, this function returns false. That should prevent the Window from closing.
2418    /// Otherwise it returns true, which allows the Window to hide.
2419    pub fn request_close(&self) -> bool {
2420        match self.close_requested.call(&()) {
2421            CloseRequestResponse::HideWindow => true,
2422            CloseRequestResponse::KeepWindowShown => false,
2423        }
2424    }
2425
2426    /// Returns if the window is currently in fullscreen mode
2427    pub fn is_fullscreen(&self) -> bool {
2428        if let Some(window_item) = self.window_item() {
2429            window_item.as_pin_ref().full_screen()
2430        } else {
2431            false
2432        }
2433    }
2434
2435    /// Set or unset the window to display fullscreen.
2436    pub fn set_fullscreen(&self, enabled: bool) {
2437        if let Some(window_item) = self.window_item() {
2438            window_item.as_pin_ref().full_screen.set(enabled);
2439            self.update_window_properties()
2440        }
2441    }
2442
2443    /// Returns if the window is currently maximized
2444    pub fn is_maximized(&self) -> bool {
2445        self.window_item().is_some_and(|window_item| window_item.as_pin_ref().maximized())
2446    }
2447
2448    /// Set the window as maximized or unmaximized
2449    pub fn set_maximized(&self, maximized: bool) {
2450        if let Some(window_item) = self.window_item() {
2451            window_item.as_pin_ref().maximized.set(maximized);
2452            self.update_window_properties()
2453        }
2454    }
2455
2456    /// Returns if the window is currently minimized
2457    pub fn is_minimized(&self) -> bool {
2458        self.window_item().is_some_and(|window_item| window_item.as_pin_ref().minimized())
2459    }
2460
2461    /// Set the window as minimized or unminimized
2462    pub fn set_minimized(&self, minimized: bool) {
2463        if let Some(window_item) = self.window_item() {
2464            window_item.as_pin_ref().minimized.set(minimized);
2465            self.update_window_properties()
2466        }
2467    }
2468
2469    /// Returns the (context global) xdg app id for use with wayland and x11.
2470    pub fn xdg_app_id(&self) -> Option<SharedString> {
2471        self.context().xdg_app_id()
2472    }
2473
2474    /// Returns the upgraded window adapter
2475    pub fn window_adapter(&self) -> Rc<dyn WindowAdapter> {
2476        self.window_adapter_weak.upgrade().unwrap()
2477    }
2478
2479    /// Private access to the WindowInner for a given window.
2480    pub fn from_pub(window: &crate::api::Window) -> &Self {
2481        &window.0
2482    }
2483
2484    /// Provides access to the Windows' Slint context.
2485    pub fn context(&self) -> &crate::SlintContext {
2486        self.ctx
2487            .get_or_init(|| crate::context::GLOBAL_CONTEXT.with(|ctx| ctx.get().unwrap().clone()))
2488    }
2489
2490    /// Like [`Self::context`], but returns `None` instead of panicking when no context is
2491    /// available yet.
2492    pub fn try_context(&self) -> Option<&crate::SlintContext> {
2493        if self.ctx.get().is_none()
2494            && let Some(ctx) = crate::context::GLOBAL_CONTEXT.with(|ctx| ctx.get().cloned())
2495        {
2496            let _ = self.ctx.set(ctx);
2497        }
2498        self.ctx.get()
2499    }
2500
2501    /// Set the SlintContext.
2502    /// This needs to be called once before any other functions that would use the context.
2503    pub fn set_context(&self, ctx: crate::SlintContext) {
2504        self.ctx.set(ctx).map_err(|_| ()).expect("context shouldn't have been set before")
2505    }
2506}
2507
2508/// Internal alias for `Rc<dyn WindowAdapter>`.
2509pub type WindowAdapterRc = Rc<dyn WindowAdapter>;
2510
2511/// Resolve the [`crate::SlintContext`] associated with a component root by
2512/// asking it for (or creating) its window adapter and reading the context off
2513/// the resulting window. Returns `None` only when no adapter can be produced.
2514pub fn context_for_root(root: &ItemTreeRc) -> Option<crate::SlintContext> {
2515    let comp_ref_pin = vtable::VRc::borrow_pin(root);
2516    let mut adapter = None;
2517    comp_ref_pin.as_ref().window_adapter(true, &mut adapter);
2518    adapter.map(|a| WindowInner::from_pub(a.window()).context().clone())
2519}
2520
2521/// Runtime entry point for `BuiltinFunction::AccentColor`. Returns the accent color
2522/// from the component's [`crate::SlintContext`] reached via its window adapter, or
2523/// transparent if none is associated.
2524pub fn accent_color(root: &crate::item_tree::ItemTreeRc) -> crate::graphics::Color {
2525    let comp_ref_pin = vtable::VRc::borrow_pin(root);
2526    let mut adapter = None;
2527    comp_ref_pin.as_ref().window_adapter(true, &mut adapter);
2528    adapter.map_or(crate::graphics::Color::default(), |a| {
2529        WindowInner::from_pub(a.window()).context().accent_color()
2530    })
2531}
2532
2533/// This module contains the functions needed to interface with the event loop and window traits
2534/// from outside the Rust language.
2535#[cfg(feature = "ffi")]
2536pub mod ffi {
2537    #![allow(unsafe_code)]
2538    #![allow(clippy::missing_safety_doc)]
2539    #![allow(missing_docs)]
2540
2541    use super::*;
2542    #[cfg(feature = "std")]
2543    use crate::SharedVector;
2544    use crate::api::{RenderingNotifier, RenderingState, SetRenderingNotifierError};
2545    use crate::graphics::IntSize;
2546    #[cfg(feature = "std")]
2547    use crate::graphics::Rgba8Pixel;
2548    use crate::graphics::Size;
2549    use crate::items::WindowItem;
2550    use core::ffi::c_void;
2551
2552    /// This enum describes a low-level access to specific graphics APIs used
2553    /// by the renderer.
2554    #[repr(u8)]
2555    pub enum GraphicsAPI {
2556        /// The rendering is done using OpenGL.
2557        NativeOpenGL,
2558        /// The rendering is done using APIs inaccessible from C++, such as WGPU.
2559        Inaccessible,
2560    }
2561
2562    struct WithUserData<T> {
2563        callback: T,
2564        drop_user_data: extern "C" fn(*mut c_void),
2565        user_data: *mut c_void,
2566    }
2567
2568    impl<T> Drop for WithUserData<T> {
2569        fn drop(&mut self) {
2570            (self.drop_user_data)(self.user_data)
2571        }
2572    }
2573
2574    impl WithUserData<extern "C" fn(user_data: *mut c_void, pos: &mut LogicalPosition)> {
2575        fn call(&self) -> LogicalPosition {
2576            let mut logical_position = LogicalPosition::default();
2577            (self.callback)(self.user_data, &mut logical_position);
2578            logical_position
2579        }
2580    }
2581
2582    impl WithUserData<extern "C" fn(user_data: *mut c_void) -> CloseRequestResponse> {
2583        fn call(&self) -> CloseRequestResponse {
2584            (self.callback)(self.user_data)
2585        }
2586    }
2587
2588    impl WithUserData<extern "C" fn(user_data: *mut c_void, is_open: bool)> {
2589        fn call(&self, is_open: bool) {
2590            (self.callback)(self.user_data, is_open)
2591        }
2592    }
2593
2594    /// Same layout as WindowAdapterRc
2595    #[repr(C)]
2596    pub struct WindowAdapterRcOpaque(*const c_void, *const c_void);
2597
2598    /// The title a window shows when the application doesn't set one
2599    #[unsafe(no_mangle)]
2600    pub extern "C" fn slint_default_window_title(out: &mut SharedString) {
2601        *out = super::default_window_title();
2602    }
2603
2604    /// Releases the reference to the windowrc held by handle.
2605    #[unsafe(no_mangle)]
2606    pub unsafe extern "C" fn slint_windowrc_drop(handle: *mut WindowAdapterRcOpaque) {
2607        unsafe {
2608            assert_eq!(
2609                core::mem::size_of::<Rc<dyn WindowAdapter>>(),
2610                core::mem::size_of::<WindowAdapterRcOpaque>()
2611            );
2612            assert_eq!(
2613                core::mem::size_of::<Option<Rc<dyn WindowAdapter>>>(),
2614                core::mem::size_of::<WindowAdapterRcOpaque>()
2615            );
2616            drop(core::ptr::read(handle as *mut Option<Rc<dyn WindowAdapter>>));
2617        }
2618    }
2619
2620    /// Releases the reference to the component window held by handle.
2621    #[unsafe(no_mangle)]
2622    pub unsafe extern "C" fn slint_windowrc_clone(
2623        source: *const WindowAdapterRcOpaque,
2624        target: *mut WindowAdapterRcOpaque,
2625    ) {
2626        unsafe {
2627            assert_eq!(
2628                core::mem::size_of::<Rc<dyn WindowAdapter>>(),
2629                core::mem::size_of::<WindowAdapterRcOpaque>()
2630            );
2631            let window = &*(source as *const Rc<dyn WindowAdapter>);
2632            core::ptr::write(target as *mut Rc<dyn WindowAdapter>, window.clone());
2633        }
2634    }
2635
2636    /// Ensure repeaters, conditionals and component containers are instantiated.
2637    #[unsafe(no_mangle)]
2638    pub unsafe extern "C" fn slint_windowrc_ensure_tree_instantiated(
2639        handle: *const WindowAdapterRcOpaque,
2640    ) {
2641        unsafe {
2642            let window_adapter = &*(handle as *const Rc<dyn WindowAdapter>);
2643            WindowInner::from_pub(window_adapter.window()).ensure_tree_instantiated();
2644        }
2645    }
2646
2647    /// Spins an event loop and renders the items of the provided component in this window.
2648    #[unsafe(no_mangle)]
2649    pub unsafe extern "C" fn slint_windowrc_show(handle: *const WindowAdapterRcOpaque) {
2650        unsafe {
2651            let window_adapter = &*(handle as *const Rc<dyn WindowAdapter>);
2652
2653            window_adapter.window().show().unwrap();
2654        }
2655    }
2656
2657    /// Spins an event loop and renders the items of the provided component in this window.
2658    #[unsafe(no_mangle)]
2659    pub unsafe extern "C" fn slint_windowrc_hide(handle: *const WindowAdapterRcOpaque) {
2660        unsafe {
2661            let window_adapter = &*(handle as *const Rc<dyn WindowAdapter>);
2662            window_adapter.window().hide().unwrap();
2663        }
2664    }
2665
2666    /// Returns the visibility state of the window. This function can return false even if you previously called show()
2667    /// on it, for example if the user minimized the window.
2668    #[unsafe(no_mangle)]
2669    pub unsafe extern "C" fn slint_windowrc_is_visible(
2670        handle: *const WindowAdapterRcOpaque,
2671    ) -> bool {
2672        unsafe {
2673            let window = &*(handle as *const Rc<dyn WindowAdapter>);
2674            window.window().is_visible()
2675        }
2676    }
2677
2678    /// Returns the window scale factor.
2679    #[unsafe(no_mangle)]
2680    pub unsafe extern "C" fn slint_windowrc_get_scale_factor(
2681        handle: *const WindowAdapterRcOpaque,
2682    ) -> f32 {
2683        unsafe {
2684            assert_eq!(
2685                core::mem::size_of::<Rc<dyn WindowAdapter>>(),
2686                core::mem::size_of::<WindowAdapterRcOpaque>()
2687            );
2688            let window_adapter = &*(handle as *const Rc<dyn WindowAdapter>);
2689            WindowInner::from_pub(window_adapter.window()).scale_factor()
2690        }
2691    }
2692
2693    /// Sets the window scale factor, merely for testing purposes.
2694    #[unsafe(no_mangle)]
2695    pub unsafe extern "C" fn slint_windowrc_set_const_scale_factor(
2696        handle: *const WindowAdapterRcOpaque,
2697        value: f32,
2698    ) {
2699        unsafe {
2700            let window_adapter = &*(handle as *const Rc<dyn WindowAdapter>);
2701            WindowInner::from_pub(window_adapter.window()).set_const_scale_factor(value)
2702        }
2703    }
2704
2705    /// Returns the text-input-focused property value.
2706    #[unsafe(no_mangle)]
2707    pub unsafe extern "C" fn slint_windowrc_get_text_input_focused(
2708        handle: *const WindowAdapterRcOpaque,
2709    ) -> bool {
2710        unsafe {
2711            assert_eq!(
2712                core::mem::size_of::<Rc<dyn WindowAdapter>>(),
2713                core::mem::size_of::<WindowAdapterRcOpaque>()
2714            );
2715            let window_adapter = &*(handle as *const Rc<dyn WindowAdapter>);
2716            WindowInner::from_pub(window_adapter.window()).text_input_focused()
2717        }
2718    }
2719
2720    /// Set the text-input-focused property.
2721    #[unsafe(no_mangle)]
2722    pub unsafe extern "C" fn slint_windowrc_set_text_input_focused(
2723        handle: *const WindowAdapterRcOpaque,
2724        value: bool,
2725    ) {
2726        unsafe {
2727            let window_adapter = &*(handle as *const Rc<dyn WindowAdapter>);
2728            WindowInner::from_pub(window_adapter.window()).set_text_input_focused(value)
2729        }
2730    }
2731
2732    /// Sets the focus item.
2733    #[unsafe(no_mangle)]
2734    pub unsafe extern "C" fn slint_windowrc_set_focus_item(
2735        handle: *const WindowAdapterRcOpaque,
2736        focus_item: &ItemRc,
2737        set_focus: bool,
2738        reason: FocusReason,
2739    ) {
2740        unsafe {
2741            let window_adapter = &*(handle as *const Rc<dyn WindowAdapter>);
2742            WindowInner::from_pub(window_adapter.window())
2743                .set_focus_item(focus_item, set_focus, reason)
2744        }
2745    }
2746
2747    /// Associates the window with the given component.
2748    #[unsafe(no_mangle)]
2749    pub unsafe extern "C" fn slint_windowrc_set_component(
2750        handle: *const WindowAdapterRcOpaque,
2751        component: &ItemTreeRc,
2752    ) {
2753        unsafe {
2754            let window_adapter = &*(handle as *const Rc<dyn WindowAdapter>);
2755            WindowInner::from_pub(window_adapter.window()).set_component(component)
2756        }
2757    }
2758
2759    /// Show a popup and return its ID. The returned ID will always be non-zero.
2760    #[unsafe(no_mangle)]
2761    pub unsafe extern "C" fn slint_windowrc_show_popup(
2762        handle: *const WindowAdapterRcOpaque,
2763        popup: &ItemTreeRc,
2764        position: extern "C" fn(user_data: *mut c_void, pos: &mut LogicalPosition),
2765        drop_user_data: extern "C" fn(user_data: *mut c_void),
2766        user_data: *mut c_void,
2767        close_policy: PopupClosePolicy,
2768        parent_item: &ItemRc,
2769        window_kind: WindowKind,
2770        is_open_setter: extern "C" fn(user_data: *mut c_void, is_open: bool),
2771        is_open_setter_drop_user_data: extern "C" fn(user_data: *mut c_void),
2772        is_open_setter_user_data: *mut c_void,
2773    ) -> NonZeroU32 {
2774        unsafe {
2775            let with_user_data = WithUserData { callback: position, drop_user_data, user_data };
2776            let is_open_with_user_data = WithUserData {
2777                callback: is_open_setter,
2778                drop_user_data: is_open_setter_drop_user_data,
2779                user_data: is_open_setter_user_data,
2780            };
2781            let window_adapter = &*(handle as *const Rc<dyn WindowAdapter>);
2782            WindowInner::from_pub(window_adapter.window()).show_popup(
2783                popup,
2784                Box::new(move || with_user_data.call()),
2785                close_policy,
2786                parent_item,
2787                window_kind,
2788                Box::new(move |is_open| is_open_with_user_data.call(is_open)),
2789            )
2790        }
2791    }
2792
2793    /// Create a popup window adapter. Returns true if a new adapter was created and written to result.
2794    /// Returns false if the backend does not support top-level popups.
2795    /// This can be used to set the correct window adapter on a popup component before showing it.
2796    #[unsafe(no_mangle)]
2797    pub unsafe extern "C" fn slint_windowrc_create_child_window_adapter(
2798        handle: *const WindowAdapterRcOpaque,
2799        window_kind: WindowKind,
2800        result: *mut WindowAdapterRcOpaque,
2801    ) -> bool {
2802        unsafe {
2803            let window_adapter = &*(handle as *const Rc<dyn WindowAdapter>);
2804            match WindowInner::from_pub(window_adapter.window())
2805                .create_child_window_adapter(window_kind)
2806            {
2807                Some(wa) => {
2808                    core::ptr::write(result as *mut Rc<dyn WindowAdapter>, wa);
2809                    true
2810                }
2811                None => false,
2812            }
2813        }
2814    }
2815
2816    /// Close the popup by the given ID.
2817    #[unsafe(no_mangle)]
2818    pub unsafe extern "C" fn slint_windowrc_close_popup(
2819        handle: *const WindowAdapterRcOpaque,
2820        popup_id: NonZeroU32,
2821    ) {
2822        unsafe {
2823            let window_adapter = &*(handle as *const Rc<dyn WindowAdapter>);
2824            WindowInner::from_pub(window_adapter.window()).close_popup(popup_id);
2825        }
2826    }
2827
2828    /// C binding to the set_rendering_notifier() API of Window
2829    #[unsafe(no_mangle)]
2830    pub unsafe extern "C" fn slint_windowrc_set_rendering_notifier(
2831        handle: *const WindowAdapterRcOpaque,
2832        callback: extern "C" fn(
2833            rendering_state: RenderingState,
2834            graphics_api: GraphicsAPI,
2835            user_data: *mut c_void,
2836        ),
2837        drop_user_data: extern "C" fn(user_data: *mut c_void),
2838        user_data: *mut c_void,
2839        error: *mut SetRenderingNotifierError,
2840    ) -> bool {
2841        unsafe {
2842            struct CNotifier {
2843                callback: extern "C" fn(
2844                    rendering_state: RenderingState,
2845                    graphics_api: GraphicsAPI,
2846                    user_data: *mut c_void,
2847                ),
2848                drop_user_data: extern "C" fn(*mut c_void),
2849                user_data: *mut c_void,
2850            }
2851
2852            impl Drop for CNotifier {
2853                fn drop(&mut self) {
2854                    (self.drop_user_data)(self.user_data)
2855                }
2856            }
2857
2858            impl RenderingNotifier for CNotifier {
2859                fn notify(
2860                    &mut self,
2861                    state: RenderingState,
2862                    graphics_api: &crate::api::GraphicsAPI,
2863                ) {
2864                    let cpp_graphics_api = match graphics_api {
2865                        crate::api::GraphicsAPI::NativeOpenGL { .. } => GraphicsAPI::NativeOpenGL,
2866                        crate::api::GraphicsAPI::WebGL { .. } => unreachable!(), // We don't support wasm with C++
2867                        #[cfg(feature = "unstable-wgpu-29")]
2868                        crate::api::GraphicsAPI::WGPU29 { .. } => GraphicsAPI::Inaccessible, // There is no C++ API for wgpu (maybe wgpu c in the future?)
2869                        #[cfg(feature = "unstable-wgpu-30")]
2870                        crate::api::GraphicsAPI::WGPU30 { .. } => GraphicsAPI::Inaccessible, // There is no C++ API for wgpu (maybe wgpu c in the future?)
2871                    };
2872                    (self.callback)(state, cpp_graphics_api, self.user_data)
2873                }
2874            }
2875
2876            let window = &*(handle as *const Rc<dyn WindowAdapter>);
2877            match window.renderer().set_rendering_notifier(Box::new(CNotifier {
2878                callback,
2879                drop_user_data,
2880                user_data,
2881            })) {
2882                Ok(()) => true,
2883                Err(err) => {
2884                    *error = err;
2885                    false
2886                }
2887            }
2888        }
2889    }
2890
2891    /// C binding to the on_close_requested() API of Window
2892    #[unsafe(no_mangle)]
2893    pub unsafe extern "C" fn slint_windowrc_on_close_requested(
2894        handle: *const WindowAdapterRcOpaque,
2895        callback: extern "C" fn(user_data: *mut c_void) -> CloseRequestResponse,
2896        drop_user_data: extern "C" fn(user_data: *mut c_void),
2897        user_data: *mut c_void,
2898    ) {
2899        unsafe {
2900            let with_user_data = WithUserData { callback, drop_user_data, user_data };
2901            let window_adapter = &*(handle as *const Rc<dyn WindowAdapter>);
2902            window_adapter.window().on_close_requested(move || with_user_data.call());
2903        }
2904    }
2905
2906    /// This function issues a request to the windowing system to redraw the contents of the window.
2907    #[unsafe(no_mangle)]
2908    pub unsafe extern "C" fn slint_windowrc_request_redraw(handle: *const WindowAdapterRcOpaque) {
2909        unsafe {
2910            let window_adapter = &*(handle as *const Rc<dyn WindowAdapter>);
2911            window_adapter.request_redraw();
2912        }
2913    }
2914
2915    /// Returns the position of the window on the screen, in physical screen coordinates and including
2916    /// a window frame (if present).
2917    #[unsafe(no_mangle)]
2918    pub unsafe extern "C" fn slint_windowrc_position(
2919        handle: *const WindowAdapterRcOpaque,
2920        pos: &mut euclid::default::Point2D<i32>,
2921    ) {
2922        unsafe {
2923            let window_adapter = &*(handle as *const Rc<dyn WindowAdapter>);
2924            *pos = window_adapter.position().unwrap_or_default().to_euclid()
2925        }
2926    }
2927
2928    /// Sets the position of the window on the screen, in physical screen coordinates and including
2929    /// a window frame (if present).
2930    /// Note that on some windowing systems, such as Wayland, this functionality is not available.
2931    #[unsafe(no_mangle)]
2932    pub unsafe extern "C" fn slint_windowrc_set_physical_position(
2933        handle: *const WindowAdapterRcOpaque,
2934        pos: &euclid::default::Point2D<i32>,
2935    ) {
2936        unsafe {
2937            let window_adapter = &*(handle as *const Rc<dyn WindowAdapter>);
2938            window_adapter.set_position(crate::api::PhysicalPosition::new(pos.x, pos.y).into());
2939        }
2940    }
2941
2942    /// Sets the position of the window on the screen, in physical screen coordinates and including
2943    /// a window frame (if present).
2944    /// Note that on some windowing systems, such as Wayland, this functionality is not available.
2945    #[unsafe(no_mangle)]
2946    pub unsafe extern "C" fn slint_windowrc_set_logical_position(
2947        handle: *const WindowAdapterRcOpaque,
2948        pos: &euclid::default::Point2D<f32>,
2949    ) {
2950        unsafe {
2951            let window_adapter = &*(handle as *const Rc<dyn WindowAdapter>);
2952            window_adapter.set_position(LogicalPosition::new(pos.x, pos.y).into());
2953        }
2954    }
2955
2956    /// Returns the size of the window on the screen, in physical screen coordinates and excluding
2957    /// a window frame (if present).
2958    #[unsafe(no_mangle)]
2959    pub unsafe extern "C" fn slint_windowrc_size(handle: *const WindowAdapterRcOpaque) -> IntSize {
2960        unsafe {
2961            let window_adapter = &*(handle as *const Rc<dyn WindowAdapter>);
2962            window_adapter.size().to_euclid().cast()
2963        }
2964    }
2965
2966    /// Resizes the window to the specified size on the screen, in physical pixels and excluding
2967    /// a window frame (if present).
2968    #[unsafe(no_mangle)]
2969    pub unsafe extern "C" fn slint_windowrc_set_physical_size(
2970        handle: *const WindowAdapterRcOpaque,
2971        size: &IntSize,
2972    ) {
2973        unsafe {
2974            let window_adapter = &*(handle as *const Rc<dyn WindowAdapter>);
2975            window_adapter
2976                .window()
2977                .set_size(crate::api::PhysicalSize::new(size.width, size.height));
2978        }
2979    }
2980
2981    /// Resizes the window to the specified size on the screen, in physical pixels and excluding
2982    /// a window frame (if present).
2983    #[unsafe(no_mangle)]
2984    pub unsafe extern "C" fn slint_windowrc_set_logical_size(
2985        handle: *const WindowAdapterRcOpaque,
2986        size: &Size,
2987    ) {
2988        unsafe {
2989            let window_adapter = &*(handle as *const Rc<dyn WindowAdapter>);
2990            window_adapter.window().set_size(crate::api::LogicalSize::new(size.width, size.height));
2991        }
2992    }
2993
2994    /// Return whether the platform supports native menu bars
2995    #[unsafe(no_mangle)]
2996    pub unsafe extern "C" fn slint_windowrc_supports_native_menu_bar(
2997        handle: *const WindowAdapterRcOpaque,
2998    ) -> bool {
2999        unsafe {
3000            let window_adapter = &*(handle as *const Rc<dyn WindowAdapter>);
3001            window_adapter
3002                .internal(crate::InternalToken)
3003                .is_some_and(|x| x.supports_native_menu_bar())
3004        }
3005    }
3006
3007    /// Setup the native menu bar
3008    #[unsafe(no_mangle)]
3009    pub unsafe extern "C" fn slint_windowrc_setup_native_menu_bar(
3010        handle: *const WindowAdapterRcOpaque,
3011        menu_instance: &vtable::VRc<MenuVTable>,
3012    ) {
3013        let window_adapter = unsafe { &*(handle as *const Rc<dyn WindowAdapter>) };
3014        let window = window_adapter.window();
3015        window.0.setup_menubar(vtable::VRc::clone(menu_instance));
3016    }
3017
3018    #[unsafe(no_mangle)]
3019    pub unsafe extern "C" fn slint_windowrc_setup_menu_bar_shortcuts(
3020        handle: *const WindowAdapterRcOpaque,
3021        menu_instance: &vtable::VRc<MenuVTable>,
3022    ) {
3023        let window_adapter = unsafe { &*(handle as *const Rc<dyn WindowAdapter>) };
3024        let window = window_adapter.window();
3025        window.0.setup_menubar_shortcuts(vtable::VRc::clone(menu_instance));
3026    }
3027
3028    /// Show a native context menu
3029    #[unsafe(no_mangle)]
3030    pub unsafe extern "C" fn slint_windowrc_show_native_popup_menu(
3031        handle: *const WindowAdapterRcOpaque,
3032        context_menu: &vtable::VRc<MenuVTable>,
3033        position: LogicalPosition,
3034        parent_item: &ItemRc,
3035    ) -> bool {
3036        unsafe {
3037            let window_adapter = &*(handle as *const Rc<dyn WindowAdapter>);
3038            WindowInner::from_pub(window_adapter.window()).show_native_popup_menu(
3039                context_menu.clone(),
3040                position,
3041                parent_item,
3042            )
3043        }
3044    }
3045
3046    /// Return the default-font-size property of the WindowItem
3047    #[unsafe(no_mangle)]
3048    pub unsafe extern "C" fn slint_windowrc_resolved_default_font_size(
3049        item_tree: &ItemTreeRc,
3050    ) -> f32 {
3051        WindowItem::resolved_default_font_size(item_tree.clone()).get()
3052    }
3053
3054    /// Dispatch a key pressed or release event
3055    #[unsafe(no_mangle)]
3056    pub unsafe extern "C" fn slint_windowrc_dispatch_key_event(
3057        handle: *const WindowAdapterRcOpaque,
3058        event_type: crate::input::KeyEventType,
3059        text: &SharedString,
3060        repeat: bool,
3061    ) {
3062        unsafe {
3063            let window_adapter = &*(handle as *const Rc<dyn WindowAdapter>);
3064            window_adapter.window().dispatch_event(crate::platform::WindowEvent::internal(
3065                InternalKeyEvent {
3066                    event_type,
3067                    key_event: crate::items::KeyEvent {
3068                        text: text.clone(),
3069                        repeat,
3070                        ..Default::default()
3071                    },
3072                    ..Default::default()
3073                },
3074            ));
3075        }
3076    }
3077
3078    /// Dispatch a mouse event
3079    #[unsafe(no_mangle)]
3080    pub unsafe extern "C" fn slint_windowrc_dispatch_pointer_event(
3081        handle: *const WindowAdapterRcOpaque,
3082        event: &crate::input::BackendMouseEvent,
3083    ) {
3084        unsafe {
3085            let window_adapter = &*(handle as *const Rc<dyn WindowAdapter>);
3086            window_adapter.window().dispatch_event(crate::platform::WindowEvent::internal(*event));
3087        }
3088    }
3089
3090    /// Dispatch a window event
3091    #[unsafe(no_mangle)]
3092    pub unsafe extern "C" fn slint_windowrc_dispatch_event(
3093        handle: *const WindowAdapterRcOpaque,
3094        event: &crate::platform::WindowEvent,
3095    ) {
3096        unsafe {
3097            let window_adapter = &*(handle as *const Rc<dyn WindowAdapter>);
3098            window_adapter.window().dispatch_event(event.clone());
3099        }
3100    }
3101
3102    #[unsafe(no_mangle)]
3103    pub unsafe extern "C" fn slint_windowrc_is_fullscreen(
3104        handle: *const WindowAdapterRcOpaque,
3105    ) -> bool {
3106        unsafe {
3107            let window_adapter = &*(handle as *const Rc<dyn WindowAdapter>);
3108            window_adapter.window().is_fullscreen()
3109        }
3110    }
3111
3112    #[unsafe(no_mangle)]
3113    pub unsafe extern "C" fn slint_windowrc_is_minimized(
3114        handle: *const WindowAdapterRcOpaque,
3115    ) -> bool {
3116        unsafe {
3117            let window_adapter = &*(handle as *const Rc<dyn WindowAdapter>);
3118            window_adapter.window().is_minimized()
3119        }
3120    }
3121
3122    #[unsafe(no_mangle)]
3123    pub unsafe extern "C" fn slint_windowrc_is_maximized(
3124        handle: *const WindowAdapterRcOpaque,
3125    ) -> bool {
3126        unsafe {
3127            let window_adapter = &*(handle as *const Rc<dyn WindowAdapter>);
3128            window_adapter.window().is_maximized()
3129        }
3130    }
3131
3132    #[unsafe(no_mangle)]
3133    pub unsafe extern "C" fn slint_windowrc_set_fullscreen(
3134        handle: *const WindowAdapterRcOpaque,
3135        value: bool,
3136    ) {
3137        unsafe {
3138            let window_adapter = &*(handle as *const Rc<dyn WindowAdapter>);
3139            window_adapter.window().set_fullscreen(value)
3140        }
3141    }
3142
3143    #[unsafe(no_mangle)]
3144    pub unsafe extern "C" fn slint_windowrc_set_minimized(
3145        handle: *const WindowAdapterRcOpaque,
3146        value: bool,
3147    ) {
3148        unsafe {
3149            let window_adapter = &*(handle as *const Rc<dyn WindowAdapter>);
3150            window_adapter.window().set_minimized(value)
3151        }
3152    }
3153
3154    #[unsafe(no_mangle)]
3155    pub unsafe extern "C" fn slint_windowrc_set_maximized(
3156        handle: *const WindowAdapterRcOpaque,
3157        value: bool,
3158    ) {
3159        unsafe {
3160            let window_adapter = &*(handle as *const Rc<dyn WindowAdapter>);
3161            window_adapter.window().set_maximized(value)
3162        }
3163    }
3164
3165    /// Takes a snapshot of the window contents and returns it as RGBA8 encoded pixel buffer.
3166    #[cfg(feature = "std")]
3167    #[unsafe(no_mangle)]
3168    pub unsafe extern "C" fn slint_windowrc_take_snapshot(
3169        handle: *const WindowAdapterRcOpaque,
3170        data: &mut SharedVector<Rgba8Pixel>,
3171        width: &mut u32,
3172        height: &mut u32,
3173    ) -> bool {
3174        unsafe {
3175            let window_adapter = &*(handle as *const Rc<dyn WindowAdapter>);
3176            if let Ok(snapshot) = window_adapter.window().take_snapshot() {
3177                *data = snapshot.data.clone();
3178                *width = snapshot.width();
3179                *height = snapshot.height();
3180                true
3181            } else {
3182                false
3183            }
3184        }
3185    }
3186}
3187
3188/// This module contains the functions needed to interface with window handles from outside the Rust language.
3189#[cfg(all(feature = "ffi", feature = "raw-window-handle-06"))]
3190pub mod ffi_window {
3191    #![allow(unsafe_code)]
3192    #![allow(clippy::missing_safety_doc)]
3193
3194    use super::ffi::WindowAdapterRcOpaque;
3195    use super::*;
3196    use std::ffi::c_void;
3197    use std::ptr::null_mut;
3198    use std::sync::Arc;
3199
3200    /// Helper to grab the `HasWindowHandle` for the `WindowAdapter` behind `handle`.
3201    fn has_window_handle(
3202        handle: *const WindowAdapterRcOpaque,
3203    ) -> Option<Arc<dyn raw_window_handle_06::HasWindowHandle>> {
3204        let window_adapter = unsafe { &*(handle as *const Rc<dyn WindowAdapter>) };
3205        let window_adapter = window_adapter.internal(crate::InternalToken)?;
3206        window_adapter.window_handle_06_rc().ok()
3207    }
3208
3209    /// Helper to grab the `HasDisplayHandle` for the `WindowAdapter` behind `handle`.
3210    fn has_display_handle(
3211        handle: *const WindowAdapterRcOpaque,
3212    ) -> Option<Arc<dyn raw_window_handle_06::HasDisplayHandle>> {
3213        let window_adapter = unsafe { &*(handle as *const Rc<dyn WindowAdapter>) };
3214        let window_adapter = window_adapter.internal(crate::InternalToken)?;
3215        window_adapter.display_handle_06_rc().ok()
3216    }
3217
3218    /// Returns the `HWND` associated with this window, or null if it doesn't exist or isn't created yet.
3219    #[unsafe(no_mangle)]
3220    pub unsafe extern "C" fn slint_windowrc_hwnd_win32(
3221        handle: *const WindowAdapterRcOpaque,
3222    ) -> *mut c_void {
3223        use raw_window_handle_06::HasWindowHandle;
3224
3225        if let Some(has_window_handle) = has_window_handle(handle)
3226            && let Ok(window_handle) = has_window_handle.window_handle()
3227            && let raw_window_handle_06::RawWindowHandle::Win32(win32) = window_handle.as_raw()
3228        {
3229            isize::from(win32.hwnd) as *mut c_void
3230        } else {
3231            null_mut()
3232        }
3233    }
3234
3235    /// Returns the `HINSTANCE` associated with this window, or null if it doesn't exist or isn't created yet.
3236    #[unsafe(no_mangle)]
3237    pub unsafe extern "C" fn slint_windowrc_hinstance_win32(
3238        handle: *const WindowAdapterRcOpaque,
3239    ) -> *mut c_void {
3240        use raw_window_handle_06::HasWindowHandle;
3241
3242        if let Some(has_window_handle) = has_window_handle(handle)
3243            && let Ok(window_handle) = has_window_handle.window_handle()
3244            && let raw_window_handle_06::RawWindowHandle::Win32(win32) = window_handle.as_raw()
3245        {
3246            win32
3247                .hinstance
3248                .map(|hinstance| isize::from(hinstance) as *mut c_void)
3249                .unwrap_or_default()
3250        } else {
3251            null_mut()
3252        }
3253    }
3254
3255    /// Returns the `wl_surface` associated with this window, or null if it doesn't exist or isn't created yet.
3256    #[unsafe(no_mangle)]
3257    pub unsafe extern "C" fn slint_windowrc_wlsurface_wayland(
3258        handle: *const WindowAdapterRcOpaque,
3259    ) -> *mut c_void {
3260        use raw_window_handle_06::HasWindowHandle;
3261
3262        if let Some(has_window_handle) = has_window_handle(handle)
3263            && let Ok(window_handle) = has_window_handle.window_handle()
3264            && let raw_window_handle_06::RawWindowHandle::Wayland(wayland) = window_handle.as_raw()
3265        {
3266            wayland.surface.as_ptr()
3267        } else {
3268            null_mut()
3269        }
3270    }
3271
3272    /// Returns the `wl_display` associated with this window, or null if it doesn't exist or isn't created yet.
3273    #[unsafe(no_mangle)]
3274    pub unsafe extern "C" fn slint_windowrc_wldisplay_wayland(
3275        handle: *const WindowAdapterRcOpaque,
3276    ) -> *mut c_void {
3277        use raw_window_handle_06::HasDisplayHandle;
3278
3279        if let Some(has_display_handle) = has_display_handle(handle)
3280            && let Ok(display_handle) = has_display_handle.display_handle()
3281            && let raw_window_handle_06::RawDisplayHandle::Wayland(wayland) =
3282                display_handle.as_raw()
3283        {
3284            wayland.display.as_ptr()
3285        } else {
3286            null_mut()
3287        }
3288    }
3289
3290    /// Returns the `NSView` associated with this window, or null if it doesn't exist or isn't created yet.
3291    #[unsafe(no_mangle)]
3292    pub unsafe extern "C" fn slint_windowrc_nsview_appkit(
3293        handle: *const WindowAdapterRcOpaque,
3294    ) -> *mut c_void {
3295        use raw_window_handle_06::HasWindowHandle;
3296
3297        if let Some(has_window_handle) = has_window_handle(handle)
3298            && let Ok(window_handle) = has_window_handle.window_handle()
3299            && let raw_window_handle_06::RawWindowHandle::AppKit(appkit) = window_handle.as_raw()
3300        {
3301            appkit.ns_view.as_ptr()
3302        } else {
3303            null_mut()
3304        }
3305    }
3306}
3307
3308#[cfg(all(test, feature = "std"))]
3309mod tests {
3310    use super::*;
3311
3312    fn name_of(path: &str) -> Option<SharedString> {
3313        program_name(std::path::Path::new(path))
3314    }
3315
3316    #[test]
3317    fn a_program_name_is_the_bare_file_name() {
3318        assert_eq!(name_of("/usr/bin/gallery").as_deref(), Some("gallery"));
3319        assert_eq!(name_of("gallery").as_deref(), Some("gallery"));
3320        assert_eq!(name_of("./gallery").as_deref(), Some("gallery"));
3321        assert_eq!(name_of("/opt/my-tool.v2").as_deref(), Some("my-tool.v2"));
3322        // Windows spells the suffix either way, and its file names don't care
3323        assert_eq!(name_of("gallery.exe").as_deref(), Some("gallery"));
3324        assert_eq!(name_of("GALLERY.EXE").as_deref(), Some("GALLERY"));
3325        // A leading dot makes it the whole name, not a suffix
3326        assert_eq!(name_of(".exe").as_deref(), Some(".exe"));
3327        assert_eq!(name_of(""), None);
3328        assert_eq!(name_of("/"), None);
3329        assert_eq!(name_of("some/dir/").as_deref(), Some("dir"));
3330    }
3331
3332    #[test]
3333    fn a_host_overrides_the_default_title() {
3334        // The test binary is the running program, so the name is never empty here
3335        assert!(!default_window_title().is_empty());
3336        set_default_window_title("Some Viewer".into());
3337        assert_eq!(default_window_title(), "Some Viewer");
3338        DEFAULT_WINDOW_TITLE.with(|slot| slot.replace(None));
3339        assert_eq!(default_window_title(), application_name());
3340    }
3341}