Skip to main content

ferroui_base/collections/
ferro_dictionary_extensions.rs

1use super::ferro_list_extensions::weak_subscribe;
2use super::{CollectionChangedHandler, IFerroReadOnlyDictionary, NotifyCollectionChangedAction, NotifyCollectionChangedEventArgs};
3use crate::reactive::{Disposable, IDisposable};
4use crate::utilities::WeakEventSender;
5use std::rc::Rc;
6
7/// Defines extension methods for working with [`FerroDictionary`](super::FerroDictionary)s.
8pub struct FerroDictionaryExtensions;
9
10impl FerroDictionaryExtensions {
11    /// Invokes an action for each item in a collection and subsequently each
12    /// item added or removed from the collection.
13    ///
14    /// `added` is called initially for each item in the collection and
15    /// subsequently for each item added to the collection, with the key and
16    /// the value; `removed` for each item removed from the collection;
17    /// `reset` when the collection is reset, which is followed by calls to
18    /// `added` for each item present in the collection after the reset.
19    /// `weak_subscription` indicates if a weak subscription should be used to
20    /// track changes to the collection (held by the returned disposable, see
21    /// [`FerroListExtensions::for_each_item_indexed`](super::FerroListExtensions::for_each_item_indexed)).
22    /// Returns a disposable used to terminate the subscription.
23    pub fn for_each_item<K, V, D>(
24        collection: &D,
25        added: impl Fn(&K, &V) + 'static,
26        removed: impl Fn(&K, &V) + 'static,
27        reset: impl Fn() + 'static,
28        weak_subscription: bool,
29    ) -> Rc<dyn IDisposable>
30    where
31        K: Clone + 'static,
32        V: Clone + 'static,
33        D: IFerroReadOnlyDictionary<K, V> + WeakEventSender,
34    {
35        let add = Rc::new(move |items: &[(K, V)]| {
36            for (key, value) in items {
37                added(key, value);
38            }
39        });
40
41        let remove = move |items: &[(K, V)]| {
42            for (key, value) in items {
43                removed(key, value);
44            }
45        };
46
47        // The handler belongs to the collection, so it reads the collection
48        // through a weak reference.
49        let source = collection.downgrade_sender();
50        let add_items = add.clone();
51        let handler: Rc<CollectionChangedHandler<(K, V)>> =
52            Rc::new(move |e: &NotifyCollectionChangedEventArgs<'_, (K, V)>| match e.action {
53                NotifyCollectionChangedAction::Add => add_items(e.new_items),
54
55                NotifyCollectionChangedAction::Move | NotifyCollectionChangedAction::Replace => {
56                    remove(e.old_items);
57                    add_items(e.new_items);
58                }
59
60                NotifyCollectionChangedAction::Remove => remove(e.old_items),
61
62                NotifyCollectionChangedAction::Reset => {
63                    reset();
64                    if let Some(collection) = D::upgrade_sender(&source) {
65                        add_items(&collection.to_vec());
66                    }
67                }
68            });
69
70        add(&collection.to_vec());
71
72        if weak_subscription {
73            weak_subscribe(collection, handler, D::add_collection_changed, D::remove_collection_changed)
74        } else {
75            let token = collection.add_collection_changed(handler);
76
77            let collection = collection.clone();
78            Disposable::create(move || {
79                collection.remove_collection_changed(token);
80            })
81        }
82    }
83}
84
85#[cfg(test)]
86mod tests {
87    //! Not from upstream: the managed test suite has no tests for these
88    //! extensions.
89
90    use super::*;
91    use crate::collections::FerroDictionary;
92    use std::cell::RefCell;
93
94    #[test]
95    fn for_each_item_reports_initial_added_replaced_and_removed_entries() {
96        let dictionary = FerroDictionary::from_dictionary([("a".to_string(), 1)]);
97        let log = Rc::new(RefCell::new(Vec::new()));
98        let (a, r, z) = (log.clone(), log.clone(), log.clone());
99
100        let subscription = FerroDictionaryExtensions::for_each_item(
101            &dictionary,
102            move |k: &String, v: &i32| a.borrow_mut().push(format!("+{k}={v}")),
103            move |k: &String, v: &i32| r.borrow_mut().push(format!("-{k}={v}")),
104            move || z.borrow_mut().push("reset".to_string()),
105            false,
106        );
107        dictionary.add("b".to_string(), 2);
108        dictionary.set("a".to_string(), 3);
109        dictionary.remove(&"b".to_string());
110        subscription.dispose();
111        dictionary.add("c".to_string(), 4);
112
113        assert_eq!(*log.borrow(), ["+a=1", "+b=2", "-a=1", "+a=3", "-b=2"].map(String::from).to_vec());
114    }
115}