Navigation: review r3 fix

This commit is contained in:
ZacharyZhang-NY committed 2026-10-05 13:15:17 -04:00
1 parent 529aea6fa4
commit 7e4d862c12
4 files changed
+48 -1

No files matched your search

+1
View File
@@ -74,3 +74,4 @@ if id, ok := res.Chosen(); ok && id == "wrap" {
- 菜单打开期间,焦点保持在标题上,高亮项由组件管理。驱动菜单的按键注册在标题上,而 MyGo 的 active descendant 不公开,因此高亮变化时用 `c.Announce` 朗读当前项,并把它写入标题的 `Description`。 - 菜单打开期间,焦点保持在标题上,高亮项由组件管理。驱动菜单的按键注册在标题上,而 MyGo 的 active descendant 不公开,因此高亮变化时用 `c.Announce` 朗读当前项,并把它写入标题的 `Description`。
- 弹层打开时在 160 ms 内淡入,减少动画时直接显示。关闭时立即消失:弹层关闭后 MyGo 不再构建其元素,公开接口无法为已移除的浮层做淡出。 - 弹层打开时在 160 ms 内淡入,减少动画时直接显示。关闭时立即消失:弹层关闭后 MyGo 不再构建其元素,公开接口无法为已移除的浮层做淡出。
- 点击菜单内任意位置(包括禁用项)或用指针打开子菜单后,焦点回到所属标题,方向键、Enter、Escape 继续有效。 - 点击菜单内任意位置(包括禁用项)或用指针打开子菜单后,焦点回到所属标题,方向键、Enter、Escape 继续有效。
- 菜单打开时用 Tab 或 Shift+Tab 离开菜单栏,菜单关闭,焦点留在 Tab 目标上;焦点在菜单栏内任意位置时 Escape 关闭菜单。
+8
View File
@@ -105,9 +105,17 @@ func Menubar(c *ui.Context, menus []MenubarMenu, opts MenubarOptions) MenubarRes
titles[i] = t.Bounds() titles[i] = t.Bounds()
} }
}) })
if m.st.open >= 0 && bar.Shortcut(0, ui.KeyEscape) {
m.close()
}
if m.st.open >= 0 { if m.st.open >= 0 {
m.overlay(titles) m.overlay(titles)
} }
// Presses in the menu refocus the title, so lost focus means Tab left the menubar.
if m.st.open >= 0 && m.st.focusTo < 0 && !bar.FocusWithin() {
slog.Debug("mujica: menubar close on focus out")
m.st.open, m.st.hl = -1, nil
}
// Focus stays on the title, so the highlighted item is read out. // Focus stays on the title, so the highlighted item is read out.
if name := m.current(); m.moved && name != "" { if name := m.current(); m.moved && name != "" {
c.Announce(name) c.Announce(name)
+7 -1
View File
@@ -20,6 +20,9 @@ func (m *menubarRun) overlay(titles []ui.Rect) {
m.close() m.close()
return return
} }
if back.Pressed() {
m.focus(st.open)
}
// Title hotspots above the backdrop switch menus as the pointer moves. // Title hotspots above the backdrop switch menus as the pointer moves.
for i, r := range titles { for i, r := range titles {
hot := ui.Box(c).Absolute().Left(r.X).Top(r.Y).Size(r.W, r.H).Cursor(ui.CursorPointer) hot := ui.Box(c).Absolute().Left(r.X).Top(r.Y).Size(r.W, r.H).Cursor(ui.CursorPointer)
@@ -31,6 +34,9 @@ func (m *menubarRun) overlay(titles []ui.Rect) {
} }
return return
} }
if hot.Pressed() {
m.focus(st.open)
}
if hot.Hovered() && i != st.open { if hot.Hovered() && i != st.open {
m.openMenu(i, false) m.openMenu(i, false)
} }
@@ -76,7 +82,7 @@ func (m *menubarRun) panel(l int, x, y float32, parent ui.Rect) (*ui.Element, *u
y = max(4, wh-4-pb.H) y = max(4, wh-4-pb.H)
} }
panel.Left(max(4, x)).Top(y) panel.Left(max(4, x)).Top(y)
if panel.Clicked() { if panel.Clicked() || panel.Pressed() {
m.focus(st.open) m.focus(st.open)
} }
var anchor *ui.Element var anchor *ui.Element
+32
View File
@@ -178,6 +178,38 @@ func TestMenubarKeysAfterClickInMenu(t *testing.T) {
} }
} }
func TestMenubarTabOutClosesMenu(t *testing.T) {
for _, back := range []bool{false, true} {
a := &menubarApp{}
tt := ui.NewTester(func(c *ui.Context) {
Use(c, Settings{})
ui.Column(c).Fill().Children(func() {
Button(c, "Before", ButtonOptions{})
Menubar(c, a.menus(), MenubarOptions{})
Button(c, "After", ButtonOptions{})
})
}, 700, 500)
tt.Key(0, ui.KeyTab)
tt.Key(0, ui.KeyTab)
tt.Key(0, ui.KeyDown)
if !tt.HasText("Quit") {
t.Fatal("menu did not open")
}
mods, want := ui.Modifiers(0), "After"
if back {
mods, want = ui.Shift, "Before"
}
tt.Key(mods, ui.KeyTab)
if tt.HasText("Quit") || !tt.Focused(want) {
t.Fatalf("shift=%v: menu open %v, focus on %s %v", back, tt.HasText("Quit"), want, tt.Focused(want))
}
tt.Key(0, ui.KeyEscape)
if !tt.Focused(want) {
t.Fatalf("shift=%v: Escape moved focus off %s", back, want)
}
}
}
func TestMenubarLongMenuScrolls(t *testing.T) { func TestMenubarLongMenuScrolls(t *testing.T) {
var items []MenubarItem var items []MenubarItem
for i := range 30 { for i := range 30 {