ferroui_base/collections/
notify_collection_changed_extensions.rs1use 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 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 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 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
45struct 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 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 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}