T01 primitives: image, divider, backdrop, focus, pressable, measure, tooltip

Ely-built primitives plus gallery demos for every chapter-one entry,
including gpui natives. The gallery now scripts real input by posting
NSEvents to its own queue and shoots each interaction in both modes.
Codex gpt-6-astra (max): three rounds; the last finding, a two-line
doc comment, was fixed after the three-review cap.
This commit is contained in:
ZacharyZhang-NY committed 2026-09-25 00:39:06 -04:00
1 parent abf4cbcfc6
commit a13343c2a5
38 files changed
+2165 -157

No files matched your search

+83
View File
@@ -0,0 +1,83 @@
use std::rc::Rc;
use gpui::{
Animation, AnimationExt, AnyElement, App, ElementId, InteractiveElement, IntoElement,
MouseButton, ParentElement, Point, RenderOnce, Styled, Window, anchored, deferred, div,
prelude::*,
};
use smallvec::SmallVec;
use crate::{motion, theme::ActiveTheme};
type Dismiss = Rc<dyn Fn(&mut Window, &mut App)>;
/// Full-window scrim. Children sit centered above it.
#[derive(IntoElement)]
pub struct Backdrop {
id: ElementId,
on_dismiss: Option<Dismiss>,
children: SmallVec<[AnyElement; 1]>,
}
impl Backdrop {
pub fn new(id: impl Into<ElementId>) -> Self {
Self {
id: id.into(),
on_dismiss: None,
children: SmallVec::new(),
}
}
/// 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),
);
deferred(
anchored().position(Point::default()).child(
div()
.relative()
.w(viewport.width)
.h(viewport.height)
.flex()
.items_center()
.justify_center()
.child(scrim)
.children(
self.children
.into_iter()
.map(|child| div().relative().occlude().child(child)),
),
),
)
}
}