09 · 浮层与弹出¶
FlowUI 用每窗口的 Overlay Host 管理弹层:主树布局时注册,再在视口坐标绘制。
优先用高层组件¶
| 需求 | 优先 API |
|---|---|
| 确认 / 危险操作 | AlertDialog / Modal |
| 锚定在按钮旁的面板 | Popover |
| 悬停说明 | Tooltip |
| 菜单 | Menu / Menubar / Dropdown / ContextMenu |
| 短暂通知 | Toast |
| 完全自定义挂载 | Portal(底层) |
Modal¶
典型:受控 Open + 内容 + 关闭消息。
ui.Modal("settings", false, "设置", ui.Text("设置内容")).
Open(m.ShowSettings).
OnOpenChange(func(open bool) { send(SetSettingsOpen(open)) })
// 内容、标题、操作按钮按组件 API 配置
示例:examples/modals、examples/alert_dialogs。
Popover / Dropdown / Menu¶
- 锚定控件 + 面板内容
- open 遵循 07 的 Open 契约
- 点击外部 / Escape 关闭由组件策略处理
Dropdown 默认使用点击触发,也可以按平台交互改为长按或悬停:
悬停模式使用短暂的进入/离开延迟,指针从触发器移动到菜单面板时不会立即关闭。
下拉菜单也支持右键触发、弹层箭头和尺寸约束:
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 的选择与数据能力,包括 OnCheckedChange、
OnRadioChange、AutoSeparateSections、Compact 和 DataVersion。
动态菜单应在内容、分组或影响宽度的子控件变化时递增 DataVersion,这样展开面板
可以复用扁平化菜单数据和 AutoWidth 的测量结果;自定义前后内容可以使用
BeforeContent / AfterContent。
下拉项的行为可使用 DropdownItemAction、DropdownItemCheckbox、
DropdownItemRadio 和 DropdownItemSubmenu 指定,分组标题使用
DropdownGroupLabel。
示例:examples/popovers、examples/dropdowns、examples/context_menus、examples/menubars。
Tooltip¶
示例: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。
行为契约(应用作者需知)¶
-
策略输入可滞后一帧 谁处理 Escape / dismiss,可能相对「视觉最顶层」慢一帧。这是有意设计,避免首帧多层同时消费按键。
-
几何命中 vs 策略 指针命中可按视觉层;open/close 策略由 Overlay Host 统一协调。
-
嵌套 模态上的 Select/Popover freestyles 正常注册;焦点边界由 Host 维护,避免焦点逃出模态。
-
自定义布局 若用原始 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))
})