1use crate::{
2 AnyElement, App, Bounds, Element, GlobalElementId, InspectorElementId, IntoElement, LayoutId,
3 Pixels, Window,
4};
5
6pub fn deferred(child: impl IntoElement) -> Deferred {
8 Deferred {
9 child: Some(child.into_any_element()),
10 priority: 0,
11 }
12}
13
14pub struct Deferred {
17 child: Option<AnyElement>,
18 priority: usize,
19}
20
21impl Deferred {
22 pub fn with_priority(mut self, priority: usize) -> Self {
26 self.priority = priority;
27 self
28 }
29}
30
31impl Element for Deferred {
32 type RequestLayoutState = ();
33 type PrepaintState = ();
34
35 fn id(&self) -> Option<crate::ElementId> {
36 None
37 }
38
39 fn source_location(&self) -> Option<&'static core::panic::Location<'static>> {
40 None
41 }
42
43 fn request_layout(
44 &mut self,
45 _id: Option<&GlobalElementId>,
46 _inspector_id: Option<&InspectorElementId>,
47 window: &mut Window,
48 cx: &mut App,
49 ) -> (LayoutId, ()) {
50 let layout_id = self.child.as_mut().unwrap().request_layout(window, cx);
51 (layout_id, ())
52 }
53
54 fn prepaint(
55 &mut self,
56 _id: Option<&GlobalElementId>,
57 _inspector_id: Option<&InspectorElementId>,
58 _bounds: Bounds<Pixels>,
59 _request_layout: &mut Self::RequestLayoutState,
60 window: &mut Window,
61 _cx: &mut App,
62 ) {
63 let child = self.child.take().unwrap();
64 let element_offset = window.element_offset();
65 window.defer_draw(child, element_offset, self.priority, None)
66 }
67
68 fn paint(
69 &mut self,
70 _id: Option<&GlobalElementId>,
71 _inspector_id: Option<&InspectorElementId>,
72 _bounds: Bounds<Pixels>,
73 _request_layout: &mut Self::RequestLayoutState,
74 _prepaint: &mut Self::PrepaintState,
75 _window: &mut Window,
76 _cx: &mut App,
77 ) {
78 }
79}
80
81impl IntoElement for Deferred {
82 type Element = Self;
83
84 fn into_element(self) -> Self::Element {
85 self
86 }
87}
88
89impl Deferred {
90 pub fn priority(mut self, priority: usize) -> Self {
93 self.priority = priority;
94 self
95 }
96}
97
98#[cfg(test)]
99mod tests {
100 use std::{cell::Cell, rc::Rc};
101
102 use crate::{
103 Context, Entity, StyleRefinement, TestAppContext, Window, anchored, deferred, div, point,
104 prelude::*, px, size,
105 };
106
107 struct SubscriptionLeaf {
108 value: usize,
109 output: Rc<Cell<usize>>,
110 }
111
112 struct SubscriptionPanel {
113 leaf: Entity<SubscriptionLeaf>,
114 deferred_child: bool,
115 renders: Rc<Cell<usize>>,
116 }
117
118 impl Render for SubscriptionPanel {
119 fn render(&mut self, _: &mut Window, _: &mut Context<Self>) -> impl IntoElement {
120 self.renders.set(self.renders.get() + 1);
121 let leaf = self.leaf.clone();
122 let child = crate::canvas(
123 |_, _, _| {},
124 move |_, _, _, cx| {
125 let leaf = leaf.read(cx);
126 leaf.output.set(leaf.value);
127 },
128 )
129 .size(px(50.));
130 if self.deferred_child {
131 deferred(child).into_any_element()
132 } else {
133 child.into_any_element()
134 }
135 }
136 }
137
138 struct SubscriptionRoot {
139 panel: Entity<SubscriptionPanel>,
140 deferred_panel: bool,
141 renders: Rc<Cell<usize>>,
142 }
143
144 impl Render for SubscriptionRoot {
145 fn render(&mut self, _: &mut Window, _: &mut Context<Self>) -> impl IntoElement {
146 self.renders.set(self.renders.get() + 1);
147 let panel = self
148 .panel
149 .clone()
150 .cached(StyleRefinement::default().size(px(100.)));
151 if self.deferred_panel {
152 deferred(panel).into_any_element()
153 } else {
154 panel.into_any_element()
155 }
156 }
157 }
158
159 #[gpui::test]
160 fn test_cached_reuse_keeps_entity_subscriptions(cx: &mut TestAppContext) {
161 for deferred_child in [true, false] {
162 for deferred_panel in [false, true] {
163 let output = Rc::new(Cell::new(0));
164 let renders = Rc::new(Cell::new(0));
165 let root_renders = Rc::new(Cell::new(0));
166 let leaf = cx.new(|_| SubscriptionLeaf {
167 value: 1,
168 output: output.clone(),
169 });
170 let panel = cx.new(|_| SubscriptionPanel {
171 leaf: leaf.clone(),
172 deferred_child,
173 renders: renders.clone(),
174 });
175 let window = cx.open_window(size(px(800.), px(600.)), |_, _| SubscriptionRoot {
176 panel,
177 deferred_panel,
178 renders: root_renders.clone(),
179 });
180 cx.run_until_parked();
181 assert_eq!(output.get(), 1);
182 let initial_renders = renders.get();
183 for _ in 0..2 {
184 window
185 .update(cx, |_, _, cx| cx.notify())
186 .expect("notify root");
187 cx.run_until_parked();
188 assert_eq!(
189 renders.get(),
190 initial_renders,
191 "panel should reuse its cache"
192 );
193 }
194
195 let renders_before_notify = root_renders.get();
196 leaf.update(cx, |leaf, cx| {
197 leaf.value = 2;
198 cx.notify();
199 });
200 cx.run_until_parked();
201 let context =
202 format!("deferred child {deferred_child}, deferred panel {deferred_panel}");
203 assert!(
204 root_renders.get() > renders_before_notify,
205 "leaf notification should redraw: {context}"
206 );
207 window
210 .update(cx, |_, window, _| window.refresh())
211 .expect("refresh canvas");
212 cx.run_until_parked();
213 assert_eq!(output.get(), 2, "{context}");
214 }
215 }
216 }
217
218 struct PanelView;
222
223 impl Render for PanelView {
224 fn render(&mut self, _window: &mut Window, _cx: &mut Context<Self>) -> impl IntoElement {
225 div().key_context("Panel").size_full().child(
226 deferred(
227 anchored().position(point(px(10.), px(10.))).child(
228 div().key_context("Popover").w(px(200.)).h(px(200.)).child(
229 deferred(
230 anchored().position(point(px(30.), px(30.))).child(
231 div()
232 .key_context("NestedMenu")
233 .debug_selector(|| "NESTED_MENU".into())
234 .w(px(50.))
235 .h(px(50.)),
236 ),
237 )
238 .with_priority(2),
239 ),
240 ),
241 )
242 .with_priority(1),
243 )
244 }
245 }
246
247 struct RootView {
248 panel: Entity<PanelView>,
249 }
250
251 impl Render for RootView {
252 fn render(&mut self, _window: &mut Window, _cx: &mut Context<Self>) -> impl IntoElement {
253 div().key_context("Root").size_full().child(
254 self.panel
255 .clone()
256 .cached(StyleRefinement::default().size_full()),
257 )
258 }
259 }
260
261 #[gpui::test]
269 fn test_nested_deferred_draws_with_reused_views(cx: &mut TestAppContext) {
270 let window = cx.open_window(size(px(800.), px(600.)), |_, cx| {
271 let panel = cx.new(|_| PanelView);
272 RootView { panel }
273 });
274 cx.run_until_parked();
275
276 let menu_bounds = window
277 .update(cx, |_, window, _| {
278 window
279 .rendered_frame
280 .debug_bounds
281 .get("NESTED_MENU")
282 .copied()
283 })
284 .unwrap()
285 .expect("NESTED_MENU debug bounds not found");
286 assert_eq!(menu_bounds.size, size(px(50.), px(50.)));
287
288 window.update(cx, |_, _, cx| cx.notify()).unwrap();
292 cx.run_until_parked();
293
294 window.update(cx, |_, _, cx| cx.notify()).unwrap();
297 cx.run_until_parked();
298
299 window
301 .update(cx, |root, _, cx| {
302 root.panel.update(cx, |_, cx| cx.notify());
303 })
304 .unwrap();
305 cx.run_until_parked();
306
307 window
308 .update(cx, |_, window, _| {
309 assert_eq!(window.rendered_frame.deferred_draws.len(), 2);
310 assert!(
311 window
312 .rendered_frame
313 .debug_bounds
314 .contains_key("NESTED_MENU")
315 );
316 })
317 .unwrap();
318 }
319}