Skip to main content

ferroui_diagnostics/diagnostics/controls/
application.rs

1//! Port of `Diagnostics/Controls/Application.cs`: the application as the root of the trees of
2//! DevTools, a top-level group of its windows.
3
4use super::TopLevelGroup;
5use crate::diagnostics::{ClassicDesktopStyleApplicationLifetimeTopLevelGroup, IDevToolsTopLevelGroup};
6use ferroui_base::controls::ResourceDictionary;
7use ferroui_base::input::{ICloseable, InputManager};
8use ferroui_base::reactive::{Disposable, IDisposable};
9use ferroui_base::styling::{Styles, ThemeVariant};
10use ferroui_base::utilities::HandlerList;
11use ferroui_base::{
12    ferro_class, ferro_class_info, ferro_properties, instantiate, BoxedValue, FerroObjectImpl, FerroObjectImplExt,
13    FerroPropertyChangedEventArgs, Ref, StyledProperty,
14};
15use ferroui_controls::application_lifetimes::{ControlledApplicationLifetimeExitEventArgs, IApplicationLifetime};
16use ferroui_controls::presentation_source::ITopLevelRenderer;
17use ferroui_controls::templates::DataTemplates;
18use ferroui_controls::{ThemeVariantScope, TopLevel};
19use std::cell::RefCell;
20use std::rc::Rc;
21
22/// The application DevTools is attached to, as the root of its trees: the windows of the
23/// application, and the members of the application that DevTools shows. It closes when the
24/// application exits.
25#[repr(C)]
26pub struct Application {
27    base: TopLevelGroup,
28    application: Ref<ferroui_controls::Application>,
29    closed: HandlerList<dyn Fn()>,
30    renderer_root: RefCell<Option<Rc<dyn ITopLevelRenderer>>>,
31    /// `_application.PropertyChanged += ApplicationOnPropertyChanged`.
32    application_property_changed: RefCell<Option<Rc<dyn IDisposable>>>,
33}
34
35ferro_class!(Application: TopLevelGroup);
36ferro_class_info!(Application {});
37
38ferro_properties! {
39    impl Application {
40        /// Defines the [`requested_theme_variant`](Application::requested_theme_variant) property.
41        pub fn requested_theme_variant_property() -> StyledProperty<Option<ThemeVariant>> {
42            ThemeVariantScope::requested_theme_variant_property().add_owner::<Application>()
43        }
44    }
45}
46
47impl FerroObjectImpl for Application {
48    fn constructed(this: &Self) {
49        Self::parent_constructed(this);
50
51        let application = this.application.clone();
52        let lifetime = application.application_lifetime();
53
54        if let Some(controller) = lifetime.as_ref().and_then(|lifetime| lifetime.as_controlled_application_lifetime()) {
55            // `controller.Exit -= eh` from the handler itself.
56            let subscription: Rc<RefCell<Option<Rc<dyn IDisposable>>>> = Rc::new(RefCell::new(None));
57            let handler_subscription = subscription.clone();
58            let weak = this.to_ref().downgrade();
59            let exit = controller.exit(Rc::new(move |_: &ControlledApplicationLifetimeExitEventArgs| {
60                let own = handler_subscription.borrow_mut().take();
61                if let Some(own) = own {
62                    own.dispose();
63                }
64                if let Some(this) = weak.upgrade() {
65                    this.raise_closed();
66                }
67            }));
68            *subscription.borrow_mut() = Some(exit);
69        }
70
71        let renderer_root = match lifetime.as_ref() {
72            Some(lifetime) => {
73                if let Some(classic) = lifetime.as_classic_desktop_style_application_lifetime() {
74                    classic.main_window().map(|main_window| main_window.renderer())
75                } else if let Some(single) = lifetime.as_single_view_application_lifetime() {
76                    single
77                        .main_view()
78                        .and_then(|main_view| main_view.visual_root())
79                        .and_then(|visual_root| visual_root.cast::<TopLevel>())
80                        .map(|top_level| top_level.renderer())
81                } else {
82                    None
83                }
84            }
85            None => None,
86        };
87        *this.renderer_root.borrow_mut() = renderer_root;
88
89        this.set_current_value(Self::requested_theme_variant_property(), application.requested_theme_variant());
90
91        let weak = this.to_ref().downgrade();
92        let subscription = application.property_changed(move |e| {
93            if let Some(this) = weak.upgrade() {
94                this.application_on_property_changed(e);
95            }
96        });
97        *this.application_property_changed.borrow_mut() = Some(subscription);
98    }
99
100    fn on_property_changed(this: &Self, change: &FerroPropertyChangedEventArgs<'_>) {
101        Self::parent_on_property_changed(this, change);
102
103        if change.property() == Self::requested_theme_variant_property().as_property() {
104            this.application.set_requested_theme_variant(change.get_new_value::<Option<ThemeVariant>>());
105        }
106    }
107}
108
109impl Application {
110    pub fn construct(
111        group: Rc<dyn IDevToolsTopLevelGroup>,
112        application: Ref<ferroui_controls::Application>,
113    ) -> Self {
114        debug_assert!(
115            group.as_any().is::<ClassicDesktopStyleApplicationLifetimeTopLevelGroup>(),
116            "the group of an application is the group of the windows of its lifetime"
117        );
118        Self {
119            base: TopLevelGroup::construct(group),
120            application,
121            closed: HandlerList::new(),
122            renderer_root: RefCell::new(None),
123            application_property_changed: RefCell::new(None),
124        }
125    }
126
127    /// C# `new Application(ClassicDesktopStyleApplicationLifetimeTopLevelGroup group,
128    /// Application application)`: `group` is the
129    /// [`ClassicDesktopStyleApplicationLifetimeTopLevelGroup`] of the lifetime of `application`.
130    pub fn new(group: Rc<dyn IDevToolsTopLevelGroup>, application: Ref<ferroui_controls::Application>) -> Ref<Self> {
131        instantiate(Self::construct(group, application))
132    }
133
134    /// Raised when the application exits. Disposing the returned handle unsubscribes.
135    pub fn closed(&self, handler: impl Fn() + 'static) -> Rc<dyn IDisposable> {
136        let token = self.closed.add(Rc::new(handler));
137        let weak = self.to_ref().downgrade();
138        Disposable::create(move || {
139            if let Some(this) = weak.upgrade() {
140                this.closed.remove(token);
141            }
142        })
143    }
144
145    fn raise_closed(&self) {
146        for (_, handler) in self.closed.snapshot().iter() {
147            handler();
148        }
149    }
150
151    /// The application as `ICloseable` (the class implements it in the original).
152    pub fn as_closeable(this: Ref<Self>) -> Rc<dyn ICloseable> {
153        Rc::new(ApplicationHandle(this))
154    }
155
156    /// The application as `IDisposable` (the class implements it in the original).
157    pub fn as_disposable(this: Ref<Self>) -> Rc<dyn IDisposable> {
158        Rc::new(ApplicationHandle(this))
159    }
160
161    /// The application DevTools is attached to.
162    pub fn instance(&self) -> Ref<ferroui_controls::Application> {
163        self.application.clone()
164    }
165
166    /// The data context of the application.
167    pub fn data_context(&self) -> Option<BoxedValue> {
168        self.application.data_context()
169    }
170
171    /// Gets or sets the application's global data templates.
172    pub fn data_templates(&self) -> DataTemplates {
173        self.application.data_templates()
174    }
175
176    /// Gets the application's input manager.
177    pub fn input_manager(&self) -> Option<Rc<InputManager>> {
178        self.application.input_manager()
179    }
180
181    /// Gets the application's global resource dictionary.
182    pub fn resources(&self) -> Ref<ResourceDictionary> {
183        self.application.resources()
184    }
185
186    /// Gets the application's global styles.
187    ///
188    /// Global styles apply to all windows in the application.
189    pub fn styles(&self) -> Ref<Styles> {
190        self.application.styles()
191    }
192
193    /// Application lifetime, use it for things like setting the main window and exiting the app from code
194    /// Currently supported lifetimes are:
195    /// - `IClassicDesktopStyleApplicationLifetime`
196    /// - `ISingleViewApplicationLifetime`
197    /// - `IControlledApplicationLifetime`
198    pub fn application_lifetime(&self) -> Option<Rc<dyn IApplicationLifetime>> {
199        self.application.application_lifetime()
200    }
201
202    /// Application name to be used for various platform-specific purposes
203    pub fn name(&self) -> Option<String> {
204        self.application.name()
205    }
206
207    /// Gets the renderer of the root of the visual tree of the application: the renderer of
208    /// its main window, or of the top level of its main view.
209    pub fn renderer_root(&self) -> Option<Rc<dyn ITopLevelRenderer>> {
210        self.renderer_root.borrow().clone()
211    }
212
213    /// The theme variant requested for the application (`ThemeVariantScope.RequestedThemeVariant`).
214    pub fn requested_theme_variant(&self) -> Option<ThemeVariant> {
215        self.get_value(Self::requested_theme_variant_property())
216    }
217
218    pub fn set_requested_theme_variant(&self, value: Option<ThemeVariant>) {
219        self.set_value(Self::requested_theme_variant_property(), value);
220    }
221
222    /// `Dispose`: stops following the changes of the application.
223    pub fn dispose(&self) {
224        let subscription = self.application_property_changed.borrow_mut().take();
225        if let Some(subscription) = subscription {
226            subscription.dispose();
227        }
228    }
229
230    fn application_on_property_changed(&self, e: &FerroPropertyChangedEventArgs<'_>) {
231        if e.property() == ferroui_controls::Application::requested_theme_variant_property().as_property() {
232            self.set_current_value(
233                Self::requested_theme_variant_property(),
234                e.get_new_value::<Option<ThemeVariant>>(),
235            );
236        }
237    }
238}
239
240/// The `ICloseable` and `IDisposable` contracts of an [`Application`].
241struct ApplicationHandle(Ref<Application>);
242
243impl ICloseable for ApplicationHandle {
244    fn closed(&self, handler: Rc<dyn Fn()>) -> Rc<dyn IDisposable> {
245        self.0.closed(move || handler())
246    }
247}
248
249impl IDisposable for ApplicationHandle {
250    fn dispose(&self) {
251        self.0.dispose();
252    }
253}