ferroui_diagnostics/diagnostics/
view_locator.rs1use 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
11const VIEW_MODELS_NAMESPACE: &str = "FerroUI.Diagnostics.ViewModels";
13
14const 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
42pub 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 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 fn markup_type_of(data: &BoxedValue) -> Option<&'static MarkupType> {
66 MarkupType::find_by_handle(ValueType::of_value(&**data).id())
67 }
68
69 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 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 fn is_view_model_base(data: &BoxedValue) -> bool {
88 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
108impl 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 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});