Skip to main content

ferroui_controls/
window_base.rs

1use crate::platform::{ITopLevelImpl, IWindowBaseImpl};
2use crate::primitives::TemplatedControlImpl;
3use crate::{
4    ContentControlImpl, ControlImpl, PixelPointEventArgs, Screens, TopLevel, TopLevelImpl, TopLevelImplExt,
5    WindowResizeReason, WindowResizedEventArgs,
6};
7use ferroui_base::input::InputElementImpl;
8use ferroui_base::interactivity::InteractiveImpl;
9use ferroui_base::layout::{ILayoutManager, LayoutHelper, LayoutableImpl};
10use ferroui_base::reactive::{Disposable, IDisposable};
11use ferroui_base::styling::Container;
12use ferroui_base::utilities::HandlerList;
13use ferroui_base::{
14    ferro_class, ferro_impl_classes, ferro_property, instantiate, DirectProperty, FerroObjectImpl,
15    FerroObjectImplExt, FerroProperty, FerroPropertyChangedEventArgs, IFerroDependencyResolver, PixelPoint, Rect,
16    Ref, Size, StyledElementImpl, StyledProperty, Visual, VisualImpl, WeakRef,
17};
18use std::cell::{Cell, RefCell};
19use std::rc::Rc;
20
21/// Base class for top-level windows.
22///
23/// This class acts as a base for top level windows such as `Window` and
24/// `PopupRoot`. It handles scheduling layout, styling and rendering as well
25/// as tracking the window client size and window activation.
26#[repr(C)]
27pub struct WindowBase {
28    base: TopLevel,
29    window_base_impl: RefCell<Option<Rc<dyn IWindowBaseImpl>>>,
30    /// Whether the single-argument constructor was used: it binds `Topmost`
31    /// to the platform implementation and takes its frame size.
32    default_resolver: Cell<bool>,
33    has_executed_initial_layout_pass: Cell<bool>,
34    is_active: Cell<bool>,
35    ignore_visibility_changes: Cell<i32>,
36    owner: RefCell<Option<WeakRef<WindowBase>>>,
37    desktop_scaling_override: Cell<Option<f64>>,
38    activated: HandlerList<dyn Fn()>,
39    deactivated: HandlerList<dyn Fn()>,
40    position_changed: HandlerList<dyn Fn(&PixelPointEventArgs)>,
41    resized: HandlerList<dyn Fn(&WindowResizedEventArgs)>,
42}
43
44ferro_class! {
45    WindowBase: TopLevel, virtuals WindowBaseImpl: TopLevelImpl {
46        /// Hides the window.
47        fn hide(this);
48        /// Shows the window.
49        fn show(this);
50        /// Raises the `Resized` event.
51        fn on_resized(this, e: &WindowResizedEventArgs);
52        /// Called during the arrange pass to set the size of the window;
53        /// returns the size to arrange the content with.
54        fn arrange_set_bounds(this, size: Size) -> Size;
55        /// Called when the `IsVisible` property changes.
56        fn is_visible_changed(this, e: &FerroPropertyChangedEventArgs<'_>);
57    }
58}
59
60ferro_impl_classes!(
61    WindowBase: StyledElementImpl,
62    VisualImpl,
63    InteractiveImpl,
64    InputElementImpl,
65    ControlImpl,
66    TemplatedControlImpl,
67    ContentControlImpl
68);
69
70impl FerroObjectImpl for WindowBase {
71    fn constructed(this: &Self) {
72        Self::parent_constructed(this);
73
74        let window_base_impl = this.window_base_impl.borrow().clone().expect("the window has a platform implementation");
75        let weak = this.to_ref().downgrade();
76        window_base_impl.set_activated(Some(Rc::new({
77            let weak = weak.clone();
78            move || {
79                if let Some(this) = weak.upgrade() {
80                    this.handle_activated();
81                }
82            }
83        })));
84        window_base_impl.set_deactivated(Some(Rc::new({
85            let weak = weak.clone();
86            move || {
87                if let Some(this) = weak.upgrade() {
88                    this.handle_deactivated();
89                    this.raise_platform_deactivated();
90                }
91            }
92        })));
93        window_base_impl.set_position_changed(Some(Rc::new({
94            let weak = weak.clone();
95            move |pos| {
96                if let Some(this) = weak.upgrade() {
97                    this.handle_position_changed(pos);
98                    this.raise_platform_position_changed(pos);
99                }
100            }
101        })));
102
103        if this.default_resolver.get() {
104            this.create_platform_impl_binding(Self::topmost_property(), move |topmost: bool| {
105                if let Some(platform_impl) = weak.upgrade().and_then(|this| this.platform_impl()) {
106                    platform_impl.set_topmost(topmost);
107                }
108            });
109            this.set_frame_size(window_base_impl.frame_size());
110        }
111    }
112
113    fn on_property_changed(this: &Self, change: &FerroPropertyChangedEventArgs<'_>) {
114        Self::parent_on_property_changed(this, change);
115
116        if change.property() == Visual::is_visible_property().as_property() {
117            let (_, new_value) = change.get_old_and_new_value::<bool>();
118            if let Some(visual_root) = this.visual_root() {
119                visual_root.set_is_visible(new_value);
120            }
121            this.is_visible_changed(change);
122        }
123    }
124}
125
126impl LayoutableImpl for WindowBase {
127    fn measure_core(this: &Self, available_size: Size) -> Size {
128        this.apply_styling();
129        this.apply_template();
130
131        let constraint = LayoutHelper::apply_layout_constraints(this, available_size);
132
133        Container::set_query_provider_size(this, constraint.width, constraint.height, Container::get_sizing(this));
134
135        this.measure_override(constraint)
136    }
137
138    fn arrange_core(this: &Self, final_rect: Rect) {
139        let constraint = this.arrange_set_bounds(final_rect.size());
140        let arrange_size = this.arrange_override(constraint);
141        this.set_bounds(Rect::from_position_size(final_rect.position(), arrange_size));
142    }
143}
144
145impl TopLevelImpl for WindowBase {
146    fn on_closed(this: &Self) {
147        // Window must manually raise Loaded/Unloaded events as it is a visual root and
148        // does not raise the attached/detached visual tree notifications
149        this.on_unloaded_core();
150
151        Self::parent_on_closed(this);
152    }
153
154    fn on_opened(this: &Self) {
155        this.set_frame_size(this.platform_impl().and_then(|platform_impl| platform_impl.frame_size()));
156
157        // Window must manually raise Loaded/Unloaded events as it is a visual root and
158        // does not raise the attached/detached visual tree notifications
159        this.schedule_on_loaded_core();
160
161        Self::parent_on_opened(this);
162    }
163
164    fn handle_closed(this: &Self) {
165        let _freeze = this.freeze_visibility_change_handling();
166
167        this.set_is_visible(false);
168
169        if this.is_active() {
170            this.handle_deactivated();
171        }
172
173        if this.is_focus_scope() {
174            this.focus_manager().remove_focus_root(this);
175        }
176
177        Self::parent_handle_closed(this);
178        *this.window_base_impl.borrow_mut() = None;
179    }
180
181    fn handle_resized(this: &Self, client_size: Size, reason: WindowResizeReason) {
182        this.set_frame_size(this.platform_impl().and_then(|platform_impl| platform_impl.frame_size()));
183
184        let client_size_changed = this.client_size() != client_size;
185
186        this.set_client_size(client_size);
187
188        this.on_resized(&WindowResizedEventArgs::new(client_size, reason));
189
190        if client_size_changed {
191            this.layout_manager().execute_layout_pass();
192            this.renderer().resized(client_size);
193        }
194    }
195}
196
197impl WindowBaseImpl for WindowBase {
198    fn hide(this: &Self) {
199        let _freeze = this.freeze_visibility_change_handling();
200
201        this.stop_rendering();
202        if let Some(platform_impl) = this.platform_impl() {
203            platform_impl.hide();
204        }
205        this.set_is_visible(false);
206    }
207
208    fn show(this: &Self) {
209        let _freeze = this.freeze_visibility_change_handling();
210
211        this.ensure_initialized();
212        this.apply_styling();
213        this.set_is_visible(true);
214
215        if !this.has_executed_initial_layout_pass.get() {
216            this.layout_manager().execute_initial_layout_pass();
217            this.has_executed_initial_layout_pass.set(true);
218        }
219
220        if let Some(platform_impl) = this.platform_impl() {
221            platform_impl.show(true, false);
222        }
223        this.start_rendering();
224        this.on_opened();
225    }
226
227    fn on_resized(this: &Self, e: &WindowResizedEventArgs) {
228        for (_, handler) in this.resized.snapshot().iter() {
229            handler(e);
230        }
231    }
232
233    fn arrange_set_bounds(_this: &Self, size: Size) -> Size {
234        size
235    }
236
237    fn is_visible_changed(this: &Self, e: &FerroPropertyChangedEventArgs<'_>) {
238        if this.ignore_visibility_changes.get() == 0 {
239            let (_, new_value) = e.get_old_and_new_value::<bool>();
240            if new_value {
241                this.show();
242            } else {
243                this.hide();
244            }
245        }
246    }
247}
248
249/// Keeps visibility changes from showing or hiding the window while it is
250/// alive.
251pub struct IgnoreVisibilityChangesGuard {
252    window_base: Ref<WindowBase>,
253}
254
255impl Drop for IgnoreVisibilityChangesGuard {
256    fn drop(&mut self) {
257        let count = &self.window_base.ignore_visibility_changes;
258        count.set(count.get() - 1);
259    }
260}
261
262ferroui_base::ferro_properties! { impl WindowBase {
263    ferro_property!(
264        /// Defines the `IsActive` property.
265        pub fn is_active_property() -> DirectProperty<WindowBase, bool> {
266            FerroProperty::register_direct::<WindowBase, _>("IsActive", |o| o.is_active(), None, false)
267        }
268    );
269
270    ferro_property!(
271        /// Defines the `Owner` property.
272        pub fn owner_property() -> DirectProperty<WindowBase, Option<Ref<WindowBase>>> {
273            FerroProperty::register_direct::<WindowBase, _>("Owner", |o| o.owner(), None, None)
274        }
275    );
276
277    ferro_property!(
278        /// Defines the `Topmost` property.
279        pub fn topmost_property() -> StyledProperty<bool> {
280            FerroProperty::register::<WindowBase, _>("Topmost", false)
281        }
282    );
283} }
284
285impl WindowBase {
286    fn static_constructor() {
287        Visual::is_visible_property().override_default_value::<WindowBase>(false);
288    }
289
290    /// Field initialisation of a window over the platform implementation
291    /// `platform_impl`, with the services of the current service locator.
292    pub fn construct(platform_impl: Rc<dyn IWindowBaseImpl>) -> Self {
293        let mut this = Self::construct_with_resolver(platform_impl, None);
294        this.default_resolver = Cell::new(true);
295        this
296    }
297
298    /// Field initialisation of a window; `dependency_resolver` is the
299    /// dependency resolver to use, the current service locator when `None`.
300    ///
301    /// Unlike [`construct`](Self::construct) this does not forward `Topmost`
302    /// to the platform implementation or take its frame size.
303    pub fn construct_with_resolver(
304        platform_impl: Rc<dyn IWindowBaseImpl>,
305        dependency_resolver: Option<Rc<dyn IFerroDependencyResolver>>,
306    ) -> Self {
307        let top_level_impl: Rc<dyn ITopLevelImpl> = platform_impl.clone();
308        Self {
309            base: TopLevel::construct_with_resolver(top_level_impl, dependency_resolver),
310            window_base_impl: RefCell::new(Some(platform_impl)),
311            default_resolver: Cell::new(false),
312            has_executed_initial_layout_pass: Cell::new(false),
313            is_active: Cell::new(false),
314            ignore_visibility_changes: Cell::new(0),
315            owner: RefCell::new(None),
316            desktop_scaling_override: Cell::new(None),
317            activated: HandlerList::new(),
318            deactivated: HandlerList::new(),
319            position_changed: HandlerList::new(),
320            resized: HandlerList::new(),
321        }
322    }
323
324    /// Creates a window base over a platform implementation (C#
325    /// `new WindowBase(impl)`).
326    pub fn new(platform_impl: Rc<dyn IWindowBaseImpl>) -> Ref<Self> {
327        instantiate(Self::construct(platform_impl))
328    }
329
330    /// Whether visibility changes are currently ignored (protected
331    /// upstream).
332    pub fn ignore_visibility_changes(&self) -> bool {
333        self.ignore_visibility_changes.get() > 0
334    }
335
336    /// Ignores visibility changes until the returned guard is dropped
337    /// (private protected upstream: for derived classes).
338    pub fn freeze_visibility_change_handling(&self) -> IgnoreVisibilityChangesGuard {
339        self.ignore_visibility_changes.set(self.ignore_visibility_changes.get() + 1);
340        IgnoreVisibilityChangesGuard { window_base: self.to_ref() }
341    }
342
343    fn subscribe<F: ?Sized + 'static>(
344        &self,
345        select: fn(&WindowBase) -> &HandlerList<F>,
346        handler: Rc<F>,
347    ) -> Rc<dyn IDisposable> {
348        let token = select(self).add(handler);
349        let weak = self.to_ref().downgrade();
350        Disposable::create(move || {
351            if let Some(this) = weak.upgrade() {
352                select(&this).remove(token);
353            }
354        })
355    }
356
357    /// Fired when the window is activated. Disposing the returned handle
358    /// unsubscribes.
359    pub fn activated(&self, handler: impl Fn() + 'static) -> Rc<dyn IDisposable> {
360        self.subscribe::<dyn Fn()>(|w| &w.activated, Rc::new(handler))
361    }
362
363    /// Fired when the window is deactivated. Disposing the returned handle
364    /// unsubscribes.
365    pub fn deactivated(&self, handler: impl Fn() + 'static) -> Rc<dyn IDisposable> {
366        self.subscribe::<dyn Fn()>(|w| &w.deactivated, Rc::new(handler))
367    }
368
369    /// Fired when the window position is changed. Disposing the returned
370    /// handle unsubscribes.
371    pub fn position_changed(&self, handler: impl Fn(&PixelPointEventArgs) + 'static) -> Rc<dyn IDisposable> {
372        self.subscribe::<dyn Fn(&PixelPointEventArgs)>(|w| &w.position_changed, Rc::new(handler))
373    }
374
375    /// Fired when the window is resized. Disposing the returned handle
376    /// unsubscribes.
377    pub fn resized(&self, handler: impl Fn(&WindowResizedEventArgs) + 'static) -> Rc<dyn IDisposable> {
378        self.subscribe::<dyn Fn(&WindowResizedEventArgs)>(|w| &w.resized, Rc::new(handler))
379    }
380
381    /// The platform-specific window implementation; `None` once the window
382    /// has closed.
383    pub fn platform_impl(&self) -> Option<Rc<dyn IWindowBaseImpl>> {
384        TopLevel::platform_impl(self)?;
385        self.window_base_impl.borrow().clone()
386    }
387
388    /// Whether the window is active.
389    pub fn is_active(&self) -> bool {
390        self.is_active.get()
391    }
392
393    fn set_is_active(&self, value: bool) {
394        self.set_and_raise_cell(Self::is_active_property(), &self.is_active, value);
395    }
396
397    /// The screens of the platform.
398    ///
399    /// # Panics
400    /// Panics when the windowing backend does not provide screens.
401    pub fn screens(&self) -> Rc<Screens> {
402        TopLevel::screens(self).expect("Windowing backend wasn't properly initialized.")
403    }
404
405    /// The owner of the window.
406    pub fn owner(&self) -> Option<Ref<WindowBase>> {
407        self.owner.borrow().as_ref().and_then(WeakRef::upgrade)
408    }
409
410    /// Sets the owner of the window (protected upstream: for derived
411    /// classes).
412    pub fn set_owner(&self, value: Option<Ref<WindowBase>>) {
413        let old = self.owner();
414        if old == value {
415            return;
416        }
417        *self.owner.borrow_mut() = value.as_ref().map(Ref::downgrade);
418        self.raise_direct_property_changed(Self::owner_property(), &old, &value);
419    }
420
421    /// Whether this window appears on top of all other windows.
422    pub fn topmost(&self) -> bool {
423        self.get_value(Self::topmost_property())
424    }
425
426    pub fn set_topmost(&self, value: bool) {
427        self.set_value(Self::topmost_property(), value)
428    }
429
430    /// The scaling factor for window positioning and sizing.
431    pub fn desktop_scaling(&self) -> f64 {
432        self.desktop_scaling_override
433            .get()
434            .or_else(|| self.platform_impl().map(|platform_impl| platform_impl.desktop_scaling()))
435            .unwrap_or(1.0)
436    }
437
438    /// The desktop scaling used instead of the platform's (private
439    /// protected upstream: for derived classes).
440    pub fn desktop_scaling_override(&self) -> Option<f64> {
441        self.desktop_scaling_override.get()
442    }
443
444    pub fn set_desktop_scaling_override(&self, value: Option<f64>) {
445        self.desktop_scaling_override.set(value)
446    }
447
448    /// Activates the window.
449    pub fn activate(&self) {
450        if let Some(platform_impl) = self.platform_impl() {
451            platform_impl.activate();
452        }
453    }
454
455    /// Ensures that the window is initialized (protected upstream: for
456    /// derived classes).
457    pub fn ensure_initialized(&self) {
458        if !self.is_initialized() {
459            self.begin_init();
460            self.end_init();
461        }
462    }
463
464    fn handle_position_changed(&self, pos: PixelPoint) {
465        let e = PixelPointEventArgs::new(pos);
466        for (_, handler) in self.position_changed.snapshot().iter() {
467            handler(&e);
468        }
469    }
470
471    fn handle_activated(&self) {
472        for (_, handler) in self.activated.snapshot().iter() {
473            handler();
474        }
475
476        if self.is_focus_scope() {
477            self.focus_manager().set_focus_scope(&self.to_ref().upcast());
478        }
479
480        self.set_is_active(true);
481    }
482
483    fn handle_deactivated(&self) {
484        self.set_is_active(false);
485
486        for (_, handler) in self.deactivated.snapshot().iter() {
487            handler();
488        }
489    }
490}