Skip to main content

ferroui_controls/
window.rs

1use crate::platform::{IWindowBaseImpl, IWindowImpl, IWindowingPlatform, PlatformAllowedWindowActions, PlatformManager, Screen};
2use crate::primitives::{TemplateAppliedEventArgs, TemplatedControl, TemplatedControlImpl, TemplatedControlImplExt};
3use crate::window_icon::WindowIcon;
4use crate::{
5    ContentControlImpl, ControlImpl, SizeToContent, TopLevel, TopLevelImpl, TopLevelImplExt, WindowBase,
6    WindowBaseImpl, WindowCloseReason, WindowClosingEventArgs, WindowDecorations, WindowEdge, WindowResizeReason,
7    WindowResizedEventArgs, WindowStartupLocation, WindowState,
8};
9use ferroui_base::input::{IFocusScope, InputElementImpl, PointerPressedEventArgs};
10use ferroui_base::interactivity::{InteractiveImpl, RoutedEvent, RoutedEventArgs, RoutingStrategies};
11use ferroui_base::layout::{ILayoutManager, Layoutable, LayoutableImpl, LayoutableImplExt, MinMax};
12use ferroui_base::media::{Brushes, IBrush};
13use ferroui_base::reactive::{CompositeDisposable, Disposable, IDisposable, ObservableExt};
14use ferroui_base::styling::ControlTheme;
15use ferroui_base::threading::{Dispatcher, DispatcherTask};
16use ferroui_base::utilities::{HandlerList, MathUtilities};
17use ferroui_base::{
18    ferro_class, ferro_impl_classes, ferro_property, ferro_routed_event, instantiate, BoxedValue, DirectProperty,
19    FerroLocator, FerroObject, FerroObjectExtensions, FerroObjectImpl, FerroObjectImplExt, FerroProperty,
20    FerroPropertyChangedEventArgs, LocatorExtensions, Nullable, PixelPoint, PixelRect, PixelSize, PropertyValue, Ref,
21    Size, StaticType, StyledElementImpl, StyledProperty, StyledPropertyOptions, Thickness, TypeInfo, VisualImpl,
22};
23use std::cell::{Cell, RefCell};
24use std::future::Future;
25use std::pin::Pin;
26use std::rc::Rc;
27use std::task::{Context, Poll, Waker};
28
29/// Describes how the [`Window::closing`] event behaves in the presence of
30/// child windows.
31#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
32#[repr(i32)]
33pub enum WindowClosingBehavior {
34    /// When the owner window is closed, the child windows' closing event
35    /// will be raised, followed by the owner window's closing events. A
36    /// child canceling the close will result in the owner window's close
37    /// being cancelled.
38    #[default]
39    OwnerAndChildWindows = 0,
40
41    /// When the owner window is closed, only the owner window's closing
42    /// event will be raised.
43    OwnerWindowOnly = 1,
44}
45
46/// A top-level window.
47#[repr(C)]
48pub struct Window {
49    base: WindowBase,
50    window_impl: RefCell<Option<Rc<dyn IWindowImpl>>>,
51    children: RefCell<Vec<(Ref<Window>, bool)>>,
52    is_extended_into_window_decorations: Cell<bool>,
53    window_decoration_margin: Cell<Thickness>,
54    off_screen_margin: Cell<Thickness>,
55    can_handle_resized: Cell<bool>,
56    arrange_bounds: Cell<Size>,
57    is_forced_decoration_mode: Cell<bool>,
58    dialog_result: RefCell<Option<BoxedValue>>,
59    shown: Cell<bool>,
60    showing_as_dialog: Cell<bool>,
61    position_was_set: Cell<bool>,
62    was_shown_before: Cell<bool>,
63    modal_subscription: RefCell<Option<Rc<dyn IDisposable>>>,
64    allowed_window_actions: Cell<PlatformAllowedWindowActions>,
65    last_window_state: Cell<WindowState>,
66    closing: HandlerList<dyn Fn(&WindowClosingEventArgs)>,
67    allowed_window_actions_changed: HandlerList<dyn Fn(PlatformAllowedWindowActions)>,
68}
69
70ferro_class! {
71    Window: WindowBase, virtuals WindowImpl: WindowBaseImpl {
72        /// Handles a closing notification from the platform implementation.
73        /// Returns true if closing is cancelled, otherwise false. `reason`
74        /// is the reason the window is closing.
75        fn handle_closing(this, reason: WindowCloseReason) -> bool;
76        /// Raises the `Closing` event.
77        ///
78        /// A type that derives from `Window` may override this member. The
79        /// override must call the base implementation if the `Closing`
80        /// event needs to be raised.
81        fn on_closing(this, e: &WindowClosingEventArgs);
82    }
83}
84ferroui_base::ferro_class_info!(Window { new: Window::new });
85
86ferro_impl_classes!(Window: VisualImpl, InteractiveImpl, ContentControlImpl);
87
88impl ControlImpl for Window {
89    fn on_create_automation_peer(this: &Self) -> Ref<crate::automation::peers::AutomationPeer> {
90        crate::automation::peers::WindowAutomationPeer::new(this).upcast()
91    }
92}
93
94impl IFocusScope for Window {}
95
96impl InputElementImpl for Window {
97    fn is_focus_scope(_this: &Self) -> bool {
98        true
99    }
100}
101
102impl StyledElementImpl for Window {
103    fn style_key_override(_this: &Self) -> &'static TypeInfo {
104        <Window as StaticType>::TYPE
105    }
106}
107
108impl FerroObjectImpl for Window {
109    fn constructed(this: &Self) {
110        Self::parent_constructed(this);
111        this.initialize();
112    }
113
114    fn on_property_changed(this: &Self, change: &FerroPropertyChangedEventArgs<'_>) {
115        Self::parent_on_property_changed(this, change);
116
117        if change.property() == Self::window_decorations_property().as_property() {
118            let (_, typed_new_value) = change.get_old_and_new_value::<WindowDecorations>();
119
120            if let Some(platform_impl) = this.platform_impl() {
121                platform_impl.set_window_decorations(typed_new_value);
122            }
123            this.update_drawn_decorations();
124        } else if change.property() == Self::window_decorations_theme_property().as_property() {
125            this.update_drawn_decorations();
126        } else if change.property() == WindowBase::owner_property().as_property() {
127            let (old_parent, new_parent) = change.get_old_and_new_value::<Option<Ref<WindowBase>>>();
128            let old_parent = old_parent.and_then(|parent| parent.cast::<Window>());
129            let new_parent = new_parent.and_then(|parent| parent.cast::<Window>());
130
131            if let Some(old_parent) = &old_parent {
132                old_parent.remove_child(this);
133            }
134            if let Some(new_parent) = &new_parent {
135                new_parent.add_child(this, this.showing_as_dialog.get());
136            }
137
138            if let Some(platform_impl) = this.platform_impl() {
139                platform_impl.set_parent(new_parent.and_then(|parent| parent.platform_impl()));
140            }
141        } else if change.property() == Self::can_resize_property().as_property() {
142            this.coerce_value(Self::can_maximize_property().as_property());
143        }
144    }
145}
146
147impl TemplatedControlImpl for Window {
148    fn on_apply_template(this: &Self, e: &TemplateAppliedEventArgs) {
149        Self::parent_on_apply_template(this, e);
150        this.enable_visual_layer_manager_layers();
151    }
152}
153
154impl LayoutableImpl for Window {
155    fn measure_override(this: &Self, available_size: Size) -> Size {
156        let size_to_content = this.size_to_content();
157        let mut client_size = this.client_size();
158        if this.is_forced_decoration_mode.get() {
159            client_size = this.platform_impl().map(|platform_impl| platform_impl.client_size()).unwrap_or(client_size);
160            let inset = this.top_level_host().decoration_inset();
161            client_size = Size::new(
162                (client_size.width - inset.left - inset.right).max(0.0),
163                (client_size.height - inset.top - inset.bottom).max(0.0),
164            );
165        }
166        let mut max_auto_size =
167            this.platform_impl().map(|platform_impl| platform_impl.max_auto_size_hint()).unwrap_or(Size::INFINITY);
168        let use_auto_width = size_to_content.contains(SizeToContent::WIDTH);
169        let use_auto_height = size_to_content.contains(SizeToContent::HEIGHT);
170
171        let mut constraint = Size::new(
172            if use_auto_width || available_size.width.is_infinite() { client_size.width } else { available_size.width },
173            if use_auto_height || available_size.height.is_infinite() {
174                client_size.height
175            } else {
176                available_size.height
177            },
178        );
179
180        if this.max_width() > 0.0 && this.max_width() < max_auto_size.width {
181            max_auto_size = max_auto_size.with_width(this.max_width());
182        }
183        if this.max_height() > 0.0 && this.max_height() < max_auto_size.height {
184            max_auto_size = max_auto_size.with_height(this.max_height());
185        }
186
187        if use_auto_width {
188            constraint = constraint.with_width(max_auto_size.width);
189        }
190
191        if use_auto_height {
192            constraint = constraint.with_height(max_auto_size.height);
193        }
194
195        let mut result = Self::parent_measure_override(this, constraint);
196
197        if !use_auto_width {
198            if !available_size.width.is_infinite() {
199                result = result.with_width(available_size.width);
200            } else {
201                result = result.with_width(client_size.width);
202            }
203        }
204
205        if !use_auto_height {
206            if !available_size.height.is_infinite() {
207                result = result.with_height(available_size.height);
208            } else {
209                result = result.with_height(client_size.height);
210            }
211        }
212
213        result
214    }
215}
216
217impl TopLevelImpl for Window {
218    fn handle_closed(this: &Self) {
219        this.shown.set(false);
220
221        Self::parent_handle_closed(this);
222        *this.window_impl.borrow_mut() = None;
223
224        this.raise_event(&RoutedEventArgs::with_event(Self::window_closed_event()));
225
226        this.set_owner(None);
227    }
228
229    fn handle_resized(this: &Self, mut client_size: Size, reason: WindowResizeReason) {
230        // In forced decoration mode, the platform's client size includes the decoration area.
231        // Subtract the decoration inset so the window's client size reflects the usable content area.
232        if this.is_forced_decoration_mode.get() {
233            let inset = this.top_level_host().decoration_inset();
234            client_size = Size::new(
235                (client_size.width - inset.left - inset.right).max(0.0),
236                (client_size.height - inset.top - inset.bottom).max(0.0),
237            );
238        }
239
240        if this.can_handle_resized.get()
241            && (this.client_size() != client_size || this.width().is_nan() || this.height().is_nan())
242        {
243            let mut size_to_content = this.size_to_content();
244
245            // If auto-sizing is enabled, and the resize came from a user resize (or the reason was
246            // unspecified) then turn off auto-resizing for any window dimension that is not equal
247            // to the requested size.
248            if size_to_content != SizeToContent::MANUAL
249                && this.can_resize()
250                && reason == WindowResizeReason::Unspecified
251                || reason == WindowResizeReason::User
252            {
253                if client_size.width != this.client_size().width {
254                    size_to_content &= !SizeToContent::WIDTH;
255                }
256                if client_size.height != this.client_size().height {
257                    size_to_content &= !SizeToContent::HEIGHT;
258                }
259                this.set_size_to_content(size_to_content);
260            }
261
262            this.set_width(client_size.width);
263            this.set_height(client_size.height);
264        }
265
266        Self::parent_handle_resized(this, client_size, reason);
267    }
268}
269
270impl WindowBaseImpl for Window {
271    /// Hides the window but does not close it.
272    fn hide(this: &Self) {
273        let _freeze = this.freeze_visibility_change_handling();
274
275        if !this.shown.get() {
276            return;
277        }
278
279        this.stop_rendering();
280
281        let children = this.children.borrow().clone();
282        for (child, _) in children {
283            child.hide();
284        }
285
286        this.set_owner(None);
287        if let Some(platform_impl) = this.platform_impl() {
288            platform_impl.hide();
289        }
290        this.set_is_visible(false);
291
292        let modal_subscription = this.modal_subscription.borrow().clone();
293        if let Some(modal_subscription) = modal_subscription {
294            modal_subscription.dispose();
295        }
296        this.shown.set(false);
297    }
298
299    /// Shows the window.
300    ///
301    /// # Panics
302    /// Panics when the window has already been closed.
303    fn show(this: &Self) {
304        this.show_core(None, false);
305    }
306
307    fn arrange_set_bounds(this: &Self, size: Size) -> Size {
308        this.arrange_bounds.set(size);
309        if this.can_handle_resized.get() {
310            this.resize_platform_impl(size, WindowResizeReason::Layout);
311        }
312        this.client_size()
313    }
314
315    fn is_visible_changed(this: &Self, e: &FerroPropertyChangedEventArgs<'_>) {
316        if !this.ignore_visibility_changes() {
317            let is_visible = e.get_new_value::<bool>();
318
319            if this.shown.get() != is_visible {
320                if !this.shown.get() {
321                    this.show();
322                } else {
323                    this.hide();
324                }
325            }
326        }
327    }
328}
329
330impl WindowImpl for Window {
331    fn handle_closing(this: &Self, reason: WindowCloseReason) -> bool {
332        if !this.should_cancel_close(&WindowClosingEventArgs::new(reason, false)) {
333            this.close_internal();
334            return false;
335        }
336
337        true
338    }
339
340    fn on_closing(this: &Self, e: &WindowClosingEventArgs) {
341        for (_, handler) in this.closing.snapshot().iter() {
342            handler(e);
343        }
344    }
345}
346
347struct DialogCompletionState {
348    result: Option<Option<BoxedValue>>,
349    waker: Option<Waker>,
350}
351
352/// Completes once, with the result of a dialog, when the dialog closes.
353#[derive(Clone)]
354struct DialogCompletion(Rc<RefCell<DialogCompletionState>>);
355
356impl DialogCompletion {
357    fn new() -> Self {
358        Self(Rc::new(RefCell::new(DialogCompletionState { result: None, waker: None })))
359    }
360
361    fn set_result(&self, result: Option<BoxedValue>) {
362        let waker = {
363            let mut state = self.0.borrow_mut();
364            state.result = Some(result);
365            state.waker.take()
366        };
367        if let Some(waker) = waker {
368            waker.wake();
369        }
370    }
371}
372
373impl Future for DialogCompletion {
374    type Output = Option<BoxedValue>;
375
376    fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
377        let mut state = self.0.borrow_mut();
378        match state.result.take() {
379            Some(result) => Poll::Ready(result),
380            None => {
381                state.waker = Some(cx.waker().clone());
382                Poll::Pending
383            }
384        }
385    }
386}
387
388/// Closes the window when the scope of a close request ends, unless the
389/// close was cancelled.
390struct CloseScope<'a> {
391    window: &'a Window,
392    close: Cell<bool>,
393    ignore_cancel: bool,
394}
395
396impl Drop for CloseScope<'_> {
397    fn drop(&mut self) {
398        if self.close.get() || self.ignore_cancel {
399            self.window.close_internal();
400        }
401    }
402}
403
404ferroui_base::ferro_properties! { impl Window {
405    ferro_property!(
406        /// Defines the `SizeToContent` property.
407        pub fn size_to_content_property() -> StyledProperty<SizeToContent> {
408            FerroProperty::register::<Window, _>("SizeToContent", SizeToContent::MANUAL)
409        }
410    );
411
412    ferro_property!(
413        /// Defines the `ExtendClientAreaToDecorationsHint` property.
414        pub fn extend_client_area_to_decorations_hint_property() -> StyledProperty<bool> {
415            FerroProperty::register::<Window, _>("ExtendClientAreaToDecorationsHint", false)
416        }
417    );
418
419    ferro_property!(
420        /// Defines the `ExtendClientAreaTitleBarHeightHint` property.
421        pub fn extend_client_area_title_bar_height_hint_property() -> StyledProperty<f64> {
422            FerroProperty::register::<Window, _>("ExtendClientAreaTitleBarHeightHint", -1.0)
423        }
424    );
425
426    ferro_property!(
427        /// Defines the `IsExtendedIntoWindowDecorations` property.
428        pub fn is_extended_into_window_decorations_property() -> DirectProperty<Window, bool> {
429            FerroProperty::register_direct::<Window, _>(
430                "IsExtendedIntoWindowDecorations",
431                |o| o.is_extended_into_window_decorations(),
432                None,
433                false,
434            )
435        }
436    );
437
438    ferro_property!(
439        /// Defines the `WindowDecorationMargin` property.
440        pub fn window_decoration_margin_property() -> DirectProperty<Window, Thickness> {
441            FerroProperty::register_direct::<Window, _>(
442                "WindowDecorationMargin",
443                |o| o.window_decoration_margin(),
444                None,
445                Thickness::default(),
446            )
447        }
448    );
449
450    ferro_property!(
451        /// Defines the `OffScreenMargin` property.
452        pub fn off_screen_margin_property() -> DirectProperty<Window, Thickness> {
453            FerroProperty::register_direct::<Window, _>(
454                "OffScreenMargin",
455                |o| o.off_screen_margin(),
456                None,
457                Thickness::default(),
458            )
459        }
460    );
461
462    ferro_property!(
463        /// Defines the `WindowDecorations` property.
464        pub fn window_decorations_property() -> StyledProperty<WindowDecorations> {
465            FerroProperty::register::<Window, _>("WindowDecorations", WindowDecorations::Full)
466        }
467    );
468
469    ferro_property!(
470        /// Defines the `WindowDecorationsTheme` property.
471        pub fn window_decorations_theme_property() -> StyledProperty<Option<Ref<ControlTheme>>> {
472            FerroProperty::register::<Window, _>("WindowDecorationsTheme", None)
473        }
474    );
475
476    ferro_property!(
477        /// Defines the `ShowActivated` property.
478        pub fn show_activated_property() -> StyledProperty<bool> {
479            FerroProperty::register::<Window, _>("ShowActivated", true)
480        }
481    );
482
483    ferro_property!(
484        /// Enables or disables the taskbar icon.
485        pub fn show_in_taskbar_property() -> StyledProperty<bool> {
486            FerroProperty::register::<Window, _>("ShowInTaskbar", true)
487        }
488    );
489
490    ferro_property!(
491        /// Defines the `ClosingBehavior` property.
492        pub fn closing_behavior_property() -> StyledProperty<WindowClosingBehavior> {
493            FerroProperty::register::<Window, _>("ClosingBehavior", WindowClosingBehavior::OwnerAndChildWindows)
494        }
495    );
496
497    ferro_property!(
498        /// Represents the currently effective window state (normal,
499        /// minimized, maximized).
500        pub fn window_state_property() -> DirectProperty<Window, WindowState> {
501            FerroProperty::register_direct::<Window, _>(
502                "WindowState",
503                |o| o.window_state(),
504                Some(|o, v| o.set_window_state(v)),
505                WindowState::Normal,
506            )
507        }
508    );
509
510    ferro_property!(
511        /// Defines the `Title` property.
512        pub fn title_property() -> StyledProperty<Option<String>> {
513            FerroProperty::register::<Window, _>("Title", Some("Window".to_string()))
514        }
515    );
516
517    ferro_property!(
518        /// Defines the `Icon` property.
519        pub fn icon_property() -> StyledProperty<Option<Rc<WindowIcon>>> {
520            FerroProperty::register::<Window, _>("Icon", None)
521        }
522    );
523
524    ferro_property!(
525        /// Defines the `WindowStartupLocation` property.
526        pub fn window_startup_location_property() -> StyledProperty<WindowStartupLocation> {
527            FerroProperty::register::<Window, _>("WindowStartupLocation", WindowStartupLocation::Manual)
528        }
529    );
530
531    ferro_property!(
532        /// Defines the `CanResize` property.
533        pub fn can_resize_property() -> StyledProperty<bool> {
534            FerroProperty::register::<Window, _>("CanResize", true)
535        }
536    );
537
538    ferro_property!(
539        /// Defines the `CanMinimize` property.
540        pub fn can_minimize_property() -> StyledProperty<bool> {
541            FerroProperty::register::<Window, _>("CanMinimize", true)
542        }
543    );
544
545    ferro_property!(
546        /// Defines the `CanMaximize` property.
547        pub fn can_maximize_property() -> StyledProperty<bool> {
548            FerroProperty::register_with::<Window, _>(
549                "CanMaximize",
550                StyledPropertyOptions::new(true).coerce(|target, value| {
551                    value && !target.downcast_ref::<Window>().is_some_and(|window| !window.can_resize())
552                }),
553            )
554        }
555    );
556} }
557
558impl Window {
559    ferro_routed_event!(
560        /// Routed event that can be used for global tracking of window
561        /// destruction.
562        pub fn window_closed_event() -> RoutedEvent<RoutedEventArgs> {
563            RoutedEvent::register::<Window, _>("WindowClosed", RoutingStrategies::DIRECT)
564        }
565    );
566
567    ferro_routed_event!(
568        /// Routed event that can be used for global tracking of opening
569        /// windows.
570        pub fn window_opened_event() -> RoutedEvent<RoutedEventArgs> {
571            RoutedEvent::register::<Window, _>("WindowOpened", RoutingStrategies::DIRECT)
572        }
573    );
574
575    fn static_constructor() {
576        let white: Rc<dyn IBrush> = Brushes::white();
577        TemplatedControl::background_property().override_default_value::<Window>(Some(white));
578        let _ = Self::extend_client_area_title_bar_height_hint_property()
579            .changed()
580            .add_class_handler::<Window>(|w, _| w.on_title_bar_height_hint_changed());
581    }
582
583    /// Field initialisation of a window over the window implementation
584    /// `platform_impl`.
585    pub fn construct(platform_impl: Rc<dyn IWindowImpl>) -> Self {
586        let window_base_impl: Rc<dyn IWindowBaseImpl> = platform_impl.clone();
587        Self {
588            base: WindowBase::construct(window_base_impl),
589            window_impl: RefCell::new(Some(platform_impl)),
590            children: RefCell::new(Vec::new()),
591            is_extended_into_window_decorations: Cell::new(false),
592            window_decoration_margin: Cell::new(Thickness::default()),
593            off_screen_margin: Cell::new(Thickness::default()),
594            can_handle_resized: Cell::new(false),
595            arrange_bounds: Cell::new(Size::default()),
596            is_forced_decoration_mode: Cell::new(false),
597            dialog_result: RefCell::new(None),
598            shown: Cell::new(false),
599            showing_as_dialog: Cell::new(false),
600            position_was_set: Cell::new(false),
601            was_shown_before: Cell::new(false),
602            modal_subscription: RefCell::new(None),
603            allowed_window_actions: Cell::new(PlatformAllowedWindowActions::ALL),
604            last_window_state: Cell::new(WindowState::Normal),
605            closing: HandlerList::new(),
606            allowed_window_actions_changed: HandlerList::new(),
607        }
608    }
609
610    /// Creates a window over a window of the windowing platform.
611    ///
612    /// # Panics
613    /// Panics when no windowing platform is registered.
614    pub fn new() -> Ref<Self> {
615        Self::with_impl(PlatformManager::create_window())
616    }
617
618    /// Creates a window over the window implementation `platform_impl`.
619    pub fn with_impl(platform_impl: Rc<dyn IWindowImpl>) -> Ref<Self> {
620        instantiate(Self::construct(platform_impl))
621    }
622
623    /// The constructor body.
624    fn initialize(&self) {
625        let platform_impl = self.platform_impl().expect("the window has a platform implementation");
626        let weak = self.to_ref().downgrade();
627
628        platform_impl.set_closing(Some(Rc::new({
629            let weak = weak.clone();
630            move |reason| weak.upgrade().is_some_and(|this| this.handle_closing(reason))
631        })));
632        platform_impl.set_got_input_when_disabled(Some(Rc::new({
633            let weak = weak.clone();
634            move || {
635                if let Some(this) = weak.upgrade() {
636                    this.on_got_input_when_disabled();
637                }
638            }
639        })));
640        platform_impl.set_window_state_changed(Some(Rc::new({
641            let weak = weak.clone();
642            move |state| {
643                if let Some(this) = weak.upgrade() {
644                    this.handle_window_state_changed(state);
645                }
646            }
647        })));
648        platform_impl.set_drawn_decorations_request_changed(Some(Rc::new({
649            let weak = weak.clone();
650            move || {
651                if let Some(this) = weak.upgrade() {
652                    this.update_drawn_decorations();
653                }
654            }
655        })));
656        platform_impl.set_extend_client_area_to_decorations_changed(Some(Rc::new({
657            let weak = weak.clone();
658            move |is_extended| {
659                if let Some(this) = weak.upgrade() {
660                    this.extend_client_area_to_decorations_changed(is_extended);
661                }
662            }
663        })));
664        platform_impl.set_allowed_window_actions_changed(Some(Rc::new({
665            let weak = weak.clone();
666            move |actions| {
667                if let Some(this) = weak.upgrade() {
668                    this.on_allowed_window_actions_changed(actions);
669                }
670            }
671        })));
672        self.allowed_window_actions.set(platform_impl.allowed_window_actions());
673
674        // The first value is the current one; later ones are changes.
675        let skip = Cell::new(true);
676        let object: &FerroObject = self;
677        let _ = FerroObjectExtensions::get_observable(object, TopLevel::client_size_property()).subscribe_fn({
678            let weak = weak.clone();
679            move |x: Size| {
680                if skip.replace(false) {
681                    return;
682                }
683                if let Some(this) = weak.upgrade() {
684                    this.resize_platform_impl(x, WindowResizeReason::Application);
685                }
686            }
687        });
688        let _ = self.scaling_changed({
689            let weak = weak.clone();
690            move || {
691                if let Some(this) = weak.upgrade() {
692                    this.on_scaling_changed_update_decorations();
693                }
694            }
695        });
696
697        self.bind_impl(Self::title_property(), |_, platform_impl, title| platform_impl.set_title(title.as_deref()));
698        self.bind_impl(Self::icon_property(), |this, _, icon| this.set_effective_icon(icon));
699        self.bind_impl(Self::can_resize_property(), |_, platform_impl, can_resize| platform_impl.can_resize(can_resize));
700        self.bind_impl(Self::can_minimize_property(), |_, platform_impl, can_minimize| {
701            platform_impl.set_can_minimize(can_minimize)
702        });
703        self.bind_impl(Self::can_maximize_property(), |_, platform_impl, can_maximize| {
704            platform_impl.set_can_maximize(can_maximize)
705        });
706        self.bind_impl(Self::show_in_taskbar_property(), |_, platform_impl, show| platform_impl.show_taskbar_icon(show));
707
708        self.bind_impl(Self::extend_client_area_to_decorations_hint_property(), |_, platform_impl, hint| {
709            platform_impl.set_extend_client_area_to_decorations_hint(hint)
710        });
711        self.bind_impl(Self::extend_client_area_title_bar_height_hint_property(), |_, platform_impl, height| {
712            platform_impl.set_extend_client_area_title_bar_height_hint(height)
713        });
714
715        fn update_min_max_size(this: &Window, platform_impl: &Rc<dyn IWindowImpl>, _: f64) {
716            platform_impl.set_min_max_size(
717                Size::new(this.min_width(), this.min_height()),
718                Size::new(this.max_width(), this.max_height()),
719            );
720        }
721        self.bind_impl(Layoutable::min_width_property(), update_min_max_size);
722        self.bind_impl(Layoutable::max_width_property(), update_min_max_size);
723        self.bind_impl(Layoutable::min_height_property(), update_min_max_size);
724        self.bind_impl(Layoutable::max_height_property(), update_min_max_size);
725    }
726
727    /// Forwards the value of `property` to the platform implementation, now
728    /// and whenever it changes.
729    fn bind_impl<T: PropertyValue>(
730        &self,
731        property: &'static StyledProperty<T>,
732        on_value: impl Fn(&Window, &Rc<dyn IWindowImpl>, T) + 'static,
733    ) {
734        let weak = self.to_ref().downgrade();
735        self.create_platform_impl_binding(property, move |value| {
736            if let Some(this) = weak.upgrade() {
737                if let Some(platform_impl) = this.platform_impl() {
738                    on_value(&this, &platform_impl, value);
739                }
740            }
741        });
742    }
743
744    /// The platform-specific window implementation; `None` once the window
745    /// has closed.
746    pub fn platform_impl(&self) -> Option<Rc<dyn IWindowImpl>> {
747        WindowBase::platform_impl(self)?;
748        self.window_impl.borrow().clone()
749    }
750
751    /// The child windows owned by this window.
752    pub fn owned_windows(&self) -> Vec<Ref<Window>> {
753        self.children.borrow().iter().map(|(child, _)| child.clone()).collect()
754    }
755
756    /// A value indicating how the window will size itself to fit its
757    /// content.
758    ///
759    /// If the value is other than [`SizeToContent::MANUAL`], it is
760    /// automatically set to [`SizeToContent::MANUAL`] if a user resizes the
761    /// window by using the resize grip or dragging the border.
762    ///
763    /// NOTE: Because of a limitation of X11, the value will be reset on X11
764    /// to [`SizeToContent::MANUAL`] on any resize - including the resize
765    /// that happens when the window is first shown. This is because X11
766    /// resize notifications are asynchronous and there is no way to know
767    /// whether a resize came from the user or the layout system. To avoid
768    /// this, consider setting `CanResize` to false, which will disable user
769    /// resizing of the window.
770    pub fn size_to_content(&self) -> SizeToContent {
771        self.get_value(Self::size_to_content_property())
772    }
773
774    pub fn set_size_to_content(&self, value: SizeToContent) {
775        self.set_value(Self::size_to_content_property(), value)
776    }
777
778    /// The title of the window.
779    pub fn title(&self) -> Option<String> {
780        self.get_value(Self::title_property())
781    }
782
783    pub fn set_title(&self, value: Option<String>) {
784        self.set_value(Self::title_property(), value)
785    }
786
787    /// Whether the client area is extended into the window decorations
788    /// (chrome or border).
789    pub fn extend_client_area_to_decorations_hint(&self) -> bool {
790        self.get_value(Self::extend_client_area_to_decorations_hint_property())
791    }
792
793    pub fn set_extend_client_area_to_decorations_hint(&self, value: bool) {
794        self.set_value(Self::extend_client_area_to_decorations_hint_property(), value)
795    }
796
797    /// The title bar height hint for when the client area is extended. A
798    /// value of -1 will cause the title bar to be auto sized to the OS
799    /// default. Any other positive value will cause the title bar to assume
800    /// that height.
801    pub fn extend_client_area_title_bar_height_hint(&self) -> f64 {
802        self.get_value(Self::extend_client_area_title_bar_height_hint_property())
803    }
804
805    pub fn set_extend_client_area_title_bar_height_hint(&self, value: f64) {
806        self.set_value(Self::extend_client_area_title_bar_height_hint_property(), value)
807    }
808
809    /// Whether the client area is extended into the window decorations.
810    pub fn is_extended_into_window_decorations(&self) -> bool {
811        self.is_extended_into_window_decorations.get()
812    }
813
814    fn set_is_extended_into_window_decorations(&self, value: bool) {
815        self.set_and_raise_cell(
816            Self::is_extended_into_window_decorations_property(),
817            &self.is_extended_into_window_decorations,
818            value,
819        );
820    }
821
822    /// The thickness around the window that is used by borders and the
823    /// title bar.
824    pub fn window_decoration_margin(&self) -> Thickness {
825        self.window_decoration_margin.get()
826    }
827
828    fn set_window_decoration_margin(&self, value: Thickness) {
829        self.set_and_raise_cell(Self::window_decoration_margin_property(), &self.window_decoration_margin, value);
830    }
831
832    /// The room the caption buttons take at the left and the right of the title bar while the
833    /// client area is extended into the decorations: the traffic lights of macOS at the left, the
834    /// caption buttons the toolkit draws (Windows, Linux) at the right; zero otherwise. Not in
835    /// upstream: the insets of WinUI's `AppWindowTitleBar` (`LeftInset`, `RightInset`), which a
836    /// [`TitleBar`](crate::TitleBar) leaves free.
837    pub fn caption_button_insets(&self) -> Thickness {
838        // Full screen hides the caption buttons of the system and of the toolkit.
839        if !self.is_extended_into_window_decorations() || self.window_state() == WindowState::FullScreen {
840            return Thickness::default();
841        }
842        if let Some(insets) = self.platform_impl().and_then(|platform_impl| platform_impl.caption_button_insets()) {
843            return insets;
844        }
845        if let Some(decorations) = self.top_level_host().decorations() {
846            if decorations.enabled_parts().contains(crate::chrome::DrawnWindowDecorationParts::TITLE_BAR) {
847                return Thickness::new(0.0, 0.0, decorations.caption_buttons_width(), 0.0);
848            }
849        }
850        Thickness::default()
851    }
852
853    /// The window margin that is hidden off the screen area. This is
854    /// generally only the case on Windows when maximized, where the window
855    /// border is hidden off the screen. This margin may be used to ensure
856    /// user content doesn't overlap this space.
857    pub fn off_screen_margin(&self) -> Thickness {
858        self.off_screen_margin.get()
859    }
860
861    fn set_off_screen_margin(&self, value: Thickness) {
862        self.set_and_raise_cell(Self::off_screen_margin_property(), &self.off_screen_margin, value);
863    }
864
865    /// The window decorations (title bar, border, etc).
866    pub fn window_decorations(&self) -> WindowDecorations {
867        self.get_value(Self::window_decorations_property())
868    }
869
870    pub fn set_window_decorations(&self, value: WindowDecorations) {
871        self.set_value(Self::window_decorations_property(), value)
872    }
873
874    /// The theme used to render the window decorations when they are not
875    /// drawn by the system.
876    pub fn window_decorations_theme(&self) -> Option<Ref<ControlTheme>> {
877        self.get_value(Self::window_decorations_theme_property())
878    }
879
880    pub fn set_window_decorations_theme(&self, value: impl Into<Nullable<ControlTheme>>) {
881        self.set_value(Self::window_decorations_theme_property(), value.into().0)
882    }
883
884    #[deprecated(note = "Use window_decorations instead.")]
885    pub fn system_decorations(&self) -> WindowDecorations {
886        self.window_decorations()
887    }
888
889    #[deprecated(note = "Use set_window_decorations instead.")]
890    pub fn set_system_decorations(&self, value: WindowDecorations) {
891        self.set_window_decorations(value)
892    }
893
894    /// Whether a window is activated when first shown.
895    pub fn show_activated(&self) -> bool {
896        self.get_value(Self::show_activated_property())
897    }
898
899    pub fn set_show_activated(&self, value: bool) {
900        self.set_value(Self::show_activated_property(), value)
901    }
902
903    /// Enables or disables the taskbar icon.
904    pub fn show_in_taskbar(&self) -> bool {
905        self.get_value(Self::show_in_taskbar_property())
906    }
907
908    pub fn set_show_in_taskbar(&self, value: bool) {
909        self.set_value(Self::show_in_taskbar_property(), value)
910    }
911
912    /// A value indicating how the closing event behaves in the presence of
913    /// child windows.
914    pub fn closing_behavior(&self) -> WindowClosingBehavior {
915        self.get_value(Self::closing_behavior_property())
916    }
917
918    pub fn set_closing_behavior(&self, value: WindowClosingBehavior) {
919        self.set_value(Self::closing_behavior_property(), value)
920    }
921
922    /// Represents the currently effective window state (normal, minimized,
923    /// maximized).
924    pub fn window_state(&self) -> WindowState {
925        match self.platform_impl() {
926            Some(platform_impl) if platform_impl.window_state_getter_is_usable() => platform_impl.window_state(),
927            _ => self.last_window_state.get(),
928        }
929    }
930
931    pub fn set_window_state(&self, value: WindowState) {
932        if let Some(platform_impl) = self.platform_impl() {
933            if platform_impl.window_state_getter_is_usable() {
934                // Attempt to set the window state to desired value, if it succeeds the platform will
935                // trigger the window state changed callback which will set and raise the property
936                platform_impl.set_window_state(value);
937
938                // If the request was refused - trigger a synthetic property change notification
939                // for data bindings and user state to fix itself.
940                if platform_impl.window_state() != value {
941                    // Since it's a force notify, we aren't checking for the old value and sometimes
942                    // trigger notification with old value = new value.
943                    let old_value = self.last_window_state.get();
944                    self.last_window_state.set(platform_impl.window_state());
945                    self.raise_direct_property_changed(
946                        Self::window_state_property(),
947                        &old_value,
948                        &self.last_window_state.get(),
949                    );
950                }
951            } else {
952                // Legacy behavior - update the property and hope for the best that the platform
953                // will update it back to match the actual window state
954                self.set_and_raise_cell(Self::window_state_property(), &self.last_window_state, value);
955                platform_impl.set_window_state(self.last_window_state.get());
956            }
957        }
958    }
959
960    /// Enables or disables resizing of the window.
961    pub fn can_resize(&self) -> bool {
962        self.get_value(Self::can_resize_property())
963    }
964
965    pub fn set_can_resize(&self, value: bool) {
966        self.set_value(Self::can_resize_property(), value)
967    }
968
969    /// Enables or disables minimizing the window.
970    ///
971    /// This property might be ignored by some window managers on Linux.
972    pub fn can_minimize(&self) -> bool {
973        self.get_value(Self::can_minimize_property())
974    }
975
976    pub fn set_can_minimize(&self, value: bool) {
977        self.set_value(Self::can_minimize_property(), value)
978    }
979
980    /// Enables or disables maximizing the window.
981    ///
982    /// When `CanResize` is false, this property is always false. On macOS,
983    /// setting this property to false also disables the full screen mode.
984    /// This property might be ignored by some window managers on Linux.
985    pub fn can_maximize(&self) -> bool {
986        self.get_value(Self::can_maximize_property())
987    }
988
989    pub fn set_can_maximize(&self, value: bool) {
990        self.set_value(Self::can_maximize_property(), value)
991    }
992
993    /// The window actions currently allowed by the underlying platform
994    /// (internal upstream: for the window decorations).
995    pub fn allowed_window_actions(&self) -> PlatformAllowedWindowActions {
996        self.allowed_window_actions.get()
997    }
998
999    /// The icon of the window.
1000    pub fn icon(&self) -> Option<Rc<WindowIcon>> {
1001        self.get_value(Self::icon_property())
1002    }
1003
1004    pub fn set_icon(&self, value: Option<Rc<WindowIcon>>) {
1005        self.set_value(Self::icon_property(), value)
1006    }
1007
1008    /// The startup location of the window.
1009    pub fn window_startup_location(&self) -> WindowStartupLocation {
1010        self.get_value(Self::window_startup_location_property())
1011    }
1012
1013    pub fn set_window_startup_location(&self, value: WindowStartupLocation) {
1014        self.set_value(Self::window_startup_location_property(), value)
1015    }
1016
1017    /// The window position in screen coordinates.
1018    pub fn position(&self) -> PixelPoint {
1019        self.platform_impl().map(|platform_impl| platform_impl.position()).unwrap_or_default()
1020    }
1021
1022    pub fn set_position(&self, value: PixelPoint) {
1023        if let Some(platform_impl) = self.platform_impl() {
1024            platform_impl.move_(value);
1025        }
1026        self.position_was_set.set(true);
1027    }
1028
1029    /// Whether this window was opened as a dialog.
1030    pub fn is_dialog(&self) -> bool {
1031        self.showing_as_dialog.get()
1032    }
1033
1034    /// Starts moving a window with left button being held. Should be called
1035    /// from a left mouse button press event handler.
1036    pub fn begin_move_drag(&self, e: &PointerPressedEventArgs) {
1037        if let Some(platform_impl) = self.platform_impl() {
1038            platform_impl.begin_move_drag(e);
1039        }
1040    }
1041
1042    /// Starts resizing a window. This function is used if an application
1043    /// has window resizing controls. Should be called from a left mouse
1044    /// button press event handler.
1045    pub fn begin_resize_drag(&self, edge: WindowEdge, e: &PointerPressedEventArgs) {
1046        if let Some(platform_impl) = self.platform_impl() {
1047            platform_impl.begin_resize_drag(edge, e);
1048        }
1049    }
1050
1051    /// Fired before a window is closed. Disposing the returned handle
1052    /// unsubscribes.
1053    pub fn closing(&self, handler: impl Fn(&WindowClosingEventArgs) + 'static) -> Rc<dyn IDisposable> {
1054        let token = self.closing.add(Rc::new(handler));
1055        let weak = self.to_ref().downgrade();
1056        Disposable::create(move || {
1057            if let Some(this) = weak.upgrade() {
1058                this.closing.remove(token);
1059            }
1060        })
1061    }
1062
1063    /// Closes the window.
1064    pub fn close(&self) {
1065        self.close_core(WindowCloseReason::WindowClosing, true, false);
1066    }
1067
1068    /// Closes a dialog window with the specified result.
1069    ///
1070    /// When the window is shown with [`show_dialog`](Self::show_dialog) or
1071    /// [`show_dialog_typed`](Self::show_dialog_typed), the resulting task
1072    /// will produce the `dialog_result` value when the window is closed.
1073    pub fn close_with_result(&self, dialog_result: Option<BoxedValue>) {
1074        *self.dialog_result.borrow_mut() = dialog_result;
1075        self.close_core(WindowCloseReason::WindowClosing, true, false);
1076    }
1077
1078    /// Closes the window for `reason`; with `ignore_cancel` the window
1079    /// closes even when a closing handler cancels (internal upstream: for
1080    /// the application lifetime).
1081    pub fn close_core(&self, reason: WindowCloseReason, is_programmatic: bool, ignore_cancel: bool) {
1082        let scope = CloseScope { window: self, close: Cell::new(true), ignore_cancel };
1083
1084        if self.should_cancel_close(&WindowClosingEventArgs::new(reason, is_programmatic)) {
1085            scope.close.set(false);
1086        }
1087    }
1088
1089    fn close_internal(&self) {
1090        let children = self.children.borrow().clone();
1091        for (child, _) in children {
1092            child.close_internal();
1093        }
1094
1095        if let Some(platform_impl) = self.platform_impl() {
1096            platform_impl.dispose();
1097        }
1098
1099        self.showing_as_dialog.set(false);
1100
1101        self.set_owner(None);
1102    }
1103
1104    fn should_cancel_close(&self, args: &WindowClosingEventArgs) -> bool {
1105        match self.closing_behavior() {
1106            WindowClosingBehavior::OwnerAndChildWindows => {
1107                let mut can_close = true;
1108
1109                let children = self.children.borrow().clone();
1110                if !children.is_empty() {
1111                    let owner_closing_args;
1112                    let child_args = if args.close_reason() == WindowCloseReason::WindowClosing {
1113                        owner_closing_args =
1114                            WindowClosingEventArgs::new(WindowCloseReason::OwnerWindowClosing, args.is_programmatic());
1115                        &owner_closing_args
1116                    } else {
1117                        args
1118                    };
1119
1120                    for (child, _) in children {
1121                        if child.should_cancel_close(child_args) {
1122                            can_close = false;
1123                        }
1124                    }
1125                }
1126
1127                if can_close {
1128                    self.on_closing(args);
1129
1130                    return args.cancel();
1131                }
1132
1133                true
1134            }
1135            WindowClosingBehavior::OwnerWindowOnly => {
1136                self.on_closing(args);
1137
1138                args.cancel()
1139            }
1140        }
1141    }
1142
1143    fn handle_window_state_changed(&self, state: WindowState) {
1144        // Check if platform impl doesn't lie about the window state getter being usable
1145        debug_assert!(self
1146            .platform_impl()
1147            .is_none_or(|platform_impl| !platform_impl.window_state_getter_is_usable()
1148                || platform_impl.window_state() == state));
1149
1150        self.set_and_raise_cell(Self::window_state_property(), &self.last_window_state, state);
1151
1152        if state == WindowState::Minimized {
1153            self.stop_rendering();
1154        } else {
1155            self.start_rendering();
1156        }
1157
1158        // Update decoration parts and fullscreen popover state for the new window state
1159        self.update_drawn_decoration_parts();
1160    }
1161
1162    /// Fired when the window actions allowed by the platform change
1163    /// (internal upstream: for the window decorations). Disposing the
1164    /// returned handle unsubscribes.
1165    pub fn allowed_window_actions_changed(
1166        &self,
1167        handler: impl Fn(PlatformAllowedWindowActions) + 'static,
1168    ) -> Rc<dyn IDisposable> {
1169        let token = self.allowed_window_actions_changed.add(Rc::new(handler));
1170        let weak = self.to_ref().downgrade();
1171        Disposable::create(move || {
1172            if let Some(this) = weak.upgrade() {
1173                this.allowed_window_actions_changed.remove(token);
1174            }
1175        })
1176    }
1177
1178    fn on_allowed_window_actions_changed(&self, actions: PlatformAllowedWindowActions) {
1179        self.allowed_window_actions.set(actions);
1180        for (_, handler) in self.allowed_window_actions_changed.snapshot().iter() {
1181            handler(actions);
1182        }
1183    }
1184
1185    fn extend_client_area_to_decorations_changed(&self, is_extended: bool) {
1186        self.set_is_extended_into_window_decorations(is_extended);
1187        self.set_off_screen_margin(
1188            self.platform_impl().map(|platform_impl| platform_impl.off_screen_margin()).unwrap_or_default(),
1189        );
1190
1191        self.update_drawn_decorations();
1192    }
1193
1194    fn update_drawn_decorations(&self) {
1195        let parts = self.compute_decoration_parts();
1196
1197        // Detect forced mode: platform needs managed decorations but app hasn't opted in
1198        self.is_forced_decoration_mode.set(parts.is_some() && !self.is_extended_into_window_decorations());
1199
1200        self.top_level_host().update_drawn_decorations(parts, self.window_state(), self.window_decorations_theme());
1201
1202        if parts.is_some() {
1203            // Forward the title bar height hint to the title bar height of the decorations
1204            if let Some(decorations) = self.top_level_host().decorations() {
1205                decorations.set_render_scaling(self.render_scaling());
1206
1207                let hint = self.extend_client_area_title_bar_height_hint();
1208                if hint >= 0.0 {
1209                    decorations.set_title_bar_height_override(hint);
1210                }
1211            }
1212        }
1213
1214        self.update_drawn_decoration_margins();
1215    }
1216
1217    fn on_scaling_changed_update_decorations(&self) {
1218        if let Some(decorations) = self.top_level_host().decorations() {
1219            decorations.set_render_scaling(self.render_scaling());
1220        }
1221    }
1222
1223    /// Updates decoration parts based on current window state without
1224    /// re-creating the decorations instance.
1225    fn update_drawn_decoration_parts(&self) {
1226        if self.top_level_host().decorations().is_none() {
1227            return;
1228        }
1229
1230        self.top_level_host().update_drawn_decorations(
1231            self.compute_decoration_parts(),
1232            self.window_state(),
1233            self.window_decorations_theme(),
1234        );
1235    }
1236
1237    fn compute_decoration_parts(&self) -> Option<crate::chrome::DrawnWindowDecorationParts> {
1238        use crate::chrome::DrawnWindowDecorationParts;
1239        use crate::platform::PlatformRequestedDrawnDecoration;
1240
1241        let platform_impl = self.platform_impl();
1242        if !platform_impl.as_ref().is_some_and(|platform_impl| platform_impl.needs_managed_decorations()) {
1243            return None;
1244        }
1245
1246        let platform_needs = platform_impl
1247            .map(|platform_impl| platform_impl.requested_drawn_decorations())
1248            .unwrap_or(PlatformRequestedDrawnDecoration::NONE);
1249        let mut parts = DrawnWindowDecorationParts::NONE;
1250        if self.window_decorations() != WindowDecorations::None {
1251            if platform_needs.contains(PlatformRequestedDrawnDecoration::TITLE_BAR)
1252                && self.window_decorations() == WindowDecorations::Full
1253            {
1254                parts |= DrawnWindowDecorationParts::TITLE_BAR;
1255            }
1256            if platform_needs.contains(PlatformRequestedDrawnDecoration::SHADOW) {
1257                parts |= DrawnWindowDecorationParts::SHADOW;
1258            }
1259            if platform_needs.contains(PlatformRequestedDrawnDecoration::BORDER) {
1260                parts |= DrawnWindowDecorationParts::BORDER;
1261            }
1262            if platform_needs.contains(PlatformRequestedDrawnDecoration::RESIZE_GRIPS) && self.can_resize() {
1263                parts |= DrawnWindowDecorationParts::RESIZE_GRIPS;
1264            }
1265
1266            // In fullscreen: no shadow, border, resize grips, or titlebar (popover takes over)
1267            if self.window_state() == WindowState::FullScreen {
1268                parts &= !(DrawnWindowDecorationParts::SHADOW
1269                    | DrawnWindowDecorationParts::BORDER
1270                    | DrawnWindowDecorationParts::RESIZE_GRIPS
1271                    | DrawnWindowDecorationParts::TITLE_BAR);
1272            }
1273            // In maximized: no shadow, border, or resize grips (titlebar stays)
1274            else if self.window_state() == WindowState::Maximized {
1275                parts &= !(DrawnWindowDecorationParts::SHADOW
1276                    | DrawnWindowDecorationParts::BORDER
1277                    | DrawnWindowDecorationParts::RESIZE_GRIPS);
1278            }
1279        }
1280
1281        Some(parts)
1282    }
1283
1284    fn on_title_bar_height_hint_changed(&self) {
1285        let Some(decorations) = self.top_level_host().decorations() else { return };
1286
1287        decorations.set_title_bar_height_override(self.extend_client_area_title_bar_height_hint());
1288
1289        self.update_drawn_decoration_margins();
1290    }
1291
1292    /// Called by the host of the window when the effective geometry of the
1293    /// decorations changes (e.g. the theme changes the default values, or
1294    /// the enabled parts change).
1295    pub(crate) fn on_drawn_decorations_geometry_changed(&self) {
1296        self.update_drawn_decoration_margins();
1297    }
1298
1299    fn update_drawn_decoration_margins(&self) {
1300        if let Some(decorations) = self.top_level_host().decorations() {
1301            use crate::chrome::DrawnWindowDecorationParts;
1302
1303            let parts = decorations.enabled_parts();
1304            let title_bar_height =
1305                if parts.contains(DrawnWindowDecorationParts::TITLE_BAR) { decorations.title_bar_height() } else { 0.0 };
1306            let frame = if parts.contains(DrawnWindowDecorationParts::BORDER) {
1307                decorations.frame_thickness()
1308            } else {
1309                Thickness::default()
1310            };
1311            let shadow = if parts.contains(DrawnWindowDecorationParts::SHADOW) {
1312                decorations.shadow_thickness()
1313            } else {
1314                Thickness::default()
1315            };
1316
1317            if let Some(platform_impl) = self.platform_impl() {
1318                platform_impl.set_shadow_extents(shadow);
1319            }
1320
1321            let margin = Thickness::new(
1322                frame.left + shadow.left,
1323                title_bar_height + frame.top + shadow.top,
1324                frame.right + shadow.right,
1325                frame.bottom + shadow.bottom,
1326            );
1327
1328            if self.is_forced_decoration_mode.get() {
1329                // In forced mode, app is unaware of decorations.
1330                // The host insets the window child; the window decoration margin stays zero.
1331                self.set_window_decoration_margin(Thickness::default());
1332                self.top_level_host().set_decoration_inset(margin);
1333            } else {
1334                // In extended mode, app handles the margin itself.
1335                self.set_window_decoration_margin(margin);
1336                self.top_level_host().set_decoration_inset(Thickness::default());
1337            }
1338            return;
1339        }
1340
1341        // Only use platform margins if drawn decorations are not active
1342        let platform_impl = self.platform_impl();
1343        self.set_window_decoration_margin(
1344            platform_impl.as_ref().map(|platform_impl| platform_impl.extended_margins()).unwrap_or_default(),
1345        );
1346        self.top_level_host().set_decoration_inset(Thickness::default());
1347        if let Some(platform_impl) = platform_impl {
1348            platform_impl.set_shadow_extents(Thickness::default());
1349        }
1350    }
1351
1352    /// Shows the window as a child of `owner`.
1353    ///
1354    /// # Panics
1355    /// Panics when the window has already been closed, and when the owner
1356    /// is closed, not visible or the window itself.
1357    pub fn show_with_owner(&self, owner: &Window) {
1358        self.show_core(Some(owner), false);
1359    }
1360
1361    fn ensure_state_before_show(&self) {
1362        if self.platform_impl().is_none() {
1363            panic!("Cannot re-show a closed window.");
1364        }
1365    }
1366
1367    fn ensure_parent_state_before_show(&self, owner: &Window) {
1368        if owner.platform_impl().is_none() {
1369            panic!("Cannot show a window with a closed owner.");
1370        }
1371
1372        if std::ptr::eq(owner, self) {
1373            panic!("A Window cannot be its own owner.");
1374        }
1375
1376        if !owner.is_visible() {
1377            panic!("Cannot show window with non-visible owner.");
1378        }
1379    }
1380
1381    fn show_core(&self, owner: Option<&Window>, modal: bool) -> Option<DialogCompletion> {
1382        let _freeze = self.freeze_visibility_change_handling();
1383
1384        self.ensure_state_before_show();
1385
1386        if modal && owner.is_none() {
1387            panic!("A dialog requires an owner.");
1388        }
1389        if let Some(owner) = owner {
1390            self.ensure_parent_state_before_show(owner);
1391        }
1392
1393        if self.shown.get() {
1394            if modal {
1395                panic!("The window is already being shown.");
1396            }
1397            return None;
1398        }
1399
1400        self.showing_as_dialog.set(modal);
1401        self.raise_event(&RoutedEventArgs::with_event(Self::window_opened_event()));
1402
1403        self.ensure_initialized();
1404        self.apply_styling();
1405
1406        // Enable drawn decorations before layout so margins are computed
1407        self.update_drawn_decorations();
1408
1409        // In forced mode, adjust the client size to reflect usable content area
1410        if self.is_forced_decoration_mode.get() {
1411            let inset = self.top_level_host().decoration_inset();
1412            self.set_client_size(Size::new(
1413                (self.client_size().width - inset.left - inset.right).max(0.0),
1414                (self.client_size().height - inset.top - inset.bottom).max(0.0),
1415            ));
1416        }
1417
1418        self.shown.set(true);
1419        self.set_is_visible(true);
1420
1421        self.set_effective_icon(self.icon());
1422
1423        // If window position was not set before then platform may provide incorrect scaling at this time,
1424        // but we need it for proper calculation of position and in some cases size (size to content)
1425        self.set_expected_scaling(owner);
1426
1427        let mut initial_size = Size::new(
1428            if self.width().is_nan() { self.client_size().width } else { self.width() },
1429            if self.height().is_nan() { self.client_size().height } else { self.height() },
1430        );
1431
1432        let min_max = MinMax::new(self);
1433
1434        initial_size = Size::new(
1435            MathUtilities::clamp(initial_size.width, min_max.min_width, min_max.max_width),
1436            MathUtilities::clamp(initial_size.height, min_max.min_height, min_max.max_height),
1437        );
1438
1439        let mut client_size_changed = initial_size != self.client_size();
1440        self.set_client_size(initial_size); // The client size is required for measure and arrange
1441
1442        // this will call arrange_set_bounds
1443        self.layout_manager().execute_initial_layout_pass();
1444
1445        if self.size_to_content().contains(SizeToContent::WIDTH) {
1446            initial_size = initial_size.with_width(MathUtilities::clamp(
1447                self.arrange_bounds.get().width,
1448                min_max.min_width,
1449                min_max.max_width,
1450            ));
1451            client_size_changed |= initial_size != self.client_size();
1452            self.set_client_size(initial_size);
1453        }
1454
1455        if self.size_to_content().contains(SizeToContent::HEIGHT) {
1456            initial_size = initial_size.with_height(MathUtilities::clamp(
1457                self.arrange_bounds.get().height,
1458                min_max.min_height,
1459                min_max.max_height,
1460            ));
1461            client_size_changed |= initial_size != self.client_size();
1462            self.set_client_size(initial_size);
1463        }
1464
1465        self.set_owner(owner.map(|owner| owner.to_ref().upcast()));
1466
1467        self.apply_window_startup_location(owner);
1468
1469        self.set_desktop_scaling_override(None);
1470
1471        // In forced mode, compare against adjusted platform size
1472        let platform_client_size =
1473            self.platform_impl().map(|platform_impl| platform_impl.client_size()).unwrap_or_default();
1474        let comparable_client_size = if self.is_forced_decoration_mode.get() {
1475            let inset = self.top_level_host().decoration_inset();
1476            Size::new(
1477                (platform_client_size.width - inset.left - inset.right).max(0.0),
1478                (platform_client_size.height - inset.top - inset.bottom).max(0.0),
1479            )
1480        } else {
1481            platform_client_size
1482        };
1483
1484        if client_size_changed || self.client_size() != comparable_client_size {
1485            // Previously it was called before the initial layout pass
1486            self.resize_platform_impl(self.client_size(), WindowResizeReason::Layout);
1487
1488            // we do not want the platform resize to trigger handle_resized yet because it will set the width
1489            // and height. So perform some important actions from handle_resized
1490
1491            self.renderer().resized(self.client_size());
1492            self.on_resized(&WindowResizedEventArgs::new(self.client_size(), WindowResizeReason::Layout));
1493
1494            if !self.width().is_nan() {
1495                self.set_width(self.client_size().width);
1496            }
1497            if !self.height().is_nan() {
1498                self.set_height(self.client_size().height);
1499            }
1500        }
1501
1502        self.set_frame_size(self.platform_impl().and_then(|platform_impl| platform_impl.frame_size()));
1503
1504        self.can_handle_resized.set(true);
1505
1506        self.start_rendering();
1507        if let Some(platform_impl) = self.platform_impl() {
1508            platform_impl.show(self.show_activated(), modal);
1509        }
1510
1511        let mut result = None;
1512        if let (true, Some(owner)) = (modal, owner) {
1513            let completion = DialogCompletion::new();
1514            let weak = self.to_ref().downgrade();
1515
1516            let taken = Cell::new(false);
1517            let closed_subscription = TopLevel::closed(self, {
1518                let weak = weak.clone();
1519                move || {
1520                    if taken.replace(true) {
1521                        return;
1522                    }
1523                    let modal_subscription =
1524                        weak.upgrade().and_then(|this| this.modal_subscription.borrow().clone());
1525                    if let Some(modal_subscription) = modal_subscription {
1526                        modal_subscription.dispose();
1527                    }
1528                }
1529            });
1530            let complete = Disposable::create({
1531                let owner = owner.to_ref().downgrade();
1532                let completion = completion.clone();
1533                move || {
1534                    let this = weak.upgrade();
1535                    if let Some(this) = &this {
1536                        *this.modal_subscription.borrow_mut() = None;
1537                    }
1538                    if let Some(owner) = owner.upgrade() {
1539                        owner.activate();
1540                    }
1541                    completion.set_result(this.and_then(|this| this.dialog_result.borrow().clone()));
1542                }
1543            });
1544
1545            let disposables: Rc<dyn IDisposable> =
1546                Rc::new(CompositeDisposable::from_disposables([closed_subscription, complete]));
1547            *self.modal_subscription.borrow_mut() = Some(disposables);
1548            result = Some(completion);
1549        }
1550
1551        self.on_opened();
1552        if !modal {
1553            self.was_shown_before.set(true);
1554        }
1555
1556        result
1557    }
1558
1559    fn resize_platform_impl(&self, mut size: Size, reason: WindowResizeReason) {
1560        let Some(platform_impl) = self.platform_impl() else { return };
1561
1562        // In forced mode, add decoration inset so platform gets full frame size
1563        if self.is_forced_decoration_mode.get() {
1564            let inset = self.top_level_host().decoration_inset();
1565            size = Size::new(size.width + inset.left + inset.right, size.height + inset.top + inset.bottom);
1566            if platform_impl.client_size() != size {
1567                platform_impl.resize(size, reason);
1568            }
1569        } else {
1570            platform_impl.resize(size, reason);
1571        }
1572    }
1573
1574    /// Shows the window as a dialog owned by `owner`.
1575    ///
1576    /// Returns a task that can be used to track the lifetime of the dialog:
1577    /// it completes, from a dispatcher job, with the dialog result (see
1578    /// [`close_with_result`](Self::close_with_result)) once the dialog has
1579    /// closed. Nothing blocks: the caller keeps running and the task is
1580    /// observed through the dispatcher.
1581    ///
1582    /// # Panics
1583    /// Panics when the window has already been closed or is already shown,
1584    /// and when the owner is closed, not visible or the window itself.
1585    pub fn show_dialog(&self, owner: &Window) -> DispatcherTask<Option<BoxedValue>> {
1586        let completion = self.show_core(Some(owner), true).expect("showing a dialog creates its completion");
1587        Dispatcher::ui_thread().invoke_async_task_local(move || completion)
1588    }
1589
1590    /// Shows the window as a dialog owned by `owner`; `TResult` is the type
1591    /// of the result produced by the dialog.
1592    ///
1593    /// Returns a task that can be used to retrieve the result of the dialog
1594    /// when it closes: the value passed to
1595    /// [`close_with_result`](Self::close_with_result), or the default of
1596    /// `TResult` when the dialog closed without a result. The task fails
1597    /// when the dialog result is not a `TResult`.
1598    ///
1599    /// # Panics
1600    /// See [`show_dialog`](Self::show_dialog).
1601    pub fn show_dialog_typed<TResult: Clone + Default + 'static>(&self, owner: &Window) -> DispatcherTask<TResult> {
1602        let completion = self.show_core(Some(owner), true).expect("showing a dialog creates its completion");
1603        Dispatcher::ui_thread().invoke_async_task_local(move || async move {
1604            match completion.await {
1605                Some(dialog_result) => match dialog_result.downcast_ref::<TResult>() {
1606                    Some(dialog_result) => dialog_result.clone(),
1607                    None => panic!(
1608                        "Unable to cast the dialog result of type {} to {}.",
1609                        dialog_result.any_value_type_name(),
1610                        std::any::type_name::<TResult>()
1611                    ),
1612                },
1613                None => TResult::default(),
1614            }
1615        })
1616    }
1617
1618    /// Sorts the windows ascending by their Z order - the topmost window
1619    /// will be the last in the list.
1620    ///
1621    /// # Panics
1622    /// Panics when one of the windows is closed, and when no windowing
1623    /// platform is registered.
1624    pub fn sort_windows_by_z_order(windows: &mut [Ref<Window>]) {
1625        if windows.len() <= 1 {
1626            return;
1627        }
1628
1629        let platform = FerroLocator::current().get_required_service::<dyn IWindowingPlatform>();
1630
1631        let window_impls: Vec<Rc<dyn IWindowImpl>> = windows
1632            .iter()
1633            .enumerate()
1634            .map(|(i, window)| window.platform_impl().unwrap_or_else(|| panic!("Invalid window at index {i}")))
1635            .collect();
1636
1637        let mut z_order = vec![0i64; windows.len()];
1638        platform.get_windows_z_order(&window_impls, &mut z_order);
1639
1640        let mut keyed: Vec<(i64, Ref<Window>)> = z_order.into_iter().zip(windows.iter().cloned()).collect();
1641        keyed.sort_by_key(|(z, _)| *z);
1642        for (slot, (_, window)) in windows.iter_mut().zip(keyed) {
1643            *slot = window;
1644        }
1645    }
1646
1647    fn update_enabled(&self) {
1648        let is_enabled = !self.children.borrow().iter().any(|(_, is_dialog)| *is_dialog);
1649
1650        if let Some(platform_impl) = self.platform_impl() {
1651            platform_impl.set_enabled(is_enabled);
1652        }
1653    }
1654
1655    fn add_child(&self, window: &Window, is_dialog: bool) {
1656        self.children.borrow_mut().push((window.to_ref(), is_dialog));
1657        self.update_enabled();
1658    }
1659
1660    fn remove_child(&self, window: &Window) {
1661        self.children.borrow_mut().retain(|(child, _)| !std::ptr::eq(&**child, window));
1662
1663        self.update_enabled();
1664    }
1665
1666    fn on_got_input_when_disabled(&self) {
1667        let first_dialog_child =
1668            self.children.borrow().iter().find(|(_, is_dialog)| *is_dialog).map(|(child, _)| child.clone());
1669
1670        if let Some(first_dialog_child) = first_dialog_child {
1671            first_dialog_child.on_got_input_when_disabled();
1672        } else {
1673            self.activate();
1674        }
1675    }
1676
1677    fn set_expected_scaling(&self, owner: Option<&Window>) {
1678        if self.was_shown_before.get() {
1679            return;
1680        }
1681
1682        let location = self.get_effective_window_startup_location(owner);
1683
1684        match location {
1685            WindowStartupLocation::CenterOwner => {
1686                self.set_desktop_scaling_override(owner.map(|owner| owner.desktop_scaling()));
1687            }
1688            WindowStartupLocation::CenterScreen => {
1689                let scaling = owner
1690                    .map(|owner| owner.desktop_scaling())
1691                    .or_else(|| self.screens().screen_from_point(self.position()).map(|screen| screen.scaling()))
1692                    .or_else(|| self.screens().primary().map(|screen| screen.scaling()));
1693                self.set_desktop_scaling_override(scaling);
1694            }
1695            WindowStartupLocation::Manual => {
1696                self.set_desktop_scaling_override(
1697                    self.screens().screen_from_point(self.position()).map(|screen| screen.scaling()),
1698                );
1699            }
1700        }
1701    }
1702
1703    fn get_effective_window_startup_location(&self, owner: Option<&Window>) -> WindowStartupLocation {
1704        let mut startup_location = self.window_startup_location();
1705
1706        if startup_location == WindowStartupLocation::CenterOwner
1707            && owner.is_none_or(|owner| owner.window_state() == WindowState::Minimized)
1708        {
1709            // If startup location is CenterOwner, but owner is null or minimized then fall back
1710            // to CenterScreen. This behavior is consistent with WPF.
1711            startup_location = WindowStartupLocation::CenterScreen;
1712        }
1713
1714        startup_location
1715    }
1716
1717    fn apply_window_startup_location(&self, owner: Option<&Window>) {
1718        if self.was_shown_before.get() {
1719            return;
1720        }
1721
1722        let startup_location = self.get_effective_window_startup_location(owner);
1723
1724        // Use frame size, falling back to client size if the platform can't give it to us.
1725        let platform_frame = self
1726            .platform_impl()
1727            .and_then(|platform_impl| platform_impl.frame_size().map(|frame_size| (frame_size, platform_impl.client_size())));
1728        let rect = match platform_frame {
1729            Some((frame_size, platform_client_size)) => {
1730                // Platform may calculate the frame size with incorrect scaling, so do not trust the value.
1731                let diff = frame_size - platform_client_size;
1732                PixelRect::from_size(PixelSize::from_size(self.client_size() + diff, self.desktop_scaling()))
1733            }
1734            None => PixelRect::from_size(PixelSize::from_size(self.client_size(), self.desktop_scaling())),
1735        };
1736
1737        let apply_screen_constraint = |screen: Option<&Rc<Screen>>, mut child_rect: PixelRect| -> PixelRect {
1738            if let Some(constraint) = screen.map(|screen| screen.working_area()) {
1739                let max_x = constraint.right() - rect.width;
1740                let max_y = constraint.bottom() - rect.height;
1741
1742                if constraint.x <= max_x {
1743                    child_rect = child_rect.with_x(MathUtilities::clamp_i32(child_rect.x, constraint.x, max_x));
1744                }
1745                if constraint.y <= max_y {
1746                    child_rect = child_rect.with_y(MathUtilities::clamp_i32(child_rect.y, constraint.y, max_y));
1747                }
1748            }
1749
1750            child_rect
1751        };
1752
1753        if startup_location == WindowStartupLocation::CenterScreen {
1754            let mut screen: Option<Rc<Screen>> = None;
1755
1756            if let Some(owner) = owner {
1757                screen = self
1758                    .screens()
1759                    .screen_from_window(owner)
1760                    .or_else(|| self.screens().screen_from_point(owner.position()));
1761            }
1762
1763            let screen = screen
1764                .or_else(|| self.screens().screen_from_point(self.position()))
1765                .or_else(|| self.screens().primary());
1766
1767            if let Some(screen) = screen {
1768                let mut child_rect = screen.working_area().center_rect(rect);
1769
1770                if self.screens().screen_from_point(child_rect.position()).is_none() {
1771                    child_rect = apply_screen_constraint(Some(&screen), child_rect);
1772                }
1773
1774                self.set_position(child_rect.position());
1775            }
1776        } else if startup_location == WindowStartupLocation::CenterOwner {
1777            let owner = owner.expect("the effective startup location is the owner's center only with an owner");
1778            let owner_size = owner.frame_size().unwrap_or_else(|| owner.client_size());
1779            let owner_rect = PixelRect::from_position_size(
1780                owner.position(),
1781                PixelSize::from_size(owner_size, owner.desktop_scaling()),
1782            );
1783            let mut child_rect = owner_rect.center_rect(rect);
1784
1785            let screen = self.screens().screen_from_window(owner);
1786
1787            child_rect = apply_screen_constraint(screen.as_ref(), child_rect);
1788
1789            self.set_position(child_rect.position());
1790        }
1791
1792        // Platform returns incorrect scaling, forcing setting position may fix it
1793        if !self.position_was_set.get() {
1794            if let Some(platform_impl) = self.platform_impl() {
1795                if self.desktop_scaling() != platform_impl.desktop_scaling() {
1796                    platform_impl.move_(self.position());
1797                }
1798            }
1799        }
1800    }
1801
1802    fn set_effective_icon(&self, icon: Option<Rc<WindowIcon>>) {
1803        if let Some(platform_impl) = self.platform_impl() {
1804            platform_impl.set_icon(icon.map(|icon| icon.platform_impl()));
1805        }
1806    }
1807}