T09a overlays: popover, hover card, dialog with fullscreen, alert, confirm and prompt dialogs; shared severity; tooltip, sheet and drawer point to their homes

This commit is contained in:
ZacharyZhang-NY committed 2026-09-25 14:07:30 -04:00
1 parent d6b982016d
commit ca73d24c01
17 files changed
+1750 -16

No files matched your search

+135
View File
@@ -0,0 +1,135 @@
use std::{rc::Rc, time::Duration};
use gpui::{
AnyElement, App, Bounds, ElementId, Entity, InteractiveElement, IntoElement, ParentElement,
Pixels, RenderOnce, StatefulInteractiveElement, Styled, Task, Window, canvas, div,
};
use crate::forms::{float_height, surface};
/// How long the pointer rests on the trigger before the card opens.
const OPEN: Duration = Duration::from_millis(500);
/// How long the card waits after the pointer leaves, so it can cross the gap.
const LINGER: Duration = Duration::from_millis(200);
type Content = Rc<dyn Fn(&mut Window, &mut App) -> AnyElement>;
/// Whether the card shows, what the pointer is over, the pending change and the trigger's box.
#[derive(Default)]
struct Hover {
shown: bool,
on_trigger: bool,
on_card: bool,
host: Bounds<Pixels>,
height: Pixels,
_pending: Option<Task<()>>,
}
/// After `after`, shows or hides the card as the pointer then warrants.
fn settle_after(state: &Entity<Hover>, after: Duration, window: &mut Window, cx: &mut App) {
let weak = state.downgrade();
let task = window.spawn(cx, async move |cx| {
cx.background_executor().timer(after).await;
let done = cx.update(|_, cx| {
weak.update(cx, |hover, cx| {
let shown = hover.on_trigger || hover.on_card;
if hover.shown != shown {
log::info!("hover card: {}", if shown { "shown" } else { "hidden" });
hover.shown = shown;
cx.notify();
}
hover._pending = None;
})
});
if let Err(error) = done.and_then(|inner| inner) {
log::error!("hover card: trigger vanished: {error:#}");
}
});
state.update(cx, |hover, _| hover._pending = Some(task));
}
/// A card that opens once the pointer rests on its trigger, and stays while the pointer is on either. It never takes focus.
#[derive(IntoElement)]
pub struct HoverCard {
id: ElementId,
trigger: AnyElement,
content: Content,
}
impl HoverCard {
pub fn new<E: IntoElement>(
id: impl Into<ElementId>,
trigger: impl IntoElement,
content: impl Fn(&mut Window, &mut App) -> E + 'static,
) -> Self {
Self {
id: id.into(),
trigger: trigger.into_any_element(),
content: Rc::new(move |window, cx| content(window, cx).into_any_element()),
}
}
}
impl RenderOnce for HoverCard {
fn render(self, window: &mut Window, cx: &mut App) -> impl IntoElement {
let state = window.use_keyed_state((self.id.clone(), "hover"), cx, |_, _| Hover::default());
let (enter, measure) = (state.clone(), state.clone());
let host = div()
.id(self.id.clone())
.relative()
.child(self.trigger)
.on_hover(move |hovered, window, cx| {
enter.update(cx, |hover, _| hover.on_trigger = *hovered);
settle_after(&enter, if *hovered { OPEN } else { LINGER }, window, cx);
})
.child(
canvas(
move |bounds, _, cx| {
if measure.read(cx).host != bounds {
measure.update(cx, |hover, cx| {
hover.host = bounds;
cx.notify();
});
}
},
|_, _, _, _| {},
)
.absolute()
.top_0()
.left_0()
.size_full(),
);
if !state.read(cx).shown {
return host;
}
let (over, tall) = (state.clone(), state.clone());
let card = surface((self.id.clone(), "card"), cx)
.relative()
.occlude()
.p_4()
.on_hover(move |hovered, window, cx| {
over.update(cx, |hover, _| hover.on_card = *hovered);
settle_after(&over, LINGER, window, cx);
})
.child((self.content)(window, cx))
.child(
canvas(
move |bounds, _, cx| {
if tall.read(cx).height != bounds.size.height {
tall.update(cx, |hover, cx| {
hover.height = bounds.size.height;
cx.notify();
});
}
},
|_, _, _, _| {},
)
.absolute()
.top_0()
.left_0()
.size_full(),
);
let (anchor, height) = (state.read(cx).host, state.read(cx).height);
host.child(float_height(anchor, height, card, window, cx))
}
}