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}