audit popover and dialog

This commit is contained in:
2026-11-07 20:07:09 +07:00
parent 66961a1026
commit 2b3e3b4e99
7 changed files with 75 additions and 23 deletions
+5 -1
View File
@@ -130,7 +130,11 @@ impl RenderOnce for Dialog {
let theme = cx.theme();
(
theme.colors.overlay,
theme.colors.border,
if theme.borders.overlays {
theme.colors.border
} else {
theme.colors.surface
},
theme.colors.foreground,
theme.colors.muted_foreground,
theme.colors.surface,
+4 -3
View File
@@ -7,7 +7,7 @@ use gpui::{
Entity, InteractiveElement as _, IntoElement, KeyDownEvent, MouseButton, MouseDownEvent,
ParentElement as _, Pixels, Point, RenderOnce, Role, ScrollHandle, SharedString,
StatefulInteractiveElement as _, Styled as _, Window, anchored, canvas, deferred, div,
ease_out_quint, px, relative,
ease_out_quint, point, px, relative,
};
use gpui_native_icons::{IconRole, icon as icon_glyph};
use gpui_native_theme::ActiveTheme as _;
@@ -374,11 +374,11 @@ impl RenderOnce for Menu {
let panel = v_flex()
.id("menu-panel")
.debug_selector(|| "menu-panel".to_string())
.occlude()
.track_focus(&focus)
.role(Role::Menu)
.aria_label(label)
.mt_1()
.min_w_56()
.p(chrome.spacing_unit)
.bg(chrome.surface)
@@ -435,7 +435,8 @@ impl RenderOnce for Menu {
let anchored = match self.open_at {
Some(position) => anchored().position(position),
None => anchored(),
};
}
.offset(point(px(0.), chrome.spacing_unit));
wrapper = wrapper.child(deferred(anchored.child(panel.with_animation(
"menu-enter",
+19 -9
View File
@@ -6,7 +6,7 @@ use gpui::{
Animation, AnimationExt as _, AnyElement, App, Bounds, DispatchPhase, ElementId,
InteractiveElement as _, IntoElement, KeyDownEvent, MouseButton, MouseDownEvent,
ParentElement as _, Pixels, RenderOnce, Role, SharedString, StatefulInteractiveElement as _,
Styled as _, Window, anchored, canvas, deferred, div, ease_out_quint,
Styled as _, Window, anchored, canvas, deferred, div, ease_out_quint, point, px,
};
use gpui_native_theme::ActiveTheme as _;
@@ -91,13 +91,19 @@ impl Popover {
impl RenderOnce for Popover {
fn render(self, window: &mut Window, cx: &mut App) -> impl IntoElement {
let (colors, radius, shadows, enter_duration) = {
let (colors, radius, shadows, enter_duration, panel_border, anchor_gap) = {
let theme = cx.theme();
(
theme.colors,
theme.radius,
theme.elevations.popover.shadows(),
theme.motion.overlay,
if theme.borders.overlays {
theme.colors.border
} else {
theme.colors.surface
},
theme.spacing.unit,
)
};
@@ -165,17 +171,17 @@ impl RenderOnce for Popover {
let panel = v_flex()
.id("popover-panel")
.debug_selector(|| "popover-panel".to_string())
.occlude()
.track_focus(&focus)
.role(Role::Dialog)
.aria_label(label)
.mt_1()
.min_w_32()
.bg(colors.surface)
.text_color(colors.surface_foreground)
.rounded(radius.overlay)
.border_1()
.border_color(colors.border)
.border_color(panel_border)
.shadow(shadows)
.overflow_hidden()
.on_mouse_down_out({
@@ -218,11 +224,15 @@ impl RenderOnce for Popover {
.inset_0(),
);
wrapper = wrapper.child(deferred(anchored().child(panel.with_animation(
"popover-enter",
Animation::new(enter_duration).with_easing(ease_out_quint()),
|panel, delta| panel.opacity(delta),
))));
wrapper = wrapper.child(deferred(
anchored()
.offset(point(px(0.), anchor_gap))
.child(panel.with_animation(
"popover-enter",
Animation::new(enter_duration).with_easing(ease_out_quint()),
|panel, delta| panel.opacity(delta),
)),
));
}
wrapper
+24
View File
@@ -45,6 +45,7 @@ impl Render for Harness {
})
.trigger(|_, _| {
div()
.debug_selector(|| "popover-trigger".to_string())
.w(px(100.))
.h(px(32.))
.bg(rgba(0x000000ff))
@@ -188,3 +189,26 @@ fn clicking_another_control_dismisses_and_reaches_it() {
assert_eq!(*changes.borrow(), vec![false]);
assert_eq!(*clicks.borrow(), 1);
}
/// The panel hangs one spacing unit below the trigger: the anchor gap is
/// theme-owned, and rides the anchored element so the window-edge clamp
/// stays intact.
#[test]
fn the_panel_hangs_one_spacing_unit_below_the_trigger() {
let (cx, mut visual, _window, _changes, _clicks) = popover_window(true);
let trigger = visual
.debug_bounds("popover-trigger")
.expect("trigger rendered");
let panel = visual
.debug_bounds("popover-panel")
.expect("panel rendered");
let expected = cx.update(|cx| Theme::global(cx).spacing.unit);
let gap = panel.top() - trigger.bottom();
// Painted bounds are snapped to device pixels; allow half a pixel.
assert!(
(gap - expected).abs() <= px(0.5),
"the anchor gap is {gap:?}, expected {expected:?}"
);
}
+9 -2
View File
@@ -189,6 +189,13 @@ pub struct Elevation {
}
impl Elevation {
/// No shadow: dark floating layers separate by border.
fn none() -> Self {
Self {
layers: [Shadow::none(), Shadow::none()],
}
}
fn single(key: Shadow) -> Self {
Self {
layers: [key, Shadow::none()],
@@ -232,8 +239,8 @@ impl Elevations {
pub(crate) fn for_platform(source: SourceKind, variant: ThemeVariant) -> Self {
if variant == ThemeVariant::Dark {
return Self {
popover: Elevation::single(Shadow::black(0., 4., 24., 0.3)),
dialog: Elevation::single(Shadow::black(0., 8., 40., 0.35)),
popover: Elevation::none(),
dialog: Elevation::none(),
control: Elevation::single(Shadow::black(0., 1., 2., 0.25)),
};
}
+4 -2
View File
@@ -545,6 +545,8 @@ mod tests {
resolved.borders.overlays,
"{source:?} frames floating layers in dark"
);
assert!(resolved.elevations.popover.shadows().is_empty());
assert!(resolved.elevations.dialog.shadows().is_empty());
}
}
@@ -578,7 +580,7 @@ mod tests {
#[test]
fn elevations_and_motion_resolve_with_defaults() {
let resolved = resolve(
&system(ThemeVariant::Dark, PartialPalette::default(), None),
&system(ThemeVariant::Light, PartialPalette::default(), None),
None,
None,
);
@@ -590,7 +592,7 @@ mod tests {
);
assert!(resolved.motion.overlay > std::time::Duration::ZERO);
let shadows = resolved.elevations.popover.shadows();
assert_eq!(shadows.len(), 1);
assert_eq!(shadows.len(), 2);
assert!(!shadows[0].inset);
}