Skip to main content

ferroui_diagnostics/diagnostics/view_models/
setter_view_model.rs

1//! Port of `Diagnostics/ViewModels/SetterViewModel.cs`.
2//!
3//! The class is the base of [`BindingSetterViewModel`] and [`ResourceSetterViewModel`], which
4//! embed it and dereference to it. A list of setters of any of the three classes (the
5//! `List<SetterViewModel>` of a value frame) holds [`SetterViewModelRef`]s: the shared handle
6//! of an instance of one of the classes, through which the virtual `CopyValue` dispatches.
7
8use super::{BindingSetterViewModel, ResourceSetterViewModel, ViewModelBase};
9use ferroui_base::data::core::ValueTypes;
10use ferroui_base::data::model::{Event, INotifyPropertyChanged};
11use ferroui_base::input::platform::{ClipboardExtensions, IClipboard};
12use ferroui_base::threading::Dispatcher;
13use ferroui_base::{ferro_markup_type, BoxedValue, FerroProperty};
14use std::cell::RefCell;
15use std::ops::Deref;
16use std::rc::Rc;
17
18/// A setter of a value frame: the property, the value, and whether the setter is the one that
19/// is in effect (`IsActive`) and whether the filter of the setters shows it (`IsVisible`).
20pub struct SetterViewModel {
21    base: ViewModelBase,
22    is_active: RefCell<bool>,
23    is_visible: RefCell<bool>,
24    property: &'static FerroProperty,
25    name: String,
26    value: Option<BoxedValue>,
27    clipboard: Option<Rc<dyn IClipboard>>,
28}
29
30impl PartialEq for SetterViewModel {
31    fn eq(&self, other: &Self) -> bool {
32        std::ptr::eq(self, other)
33    }
34}
35
36impl INotifyPropertyChanged for SetterViewModel {
37    fn property_changed(&self) -> &Event<str> {
38        self.base.property_changed()
39    }
40}
41
42impl SetterViewModel {
43    pub fn new(
44        property: &'static FerroProperty,
45        value: Option<BoxedValue>,
46        clipboard: Option<Rc<dyn IClipboard>>,
47    ) -> Rc<Self> {
48        Rc::new(Self::construct(property, value, clipboard))
49    }
50
51    /// The state of the class, for the classes that derive from it (the constructor body runs
52    /// on it before it is shared).
53    pub fn construct(
54        property: &'static FerroProperty,
55        value: Option<BoxedValue>,
56        clipboard: Option<Rc<dyn IClipboard>>,
57    ) -> Self {
58        let this = Self {
59            base: ViewModelBase::new(),
60            is_active: RefCell::new(false),
61            is_visible: RefCell::new(false),
62            property,
63            name: property.name().to_string(),
64            value,
65            clipboard,
66        };
67
68        this.set_is_active(true);
69        this.set_is_visible(true);
70
71        this
72    }
73
74    pub fn property(&self) -> &'static FerroProperty {
75        self.property
76    }
77
78    pub fn name(&self) -> String {
79        self.name.clone()
80    }
81
82    pub fn value(&self) -> Option<BoxedValue> {
83        self.value.clone()
84    }
85
86    pub fn is_active(&self) -> bool {
87        *self.is_active.borrow()
88    }
89
90    pub fn set_is_active(&self, value: bool) {
91        self.base.raise_and_set_if_changed(&self.is_active, value, "IsActive");
92    }
93
94    pub fn is_visible(&self) -> bool {
95        *self.is_visible.borrow()
96    }
97
98    pub fn set_is_visible(&self, value: bool) {
99        self.base.raise_and_set_if_changed(&self.is_visible, value, "IsVisible");
100    }
101
102    /// `CopyValue` of this class (the virtual member dispatches through [`SetterViewModelRef`]):
103    /// copies the text of the value.
104    pub fn copy_value(&self) {
105        // `Value?.ToString()`: nothing to copy for a null value.
106        let Some(value) = self.value.as_ref() else {
107            return;
108        };
109        let text_to_copy = ValueTypes::to_display_string(Some(value));
110
111        self.copy_to_clipboard(&text_to_copy);
112    }
113
114    pub fn copy_property_name(&self) {
115        self.copy_to_clipboard(self.property.name());
116    }
117
118    /// `CopyToClipboard`: starts setting the text of the clipboard, without waiting for it (the
119    /// managed original discards the task).
120    pub fn copy_to_clipboard(&self, value: &str) {
121        let Some(clipboard) = self.clipboard.clone() else {
122            return;
123        };
124        let text = value.to_string();
125        let _ = Dispatcher::ui_thread().invoke_async_task_local(move || async move {
126            let _ = clipboard.set_text_async(Some(&text)).await;
127        });
128    }
129}
130
131/// The shared handle of a setter view model of any of the classes `SetterViewModel`,
132/// `BindingSetterViewModel` and `ResourceSetterViewModel`: a reference typed with the base
133/// class. It dereferences to the base class and dispatches the virtual member.
134#[derive(Clone)]
135pub enum SetterViewModelRef {
136    Setter(Rc<SetterViewModel>),
137    Binding(Rc<BindingSetterViewModel>),
138    Resource(Rc<ResourceSetterViewModel>),
139}
140
141impl PartialEq for SetterViewModelRef {
142    fn eq(&self, other: &Self) -> bool {
143        std::ptr::eq::<SetterViewModel>(&**self, &**other)
144    }
145}
146
147impl Deref for SetterViewModelRef {
148    type Target = SetterViewModel;
149
150    fn deref(&self) -> &SetterViewModel {
151        match self {
152            Self::Setter(setter) => setter,
153            Self::Binding(setter) => setter,
154            Self::Resource(setter) => setter,
155        }
156    }
157}
158
159impl SetterViewModelRef {
160    /// The virtual `CopyValue`.
161    pub fn copy_value(&self) {
162        match self {
163            Self::Setter(setter) => setter.copy_value(),
164            Self::Binding(setter) => setter.copy_value(),
165            Self::Resource(setter) => setter.copy_value(),
166        }
167    }
168
169    /// The setter as an untyped value of its class (what a list of the setters holds for the
170    /// data templates that choose by the class).
171    pub fn boxed(&self) -> BoxedValue {
172        match self {
173            Self::Setter(setter) => setter.clone(),
174            Self::Binding(setter) => setter.clone(),
175            Self::Resource(setter) => setter.clone(),
176        }
177    }
178
179    /// The setter of an untyped value of one of the classes (`DataContext as SetterViewModel`).
180    pub fn from_boxed(value: &BoxedValue) -> Option<Self> {
181        if let Some(setter) = value.downcast_ref::<Rc<BindingSetterViewModel>>() {
182            return Some(Self::Binding(setter.clone()));
183        }
184        if let Some(setter) = value.downcast_ref::<Rc<ResourceSetterViewModel>>() {
185            return Some(Self::Resource(setter.clone()));
186        }
187        if let Some(setter) = value.downcast_ref::<Rc<SetterViewModel>>() {
188            return Some(Self::Setter(setter.clone()));
189        }
190        // The box that is the shared object itself.
191        let any: Rc<dyn std::any::Any> = value.clone();
192        let any = match any.downcast::<BindingSetterViewModel>() {
193            Ok(setter) => return Some(Self::Binding(setter)),
194            Err(any) => any,
195        };
196        let any = match any.downcast::<ResourceSetterViewModel>() {
197            Ok(setter) => return Some(Self::Resource(setter)),
198            Err(any) => any,
199        };
200        any.downcast::<SetterViewModel>().ok().map(Self::Setter)
201    }
202}
203
204ferro_markup_type!(class SetterViewModel {
205    this: Rc<SetterViewModel>,
206    handles: [SetterViewModel, Rc<SetterViewModel>, Option<Rc<SetterViewModel>>],
207    properties: [
208        Property: &'static FerroProperty { get: SetterViewModel::property },
209        Name: String { get: SetterViewModel::name },
210        Value: Option<BoxedValue> { get: SetterViewModel::value },
211        IsActive: bool { get: SetterViewModel::is_active, set: SetterViewModel::set_is_active },
212        IsVisible: bool { get: SetterViewModel::is_visible, set: SetterViewModel::set_is_visible },
213    ],
214    methods: [
215        fn CopyValue() => SetterViewModel::copy_value,
216        fn CopyPropertyName() => SetterViewModel::copy_property_name,
217    ],
218    notify_property_changed: SetterViewModel,
219});