use crate::{
AnyElement, App, Bounds, ContentMask, Element, GlobalElementId, InspectorElementId,
IntoElement, LayoutId, Pixels, Window,
};
pub fn deferred(child: impl IntoElement) -> Deferred {
Deferred {
child: Some(child.into_any_element()),
priority: DeferredPriority::from(0),
content_mask: None,
}
}
#[derive(derive_setters::Setters)]
#[setters(generate = false)]
pub struct Deferred {
child: Option<AnyElement>,
#[setters(generate, into)]
priority: DeferredPriority,
#[setters(generate, strip_option)]
content_mask: Option<ContentMask<Pixels>>,
}
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();
let priority = self.priority.evaluate(window);
window.defer_draw(child, element_offset, priority, self.content_mask);
}
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_auto(mut self) -> Self {
self.priority = DeferredPriority::Auto;
self
}
}
pub enum DeferredPriority {
Auto,
Value(usize),
}
impl From<usize> for DeferredPriority {
fn from(value: usize) -> Self {
Self::Value(value)
}
}
impl DeferredPriority {
fn evaluate(&self, window: &Window) -> usize {
match self {
DeferredPriority::Value(value) => *value,
DeferredPriority::Auto => match window.deferred_priority() {
None => 1, Some(prev_deferred_priority) => prev_deferred_priority.saturating_add(1),
},
}
}
}
#[cfg(test)]
mod tests {
use crate::{
Bounds, ContentMask, Context, Entity, Modifiers, MouseButton, MouseDownEvent,
PlatformInput, StyleRefinement, TestAppContext, Window, anchored, deferred, div, point,
prelude::*, px, size,
};
use std::{cell::Cell, rc::Rc};
struct MaskedOverlay {
presses: Rc<Cell<usize>>,
}
impl Render for MaskedOverlay {
fn render(&mut self, _: &mut Window, _: &mut Context<Self>) -> impl IntoElement {
let presses = self.presses.clone();
deferred(div().id("masked-overlay").size(px(100.)).on_mouse_down(
MouseButton::Left,
move |_, _, _| {
presses.set(presses.get() + 1);
},
))
.content_mask(ContentMask {
bounds: Bounds::new(point(px(0.), px(0.)), size(px(40.), px(40.))),
fade_out: Default::default(),
})
}
}
struct MaskedOverlayRoot {
overlay: Entity<MaskedOverlay>,
}
impl Render for MaskedOverlayRoot {
fn render(&mut self, _: &mut Window, _: &mut Context<Self>) -> impl IntoElement {
div().size_full().child(
self.overlay
.clone()
.cached(StyleRefinement::default().size_full()),
)
}
}
#[gpui::test]
fn test_deferred_mask_clips_input(cx: &mut TestAppContext) {
let presses = Rc::new(Cell::new(0));
let window = cx.open_window(size(px(200.), px(200.)), |_, cx| {
let overlay = cx.new(|_| MaskedOverlay {
presses: presses.clone(),
});
MaskedOverlayRoot { overlay }
});
cx.run_until_parked();
for pass in 0..2 {
window
.update(cx, |_, window, cx| {
for position in [point(px(60.), px(60.)), point(px(20.), px(20.))] {
window.dispatch_event(
PlatformInput::MouseDown(MouseDownEvent {
button: MouseButton::Left,
position,
modifiers: Modifiers::default(),
click_count: 1,
first_mouse: false,
}),
cx,
);
}
})
.unwrap();
assert_eq!(
presses.get(),
pass + 1,
"only the visible region accepts input"
);
window.update(cx, |_, _, cx| cx.notify()).unwrap();
cx.run_until_parked();
}
}
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.)),
),
)
.priority(2),
),
),
)
.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();
}
}