Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
207 lines
6.7 KiB
Rust
207 lines
6.7 KiB
Rust
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();
|
|
}
|
|
}
|