Skip to main content

ferroui_diagnostics/diagnostics/view_models/
property_view_model.rs

1//! Port of `Diagnostics/ViewModels/PropertyViewModel.cs`.
2//!
3//! The abstract class is the state the property view models share ([`PropertyViewModel`]),
4//! which [`FerroPropertyViewModel`] and [`ClrPropertyViewModel`] embed and dereference to. A
5//! property view model of either class is held as a [`PropertyViewModelRef`]: the shared
6//! handle typed with the abstract class, through which its abstract members dispatch.
7//!
8//! The `Type`-typed members of the original are [`ValueType`]s: the type of the values of a
9//! property, the type of its current value, and the type that declares it.
10
11use super::{ClrPropertyViewModel, FerroPropertyViewModel, ViewModelBase};
12use crate::diagnostics::TypeExtesnions;
13use ferroui_base::data::core::{ValueType, ValueTypes};
14use ferroui_base::data::model::{Event, INotifyPropertyChanged};
15use ferroui_base::metadata::MarkupType;
16use ferroui_base::{BoxedValue, FerroProperty};
17use std::cell::RefCell;
18use std::ops::Deref;
19use std::rc::Rc;
20
21/// The key of a property view model in the index of the properties of an object (`object Key`):
22/// the registered property, or the name of a plain property.
23#[derive(Clone, Debug, PartialEq, Eq, Hash)]
24pub enum PropertyViewModelKey {
25    Property(&'static FerroProperty),
26    Name(String),
27}
28
29/// The state of the abstract class `PropertyViewModel`: the base of the view models of the
30/// properties of an object, and whether the property is pinned.
31pub struct PropertyViewModel {
32    base: ViewModelBase,
33    is_pinned: RefCell<bool>,
34}
35
36impl INotifyPropertyChanged for PropertyViewModel {
37    fn property_changed(&self) -> &Event<str> {
38        self.base.property_changed()
39    }
40}
41
42impl PropertyViewModel {
43    /// The state of the class, for the classes that derive from it.
44    pub fn construct() -> Self {
45        Self { base: ViewModelBase::new(), is_pinned: RefCell::new(false) }
46    }
47
48    /// The base class of the view models (`RaiseAndSetIfChanged`, `RaisePropertyChanged`).
49    pub fn view_model_base(&self) -> &ViewModelBase {
50        &self.base
51    }
52
53    pub fn is_pinned(&self) -> bool {
54        *self.is_pinned.borrow()
55    }
56
57    /// Stores `IsPinned` without raising the notification; `true` when the value changed. The
58    /// setter of the property is [`PropertyViewModelRef::set_is_pinned`], which runs the
59    /// override of `OnPropertyChanged` of the deriving class before the handlers.
60    pub(crate) fn store_is_pinned(&self, value: bool) -> bool {
61        if *self.is_pinned.borrow() == value {
62            return false;
63        }
64        *self.is_pinned.borrow_mut() = value;
65        true
66    }
67
68    /// `RaisePropertyChanged`, for the deriving classes.
69    pub fn raise_property_changed(&self, property_name: &str) {
70        self.base.raise_property_changed(property_name);
71    }
72}
73
74/// The text of a type as the developer tools show it (`Type.GetTypeName()`).
75pub(crate) fn type_name(type_: ValueType) -> String {
76    type_.get_type_name()
77}
78
79/// The dotted full name of a type (`Type.FullName`): the name of a class, of a type with markup
80/// metadata, or the name the value types know.
81pub(crate) fn type_full_name(type_: ValueType) -> String {
82    if let Some(class) = ValueTypes::class_of(type_) {
83        return class.full_name();
84    }
85    if let Some(markup) = MarkupType::find_by_handle(type_.id()) {
86        return markup.full_name();
87    }
88    ValueTypes::type_full_name(type_)
89}
90
91/// The type of a value as the developer tools show it (`value.GetType()`): the run-time class of
92/// an object, the type a nullable form holds, else the type of the value. `None` for null.
93pub(crate) fn runtime_type_of(value: Option<&BoxedValue>) -> Option<ValueType> {
94    let value = ValueTypes::normalize(value?.clone())?;
95
96    if let Some(object) = ValueTypes::as_object(&*value) {
97        let class = object.get_type();
98        if let Some(handle) = class.handle() {
99            return Some(ValueType::new(handle, class.name()));
100        }
101    }
102
103    Some(ValueType::of_value(&*value))
104}
105
106/// The shared handle of a property view model of either class: a reference typed with the
107/// abstract class `PropertyViewModel`.
108#[derive(Clone)]
109pub enum PropertyViewModelRef {
110    Ferro(Rc<FerroPropertyViewModel>),
111    Clr(Rc<ClrPropertyViewModel>),
112}
113
114/// The kind and the name of the property.
115impl std::fmt::Debug for PropertyViewModelRef {
116    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
117        let kind = match self {
118            Self::Ferro(_) => "Ferro",
119            Self::Clr(_) => "Clr",
120        };
121        f.debug_tuple(kind).field(&self.name()).finish()
122    }
123}
124
125/// Compares by identity, as the reference type this mirrors.
126impl PartialEq for PropertyViewModelRef {
127    fn eq(&self, other: &Self) -> bool {
128        std::ptr::eq::<PropertyViewModel>(&**self, &**other)
129    }
130}
131
132impl Deref for PropertyViewModelRef {
133    type Target = PropertyViewModel;
134
135    fn deref(&self) -> &PropertyViewModel {
136        match self {
137            Self::Ferro(property) => property,
138            Self::Clr(property) => property,
139        }
140    }
141}
142
143impl INotifyPropertyChanged for PropertyViewModelRef {
144    fn property_changed(&self) -> &Event<str> {
145        (**self).property_changed()
146    }
147}
148
149impl PropertyViewModelRef {
150    pub fn key(&self) -> PropertyViewModelKey {
151        match self {
152            Self::Ferro(property) => property.key(),
153            Self::Clr(property) => property.key(),
154        }
155    }
156
157    pub fn name(&self) -> String {
158        match self {
159            Self::Ferro(property) => property.name(),
160            Self::Clr(property) => property.name(),
161        }
162    }
163
164    pub fn group(&self) -> String {
165        match self {
166            Self::Ferro(property) => property.group(),
167            Self::Clr(property) => property.group(),
168        }
169    }
170
171    pub fn assigned_type(&self) -> ValueType {
172        match self {
173            Self::Ferro(property) => property.assigned_type(),
174            Self::Clr(property) => property.assigned_type(),
175        }
176    }
177
178    pub fn declaring_type(&self) -> Option<ValueType> {
179        match self {
180            Self::Ferro(property) => property.declaring_type(),
181            Self::Clr(property) => property.declaring_type(),
182        }
183    }
184
185    pub fn value(&self) -> Option<BoxedValue> {
186        match self {
187            Self::Ferro(property) => property.value(),
188            Self::Clr(property) => property.value(),
189        }
190    }
191
192    pub fn set_value(&self, value: Option<BoxedValue>) {
193        match self {
194            Self::Ferro(property) => property.set_value(value),
195            Self::Clr(property) => property.set_value(value),
196        }
197    }
198
199    pub fn priority(&self) -> String {
200        match self {
201            Self::Ferro(property) => property.priority(),
202            Self::Clr(property) => property.priority(),
203        }
204    }
205
206    pub fn is_attached(&self) -> Option<bool> {
207        match self {
208            Self::Ferro(property) => property.is_attached(),
209            Self::Clr(property) => property.is_attached(),
210        }
211    }
212
213    pub fn update(&self) {
214        match self {
215            Self::Ferro(property) => property.update(),
216            Self::Clr(property) => property.update(),
217        }
218    }
219
220    pub fn property_type(&self) -> ValueType {
221        match self {
222            Self::Ferro(property) => property.property_type(),
223            Self::Clr(property) => property.property_type(),
224        }
225    }
226
227    pub fn is_readonly(&self) -> bool {
228        match self {
229            Self::Ferro(property) => property.is_readonly(),
230            Self::Clr(property) => property.is_readonly(),
231        }
232    }
233
234    /// The text of the type of the property, followed by the type of its value in braces when
235    /// the two differ.
236    pub fn type_(&self) -> String {
237        let property_type = self.property_type();
238        let assigned_type = self.assigned_type();
239
240        if property_type == assigned_type {
241            type_name(property_type)
242        } else {
243            format!("{} {{{}}}", type_name(property_type), type_name(assigned_type))
244        }
245    }
246
247    pub fn set_is_pinned(&self, value: bool) {
248        if self.store_is_pinned(value) {
249            // `RaisePropertyChanged`: the override of `OnPropertyChanged`, then the handlers.
250            self.on_property_changed("IsPinned");
251            self.raise_property_changed("IsPinned");
252        }
253    }
254
255    /// The virtual `OnPropertyChanged`.
256    pub(crate) fn on_property_changed(&self, property_name: &str) {
257        match self {
258            Self::Ferro(property) => property.on_property_changed(property_name),
259            Self::Clr(property) => property.on_property_changed(property_name),
260        }
261    }
262
263    /// The name of the class without `PropertyViewModel`, a colon, the full name of the
264    /// declaring type, a dot and the name: the key a pinned property is remembered by.
265    pub fn full_name(&self) -> String {
266        // `GetType().Name.Replace("PropertyViewModel", "")`.
267        let class_name = match self {
268            Self::Ferro(_) => "FerroPropertyViewModel",
269            Self::Clr(_) => "ClrPropertyViewModel",
270        };
271        let declaring_type = self.declaring_type().map(type_full_name).unwrap_or_default();
272
273        format!("{}:{}.{}", class_name.replace("PropertyViewModel", ""), declaring_type, self.name())
274    }
275
276    /// The view model as a value of its class (the source of the bindings of an editor).
277    pub fn boxed(&self) -> BoxedValue {
278        match self {
279            Self::Ferro(property) => property.clone(),
280            Self::Clr(property) => property.clone(),
281        }
282    }
283
284    /// The view model of an untyped value of one of the classes (`DataContext as
285    /// PropertyViewModel`).
286    pub fn from_boxed(value: &BoxedValue) -> Option<Self> {
287        if let Some(property) = value.downcast_ref::<PropertyViewModelRef>() {
288            return Some(property.clone());
289        }
290        if let Some(property) = value.downcast_ref::<Rc<FerroPropertyViewModel>>() {
291            return Some(Self::Ferro(property.clone()));
292        }
293        if let Some(property) = value.downcast_ref::<Rc<ClrPropertyViewModel>>() {
294            return Some(Self::Clr(property.clone()));
295        }
296        // The box that is the shared object itself.
297        let any: Rc<dyn std::any::Any> = value.clone();
298        let any = match any.downcast::<FerroPropertyViewModel>() {
299            Ok(property) => return Some(Self::Ferro(property)),
300            Err(any) => any,
301        };
302        any.downcast::<ClrPropertyViewModel>().ok().map(Self::Clr)
303    }
304
305    /// `as FerroPropertyViewModel`.
306    pub fn as_ferro(&self) -> Option<&Rc<FerroPropertyViewModel>> {
307        match self {
308            Self::Ferro(property) => Some(property),
309            Self::Clr(_) => None,
310        }
311    }
312
313    /// `as ClrPropertyViewModel`.
314    pub fn as_clr(&self) -> Option<&Rc<ClrPropertyViewModel>> {
315        match self {
316            Self::Ferro(_) => None,
317            Self::Clr(property) => Some(property),
318        }
319    }
320}