Skip to main content

gpui_core/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)
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 crate::{
101        AnyView, Context, Entity, StyleRefinement, TestAppContext, Window, anchored, deferred, div,
102        point, prelude::*, px, size,
103    };
104
105    struct PanelView;
106
107    impl Render for PanelView {
108        fn render(&mut self, _window: &mut Window, _cx: &mut Context<Self>) -> impl IntoElement {
109            div().key_context("Panel").size_full().child(
110                deferred(
111                    anchored().position(point(px(10.), px(10.))).child(
112                        div().key_context("Popover").w(px(200.)).h(px(200.)).child(
113                            deferred(
114                                anchored().position(point(px(30.), px(30.))).child(
115                                    div()
116                                        .key_context("NestedMenu")
117                                        .debug_selector(|| "NESTED_MENU".into())
118                                        .w(px(50.))
119                                        .h(px(50.)),
120                                ),
121                            )
122                            .with_priority(2),
123                        ),
124                    ),
125                )
126                .with_priority(1),
127            )
128        }
129    }
130
131    struct RootView {
132        panel: Entity<PanelView>,
133    }
134
135    impl Render for RootView {
136        fn render(&mut self, _window: &mut Window, _cx: &mut Context<Self>) -> impl IntoElement {
137            div().key_context("Root").size_full().child(
138                AnyView::from(self.panel.clone()).cached(StyleRefinement::default().size_full()),
139            )
140        }
141    }
142
143    #[gpui::test]
144    fn nested_deferred_draws_survive_cached_view_reuse(cx: &mut TestAppContext) {
145        let window = cx.add_window(|_, cx| {
146            let panel = cx.new(|_| PanelView);
147            RootView { panel }
148        });
149        cx.run_until_parked();
150
151        let menu_bounds = window
152            .update(cx, |_, window, _| {
153                window
154                    .rendered_frame
155                    .debug_bounds
156                    .get("NESTED_MENU")
157                    .copied()
158            })
159            .unwrap()
160            .expect("NESTED_MENU debug bounds not found");
161        assert_eq!(menu_bounds.size, size(px(50.), px(50.)));
162
163        for _ in 0..2 {
164            window.update(cx, |_, _, cx| cx.notify()).unwrap();
165            cx.run_until_parked();
166        }
167
168        window
169            .update(cx, |root, _, cx| {
170                root.panel.update(cx, |_, cx| cx.notify());
171            })
172            .unwrap();
173        cx.run_until_parked();
174
175        window
176            .update(cx, |_, window, _| {
177                assert_eq!(window.rendered_frame.deferred_draws.len(), 2);
178                assert!(
179                    window
180                        .rendered_frame
181                        .debug_bounds
182                        .contains_key("NESTED_MENU")
183                );
184            })
185            .unwrap();
186    }
187}