Skip to main content

ferroui_diagnostics/diagnostics/view_models/
clr_property_view_model.rs

1//! Port of `Diagnostics/ViewModels/ClrPropertyViewModel.cs`.
2//!
3//! The managed original reflects over the public properties of the object's type. Their
4//! counterpart here is the markup metadata of the type (`ferroui_base::metadata`): the plain
5//! properties a type declares ([`MarkupProperty`]), read and written through their untyped
6//! accessors.
7
8use super::property_view_model::runtime_type_of;
9use super::{PropertyViewModel, PropertyViewModelKey, PropertyViewModelRef};
10use ferroui_base::data::core::{ValueType, ValueTypes};
11use ferroui_base::data::model::{Event, INotifyPropertyChanged};
12use ferroui_base::metadata::{MarkupProperty, MarkupType, MarkupTypeKind};
13use ferroui_base::{ferro_markup_type, BoxedValue};
14use std::cell::RefCell;
15use std::ops::Deref;
16use std::rc::Rc;
17
18/// The view model of a plain property of an object: its value, read through the metadata of the
19/// type that declares it.
20pub struct ClrPropertyViewModel {
21    base: PropertyViewModel,
22    target: BoxedValue,
23    assigned_type: RefCell<ValueType>,
24    value: RefCell<Option<BoxedValue>>,
25    property_type: ValueType,
26    property: &'static MarkupProperty,
27    declaring: &'static MarkupType,
28    name: String,
29    declaring_type: Option<ValueType>,
30}
31
32impl PartialEq for ClrPropertyViewModel {
33    fn eq(&self, other: &Self) -> bool {
34        std::ptr::eq(self, other)
35    }
36}
37
38impl Deref for ClrPropertyViewModel {
39    type Target = PropertyViewModel;
40
41    fn deref(&self) -> &PropertyViewModel {
42        &self.base
43    }
44}
45
46impl INotifyPropertyChanged for ClrPropertyViewModel {
47    fn property_changed(&self) -> &Event<str> {
48        self.base.property_changed()
49    }
50}
51
52impl ClrPropertyViewModel {
53    /// The view model of the property `property` of `o`, which the type `declaring_type`
54    /// declares (the `PropertyInfo` of the managed original, with its `DeclaringType`).
55    pub fn new(o: BoxedValue, property: &'static MarkupProperty, declaring_type: &'static MarkupType) -> Rc<Self> {
56        let name = if declaring_type.kind != MarkupTypeKind::Interface {
57            property.name.to_string()
58        } else {
59            format!("{}.{}", declaring_type.name, property.name)
60        };
61        let property_type = (property.type_)();
62
63        let this = Rc::new(Self {
64            base: PropertyViewModel::construct(),
65            target: o,
66            assigned_type: RefCell::new(property_type),
67            value: RefCell::new(None),
68            property_type,
69            property,
70            declaring: declaring_type,
71            name,
72            declaring_type: declaring_type.handle(),
73        });
74
75        this.update();
76
77        this
78    }
79
80    /// The handle of the view model typed with the abstract class.
81    pub fn to_property_view_model(self: &Rc<Self>) -> PropertyViewModelRef {
82        PropertyViewModelRef::Clr(self.clone())
83    }
84
85    /// The property (`PropertyInfo Property`).
86    pub fn property(&self) -> &'static MarkupProperty {
87        self.property
88    }
89
90    /// The metadata of the type that declares the property.
91    pub fn declaring_markup_type(&self) -> &'static MarkupType {
92        self.declaring
93    }
94
95    pub fn key(&self) -> PropertyViewModelKey {
96        PropertyViewModelKey::Name(self.name.clone())
97    }
98
99    pub fn name(&self) -> String {
100        self.name.clone()
101    }
102
103    pub fn group(&self) -> String {
104        if self.is_pinned() {
105            "Pinned".to_string()
106        } else {
107            "CLR Properties".to_string()
108        }
109    }
110
111    pub fn assigned_type(&self) -> ValueType {
112        *self.assigned_type.borrow()
113    }
114
115    pub fn property_type(&self) -> ValueType {
116        self.property_type
117    }
118
119    /// `!Property.CanWrite`.
120    pub fn is_readonly(&self) -> bool {
121        self.property.set.is_none()
122    }
123
124    pub fn value(&self) -> Option<BoxedValue> {
125        self.value.borrow().clone()
126    }
127
128    /// Sets the property of the object to `value` and reads it back. A value the setter does
129    /// not take is ignored (the managed original swallows the exception).
130    pub fn set_value(&self, value: Option<BoxedValue>) {
131        let Some(set) = self.property.set else {
132            return;
133        };
134        if set(&[Some(self.target.clone()), value]).is_ok() {
135            self.update();
136        }
137    }
138
139    pub fn priority(&self) -> String {
140        String::new()
141    }
142
143    pub fn is_attached(&self) -> Option<bool> {
144        None
145    }
146
147    pub fn declaring_type(&self) -> Option<ValueType> {
148        self.declaring_type
149    }
150
151    pub fn update(&self) {
152        let base = self.base.view_model_base();
153
154        // `Property.GetValue(_target)`: the exception of a getter is shown as the value (its
155        // message here).
156        let value: Option<BoxedValue> = match self.property.get {
157            Some(get) => match get(&[Some(self.target.clone())]) {
158                Ok(value) => value.and_then(ValueTypes::normalize),
159                Err(error) => Some(Rc::new(error.to_string()) as BoxedValue),
160            },
161            None => None,
162        };
163        let value_type = runtime_type_of(value.as_ref());
164
165        base.raise_and_set_if_changed(&self.value, value, "Value");
166        base.raise_and_set_if_changed(&self.assigned_type, value_type.unwrap_or(self.property_type), "AssignedType");
167        base.raise_property_changed("Type");
168    }
169
170    /// The override of `OnPropertyChanged`: pinning the property moves it to another group.
171    pub(crate) fn on_property_changed(&self, property_name: &str) {
172        if property_name == "IsPinned" {
173            self.base.raise_property_changed("Group");
174        }
175    }
176}
177
178ferro_markup_type!(class ClrPropertyViewModel {
179    this: Rc<ClrPropertyViewModel>,
180    handles: [ClrPropertyViewModel, Rc<ClrPropertyViewModel>, Option<Rc<ClrPropertyViewModel>>],
181    properties: [
182        Name: String { get: ClrPropertyViewModel::name },
183        Group: String { get: ClrPropertyViewModel::group },
184        Priority: String { get: ClrPropertyViewModel::priority },
185        Value: Option<BoxedValue> { get: ClrPropertyViewModel::value, set: ClrPropertyViewModel::set_value },
186        AssignedType: ValueType { get: ClrPropertyViewModel::assigned_type },
187        PropertyType: ValueType { get: ClrPropertyViewModel::property_type },
188        Type: String { get: |this: &Rc<ClrPropertyViewModel>| this.to_property_view_model().type_() },
189        IsReadonly: bool { get: ClrPropertyViewModel::is_readonly },
190        IsAttached: Option<bool> { get: ClrPropertyViewModel::is_attached },
191        IsPinned: bool {
192            get: |this: &Rc<ClrPropertyViewModel>| this.is_pinned(),
193            set: |this: &Rc<ClrPropertyViewModel>, value: bool| this.to_property_view_model().set_is_pinned(value)
194        },
195        FullName: String { get: |this: &Rc<ClrPropertyViewModel>| this.to_property_view_model().full_name() },
196    ],
197    methods: [
198        fn Update() => ClrPropertyViewModel::update,
199    ],
200    notify_property_changed: ClrPropertyViewModel,
201});