Skip to main content

ferroui_diagnostics/diagnostics/view_models/
binding_setter_view_model.rs

1//! Port of `Diagnostics/ViewModels/BindingSetterViewModel.cs`.
2
3use super::SetterViewModel;
4use ferroui_base::data::model::{Event, INotifyPropertyChanged};
5use ferroui_base::data::{BindingBase, CompiledBinding, ReflectionBinding, TemplateBinding};
6use ferroui_base::input::platform::IClipboard;
7use ferroui_base::media::{Brushes, IBrush};
8use ferroui_base::{ferro_markup_type, BoxedValue, FerroProperty};
9use ferroui_markup::data::Binding;
10use ferroui_markup_xaml::markup_extensions::CompiledBindingExtension;
11use std::ops::Deref;
12use std::rc::Rc;
13
14/// A setter whose value is a binding: the path of the binding, with a tint and a tooltip that
15/// tell the kind of binding.
16pub struct BindingSetterViewModel {
17    base: SetterViewModel,
18    tint: Rc<dyn IBrush>,
19    value_type_tooltip: String,
20    path: String,
21}
22
23impl PartialEq for BindingSetterViewModel {
24    fn eq(&self, other: &Self) -> bool {
25        std::ptr::eq(self, other)
26    }
27}
28
29impl Deref for BindingSetterViewModel {
30    type Target = SetterViewModel;
31
32    fn deref(&self) -> &SetterViewModel {
33        &self.base
34    }
35}
36
37impl INotifyPropertyChanged for BindingSetterViewModel {
38    fn property_changed(&self) -> &Event<str> {
39        self.base.property_changed()
40    }
41}
42
43/// The binding a setter value holds: the binding description of a setter of a style
44/// (`Rc<dyn BindingBase>`), or a binding of one of the classes held as itself.
45pub(crate) fn binding_of(value: &BoxedValue) -> Option<Rc<dyn BindingBase>> {
46    if let Some(binding) = value.downcast_ref::<Rc<dyn BindingBase>>() {
47        return Some(binding.clone());
48    }
49    if let Some(binding) = value.downcast_ref::<Rc<Binding>>() {
50        return Some(binding.clone());
51    }
52    if let Some(binding) = value.downcast_ref::<Rc<ReflectionBinding>>() {
53        return Some(binding.clone());
54    }
55    if let Some(binding) = value.downcast_ref::<Rc<CompiledBindingExtension>>() {
56        return Some(binding.clone());
57    }
58    if let Some(binding) = value.downcast_ref::<Rc<CompiledBinding>>() {
59        return Some(binding.clone());
60    }
61    if let Some(binding) = value.downcast_ref::<Rc<TemplateBinding>>() {
62        return Some(binding.clone());
63    }
64    None
65}
66
67/// The kinds of binding the developer tools tell apart (the cases of the type switches of the
68/// managed original).
69pub(crate) enum BindingKind {
70    /// `Binding`: a binding with a string path, resolved at run time.
71    Reflection { path: String },
72    /// `CompiledBindingExtension`: a compiled binding.
73    Compiled { path: String },
74    /// `TemplateBinding`, with the property of the templated parent it binds to.
75    Template { property: Option<&'static FerroProperty> },
76}
77
78/// The kind of the binding a setter value holds; `None` for a value that is not one of the
79/// three kinds of binding.
80pub(crate) fn binding_kind(value: &BoxedValue) -> Option<BindingKind> {
81    let binding = binding_of(value)?;
82    let any = binding.as_any()?;
83
84    if let Some(binding) = any.downcast_ref::<Binding>() {
85        return Some(BindingKind::Reflection { path: binding.path() });
86    }
87    // The markup `Binding` derives from it, and the run-time loader creates it for a binding
88    // that is not compiled.
89    if let Some(binding) = any.downcast_ref::<ReflectionBinding>() {
90        return Some(BindingKind::Reflection { path: binding.path() });
91    }
92    if let Some(binding) = any.downcast_ref::<CompiledBindingExtension>() {
93        return Some(BindingKind::Compiled { path: binding.path().map(|path| path.to_string()).unwrap_or_default() });
94    }
95    // The binding the compiled markup of a document creates for `{CompiledBinding}`: what the
96    // extension provides.
97    if let Some(binding) = any.downcast_ref::<CompiledBinding>() {
98        return Some(BindingKind::Compiled { path: binding.path().map(|path| path.to_string()).unwrap_or_default() });
99    }
100    if let Some(binding) = any.downcast_ref::<TemplateBinding>() {
101        return Some(BindingKind::Template { property: binding.property() });
102    }
103
104    None
105}
106
107impl BindingSetterViewModel {
108    /// # Panics
109    /// When `value` is not a binding of one of the three kinds (`ArgumentException`: "Invalid
110    /// binding type").
111    pub fn new(
112        property: &'static FerroProperty,
113        value: Option<BoxedValue>,
114        clipboard: Option<Rc<dyn IClipboard>>,
115    ) -> Rc<Self> {
116        let kind = value.as_ref().and_then(binding_kind);
117
118        let (path, tint, value_type_tooltip): (String, Rc<dyn IBrush>, &str) = match kind {
119            Some(BindingKind::Reflection { path }) => (path, Brushes::cornflower_blue(), "Reflection Binding"),
120            Some(BindingKind::Compiled { path }) => (path, Brushes::dark_green(), "Compiled Binding"),
121            Some(BindingKind::Template { property }) => {
122                let path = match property {
123                    Some(template_property) => {
124                        format!("{}.{}", template_property.owner_type().name(), template_property.name())
125                    }
126                    None => "Unassigned".to_string(),
127                };
128
129                (path, Brushes::orange_red(), "Template Binding")
130            }
131            None => panic!("Invalid binding type (Parameter 'value')"),
132        };
133
134        Rc::new(Self {
135            base: SetterViewModel::construct(property, value, clipboard),
136            tint,
137            value_type_tooltip: value_type_tooltip.to_string(),
138            path,
139        })
140    }
141
142    pub fn tint(&self) -> Rc<dyn IBrush> {
143        self.tint.clone()
144    }
145
146    pub fn value_type_tooltip(&self) -> String {
147        self.value_type_tooltip.clone()
148    }
149
150    pub fn path(&self) -> String {
151        self.path.clone()
152    }
153
154    /// The override of `CopyValue`: copies the path of the binding.
155    pub fn copy_value(&self) {
156        self.base.copy_to_clipboard(&self.path);
157    }
158}
159
160ferro_markup_type!(class BindingSetterViewModel {
161    this: Rc<BindingSetterViewModel>,
162    handles: [BindingSetterViewModel, Rc<BindingSetterViewModel>, Option<Rc<BindingSetterViewModel>>],
163    base: SetterViewModel,
164    properties: [
165        Property: &'static FerroProperty { get: |this: &Rc<BindingSetterViewModel>| this.property() },
166        Name: String { get: |this: &Rc<BindingSetterViewModel>| this.name() },
167        Value: Option<BoxedValue> { get: |this: &Rc<BindingSetterViewModel>| this.value() },
168        IsActive: bool {
169            get: |this: &Rc<BindingSetterViewModel>| this.is_active(),
170            set: |this: &Rc<BindingSetterViewModel>, value: bool| this.set_is_active(value)
171        },
172        IsVisible: bool {
173            get: |this: &Rc<BindingSetterViewModel>| this.is_visible(),
174            set: |this: &Rc<BindingSetterViewModel>, value: bool| this.set_is_visible(value)
175        },
176        Tint: Rc<dyn IBrush> { get: BindingSetterViewModel::tint },
177        ValueTypeTooltip: String { get: BindingSetterViewModel::value_type_tooltip },
178        Path: String { get: BindingSetterViewModel::path },
179    ],
180    methods: [
181        fn CopyValue() => BindingSetterViewModel::copy_value,
182        fn CopyPropertyName() => |this: &Rc<BindingSetterViewModel>| this.copy_property_name(),
183    ],
184    notify_property_changed: BindingSetterViewModel,
185});