1use std::cell::RefCell;
2use std::rc::Rc;
3
4use geometry_core::Rect;
5use layout_core::{LayoutError, LayoutStyle, NodeId};
6use platform_core::Event;
7use reactive_core::{Effect, RwSignal, effect, signal};
8use ui_tree::{Component, EventResult, RenderNode};
9
10use crate::context::{mark_dirty, new_container, set_children, set_display, track_layout};
11use crate::layout_item::{LayoutItem, TrackedChildren, make_child};
12use crate::pointer::dispatch_container_event;
13
14type LazyBuild = Box<dyn FnOnce() -> Result<Vec<Box<dyn LayoutItem>>, LayoutError>>;
16
17struct LazyState {
18 node: NodeId,
19 children: TrackedChildren,
20 build: Option<LazyBuild>,
21}
22
23pub struct Lazy {
36 node: NodeId,
37 rect: RwSignal<Rect>,
38 state: Rc<RefCell<LazyState>>,
39 visible: Rc<dyn Fn() -> bool>,
40 version: RwSignal<u64>,
42 _effect: Effect,
43}
44
45impl Lazy {
46 pub fn new(
49 container_style: LayoutStyle,
50 visible: impl Fn() -> bool + 'static,
51 build: impl FnOnce() -> Result<Vec<Box<dyn LayoutItem>>, LayoutError> + 'static,
52 ) -> Result<Self, LayoutError> {
53 let node = new_container(container_style, &[])?;
54 let rect = track_layout(node).expect("lazy container is registered");
55 let state = Rc::new(RefCell::new(LazyState {
56 node,
57 children: Vec::new(),
58 build: Some(Box::new(build)),
59 }));
60 let version = signal(0u64);
61 let visible: Rc<dyn Fn() -> bool> = Rc::new(visible);
62
63 let eff_state = Rc::clone(&state);
64 let eff_version = version.clone();
65 let eff_visible = Rc::clone(&visible);
66 let _effect = effect(move || {
68 let show = eff_visible();
69 if show && realize(&eff_state) {
70 eff_version.update(|v| *v = v.wrapping_add(1));
71 }
72 set_display(node, show);
73 mark_dirty(node).ok();
74 });
75
76 Ok(Self {
77 node,
78 rect,
79 state,
80 visible,
81 version,
82 _effect,
83 })
84 }
85
86 pub fn is_built(&self) -> bool {
88 self.state.borrow().build.is_none()
89 }
90}
91
92fn realize(state: &Rc<RefCell<LazyState>>) -> bool {
96 let Some(build) = state.borrow_mut().build.take() else {
97 return false;
98 };
99 let Ok(items) = build() else {
101 return false;
102 };
103 let children: TrackedChildren = items.into_iter().map(make_child).collect();
104 let nodes: Vec<NodeId> = children.iter().map(|c| c.node()).collect();
105
106 let mut st = state.borrow_mut();
107 st.children = children;
108 let container = st.node;
109 drop(st);
110 let _ = set_children(container, &nodes);
111 true
112}
113
114impl LayoutItem for Lazy {
115 fn layout_node(&self) -> NodeId {
116 self.node
117 }
118}
119
120impl Component for Lazy {
121 fn view(&self) -> RenderNode {
122 let show = (self.visible)();
124 self.version.get();
125 let _ = self.rect.get();
126 if !show {
127 return RenderNode::Empty;
128 }
129 let st = self.state.borrow();
130 RenderNode::group(st.children.iter().map(|c| c.segment.boundary()))
131 }
132
133 fn on_event(&mut self, event: &Event) -> EventResult {
134 if !(self.visible)() {
136 return EventResult::Ignored;
137 }
138 let mut st = self.state.borrow_mut();
139 dispatch_container_event(&mut st.children, event)
140 }
141
142 fn debug_name(&self) -> &'static str {
143 "Lazy"
144 }
145}
146
147#[cfg(test)]
148mod tests {
149 use std::cell::Cell;
150
151 use layout_core::AvailableSpace;
152 use reactive_core::signal;
153
154 use super::*;
155 use crate::container::Container;
156 use crate::context::{compute_layout, reset_layout_runtime};
157
158 fn leaf() -> Result<Box<dyn LayoutItem>, LayoutError> {
159 Ok(Box::new(Container::new(
160 LayoutStyle::new().width(10.0).height(10.0),
161 vec![],
162 )?))
163 }
164
165 #[test]
166 fn defers_construction_until_the_condition_first_holds() {
167 reset_layout_runtime();
168 let show = signal(false);
169 let builds = Rc::new(Cell::new(0));
170 let lazy = {
171 let (cond, builds) = (show.clone(), builds.clone());
172 Lazy::new(
173 LayoutStyle::new().flex_column(),
174 move || cond.get(),
175 move || {
176 builds.set(builds.get() + 1);
177 Ok(vec![leaf()?])
178 },
179 )
180 .unwrap()
181 };
182 assert_eq!(builds.get(), 0, "a block never shown costs nothing");
183 assert!(!lazy.is_built());
184 assert!(matches!(lazy.view(), RenderNode::Empty));
185
186 show.set(true);
187 assert_eq!(builds.get(), 1, "the first showing builds the subtree");
188 assert!(lazy.is_built());
189 }
190
191 #[test]
194 fn builds_once_and_only_toggles_afterwards() {
195 reset_layout_runtime();
196 let show = signal(true);
197 let builds = Rc::new(Cell::new(0));
198 let lazy = {
199 let (cond, builds) = (show.clone(), builds.clone());
200 Lazy::new(
201 LayoutStyle::new().flex_column(),
202 move || cond.get(),
203 move || {
204 builds.set(builds.get() + 1);
205 Ok(vec![leaf()?])
206 },
207 )
208 .unwrap()
209 };
210 assert_eq!(builds.get(), 1, "an initially-true block builds at once");
211 let node = lazy.state.borrow().children[0].node();
212
213 show.set(false);
214 show.set(true);
215 show.set(false);
216 show.set(true);
217 assert_eq!(builds.get(), 1, "reopening never rebuilds");
218 assert_eq!(
219 lazy.state.borrow().children[0].node(),
220 node,
221 "it is the same subtree, not a fresh one"
222 );
223 }
224
225 #[test]
226 fn a_hidden_block_takes_no_space() {
227 reset_layout_runtime();
228 let show = signal(true);
229 let lazy = {
230 let cond = show.clone();
231 Lazy::new(
232 LayoutStyle::new().flex_column(),
233 move || cond.get(),
234 || Ok(vec![leaf()?]),
235 )
236 .unwrap()
237 };
238 let node = lazy.layout_node();
239 compute_layout(
240 node,
241 AvailableSpace::Definite(100.0),
242 AvailableSpace::Definite(100.0),
243 )
244 .unwrap();
245 assert!(track_layout(node).unwrap().get().height > 0.0);
246
247 show.set(false);
248 compute_layout(
249 node,
250 AvailableSpace::Definite(100.0),
251 AvailableSpace::Definite(100.0),
252 )
253 .unwrap();
254 assert_eq!(track_layout(node).unwrap().get().height, 0.0);
255 }
256
257 #[test]
258 fn a_hidden_block_ignores_events() {
259 reset_layout_runtime();
260 let show = signal(false);
261 let mut lazy = {
262 let cond = show.clone();
263 Lazy::new(
264 LayoutStyle::new().flex_column(),
265 move || cond.get(),
266 || Ok(vec![leaf()?]),
267 )
268 .unwrap()
269 };
270 assert_eq!(
271 lazy.on_event(&Event::CursorEntered),
272 EventResult::Ignored,
273 "an unbuilt block answers for nothing"
274 );
275 }
276}