外观
覆盖层
覆盖层显示在窗口内容之上:变暗窗口上的对话框、警告对话框、元素下方的弹出面板、选择框和组合框的弹窗、在窗口内绘制的菜单(PopoverMenu 和 PopoverMenuButton)、工具提示和提示条。它们在键盘处理和背后内容的处理上是共通的。
go
more := ui.Button(c, "More ▾")
if more.Clicked() {
app.menu = !app.menu
}
ui.Popover(c, more, &app.menu, func() {
if ui.Button(c, "Rename").Clicked() {
app.menu, app.renaming = false, true
}
})
ui.Modal(c, &app.renaming, func() {
ui.Text(c, "Rename").Bold()
if ui.TextInput(c, &app.name).AutoFocus().Submitted() {
app.renaming = false
}
})键盘
对话框会"留住"键盘:打开时夺取焦点,落在其中第一个可聚焦元素上(除非有元素主动要求焦点(AutoFocus)),Tab 在其元素间循环,窗口外部定义的快捷键会被搁置——因为对话框背后的内容处于不可交互状态,屏幕阅读器也看不到它们。弹出面板的元素在 Tab 移动时会跟随其锚点元素。
Escape 关闭最上层的覆盖层——选择框的弹窗先于它所在的对话框关闭——除非获得焦点的元素自己处理 Escape(终端就是如此);关闭时焦点仍在其中的覆盖层会把焦点交还给打开它时持有焦点的元素,比如它对应的按钮。
在弹出面板之外按下会关闭它,并且按下事件会继续传给指针下方的内容,就像 Web 的 popover 一样:点击另一个按钮会关闭弹出面板并按下该按钮。选择框的弹窗会吞下这次按下,就像系统的弹出菜单一样;对话框的背景遮罩被点击时会关闭对话框。
自定义覆盖层
ui.Overlay 在所有内容之上构建元素,用 Absolute 按窗口的 DIP 坐标定位:
go
ui.Overlay(c, func() {
ui.Text(c, "Offline").Absolute().Top(12).Right(12).
Padding(4, 10).Radius(999).Background(t.Danger).TextColor(t.AccentText)
})PopoverBase 和 DialogBase 是没有外观的弹出面板和对话框,供你自己设计覆盖层:见自定义控件。两者都不适合的覆盖层——如抽屉、悬停卡片或菜单栏的菜单——可以用它们的组成部分搭建:
AttachTo(target, at, self)把一个元素放到另一个元素旁边,无论目标在哪里:把该元素的self点对准目标的at点,例如AttachTo(button, ui.AnchorBottomLeft, ui.AnchorTopLeft)会把元素放在按钮下方。若它会超出窗口,就翻到另一侧,或沿目标反向展开,然后再移回窗口内;纵向可滚动的元素,其高度不超过目标较大一侧的剩余空间,并可以滚动。它的 margin 使其与目标保持间距。它成为目标的弹出面板:Tab 移动时,它的元素跟随目标。PressedOutside报告在元素、其目标以及其内部内容的弹出面板之外的按下——弹出面板正是因此关闭的。元素显示之前的按下不算"之外":由右键点击打开的覆盖层会保持打开,因为打开它的按下发生在同一帧。OverlayShortcut接收焦点元素及其周围元素未处理的按键,无论焦点在哪里;最后构建的覆盖层优先,例如用 Escape 关闭最上层的那个。Modal把一个元素变成对话框的背景遮罩:焦点移入其中并留在里面,它之外的窗口快捷键被搁置,屏幕阅读器只能看到它以及它之上的内容。
在 ui.Overlay 顶层构建的每个元素,若在其持有焦点时被移除,会把焦点交还给它出现时持有焦点的元素。右侧抽屉,通过 Escape 或点击其背景遮罩关闭:
go
if app.drawer {
ui.Overlay(c, func() {
back := ui.Row(c).Absolute().Left(0).Top(0).Right(0).Bottom(0).Justify(ui.End).
Background(ui.RGBA(0, 0, 0, 0.3)).Modal()
back.Children(func() {
panel := ui.Column(c).Width(320).FillHeight().Padding(16).Background(t.Background).
Role(ui.RoleDialog).Label("Filters").Children(app.filters)
if panel.PressedOutside() || back.OverlayShortcut(0, ui.KeyEscape) {
app.drawer = false
}
})
})
}原生对话框同样可用:在 goroutine 中调用它们,以免视图等待它们。