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 window margin that is hidden off the screen area. This is
833    /// generally only the case on Windows when maximized, where the window
834    /// border is hidden off the screen. This margin may be used to ensure
835    /// user content doesn't overlap this space.
836    pub fn off_screen_margin(&self) -> Thickness {
837        self.off_screen_margin.get()
838    }
839
840    fn set_off_screen_margin(&self, value: Thickness) {
841        self.set_and_raise_cell(Self::off_screen_margin_property(), &self.off_screen_margin, value);
842    }
843
844    /// The window decorations (title bar, border, etc).
845    pub fn window_decorations(&self) -> WindowDecorations {
846        self.get_value(Self::window_decorations_property())
847    }
848
849    pub fn set_window_decorations(&self, value: WindowDecorations) {
850        self.set_value(Self::window_decorations_property(), value)
851    }
852
853    /// The theme used to render the window decorations when they are not
854    /// drawn by the system.
855    pub fn window_decorations_theme(&self) -> Option<Ref<ControlTheme>> {
856        self.get_value(Self::window_decorations_theme_property())
857    }
858
859    pub fn set_window_decorations_theme(&self, value: impl Into<Nullable<ControlTheme>>) {
860        self.set_value(Self::window_decorations_theme_property(), value.into().0)
861    }
862
863    #[deprecated(note = "Use window_decorations instead.")]
864    pub fn system_decorations(&self) -> WindowDecorations {
865        self.window_decorations()
866    }
867
868    #[deprecated(note = "Use set_window_decorations instead.")]
869    pub fn set_system_decorations(&self, value: WindowDecorations) {
870        self.set_window_decorations(value)
871    }
872
873    /// Whether a window is activated when first shown.
874    pub fn show_activated(&self) -> bool {
875        self.get_value(Self::show_activated_property())
876    }
877
878    pub fn set_show_activated(&self, value: bool) {
879        self.set_value(Self::show_activated_property(), value)
880    }
881
882    /// Enables or disables the taskbar icon.
883    pub fn show_in_taskbar(&self) -> bool {
884        self.get_value(Self::show_in_taskbar_property())
885    }
886
887    pub fn set_show_in_taskbar(&self, value: bool) {
888        self.set_value(Self::show_in_taskbar_property(), value)
889    }
890
891    /// A value indicating how the closing event behaves in the presence of
892    /// child windows.
893    pub fn closing_behavior(&self) -> WindowClosingBehavior {
894        self.get_value(Self::closing_behavior_property())
895    }
896
897    pub fn set_closing_behavior(&self, value: WindowClosingBehavior) {
898        self.set_value(Self::closing_behavior_property(), value)
899    }
900
901    /// Represents the currently effective window state (normal, minimized,
902    /// maximized).
903    pub fn window_state(&self) -> WindowState {
904        match self.platform_impl() {
905            Some(platform_impl) if platform_impl.window_state_getter_is_usable() => platform_impl.window_state(),
906            _ => self.last_window_state.get(),
907        }
908    }
909
910    pub fn set_window_state(&self, value: WindowState) {
911        if let Some(platform_impl) = self.platform_impl() {
912            if platform_impl.window_state_getter_is_usable() {
913                // Attempt to set the window state to desired value, if it succeeds the platform will
914                // trigger the window state changed callback which will set and raise the property
915                platform_impl.set_window_state(value);
916
917                // If the request was refused - trigger a synthetic property change notification
918                // for data bindings and user state to fix itself.
919                if platform_impl.window_state() != value {
920                    // Since it's a force notify, we aren't checking for the old value and sometimes
921                    // trigger notification with old value = new value.
922                    let old_value = self.last_window_state.get();
923                    self.last_window_state.set(platform_impl.window_state());
924                    self.raise_direct_property_changed(
925                        Self::window_state_property(),
926                        &old_value,
927                        &self.last_window_state.get(),
928                    );
929                }
930            } else {
931                // Legacy behavior - update the property and hope for the best that the platform
932                // will update it back to match the actual window state
933                self.set_and_raise_cell(Self::window_state_property(), &self.last_window_state, value);
934                platform_impl.set_window_state(self.last_window_state.get());
935            }
936        }
937    }
938
939    /// Enables or disables resizing of the window.
940    pub fn can_resize(&self) -> bool {
941        self.get_value(Self::can_resize_property())
942    }
943
944    pub fn set_can_resize(&self, value: bool) {
945        self.set_value(Self::can_resize_property(), value)
946    }
947
948    /// Enables or disables minimizing the window.
949    ///
950    /// This property might be ignored by some window managers on Linux.
951    pub fn can_minimize(&self) -> bool {
952        self.get_value(Self::can_minimize_property())
953    }
954
955    pub fn set_can_minimize(&self, value: bool) {
956        self.set_value(Self::can_minimize_property(), value)
957    }
958
959    /// Enables or disables maximizing the window.
960    ///
961    /// When `CanResize` is false, this property is always false. On macOS,
962    /// setting this property to false also disables the full screen mode.
963    /// This property might be ignored by some window managers on Linux.
964    pub fn can_maximize(&self) -> bool {
965        self.get_value(Self::can_maximize_property())
966    }
967
968    pub fn set_can_maximize(&self, value: bool) {
969        self.set_value(Self::can_maximize_property(), value)
970    }
971
972    /// The window actions currently allowed by the underlying platform
973    /// (internal upstream: for the window decorations).
974    pub fn allowed_window_actions(&self) -> PlatformAllowedWindowActions {
975        self.allowed_window_actions.get()
976    }
977
978    /// The icon of the window.
979    pub fn icon(&self) -> Option<Rc<WindowIcon>> {
980        self.get_value(Self::icon_property())
981    }
982
983    pub fn set_icon(&self, value: Option<Rc<WindowIcon>>) {
984        self.set_value(Self::icon_property(), value)
985    }
986
987    /// The startup location of the window.
988    pub fn window_startup_location(&self) -> WindowStartupLocation {
989        self.get_value(Self::window_startup_location_property())
990    }
991
992    pub fn set_window_startup_location(&self, value: WindowStartupLocation) {
993        self.set_value(Self::window_startup_location_property(), value)
994    }
995
996    /// The window position in screen coordinates.
997    pub fn position(&self) -> PixelPoint {
998        self.platform_impl().map(|platform_impl| platform_impl.position()).unwrap_or_default()
999    }
1000
1001    pub fn set_position(&self, value: PixelPoint) {
1002        if let Some(platform_impl) = self.platform_impl() {
1003            platform_impl.move_(value);
1004        }
1005        self.position_was_set.set(true);
1006    }
1007
1008    /// Whether this window was opened as a dialog.
1009    pub fn is_dialog(&self) -> bool {
1010        self.showing_as_dialog.get()
1011    }
1012
1013    /// Starts moving a window with left button being held. Should be called
1014    /// from a left mouse button press event handler.
1015    pub fn begin_move_drag(&self, e: &PointerPressedEventArgs) {
1016        if let Some(platform_impl) = self.platform_impl() {
1017            platform_impl.begin_move_drag(e);
1018        }
1019    }
1020
1021    /// Starts resizing a window. This function is used if an application
1022    /// has window resizing controls. Should be called from a left mouse
1023    /// button press event handler.
1024    pub fn begin_resize_drag(&self, edge: WindowEdge, e: &PointerPressedEventArgs) {
1025        if let Some(platform_impl) = self.platform_impl() {
1026            platform_impl.begin_resize_drag(edge, e);
1027        }
1028    }
1029
1030    /// Fired before a window is closed. Disposing the returned handle
1031    /// unsubscribes.
1032    pub fn closing(&self, handler: impl Fn(&WindowClosingEventArgs) + 'static) -> Rc<dyn IDisposable> {
1033        let token = self.closing.add(Rc::new(handler));
1034        let weak = self.to_ref().downgrade();
1035        Disposable::create(move || {
1036            if let Some(this) = weak.upgrade() {
1037                this.closing.remove(token);
1038            }
1039        })
1040    }
1041
1042    /// Closes the window.
1043    pub fn close(&self) {
1044        self.close_core(WindowCloseReason::WindowClosing, true, false);
1045    }
1046
1047    /// Closes a dialog window with the specified result.
1048    ///
1049    /// When the window is shown with [`show_dialog`](Self::show_dialog) or
1050    /// [`show_dialog_typed`](Self::show_dialog_typed), the resulting task
1051    /// will produce the `dialog_result` value when the window is closed.
1052    pub fn close_with_result(&self, dialog_result: Option<BoxedValue>) {
1053        *self.dialog_result.borrow_mut() = dialog_result;
1054        self.close_core(WindowCloseReason::WindowClosing, true, false);
1055    }
1056
1057    /// Closes the window for `reason`; with `ignore_cancel` the window
1058    /// closes even when a closing handler cancels (internal upstream: for
1059    /// the application lifetime).
1060    pub fn close_core(&self, reason: WindowCloseReason, is_programmatic: bool, ignore_cancel: bool) {
1061        let scope = CloseScope { window: self, close: Cell::new(true), ignore_cancel };
1062
1063        if self.should_cancel_close(&WindowClosingEventArgs::new(reason, is_programmatic)) {
1064            scope.close.set(false);
1065        }
1066    }
1067
1068    fn close_internal(&self) {
1069        let children = self.children.borrow().clone();
1070        for (child, _) in children {
1071            child.close_internal();
1072        }
1073
1074        if let Some(platform_impl) = self.platform_impl() {
1075            platform_impl.dispose();
1076        }
1077
1078        self.showing_as_dialog.set(false);
1079
1080        self.set_owner(None);
1081    }
1082
1083    fn should_cancel_close(&self, args: &WindowClosingEventArgs) -> bool {
1084        match self.closing_behavior() {
1085            WindowClosingBehavior::OwnerAndChildWindows => {
1086                let mut can_close = true;
1087
1088                let children = self.children.borrow().clone();
1089                if !children.is_empty() {
1090                    let owner_closing_args;
1091                    let child_args = if args.close_reason() == WindowCloseReason::WindowClosing {
1092                        owner_closing_args =
1093                            WindowClosingEventArgs::new(WindowCloseReason::OwnerWindowClosing, args.is_programmatic());
1094                        &owner_closing_args
1095                    } else {
1096                        args
1097                    };
1098
1099                    for (child, _) in children {
1100                        if child.should_cancel_close(child_args) {
1101                            can_close = false;
1102                        }
1103                    }
1104                }
1105
1106                if can_close {
1107                    self.on_closing(args);
1108
1109                    return args.cancel();
1110                }
1111
1112                true
1113            }
1114            WindowClosingBehavior::OwnerWindowOnly => {
1115                self.on_closing(args);
1116
1117                args.cancel()
1118            }
1119        }
1120    }
1121
1122    fn handle_window_state_changed(&self, state: WindowState) {
1123        // Check if platform impl doesn't lie about the window state getter being usable
1124        debug_assert!(self
1125            .platform_impl()
1126            .is_none_or(|platform_impl| !platform_impl.window_state_getter_is_usable()
1127                || platform_impl.window_state() == state));
1128
1129        self.set_and_raise_cell(Self::window_state_property(), &self.last_window_state, state);
1130
1131        if state == WindowState::Minimized {
1132            self.stop_rendering();
1133        } else {
1134            self.start_rendering();
1135        }
1136
1137        // Update decoration parts and fullscreen popover state for the new window state
1138        self.update_drawn_decoration_parts();
1139    }
1140
1141    /// Fired when the window actions allowed by the platform change
1142    /// (internal upstream: for the window decorations). Disposing the
1143    /// returned handle unsubscribes.
1144    pub fn allowed_window_actions_changed(
1145        &self,
1146        handler: impl Fn(PlatformAllowedWindowActions) + 'static,
1147    ) -> Rc<dyn IDisposable> {
1148        let token = self.allowed_window_actions_changed.add(Rc::new(handler));
1149        let weak = self.to_ref().downgrade();
1150        Disposable::create(move || {
1151            if let Some(this) = weak.upgrade() {
1152                this.allowed_window_actions_changed.remove(token);
1153            }
1154        })
1155    }
1156
1157    fn on_allowed_window_actions_changed(&self, actions: PlatformAllowedWindowActions) {
1158        self.allowed_window_actions.set(actions);
1159        for (_, handler) in self.allowed_window_actions_changed.snapshot().iter() {
1160            handler(actions);
1161        }
1162    }
1163
1164    fn extend_client_area_to_decorations_changed(&self, is_extended: bool) {
1165        self.set_is_extended_into_window_decorations(is_extended);
1166        self.set_off_screen_margin(
1167            self.platform_impl().map(|platform_impl| platform_impl.off_screen_margin()).unwrap_or_default(),
1168        );
1169
1170        self.update_drawn_decorations();
1171    }
1172
1173    fn update_drawn_decorations(&self) {
1174        let parts = self.compute_decoration_parts();
1175
1176        // Detect forced mode: platform needs managed decorations but app hasn't opted in
1177        self.is_forced_decoration_mode.set(parts.is_some() && !self.is_extended_into_window_decorations());
1178
1179        self.top_level_host().update_drawn_decorations(parts, self.window_state(), self.window_decorations_theme());
1180
1181        if parts.is_some() {
1182            // Forward the title bar height hint to the title bar height of the decorations
1183            if let Some(decorations) = self.top_level_host().decorations() {
1184                decorations.set_render_scaling(self.render_scaling());
1185
1186                let hint = self.extend_client_area_title_bar_height_hint();
1187                if hint >= 0.0 {
1188                    decorations.set_title_bar_height_override(hint);
1189                }
1190            }
1191        }
1192
1193        self.update_drawn_decoration_margins();
1194    }
1195
1196    fn on_scaling_changed_update_decorations(&self) {
1197        if let Some(decorations) = self.top_level_host().decorations() {
1198            decorations.set_render_scaling(self.render_scaling());
1199        }
1200    }
1201
1202    /// Updates decoration parts based on current window state without
1203    /// re-creating the decorations instance.
1204    fn update_drawn_decoration_parts(&self) {
1205        if self.top_level_host().decorations().is_none() {
1206            return;
1207        }
1208
1209        self.top_level_host().update_drawn_decorations(
1210            self.compute_decoration_parts(),
1211            self.window_state(),
1212            self.window_decorations_theme(),
1213        );
1214    }
1215
1216    fn compute_decoration_parts(&self) -> Option<crate::chrome::DrawnWindowDecorationParts> {
1217        use crate::chrome::DrawnWindowDecorationParts;
1218        use crate::platform::PlatformRequestedDrawnDecoration;
1219
1220        let platform_impl = self.platform_impl();
1221        if !platform_impl.as_ref().is_some_and(|platform_impl| platform_impl.needs_managed_decorations()) {
1222            return None;
1223        }
1224
1225        let platform_needs = platform_impl
1226            .map(|platform_impl| platform_impl.requested_drawn_decorations())
1227            .unwrap_or(PlatformRequestedDrawnDecoration::NONE);
1228        let mut parts = DrawnWindowDecorationParts::NONE;
1229        if self.window_decorations() != WindowDecorations::None {
1230            if platform_needs.contains(PlatformRequestedDrawnDecoration::TITLE_BAR)
1231                && self.window_decorations() == WindowDecorations::Full
1232            {
1233                parts |= DrawnWindowDecorationParts::TITLE_BAR;
1234            }
1235            if platform_needs.contains(PlatformRequestedDrawnDecoration::SHADOW) {
1236                parts |= DrawnWindowDecorationParts::SHADOW;
1237            }
1238            if platform_needs.contains(PlatformRequestedDrawnDecoration::BORDER) {
1239                parts |= DrawnWindowDecorationParts::BORDER;
1240            }
1241            if platform_needs.contains(PlatformRequestedDrawnDecoration::RESIZE_GRIPS) && self.can_resize() {
1242                parts |= DrawnWindowDecorationParts::RESIZE_GRIPS;
1243            }
1244
1245            // In fullscreen: no shadow, border, resize grips, or titlebar (popover takes over)
1246            if self.window_state() == WindowState::FullScreen {
1247                parts &= !(DrawnWindowDecorationParts::SHADOW
1248                    | DrawnWindowDecorationParts::BORDER
1249                    | DrawnWindowDecorationParts::RESIZE_GRIPS
1250                    | DrawnWindowDecorationParts::TITLE_BAR);
1251            }
1252            // In maximized: no shadow, border, or resize grips (titlebar stays)
1253            else if self.window_state() == WindowState::Maximized {
1254                parts &= !(DrawnWindowDecorationParts::SHADOW
1255                    | DrawnWindowDecorationParts::BORDER
1256                    | DrawnWindowDecorationParts::RESIZE_GRIPS);
1257            }
1258        }
1259
1260        Some(parts)
1261    }
1262
1263    fn on_title_bar_height_hint_changed(&self) {
1264        let Some(decorations) = self.top_level_host().decorations() else { return };
1265
1266        decorations.set_title_bar_height_override(self.extend_client_area_title_bar_height_hint());
1267
1268        self.update_drawn_decoration_margins();
1269    }
1270
1271    /// Called by the host of the window when the effective geometry of the
1272    /// decorations changes (e.g. the theme changes the default values, or
1273    /// the enabled parts change).
1274    pub(crate) fn on_drawn_decorations_geometry_changed(&self) {
1275        self.update_drawn_decoration_margins();
1276    }
1277
1278    fn update_drawn_decoration_margins(&self) {
1279        if let Some(decorations) = self.top_level_host().decorations() {
1280            use crate::chrome::DrawnWindowDecorationParts;
1281
1282            let parts = decorations.enabled_parts();
1283            let title_bar_height =
1284                if parts.contains(DrawnWindowDecorationParts::TITLE_BAR) { decorations.title_bar_height() } else { 0.0 };
1285            let frame = if parts.contains(DrawnWindowDecorationParts::BORDER) {
1286                decorations.frame_thickness()
1287            } else {
1288                Thickness::default()
1289            };
1290            let shadow = if parts.contains(DrawnWindowDecorationParts::SHADOW) {
1291                decorations.shadow_thickness()
1292            } else {
1293                Thickness::default()
1294            };
1295
1296            if let Some(platform_impl) = self.platform_impl() {
1297                platform_impl.set_shadow_extents(shadow);
1298            }
1299
1300            let margin = Thickness::new(
1301                frame.left + shadow.left,
1302                title_bar_height + frame.top + shadow.top,
1303                frame.right + shadow.right,
1304                frame.bottom + shadow.bottom,
1305            );
1306
1307            if self.is_forced_decoration_mode.get() {
1308                // In forced mode, app is unaware of decorations.
1309                // The host insets the window child; the window decoration margin stays zero.
1310                self.set_window_decoration_margin(Thickness::default());
1311                self.top_level_host().set_decoration_inset(margin);
1312            } else {
1313                // In extended mode, app handles the margin itself.
1314                self.set_window_decoration_margin(margin);
1315                self.top_level_host().set_decoration_inset(Thickness::default());
1316            }
1317            return;
1318        }
1319
1320        // Only use platform margins if drawn decorations are not active
1321        let platform_impl = self.platform_impl();
1322        self.set_window_decoration_margin(
1323            platform_impl.as_ref().map(|platform_impl| platform_impl.extended_margins()).unwrap_or_default(),
1324        );
1325        self.top_level_host().set_decoration_inset(Thickness::default());
1326        if let Some(platform_impl) = platform_impl {
1327            platform_impl.set_shadow_extents(Thickness::default());
1328        }
1329    }
1330
1331    /// Shows the window as a child of `owner`.
1332    ///
1333    /// # Panics
1334    /// Panics when the window has already been closed, and when the owner
1335    /// is closed, not visible or the window itself.
1336    pub fn show_with_owner(&self, owner: &Window) {
1337        self.show_core(Some(owner), false);
1338    }
1339
1340    fn ensure_state_before_show(&self) {
1341        if self.platform_impl().is_none() {
1342            panic!("Cannot re-show a closed window.");
1343        }
1344    }
1345
1346    fn ensure_parent_state_before_show(&self, owner: &Window) {
1347        if owner.platform_impl().is_none() {
1348            panic!("Cannot show a window with a closed owner.");
1349        }
1350
1351        if std::ptr::eq(owner, self) {
1352            panic!("A Window cannot be its own owner.");
1353        }
1354
1355        if !owner.is_visible() {
1356            panic!("Cannot show window with non-visible owner.");
1357        }
1358    }
1359
1360    fn show_core(&self, owner: Option<&Window>, modal: bool) -> Option<DialogCompletion> {
1361        let _freeze = self.freeze_visibility_change_handling();
1362
1363        self.ensure_state_before_show();
1364
1365        if modal && owner.is_none() {
1366            panic!("A dialog requires an owner.");
1367        }
1368        if let Some(owner) = owner {
1369            self.ensure_parent_state_before_show(owner);
1370        }
1371
1372        if self.shown.get() {
1373            if modal {
1374                panic!("The window is already being shown.");
1375            }
1376            return None;
1377        }
1378
1379        self.showing_as_dialog.set(modal);
1380        self.raise_event(&RoutedEventArgs::with_event(Self::window_opened_event()));
1381
1382        self.ensure_initialized();
1383        self.apply_styling();
1384
1385        // Enable drawn decorations before layout so margins are computed
1386        self.update_drawn_decorations();
1387
1388        // In forced mode, adjust the client size to reflect usable content area
1389        if self.is_forced_decoration_mode.get() {
1390            let inset = self.top_level_host().decoration_inset();
1391            self.set_client_size(Size::new(
1392                (self.client_size().width - inset.left - inset.right).max(0.0),
1393                (self.client_size().height - inset.top - inset.bottom).max(0.0),
1394            ));
1395        }
1396
1397        self.shown.set(true);
1398        self.set_is_visible(true);
1399
1400        self.set_effective_icon(self.icon());
1401
1402        // If window position was not set before then platform may provide incorrect scaling at this time,
1403        // but we need it for proper calculation of position and in some cases size (size to content)
1404        self.set_expected_scaling(owner);
1405
1406        let mut initial_size = Size::new(
1407            if self.width().is_nan() { self.client_size().width } else { self.width() },
1408            if self.height().is_nan() { self.client_size().height } else { self.height() },
1409        );
1410
1411        let min_max = MinMax::new(self);
1412
1413        initial_size = Size::new(
1414            MathUtilities::clamp(initial_size.width, min_max.min_width, min_max.max_width),
1415            MathUtilities::clamp(initial_size.height, min_max.min_height, min_max.max_height),
1416        );
1417
1418        let mut client_size_changed = initial_size != self.client_size();
1419        self.set_client_size(initial_size); // The client size is required for measure and arrange
1420
1421        // this will call arrange_set_bounds
1422        self.layout_manager().execute_initial_layout_pass();
1423
1424        if self.size_to_content().contains(SizeToContent::WIDTH) {
1425            initial_size = initial_size.with_width(MathUtilities::clamp(
1426                self.arrange_bounds.get().width,
1427                min_max.min_width,
1428                min_max.max_width,
1429            ));
1430            client_size_changed |= initial_size != self.client_size();
1431            self.set_client_size(initial_size);
1432        }
1433
1434        if self.size_to_content().contains(SizeToContent::HEIGHT) {
1435            initial_size = initial_size.with_height(MathUtilities::clamp(
1436                self.arrange_bounds.get().height,
1437                min_max.min_height,
1438                min_max.max_height,
1439            ));
1440            client_size_changed |= initial_size != self.client_size();
1441            self.set_client_size(initial_size);
1442        }
1443
1444        self.set_owner(owner.map(|owner| owner.to_ref().upcast()));
1445
1446        self.apply_window_startup_location(owner);
1447
1448        self.set_desktop_scaling_override(None);
1449
1450        // In forced mode, compare against adjusted platform size
1451        let platform_client_size =
1452            self.platform_impl().map(|platform_impl| platform_impl.client_size()).unwrap_or_default();
1453        let comparable_client_size = if self.is_forced_decoration_mode.get() {
1454            let inset = self.top_level_host().decoration_inset();
1455            Size::new(
1456                (platform_client_size.width - inset.left - inset.right).max(0.0),
1457                (platform_client_size.height - inset.top - inset.bottom).max(0.0),
1458            )
1459        } else {
1460            platform_client_size
1461        };
1462
1463        if client_size_changed || self.client_size() != comparable_client_size {
1464            // Previously it was called before the initial layout pass
1465            self.resize_platform_impl(self.client_size(), WindowResizeReason::Layout);
1466
1467            // we do not want the platform resize to trigger handle_resized yet because it will set the width
1468            // and height. So perform some important actions from handle_resized
1469
1470            self.renderer().resized(self.client_size());
1471            self.on_resized(&WindowResizedEventArgs::new(self.client_size(), WindowResizeReason::Layout));
1472
1473            if !self.width().is_nan() {
1474                self.set_width(self.client_size().width);
1475            }
1476            if !self.height().is_nan() {
1477                self.set_height(self.client_size().height);
1478            }
1479        }
1480
1481        self.set_frame_size(self.platform_impl().and_then(|platform_impl| platform_impl.frame_size()));
1482
1483        self.can_handle_resized.set(true);
1484
1485        self.start_rendering();
1486        if let Some(platform_impl) = self.platform_impl() {
1487            platform_impl.show(self.show_activated(), modal);
1488        }
1489
1490        let mut result = None;
1491        if let (true, Some(owner)) = (modal, owner) {
1492            let completion = DialogCompletion::new();
1493            let weak = self.to_ref().downgrade();
1494
1495            let taken = Cell::new(false);
1496            let closed_subscription = TopLevel::closed(self, {
1497                let weak = weak.clone();
1498                move || {
1499                    if taken.replace(true) {
1500                        return;
1501                    }
1502                    let modal_subscription =
1503                        weak.upgrade().and_then(|this| this.modal_subscription.borrow().clone());
1504                    if let Some(modal_subscription) = modal_subscription {
1505                        modal_subscription.dispose();
1506                    }
1507                }
1508            });
1509            let complete = Disposable::create({
1510                let owner = owner.to_ref().downgrade();
1511                let completion = completion.clone();
1512                move || {
1513                    let this = weak.upgrade();
1514                    if let Some(this) = &this {
1515                        *this.modal_subscription.borrow_mut() = None;
1516                    }
1517                    if let Some(owner) = owner.upgrade() {
1518                        owner.activate();
1519                    }
1520                    completion.set_result(this.and_then(|this| this.dialog_result.borrow().clone()));
1521                }
1522            });
1523
1524            let disposables: Rc<dyn IDisposable> =
1525                Rc::new(CompositeDisposable::from_disposables([closed_subscription, complete]));
1526            *self.modal_subscription.borrow_mut() = Some(disposables);
1527            result = Some(completion);
1528        }
1529
1530        self.on_opened();
1531        if !modal {
1532            self.was_shown_before.set(true);
1533        }
1534
1535        result
1536    }
1537
1538    fn resize_platform_impl(&self, mut size: Size, reason: WindowResizeReason) {
1539        let Some(platform_impl) = self.platform_impl() else { return };
1540
1541        // In forced mode, add decoration inset so platform gets full frame size
1542        if self.is_forced_decoration_mode.get() {
1543            let inset = self.top_level_host().decoration_inset();
1544            size = Size::new(size.width + inset.left + inset.right, size.height + inset.top + inset.bottom);
1545            if platform_impl.client_size() != size {
1546                platform_impl.resize(size, reason);
1547            }
1548        } else {
1549            platform_impl.resize(size, reason);
1550        }
1551    }
1552
1553    /// Shows the window as a dialog owned by `owner`.
1554    ///
1555    /// Returns a task that can be used to track the lifetime of the dialog:
1556    /// it completes, from a dispatcher job, with the dialog result (see
1557    /// [`close_with_result`](Self::close_with_result)) once the dialog has
1558    /// closed. Nothing blocks: the caller keeps running and the task is
1559    /// observed through the dispatcher.
1560    ///
1561    /// # Panics
1562    /// Panics when the window has already been closed or is already shown,
1563    /// and when the owner is closed, not visible or the window itself.
1564    pub fn show_dialog(&self, owner: &Window) -> DispatcherTask<Option<BoxedValue>> {
1565        let completion = self.show_core(Some(owner), true).expect("showing a dialog creates its completion");
1566        Dispatcher::ui_thread().invoke_async_task_local(move || completion)
1567    }
1568
1569    /// Shows the window as a dialog owned by `owner`; `TResult` is the type
1570    /// of the result produced by the dialog.
1571    ///
1572    /// Returns a task that can be used to retrieve the result of the dialog
1573    /// when it closes: the value passed to
1574    /// [`close_with_result`](Self::close_with_result), or the default of
1575    /// `TResult` when the dialog closed without a result. The task fails
1576    /// when the dialog result is not a `TResult`.
1577    ///
1578    /// # Panics
1579    /// See [`show_dialog`](Self::show_dialog).
1580    pub fn show_dialog_typed<TResult: Clone + Default + 'static>(&self, owner: &Window) -> DispatcherTask<TResult> {
1581        let completion = self.show_core(Some(owner), true).expect("showing a dialog creates its completion");
1582        Dispatcher::ui_thread().invoke_async_task_local(move || async move {
1583            match completion.await {
1584                Some(dialog_result) => match dialog_result.downcast_ref::<TResult>() {
1585                    Some(dialog_result) => dialog_result.clone(),
1586                    None => panic!(
1587                        "Unable to cast the dialog result of type {} to {}.",
1588                        dialog_result.any_value_type_name(),
1589                        std::any::type_name::<TResult>()
1590                    ),
1591                },
1592                None => TResult::default(),
1593            }
1594        })
1595    }
1596
1597    /// Sorts the windows ascending by their Z order - the topmost window
1598    /// will be the last in the list.
1599    ///
1600    /// # Panics
1601    /// Panics when one of the windows is closed, and when no windowing
1602    /// platform is registered.
1603    pub fn sort_windows_by_z_order(windows: &mut [Ref<Window>]) {
1604        if windows.len() <= 1 {
1605            return;
1606        }
1607
1608        let platform = FerroLocator::current().get_required_service::<dyn IWindowingPlatform>();
1609
1610        let window_impls: Vec<Rc<dyn IWindowImpl>> = windows
1611            .iter()
1612            .enumerate()
1613            .map(|(i, window)| window.platform_impl().unwrap_or_else(|| panic!("Invalid window at index {i}")))
1614            .collect();
1615
1616        let mut z_order = vec![0i64; windows.len()];
1617        platform.get_windows_z_order(&window_impls, &mut z_order);
1618
1619        let mut keyed: Vec<(i64, Ref<Window>)> = z_order.into_iter().zip(windows.iter().cloned()).collect();
1620        keyed.sort_by_key(|(z, _)| *z);
1621        for (slot, (_, window)) in windows.iter_mut().zip(keyed) {
1622            *slot = window;
1623        }
1624    }
1625
1626    fn update_enabled(&self) {
1627        let is_enabled = !self.children.borrow().iter().any(|(_, is_dialog)| *is_dialog);
1628
1629        if let Some(platform_impl) = self.platform_impl() {
1630            platform_impl.set_enabled(is_enabled);
1631        }
1632    }
1633
1634    fn add_child(&self, window: &Window, is_dialog: bool) {
1635        self.children.borrow_mut().push((window.to_ref(), is_dialog));
1636        self.update_enabled();
1637    }
1638
1639    fn remove_child(&self, window: &Window) {
1640        self.children.borrow_mut().retain(|(child, _)| !std::ptr::eq(&**child, window));
1641
1642        self.update_enabled();
1643    }
1644
1645    fn on_got_input_when_disabled(&self) {
1646        let first_dialog_child =
1647            self.children.borrow().iter().find(|(_, is_dialog)| *is_dialog).map(|(child, _)| child.clone());
1648
1649        if let Some(first_dialog_child) = first_dialog_child {
1650            first_dialog_child.on_got_input_when_disabled();
1651        } else {
1652            self.activate();
1653        }
1654    }
1655
1656    fn set_expected_scaling(&self, owner: Option<&Window>) {
1657        if self.was_shown_before.get() {
1658            return;
1659        }
1660
1661        let location = self.get_effective_window_startup_location(owner);
1662
1663        match location {
1664            WindowStartupLocation::CenterOwner => {
1665                self.set_desktop_scaling_override(owner.map(|owner| owner.desktop_scaling()));
1666            }
1667            WindowStartupLocation::CenterScreen => {
1668                let scaling = owner
1669                    .map(|owner| owner.desktop_scaling())
1670                    .or_else(|| self.screens().screen_from_point(self.position()).map(|screen| screen.scaling()))
1671                    .or_else(|| self.screens().primary().map(|screen| screen.scaling()));
1672                self.set_desktop_scaling_override(scaling);
1673            }
1674            WindowStartupLocation::Manual => {
1675                self.set_desktop_scaling_override(
1676                    self.screens().screen_from_point(self.position()).map(|screen| screen.scaling()),
1677                );
1678            }
1679        }
1680    }
1681
1682    fn get_effective_window_startup_location(&self, owner: Option<&Window>) -> WindowStartupLocation {
1683        let mut startup_location = self.window_startup_location();
1684
1685        if startup_location == WindowStartupLocation::CenterOwner
1686            && owner.is_none_or(|owner| owner.window_state() == WindowState::Minimized)
1687        {
1688            // If startup location is CenterOwner, but owner is null or minimized then fall back
1689            // to CenterScreen. This behavior is consistent with WPF.
1690            startup_location = WindowStartupLocation::CenterScreen;
1691        }
1692
1693        startup_location
1694    }
1695
1696    fn apply_window_startup_location(&self, owner: Option<&Window>) {
1697        if self.was_shown_before.get() {
1698            return;
1699        }
1700
1701        let startup_location = self.get_effective_window_startup_location(owner);
1702
1703        // Use frame size, falling back to client size if the platform can't give it to us.
1704        let platform_frame = self
1705            .platform_impl()
1706            .and_then(|platform_impl| platform_impl.frame_size().map(|frame_size| (frame_size, platform_impl.client_size())));
1707        let rect = match platform_frame {
1708            Some((frame_size, platform_client_size)) => {
1709                // Platform may calculate the frame size with incorrect scaling, so do not trust the value.
1710                let diff = frame_size - platform_client_size;
1711                PixelRect::from_size(PixelSize::from_size(self.client_size() + diff, self.desktop_scaling()))
1712            }
1713            None => PixelRect::from_size(PixelSize::from_size(self.client_size(), self.desktop_scaling())),
1714        };
1715
1716        let apply_screen_constraint = |screen: Option<&Rc<Screen>>, mut child_rect: PixelRect| -> PixelRect {
1717            if let Some(constraint) = screen.map(|screen| screen.working_area()) {
1718                let max_x = constraint.right() - rect.width;
1719                let max_y = constraint.bottom() - rect.height;
1720
1721                if constraint.x <= max_x {
1722                    child_rect = child_rect.with_x(MathUtilities::clamp_i32(child_rect.x, constraint.x, max_x));
1723                }
1724                if constraint.y <= max_y {
1725                    child_rect = child_rect.with_y(MathUtilities::clamp_i32(child_rect.y, constraint.y, max_y));
1726                }
1727            }
1728
1729            child_rect
1730        };
1731
1732        if startup_location == WindowStartupLocation::CenterScreen {
1733            let mut screen: Option<Rc<Screen>> = None;
1734
1735            if let Some(owner) = owner {
1736                screen = self
1737                    .screens()
1738                    .screen_from_window(owner)
1739                    .or_else(|| self.screens().screen_from_point(owner.position()));
1740            }
1741
1742            let screen = screen
1743                .or_else(|| self.screens().screen_from_point(self.position()))
1744                .or_else(|| self.screens().primary());
1745
1746            if let Some(screen) = screen {
1747                let mut child_rect = screen.working_area().center_rect(rect);
1748
1749                if self.screens().screen_from_point(child_rect.position()).is_none() {
1750                    child_rect = apply_screen_constraint(Some(&screen), child_rect);
1751                }
1752
1753                self.set_position(child_rect.position());
1754            }
1755        } else if startup_location == WindowStartupLocation::CenterOwner {
1756            let owner = owner.expect("the effective startup location is the owner's center only with an owner");
1757            let owner_size = owner.frame_size().unwrap_or_else(|| owner.client_size());
1758            let owner_rect = PixelRect::from_position_size(
1759                owner.position(),
1760                PixelSize::from_size(owner_size, owner.desktop_scaling()),
1761            );
1762            let mut child_rect = owner_rect.center_rect(rect);
1763
1764            let screen = self.screens().screen_from_window(owner);
1765
1766            child_rect = apply_screen_constraint(screen.as_ref(), child_rect);
1767
1768            self.set_position(child_rect.position());
1769        }
1770
1771        // Platform returns incorrect scaling, forcing setting position may fix it
1772        if !self.position_was_set.get() {
1773            if let Some(platform_impl) = self.platform_impl() {
1774                if self.desktop_scaling() != platform_impl.desktop_scaling() {
1775                    platform_impl.move_(self.position());
1776                }
1777            }
1778        }
1779    }
1780
1781    fn set_effective_icon(&self, icon: Option<Rc<WindowIcon>>) {
1782        if let Some(platform_impl) = self.platform_impl() {
1783            platform_impl.set_icon(icon.map(|icon| icon.platform_impl()));
1784        }
1785    }
1786}