Skip to main content

ferroui_system/collections/object_model/
observable_collection.rs

1//! `ObservableCollection<T>` (`System.Collections.ObjectModel.ObservableCollection<T>`).
2
3use crate::collections::specialized::{CollectionChange, INotifyCollectionChanged, NotifyCollectionChangedAction, NotifyCollectionChangedEventArgs};
4use crate::component_model::INotifyPropertyChanged;
5use crate::events::{Event, HandlerList};
6use std::cell::{Cell, Ref, RefCell, RefMut};
7use std::rc::Rc;
8
9/// A handler of the typed change notifications of a collection (`NotifyCollectionChangedEventHandler`).
10pub type CollectionChangedHandler<T> = dyn for<'a> Fn(&NotifyCollectionChangedEventArgs<'a, T>);
11
12/// The name of the property of the indexer (`"Item[]"`).
13const INDEXER_NAME: &str = "Item[]";
14
15struct Inner<T> {
16    items: RefCell<Vec<T>>,
17    collection_changed: HandlerList<CollectionChangedHandler<T>>,
18    untyped_collection_changed: Event<CollectionChange>,
19    property_changed: Event<str>,
20    /// The depth of the notifications being raised (`_blockReentrancyCount`).
21    block_reentrancy_count: Cell<usize>,
22    /// Notifications are held back (the overrides of `OnCollectionChanged`/`OnPropertyChanged` of
23    /// subclasses such as DynamicData's `ObservableCollectionExtended`).
24    notifications_suspended: Cell<bool>,
25}
26
27/// A list that notifies its changes (`ObservableCollection<T>`): a shared handle, as the object
28/// of .NET is a reference. Each change raises `PropertyChanged` for `Count` and `Item[]` as it
29/// applies, then `CollectionChanged`, in .NET's order.
30pub struct ObservableCollection<T> {
31    inner: Rc<Inner<T>>,
32}
33
34impl<T> Clone for ObservableCollection<T> {
35    fn clone(&self) -> Self {
36        Self { inner: self.inner.clone() }
37    }
38}
39
40impl<T: Clone + PartialEq + 'static> Default for ObservableCollection<T> {
41    fn default() -> Self {
42        Self::new()
43    }
44}
45
46impl<T> PartialEq for ObservableCollection<T> {
47    /// The same collection (reference equality, as for the object of .NET).
48    fn eq(&self, other: &Self) -> bool {
49        Rc::ptr_eq(&self.inner, &other.inner)
50    }
51}
52
53impl<T: Clone + PartialEq + 'static> ObservableCollection<T> {
54    /// An empty collection.
55    pub fn new() -> Self {
56        Self::from_items(Vec::new())
57    }
58
59    /// A collection of the items (`ObservableCollection(IEnumerable<T>)`).
60    pub fn from_items(items: Vec<T>) -> Self {
61        Self {
62            inner: Rc::new(Inner {
63                items: RefCell::new(items),
64                collection_changed: HandlerList::new(),
65                untyped_collection_changed: Event::new(),
66                property_changed: Event::new(),
67                block_reentrancy_count: Cell::new(0),
68                notifications_suspended: Cell::new(false),
69            }),
70        }
71    }
72
73    /// Whether `self` and `other` are the same collection.
74    pub fn ptr_eq(&self, other: &Self) -> bool {
75        Rc::ptr_eq(&self.inner, &other.inner)
76    }
77
78    /// Adds a handler of the typed change notifications; returns the token that removes it
79    /// (`CollectionChanged +=`).
80    pub fn add_collection_changed(&self, handler: Rc<CollectionChangedHandler<T>>) -> u64 {
81        self.inner.collection_changed.add(handler)
82    }
83
84    /// Removes a handler (`CollectionChanged -=`).
85    pub fn remove_collection_changed(&self, token: u64) -> bool {
86        self.inner.collection_changed.remove(token)
87    }
88
89    /// The number of items (`Count`).
90    pub fn count(&self) -> usize {
91        self.inner.items.borrow().len()
92    }
93
94    pub fn is_empty(&self) -> bool {
95        self.inner.items.borrow().is_empty()
96    }
97
98    /// The item at `index` (the indexer's getter).
99    ///
100    /// # Panics
101    /// When `index` is out of range (`ArgumentOutOfRangeException`).
102    pub fn get(&self, index: usize) -> T {
103        self.inner.items.borrow()[index].clone()
104    }
105
106    /// The items, borrowed.
107    pub fn items(&self) -> Ref<'_, Vec<T>> {
108        self.inner.items.borrow()
109    }
110
111    /// A copy of the items.
112    pub fn to_vec(&self) -> Vec<T> {
113        self.inner.items.borrow().clone()
114    }
115
116    /// Whether the collection has `item` (`Contains`).
117    pub fn contains(&self, item: &T) -> bool {
118        self.inner.items.borrow().contains(item)
119    }
120
121    /// The index of `item` (`IndexOf`).
122    pub fn index_of(&self, item: &T) -> Option<usize> {
123        self.inner.items.borrow().iter().position(|candidate| candidate == item)
124    }
125
126    /// Adds an item at the end (`Add`).
127    pub fn add(&self, item: T) {
128        let index = self.count();
129        self.insert(index, item);
130    }
131
132    /// Inserts an item (`InsertItem`).
133    pub fn insert(&self, index: usize, item: T) {
134        self.check_reentrancy();
135        self.inner.items.borrow_mut().insert(index, item.clone());
136        self.on_count_property_changed();
137        self.on_indexer_property_changed();
138        self.on_collection_changed(NotifyCollectionChangedAction::Add, std::slice::from_ref(&item), &[], index as i32, -1);
139    }
140
141    /// Replaces the item at `index` (the indexer's setter, `SetItem`).
142    pub fn set(&self, index: usize, item: T) {
143        self.check_reentrancy();
144        let original = std::mem::replace(&mut self.inner.items.borrow_mut()[index], item.clone());
145        self.on_indexer_property_changed();
146        self.on_collection_changed(NotifyCollectionChangedAction::Replace, std::slice::from_ref(&item), std::slice::from_ref(&original), index as i32, index as i32);
147    }
148
149    /// Removes the item at `index` (`RemoveItem`).
150    pub fn remove_at(&self, index: usize) {
151        self.check_reentrancy();
152        let removed = self.inner.items.borrow_mut().remove(index);
153        self.on_count_property_changed();
154        self.on_indexer_property_changed();
155        self.on_collection_changed(NotifyCollectionChangedAction::Remove, &[], std::slice::from_ref(&removed), -1, index as i32);
156    }
157
158    /// Removes `item`; false when it is not there (`Remove`).
159    pub fn remove(&self, item: &T) -> bool {
160        match self.index_of(item) {
161            Some(index) => {
162                self.remove_at(index);
163                true
164            }
165            None => false,
166        }
167    }
168
169    /// Moves the item at `old_index` to `new_index` (`Move`, `MoveItem`).
170    pub fn move_item(&self, old_index: usize, new_index: usize) {
171        self.check_reentrancy();
172        let item = {
173            let mut items = self.inner.items.borrow_mut();
174            let item = items.remove(old_index);
175            items.insert(new_index, item.clone());
176            item
177        };
178        self.on_indexer_property_changed();
179        self.on_collection_changed(NotifyCollectionChangedAction::Move, std::slice::from_ref(&item), std::slice::from_ref(&item), new_index as i32, old_index as i32);
180    }
181
182    /// Removes every item (`ClearItems`): a reset.
183    pub fn clear(&self) {
184        self.check_reentrancy();
185        self.inner.items.borrow_mut().clear();
186        self.on_count_property_changed();
187        self.on_indexer_property_changed();
188        self.on_collection_reset();
189    }
190
191    /// The items to change without notifications (the protected `Items` of `Collection<T>`), for
192    /// a collection that raises its own notifications afterwards.
193    pub fn items_mut_silent(&self) -> RefMut<'_, Vec<T>> {
194        self.inner.items.borrow_mut()
195    }
196
197    /// Holds back the notifications until `false` (the overrides of `OnCollectionChanged` and
198    /// `OnPropertyChanged` of a collection that suspends them).
199    pub fn set_notifications_suspended(&self, suspended: bool) {
200        self.inner.notifications_suspended.set(suspended);
201    }
202
203    pub fn notifications_suspended(&self) -> bool {
204        self.inner.notifications_suspended.get()
205    }
206
207    /// Panics on a change made by a handler of a notification while more than one handler listens
208    /// (`CheckReentrancy`, `InvalidOperationException`).
209    pub fn check_reentrancy(&self) {
210        if self.inner.block_reentrancy_count.get() > 0 && self.inner.collection_changed.len() + usize::from(self.inner.untyped_collection_changed.has_handlers()) > 1 {
211            panic!("Cannot change ObservableCollection during a CollectionChanged event.");
212        }
213    }
214
215    /// Raises `PropertyChanged` for `name` (`OnPropertyChanged`).
216    pub fn on_property_changed(&self, name: &str) {
217        if self.inner.notifications_suspended.get() {
218            return;
219        }
220        self.inner.property_changed.raise(name);
221    }
222
223    /// `OnCountPropertyChanged`.
224    pub fn on_count_property_changed(&self) {
225        self.on_property_changed("Count");
226    }
227
228    /// `OnIndexerPropertyChanged`.
229    pub fn on_indexer_property_changed(&self) {
230        self.on_property_changed(INDEXER_NAME);
231    }
232
233    /// A reset (`OnCollectionReset`).
234    pub fn on_collection_reset(&self) {
235        self.on_collection_changed(NotifyCollectionChangedAction::Reset, &[], &[], -1, -1);
236    }
237
238    /// Raises `CollectionChanged` (`OnCollectionChanged`), with the typed and the untyped
239    /// handlers, blocking re-entrant changes meanwhile (`BlockReentrancy`).
240    pub fn on_collection_changed(&self, action: NotifyCollectionChangedAction, new_items: &[T], old_items: &[T], new_starting_index: i32, old_starting_index: i32) {
241        if self.inner.notifications_suspended.get() {
242            return;
243        }
244        self.inner.block_reentrancy_count.set(self.inner.block_reentrancy_count.get() + 1);
245        struct Unblock<'a>(&'a Cell<usize>);
246        impl Drop for Unblock<'_> {
247            fn drop(&mut self) {
248                self.0.set(self.0.get() - 1);
249            }
250        }
251        let _unblock = Unblock(&self.inner.block_reentrancy_count);
252        let args = NotifyCollectionChangedEventArgs { action, new_items, old_items, new_starting_index, old_starting_index };
253        for (_, handler) in self.inner.collection_changed.snapshot().iter() {
254            handler(&args);
255        }
256        self.inner.untyped_collection_changed.raise(&CollectionChange {
257            action,
258            new_starting_index,
259            new_count: new_items.len(),
260            old_starting_index,
261            old_count: old_items.len(),
262        });
263    }
264}
265
266impl<T> INotifyCollectionChanged for ObservableCollection<T> {
267    fn collection_changed(&self) -> &Event<CollectionChange> {
268        &self.inner.untyped_collection_changed
269    }
270}
271
272impl<T> INotifyPropertyChanged for ObservableCollection<T> {
273    fn property_changed(&self) -> &Event<str> {
274        &self.inner.property_changed
275    }
276}