ferroui_base/utilities/
weak_events.rs1use super::{EventArgs, WeakEvent, WeakEventArgs};
2use crate::data::model::{CollectionChange, INotifyCollectionChanged, INotifyPropertyChanged};
3use crate::input::ICommand;
4use crate::reactive::IDisposable;
5use crate::{FerroObject, FerroPropertyChangedEventArgs, ObjectType, Ref};
6use std::any::Any;
7use std::cell::RefCell;
8use std::rc::{Rc, Weak};
9
10impl WeakEventArgs for CollectionChange {
11 type Args<'a> = CollectionChange;
12}
13
14impl WeakEventArgs for FerroPropertyChangedEventArgs<'static> {
15 type Args<'a> = FerroPropertyChangedEventArgs<'a>;
16}
17
18pub struct WeakEvents;
24
25crate::ferro_thread_local! {
26 static COLLECTION_CHANGED: Rc<WeakEvent<Rc<dyn INotifyCollectionChanged>, CollectionChange>> =
27 WeakEvent::<Rc<dyn INotifyCollectionChanged>, CollectionChange>::register_returning_unsubscribe(|c, s| {
28 let sender = Rc::downgrade(c);
30 let handler = c.collection_changed().add(Rc::new(move |e: &CollectionChange| {
31 let c = sender.upgrade();
32 s(c.as_ref().map(|c| c as &dyn Any), e)
33 }));
34 let c = Rc::downgrade(c);
35 Box::new(move || {
36 if let Some(c) = c.upgrade() {
37 c.collection_changed().remove(handler);
38 }
39 })
40 });
41
42 static THREAD_SAFE_PROPERTY_CHANGED: Rc<WeakEvent<Rc<dyn INotifyPropertyChanged>, str>> =
43 WeakEvent::<Rc<dyn INotifyPropertyChanged>, str>::register_returning_unsubscribe(|s, h| {
44 let sender = Rc::downgrade(s);
45 let handler = s.property_changed().add(Rc::new(move |e: &str| {
46 let s = sender.upgrade();
47 h(s.as_ref().map(|s| s as &dyn Any), e)
48 }));
49 let s = Rc::downgrade(s);
50 Box::new(move || {
51 if let Some(s) = s.upgrade() {
52 s.property_changed().remove(handler);
53 }
54 })
55 });
56
57 static FERRO_PROPERTY_CHANGED: Rc<WeakEvent<Ref<FerroObject>, FerroPropertyChangedEventArgs<'static>>> =
58 WeakEvent::<Ref<FerroObject>, FerroPropertyChangedEventArgs<'static>>::register_returning_unsubscribe(
59 |s, h| {
60 let sender = s.downgrade();
61 let handler = s.property_changed(move |e: &FerroPropertyChangedEventArgs<'_>| {
62 let s = sender.upgrade();
63 h(s.as_ref().map(|s| s as &dyn Any), e)
64 });
65 Box::new(move || handler.dispose())
66 },
67 );
68
69 static COMMAND_CAN_EXECUTE_CHANGED: Rc<WeakEvent<Rc<dyn ICommand>, EventArgs>> =
70 WeakEvent::<Rc<dyn ICommand>, EventArgs>::register_returning_unsubscribe(|s, h| {
71 let sender = Rc::downgrade(s);
72 let handler = s.can_execute_changed(Rc::new(move || {
73 if let Some(s) = sender.upgrade() {
74 h(Some(&s as &dyn Any), &EventArgs::EMPTY)
75 }
76 }));
77 Box::new(move || handler.dispose())
78 });
79}
80
81impl WeakEvents {
82 pub fn collection_changed() -> Rc<WeakEvent<Rc<dyn INotifyCollectionChanged>, CollectionChange>> {
84 COLLECTION_CHANGED.with(Rc::clone)
85 }
86
87 pub fn thread_safe_property_changed() -> Rc<WeakEvent<Rc<dyn INotifyPropertyChanged>, str>> {
94 THREAD_SAFE_PROPERTY_CHANGED.with(Rc::clone)
95 }
96
97 pub fn ferro_property_changed() -> Rc<WeakEvent<Ref<FerroObject>, FerroPropertyChangedEventArgs<'static>>> {
99 FERRO_PROPERTY_CHANGED.with(Rc::clone)
100 }
101
102 pub fn command_can_execute_changed() -> Rc<WeakEvent<Rc<dyn ICommand>, EventArgs>> {
104 COMMAND_CAN_EXECUTE_CHANGED.with(Rc::clone)
105 }
106
107 pub fn subscribe_command_can_execute_changed<T: ObjectType>(
121 command: &Rc<dyn ICommand>,
122 subscriber: &Ref<T>,
123 handler: fn(&T),
124 ) -> Rc<dyn IDisposable> {
125 let subscription = Rc::new(WeakEventSubscription { inner: RefCell::new(None) });
126
127 let weak_subscriber = subscriber.downgrade();
128 let weak_subscription: Weak<WeakEventSubscription> = Rc::downgrade(&subscription);
129 let inner = command.can_execute_changed(Rc::new(move || match weak_subscriber.upgrade() {
130 Some(subscriber) => handler(&subscriber),
131 None => {
132 if let Some(subscription) = weak_subscription.upgrade() {
133 subscription.dispose();
134 }
135 }
136 }));
137 *subscription.inner.borrow_mut() = Some(inner);
138
139 subscription
140 }
141}
142
143struct WeakEventSubscription {
146 inner: RefCell<Option<Rc<dyn IDisposable>>>,
147}
148
149impl IDisposable for WeakEventSubscription {
150 fn dispose(&self) {
151 let inner = self.inner.borrow_mut().take();
152 if let Some(inner) = inner {
153 inner.dispose();
154 }
155 }
156}
157
158impl Drop for WeakEventSubscription {
159 fn drop(&mut self) {
160 self.dispose();
161 }
162}
163
164#[cfg(test)]
165mod tests {
166 use super::*;
167 use crate::input::KeyBinding;
168 use crate::reactive::Disposable;
169 use crate::BoxedValue;
170 use std::cell::Cell;
171
172 struct CountingCommand {
174 this: Weak<CountingCommand>,
175 handlers: RefCell<Vec<(u64, Rc<dyn Fn()>)>>,
176 next_token: Cell<u64>,
177 }
178
179 impl CountingCommand {
180 fn new() -> Rc<Self> {
181 Rc::new_cyclic(|this| Self { this: this.clone(), handlers: RefCell::new(Vec::new()), next_token: Cell::new(0) })
182 }
183
184 fn subscription_count(&self) -> usize {
185 self.handlers.borrow().len()
186 }
187
188 fn raise(&self) {
189 let handlers: Vec<Rc<dyn Fn()>> = self.handlers.borrow().iter().map(|(_, h)| h.clone()).collect();
190 for handler in handlers {
191 handler();
192 }
193 }
194 }
195
196 impl ICommand for CountingCommand {
197 fn can_execute(&self, _parameter: Option<&BoxedValue>) -> bool {
198 true
199 }
200
201 fn execute(&self, _parameter: Option<&BoxedValue>) {}
202
203 fn can_execute_changed(&self, handler: Rc<dyn Fn()>) -> Rc<dyn IDisposable> {
204 let token = self.next_token.get();
205 self.next_token.set(token + 1);
206 self.handlers.borrow_mut().push((token, handler));
207 let weak = self.this.clone();
208 Disposable::create(move || {
209 if let Some(this) = weak.upgrade() {
210 this.handlers.borrow_mut().retain(|(t, _)| *t != token);
211 }
212 })
213 }
214 }
215
216 crate::ferro_thread_local! {
217 static CALLS: Cell<i32> = const { Cell::new(0) };
218 }
219
220 fn on_event(_subscriber: &KeyBinding) {
221 CALLS.with(|calls| calls.set(calls.get() + 1));
222 }
223
224 #[test]
225 fn handler_is_called_while_the_subscriber_is_alive() {
226 CALLS.with(|calls| calls.set(0));
227 let command = CountingCommand::new();
228 let as_command: Rc<dyn ICommand> = command.clone();
229 let subscriber = KeyBinding::new();
230
231 let _subscription = WeakEvents::subscribe_command_can_execute_changed(&as_command, &subscriber, on_event);
232 command.raise();
233 command.raise();
234
235 assert_eq!(1, command.subscription_count());
236 assert_eq!(2, CALLS.with(Cell::get));
237 }
238
239 #[test]
240 fn subscription_does_not_keep_the_subscriber_alive() {
241 let command = CountingCommand::new();
242 let as_command: Rc<dyn ICommand> = command.clone();
243 let subscriber = KeyBinding::new();
244 let weak = subscriber.downgrade();
245
246 let _subscription = WeakEvents::subscribe_command_can_execute_changed(&as_command, &subscriber, on_event);
247 drop(subscriber);
248
249 assert!(weak.upgrade().is_none());
250 }
251
252 #[test]
253 fn dropping_the_handle_unsubscribes() {
254 let command = CountingCommand::new();
255 let as_command: Rc<dyn ICommand> = command.clone();
256 let subscriber = KeyBinding::new();
257
258 let subscription = WeakEvents::subscribe_command_can_execute_changed(&as_command, &subscriber, on_event);
259 assert_eq!(1, command.subscription_count());
260 drop(subscription);
261
262 assert_eq!(0, command.subscription_count());
263 }
264
265 #[test]
266 fn disposing_the_handle_unsubscribes_once() {
267 let command = CountingCommand::new();
268 let as_command: Rc<dyn ICommand> = command.clone();
269 let subscriber = KeyBinding::new();
270
271 let subscription = WeakEvents::subscribe_command_can_execute_changed(&as_command, &subscriber, on_event);
272 subscription.dispose();
273 subscription.dispose();
274
275 assert_eq!(0, command.subscription_count());
276 }
277
278 #[test]
279 fn subscription_removes_itself_on_the_next_raise_after_the_subscriber_is_gone() {
280 CALLS.with(|calls| calls.set(0));
281 let command = CountingCommand::new();
282 let as_command: Rc<dyn ICommand> = command.clone();
283 let subscriber = KeyBinding::new();
284
285 let subscription = WeakEvents::subscribe_command_can_execute_changed(&as_command, &subscriber, on_event);
287 drop(subscriber);
288 assert_eq!(1, command.subscription_count());
289
290 command.raise();
291
292 assert_eq!(0, command.subscription_count());
293 assert_eq!(0, CALLS.with(Cell::get));
294 drop(subscription);
295 }
296}