Files
Ely-GPUI-Components/src/primitives/backdrop.rs
T
ZacharyZhang-NYandClaude Opus 5.5 9abab5f2ba Primitives · raise: what opens inside a dialog lies over it
gpui 0.2.2 asserts on a deferred draw inside another, and every dialog,
sheet and popover draws inside one, so opening a date field in the
snooze or schedule dialog panicked, as would any list, menu or picker
opened in an overlay. primitives::raise replaces each deferred draw:
at the top it defers as before; inside something raised it leaves a
placeholder in the tree, and its layer lays it out on its own, then
draws it last. check.sh refuses gpui's deferred anywhere else.

The time dialog's rule is now times::ahead, tested for a moment
behind, now itself, a moment ahead and another zone; a gpui test picks
yesterday in the snooze dialog, sees that it has passed and that
nothing is sent, then picks two days ahead and hears that moment.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-27 01:51:51 -04:00

108 lines
3.2 KiB
Rust

use std::rc::Rc;
use gpui::{
Animation, AnimationExt, AnyElement, App, ElementId, InteractiveElement, IntoElement,
MouseButton, ParentElement, Point, RenderOnce, Styled, Window, anchored, div, prelude::*,
};
use smallvec::SmallVec;
use super::raise;
use crate::{motion, theme::ActiveTheme};
type Dismiss = Rc<dyn Fn(&mut Window, &mut App)>;
/// Where a `Backdrop` holds its children.
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub enum Place {
#[default]
Center,
Left,
Right,
Top,
Bottom,
}
/// Full-window scrim. Children sit above it, centered or on an edge.
#[derive(IntoElement)]
pub struct Backdrop {
id: ElementId,
place: Place,
on_dismiss: Option<Dismiss>,
children: SmallVec<[AnyElement; 1]>,
}
impl Backdrop {
pub fn new(id: impl Into<ElementId>) -> Self {
Self {
id: id.into(),
place: Place::Center,
on_dismiss: None,
children: SmallVec::new(),
}
}
/// Edges stretch children along that edge.
pub fn place(mut self, place: Place) -> Self {
self.place = place;
self
}
/// Runs on a click outside the children.
pub fn on_dismiss(mut self, handler: impl Fn(&mut Window, &mut App) + 'static) -> Self {
self.on_dismiss = Some(Rc::new(handler));
self
}
}
impl ParentElement for Backdrop {
fn extend(&mut self, elements: impl IntoIterator<Item = AnyElement>) {
self.children.extend(elements);
}
}
impl RenderOnce for Backdrop {
fn render(self, window: &mut Window, cx: &mut App) -> impl IntoElement {
let viewport = window.viewport_size();
let fade = motion::duration(motion::BASE, cx);
let scrim = div()
.id(self.id.clone())
.absolute()
.top_0()
.left_0()
.size_full()
.bg(cx.theme().colors.backdrop)
.occlude()
.when_some(self.on_dismiss, |scrim, dismiss| {
scrim.on_mouse_down(MouseButton::Left, move |_, window, cx| dismiss(window, cx))
})
.with_animation(
self.id,
Animation::new(fade).with_easing(motion::ease_out_cubic),
|scrim, t| scrim.opacity(t),
);
raise(
anchored().position(Point::default()).child(
div()
.relative()
.w(viewport.width)
.h(viewport.height)
.flex()
.map(|layer| match self.place {
Place::Center => layer.items_center().justify_center(),
Place::Left => layer.flex_row().justify_start(),
Place::Right => layer.flex_row().justify_end(),
Place::Top => layer.flex_col().justify_start(),
Place::Bottom => layer.flex_col().justify_end(),
})
.child(scrim)
.children(
self.children
.into_iter()
.map(|child| div().relative().occlude().child(child)),
),
),
)
}
}