跳转至

09 · 浮层与弹出

FlowUI 用每窗口的 Overlay Host 管理弹层:主树布局时注册,再在视口坐标绘制。

优先用高层组件

需求 优先 API
确认 / 危险操作 AlertDialog / Modal
锚定在按钮旁的面板 Popover
悬停说明 Tooltip
菜单 Menu / Menubar / Dropdown / ContextMenu
短暂通知 Toast
完全自定义挂载 Portal(底层)

典型:受控 Open + 内容 + 关闭消息。

ui.Modal("settings", false, "设置", ui.Text("设置内容")).
    Open(m.ShowSettings).
    OnOpenChange(func(open bool) { send(SetSettingsOpen(open)) })
// 内容、标题、操作按钮按组件 API 配置

示例:examples/modalsexamples/alert_dialogs

Popover / Dropdown / Menu

  • 锚定控件 + 面板内容
  • open 遵循 07 的 Open 契约
  • 点击外部 / Escape 关闭由组件策略处理

Dropdown 默认使用点击触发,也可以按平台交互改为长按或悬停:

ui.Dropdown("actions", trigger, items).
    TriggerMode(ui.DropdownTriggerHover)

悬停模式使用短暂的进入/离开延迟,指针从触发器移动到菜单面板时不会立即关闭。

下拉菜单也支持右键触发、弹层箭头和尺寸约束:

ui.Dropdown("actions", trigger, items).
    TriggerMode(ui.DropdownTriggerContextMenu).
    AutoWidth().
    MinWidth(160).
    MaxWidth(320).
    Arrow(true)

MatchTriggerWidth(true) 可让面板至少与触发器同宽。悬停和长按延迟可以按交互场景调整:

ui.Dropdown("actions", trigger, items).
    TriggerMode(ui.DropdownTriggerHover).
    HoverOpenDelay(200 * time.Millisecond).
    HoverCloseDelay(120 * time.Millisecond)

Dropdown 和菜单的回调统一使用事件对象。DropdownOpenChangeEvent 提供触发来源, MenuActionEvent / DropdownActionEvent 提供完整菜单项和嵌套路径:

ui.Dropdown("actions", trigger, items).
    OnOpenChangeEvent(func(event ui.DropdownOpenChangeEvent) {
        // event.Source: Trigger、ContextMenu、Menu、Outside、Keyboard 等
    }).
    OnActionEvent(func(event ui.DropdownActionEvent) {
        // event.Item 是完整菜单项,event.Path 是从根菜单到当前项的 key 路径
    })

左右分离按钮可以直接使用 DropdownButton,无需手动拼接两个按钮:

ui.DropdownButton("create", ui.Button("create-action", ui.Text("Create")), items).
    OnClick(func() { send(Create{}) }).
    MenuStyle(menuStyle)

Dropdown 透传 Menu 的选择与数据能力,包括 OnCheckedChangeOnRadioChangeAutoSeparateSectionsCompactDataVersion。 动态菜单应在内容、分组或影响宽度的子控件变化时递增 DataVersion,这样展开面板 可以复用扁平化菜单数据和 AutoWidth 的测量结果;自定义前后内容可以使用 BeforeContent / AfterContent。 下拉项的行为可使用 DropdownItemActionDropdownItemCheckboxDropdownItemRadioDropdownItemSubmenu 指定,分组标题使用 DropdownGroupLabel

示例:examples/popoversexamples/dropdownsexamples/context_menusexamples/menubars

Tooltip

// 模式:包裹目标 + 提示内容(具体构造见 go doc / examples/tooltips)

示例:examples/tooltips

Toast

适合非阻塞反馈(保存成功、网络错误摘要)。通常由 Model 持有 toast 队列,或组件状态 API(见 examples/toasts)。

Portal(高级)

Portal 只提供:

  • 解析后的视口锚点
  • 叠放分组
  • 前帧输入所有权

不提供:定位策略、点外部关闭、动画、遮罩、焦点陷阱。 这些要用 Popover/Modal 等;只有现有组件不够时才用 Portal。

ui.Portal("custom-portal", model.Open, trigger, func(anchor image.Rectangle, interactive bool) ui.Widget {
    return panel(anchor, interactive, onClose)
})

示例:examples/custom_widgets

行为契约(应用作者需知)

  1. 策略输入可滞后一帧 谁处理 Escape / dismiss,可能相对「视觉最顶层」慢一帧。这是有意设计,避免首帧多层同时消费按键。

  2. 几何命中 vs 策略 指针命中可按视觉层;open/close 策略由 Overlay Host 统一协调。

  3. 嵌套 模态上的 Select/Popover freestyles 正常注册;焦点边界由 Host 维护,避免焦点逃出模态。

  4. 自定义布局 若用原始 Gio 变换放置 FlowUI 子树并需要锚点正确,使用 ui.TrackOverlayPlacement(见 architecture 文档)。日常 Row/Column/Box 已自动处理。

受控关闭示例

type Model struct {
    DialogOpen bool
}

// 打开
send(SetDialog(true))

// 确认按钮
OnClick(func() {
    send(Confirm{})
    // Update 里:m.DialogOpen = false
})

// 受控:先创建组件,再用 .Open(modelValue) 声明受控状态;
// 关闭请求经 OnOpenChange 回传。DefaultOpen 只用于非受控初始值。
ui.Modal("confirm", false, "标题", body).
    Open(m.DialogOpen).
    OnOpenChange(func(o bool) {
        send(SetDialog(o))
    })

下一步