Skip to main content

frus_widgets/
notifier.rs

1//! **Listenables**: a value, or a controller, that tells whoever cares when it changed.
2//!
3//! [`ChangeNotifier`] is the plain form — something changed, and no more is said.
4//! [`ValueNotifier`] is a notifier that holds one value. Anything that a widget is driven
5//! by and something else may change — a text controller, a selection, a theme choice — is
6//! one of these underneath.
7//!
8//! ```
9//! use frus_widgets::{Listenable, ValueNotifier};
10//! use std::{cell::Cell, rc::Rc};
11//!
12//! let volume = ValueNotifier::new(3);
13//! let heard = Rc::new(Cell::new(0));
14//! let seen = heard.clone();
15//! let listener = volume.add_listener({
16//!     let volume = volume.clone();
17//!     move || seen.set(volume.get())
18//! });
19//!
20//! volume.set(7);
21//! assert_eq!(heard.get(), 7);
22//!
23//! drop(listener); // no longer told
24//! volume.set(9);
25//! assert_eq!(heard.get(), 7);
26//! ```
27//!
28//! Every notification also asks the shell for a rebuild, so a tree that reads a notifier
29//! while it is built shows the change without listening to it.
30
31use std::cell::{Cell, RefCell};
32use std::rc::{Rc, Weak};
33
34use crate::component::request_rebuild;
35
36type Listener = Rc<dyn Fn()>;
37
38#[derive(Default)]
39struct Listeners {
40    entries: RefCell<Vec<(u64, Listener)>>,
41    next: Cell<u64>,
42}
43
44/// Something that can be listened to.
45pub trait Listenable {
46    /// Calls `listener` whenever this changes, until the returned [`Subscription`] is
47    /// dropped.
48    fn add_listener(&self, listener: impl Fn() + 'static) -> Subscription;
49}
50
51/// The end of a listener's life: dropping it stops the notifications.
52///
53/// Keep it for as long as the listening should last — in a `State`, alongside the thing
54/// listened to; in an effect, returned as the cleanup.
55#[must_use = "a subscription stops listening as soon as it is dropped"]
56pub struct Subscription {
57    listeners: Weak<Listeners>,
58    id: u64,
59}
60
61impl Subscription {
62    /// Keeps listening for as long as the notifier lives, and forgets the handle.
63    pub fn forget(self) {
64        std::mem::forget(self);
65    }
66}
67
68impl Drop for Subscription {
69    fn drop(&mut self) {
70        if let Some(listeners) = self.listeners.upgrade() {
71            let removed = {
72                let mut entries = listeners.entries.borrow_mut();
73                entries
74                    .iter()
75                    .position(|(id, _)| *id == self.id)
76                    .map(|at| entries.remove(at))
77            };
78            drop(removed);
79        }
80    }
81}
82
83/// A notifier with nothing to say but *changed*.
84///
85/// Cloning gives another handle to the same notifier.
86#[derive(Clone, Default)]
87pub struct ChangeNotifier {
88    listeners: Rc<Listeners>,
89}
90
91impl ChangeNotifier {
92    /// A notifier nobody is listening to yet.
93    pub fn new() -> Self {
94        Self::default()
95    }
96
97    /// Tells every listener, in the order they were added, and asks for a rebuild.
98    ///
99    /// A listener that adds or drops another during the notification does not disturb it:
100    /// the ones told are those there when it began.
101    pub fn notify_listeners(&self) {
102        let told: Vec<Listener> = self
103            .listeners
104            .entries
105            .borrow()
106            .iter()
107            .map(|(_, listener)| listener.clone())
108            .collect();
109        for listener in told {
110            listener();
111        }
112        request_rebuild();
113    }
114
115    /// How many listeners there are.
116    pub fn listener_count(&self) -> usize {
117        self.listeners.entries.borrow().len()
118    }
119}
120
121impl Listenable for ChangeNotifier {
122    fn add_listener(&self, listener: impl Fn() + 'static) -> Subscription {
123        let id = self.listeners.next.get();
124        self.listeners.next.set(id + 1);
125        self.listeners
126            .entries
127            .borrow_mut()
128            .push((id, Rc::new(listener)));
129        Subscription {
130            listeners: Rc::downgrade(&self.listeners),
131            id,
132        }
133    }
134}
135
136impl std::fmt::Debug for ChangeNotifier {
137    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
138        write!(f, "ChangeNotifier({} listeners)", self.listener_count())
139    }
140}
141
142/// A notifier that holds a value, and tells its listeners when the value changes.
143///
144/// Cloning gives another handle to the same value.
145pub struct ValueNotifier<T> {
146    value: Rc<RefCell<T>>,
147    notifier: ChangeNotifier,
148}
149
150impl<T> Clone for ValueNotifier<T> {
151    fn clone(&self) -> Self {
152        Self {
153            value: self.value.clone(),
154            notifier: self.notifier.clone(),
155        }
156    }
157}
158
159impl<T: 'static> ValueNotifier<T> {
160    /// A notifier holding `value`.
161    pub fn new(value: T) -> Self {
162        Self {
163            value: Rc::new(RefCell::new(value)),
164            notifier: ChangeNotifier::new(),
165        }
166    }
167
168    /// A copy of the value.
169    pub fn get(&self) -> T
170    where
171        T: Clone,
172    {
173        self.value.borrow().clone()
174    }
175
176    /// Reads the value without copying it.
177    pub fn with<R>(&self, read: impl FnOnce(&T) -> R) -> R {
178        read(&self.value.borrow())
179    }
180
181    /// Replaces the value, and notifies — unless it is the value already there, in which
182    /// case nothing changed and nobody is told.
183    pub fn set(&self, value: T)
184    where
185        T: PartialEq,
186    {
187        if *self.value.borrow() == value {
188            return;
189        }
190        *self.value.borrow_mut() = value;
191        self.notifier.notify_listeners();
192    }
193
194    /// Changes the value in place, and notifies.
195    pub fn update(&self, change: impl FnOnce(&mut T)) {
196        change(&mut self.value.borrow_mut());
197        self.notifier.notify_listeners();
198    }
199}
200
201impl<T: Default + 'static> Default for ValueNotifier<T> {
202    fn default() -> Self {
203        Self::new(T::default())
204    }
205}
206
207impl<T> Listenable for ValueNotifier<T> {
208    fn add_listener(&self, listener: impl Fn() + 'static) -> Subscription {
209        self.notifier.add_listener(listener)
210    }
211}
212
213impl<T: std::fmt::Debug> std::fmt::Debug for ValueNotifier<T> {
214    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
215        f.debug_tuple("ValueNotifier")
216            .field(&*self.value.borrow())
217            .finish()
218    }
219}
220
221#[cfg(test)]
222mod tests {
223    use super::*;
224    use crate::take_rebuild_request;
225
226    #[test]
227    fn listeners_are_told_in_the_order_they_were_added() {
228        let notifier = ChangeNotifier::new();
229        let log = Rc::new(RefCell::new(Vec::new()));
230        let (a, b) = (log.clone(), log.clone());
231        let _a = notifier.add_listener(move || a.borrow_mut().push("a"));
232        let _b = notifier.add_listener(move || b.borrow_mut().push("b"));
233        notifier.notify_listeners();
234        assert_eq!(*log.borrow(), ["a", "b"]);
235    }
236
237    #[test]
238    fn a_dropped_subscription_stops_the_notifications() {
239        let notifier = ChangeNotifier::new();
240        let count = Rc::new(Cell::new(0));
241        let counted = count.clone();
242        let subscription = notifier.add_listener(move || counted.set(counted.get() + 1));
243        notifier.notify_listeners();
244        assert_eq!(notifier.listener_count(), 1);
245        drop(subscription);
246        assert_eq!(notifier.listener_count(), 0);
247        notifier.notify_listeners();
248        assert_eq!(count.get(), 1);
249    }
250
251    #[test]
252    fn a_forgotten_subscription_keeps_listening() {
253        let notifier = ChangeNotifier::new();
254        let count = Rc::new(Cell::new(0));
255        let counted = count.clone();
256        notifier
257            .add_listener(move || counted.set(counted.get() + 1))
258            .forget();
259        notifier.notify_listeners();
260        notifier.notify_listeners();
261        assert_eq!(count.get(), 2);
262    }
263
264    #[test]
265    fn a_listener_may_drop_another_during_the_notification() {
266        let notifier = ChangeNotifier::new();
267        let count = Rc::new(Cell::new(0));
268        let slot: Rc<RefCell<Option<Subscription>>> = Rc::default();
269        let counted = count.clone();
270        let victim = notifier.add_listener(move || counted.set(counted.get() + 1));
271        *slot.borrow_mut() = Some(victim);
272        let dropper = slot.clone();
273        let _first = notifier.add_listener(move || {
274            dropper.borrow_mut().take();
275        });
276        // The listeners told are the ones there when it began, so the victim still hears it
277        // this once — and no more.
278        notifier.notify_listeners();
279        notifier.notify_listeners();
280        assert_eq!(count.get(), 1);
281    }
282
283    #[test]
284    fn a_value_notifier_tells_only_when_the_value_changes() {
285        let value = ValueNotifier::new(1);
286        let count = Rc::new(Cell::new(0));
287        let counted = count.clone();
288        let _listener = value.add_listener(move || counted.set(counted.get() + 1));
289        value.set(1);
290        assert_eq!(count.get(), 0, "the same value is not a change");
291        value.set(2);
292        value.update(|v| *v += 1);
293        assert_eq!((count.get(), value.get()), (2, 3));
294    }
295
296    #[test]
297    fn a_change_asks_for_a_rebuild() {
298        take_rebuild_request();
299        let value = ValueNotifier::new(String::new());
300        value.set("x".into());
301        assert!(take_rebuild_request());
302        value.set("x".into());
303        assert!(!take_rebuild_request(), "and no change asks for nothing");
304    }
305}