206 lines · 6.7 KB · Rust
RawDownload
use crate::{    AnyElement, App, Bounds, Element, GlobalElementId, InspectorElementId, IntoElement, LayoutId,    Pixels, Window,}; /// Builds a `Deferred` element, which delays the layout and paint of its child.pub fn deferred(child: impl IntoElement) -> Deferred {    Deferred {        child: Some(child.into_any_element()),        priority: 0,    }} /// An element which delays the painting of its child until after all of/// its ancestors, while keeping its layout as part of the current element tree.pub struct Deferred {    child: Option<AnyElement>,    priority: usize,} impl Deferred {    /// Sets the `priority` value of the `deferred` element, which    /// determines the drawing order relative to other deferred elements,    /// with higher values being drawn on top.    pub fn with_priority(mut self, priority: usize) -> Self {        self.priority = priority;        self    }} impl Element for Deferred {    type RequestLayoutState = ();    type PrepaintState = ();     fn id(&self) -> Option<crate::ElementId> {        None    }     fn source_location(&self) -> Option<&'static core::panic::Location<'static>> {        None    }     fn request_layout(        &mut self,        _id: Option<&GlobalElementId>,        _inspector_id: Option<&InspectorElementId>,        window: &mut Window,        cx: &mut App,    ) -> (LayoutId, ()) {        let layout_id = self.child.as_mut().unwrap().request_layout(window, cx);        (layout_id, ())    }     fn prepaint(        &mut self,        _id: Option<&GlobalElementId>,        _inspector_id: Option<&InspectorElementId>,        _bounds: Bounds<Pixels>,        _request_layout: &mut Self::RequestLayoutState,        window: &mut Window,        _cx: &mut App,    ) {        let child = self.child.take().unwrap();        let element_offset = window.element_offset();        window.defer_draw(child, element_offset, self.priority, None)    }     fn paint(        &mut self,        _id: Option<&GlobalElementId>,        _inspector_id: Option<&InspectorElementId>,        _bounds: Bounds<Pixels>,        _request_layout: &mut Self::RequestLayoutState,        _prepaint: &mut Self::PrepaintState,        _window: &mut Window,        _cx: &mut App,    ) {    }} impl IntoElement for Deferred {    type Element = Self;     fn into_element(self) -> Self::Element {        self    }} impl Deferred {    /// Sets a priority for the element. A higher priority conceptually means painting the element    /// on top of deferred draws with a lower priority (i.e. closer to the viewer).    pub fn priority(mut self, priority: usize) -> Self {        self.priority = priority;        self    }} #[cfg(test)]mod tests {    use crate::{        Context, Entity, StyleRefinement, TestAppContext, Window, anchored, deferred, div, point,        prelude::*, px, size,    };     /// A stand-in for a dock panel hosting a popover (deferred draw) whose    /// content opens another popover (a deferred draw created while    /// prepainting the first one's content).    struct PanelView;     impl Render for PanelView {        fn render(&mut self, _window: &mut Window, _cx: &mut Context<Self>) -> impl IntoElement {            div().key_context("Panel").size_full().child(                deferred(                    anchored().position(point(px(10.), px(10.))).child(                        div().key_context("Popover").w(px(200.)).h(px(200.)).child(                            deferred(                                anchored().position(point(px(30.), px(30.))).child(                                    div()                                        .key_context("NestedMenu")                                        .debug_selector(|| "NESTED_MENU".into())                                        .w(px(50.))                                        .h(px(50.)),                                ),                            )                            .with_priority(2),                        ),                    ),                )                .with_priority(1),            )        }    }     struct RootView {        panel: Entity<PanelView>,    }     impl Render for RootView {        fn render(&mut self, _window: &mut Window, _cx: &mut Context<Self>) -> impl IntoElement {            div().key_context("Root").size_full().child(                self.panel                    .clone()                    .cached(StyleRefinement::default().size_full()),            )        }    }     /// Regression test for a crash with nested deferred draws (e.g. a popover    /// menu inside a popover hosted by a cached dock panel). Prepaint indices    /// recorded during the deferred draw rounds must index the same    /// `deferred_draws` vector that `reuse_prepaint` slices on the next frame;    /// previously they were measured against a transient per-round vector, so    /// reusing the panel's subtree grafted the wrong deferred draws and    /// panicked in the dispatch tree.    #[gpui::test]    fn test_nested_deferred_draws_with_reused_views(cx: &mut TestAppContext) {        let window = cx.open_window(size(px(800.), px(600.)), |_, cx| {            let panel = cx.new(|_| PanelView);            RootView { panel }        });        cx.run_until_parked();         let menu_bounds = window            .update(cx, |_, window, _| {                window                    .rendered_frame                    .debug_bounds                    .get("NESTED_MENU")                    .copied()            })            .unwrap()            .expect("NESTED_MENU debug bounds not found");        assert_eq!(menu_bounds.size, size(px(50.), px(50.)));         // Re-render only the root view; the panel is cached, so its subtree -        // including both deferred draw records - is reused from the previous        // frame.        window.update(cx, |_, _, cx| cx.notify()).unwrap();        cx.run_until_parked();         // Reuse the subtree a second time, exercising ranges that were        // themselves recorded during a reused frame.        window.update(cx, |_, _, cx| cx.notify()).unwrap();        cx.run_until_parked();         // Re-render the panel itself again to prove the popovers still draw.        window            .update(cx, |root, _, cx| {                root.panel.update(cx, |_, cx| cx.notify());            })            .unwrap();        cx.run_until_parked();         window            .update(cx, |_, window, _| {                assert_eq!(window.rendered_frame.deferred_draws.len(), 2);                assert!(                    window                        .rendered_frame                        .debug_bounds                        .contains_key("NESTED_MENU")                );            })            .unwrap();    }}