use crate::{
AnyElement, App, Bounds, Element, GlobalElementId, InspectorElementId, IntoElement, LayoutId,
Pixels, Window,
};
pub fn deferred(child: impl IntoElement) -> Deferred {
Deferred {
child: Some(child.into_any_element()),
priority: 0,
}
}
pub struct Deferred {
child: Option<AnyElement>,
priority: usize,
}
impl Deferred {
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 {
pub fn priority(mut self, priority: usize) -> Self {
self.priority = priority;
self
}
}
#[cfg(test)]
mod tests {
use std::{cell::Cell, rc::Rc};
use crate::{
Context, Entity, StyleRefinement, TestAppContext, Window, anchored, deferred, div, point,
prelude::*, px, size,
};
struct SubscriptionLeaf {
value: usize,
output: Rc<Cell<usize>>,
}
struct SubscriptionPanel {
leaf: Entity<SubscriptionLeaf>,
deferred_child: bool,
renders: Rc<Cell<usize>>,
}
impl Render for SubscriptionPanel {
fn render(&mut self, _: &mut Window, _: &mut Context<Self>) -> impl IntoElement {
self.renders.set(self.renders.get() + 1);
let leaf = self.leaf.clone();
let child = crate::canvas(
|_, _, _| {},
move |_, _, _, cx| {
let leaf = leaf.read(cx);
leaf.output.set(leaf.value);
},
)
.size(px(50.));
if self.deferred_child {
deferred(child).into_any_element()
} else {
child.into_any_element()
}
}
}
struct SubscriptionRoot {
panel: Entity<SubscriptionPanel>,
deferred_panel: bool,
renders: Rc<Cell<usize>>,
}
impl Render for SubscriptionRoot {
fn render(&mut self, _: &mut Window, _: &mut Context<Self>) -> impl IntoElement {
self.renders.set(self.renders.get() + 1);
let panel = self
.panel
.clone()
.cached(StyleRefinement::default().size(px(100.)));
if self.deferred_panel {
deferred(panel).into_any_element()
} else {
panel.into_any_element()
}
}
}
#[gpui::test]
fn test_cached_reuse_keeps_entity_subscriptions(cx: &mut TestAppContext) {
for deferred_child in [true, false] {
for deferred_panel in [false, true] {
let output = Rc::new(Cell::new(0));
let renders = Rc::new(Cell::new(0));
let root_renders = Rc::new(Cell::new(0));
let leaf = cx.new(|_| SubscriptionLeaf {
value: 1,
output: output.clone(),
});
let panel = cx.new(|_| SubscriptionPanel {
leaf: leaf.clone(),
deferred_child,
renders: renders.clone(),
});
let window = cx.open_window(size(px(800.), px(600.)), |_, _| SubscriptionRoot {
panel,
deferred_panel,
renders: root_renders.clone(),
});
cx.run_until_parked();
assert_eq!(output.get(), 1);
let initial_renders = renders.get();
for _ in 0..2 {
window
.update(cx, |_, _, cx| cx.notify())
.expect("notify root");
cx.run_until_parked();
assert_eq!(
renders.get(),
initial_renders,
"panel should reuse its cache"
);
}
let renders_before_notify = root_renders.get();
leaf.update(cx, |leaf, cx| {
leaf.value = 2;
cx.notify();
});
cx.run_until_parked();
let context =
format!("deferred child {deferred_child}, deferred panel {deferred_panel}");
assert!(
root_renders.get() > renders_before_notify,
"leaf notification should redraw: {context}"
);
window
.update(cx, |_, window, _| window.refresh())
.expect("refresh canvas");
cx.run_until_parked();
assert_eq!(output.get(), 2, "{context}");
}
}
}
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()),
)
}
}
#[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.)));
window.update(cx, |_, _, cx| cx.notify()).unwrap();
cx.run_until_parked();
window.update(cx, |_, _, cx| cx.notify()).unwrap();
cx.run_until_parked();
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();
}
}