Skip to main content

ferroui_base/collections/
notify_collection_changed_extensions.rs

1use crate::data::model::{CollectionChange, INotifyCollectionChanged};
2use crate::reactive::{AnonymousObserver, Disposable, IDisposable, IObservable, IObserver};
3use crate::utilities::{IWeakEventSubscriber, WeakEvents};
4use std::any::Any;
5use std::cell::{Cell, RefCell};
6use std::rc::{Rc, Weak};
7
8pub struct NotifyCollectionChangedExtensions;
9
10impl NotifyCollectionChangedExtensions {
11    /// Gets a weak observable for the CollectionChanged event.
12    pub fn get_weak_collection_changed_observable(
13        collection: &Rc<dyn INotifyCollectionChanged>,
14    ) -> Rc<dyn IObservable<CollectionChange>> {
15        WeakCollectionChangedObservable::new(Rc::downgrade(collection))
16    }
17
18    /// Subscribes to the CollectionChanged event using a weak subscription.
19    ///
20    /// `handler` is called with the collection and the change when the
21    /// collection event is raised. Disposing the returned handle terminates
22    /// the subscription.
23    pub fn weak_subscribe(
24        collection: &Rc<dyn INotifyCollectionChanged>,
25        handler: impl Fn(&Rc<dyn INotifyCollectionChanged>, &CollectionChange) + 'static,
26    ) -> Rc<dyn IDisposable> {
27        let sender = collection.clone();
28        Self::get_weak_collection_changed_observable(collection)
29            .subscribe(Rc::new(AnonymousObserver::new(move |e: CollectionChange| handler(&sender, &e))))
30    }
31
32    /// Subscribes to the CollectionChanged event using a weak subscription.
33    ///
34    /// `handler` is called with the change when the collection event is
35    /// raised. Disposing the returned handle terminates the subscription.
36    pub fn weak_subscribe_action(
37        collection: &Rc<dyn INotifyCollectionChanged>,
38        handler: impl Fn(&CollectionChange) + 'static,
39    ) -> Rc<dyn IDisposable> {
40        Self::get_weak_collection_changed_observable(collection)
41            .subscribe(Rc::new(AnonymousObserver::new(move |e: CollectionChange| handler(&e))))
42    }
43}
44
45/// The observable of [`NotifyCollectionChangedExtensions::get_weak_collection_changed_observable`]:
46/// subscribed to the collection through [`WeakEvents::collection_changed`]
47/// while it has observers.
48///
49/// Its observer list, and the subscription when the first observer arrives
50/// and the unsubscription when the last one leaves, are those of the
51/// lightweight observable base class of the original, which is not ported.
52struct WeakCollectionChangedObservable {
53    this: Weak<WeakCollectionChangedObservable>,
54    source_reference: Weak<dyn INotifyCollectionChanged>,
55    observers: RefCell<Vec<(u64, Rc<dyn IObserver<CollectionChange>>)>>,
56    next_id: Cell<u64>,
57}
58
59impl WeakCollectionChangedObservable {
60    fn new(source: Weak<dyn INotifyCollectionChanged>) -> Rc<Self> {
61        Rc::new_cyclic(|this| Self {
62            this: this.clone(),
63            source_reference: source,
64            observers: RefCell::new(Vec::new()),
65            next_id: Cell::new(0),
66        })
67    }
68
69    fn as_subscriber(&self) -> Option<Rc<dyn IWeakEventSubscriber<CollectionChange>>> {
70        self.this.upgrade().map(|this| this as Rc<dyn IWeakEventSubscriber<CollectionChange>>)
71    }
72
73    fn initialize(&self) {
74        if let (Some(instance), Some(this)) = (self.source_reference.upgrade(), self.as_subscriber()) {
75            WeakEvents::collection_changed().subscribe(&instance, &this);
76        }
77    }
78
79    fn deinitialize(&self) {
80        if let (Some(instance), Some(this)) = (self.source_reference.upgrade(), self.as_subscriber()) {
81            WeakEvents::collection_changed().unsubscribe(&instance, &this);
82        }
83    }
84
85    fn remove(&self, id: u64) {
86        let mut observers = self.observers.borrow_mut();
87        if let Some(index) = observers.iter().position(|(i, _)| *i == id) {
88            observers.remove(index);
89
90            if observers.is_empty() {
91                observers.shrink_to_fit();
92                drop(observers);
93                self.deinitialize();
94            }
95        }
96    }
97
98    fn publish_next(&self, value: CollectionChange) {
99        let observers: Vec<Rc<dyn IObserver<CollectionChange>>> =
100            self.observers.borrow().iter().map(|(_, observer)| observer.clone()).collect();
101        for observer in observers {
102            observer.on_next(value);
103        }
104    }
105}
106
107impl IObservable<CollectionChange> for WeakCollectionChangedObservable {
108    fn subscribe(&self, observer: Rc<dyn IObserver<CollectionChange>>) -> Rc<dyn IDisposable> {
109        let id = self.next_id.get();
110        self.next_id.set(id + 1);
111        let first = {
112            let mut observers = self.observers.borrow_mut();
113            let first = observers.is_empty();
114            observers.push((id, observer));
115            first
116        };
117
118        if first {
119            self.initialize();
120        }
121
122        // The handle holds the observable: the collection holds it weakly.
123        let parent = self.this.upgrade();
124        Disposable::create(move || {
125            if let Some(parent) = parent {
126                parent.remove(id);
127            }
128        })
129    }
130}
131
132impl IWeakEventSubscriber<CollectionChange> for WeakCollectionChangedObservable {
133    fn on_event(&self, _sender: Option<&dyn Any>, _ev: &dyn Any, e: &CollectionChange) {
134        self.publish_next(*e);
135    }
136}
137
138#[cfg(test)]
139mod tests {
140    //! Not from upstream: the managed test suite has no tests for these
141    //! extensions.
142
143    use super::*;
144    use crate::collections::{FerroList, NotifyCollectionChangedAction};
145
146    #[test]
147    fn weak_subscribe_receives_changes_until_disposed() {
148        let list = FerroList::from_items([1, 2]);
149        let collection: Rc<dyn INotifyCollectionChanged> = Rc::new(list.clone());
150        let log = Rc::new(RefCell::new(Vec::new()));
151        let l = log.clone();
152
153        let subscription = NotifyCollectionChangedExtensions::weak_subscribe_action(&collection, move |e| {
154            l.borrow_mut().push((e.action, e.new_starting_index, e.new_count));
155        });
156        list.add(3);
157        subscription.dispose();
158        list.add(4);
159
160        assert_eq!(*log.borrow(), vec![(NotifyCollectionChangedAction::Add, 2, 1)]);
161        assert!(!list.has_collection_changed_subscribers());
162    }
163
164    #[test]
165    fn weak_subscription_does_not_keep_the_handler_alive() {
166        let list = FerroList::from_items([1, 2]);
167        let collection: Rc<dyn INotifyCollectionChanged> = Rc::new(list.clone());
168        let calls = Rc::new(Cell::new(0));
169        let c = calls.clone();
170
171        let subscription = NotifyCollectionChangedExtensions::weak_subscribe(&collection, move |_, _| c.set(c.get() + 1));
172        list.add(3);
173        drop(subscription);
174        list.add(4);
175
176        assert_eq!(calls.get(), 1);
177        assert!(!list.has_collection_changed_subscribers());
178    }
179}