Skip to main content

ferroui_dynamic_data/list/
change_aware_list.rs

1//! `ChangeAwareList<T>` and `IExtendedList<T>` (`List/ChangeAwareList.cs`, `List/IExtendedList.cs`).
2
3use super::{Change, ChangeSet, ListChangeReason};
4use crate::list::internal::unified;
5
6/// A list with the range operations of DynamicData (`IExtendedList<T>`, an `IList<T>`).
7///
8/// `AddRange` and `RemoveRange` are [`IList::add_range`](super::IList::add_range) and
9/// [`IList::remove_range`](super::IList::remove_range), which a list with ranges overrides.
10pub trait IExtendedList<T: Clone + PartialEq>: super::IList<T> {
11    /// Inserts the items at `index` (`InsertRange`).
12    fn insert_range(&mut self, items: Vec<T>, index: usize);
13    /// Moves the item at `original` to `destination` (`Move(int, int)`).
14    fn move_index(&mut self, original: usize, destination: usize);
15}
16
17/// What a list that tracks more than its changes does at the points upstream's subclasses
18/// override (`ChangeAwareListWithRefCounts`).
19pub(crate) trait ChangeAwareListHooks<T> {
20    fn on_insert_item(&mut self, _item: &T) {}
21    fn on_insert_items(&mut self, _items: &[T]) {}
22    fn on_remove_item(&mut self, _item: &T) {}
23    fn on_remove_items(&mut self, _items: &[T]) {}
24    fn on_set_item(&mut self, _new_item: &T, _old_item: &T) {}
25    fn on_clear(&mut self) {}
26    /// `Some` to answer `Contains` instead of a search of the list.
27    fn contains(&self, _item: &T) -> Option<bool> {
28        None
29    }
30}
31
32/// A list that records its changes until they are captured (`ChangeAwareList<T>`), batching
33/// consecutive insertions and removals into ranges as upstream does.
34pub struct ChangeAwareList<T> {
35    inner: Vec<T>,
36    changes: Vec<Change<T>>,
37    hooks: Option<Box<dyn ChangeAwareListHooks<T>>>,
38}
39
40impl<T: Clone + PartialEq> Default for ChangeAwareList<T> {
41    fn default() -> Self {
42        Self::new()
43    }
44}
45
46impl<T: Clone + PartialEq> ChangeAwareList<T> {
47    /// An empty list (`ChangeAwareList(int capacity = -1)`).
48    pub fn new() -> Self {
49        Self { inner: Vec::new(), changes: Vec::new(), hooks: None }
50    }
51
52    /// An empty list with room for `capacity` items.
53    pub fn with_capacity(capacity: usize) -> Self {
54        Self { inner: Vec::with_capacity(capacity), changes: Vec::new(), hooks: None }
55    }
56
57    /// A list of the items, whose first change adds them (`ChangeAwareList(IEnumerable<T>)`).
58    pub fn from_items(items: Vec<T>) -> Self {
59        let mut changes = Vec::new();
60        if !items.is_empty() {
61            changes.push(Change::range_change(ListChangeReason::AddRange, items.clone(), None));
62        }
63        Self { inner: items, changes, hooks: None }
64    }
65
66    /// A copy of `list`, with its changes or without (`ChangeAwareList(ChangeAwareList<T>, bool)`).
67    pub fn from_list(list: &ChangeAwareList<T>, copy_changes: bool) -> Self {
68        Self { inner: list.inner.clone(), changes: if copy_changes { list.changes.clone() } else { Vec::new() }, hooks: None }
69    }
70
71    pub(crate) fn with_hooks(hooks: Box<dyn ChangeAwareListHooks<T>>) -> Self {
72        Self { inner: Vec::new(), changes: Vec::new(), hooks: Some(hooks) }
73    }
74
75    /// The number of items (`Count`).
76    pub fn count(&self) -> usize {
77        self.inner.len()
78    }
79
80    pub fn is_empty(&self) -> bool {
81        self.inner.is_empty()
82    }
83
84    /// The items.
85    pub fn items(&self) -> &[T] {
86        &self.inner
87    }
88
89    /// The item at `index` (the indexer's getter).
90    pub fn get(&self, index: usize) -> &T {
91        &self.inner[index]
92    }
93
94    /// Replaces the item at `index` (the indexer's setter, `SetItem`).
95    pub fn set(&mut self, index: usize, item: T) {
96        assert!(index <= self.inner.len(), "index cannot be greater than the size of the collection");
97        let previous = self.inner[index].clone();
98        self.changes.push(Change::with_previous(ListChangeReason::Replace, item.clone(), Some(previous.clone()), Some(index), Some(index)));
99        self.inner[index] = item.clone();
100        if let Some(hooks) = &mut self.hooks {
101            hooks.on_set_item(&item, &previous);
102        }
103    }
104
105    /// Adds an item at the end (`Add`).
106    pub fn add(&mut self, item: T) {
107        let index = self.inner.len();
108        self.insert_item(index, item);
109    }
110
111    /// The changes since the last capture, and a clean slate (`CaptureChanges`): removals that
112    /// emptied the list are reported as one `Clear`.
113    pub fn capture_changes(&mut self) -> ChangeSet<T> {
114        if self.changes.is_empty() {
115            return ChangeSet::empty();
116        }
117        let mut changes = ChangeSet::from(std::mem::take(&mut self.changes));
118        // We can infer this is a clear.
119        if self.inner.is_empty() && changes.removes() == changes.total_changes() && changes.total_changes() > 1 {
120            let removed: Vec<T> = unified(&changes).into_iter().map(|change| change.current).collect();
121            changes = ChangeSet::from(vec![Change::range_change(ListChangeReason::Clear, removed, None)]);
122        }
123        changes
124    }
125
126    /// Removes every item (`Clear`).
127    pub fn clear(&mut self) {
128        if let Some(hooks) = &mut self.hooks {
129            hooks.on_clear();
130        }
131        if self.inner.is_empty() {
132            return;
133        }
134        let removed = std::mem::take(&mut self.inner);
135        self.changes.push(Change::range_change(ListChangeReason::Clear, removed, None));
136    }
137
138    /// Whether the list has `item` (`Contains`).
139    pub fn contains(&self, item: &T) -> bool {
140        if let Some(answer) = self.hooks.as_ref().and_then(|hooks| hooks.contains(item)) {
141            return answer;
142        }
143        self.inner.contains(item)
144    }
145
146    /// The index of `item` (`IndexOf`).
147    pub fn index_of(&self, item: &T) -> Option<usize> {
148        self.inner.iter().position(|candidate| candidate == item)
149    }
150
151    /// The index of `item` by `comparer` (`IndexOf(T, IEqualityComparer<T>)`).
152    pub fn index_of_with(&self, item: &T, comparer: &dyn Fn(&T, &T) -> bool) -> Option<usize> {
153        self.inner.iter().position(|candidate| comparer(candidate, item))
154    }
155
156    /// Inserts an item (`Insert`).
157    ///
158    /// # Panics
159    /// When `index` is beyond the end (`ArgumentException`).
160    pub fn insert(&mut self, index: usize, item: T) {
161        assert!(index <= self.inner.len(), "index cannot be greater than the size of the collection");
162        self.insert_item(index, item);
163    }
164
165    /// Moves `item` to `destination` (`Move(T, int)`).
166    pub fn move_item(&mut self, item: &T, destination: usize) {
167        assert!(destination <= self.inner.len(), "destination cannot be greater than the size of the collection");
168        let index = self.index_of(item).expect("the item to move is in the list");
169        self.move_index(index, destination);
170    }
171
172    /// Replaces the item at `index` with `item` and reports a refresh (`Refresh(T, int)`).
173    pub fn refresh_with(&mut self, item: T, index: usize) {
174        assert!(index <= self.inner.len(), "index cannot be greater than the size of the collection");
175        let previous = std::mem::replace(&mut self.inner[index], item.clone());
176        self.changes.push(Change::with_previous(ListChangeReason::Refresh, item, Some(previous), Some(index), None));
177    }
178
179    /// Reports a refresh of `item`; false when it is not in the list (`Refresh(T)`).
180    pub fn refresh(&mut self, item: &T) -> bool {
181        let Some(index) = self.index_of(item) else { return false };
182        self.changes.push(Change::new(ListChangeReason::Refresh, item.clone(), Some(index)));
183        true
184    }
185
186    /// Reports a refresh of the item at `index` (`RefreshAt`).
187    pub fn refresh_at(&mut self, index: usize) {
188        assert!(index <= self.inner.len(), "index cannot be greater than the size of the collection");
189        self.changes.push(Change::new(ListChangeReason::Refresh, self.inner[index].clone(), Some(index)));
190    }
191
192    /// Removes `item`; false when it is not in the list (`Remove(T)`).
193    pub fn remove(&mut self, item: &T) -> bool {
194        let Some(index) = self.index_of(item) else { return false };
195        let item = self.inner[index].clone();
196        self.remove_item(index, item);
197        true
198    }
199
200    /// Removes the item at `index` (`RemoveAt`).
201    pub fn remove_at(&mut self, index: usize) {
202        assert!(index <= self.inner.len(), "index cannot be greater than the size of the collection");
203        let item = self.inner[index].clone();
204        self.remove_item(index, item);
205    }
206
207    /// Inserts with the batching of consecutive insertions into a range (`InsertItem`).
208    fn insert_item(&mut self, index: usize, item: T) {
209        assert!(index <= self.inner.len(), "index cannot be greater than the size of the collection");
210        if let Some(hooks) = &mut self.hooks {
211            hooks.on_insert_item(&item);
212        }
213        // Attempt to batch updates, as lists love to deal with ranges.
214        let last_reason = self.changes.last().map(Change::reason);
215        match last_reason {
216            Some(ListChangeReason::Add) => {
217                let first_of_batch = self.changes.len() - 1;
218                let previous = self.changes[first_of_batch].item().clone();
219                let previous_index = previous.current_index();
220                if Some(index) == previous_index {
221                    self.changes[first_of_batch] = Change::range_change(ListChangeReason::AddRange, vec![item.clone(), previous.current().clone()], Some(index));
222                } else if previous_index.is_some_and(|previous_index| index == previous_index + 1) {
223                    self.changes[first_of_batch] = Change::range_change(ListChangeReason::AddRange, vec![previous.current().clone(), item.clone()], previous_index);
224                } else {
225                    self.changes.push(Change::new(ListChangeReason::Add, item.clone(), Some(index)));
226                }
227            }
228            Some(ListChangeReason::AddRange) => {
229                let last = self.changes.last_mut().expect("there is a last change");
230                let range = last.range_mut();
231                // Upstream's index of a range without one is -1.
232                let range_index = range.index().map_or(-1, |index| index as isize);
233                let minimum = (range_index - 1).max(0);
234                let maximum = range_index + range.count() as isize;
235                let position = index as isize;
236                if position >= minimum && position <= maximum {
237                    let insert_position = (position - range_index).clamp(0, range.count() as isize) as usize;
238                    range.insert(insert_position, item.clone());
239                    if position < range_index {
240                        range.set_starting_index(Some(index));
241                    }
242                } else {
243                    self.changes.push(Change::new(ListChangeReason::Add, item.clone(), Some(index)));
244                }
245            }
246            // The first insertion: no range can be inferred.
247            _ => self.changes.push(Change::new(ListChangeReason::Add, item.clone(), Some(index))),
248        }
249        self.inner.insert(index, item);
250    }
251
252    /// Removes with the batching of consecutive removals into a range (`RemoveItem`).
253    fn remove_item(&mut self, index: usize, item: T) {
254        assert!(index <= self.inner.len(), "index cannot be greater than the size of the collection");
255        if let Some(hooks) = &mut self.hooks {
256            hooks.on_remove_item(&item);
257        }
258        let last_reason = self.changes.last().map(Change::reason);
259        match last_reason {
260            Some(ListChangeReason::Remove) => {
261                let first_of_batch = self.changes.len() - 1;
262                let previous = self.changes[first_of_batch].item().clone();
263                let previous_index = previous.current_index();
264                if Some(index) == previous_index {
265                    self.changes[first_of_batch] = Change::range_change(ListChangeReason::RemoveRange, vec![previous.current().clone(), item.clone()], Some(index));
266                } else if previous_index.is_some_and(|previous_index| index + 1 == previous_index) {
267                    self.changes[first_of_batch] = Change::range_change(ListChangeReason::RemoveRange, vec![item.clone(), previous.current().clone()], Some(index));
268                } else {
269                    self.changes.push(Change::new(ListChangeReason::Remove, item.clone(), Some(index)));
270                }
271            }
272            Some(ListChangeReason::RemoveRange) => {
273                let range_index = self.changes.last().expect("there is a last change").range().index();
274                if range_index == Some(index) {
275                    // Removed in order.
276                    self.changes.last_mut().expect("there is a last change").range_mut().add(item.clone());
277                } else if range_index == Some(index + 1) {
278                    // Removed in reverse order.
279                    let range = self.changes.last_mut().expect("there is a last change").range_mut();
280                    range.insert(0, item.clone());
281                    range.set_starting_index(Some(index));
282                } else {
283                    // Including the case upstream checks apart (`range.Index == index - 1`).
284                    self.changes.push(Change::new(ListChangeReason::Remove, item.clone(), Some(index)));
285                }
286            }
287            // The first removal: no range can be inferred.
288            _ => self.changes.push(Change::new(ListChangeReason::Remove, item.clone(), Some(index))),
289        }
290        self.inner.remove(index);
291    }
292}
293
294impl<T: Clone + PartialEq> ChangeAwareList<T> {
295    /// Adds the items at the end (`AddRange`).
296    pub fn add_range(&mut self, items: Vec<T>) {
297        if items.is_empty() {
298            return;
299        }
300        let index = self.inner.len();
301        self.changes.push(Change::range_change(ListChangeReason::AddRange, items.clone(), Some(index)));
302        if let Some(hooks) = &mut self.hooks {
303            hooks.on_insert_items(&items);
304        }
305        self.inner.extend(items);
306    }
307
308    /// Removes `count` items from `index` (`RemoveRange`).
309    pub fn remove_range(&mut self, index: usize, count: usize) {
310        assert!(count <= self.inner.len() && index <= self.inner.len() - count, "the range is beyond the end of the collection");
311        let removed: Vec<T> = self.inner[index..index + count].to_vec();
312        if removed.is_empty() {
313            return;
314        }
315        self.changes.push(Change::range_change(ListChangeReason::RemoveRange, removed.clone(), Some(index)));
316        self.inner.drain(index..index + count);
317        if let Some(hooks) = &mut self.hooks {
318            hooks.on_remove_items(&removed);
319        }
320    }
321}
322
323impl<T: Clone + PartialEq> IExtendedList<T> for ChangeAwareList<T> {
324    fn insert_range(&mut self, items: Vec<T>, index: usize) {
325        if items.is_empty() {
326            return;
327        }
328        self.changes.push(Change::range_change(ListChangeReason::AddRange, items.clone(), Some(index)));
329        self.inner.splice(index..index, items.iter().cloned());
330        if let Some(hooks) = &mut self.hooks {
331            hooks.on_insert_items(&items);
332        }
333    }
334
335    fn move_index(&mut self, original: usize, destination: usize) {
336        assert!(original <= self.inner.len(), "original cannot be greater than the size of the collection");
337        assert!(destination <= self.inner.len(), "destination cannot be greater than the size of the collection");
338        let item = self.inner.remove(original);
339        self.inner.insert(destination, item.clone());
340        self.changes.push(Change::moved(item, destination, original));
341    }
342
343}