Skip to main content

ferroui_dynamic_data/list/
i_list.rs

1//! `IList<T>` as DynamicData uses it, and `ListEx` (`List/ListEx.cs`): applying the changes of a
2//! list to another list.
3//!
4//! Upstream's `ListEx` takes any `IList<T>` and branches on its type: `List<T>` and
5//! `IExtendedList<T>` insert and remove ranges at once, `ObservableCollection<T>` moves items
6//! itself, `ChangeAwareList<T>` reports refreshes. Here those are provided methods of [`IList`]
7//! with upstream's general form, which those types override.
8
9use super::{Change, ChangeAwareList, ChangeSet, IExtendedList, ListChangeReason};
10
11/// A list that the changes of a list can be applied to (`System.Collections.Generic.IList<T>`,
12/// with the forms of `ListEx` as provided methods).
13pub trait IList<T: Clone + PartialEq> {
14    /// The number of items (`Count`).
15    fn count(&self) -> usize;
16    /// The item at `index` (the indexer's getter).
17    fn get(&self, index: usize) -> T;
18    /// Replaces the item at `index` (the indexer's setter).
19    fn set(&mut self, index: usize, item: T);
20    /// Inserts an item (`Insert`).
21    fn insert(&mut self, index: usize, item: T);
22    /// Removes the item at `index` (`RemoveAt`).
23    fn remove_at(&mut self, index: usize);
24    /// Removes every item (`Clear`).
25    fn clear(&mut self);
26
27    /// Adds an item at the end (`Add`).
28    fn add(&mut self, item: T) {
29        let index = self.count();
30        self.insert(index, item);
31    }
32
33    /// The index of `item` (`IndexOf`).
34    fn index_of(&self, item: &T) -> Option<usize> {
35        (0..self.count()).find(|&index| &self.get(index) == item)
36    }
37
38    /// The index of `item` by `comparer` (`ListEx.IndexOf(IEnumerable, T, IEqualityComparer)`).
39    fn index_of_with(&self, item: &T, comparer: &dyn Fn(&T, &T) -> bool) -> Option<usize> {
40        (0..self.count()).find(|&index| comparer(item, &self.get(index)))
41    }
42
43    /// Removes `item`; false when it is not in the list (`Remove`).
44    fn remove(&mut self, item: &T) -> bool {
45        match self.index_of(item) {
46            Some(index) => {
47                self.remove_at(index);
48                true
49            }
50            None => false,
51        }
52    }
53
54    /// Adds the items at the end (`ListEx.AddRange`): one by one unless the list adds ranges.
55    fn add_range(&mut self, items: Vec<T>) {
56        for item in items {
57            self.add(item);
58        }
59    }
60
61    /// Inserts the items at `index`, or adds them without an index (`ListEx.AddOrInsertRange`): in
62    /// reverse order at the index unless the list inserts ranges, as upstream does.
63    fn add_or_insert_range(&mut self, items: Vec<T>, index: Option<usize>) {
64        match index {
65            Some(index) => {
66                for item in items.into_iter().rev() {
67                    self.insert(index, item);
68                }
69            }
70            None => self.add_range(items),
71        }
72    }
73
74    /// Removes `count` items from `index` (`ListEx.RemoveRange`).
75    ///
76    /// # Panics
77    /// For a list without ranges (`NotSupportedException`).
78    fn remove_range(&mut self, _index: usize, _count: usize) {
79        panic!("Cannot remove range from this list");
80    }
81
82    /// Whether the list removes ranges at once (`List<T>` or `IExtendedList<T>`).
83    fn supports_ranges(&self) -> bool {
84        false
85    }
86
87    /// Moves the item at `original` to `destination` (the `Moved` case of `ListEx.Clone`).
88    fn move_item(&mut self, original: usize, destination: usize) {
89        let item = self.get(original);
90        self.remove_at(original);
91        self.insert(destination, item);
92    }
93
94    /// Reports a refresh of the item at `index` (the `Refresh` case of `ListEx.Clone`): the item is
95    /// removed and inserted again unless the list reports refreshes.
96    fn refresh_at(&mut self, index: usize, item: T) {
97        self.remove_at(index);
98        self.insert(index, item);
99    }
100}
101
102impl<T: Clone + PartialEq> IList<T> for Vec<T> {
103    fn count(&self) -> usize {
104        self.len()
105    }
106
107    fn get(&self, index: usize) -> T {
108        self[index].clone()
109    }
110
111    fn set(&mut self, index: usize, item: T) {
112        self[index] = item;
113    }
114
115    fn insert(&mut self, index: usize, item: T) {
116        Vec::insert(self, index, item);
117    }
118
119    fn remove_at(&mut self, index: usize) {
120        Vec::remove(self, index);
121    }
122
123    fn clear(&mut self) {
124        Vec::clear(self);
125    }
126
127    fn index_of(&self, item: &T) -> Option<usize> {
128        self.iter().position(|candidate| candidate == item)
129    }
130
131    fn add_range(&mut self, items: Vec<T>) {
132        self.extend(items);
133    }
134
135    fn add_or_insert_range(&mut self, items: Vec<T>, index: Option<usize>) {
136        match index {
137            Some(index) => {
138                self.splice(index..index, items);
139            }
140            None => self.extend(items),
141        }
142    }
143
144    fn remove_range(&mut self, index: usize, count: usize) {
145        self.drain(index..index + count);
146    }
147
148    fn supports_ranges(&self) -> bool {
149        true
150    }
151}
152
153impl<T: Clone + PartialEq> IList<T> for ChangeAwareList<T> {
154    fn count(&self) -> usize {
155        ChangeAwareList::count(self)
156    }
157
158    fn get(&self, index: usize) -> T {
159        ChangeAwareList::get(self, index).clone()
160    }
161
162    fn set(&mut self, index: usize, item: T) {
163        ChangeAwareList::set(self, index, item);
164    }
165
166    fn insert(&mut self, index: usize, item: T) {
167        ChangeAwareList::insert(self, index, item);
168    }
169
170    fn remove_at(&mut self, index: usize) {
171        ChangeAwareList::remove_at(self, index);
172    }
173
174    fn clear(&mut self) {
175        ChangeAwareList::clear(self);
176    }
177
178    fn add(&mut self, item: T) {
179        ChangeAwareList::add(self, item);
180    }
181
182    fn index_of(&self, item: &T) -> Option<usize> {
183        ChangeAwareList::index_of(self, item)
184    }
185
186    fn remove(&mut self, item: &T) -> bool {
187        ChangeAwareList::remove(self, item)
188    }
189
190    fn add_range(&mut self, items: Vec<T>) {
191        ChangeAwareList::add_range(self, items);
192    }
193
194    fn add_or_insert_range(&mut self, items: Vec<T>, index: Option<usize>) {
195        match index {
196            Some(index) => IExtendedList::insert_range(self, items, index),
197            None => ChangeAwareList::add_range(self, items),
198        }
199    }
200
201    fn remove_range(&mut self, index: usize, count: usize) {
202        ChangeAwareList::remove_range(self, index, count);
203    }
204
205    fn supports_ranges(&self) -> bool {
206        true
207    }
208
209    fn move_item(&mut self, original: usize, destination: usize) {
210        IExtendedList::move_index(self, original, destination);
211    }
212
213    fn refresh_at(&mut self, index: usize, _item: T) {
214        ChangeAwareList::refresh_at(self, index);
215    }
216}
217
218/// Applies the changes to `target` (`ListEx.Clone(IList<T>, IChangeSet<T>)`).
219pub fn clone_changes<T: Clone + PartialEq>(target: &mut dyn IList<T>, changes: &ChangeSet<T>) {
220    for change in changes {
221        clone_change(target, change, &|a, b| a == b);
222    }
223}
224
225/// Applies the changes to `target`, comparing items with `comparer`
226/// (`ListEx.Clone(IList<T>, IEnumerable<Change<T>>, IEqualityComparer<T>)`).
227pub fn clone_changes_with<T: Clone + PartialEq>(target: &mut dyn IList<T>, changes: &[Change<T>], comparer: &dyn Fn(&T, &T) -> bool) {
228    for change in changes {
229        clone_change(target, change, comparer);
230    }
231}
232
233/// Removes the items of the change, or clears the list when they are all of it
234/// (`ListEx.ClearOrRemoveMany`).
235pub fn clear_or_remove_many<T: Clone + PartialEq>(target: &mut dyn IList<T>, change: &Change<T>) {
236    if target.count() == change.range().count() {
237        target.clear();
238    } else {
239        remove_many(target, change.range().items());
240    }
241}
242
243/// Removes the items (`ListEx.RemoveMany`): by their indexes from the last, unless an item is
244/// there more than once, when each is removed by a search.
245pub fn remove_many<T: Clone + PartialEq>(target: &mut dyn IList<T>, items: &[T]) {
246    let mut indexes: Vec<(usize, &T)> = items.iter().filter_map(|item| target.index_of(item).map(|index| (index, item))).collect();
247    indexes.sort_by(|a, b| b.0.cmp(&a.0));
248    let has_duplicates = indexes.iter().enumerate().any(|(i, (_, item))| indexes[..i].iter().any(|(_, other)| other == item));
249    if has_duplicates {
250        for item in items {
251            target.remove(item);
252        }
253    } else {
254        for (index, _) in indexes {
255            target.remove_at(index);
256        }
257    }
258}
259
260/// Replaces `original` with `replace_with`, or adds it when `original` is not in the list
261/// (`ListEx.ReplaceOrAdd`).
262pub fn replace_or_add<T: Clone + PartialEq>(target: &mut dyn IList<T>, original: &T, replace_with: T) {
263    match target.index_of(original) {
264        Some(index) => target.set(index, replace_with),
265        None => target.add(replace_with),
266    }
267}
268
269/// Replaces `original` with `replace_with` (`ListEx.Replace`).
270///
271/// # Panics
272/// When `original` is not in the list (`ArgumentException`).
273pub fn replace<T: Clone + PartialEq>(target: &mut dyn IList<T>, original: &T, replace_with: T) {
274    let index = target.index_of(original).expect("Cannot find index of original item. Either it does not exist in the list or the hashcode has mutated");
275    target.set(index, replace_with);
276}
277
278/// The index of `value` by a comparison, or the bitwise complement of where it belongs
279/// (`ListEx.BinarySearch`): `Err(index)` for the latter.
280pub fn binary_search_by<T: Clone + PartialEq, S>(list: &dyn IList<T>, value: &S, comparer: &dyn Fn(&S, &T) -> std::cmp::Ordering) -> Result<usize, usize> {
281    let (mut lower, mut upper) = (0isize, list.count() as isize - 1);
282    while lower <= upper {
283        let middle = lower + (upper - lower) / 2;
284        match comparer(value, &list.get(middle as usize)) {
285            std::cmp::Ordering::Less => upper = middle - 1,
286            std::cmp::Ordering::Greater => lower = middle + 1,
287            std::cmp::Ordering::Equal => return Ok(middle as usize),
288        }
289    }
290    Err(lower as usize)
291}
292
293/// Whether a move touches the range from `start_index` to `end_index` (`MovedWithinRange`).
294pub(crate) fn moved_within_range<T>(change: &Change<T>, start_index: usize, end_index: usize) -> bool {
295    if change.reason() != ListChangeReason::Moved {
296        return false;
297    }
298    let item = change.item();
299    let within = |index: Option<usize>| index.is_some_and(|index| index >= start_index && index <= end_index);
300    within(item.current_index()) || within(item.previous_index())
301}
302
303/// One change applied to `target` (the private `Clone(IList<T>, Change<T>, ..)`).
304fn clone_change<T: Clone + PartialEq>(target: &mut dyn IList<T>, change: &Change<T>, comparer: &dyn Fn(&T, &T) -> bool) {
305    match change.reason() {
306        ListChangeReason::Add => {
307            let item = change.item();
308            match item.current_index() {
309                Some(index) => target.insert(index, item.current().clone()),
310                None => target.add(item.current().clone()),
311            }
312        }
313        ListChangeReason::AddRange => target.add_or_insert_range(change.range().items().to_vec(), change.range().index()),
314        ListChangeReason::Clear => clear_or_remove_many(target, change),
315        ListChangeReason::Replace => {
316            let item = change.item();
317            if item.current_index().is_some() && item.current_index() == item.previous_index() {
318                target.set(item.current_index().expect("checked"), item.current().clone());
319            } else {
320                match item.previous_index() {
321                    None => {
322                        target.remove(item.previous().expect("a replacement has a previous item"));
323                    }
324                    // Is this best, or a replacement and a move?
325                    Some(index) => target.remove_at(index),
326                }
327                match item.current_index() {
328                    None => target.add(item.current().clone()),
329                    Some(index) => target.insert(index, item.current().clone()),
330                }
331            }
332        }
333        ListChangeReason::Refresh => {
334            let item = change.item();
335            let index = item.current_index().expect("a refresh has an index");
336            target.refresh_at(index, item.current().clone());
337        }
338        ListChangeReason::Remove => {
339            let item = change.item();
340            match item.current_index() {
341                Some(index) => target.remove_at(index),
342                None => {
343                    if let Some(index) = target.index_of_with(item.current(), comparer) {
344                        target.remove_at(index);
345                    }
346                }
347            }
348        }
349        ListChangeReason::RemoveRange => {
350            // A range without an index (one that `WhereReasonsAre` stripped) is removed item by
351            // item.
352            match change.range().index() {
353                Some(index) if target.supports_ranges() => target.remove_range(index, change.range().count()),
354                _ => remove_many(target, change.range().items()),
355            }
356        }
357        ListChangeReason::Moved => {
358            let item = change.item();
359            let current = item.current_index().expect("Cannot move as an index was not specified");
360            let previous = item.previous_index().expect("Cannot move as an index was not specified");
361            target.move_item(previous, current);
362        }
363    }
364}