Skip to main content

ferroui_diagnostics/diagnostics/view_models/
value_frame_view_model.rs

1//! Port of `Diagnostics/ViewModels/ValueFrameViewModel.cs`.
2
3use super::binding_setter_view_model::{binding_kind, binding_of};
4use super::{BindingSetterViewModel, ResourceSetterViewModel, SetterViewModel, SetterViewModelRef, ViewModelBase};
5use ferroui_base::controls::ResourceKey;
6use ferroui_base::data::core::{ValueType, ValueTypes};
7use ferroui_base::data::model::{Event, INotifyPropertyChanged};
8use ferroui_base::diagnostics::{IValueFrameDiagnostic, IValueFrameDiagnosticFrameType};
9use ferroui_base::input::platform::IClipboard;
10use ferroui_base::styling::{ControlTheme, Style, StyleBase};
11use ferroui_base::{ferro_markup_type, BoxedValue, Ref, StyledElement, TypeInfo};
12use ferroui_controls::ItemsSource;
13use ferroui_markup_xaml::markup_extensions::{DynamicResourceExtension, StaticResourceExtension};
14use std::cell::RefCell;
15use std::rc::Rc;
16
17/// A frame of the values of a control (its local values, a style or control theme that
18/// applies to it, the values its template gives it), with the setters of the frame.
19pub struct ValueFrameViewModel {
20    base: ViewModelBase,
21    value_frame: Rc<dyn IValueFrameDiagnostic>,
22    is_active: RefCell<bool>,
23    is_visible: RefCell<bool>,
24    description: Option<String>,
25    setters: Vec<SetterViewModelRef>,
26}
27
28impl PartialEq for ValueFrameViewModel {
29    fn eq(&self, other: &Self) -> bool {
30        std::ptr::eq(self, other)
31    }
32}
33
34impl INotifyPropertyChanged for ValueFrameViewModel {
35    fn property_changed(&self) -> &Event<str> {
36        self.base.property_changed()
37    }
38}
39
40impl ValueFrameViewModel {
41    pub fn new(
42        styled_element: &StyledElement,
43        value_frame: Rc<dyn IValueFrameDiagnostic>,
44        clipboard: Option<Rc<dyn IClipboard>>,
45    ) -> Rc<Self> {
46        let source = Self::source_to_string(value_frame.source());
47        let description = match (value_frame.type_(), source) {
48            // `"Local Values " + source`: a null source concatenates as nothing.
49            (IValueFrameDiagnosticFrameType::Local, source) => format!("Local Values {}", source.unwrap_or_default()),
50            (IValueFrameDiagnosticFrameType::Template, source) => format!("Template {}", source.unwrap_or_default()),
51            (IValueFrameDiagnosticFrameType::Theme, source) => format!("Theme {}", source.unwrap_or_default()),
52            (_, Some(source)) if !source.is_empty() => source,
53            // `_valueFrame.Priority.ToString()`: the name of the priority.
54            _ => format!("{:?}", value_frame.priority()),
55        };
56
57        let mut setters = Vec::new();
58
59        for entry in value_frame.values() {
60            let setter_property = entry.property;
61            // The value of a property of type `object` is held as `Option<BoxedValue>`: its
62            // contents, a null for an empty one, as the managed original sees the object.
63            let setter_value = entry.value.and_then(|value| match value.downcast_ref::<Option<BoxedValue>>() {
64                Some(untyped) => untyped.clone(),
65                None => Some(value),
66            });
67
68            let resource_info = Self::get_resource_info(setter_value.as_ref());
69
70            let setter_vm = if let Some((resource_key, is_dynamic)) = resource_info {
71                let resource_value = styled_element.find_resource(&resource_key_of(&resource_key));
72
73                SetterViewModelRef::Resource(ResourceSetterViewModel::new(
74                    setter_property,
75                    resource_key,
76                    resource_value,
77                    is_dynamic,
78                    clipboard.clone(),
79                ))
80            } else {
81                let is_binding = Self::is_binding(setter_value.as_ref());
82
83                if is_binding {
84                    SetterViewModelRef::Binding(BindingSetterViewModel::new(
85                        setter_property,
86                        setter_value,
87                        clipboard.clone(),
88                    ))
89                } else {
90                    SetterViewModelRef::Setter(SetterViewModel::new(setter_property, setter_value, clipboard.clone()))
91                }
92            };
93            setters.push(setter_vm);
94        }
95
96        let this = Rc::new(Self {
97            base: ViewModelBase::new(),
98            value_frame,
99            is_active: RefCell::new(false),
100            is_visible: RefCell::new(false),
101            description: Some(description),
102            setters,
103        });
104
105        this.set_is_visible(true);
106
107        this.update();
108
109        this
110    }
111
112    pub fn is_active(&self) -> bool {
113        *self.is_active.borrow()
114    }
115
116    pub fn set_is_active(&self, value: bool) {
117        self.base.raise_and_set_if_changed(&self.is_active, value, "IsActive");
118    }
119
120    pub fn is_visible(&self) -> bool {
121        *self.is_visible.borrow()
122    }
123
124    pub fn set_is_visible(&self, value: bool) {
125        self.base.raise_and_set_if_changed(&self.is_visible, value, "IsVisible");
126    }
127
128    pub fn description(&self) -> Option<String> {
129        self.description.clone()
130    }
131
132    pub fn setters(&self) -> &[SetterViewModelRef] {
133        &self.setters
134    }
135
136    /// The setters as a source of items, each as a value of its class (for the data templates
137    /// of the view, which choose by the class of the setter).
138    pub fn setters_source(&self) -> ItemsSource {
139        ItemsSource::from_items(self.setters.iter().map(|setter| Some(setter.boxed())))
140    }
141
142    pub fn update(&self) {
143        self.set_is_active(self.value_frame.is_active());
144    }
145
146    /// The key of the resource a setter value refers to and whether the resource is dynamic:
147    /// `None` for a value that is neither a static nor a dynamic resource with a key.
148    fn get_resource_info(value: Option<&BoxedValue>) -> Option<(BoxedValue, bool)> {
149        let value = value?;
150
151        if let Some(static_resource) = value.downcast_ref::<Rc<StaticResourceExtension>>() {
152            if let Some(resource_key) = static_resource.resource_key() {
153                return Some((resource_key, false));
154            }
155        }
156
157        // A dynamic resource is a binding of the setter: a setter of a style holds it as its
158        // binding description.
159        if let Some(dynamic_resource) = value.downcast_ref::<Rc<DynamicResourceExtension>>() {
160            if let Some(resource_key) = dynamic_resource.resource_key() {
161                return Some((resource_key, true));
162            }
163        }
164        if let Some(binding) = binding_of(value) {
165            if let Some(dynamic_resource) = binding.as_any().and_then(|any| any.downcast_ref::<DynamicResourceExtension>()) {
166                if let Some(resource_key) = dynamic_resource.resource_key() {
167                    return Some((resource_key, true));
168                }
169            }
170        }
171
172        None
173    }
174
175    /// Whether a setter value is a binding of one of the kinds the view shows as a binding.
176    fn is_binding(value: Option<&BoxedValue>) -> bool {
177        value.is_some_and(|value| binding_kind(value).is_some())
178    }
179
180    fn source_to_string(source: Option<BoxedValue>) -> Option<String> {
181        let source = source?;
182        let object = ValueTypes::as_object(&*source)?;
183
184        if let Some(style) = object.cast::<Style>() {
185            let mut current_style: Option<Ref<StyleBase>> = Some(style.upcast());
186            let mut selectors: Vec<String> = Vec::new();
187
188            while let Some(current) = current_style {
189                if let Some(selector) = current.cast::<Style>().and_then(|style| style.selector()) {
190                    selectors.push(selector.to_string());
191                }
192                if let Some(theme) = current.cast::<ControlTheme>() {
193                    selectors.push(format!("Theme {}", theme.target_type().map(TypeInfo::name).unwrap_or_default()));
194                }
195
196                current_style = current.parent();
197            }
198
199            // `string.Concat(selectors)` enumerates the stack from the last pushed selector: the
200            // outermost style first.
201            selectors.reverse();
202
203            Some(selectors.concat().replace('^', ""))
204        } else if let Some(control_theme) = object.cast::<ControlTheme>() {
205            control_theme.target_type().map(|target_type| target_type.name().to_string())
206        } else if let Some(styled_element) = object.cast::<StyledElement>() {
207            Some(styled_element.style_key().name().to_string())
208        } else {
209            None
210        }
211    }
212}
213
214/// The key of a resource as the resource lookup takes it: text, a class, or a key itself.
215///
216/// Deviation (DEVIATIONS.md, Developer tools): the markup crate's conversion of an untyped key
217/// (`object_casts::resource_key_of`) is internal to it; this repeats its cases for text, classes
218/// and keys, and looks any other key up by its text.
219fn resource_key_of(value: &BoxedValue) -> ResourceKey {
220    if let Some(key) = value.downcast_ref::<ResourceKey>() {
221        return key.clone();
222    }
223    if let Some(text) = value.downcast_ref::<String>() {
224        return ResourceKey::from(text.as_str());
225    }
226    if let Some(text) = value.downcast_ref::<&'static str>() {
227        return ResourceKey::from(*text);
228    }
229    if let Some(type_) = value.downcast_ref::<&'static TypeInfo>() {
230        return ResourceKey::Type(type_);
231    }
232    if let Some(type_) = value.downcast_ref::<ValueType>() {
233        if let Some((class, _)) = TypeInfo::find_by_handle(type_.id()) {
234            return ResourceKey::Type(class);
235        }
236    }
237    ResourceKey::from(ValueTypes::to_display_string(Some(value)))
238}
239
240ferro_markup_type!(class ValueFrameViewModel {
241    this: Rc<ValueFrameViewModel>,
242    handles: [ValueFrameViewModel, Rc<ValueFrameViewModel>, Option<Rc<ValueFrameViewModel>>],
243    properties: [
244        IsActive: bool { get: ValueFrameViewModel::is_active, set: ValueFrameViewModel::set_is_active },
245        IsVisible: bool { get: ValueFrameViewModel::is_visible, set: ValueFrameViewModel::set_is_visible },
246        Description: Option<String> { get: ValueFrameViewModel::description },
247        Setters: ItemsSource { get: ValueFrameViewModel::setters_source },
248    ],
249    methods: [
250        fn Update() => ValueFrameViewModel::update,
251    ],
252    notify_property_changed: ValueFrameViewModel,
253});