ferroui_diagnostics/diagnostics/view_models/
ferro_property_view_model.rs1use super::property_view_model::runtime_type_of;
5use super::{PropertyViewModel, PropertyViewModelKey, PropertyViewModelRef};
6use ferroui_base::data::core::{ValueType, ValueTypes};
7use ferroui_base::data::model::{Event, INotifyPropertyChanged};
8use ferroui_base::data::BindingPriority;
9use ferroui_base::diagnostics::FerroObjectDiagnosticExtensions;
10use ferroui_base::{ferro_markup_type, BoxedValue, FerroObject, FerroProperty, Ref};
11use std::cell::RefCell;
12use std::ops::Deref;
13use std::rc::Rc;
14
15pub struct FerroPropertyViewModel {
18 base: PropertyViewModel,
19 target: Ref<FerroObject>,
20 assigned_type: RefCell<ValueType>,
21 value: RefCell<Option<BoxedValue>>,
22 priority: RefCell<String>,
23 group: RefCell<String>,
24 property_type: ValueType,
25 property: &'static FerroProperty,
26 name: String,
27 declaring_type: Option<ValueType>,
28}
29
30impl PartialEq for FerroPropertyViewModel {
31 fn eq(&self, other: &Self) -> bool {
32 std::ptr::eq(self, other)
33 }
34}
35
36impl Deref for FerroPropertyViewModel {
37 type Target = PropertyViewModel;
38
39 fn deref(&self) -> &PropertyViewModel {
40 &self.base
41 }
42}
43
44impl INotifyPropertyChanged for FerroPropertyViewModel {
45 fn property_changed(&self) -> &Event<str> {
46 self.base.property_changed()
47 }
48}
49
50impl FerroPropertyViewModel {
51 pub fn new(o: &FerroObject, property: &'static FerroProperty) -> Rc<Self> {
52 let name = if property.is_attached() {
53 format!("[{}.{}]", property.owner_type().name(), property.name())
54 } else {
55 property.name().to_string()
56 };
57 let owner_type = property.owner_type();
58 let declaring_type = owner_type.handle().map(|handle| ValueType::new(handle, owner_type.name()));
59 let property_type = ValueType::new(property.property_type(), property.property_type_name());
60
61 let this = Rc::new(Self {
62 base: PropertyViewModel::construct(),
63 target: o.to_ref(),
64 assigned_type: RefCell::new(property_type),
65 value: RefCell::new(None),
66 priority: RefCell::new(String::new()),
67 group: RefCell::new(String::new()),
68 property_type,
69 property,
70 name,
71 declaring_type,
72 });
73
74 this.update();
75
76 this
77 }
78
79 pub fn to_property_view_model(self: &Rc<Self>) -> PropertyViewModelRef {
81 PropertyViewModelRef::Ferro(self.clone())
82 }
83
84 pub fn property(&self) -> &'static FerroProperty {
85 self.property
86 }
87
88 pub fn key(&self) -> PropertyViewModelKey {
89 PropertyViewModelKey::Property(self.property)
90 }
91
92 pub fn name(&self) -> String {
93 self.name.clone()
94 }
95
96 pub fn is_attached(&self) -> Option<bool> {
97 Some(self.property.is_attached())
98 }
99
100 pub fn priority(&self) -> String {
101 self.priority.borrow().clone()
102 }
103
104 pub fn assigned_type(&self) -> ValueType {
105 *self.assigned_type.borrow()
106 }
107
108 pub fn value(&self) -> Option<BoxedValue> {
109 self.value.borrow().clone()
110 }
111
112 pub fn set_value(&self, value: Option<BoxedValue>) {
115 if self.property.is_read_only() {
116 return;
117 }
118 let property_type = self.property_type;
119 let Some(Some(converted)) = ValueTypes::try_convert(value.as_ref(), property_type) else {
120 return;
121 };
122 if !self.property.is_valid_value(converted.as_any()) {
123 return;
124 }
125
126 self.target.set_value_untyped(self.property, converted.as_any(), BindingPriority::LocalValue);
127 self.update();
128 }
129
130 pub fn group(&self) -> String {
131 if self.is_pinned() {
132 "Pinned".to_string()
133 } else {
134 self.group.borrow().clone()
135 }
136 }
137
138 pub fn declaring_type(&self) -> Option<ValueType> {
139 self.declaring_type
140 }
141
142 pub fn property_type(&self) -> ValueType {
143 self.property_type
144 }
145
146 pub fn is_readonly(&self) -> bool {
147 self.property.is_read_only()
148 }
149
150 pub fn update(&self) {
151 let base = self.base.view_model_base();
152
153 if self.property.is_direct() {
154 let value = ValueTypes::normalize(self.target.get_value_untyped(self.property));
157 let value_type = runtime_type_of(value.as_ref());
158
159 base.raise_and_set_if_changed(&self.value, value, "Value");
160 base.raise_and_set_if_changed(&self.assigned_type, value_type.unwrap_or(self.property_type), "AssignedType");
161 base.raise_and_set_if_changed(&self.priority, "Direct".to_string(), "Priority");
162
163 *self.group.borrow_mut() = "Properties".to_string();
164 } else {
165 let diag = self.target.get_diagnostic(self.property);
166
167 let value = ValueTypes::normalize(diag.value().clone());
168 let value_type = runtime_type_of(value.as_ref());
169 let priority: Option<BindingPriority> = Some(diag.priority());
170
171 base.raise_and_set_if_changed(&self.value, value, "Value");
172 base.raise_and_set_if_changed(&self.assigned_type, value_type.unwrap_or(self.property_type), "AssignedType");
173
174 if let Some(priority) = priority {
175 base.raise_and_set_if_changed(&self.priority, format!("{priority:?}"), "Priority");
177 let group = if self.is_attached() == Some(true) { "Attached Properties" } else { "Properties" };
178 base.raise_and_set_if_changed(&self.group, group.to_string(), "Group");
179 } else {
180 base.raise_and_set_if_changed(&self.priority, "Unset".to_string(), "Priority");
181 base.raise_and_set_if_changed(&self.group, "Unset".to_string(), "Group");
182 }
183 }
184 base.raise_property_changed("Type");
185 }
186
187 pub(crate) fn on_property_changed(&self, property_name: &str) {
189 if property_name == "IsPinned" {
190 self.base.raise_property_changed("Group");
191 }
192 }
193}
194
195ferro_markup_type!(class FerroPropertyViewModel {
196 this: Rc<FerroPropertyViewModel>,
197 handles: [FerroPropertyViewModel, Rc<FerroPropertyViewModel>, Option<Rc<FerroPropertyViewModel>>],
198 properties: [
199 Property: &'static FerroProperty { get: FerroPropertyViewModel::property },
200 Name: String { get: FerroPropertyViewModel::name },
201 Group: String { get: FerroPropertyViewModel::group },
202 Priority: String { get: FerroPropertyViewModel::priority },
203 Value: Option<BoxedValue> { get: FerroPropertyViewModel::value, set: FerroPropertyViewModel::set_value },
204 AssignedType: ValueType { get: FerroPropertyViewModel::assigned_type },
205 PropertyType: ValueType { get: FerroPropertyViewModel::property_type },
206 Type: String { get: |this: &Rc<FerroPropertyViewModel>| this.to_property_view_model().type_() },
207 IsReadonly: bool { get: FerroPropertyViewModel::is_readonly },
208 IsAttached: Option<bool> { get: FerroPropertyViewModel::is_attached },
209 IsPinned: bool {
210 get: |this: &Rc<FerroPropertyViewModel>| this.is_pinned(),
211 set: |this: &Rc<FerroPropertyViewModel>, value: bool| this.to_property_view_model().set_is_pinned(value)
212 },
213 FullName: String { get: |this: &Rc<FerroPropertyViewModel>| this.to_property_view_model().full_name() },
214 ],
215 methods: [
216 fn Update() => FerroPropertyViewModel::update,
217 ],
218 notify_property_changed: FerroPropertyViewModel,
219});