Skip to main content

ferroui_reactiveui/reactive_object/
reactive_object.rs

1//! `ReactiveObject` (`ReactiveUI.Shared/ReactiveObject/ReactiveObject.cs`) and the macro
2//! [`reactive_object!`](crate::reactive_object) that makes a type holding one a reactive object.
3
4use super::extension_state::{ExtensionState, PropertyEvents};
5use super::{IExtensionState, IReactiveObject, IReactiveObjectExtensions};
6use crate::ReactivePropertyChangedEventArgs;
7use ferroui_system::events::Event;
8use ferroui_system::{IDisposable, IObservable, ObservableError};
9use std::any::Any;
10use std::cell::{OnceCell, RefCell};
11use std::fmt;
12use std::rc::Rc;
13
14/// The part of a view model that makes it a reactive object (`ReactiveObject`): its
15/// `PropertyChanging` and `PropertyChanged` events, and the state of its change notifications
16/// (suppression, delay, the `Changing`, `Changed` and `ThrownExceptions` observables), created
17/// when first needed.
18///
19/// Deviation (DEVIATIONS.md, ReactiveUI): upstream's base class is a part that a view model holds
20/// as a field, named `reactive` by convention. The macro [`reactive_object!`](crate::reactive_object)
21/// writes the traits of a reactive object for the type, and
22/// [`reactive_properties!`](crate::reactive_properties) its notifying properties. The view model is
23/// shared as an `Rc`, whose handle gives the observables with the sender
24/// ([`IReactiveNotifyPropertyChanged`](crate::IReactiveNotifyPropertyChanged)).
25///
26/// ```
27/// use ferroui_reactiveui::{reactive_object, IReactiveNotifyPropertyChanged, IReactiveObjectExt, ReactiveObject};
28/// use ferroui_reactive::ObservableExt;
29/// use std::cell::RefCell;
30/// use std::rc::Rc;
31///
32/// #[derive(Default)]
33/// struct Person {
34///     reactive: ReactiveObject,
35///     name: RefCell<String>,
36/// }
37/// reactive_object!(Person);
38///
39/// impl Person {
40///     fn name(&self) -> String {
41///         self.name.borrow().clone()
42///     }
43///     fn set_name(&self, value: String) {
44///         self.raise_and_set_if_changed(&self.name, value, "Name");
45///     }
46/// }
47///
48/// let person = Rc::new(Person::default());
49/// let names = Rc::new(RefCell::new(Vec::new()));
50/// let seen = names.clone();
51/// let _subscription = person.changed().subscribe_next(move |change| seen.borrow_mut().push(change.property_name));
52/// person.set_name("Ada".into());
53/// person.set_name("Ada".into());
54/// assert_eq!(person.name(), "Ada");
55/// assert_eq!(*names.borrow(), vec!["Name".to_owned()]);
56/// ```
57pub struct ReactiveObject {
58    events: Rc<PropertyEvents>,
59    /// `_reactiveStateSlot`: the state, created when first needed.
60    state: OnceCell<Rc<ExtensionState>>,
61    /// `_changing`: the `Changing` observable of the handle of the object, once asked for.
62    changing: RefCell<Option<Rc<dyn Any>>>,
63    /// `_changed`: the `Changed` observable of the handle of the object, once asked for.
64    changed: RefCell<Option<Rc<dyn Any>>>,
65    /// `_thrownExceptions`.
66    thrown_exceptions: RefCell<Option<Rc<dyn IObservable<ObservableError>>>>,
67}
68
69impl Default for ReactiveObject {
70    fn default() -> Self {
71        Self::new()
72    }
73}
74
75impl fmt::Debug for ReactiveObject {
76    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
77        f.debug_struct("ReactiveObject")
78            .field("property_changing_handlers", &self.events.property_changing.handler_count())
79            .field("property_changed_handlers", &self.events.property_changed.handler_count())
80            .field("change_notifications_enabled", &self.are_change_notifications_enabled())
81            .finish()
82    }
83}
84
85impl AsRef<ReactiveObject> for ReactiveObject {
86    fn as_ref(&self) -> &ReactiveObject {
87        self
88    }
89}
90
91impl ReactiveObject {
92    /// The part of a reactive object without handlers or state.
93    pub fn new() -> Self {
94        Self {
95            events: Rc::new(PropertyEvents::new()),
96            state: OnceCell::new(),
97            changing: RefCell::new(None),
98            changed: RefCell::new(None),
99            thrown_exceptions: RefCell::new(None),
100        }
101    }
102
103    /// The event raised when a property is about to change (`PropertyChanging`). It is raised
104    /// once it has a handler, and follows the suppression and the delay of the notifications.
105    pub fn property_changing(&self) -> &Event<str> {
106        &self.events.property_changing
107    }
108
109    /// The event raised when a property has changed (`PropertyChanged`).
110    pub fn property_changed(&self) -> &Event<str> {
111        &self.events.property_changed
112    }
113
114    /// The state of the change notifications, created at the first call (`GetState`, through
115    /// `IReactiveObjectStateSlot.GetReactiveStateSlot`).
116    pub(crate) fn state(&self) -> &Rc<ExtensionState> {
117        self.state.get_or_init(|| ExtensionState::new(self.events.clone()))
118    }
119
120    /// The state of the change notifications, created at the first call (`GetState`).
121    pub fn extension_state(&self) -> Rc<dyn IExtensionState> {
122        self.state().clone()
123    }
124
125    /// Raises the changing notifications of `property_name` for an object of the type
126    /// `sender_type` (`RaisingPropertyChanging`).
127    pub(crate) fn raising_property_changing(&self, property_name: &str, sender_type: &'static str) {
128        let state = self.state();
129        state.set_sender_type(sender_type);
130        state.raise_changing(property_name);
131    }
132
133    /// Raises the changed notifications of `property_name` for an object of the type
134    /// `sender_type` (`RaisingPropertyChanged`).
135    pub(crate) fn raising_property_changed(&self, property_name: &str, sender_type: &'static str) {
136        let state = self.state();
137        state.set_sender_type(sender_type);
138        state.raise_changed(property_name);
139    }
140
141    /// Makes changes raise the `PropertyChanging` event (`SubscribePropertyChangingEvents`). A
142    /// change does it by itself when the event has a handler.
143    pub fn subscribe_property_changing_events(&self) {
144        self.state().subscribe_changing();
145    }
146
147    /// Makes changes raise the `PropertyChanged` event (`SubscribePropertyChangedEvents`). A
148    /// change does it by itself when the event has a handler.
149    pub fn subscribe_property_changed_events(&self) {
150        self.state().subscribe_changed();
151    }
152
153    /// Stops all change notifications, the events and the observables, until the result is
154    /// disposed (`SuppressChangeNotifications`). The calls nest: the notifications resume when
155    /// every result is disposed.
156    pub fn suppress_change_notifications(&self) -> Rc<dyn IDisposable> {
157        self.state().suppress()
158    }
159
160    /// Whether change notifications are sent (`AreChangeNotificationsEnabled`).
161    pub fn are_change_notifications_enabled(&self) -> bool {
162        match self.state.get() {
163            Some(state) => state.notifications_enabled(),
164            None => true,
165        }
166    }
167
168    /// Keeps the change notifications until the result is disposed, then sends the last one of
169    /// each property, in the order of those last notifications (`DelayChangeNotifications`). The
170    /// calls nest.
171    pub fn delay_change_notifications(&self) -> Rc<dyn IDisposable> {
172        self.state().delay()
173    }
174
175    /// Whether change notifications are delayed (`AreChangeNotificationsDelayed`).
176    pub fn are_change_notifications_delayed(&self) -> bool {
177        match self.state.get() {
178            Some(state) => state.are_change_notifications_delayed(),
179            None => false,
180        }
181    }
182
183    /// The errors of the subscribers of the change observables (`ThrownExceptions`); the same
184    /// observable at every call.
185    pub fn thrown_exceptions(&self) -> Rc<dyn IObservable<ObservableError>> {
186        if let Some(cached) = self.thrown_exceptions.borrow().clone() {
187            return cached;
188        }
189        let created = self.state().thrown_exceptions();
190        *self.thrown_exceptions.borrow_mut() = Some(created.clone());
191        created
192    }
193
194    /// The `Changing` observable of `source`, whose part this is; the same observable at every
195    /// call (`Changing`).
196    pub(crate) fn changing_of<T: IReactiveObject>(&self, source: &Rc<T>) -> Rc<dyn IObservable<ReactivePropertyChangedEventArgs<Rc<T>>>> {
197        Self::cached(&self.changing, || IReactiveObjectExtensions::get_changing_observable(source))
198    }
199
200    /// The `Changed` observable of `source`, whose part this is; the same observable at every
201    /// call (`Changed`).
202    pub(crate) fn changed_of<T: IReactiveObject>(&self, source: &Rc<T>) -> Rc<dyn IObservable<ReactivePropertyChangedEventArgs<Rc<T>>>> {
203        Self::cached(&self.changed, || IReactiveObjectExtensions::get_changed_observable(source))
204    }
205
206    fn cached<O: Clone + 'static>(cache: &RefCell<Option<Rc<dyn Any>>>, create: impl FnOnce() -> O) -> O {
207        let hit = cache.borrow().as_ref().and_then(|cached| cached.downcast_ref::<O>().cloned());
208        if let Some(hit) = hit {
209            return hit;
210        }
211        let created = create();
212        *cache.borrow_mut() = Some(Rc::new(created.clone()));
213        created
214    }
215}
216
217/// Makes a type that holds a [`ReactiveObject`] a reactive object: writes its
218/// `INotifyPropertyChanged` (with `as_notify_property_changing`), `INotifyPropertyChanging`,
219/// `IEnableLogger`, [`IReactiveObject`](crate::IReactiveObject) and
220/// [`IHandleObservableErrors`](crate::IHandleObservableErrors).
221///
222/// `reactive_object!(Type)` uses the field `reactive`; `reactive_object!(Type, field)` names the
223/// field. The field is a [`ReactiveObject`] or a [`ReactiveRecord`](crate::ReactiveRecord). The type
224/// is not generic.
225///
226/// The impls cannot be blanket impls over a trait: Rust's orphan rule forbids implementing the
227/// traits of `ferroui-system` for every type that holds the part, so a macro writes them, as a
228/// derived base class gets them upstream.
229///
230/// ```
231/// use ferroui_reactiveui::{reactive_object, IReactiveObject, ReactiveObject};
232/// use ferroui_system::component_model::INotifyPropertyChanged;
233/// use std::cell::RefCell;
234/// use std::rc::Rc;
235///
236/// struct Model {
237///     state: ReactiveObject,
238/// }
239/// reactive_object!(Model, state);
240///
241/// let model = Model { state: ReactiveObject::new() };
242/// let raised = Rc::new(RefCell::new(Vec::<String>::new()));
243/// let log = raised.clone();
244/// model.property_changed().add(Rc::new(move |name: &str| log.borrow_mut().push(name.to_owned())));
245/// model.raise_property_changed("Title");
246/// assert_eq!(*raised.borrow(), vec!["Title".to_owned()]);
247/// assert!(model.as_notify_property_changing().is_some());
248/// ```
249#[macro_export]
250macro_rules! reactive_object {
251    ($type_:ty) => {
252        $crate::reactive_object!($type_, reactive);
253    };
254    ($type_:ty, $field:ident) => {
255        impl $crate::__private::INotifyPropertyChanged for $type_ {
256            fn property_changed(&self) -> &$crate::__private::Event<str> {
257                ::core::convert::AsRef::<$crate::ReactiveObject>::as_ref(&self.$field).property_changed()
258            }
259
260            fn as_notify_property_changing(&self) -> ::core::option::Option<&dyn $crate::__private::INotifyPropertyChanging> {
261                ::core::option::Option::Some(self)
262            }
263        }
264
265        impl $crate::__private::INotifyPropertyChanging for $type_ {
266            fn property_changing(&self) -> &$crate::__private::Event<str> {
267                ::core::convert::AsRef::<$crate::ReactiveObject>::as_ref(&self.$field).property_changing()
268            }
269        }
270
271        impl $crate::__private::IEnableLogger for $type_ {}
272
273        impl $crate::IReactiveObject for $type_ {
274            fn reactive_object(&self) -> &$crate::ReactiveObject {
275                ::core::convert::AsRef::<$crate::ReactiveObject>::as_ref(&self.$field)
276            }
277        }
278
279        impl $crate::IHandleObservableErrors for $type_ {
280            fn thrown_exceptions(
281                &self,
282            ) -> ::std::rc::Rc<dyn $crate::__private::IObservable<$crate::__private::ObservableError>> {
283                ::core::convert::AsRef::<$crate::ReactiveObject>::as_ref(&self.$field).thrown_exceptions()
284            }
285        }
286    };
287}