跳转至

09 · 浮层与弹出

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

优先用高层组件

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

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

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

本节展示 API 用法片段,需与应用的 Model、Update 和 View 组合。

Popover / Dropdown / Menu

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

Popover 不自带触发器点击:触发器是普通控件,点击打开要在它自己的 OnClick 里改 open 状态。 Dropdown 与 Menu 相反,触发器的点击由组件接管。

trigger := button.New("帮助").
    OnClick(func() { send(SetHelpOpen(!m.HelpOpen)) })
popover.New(trigger, helpBody).Key("help").Open(m.HelpOpen).
    OnOpenChange(func(open bool) { send(SetHelpOpen(open)) })

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

dropdown.New("actions", trigger, items).
    TriggerMode(dropdown.TriggerHover)

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

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

dropdown.New("actions", trigger, items).
    TriggerMode(dropdown.TriggerContextMenu).
    AutoWidth().
    MinWidth(160).
    MaxWidth(320).
    Arrow(true)

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

dropdown.New("actions", trigger, items).
    TriggerMode(dropdown.TriggerHover).
    HoverOpenDelay(200 * time.Millisecond).
    HoverCloseDelay(120 * time.Millisecond)

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

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

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

dropdown.Button("create", button.New("Create"), items).
    OnClick(func() { send(Create{}) }).
    MenuStyle(menuStyle)

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

本节展示 API 用法片段,需与应用的 Model、Update 和 View 组合。

Tooltip

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

本节展示 API 用法片段,需与应用的 Model、Update 和 View 组合。

Toast

适合非阻塞反馈(保存成功、网络错误摘要)。通常由 Model 持有 toast 队列,或组件状态 API;本节展示 API 用法片段,需与应用的 Model、Update 和 View 组合。

Portal(高级)

Portal 只提供:

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

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

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

自定义控件的完整职责说明见 10-自定义组件。

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

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

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

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

  4. 自定义布局 若用原始 Gio 变换放置 FlowUI 子树并需要锚点正确,使用 layout.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 只用于非受控初始值。
modal.New("标题", body).Key("confirm").
    Open(m.DialogOpen).
    OnOpenChange(func(o bool) {
        send(SetDialog(o))
    })

下一步