Skip to main content

ferroui_dynamic_data/binding/
observable_collection_extended.rs

1//! `ObservableCollectionExtended<T>` (`Binding/ObservableCollectionExtended.cs`).
2
3use super::{IObservableCollection, INotifyCollectionChangedSuspender};
4use crate::list::{IExtendedList, IList};
5use ferroui_reactive::disposables::Disposable;
6use ferroui_system::collections::object_model::{CollectionChangedHandler, ObservableCollection};
7use ferroui_system::collections::specialized::{CollectionChange, INotifyCollectionChanged, NotifyCollectionChangedAction};
8use ferroui_system::component_model::INotifyPropertyChanged;
9use ferroui_system::events::Event;
10use ferroui_system::IDisposable;
11use std::cell::{Cell, Ref};
12use std::fmt;
13use std::rc::Rc;
14
15/// The name of the property of the number of items.
16const COUNT_NAME: &str = "Count";
17/// The name of the property of the indexer.
18const INDEXER_NAME: &str = "Item[]";
19
20struct SuspendState {
21    /// `_suspendCount`.
22    suspend_count: Cell<bool>,
23    /// `_suspendNotifications`.
24    suspend_notifications: Cell<bool>,
25}
26
27/// An observable collection with ranges, loads and suspended notifications
28/// (`ObservableCollectionExtended<T>`), the collection that the binding operators fill.
29///
30/// It is a shared handle over an [`ObservableCollection<T>`] (the base class upstream), which items
31/// controls read and observe: [`collection`](Self::collection) is that collection. The changes made
32/// through this handle raise the notifications of the base, except those that are suspended
33/// (upstream's overrides of `OnCollectionChanged` and `OnPropertyChanged`).
34///
35/// Deviation (DEVIATIONS.md, DynamicData): a subclass of .NET intercepts the notifications of every
36/// change; here the changes made through [`collection`](Self::collection) directly are not
37/// suspended.
38pub struct ObservableCollectionExtended<T> {
39    collection: ObservableCollection<T>,
40    state: Rc<SuspendState>,
41}
42
43impl<T> Clone for ObservableCollectionExtended<T> {
44    fn clone(&self) -> Self {
45        Self { collection: self.collection.clone(), state: self.state.clone() }
46    }
47}
48
49impl<T> PartialEq for ObservableCollectionExtended<T> {
50    /// The same collection (reference equality).
51    fn eq(&self, other: &Self) -> bool {
52        Rc::ptr_eq(&self.state, &other.state)
53    }
54}
55
56impl<T: Clone + PartialEq + 'static> Default for ObservableCollectionExtended<T> {
57    fn default() -> Self {
58        Self::new()
59    }
60}
61
62impl<T: Clone + PartialEq + fmt::Debug + 'static> fmt::Debug for ObservableCollectionExtended<T> {
63    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
64        f.debug_list().entries(self.collection.items().iter()).finish()
65    }
66}
67
68impl<T: Clone + PartialEq + 'static> ObservableCollectionExtended<T> {
69    /// An empty collection (`ObservableCollectionExtended()`).
70    pub fn new() -> Self {
71        Self::from_items(Vec::new())
72    }
73
74    /// An empty collection that has room for `capacity` items (`ObservableCollectionExtended(new
75    /// List<T>(capacity))`).
76    pub fn with_capacity(capacity: usize) -> Self {
77        Self::from_items(Vec::with_capacity(capacity))
78    }
79
80    /// A collection of the items (`ObservableCollectionExtended(IEnumerable<T>)`,
81    /// `ObservableCollectionExtended(List<T>)`).
82    pub fn from_items(items: Vec<T>) -> Self {
83        Self {
84            collection: ObservableCollection::from_items(items),
85            state: Rc::new(SuspendState { suspend_count: Cell::new(false), suspend_notifications: Cell::new(false) }),
86        }
87    }
88
89    /// The observable collection of the items (the base class upstream).
90    pub fn collection(&self) -> &ObservableCollection<T> {
91        &self.collection
92    }
93
94    /// Whether `self` and `other` are the same collection.
95    pub fn ptr_eq(&self, other: &Self) -> bool {
96        Rc::ptr_eq(&self.state, &other.state)
97    }
98
99    /// Adds a handler of the typed change notifications; returns the token that removes it
100    /// (`CollectionChanged +=`).
101    pub fn add_collection_changed(&self, handler: Rc<CollectionChangedHandler<T>>) -> u64 {
102        self.collection.add_collection_changed(handler)
103    }
104
105    /// Removes a handler (`CollectionChanged -=`).
106    pub fn remove_collection_changed(&self, token: u64) -> bool {
107        self.collection.remove_collection_changed(token)
108    }
109
110    /// The number of items (`Count`).
111    pub fn count(&self) -> usize {
112        self.collection.count()
113    }
114
115    pub fn is_empty(&self) -> bool {
116        self.collection.is_empty()
117    }
118
119    /// The item at `index` (the indexer's getter).
120    pub fn get(&self, index: usize) -> T {
121        self.collection.get(index)
122    }
123
124    /// The items, borrowed.
125    pub fn items(&self) -> Ref<'_, Vec<T>> {
126        self.collection.items()
127    }
128
129    /// A copy of the items.
130    pub fn to_vec(&self) -> Vec<T> {
131        self.collection.to_vec()
132    }
133
134    /// Whether the collection has `item` (`Contains`).
135    pub fn contains(&self, item: &T) -> bool {
136        self.collection.contains(item)
137    }
138
139    /// The index of `item` (`IndexOf`).
140    pub fn index_of(&self, item: &T) -> Option<usize> {
141        self.collection.index_of(item)
142    }
143
144    /// Adds an item at the end (`Add`).
145    pub fn add(&self, item: T) {
146        let index = self.count();
147        self.insert(index, item);
148    }
149
150    /// Inserts an item (`InsertItem`).
151    pub fn insert(&self, index: usize, item: T) {
152        self.collection.check_reentrancy();
153        self.collection.items_mut_silent().insert(index, item.clone());
154        self.on_property_changed(COUNT_NAME);
155        self.on_property_changed(INDEXER_NAME);
156        self.on_collection_changed(NotifyCollectionChangedAction::Add, std::slice::from_ref(&item), &[], index as i32, -1);
157    }
158
159    /// Replaces the item at `index` (the indexer's setter, `SetItem`).
160    pub fn set(&self, index: usize, item: T) {
161        self.collection.check_reentrancy();
162        let original = std::mem::replace(&mut self.collection.items_mut_silent()[index], item.clone());
163        self.on_property_changed(INDEXER_NAME);
164        self.on_collection_changed(NotifyCollectionChangedAction::Replace, std::slice::from_ref(&item), std::slice::from_ref(&original), index as i32, index as i32);
165    }
166
167    /// Removes the item at `index` (`RemoveItem`).
168    pub fn remove_at(&self, index: usize) {
169        self.collection.check_reentrancy();
170        let removed = self.collection.items_mut_silent().remove(index);
171        self.on_property_changed(COUNT_NAME);
172        self.on_property_changed(INDEXER_NAME);
173        self.on_collection_changed(NotifyCollectionChangedAction::Remove, &[], std::slice::from_ref(&removed), -1, index as i32);
174    }
175
176    /// Removes `item`; false when it is not there (`Remove`).
177    pub fn remove(&self, item: &T) -> bool {
178        match self.index_of(item) {
179            Some(index) => {
180                self.remove_at(index);
181                true
182            }
183            None => false,
184        }
185    }
186
187    /// Moves the item at `old_index` to `new_index` (`Move`, `MoveItem`).
188    pub fn move_item(&self, old_index: usize, new_index: usize) {
189        self.collection.check_reentrancy();
190        let item = {
191            let mut items = self.collection.items_mut_silent();
192            let item = items.remove(old_index);
193            items.insert(new_index, item.clone());
194            item
195        };
196        self.on_property_changed(INDEXER_NAME);
197        self.on_collection_changed(NotifyCollectionChangedAction::Move, std::slice::from_ref(&item), std::slice::from_ref(&item), new_index as i32, old_index as i32);
198    }
199
200    /// Removes every item (`ClearItems`): a reset.
201    pub fn clear(&self) {
202        self.collection.check_reentrancy();
203        self.collection.items_mut_silent().clear();
204        self.on_property_changed(COUNT_NAME);
205        self.on_property_changed(INDEXER_NAME);
206        self.on_collection_changed(NotifyCollectionChangedAction::Reset, &[], &[], -1, -1);
207    }
208
209    /// Adds the items at the end, one at a time (`AddRange`).
210    pub fn add_range(&self, items: impl IntoIterator<Item = T>) {
211        for item in items {
212            self.add(item);
213        }
214    }
215
216    /// Inserts the items from `index`, one at a time (`InsertRange`).
217    pub fn insert_range(&self, items: impl IntoIterator<Item = T>, index: usize) {
218        for (offset, item) in items.into_iter().enumerate() {
219            self.insert(index + offset, item);
220        }
221    }
222
223    /// Clears the collection and adds the items (`Load`).
224    pub fn load(&self, items: impl IntoIterator<Item = T>) {
225        self.collection.check_reentrancy();
226        self.clear();
227        for item in items {
228            self.add(item);
229        }
230    }
231
232    /// Removes `count` items from `index`, one at a time (`RemoveRange`).
233    pub fn remove_range(&self, index: usize, count: usize) {
234        for _ in 0..count {
235            self.remove_at(index);
236        }
237    }
238
239    /// Holds back the notifications of `Count` until the handle is disposed, which raises `Count`
240    /// once if it changed (`SuspendCount`).
241    pub fn suspend_count(&self) -> Rc<dyn IDisposable> {
242        let count = self.count();
243        self.state.suspend_count.set(true);
244        let this = self.clone();
245        Disposable::create(move || {
246            this.state.suspend_count.set(false);
247            if this.count() != count {
248                this.on_property_changed(COUNT_NAME);
249            }
250        })
251    }
252
253    /// Holds back the notifications of changes and of `Count` until the handle is disposed, which
254    /// raises `Count` and a reset (`SuspendNotifications`).
255    pub fn suspend_notifications(&self) -> Rc<dyn IDisposable> {
256        self.state.suspend_count.set(true);
257        self.state.suspend_notifications.set(true);
258        let this = self.clone();
259        Disposable::create(move || {
260            this.state.suspend_count.set(false);
261            this.state.suspend_notifications.set(false);
262            this.on_property_changed(COUNT_NAME);
263            this.on_collection_changed(NotifyCollectionChangedAction::Reset, &[], &[], -1, -1);
264        })
265    }
266
267    /// Raises `PropertyChanged`, unless it is `Count` and the count is suspended
268    /// (`OnPropertyChanged`).
269    pub fn on_property_changed(&self, name: &str) {
270        if self.state.suspend_count.get() && name == COUNT_NAME {
271            return;
272        }
273        self.collection.on_property_changed(name);
274    }
275
276    /// Raises `CollectionChanged`, unless the notifications are suspended (`OnCollectionChanged`).
277    pub fn on_collection_changed(&self, action: NotifyCollectionChangedAction, new_items: &[T], old_items: &[T], new_starting_index: i32, old_starting_index: i32) {
278        if self.state.suspend_notifications.get() {
279            return;
280        }
281        self.collection.on_collection_changed(action, new_items, old_items, new_starting_index, old_starting_index);
282    }
283}
284
285impl<T> INotifyCollectionChanged for ObservableCollectionExtended<T> {
286    fn collection_changed(&self) -> &Event<CollectionChange> {
287        self.collection.collection_changed()
288    }
289}
290
291impl<T> INotifyPropertyChanged for ObservableCollectionExtended<T> {
292    fn property_changed(&self) -> &Event<str> {
293        self.collection.property_changed()
294    }
295}
296
297impl<T: Clone + PartialEq + 'static> INotifyCollectionChangedSuspender for ObservableCollectionExtended<T> {
298    fn suspend_count(&self) -> Rc<dyn IDisposable> {
299        ObservableCollectionExtended::suspend_count(self)
300    }
301
302    fn suspend_notifications(&self) -> Rc<dyn IDisposable> {
303        ObservableCollectionExtended::suspend_notifications(self)
304    }
305}
306
307impl<T: Clone + PartialEq + 'static> IObservableCollection<T> for ObservableCollectionExtended<T> {
308    fn count(&self) -> usize {
309        ObservableCollectionExtended::count(self)
310    }
311
312    fn get(&self, index: usize) -> T {
313        ObservableCollectionExtended::get(self, index)
314    }
315
316    fn set(&self, index: usize, item: T) {
317        ObservableCollectionExtended::set(self, index, item);
318    }
319
320    fn insert(&self, index: usize, item: T) {
321        ObservableCollectionExtended::insert(self, index, item);
322    }
323
324    fn remove_at(&self, index: usize) {
325        ObservableCollectionExtended::remove_at(self, index);
326    }
327
328    fn clear(&self) {
329        ObservableCollectionExtended::clear(self);
330    }
331
332    fn to_vec(&self) -> Vec<T> {
333        ObservableCollectionExtended::to_vec(self)
334    }
335
336    fn add(&self, item: T) {
337        ObservableCollectionExtended::add(self, item);
338    }
339
340    fn index_of(&self, item: &T) -> Option<usize> {
341        ObservableCollectionExtended::index_of(self, item)
342    }
343
344    fn remove(&self, item: &T) -> bool {
345        ObservableCollectionExtended::remove(self, item)
346    }
347
348    fn load(&self, items: Vec<T>) {
349        ObservableCollectionExtended::load(self, items);
350    }
351
352    fn move_item(&self, old_index: usize, new_index: usize) {
353        ObservableCollectionExtended::move_item(self, old_index, new_index);
354    }
355
356    /// The changes applied as to an `IExtendedList<T>` (ranges at once).
357    fn clone_changes(&self, changes: &crate::list::ChangeSet<T>) {
358        crate::list::clone_changes(&mut self.clone(), changes);
359    }
360}
361
362/// The collection as a list of DynamicData (`IList<T>`, `IExtendedList<T>`): what `ListEx` and
363/// `SortAndBind` apply changes to.
364impl<T: Clone + PartialEq + 'static> IList<T> for ObservableCollectionExtended<T> {
365    fn count(&self) -> usize {
366        ObservableCollectionExtended::count(self)
367    }
368
369    fn get(&self, index: usize) -> T {
370        ObservableCollectionExtended::get(self, index)
371    }
372
373    fn set(&mut self, index: usize, item: T) {
374        ObservableCollectionExtended::set(self, index, item);
375    }
376
377    fn insert(&mut self, index: usize, item: T) {
378        ObservableCollectionExtended::insert(self, index, item);
379    }
380
381    fn remove_at(&mut self, index: usize) {
382        ObservableCollectionExtended::remove_at(self, index);
383    }
384
385    fn clear(&mut self) {
386        ObservableCollectionExtended::clear(self);
387    }
388
389    fn add(&mut self, item: T) {
390        ObservableCollectionExtended::add(self, item);
391    }
392
393    fn index_of(&self, item: &T) -> Option<usize> {
394        ObservableCollectionExtended::index_of(self, item)
395    }
396
397    fn remove(&mut self, item: &T) -> bool {
398        ObservableCollectionExtended::remove(self, item)
399    }
400
401    fn add_range(&mut self, items: Vec<T>) {
402        ObservableCollectionExtended::add_range(self, items);
403    }
404
405    fn add_or_insert_range(&mut self, items: Vec<T>, index: Option<usize>) {
406        match index {
407            Some(index) => ObservableCollectionExtended::insert_range(self, items, index),
408            None => ObservableCollectionExtended::add_range(self, items),
409        }
410    }
411
412    fn remove_range(&mut self, index: usize, count: usize) {
413        ObservableCollectionExtended::remove_range(self, index, count);
414    }
415
416    fn supports_ranges(&self) -> bool {
417        true
418    }
419
420    fn move_item(&mut self, original: usize, destination: usize) {
421        ObservableCollectionExtended::move_item(self, original, destination);
422    }
423}
424
425impl<T: Clone + PartialEq + 'static> IExtendedList<T> for ObservableCollectionExtended<T> {
426    fn insert_range(&mut self, items: Vec<T>, index: usize) {
427        ObservableCollectionExtended::insert_range(self, items, index);
428    }
429
430    fn move_index(&mut self, original: usize, destination: usize) {
431        ObservableCollectionExtended::move_item(self, original, destination);
432    }
433}