Skip to main content

fui/
retained_view.rs

1use crate::node::{Node, NodeRef};
2use std::any::Any;
3use std::cell::{Cell, RefCell};
4use std::rc::Rc;
5
6type LifecycleHandler = Box<dyn Fn(&RetainedView)>;
7type DisposeHandler = Box<dyn FnOnce()>;
8
9struct RetainedViewInner {
10    root: NodeRef,
11    active: Cell<bool>,
12    disposed: Cell<bool>,
13    resources: RefCell<Vec<Box<dyn Any>>>,
14    activation_handlers: RefCell<Vec<LifecycleHandler>>,
15    deactivation_handlers: RefCell<Vec<LifecycleHandler>>,
16    disposal_handlers: RefCell<Vec<DisposeHandler>>,
17}
18
19impl Drop for RetainedViewInner {
20    fn drop(&mut self) {
21        if !self.disposed.get() {
22            self.disposed.set(true);
23            for handler in self.disposal_handlers.get_mut().drain(..) {
24                handler();
25            }
26            self.resources.get_mut().clear();
27            self.root.dispose();
28        }
29    }
30}
31
32#[derive(Clone)]
33pub struct RetainedView {
34    inner: Rc<RetainedViewInner>,
35}
36
37impl RetainedView {
38    pub fn new<T: Node + 'static>(root: &T) -> Self {
39        Self {
40            inner: Rc::new(RetainedViewInner {
41                root: root.node_ref(),
42                active: Cell::new(false),
43                disposed: Cell::new(false),
44                resources: RefCell::new(vec![Box::new(root.clone())]),
45                activation_handlers: RefCell::new(Vec::new()),
46                deactivation_handlers: RefCell::new(Vec::new()),
47                disposal_handlers: RefCell::new(Vec::new()),
48            }),
49        }
50    }
51
52    pub fn root(&self) -> NodeRef {
53        self.inner.root.clone()
54    }
55    pub fn is_active(&self) -> bool {
56        self.inner.active.get()
57    }
58    pub fn is_disposed(&self) -> bool {
59        self.inner.disposed.get()
60    }
61
62    pub fn keep_alive<T: 'static>(self, resource: T) -> Self {
63        if !self.inner.disposed.get() {
64            self.inner.resources.borrow_mut().push(Box::new(resource));
65        }
66        self
67    }
68
69    pub fn on_activate(self, handler: impl Fn(&RetainedView) + 'static) -> Self {
70        self.inner
71            .activation_handlers
72            .borrow_mut()
73            .push(Box::new(handler));
74        self
75    }
76
77    pub fn on_deactivate(self, handler: impl Fn(&RetainedView) + 'static) -> Self {
78        self.inner
79            .deactivation_handlers
80            .borrow_mut()
81            .push(Box::new(handler));
82        self
83    }
84
85    pub fn on_dispose(self, handler: impl FnOnce() + 'static) -> Self {
86        self.inner
87            .disposal_handlers
88            .borrow_mut()
89            .push(Box::new(handler));
90        self
91    }
92
93    pub fn activate(&self) {
94        if self.inner.disposed.get() || self.inner.active.replace(true) {
95            return;
96        }
97        for handler in self.inner.activation_handlers.borrow().iter() {
98            handler(self);
99        }
100    }
101
102    pub fn deactivate(&self) {
103        if self.inner.disposed.get() || !self.inner.active.replace(false) {
104            return;
105        }
106        crate::event::deactivate_subtree(&self.inner.root);
107        for handler in self.inner.deactivation_handlers.borrow().iter() {
108            handler(self);
109        }
110    }
111
112    pub fn dispose(&self) {
113        if self.inner.disposed.replace(true) {
114            return;
115        }
116        if self.inner.active.replace(false) {
117            crate::event::deactivate_subtree(&self.inner.root);
118            for handler in self.inner.deactivation_handlers.borrow().iter() {
119                handler(self);
120            }
121        }
122        for handler in std::mem::take(&mut *self.inner.disposal_handlers.borrow_mut()) {
123            handler();
124        }
125        self.inner.resources.borrow_mut().clear();
126        self.inner.root.dispose();
127        self.inner.activation_handlers.borrow_mut().clear();
128        self.inner.deactivation_handlers.borrow_mut().clear();
129    }
130}
131
132pub fn retained_view<T: Node + 'static>(root: &T) -> RetainedView {
133    RetainedView::new(root)
134}
135
136#[cfg(test)]
137mod tests {
138    use super::*;
139    use crate::ffi::{self, Call};
140    use crate::node::{flex_box, Node, ThemeBindable};
141    use crate::theme::{current_theme, generate_theme, use_custom_theme};
142    use std::cell::RefCell;
143
144    struct DropProbe(Rc<Cell<u32>>);
145    impl Drop for DropProbe {
146        fn drop(&mut self) {
147            self.0.set(self.0.get() + 1);
148        }
149    }
150
151    #[test]
152    fn retained_view_orders_lifecycle_retains_state_and_disposes_once() {
153        let root = flex_box();
154        let events = Rc::new(RefCell::new(Vec::new()));
155        let drops = Rc::new(Cell::new(0));
156        let view = retained_view(&root)
157            .keep_alive(DropProbe(drops.clone()))
158            .on_activate({
159                let events = events.clone();
160                move |_| events.borrow_mut().push("activate")
161            })
162            .on_deactivate({
163                let events = events.clone();
164                move |_| events.borrow_mut().push("deactivate")
165            })
166            .on_dispose({
167                let events = events.clone();
168                move || events.borrow_mut().push("dispose")
169            });
170
171        view.activate();
172        view.activate();
173        view.deactivate();
174        view.activate();
175        view.dispose();
176        view.dispose();
177
178        assert_eq!(
179            &*events.borrow(),
180            &[
181                "activate",
182                "deactivate",
183                "activate",
184                "deactivate",
185                "dispose"
186            ]
187        );
188        assert_eq!(drops.get(), 1);
189        assert!(view.is_disposed());
190        assert!(!view.is_active());
191        assert!(!root.has_built_handle());
192    }
193
194    #[test]
195    fn final_owner_drop_disposes_retained_resources_and_root() {
196        let root = flex_box();
197        root.build();
198        let drops = Rc::new(Cell::new(0));
199        {
200            let _view = retained_view(&root).keep_alive(DropProbe(drops.clone()));
201        }
202        assert_eq!(drops.get(), 1);
203        assert!(!root.has_built_handle());
204    }
205
206    #[test]
207    fn retained_view_keeps_concrete_root_theme_binding_alive() {
208        ffi::test::reset();
209        let previous_theme = current_theme();
210        let root = flex_box();
211        root.bind_theme(|root, theme| {
212            root.bg_color(theme.colors.surface);
213        });
214        root.build();
215        let handle = root.handle().raw();
216        let view = retained_view(&root);
217        drop(root);
218        ffi::test::take_calls();
219
220        let changed = generate_theme(false, 0x2468ACFF);
221        use_custom_theme(changed.clone());
222        let calls = ffi::test::take_calls();
223        assert!(calls.iter().any(|call| matches!(
224            call,
225            Call::SetBgColor {
226                handle: actual,
227                color,
228            } if *actual == handle && *color == changed.colors.surface
229        )));
230
231        view.dispose();
232        use_custom_theme(previous_theme);
233    }
234}