Popover

Popover 用于在触发元素附近展示浮动内容。它支持多种定位方式、自定义内容、不同触发方式以及自动关闭行为,适合用来实现提示卡片、上下文菜单、小表单和局部操作面板。

#导入

use gpui_kit::component::popover::{Popover};

#用法

#基础 Popover

use gpui_kit::ParentElement as _;
use gpui_kit::component::{button::Button, popover::Popover};

Popover::new("basic-popover")
    .trigger(Button::new("trigger").label("Click me").outline())
    .child("Hello, this is a popover!")
    .child("It appears when you click the button.")

#自定义定位

anchor 命名的是 Popover 自身的锚点,不是触发器的角点。 Top* 表示弹层在触发器下方,Bottom* 表示在上方; LeftCenter 表示在右侧,RightCenter 表示在左侧。 弹层只限制在窗口内,不改变 anchor,也不自动翻转。

use gpui_kit::{Anchor, px};
use gpui_kit::component::popover::Popover;

Popover::new("anchored")
    .anchor(Anchor::TopCenter)
    .offset(px(8.))
    .arrow(true)
    .trigger(Button::new("details").label("详情"))
    .child("上下文详情")
选项含义默认值
anchor(Anchor)弹层锚点,支持 TopCenterBottomCenter 等八种位置TopLeft
offset(Pixels)触发器到弹层的间距;有箭头时为到箭头尖端的间距0.25rem
arrow(bool)在 anchor 对应边显示箭头false

箭头跟随 anchor 的起始、居中或末端对齐,并向内避开圆角。 箭头额外占用 0.375rem,使用弹层背景色(未设置时使用主题的 popover 色)。 offsetarrow 都不会切换定位策略。

例如 Anchor::TopLeft 会让 Popover 出现在触发器正下方,并与其左对齐:

[ Trigger ]
┌──────────────┐
│   Popover    │
└──────────────┘
use gpui_kit::component::Anchor;

Popover::new("top-center")
    .anchor(Anchor::TopCenter)
    .trigger(Button::new("btn").label("Top Center").outline())
    .child("在触发器下方居中")

#在 Popover 中渲染 View

你也可以把实现了 RenderEntity<T> 作为 Popover 内容:

let view = cx.new(|_| MyView::new());

Popover::new("form-popover")
    .anchor(Anchor::BottomLeft)
    .trigger(Button::new("show-form").label("Open Form").outline())
    .child(view.clone())

#使用 content 构造动态内容

如果你需要根据状态动态构造内容,或者希望在闭包中拿到 Popover 的上下文,可以使用 content

use gpui_kit::ParentElement as _;
use gpui_kit::component::popover::Popover;

Popover::new("complex-popover")
    .anchor(Anchor::BottomLeft)
    .trigger(Button::new("complex").label("Complex Content").outline())
    .content(|_, _, _| {
        div()
            .child("This popover has complex content.")
            .child(
                Button::new("action-btn")
                    .label("Perform Action")
                    .outline()
            )
    })

#右键触发

如果你想把 Popover 当作自定义上下文菜单来用,可以指定鼠标按键:

use gpui_kit::MouseButton;

Popover::new("context-menu")
    .anchor(Anchor::BottomRight)
    .mouse_button(MouseButton::Right)
    .trigger(Button::new("right-click").label("Right Click Me").outline())
    .child("Context Menu")
    .child(Separator::horizontal())
    .child("This is a custom context menu.")

#手动关闭

如果你希望在内容内部主动关闭 Popover,可以发出 DismissEvent

use gpui_kit::component::{DismissEvent, popover::Popover};

Popover::new("dismiss-popover")
    .trigger(Button::new("dismiss").label("Dismiss Popover").outline())
    .content(|_, cx| {
        div()
            .child("Click the button below to dismiss this popover.")
            .child(
                Button::new("close-btn")
                    .label("Close Popover")
                    .on_click(cx.listener(|_, _, _, cx| {
                        cx.emit(DismissEvent);
                    }))
            )
    })

#自定义样式

Popover 同样支持 appearance(false) 来关闭默认样式,并通过 Styled trait 完整自定义外观:

Popover::new("custom-popover")
    .appearance(false)
    .trigger(Button::new("custom").label("Custom Style"))
    .bg(cx.theme().accent)
    .text_color(cx.theme().accent_foreground)
    .p_6()
    .rounded_xl()
    .shadow_2xl()
    .child("Fully custom styled popover")

#受控打开状态

通过 openon_open_change,你可以把 Popover 的开关状态交给外部状态管理:

use gpui_kit::component::popover::Popover;

struct MyView {
    popover_open: bool,
}

Popover::new("controlled-popover")
    .open(self.open)
    .on_open_change(cx.listener(|this, open: &bool, _, cx| {
        this.popover_open = *open;
        cx.notify();
    }))
    .trigger(Button::new("control-btn").label("Control Popover").outline())
    .child("This popover's open state is controlled programmatically.")

#默认打开

如果只想设置首次渲染时默认打开,可以使用 default_open(true)

use gpui_kit::component::popover::Popover;

Popover::new("default-open-popover")
    .default_open(true)
    .trigger(Button::new("default-open-btn").label("Default Open").outline())
    .child("This popover is open by default when first rendered.")

#自定义触发器

触发器是任意实现了 Selectable 的元素。Popover 打开期间会对触发器调用 open(true),而不是 selected(true),这样触发器可以区分「我的 Popover 正开着」 和「我是当前选中项」这两件事。

openis_open 默认落到 selectedis_selected,因此只实现了选中态的 触发器行为不变,Button 触发器打开时的外观也与选中态一致。如果你的元素已经 用 selected 表达别的含义(例如侧栏行选中表示当前视图),就覆盖这两个方法:

use gpui_kit::component::Selectable;

struct SidebarRow {
    /// 这一行是当前视图。
    selected: bool,
    /// 这一行的账户 Popover 正开着。
    open: bool,
}

impl Selectable for SidebarRow {
    fn selected(mut self, selected: bool) -> Self {
        self.selected = selected;
        self
    }

    fn is_selected(&self) -> bool {
        self.selected
    }

    fn open(mut self, open: bool) -> Self {
        self.open = open;
        self
    }

    fn is_open(&self) -> bool {
        self.open
    }
}