Skip to main content

ferroui_dynamic_data/binding/
observable_collection_ex.rs

1//! `ObservableCollectionEx` (`Binding/ObservableCollectionEx.cs`): the changes of an observable
2//! collection as an observable, and as change sets.
3
4use super::{ObservableCollectionExtended, ReadOnlyObservableCollection};
5use crate::list::{ChangeAwareList, ChangeSet, IExtendedList, ObservableListEx};
6use ferroui_reactive::disposables::Disposable;
7use ferroui_reactive::linq::EventPattern;
8use ferroui_reactive::{Observable, ObservableExt};
9use ferroui_system::collections::object_model::ObservableCollection;
10use ferroui_system::collections::specialized::{NotifyCollectionChangedAction, NotifyCollectionChangedEventArgs, SharedNotifyCollectionChangedEventArgs};
11use ferroui_system::{IObservable, IObserver};
12use std::cell::RefCell;
13use std::hash::Hash;
14use std::rc::Rc;
15
16/// A notification of a change of a collection (`EventPattern<NotifyCollectionChangedEventArgs>`):
17/// the collection that changed, and the description of the change.
18pub type CollectionChangedEventPattern<T> = EventPattern<ObservableCollection<T>, SharedNotifyCollectionChangedEventArgs<T>>;
19
20/// The changes of an observable collection as an observable (`ObservableCollectionEx`), for
21/// `ObservableCollection<T>`, `ObservableCollectionExtended<T>` and
22/// `ReadOnlyObservableCollection<T>`.
23///
24/// Deviation (DEVIATIONS.md, DynamicData): upstream's generic form takes any
25/// `INotifyCollectionChanged` that enumerates its items; here the collections are those that notify
26/// their items, which the untyped `INotifyCollectionChanged` of `ferroui-system` does not.
27pub trait ObservableCollectionEx<T: Clone + PartialEq + 'static> {
28    /// The observable collection whose changes are observed.
29    fn notifying_collection(&self) -> ObservableCollection<T>;
30
31    /// The notifications of the changes of the collection (`ObserveCollectionChanges`).
32    fn observe_collection_changes(&self) -> Rc<dyn IObservable<CollectionChangedEventPattern<T>>> {
33        observe_collection_changes(self.notifying_collection())
34    }
35
36    /// The items of the collection as change sets of a list: its items first, then each of its
37    /// changes (`ToObservableChangeSet<T>()`, `ToObservableChangeSet<TCollection, T>()`).
38    fn to_observable_change_set(&self) -> Rc<dyn IObservable<ChangeSet<T>>> {
39        to_observable_change_set(self.notifying_collection())
40    }
41
42    /// [`to_observable_change_set`](Self::to_observable_change_set) with each item keyed by
43    /// `key_selector` (`ToObservableChangeSet<TObject, TKey>(Func<TObject, TKey>)`).
44    fn to_observable_change_set_with_key<K: Eq + Hash + Clone + 'static>(&self, key_selector: impl Fn(&T) -> K + 'static) -> Rc<dyn IObservable<crate::cache::ChangeSet<T, K>>> {
45        self.to_observable_change_set().add_key(key_selector)
46    }
47}
48
49impl<T: Clone + PartialEq + 'static> ObservableCollectionEx<T> for ObservableCollection<T> {
50    fn notifying_collection(&self) -> ObservableCollection<T> {
51        self.clone()
52    }
53}
54
55impl<T: Clone + PartialEq + 'static> ObservableCollectionEx<T> for ObservableCollectionExtended<T> {
56    fn notifying_collection(&self) -> ObservableCollection<T> {
57        self.collection().clone()
58    }
59}
60
61impl<T: Clone + PartialEq + 'static> ObservableCollectionEx<T> for ReadOnlyObservableCollection<T> {
62    fn notifying_collection(&self) -> ObservableCollection<T> {
63        self.collection().clone()
64    }
65}
66
67fn observe_collection_changes<T: Clone + PartialEq + 'static>(source: ObservableCollection<T>) -> Rc<dyn IObservable<CollectionChangedEventPattern<T>>> {
68    Observable::create(move |observer: Rc<dyn IObserver<CollectionChangedEventPattern<T>>>| {
69        let sender = source.clone();
70        let token = source.add_collection_changed(Rc::new(move |args: &NotifyCollectionChangedEventArgs<'_, T>| {
71            observer.on_next(EventPattern::new(sender.clone(), args.share()));
72        }));
73        let source = source.clone();
74        Disposable::create(move || {
75            source.remove_collection_changed(token);
76        })
77    })
78}
79
80fn to_observable_change_set<T: Clone + PartialEq + 'static>(source: ObservableCollection<T>) -> Rc<dyn IObservable<ChangeSet<T>>> {
81    Observable::create(move |observer: Rc<dyn IObserver<ChangeSet<T>>>| {
82        let data = Rc::new(RefCell::new(ChangeAwareList::from_items(source.to_vec())));
83
84        let initial = data.borrow_mut().capture_changes();
85        observer.on_next(initial);
86
87        let collection = source.clone();
88        observe_collection_changes(source.clone()).subscribe_next(move |args: CollectionChangedEventPattern<T>| {
89            let changes = args.event_args;
90            let captured = {
91                let mut list = data.borrow_mut();
92                apply(&mut list, &changes, &collection);
93                list.capture_changes()
94            };
95            observer.on_next(captured);
96        })
97    })
98}
99
100/// Applies a change of the collection to the list that mirrors it (the accumulator of
101/// `ToObservableChangeSet`).
102fn apply<T: Clone + PartialEq + 'static>(list: &mut ChangeAwareList<T>, changes: &SharedNotifyCollectionChangedEventArgs<T>, source: &ObservableCollection<T>) {
103    match changes.action() {
104        NotifyCollectionChangedAction::Add => {
105            let new_index = if changes.new_starting_index() == -1 { list.count() } else { changes.new_starting_index() as usize };
106            if changes.new_items().len() == 1 {
107                list.insert(new_index, changes.new_items()[0].clone());
108            } else {
109                IExtendedList::insert_range(list, changes.new_items().to_vec(), new_index);
110            }
111        }
112        NotifyCollectionChangedAction::Remove => {
113            if changes.old_starting_index() == -1 {
114                for item in changes.old_items() {
115                    list.remove(item);
116                }
117            } else if changes.old_items().len() == 1 {
118                list.remove_at(changes.old_starting_index() as usize);
119            } else {
120                list.remove_range(changes.old_starting_index() as usize, changes.old_items().len());
121            }
122        }
123        NotifyCollectionChangedAction::Replace => {
124            if let Some(replaced_item) = changes.new_items().first() {
125                if changes.new_starting_index() == -1 {
126                    let original = changes.old_items().first().expect("a replacement has the item it replaced");
127                    let old_index = list.index_of(original).expect("the replaced item is in the list");
128                    list.set(old_index, replaced_item.clone());
129                } else {
130                    list.set(changes.new_starting_index() as usize, replaced_item.clone());
131                }
132            }
133        }
134        NotifyCollectionChangedAction::Reset => {
135            list.clear();
136            list.add_range(source.to_vec());
137        }
138        NotifyCollectionChangedAction::Move => {
139            // `UnspecifiedIndexException`.
140            assert!(changes.old_starting_index() != -1, "Move -> OldStartingIndex");
141            assert!(changes.new_starting_index() != -1, "Move -> NewStartingIndex");
142            IExtendedList::move_index(list, changes.old_starting_index() as usize, changes.new_starting_index() as usize);
143        }
144    }
145}
146