Skip to main content

ferroui_dynamic_data/binding/
notify_property_changed_ex.rs

1//! `NotifyPropertyChangedEx` (`Binding/NotifyPropertyChangedEx.cs`): the sequences of the changes
2//! of the properties of an object that raises `PropertyChanged`.
3
4use super::observable_property_factory::{when_changed_of, when_property_changed_of};
5use super::{IntoPropertyChain, PropertyValue};
6use ferroui_reactive::{Observable, ObservableExt};
7use ferroui_system::component_model::INotifyPropertyChanged;
8use ferroui_system::{IObservable, IObserver};
9use std::rc::Rc;
10
11/// A fallback value of a property whose path a null step interrupts (`Func<TProperty>?`).
12pub type Fallback<V> = Option<Rc<dyn Fn() -> V>>;
13
14/// The observation of the properties of an object that raises `PropertyChanged`
15/// (`NotifyPropertyChangedEx`), implemented for the objects held in `Rc` (upstream's classes).
16///
17/// The paths are [`PropertyPath`](ferroui_system::component_model::PropertyPath)s from `prop!`,
18/// or [`PropertyChain`](super::PropertyChain)s when a step may be null. Upstream's optional
19/// parameters become methods of their own: `_with` takes `notify_on_initial_value`, and
20/// `_with_fallback` the fallback value too.
21pub trait NotifyPropertyChangedEx: Clone + 'static {
22    /// The object, whenever one of `properties_to_monitor` changes, or any property when none is
23    /// given (`WhenAnyPropertyChanged(params string[] propertiesToMonitor)`).
24    fn when_any_property_changed(&self, properties_to_monitor: &[&str]) -> Rc<dyn IObservable<Self>>;
25
26    /// The changes of the property of `property_accessor`, as the object with the value, the
27    /// current value first; nothing while a null step interrupts the path
28    /// (`WhenPropertyChanged(Expression<Func<TObject, TProperty>>, bool notifyOnInitialValue = true)`).
29    fn when_property_changed<P: IntoPropertyChain<Self>>(&self, property_accessor: P) -> Rc<dyn IObservable<PropertyValue<Self, P::Value>>>
30    where
31        P::Value: 'static,
32    {
33        self.when_property_changed_with(property_accessor, true)
34    }
35
36    /// [`when_property_changed`](Self::when_property_changed) that starts with the current value
37    /// only when `notify_on_initial_value` is true
38    /// (`WhenPropertyChanged(Expression<Func<TObject, TProperty>>, bool notifyOnInitialValue)`).
39    fn when_property_changed_with<P: IntoPropertyChain<Self>>(
40        &self,
41        property_accessor: P,
42        notify_on_initial_value: bool,
43    ) -> Rc<dyn IObservable<PropertyValue<Self, P::Value>>>
44    where
45        P::Value: 'static,
46    {
47        when_property_changed_of(property_accessor.into_property_chain(), self.clone(), notify_on_initial_value, false)
48    }
49
50    /// [`when_property_changed_with`](Self::when_property_changed_with) with a fallback value: the
51    /// values that a null step interrupts pass too, without a value
52    /// (`WhenPropertyChanged(Expression<Func<TObject, TProperty>>, bool, Func<TProperty?>? fallbackValue)`).
53    ///
54    /// As upstream, the fallback is not called: its presence lets the unobtainable values through.
55    fn when_property_changed_with_fallback<P: IntoPropertyChain<Self>>(
56        &self,
57        property_accessor: P,
58        notify_on_initial_value: bool,
59        _fallback_value: impl Fn() -> P::Value + 'static,
60    ) -> Rc<dyn IObservable<PropertyValue<Self, P::Value>>>
61    where
62        P::Value: 'static,
63    {
64        when_property_changed_of(property_accessor.into_property_chain(), self.clone(), notify_on_initial_value, true)
65    }
66
67    /// The values of the property of `property_accessor`, the current value first; nothing while a
68    /// null step interrupts the path
69    /// (`WhenValueChanged(Expression<Func<TObject, TProperty>>, bool notifyOnInitialValue = true)`).
70    fn when_value_changed<P: IntoPropertyChain<Self>>(&self, property_accessor: P) -> Rc<dyn IObservable<P::Value>>
71    where
72        P::Value: 'static,
73    {
74        self.when_value_changed_with(property_accessor, true)
75    }
76
77    /// [`when_value_changed`](Self::when_value_changed) that starts with the current value only
78    /// when `notify_on_initial_value` is true
79    /// (`WhenValueChanged(Expression<Func<TObject, TProperty>>, bool notifyOnInitialValue)`).
80    fn when_value_changed_with<P: IntoPropertyChain<Self>>(&self, property_accessor: P, notify_on_initial_value: bool) -> Rc<dyn IObservable<P::Value>>
81    where
82        P::Value: 'static,
83    {
84        when_changed_of(property_accessor.into_property_chain(), self.clone(), notify_on_initial_value, None)
85    }
86
87    /// [`when_value_changed_with`](Self::when_value_changed_with) with `fallback_value` for the
88    /// values that a null step interrupts
89    /// (`WhenValueChanged(Expression<Func<TObject, TProperty>>, bool, Func<TProperty>? fallbackValue)`).
90    fn when_value_changed_with_fallback<P: IntoPropertyChain<Self>>(
91        &self,
92        property_accessor: P,
93        notify_on_initial_value: bool,
94        fallback_value: impl Fn() -> P::Value + 'static,
95    ) -> Rc<dyn IObservable<P::Value>>
96    where
97        P::Value: 'static,
98    {
99        when_changed_of(property_accessor.into_property_chain(), self.clone(), notify_on_initial_value, Some(Rc::new(fallback_value)))
100    }
101
102    /// The value of `p1` with the object, through `result_selector`, the current one first
103    /// (`WhenChanged(Expression<Func<TObject, TProperty1>>, Func<TObject, TProperty1?, TResult>)`).
104    fn when_changed<P1: IntoPropertyChain<Self>, R: 'static>(&self, p1: P1, result_selector: impl Fn(&Self, P1::Value) -> R + 'static) -> Rc<dyn IObservable<R>>
105    where
106        P1::Value: Clone + 'static,
107    {
108        self.when_changed_with(p1, result_selector, None)
109    }
110
111    /// [`when_changed`](Self::when_changed) with a fallback value for the values that a null step
112    /// interrupts, which are dropped without one
113    /// (`WhenChanged(.., Func<TProperty1>? p1Fallback)`).
114    fn when_changed_with<P1: IntoPropertyChain<Self>, R: 'static>(
115        &self,
116        p1: P1,
117        result_selector: impl Fn(&Self, P1::Value) -> R + 'static,
118        p1_fallback: Fallback<P1::Value>,
119    ) -> Rc<dyn IObservable<R>>
120    where
121        P1::Value: Clone + 'static,
122    {
123        let source = self.clone();
124        when_changed_of(p1.into_property_chain(), self.clone(), true, p1_fallback).select(move |v1| result_selector(&source, v1))
125    }
126
127    /// The latest values of `p1` and `p2` with the object, through `result_selector`, whenever one
128    /// changes (`WhenChanged` of two properties).
129    fn when_changed2<P1: IntoPropertyChain<Self>, P2: IntoPropertyChain<Self>, R: 'static>(
130        &self,
131        p1: P1,
132        p2: P2,
133        result_selector: impl Fn(&Self, P1::Value, P2::Value) -> R + 'static,
134    ) -> Rc<dyn IObservable<R>>
135    where
136        P1::Value: Clone + 'static,
137        P2::Value: Clone + 'static,
138    {
139        self.when_changed2_with(p1, p2, result_selector, None, None)
140    }
141
142    /// [`when_changed2`](Self::when_changed2) with fallback values (`WhenChanged` of two
143    /// properties, with `p1Fallback` and `p2Fallback`).
144    fn when_changed2_with<P1: IntoPropertyChain<Self>, P2: IntoPropertyChain<Self>, R: 'static>(
145        &self,
146        p1: P1,
147        p2: P2,
148        result_selector: impl Fn(&Self, P1::Value, P2::Value) -> R + 'static,
149        p1_fallback: Fallback<P1::Value>,
150        p2_fallback: Fallback<P2::Value>,
151    ) -> Rc<dyn IObservable<R>>
152    where
153        P1::Value: Clone + 'static,
154        P2::Value: Clone + 'static,
155    {
156        let source = self.clone();
157        when_changed_of(p1.into_property_chain(), self.clone(), true, p1_fallback)
158            .combine_latest(when_changed_of(p2.into_property_chain(), self.clone(), true, p2_fallback), move |v1, v2| result_selector(&source, v1, v2))
159    }
160
161    /// The latest values of three properties with the object, through `result_selector`
162    /// (`WhenChanged` of three properties).
163    fn when_changed3<P1: IntoPropertyChain<Self>, P2: IntoPropertyChain<Self>, P3: IntoPropertyChain<Self>, R: 'static>(
164        &self,
165        p1: P1,
166        p2: P2,
167        p3: P3,
168        result_selector: impl Fn(&Self, P1::Value, P2::Value, P3::Value) -> R + 'static,
169    ) -> Rc<dyn IObservable<R>>
170    where
171        P1::Value: Clone + 'static,
172        P2::Value: Clone + 'static,
173        P3::Value: Clone + 'static,
174    {
175        self.when_changed3_with(p1, p2, p3, result_selector, None, None, None)
176    }
177
178    /// [`when_changed3`](Self::when_changed3) with fallback values.
179    #[allow(clippy::too_many_arguments)]
180    fn when_changed3_with<P1: IntoPropertyChain<Self>, P2: IntoPropertyChain<Self>, P3: IntoPropertyChain<Self>, R: 'static>(
181        &self,
182        p1: P1,
183        p2: P2,
184        p3: P3,
185        result_selector: impl Fn(&Self, P1::Value, P2::Value, P3::Value) -> R + 'static,
186        p1_fallback: Fallback<P1::Value>,
187        p2_fallback: Fallback<P2::Value>,
188        p3_fallback: Fallback<P3::Value>,
189    ) -> Rc<dyn IObservable<R>>
190    where
191        P1::Value: Clone + 'static,
192        P2::Value: Clone + 'static,
193        P3::Value: Clone + 'static,
194    {
195        let source = self.clone();
196        when_changed_of(p1.into_property_chain(), self.clone(), true, p1_fallback).combine_latest3(
197            when_changed_of(p2.into_property_chain(), self.clone(), true, p2_fallback),
198            when_changed_of(p3.into_property_chain(), self.clone(), true, p3_fallback),
199            move |v1, v2, v3| result_selector(&source, v1, v2, v3),
200        )
201    }
202
203    /// The latest values of four properties with the object, through `result_selector`
204    /// (`WhenChanged` of four properties).
205    fn when_changed4<P1: IntoPropertyChain<Self>, P2: IntoPropertyChain<Self>, P3: IntoPropertyChain<Self>, P4: IntoPropertyChain<Self>, R: 'static>(
206        &self,
207        p1: P1,
208        p2: P2,
209        p3: P3,
210        p4: P4,
211        result_selector: impl Fn(&Self, P1::Value, P2::Value, P3::Value, P4::Value) -> R + 'static,
212    ) -> Rc<dyn IObservable<R>>
213    where
214        P1::Value: Clone + 'static,
215        P2::Value: Clone + 'static,
216        P3::Value: Clone + 'static,
217        P4::Value: Clone + 'static,
218    {
219        self.when_changed4_with(p1, p2, p3, p4, result_selector, None, None, None, None)
220    }
221
222    /// [`when_changed4`](Self::when_changed4) with fallback values.
223    #[allow(clippy::too_many_arguments)]
224    fn when_changed4_with<P1: IntoPropertyChain<Self>, P2: IntoPropertyChain<Self>, P3: IntoPropertyChain<Self>, P4: IntoPropertyChain<Self>, R: 'static>(
225        &self,
226        p1: P1,
227        p2: P2,
228        p3: P3,
229        p4: P4,
230        result_selector: impl Fn(&Self, P1::Value, P2::Value, P3::Value, P4::Value) -> R + 'static,
231        p1_fallback: Fallback<P1::Value>,
232        p2_fallback: Fallback<P2::Value>,
233        p3_fallback: Fallback<P3::Value>,
234        p4_fallback: Fallback<P4::Value>,
235    ) -> Rc<dyn IObservable<R>>
236    where
237        P1::Value: Clone + 'static,
238        P2::Value: Clone + 'static,
239        P3::Value: Clone + 'static,
240        P4::Value: Clone + 'static,
241    {
242        let source = self.clone();
243        when_changed_of(p1.into_property_chain(), self.clone(), true, p1_fallback).combine_latest4(
244            when_changed_of(p2.into_property_chain(), self.clone(), true, p2_fallback),
245            when_changed_of(p3.into_property_chain(), self.clone(), true, p3_fallback),
246            when_changed_of(p4.into_property_chain(), self.clone(), true, p4_fallback),
247            move |v1, v2, v3, v4| result_selector(&source, v1, v2, v3, v4),
248        )
249    }
250
251    /// The latest values of five properties with the object, through `result_selector`
252    /// (`WhenChanged` of five properties).
253    #[allow(clippy::too_many_arguments)]
254    fn when_changed5<P1: IntoPropertyChain<Self>, P2: IntoPropertyChain<Self>, P3: IntoPropertyChain<Self>, P4: IntoPropertyChain<Self>, P5: IntoPropertyChain<Self>, R: 'static>(
255        &self,
256        p1: P1,
257        p2: P2,
258        p3: P3,
259        p4: P4,
260        p5: P5,
261        result_selector: impl Fn(&Self, P1::Value, P2::Value, P3::Value, P4::Value, P5::Value) -> R + 'static,
262    ) -> Rc<dyn IObservable<R>>
263    where
264        P1::Value: Clone + 'static,
265        P2::Value: Clone + 'static,
266        P3::Value: Clone + 'static,
267        P4::Value: Clone + 'static,
268        P5::Value: Clone + 'static,
269    {
270        self.when_changed5_with(p1, p2, p3, p4, p5, result_selector, None, None, None, None, None)
271    }
272
273    /// [`when_changed5`](Self::when_changed5) with fallback values.
274    #[allow(clippy::too_many_arguments)]
275    fn when_changed5_with<P1: IntoPropertyChain<Self>, P2: IntoPropertyChain<Self>, P3: IntoPropertyChain<Self>, P4: IntoPropertyChain<Self>, P5: IntoPropertyChain<Self>, R: 'static>(
276        &self,
277        p1: P1,
278        p2: P2,
279        p3: P3,
280        p4: P4,
281        p5: P5,
282        result_selector: impl Fn(&Self, P1::Value, P2::Value, P3::Value, P4::Value, P5::Value) -> R + 'static,
283        p1_fallback: Fallback<P1::Value>,
284        p2_fallback: Fallback<P2::Value>,
285        p3_fallback: Fallback<P3::Value>,
286        p4_fallback: Fallback<P4::Value>,
287        p5_fallback: Fallback<P5::Value>,
288    ) -> Rc<dyn IObservable<R>>
289    where
290        P1::Value: Clone + 'static,
291        P2::Value: Clone + 'static,
292        P3::Value: Clone + 'static,
293        P4::Value: Clone + 'static,
294        P5::Value: Clone + 'static,
295    {
296        let source = self.clone();
297        when_changed_of(p1.into_property_chain(), self.clone(), true, p1_fallback).combine_latest5(
298            when_changed_of(p2.into_property_chain(), self.clone(), true, p2_fallback),
299            when_changed_of(p3.into_property_chain(), self.clone(), true, p3_fallback),
300            when_changed_of(p4.into_property_chain(), self.clone(), true, p4_fallback),
301            when_changed_of(p5.into_property_chain(), self.clone(), true, p5_fallback),
302            move |v1, v2, v3, v4, v5| result_selector(&source, v1, v2, v3, v4, v5),
303        )
304    }
305
306    /// The latest values of six properties with the object, through `result_selector`
307    /// (`WhenChanged` of six properties).
308    #[allow(clippy::too_many_arguments)]
309    fn when_changed6<P1: IntoPropertyChain<Self>, P2: IntoPropertyChain<Self>, P3: IntoPropertyChain<Self>, P4: IntoPropertyChain<Self>, P5: IntoPropertyChain<Self>, P6: IntoPropertyChain<Self>, R: 'static>(
310        &self,
311        p1: P1,
312        p2: P2,
313        p3: P3,
314        p4: P4,
315        p5: P5,
316        p6: P6,
317        result_selector: impl Fn(&Self, P1::Value, P2::Value, P3::Value, P4::Value, P5::Value, P6::Value) -> R + 'static,
318    ) -> Rc<dyn IObservable<R>>
319    where
320        P1::Value: Clone + 'static,
321        P2::Value: Clone + 'static,
322        P3::Value: Clone + 'static,
323        P4::Value: Clone + 'static,
324        P5::Value: Clone + 'static,
325        P6::Value: Clone + 'static,
326    {
327        self.when_changed6_with(p1, p2, p3, p4, p5, p6, result_selector, None, None, None, None, None, None)
328    }
329
330    /// [`when_changed6`](Self::when_changed6) with fallback values.
331    #[allow(clippy::too_many_arguments)]
332    fn when_changed6_with<P1: IntoPropertyChain<Self>, P2: IntoPropertyChain<Self>, P3: IntoPropertyChain<Self>, P4: IntoPropertyChain<Self>, P5: IntoPropertyChain<Self>, P6: IntoPropertyChain<Self>, R: 'static>(
333        &self,
334        p1: P1,
335        p2: P2,
336        p3: P3,
337        p4: P4,
338        p5: P5,
339        p6: P6,
340        result_selector: impl Fn(&Self, P1::Value, P2::Value, P3::Value, P4::Value, P5::Value, P6::Value) -> R + 'static,
341        p1_fallback: Fallback<P1::Value>,
342        p2_fallback: Fallback<P2::Value>,
343        p3_fallback: Fallback<P3::Value>,
344        p4_fallback: Fallback<P4::Value>,
345        p5_fallback: Fallback<P5::Value>,
346        p6_fallback: Fallback<P6::Value>,
347    ) -> Rc<dyn IObservable<R>>
348    where
349        P1::Value: Clone + 'static,
350        P2::Value: Clone + 'static,
351        P3::Value: Clone + 'static,
352        P4::Value: Clone + 'static,
353        P5::Value: Clone + 'static,
354        P6::Value: Clone + 'static,
355    {
356        let source = self.clone();
357        when_changed_of(p1.into_property_chain(), self.clone(), true, p1_fallback).combine_latest6(
358            when_changed_of(p2.into_property_chain(), self.clone(), true, p2_fallback),
359            when_changed_of(p3.into_property_chain(), self.clone(), true, p3_fallback),
360            when_changed_of(p4.into_property_chain(), self.clone(), true, p4_fallback),
361            when_changed_of(p5.into_property_chain(), self.clone(), true, p5_fallback),
362            when_changed_of(p6.into_property_chain(), self.clone(), true, p6_fallback),
363            move |v1, v2, v3, v4, v5, v6| result_selector(&source, v1, v2, v3, v4, v5, v6),
364        )
365    }
366}
367
368impl<M: INotifyPropertyChanged + 'static> NotifyPropertyChangedEx for Rc<M> {
369    fn when_any_property_changed(&self, properties_to_monitor: &[&str]) -> Rc<dyn IObservable<Self>> {
370        let source = self.clone();
371        let properties_to_monitor: Rc<[String]> = properties_to_monitor.iter().map(|name| name.to_string()).collect();
372        // `Observable.FromEventPattern` of `PropertyChanged`: a handler per subscription.
373        Observable::create(move |observer: Rc<dyn IObserver<Rc<M>>>| {
374            let (sender, properties_to_monitor) = (source.clone(), properties_to_monitor.clone());
375            let token = source.property_changed().add(Rc::new(move |property_name: &str| {
376                if properties_to_monitor.is_empty() || properties_to_monitor.iter().any(|name| name == property_name) {
377                    observer.on_next(sender.clone());
378                }
379            }));
380            let target = source.clone();
381            ferroui_reactive::disposables::Disposable::create(move || {
382                target.property_changed().remove(token);
383            })
384        })
385    }
386}