Skip to main content

gpui/elements/
deferred.rs

1use crate::{
2    AnyElement, App, Bounds, Element, GlobalElementId, InspectorElementId, IntoElement, LayoutId,
3    Pixels, Window,
4};
5
6/// Builds a `Deferred` element, which delays the layout and paint of its child.
7pub fn deferred(child: impl IntoElement) -> Deferred {
8    Deferred {
9        child: Some(child.into_any_element()),
10        priority: 0,
11    }
12}
13
14/// An element which delays the painting of its child until after all of
15/// its ancestors, while keeping its layout as part of the current element tree.
16pub struct Deferred {
17    child: Option<AnyElement>,
18    priority: usize,
19}
20
21impl Deferred {
22    /// Sets the `priority` value of the `deferred` element, which
23    /// determines the drawing order relative to other deferred elements,
24    /// with higher values being drawn on top.
25    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    /// Sets a priority for the element. A higher priority conceptually means painting the element
91    /// on top of deferred draws with a lower priority (i.e. closer to the viewer).
92    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                // A plain entity read has no dispatch-tree path to invalidate the
208                // cached panel, so refresh to run the paint callback again.
209                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    /// A stand-in for a dock panel hosting a popover (deferred draw) whose
219    /// content opens another popover (a deferred draw created while
220    /// prepainting the first one's content).
221    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    /// Regression test for a crash with nested deferred draws (e.g. a popover
262    /// menu inside a popover hosted by a cached dock panel). Prepaint indices
263    /// recorded during the deferred draw rounds must index the same
264    /// `deferred_draws` vector that `reuse_prepaint` slices on the next frame;
265    /// previously they were measured against a transient per-round vector, so
266    /// reusing the panel's subtree grafted the wrong deferred draws and
267    /// panicked in the dispatch tree.
268    #[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        // Re-render only the root view; the panel is cached, so its subtree -
289        // including both deferred draw records - is reused from the previous
290        // frame.
291        window.update(cx, |_, _, cx| cx.notify()).unwrap();
292        cx.run_until_parked();
293
294        // Reuse the subtree a second time, exercising ranges that were
295        // themselves recorded during a reused frame.
296        window.update(cx, |_, _, cx| cx.notify()).unwrap();
297        cx.run_until_parked();
298
299        // Re-render the panel itself again to prove the popovers still draw.
300        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}