Skip to main content

gpui_authoring/elements/
deferred.rs

1use crate::{
2    AnyElement, App, Bounds, Element, GlobalElementId, IntoElement, LayoutId, Pixels, Window,
3};
4
5/// Builds a `Deferred` element, which delays the layout and paint of its child.
6pub fn deferred(child: impl IntoElement) -> Deferred {
7    Deferred {
8        child: Some(child.into_any_element()),
9        priority: 0,
10    }
11}
12
13/// An element which delays the painting of its child until after all of
14/// its ancestors, while keeping its layout as part of the current element tree.
15pub struct Deferred {
16    child: Option<AnyElement>,
17    priority: usize,
18}
19
20impl Deferred {
21    /// Sets the `priority` value of the `deferred` element, which
22    /// determines the drawing order relative to other deferred elements,
23    /// with higher values being drawn on top.
24    pub fn with_priority(mut self, priority: usize) -> Self {
25        self.priority = priority;
26        self
27    }
28}
29
30impl Element for Deferred {
31    type RequestLayoutState = ();
32    type PrepaintState = ();
33
34    fn id(&self) -> Option<crate::ElementId> {
35        None
36    }
37
38    fn source_location(&self) -> Option<&'static core::panic::Location<'static>> {
39        None
40    }
41
42    fn request_layout(
43        &mut self,
44        _id: Option<&GlobalElementId>,
45        window: &mut Window,
46        cx: &mut App,
47    ) -> (LayoutId, ()) {
48        let layout_id = self.child.as_mut().unwrap().request_layout(window, cx);
49        (layout_id, ())
50    }
51
52    fn prepaint(
53        &mut self,
54        _id: Option<&GlobalElementId>,
55        _bounds: Bounds<Pixels>,
56        _request_layout: &mut Self::RequestLayoutState,
57        window: &mut Window,
58        _cx: &mut App,
59    ) {
60        let child = self.child.take().unwrap();
61        let element_offset = window.element_offset();
62        window.defer_draw(child, element_offset, self.priority, None)
63    }
64
65    fn paint(
66        &mut self,
67        _id: Option<&GlobalElementId>,
68        _bounds: Bounds<Pixels>,
69        _request_layout: &mut Self::RequestLayoutState,
70        _prepaint: &mut Self::PrepaintState,
71        _window: &mut Window,
72        _cx: &mut App,
73    ) {
74    }
75}
76
77impl IntoElement for Deferred {
78    type Element = Self;
79
80    fn into_element(self) -> Self::Element {
81        self
82    }
83}
84
85impl Deferred {
86    /// Sets a priority for the element. A higher priority conceptually means painting the element
87    /// on top of deferred draws with a lower priority (i.e. closer to the viewer).
88    pub fn priority(mut self, priority: usize) -> Self {
89        self.priority = priority;
90        self
91    }
92}
93
94#[cfg(test)]
95mod tests {
96    use crate::{
97        Context, Entity, StyleRefinement, TestAppContext, Window, anchored, deferred, div, point,
98        prelude::*, px, size,
99    };
100
101    /// A stand-in for a dock panel hosting a popover (deferred draw) whose
102    /// content opens another popover (a deferred draw created while
103    /// prepainting the first one's content).
104    struct PanelView;
105
106    impl Render for PanelView {
107        fn render(&mut self, _window: &mut Window, _cx: &mut Context<Self>) -> impl IntoElement {
108            div().key_context("Panel").size_full().child(
109                deferred(
110                    anchored().position(point(px(10.), px(10.))).child(
111                        div().key_context("Popover").w(px(200.)).h(px(200.)).child(
112                            deferred(
113                                anchored().position(point(px(30.), px(30.))).child(
114                                    div()
115                                        .key_context("NestedMenu")
116                                        .debug_selector(|| "NESTED_MENU".into())
117                                        .w(px(50.))
118                                        .h(px(50.)),
119                                ),
120                            )
121                            .with_priority(2),
122                        ),
123                    ),
124                )
125                .with_priority(1),
126            )
127        }
128    }
129
130    struct RootView {
131        panel: Entity<PanelView>,
132    }
133
134    impl Render for RootView {
135        fn render(&mut self, _window: &mut Window, _cx: &mut Context<Self>) -> impl IntoElement {
136            div().key_context("Root").size_full().child(
137                self.panel
138                    .clone()
139                    .cached(StyleRefinement::default().size_full()),
140            )
141        }
142    }
143
144    /// Regression test for a crash with nested deferred draws (e.g. a popover
145    /// menu inside a popover hosted by a cached dock panel). Prepaint indices
146    /// recorded during the deferred draw rounds must index the same
147    /// `deferred_draws` vector that `reuse_prepaint` slices on the next frame;
148    /// previously they were measured against a transient per-round vector, so
149    /// reusing the panel's subtree grafted the wrong deferred draws and
150    /// panicked in the dispatch tree.
151    #[gpui::test]
152    fn test_nested_deferred_draws_with_reused_views(cx: &mut TestAppContext) {
153        let window = cx.open_window(size(px(800.), px(600.)), |_, cx| {
154            let panel = cx.new(|_| PanelView);
155            RootView { panel }
156        });
157        cx.run_until_parked();
158
159        let menu_bounds = window
160            .update(cx, |_, window, _| {
161                window
162                    .frame_state
163                    .rendered_frame
164                    .debug_bounds
165                    .get("NESTED_MENU")
166                    .copied()
167            })
168            .unwrap()
169            .expect("NESTED_MENU debug bounds not found");
170        assert_eq!(menu_bounds.size, size(px(50.), px(50.)));
171
172        // Re-render only the root view; the panel is cached, so its subtree -
173        // including both deferred draw records - is reused from the previous
174        // frame.
175        window.update(cx, |_, _, cx| cx.notify()).unwrap();
176        cx.run_until_parked();
177
178        // Reuse the subtree a second time, exercising ranges that were
179        // themselves recorded during a reused frame.
180        window.update(cx, |_, _, cx| cx.notify()).unwrap();
181        cx.run_until_parked();
182
183        // Re-render the panel itself again to prove the popovers still draw.
184        window
185            .update(cx, |root, _, cx| {
186                root.panel.update(cx, |_, cx| cx.notify());
187            })
188            .unwrap();
189        cx.run_until_parked();
190
191        window
192            .update(cx, |_, window, _| {
193                assert_eq!(window.frame_state.rendered_frame.deferred_draws.len(), 2);
194                assert!(
195                    window
196                        .frame_state
197                        .rendered_frame
198                        .debug_bounds
199                        .contains_key("NESTED_MENU")
200                );
201            })
202            .unwrap();
203    }
204}