Skip to main content

ferroui_dynamic_data/binding/
observable_collection_adaptor.rs

1//! `ObservableCollectionAdaptor<T>` and `ObservableCollectionAdaptor<TObject, TKey>`
2//! (`Binding/ObservableCollectionAdaptor.cs`).
3//!
4//! Deviation (DEVIATIONS.md, DynamicData): the two classes of upstream have the same name and
5//! differ by their number of type parameters; the adaptor of lists is
6//! [`ListObservableCollectionAdaptor`] here.
7
8use super::{BindingOptions, IObservableCollection, IObservableCollectionAdaptor};
9use crate::cache::internal::Cache;
10use crate::cache::{ChangeReason, ChangeSet, ICache, IQuery};
11use crate::list::IChangeSetAdaptor;
12use crate::DynamicDataOptions;
13use std::cell::{Cell, RefCell};
14use std::hash::Hash;
15use std::rc::Rc;
16
17/// Applies the change sets of a list to an observable collection, loading it with a reset above
18/// a threshold of changes (`ObservableCollectionAdaptor<T>`).
19pub struct ListObservableCollectionAdaptor<T: Clone + PartialEq + 'static> {
20    collection: Rc<dyn IObservableCollection<T>>,
21    refresh_threshold: usize,
22    /// Unread upstream (`allowReplace`, "TODO: pass in allowReplace").
23    #[allow(dead_code)]
24    allow_replace: bool,
25    reset_on_first_time_load: bool,
26    loaded: Cell<bool>,
27}
28
29impl<T: Clone + PartialEq + 'static> ListObservableCollectionAdaptor<T> {
30    /// The adaptor with the default options (`ObservableCollectionAdaptor(IObservableCollection<T>)`,
31    /// `DynamicDataOptions.Binding`).
32    pub fn new(collection: Rc<dyn IObservableCollection<T>>) -> Self {
33        Self::with_options(collection, DynamicDataOptions::binding())
34    }
35
36    /// The adaptor with `options` (`ObservableCollectionAdaptor(IObservableCollection<T>,
37    /// BindingOptions)`).
38    pub fn with_options(collection: Rc<dyn IObservableCollection<T>>, options: BindingOptions) -> Self {
39        Self::with_threshold(collection, options.reset_threshold, options.use_replace_for_updates, options.reset_on_first_time_load)
40    }
41
42    /// The adaptor with every part (`ObservableCollectionAdaptor(IObservableCollection<T>, int,
43    /// bool allowReplace = true, bool resetOnFirstTimeLoad = true)`).
44    pub fn with_threshold(collection: Rc<dyn IObservableCollection<T>>, refresh_threshold: usize, allow_replace: bool, reset_on_first_time_load: bool) -> Self {
45        Self { collection, refresh_threshold, allow_replace, reset_on_first_time_load, loaded: Cell::new(false) }
46    }
47}
48
49impl<T: Clone + PartialEq + 'static> IChangeSetAdaptor<T> for ListObservableCollectionAdaptor<T> {
50    fn adapt(&self, changes: &crate::list::ChangeSet<T>) {
51        if changes.total_changes() - changes.refreshes() > self.refresh_threshold || (!self.loaded.get() && self.reset_on_first_time_load) {
52            let suspension = self.collection.suspend_notifications();
53            self.collection.clone_changes(changes);
54            self.loaded.set(true);
55            suspension.dispose();
56        } else {
57            self.collection.clone_changes(changes);
58        }
59    }
60}
61
62/// Applies the change sets of a cache to an observable collection, loading it with a reset above
63/// a threshold of changes (`ObservableCollectionAdaptor<TObject, TKey>`).
64pub struct ObservableCollectionAdaptor<T, K> {
65    cache: RefCell<Cache<T, K>>,
66    refresh_threshold: usize,
67    use_replace_for_updates: bool,
68    reset_on_first_time_load: bool,
69    loaded: Cell<bool>,
70}
71
72impl<T: Clone + PartialEq + 'static, K: Eq + Hash + Clone + 'static> Default for ObservableCollectionAdaptor<T, K> {
73    /// `ObservableCollectionAdaptor(int refreshThreshold = 25, bool useReplaceForUpdates = true,
74    /// bool resetOnFirstTimeLoad = true)` with the defaults.
75    fn default() -> Self {
76        Self::new(BindingOptions::DEFAULT_RESET_THRESHOLD, BindingOptions::DEFAULT_USE_REPLACE_FOR_UPDATES, BindingOptions::DEFAULT_RESET_ON_FIRST_TIME_LOAD)
77    }
78}
79
80impl<T: Clone + PartialEq + 'static, K: Eq + Hash + Clone + 'static> ObservableCollectionAdaptor<T, K> {
81    /// The adaptor with every part (`ObservableCollectionAdaptor(int, bool, bool)`).
82    pub fn new(refresh_threshold: usize, use_replace_for_updates: bool, reset_on_first_time_load: bool) -> Self {
83        Self { cache: RefCell::new(Cache::new()), refresh_threshold, use_replace_for_updates, reset_on_first_time_load, loaded: Cell::new(false) }
84    }
85
86    /// The adaptor with `options` (`ObservableCollectionAdaptor(BindingOptions)`).
87    pub fn with_options(options: BindingOptions) -> Self {
88        Self::new(options.reset_threshold, options.use_replace_for_updates, options.reset_on_first_time_load)
89    }
90
91    /// The defaults with the choice of replacements (`ObservableCollectionAdaptor(useReplaceForUpdates: ..)`).
92    pub fn with_use_replace_for_updates(use_replace_for_updates: bool) -> Self {
93        Self::new(BindingOptions::DEFAULT_RESET_THRESHOLD, use_replace_for_updates, BindingOptions::DEFAULT_RESET_ON_FIRST_TIME_LOAD)
94    }
95
96    fn do_update(&self, changes: &ChangeSet<T, K>, list: &dyn IObservableCollection<T>) {
97        for change in changes {
98            match change.reason() {
99                ChangeReason::Add => list.add(change.current().clone()),
100                ChangeReason::Remove => {
101                    list.remove(change.current());
102                }
103                ChangeReason::Update => {
104                    let previous = change.previous().expect("an update has a previous item");
105                    // Remove / Add is default as some platforms do not support list[index] = XXX
106                    // notifications.
107                    if self.use_replace_for_updates {
108                        let index = list.index_of(previous).expect("Cannot find index of original item. Either it does not exist in the list or the hashcode has mutated");
109                        list.set(index, change.current().clone());
110                    } else {
111                        list.remove(previous);
112                        list.add(change.current().clone());
113                    }
114                }
115                ChangeReason::Refresh | ChangeReason::Moved => {}
116            }
117        }
118    }
119}
120
121impl<T: Clone + PartialEq + 'static, K: Eq + Hash + Clone + 'static> IObservableCollectionAdaptor<T, K> for ObservableCollectionAdaptor<T, K> {
122    fn adapt(&self, changes: &ChangeSet<T, K>, collection: &dyn IObservableCollection<T>) {
123        self.cache.borrow_mut().clone_changes(changes);
124
125        if changes.count() - changes.refreshes() > self.refresh_threshold || (!self.loaded.get() && self.reset_on_first_time_load) {
126            self.loaded.set(true);
127            let items = self.cache.borrow().items();
128            let suspension = collection.suspend_notifications();
129            collection.load(items);
130            suspension.dispose();
131        } else {
132            let suspension = collection.suspend_count();
133            self.do_update(changes, collection);
134            suspension.dispose();
135        }
136    }
137}