Skip to main content

ferroui_base/reactive/
disposable.rs

1use std::cell::{Cell, RefCell};
2use std::rc::Rc;
3
4use super::IDisposable;
5
6/// Factory for common [`IDisposable`] implementations.
7pub struct Disposable;
8
9struct EmptyDisposable;
10
11impl IDisposable for EmptyDisposable {
12    fn dispose(&self) {}
13}
14
15struct AnonymousDisposable<F: FnOnce()> {
16    action: Cell<Option<F>>,
17}
18
19impl<F: FnOnce()> IDisposable for AnonymousDisposable<F> {
20    fn dispose(&self) {
21        if let Some(action) = self.action.take() {
22            action();
23        }
24    }
25}
26
27impl Disposable {
28    /// A disposable that does nothing.
29    pub fn empty() -> Rc<dyn IDisposable> {
30        crate::ferro_thread_local! {
31            static EMPTY: Rc<dyn IDisposable> = Rc::new(EmptyDisposable);
32        }
33        EMPTY.with(Rc::clone)
34    }
35
36    /// A disposable that runs `action` the first time it is disposed.
37    pub fn create(action: impl FnOnce() + 'static) -> Rc<dyn IDisposable> {
38        Rc::new(AnonymousDisposable { action: Cell::new(Some(action)) })
39    }
40
41    /// A disposable that gives `state` to `dispose` the first time it is
42    /// disposed.
43    pub fn create_with_state<TState: 'static>(
44        state: TState,
45        dispose: impl FnOnce(TState) + 'static,
46    ) -> Rc<dyn IDisposable> {
47        Rc::new(AnonymousDisposable { action: Cell::new(Some(move || dispose(state))) })
48    }
49}
50
51/// Holds a replaceable inner disposable; assigning a new one disposes the
52/// previous one.
53#[derive(Default)]
54pub struct SerialDisposable {
55    current: RefCell<Option<Rc<dyn IDisposable>>>,
56    disposed: Cell<bool>,
57}
58
59impl SerialDisposable {
60    pub fn new() -> Self {
61        Self::default()
62    }
63
64    pub fn set(&self, value: Option<Rc<dyn IDisposable>>) {
65        if self.disposed.get() {
66            if let Some(v) = value {
67                v.dispose();
68            }
69            return;
70        }
71        let old = self.current.replace(value);
72        if let Some(old) = old {
73            old.dispose();
74        }
75    }
76}
77
78impl IDisposable for SerialDisposable {
79    fn dispose(&self) {
80        if !self.disposed.replace(true) {
81            if let Some(old) = self.current.take() {
82                old.dispose();
83            }
84        }
85    }
86}
87
88#[cfg(test)]
89mod tests {
90    // Not from upstream: the original has no tests of this type.
91    use super::*;
92
93    #[test]
94    fn a_disposable_with_state_gives_it_to_the_action_once() {
95        let seen = Rc::new(RefCell::new(Vec::new()));
96        let s = seen.clone();
97        let target = Disposable::create_with_state(5, move |state: i32| s.borrow_mut().push(state));
98
99        assert!(seen.borrow().is_empty());
100        target.dispose();
101        target.dispose();
102        assert_eq!(vec![5], *seen.borrow());
103    }
104}