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}