Skip to main content

ferroui_dialogs/internal/
ferro_dialogs_internal_view_model_base.rs

1use ferroui_base::data::model::{Event, INotifyPropertyChanged};
2use std::cell::{Cell, RefCell};
3
4/// The base of the view models of the dialogs: the property change
5/// notification and the helpers that raise it.
6///
7/// A view model embeds it (the base class of the managed original) and
8/// forwards [`INotifyPropertyChanged`] to it. The name of the property is
9/// passed explicitly where the managed original takes the name of the
10/// calling member.
11#[derive(Default)]
12pub struct FerroDialogsInternalViewModelBase {
13    property_changed: Event<str>,
14}
15
16impl INotifyPropertyChanged for FerroDialogsInternalViewModelBase {
17    fn property_changed(&self) -> &Event<str> {
18        &self.property_changed
19    }
20}
21
22impl FerroDialogsInternalViewModelBase {
23    pub fn new() -> Self {
24        Self::default()
25    }
26
27    /// `RaiseAndSetIfChanged(ref field, value)` for a field of a type that
28    /// is not `Copy`: stores `value` and raises the notification of
29    /// `property_name` when it differs from the stored value. Returns
30    /// whether it did.
31    pub fn raise_and_set_if_changed<T: PartialEq>(&self, field: &RefCell<T>, value: T, property_name: &str) -> bool {
32        if *field.borrow() != value {
33            // The borrow ends before the handlers run: they read the property.
34            *field.borrow_mut() = value;
35            self.property_changed.raise(property_name);
36            return true;
37        }
38
39        false
40    }
41
42    /// `RaiseAndSetIfChanged(ref field, value)` for a `Copy` field.
43    pub fn raise_and_set_if_changed_cell<T: PartialEq + Copy>(&self, field: &Cell<T>, value: T, property_name: &str) -> bool {
44        if field.get() != value {
45            field.set(value);
46            self.property_changed.raise(property_name);
47            return true;
48        }
49
50        false
51    }
52
53    /// `RaisePropertyChanged(propertyName)`.
54    pub fn raise_property_changed(&self, property_name: &str) {
55        self.property_changed.raise(property_name);
56    }
57}
58
59#[cfg(test)]
60mod tests {
61    // Not from upstream: the upstream project has no tests.
62    use super::*;
63    use std::rc::Rc;
64
65    fn record(base: &FerroDialogsInternalViewModelBase) -> Rc<RefCell<Vec<String>>> {
66        let raised = Rc::new(RefCell::new(Vec::new()));
67        let sink = raised.clone();
68        base.property_changed().add(Rc::new(move |name: &str| sink.borrow_mut().push(name.to_string())));
69        raised
70    }
71
72    #[test]
73    fn raise_and_set_if_changed_stores_and_notifies_only_on_change() {
74        let base = FerroDialogsInternalViewModelBase::new();
75        let raised = record(&base);
76        let field = RefCell::new(String::from("a"));
77        let count = Cell::new(1);
78
79        assert!(!base.raise_and_set_if_changed(&field, String::from("a"), "Text"));
80        assert!(base.raise_and_set_if_changed(&field, String::from("b"), "Text"));
81        assert!(!base.raise_and_set_if_changed_cell(&count, 1, "Count"));
82        assert!(base.raise_and_set_if_changed_cell(&count, 2, "Count"));
83        base.raise_property_changed("Other");
84
85        assert_eq!("b", *field.borrow());
86        assert_eq!(2, count.get());
87        assert_eq!(vec!["Text", "Count", "Other"], *raised.borrow());
88    }
89}