Skip to main content

ferroui_diagnostics/diagnostics/
view_locator.rs

1//! Port of `Diagnostics/ViewLocator.cs`: the data template of the DevTools window that builds
2//! the view of a view model.
3
4use ferroui_base::data::core::ValueType;
5use ferroui_base::metadata::MarkupType;
6use ferroui_base::{ferro_markup_type, BoxedValue, Ref, TypeInfo};
7use ferroui_controls::templates::{IDataTemplate, IRecyclingDataTemplate, ITemplateWithParam};
8use ferroui_controls::{Control, TextBlock};
9use std::rc::{Rc, Weak};
10
11/// The namespace of the view models of DevTools.
12const VIEW_MODELS_NAMESPACE: &str = "FerroUI.Diagnostics.ViewModels";
13
14/// `ViewModelBase` and the classes of the view models that derive from it upstream, directly or
15/// through `PropertyViewModel`, `SetterViewModel`, `TreeNode` and `EventTreeNodeBase`.
16const VIEW_MODEL_BASE_CLASSES: &[&str] = &[
17    "ViewModelBase",
18    "BindingSetterViewModel",
19    "ClrPropertyViewModel",
20    "ControlDetailsViewModel",
21    "ControlLayoutViewModel",
22    "EventOwnerTreeNode",
23    "EventTreeNode",
24    "EventTreeNodeBase",
25    "EventsPageViewModel",
26    "FerroPropertyViewModel",
27    "FilterViewModel",
28    "FiredEvent",
29    "HotKeyPageViewModel",
30    "LogicalTreeNode",
31    "MainViewModel",
32    "PropertyViewModel",
33    "PseudoClassViewModel",
34    "ResourceSetterViewModel",
35    "SetterViewModel",
36    "TreeNode",
37    "TreePageViewModel",
38    "ValueFrameViewModel",
39    "VisualTreeNode",
40];
41
42/// Builds the view of a view model: the class whose full name is the full name of the class of
43/// the view model with `ViewModel` replaced by `View`.
44pub struct ViewLocator {
45    this: Weak<ViewLocator>,
46}
47
48impl PartialEq for ViewLocator {
49    fn eq(&self, other: &Self) -> bool {
50        std::ptr::eq(self, other)
51    }
52}
53
54impl ViewLocator {
55    pub fn new() -> Rc<ViewLocator> {
56        Rc::new_cyclic(|this| Self { this: this.clone() })
57    }
58
59    /// The locator as the data template contract.
60    pub fn as_data_template(&self) -> Rc<dyn IDataTemplate> {
61        self.this.upgrade().expect("the locator is alive while it is used")
62    }
63
64    /// The markup metadata of the type of a value (`data.GetType()`).
65    fn markup_type_of(data: &BoxedValue) -> Option<&'static MarkupType> {
66        MarkupType::find_by_handle(ValueType::of_value(&**data).id())
67    }
68
69    /// `data.GetType().FullName`: the full name of the markup type of the value (the name of
70    /// its Rust type when markup does not know it).
71    fn full_name_of(data: &BoxedValue) -> String {
72        match Self::markup_type_of(data) {
73            Some(markup_type) => markup_type.full_name(),
74            None => ValueType::of_value(&**data).name().to_string(),
75        }
76    }
77
78    /// `Type.GetType(name)`: the class of the full name, among the classes markup knows.
79    fn find_type(name: &str) -> Option<&'static TypeInfo> {
80        let (namespace, name) = name.rsplit_once('.').unwrap_or(("", name));
81        TypeInfo::find(namespace, name)
82    }
83
84    /// `data is ViewModelBase`: the markup type of the value, or one of its base types (`base:` of
85    /// its declaration, walked as reflection walks base classes), is `ViewModelBase` or one of
86    /// the view models that derive from it.
87    fn is_view_model_base(data: &BoxedValue) -> bool {
88        // Deviation (DEVIATIONS.md, Developer tools): the view models of the port embed
89        // `ViewModelBase` (a plain struct) and their markup metadata does not state it as their
90        // base type, so the derivation is the list of the classes that derive from it upstream.
91        let mut current = Self::markup_type_of(data);
92        while let Some(markup_type) = current {
93            if markup_type.namespace() == VIEW_MODELS_NAMESPACE && VIEW_MODEL_BASE_CLASSES.contains(&markup_type.name) {
94                return true;
95            }
96            current = markup_type.base_type();
97        }
98        false
99    }
100}
101
102impl ITemplateWithParam<Option<BoxedValue>, Option<Ref<Control>>> for ViewLocator {
103    fn build(&self, data: &Option<BoxedValue>) -> Option<Ref<Control>> {
104        self.build_with_existing(data.as_ref(), None)
105    }
106}
107
108/// Deviation (DEVIATIONS.md, Developer tools): the locator is a recycling data template. The view
109/// it built last for a content is reused for the next view model of the same view, which gets it as
110/// its data context, where upstream builds a new view for every view model: the details of a
111/// control are a new view model at every selection of a node, and building their view again (the
112/// property grid with its columns, rows and editors, the layout, the value frames) made the
113/// properties appear a moment after the selection. The views follow their data context.
114impl IRecyclingDataTemplate for ViewLocator {
115    fn build_with_existing(&self, data: Option<&BoxedValue>, existing: Option<Ref<Control>>) -> Option<Ref<Control>> {
116        let data = data?;
117
118        let name = Self::full_name_of(data).replace("ViewModel", "View");
119        let type_ = Self::find_type(&name);
120
121        if let (Some(existing), Some(type_)) = (existing, type_) {
122            if std::ptr::eq(existing.get_type(), type_) {
123                return Some(existing);
124            }
125        }
126
127        if let Some(type_) = type_ {
128            // `(Control)Activator.CreateInstance(type)!`.
129            let instance = type_
130                .create_instance()
131                .unwrap_or_else(|| panic!("No parameterless constructor defined for type '{name}'."));
132            return Some(
133                instance
134                    .cast::<Control>()
135                    .unwrap_or_else(|| panic!("Unable to cast an object of type '{name}' to type 'Control'.")),
136            );
137        }
138
139        let text_block = TextBlock::new();
140        text_block.set_text(Some(name.as_str()));
141        Some(text_block.upcast())
142    }
143}
144
145impl IDataTemplate for ViewLocator {
146    fn match_(&self, data: Option<&BoxedValue>) -> bool {
147        data.is_some_and(Self::is_view_model_base)
148    }
149
150    fn as_any(&self) -> Option<&dyn std::any::Any> {
151        Some(self)
152    }
153
154    fn as_recycling_data_template(&self) -> Option<&dyn IRecyclingDataTemplate> {
155        Some(self)
156    }
157}
158
159ferro_markup_type!(class ViewLocator {
160    this: Rc<ViewLocator>,
161    handles: [ViewLocator, Rc<ViewLocator>, Option<Rc<ViewLocator>>],
162    interfaces: [Rc<dyn IDataTemplate>],
163    constructors: [() => ViewLocator::new],
164});