Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 5 additions & 4 deletions cmd/viva/templates/context_menu.go.txt
Original file line number Diff line number Diff line change
Expand Up @@ -68,9 +68,10 @@ type ContextMenuOptions struct {
// Submenu makes this the content of a submenu, which has a smaller minimum
// width (128) than a context menu (144).
Submenu bool
// RowHeight is the height of an item row. Zero is the standard 32px; a menu
// with many items can ask for a denser one (24, say). Group labels, separators
// and everything else keep their size. An icon never grows past the row.
// RowHeight is the height of an item row. Zero is the theme's control height
// (32 at the default density, 26 at the compact one); a menu with many items
// can ask for a denser one (24, say). Group labels, separators and everything
// else keep their size. An icon never grows past the row.
RowHeight float32
}

Expand Down Expand Up @@ -121,7 +122,7 @@ func ContextMenuItemWidth(canvas *gfx.Canvas, theme gfx.Theme, item ContextMenuI
}

// ContextMenuItemHeight is the height AddItem reserves for item. rowHeight is
// ContextMenuOptions.RowHeight (zero selects the standard 32px label row).
// ContextMenuOptions.RowHeight (zero selects the theme's control height).
func ContextMenuItemHeight(theme gfx.Theme, item ContextMenuItem, rowHeight float32) float32 {
return menuItemHeight(theme, item.common(), rowHeight)
}
Expand Down
22 changes: 18 additions & 4 deletions cmd/viva/templates/dropdown_menu.go.txt
Original file line number Diff line number Diff line change
Expand Up @@ -237,8 +237,8 @@ func BeginDropdownMenu(canvas *gfx.Canvas, theme gfx.Theme, bounds gfx.Rect, opt
// Bounds returns the normalized overlay geometry.
func (menu DropdownMenu) Bounds() gfx.Rect { return menu.surface.bounds }

// NextItemBounds returns the next 32px keyboard-navigation row without
// advancing the declaration cursor.
// NextItemBounds returns the next keyboard-navigation row, as tall as the
// theme's control height, without advancing the declaration cursor.
func (menu DropdownMenu) NextItemBounds() gfx.Rect {
return menu.surface.rowBounds(menu.surface.itemHeight())
}
Expand Down Expand Up @@ -606,6 +606,8 @@ func (scroll *MenuScroll) follow(canvas *gfx.Canvas) {
}

const (
// menuRowHeight is the height of an item row in a theme without a control
// height; a theme with one sizes its rows by it (menuThemeRowHeight).
menuRowHeight = float32(32)
menuLabelHeight = float32(28)
menuSeparatorHeight = float32(9)
Expand Down Expand Up @@ -675,7 +677,8 @@ type menuSurfaceOptions struct {
indicatorLeft bool
// tabCloses is MenuScroll.TabCloses for this surface.
tabCloses bool
// rowHeight is the height of an item row; zero is menuRowHeight.
// rowHeight is the height of an item row; zero is the theme's
// (menuThemeRowHeight).
rowHeight float32
}

Expand Down Expand Up @@ -833,6 +836,17 @@ func (menu *menuSurface) itemHeight() float32 {
if menu.options.rowHeight > 0 {
return menu.options.rowHeight
}
return menuThemeRowHeight(menu.theme)
}

// menuThemeRowHeight is the height of an item row in theme: its control
// height, so a compact theme packs its menus as densely as its buttons and
// inputs (32 at the default density, 26 at the compact one), and menuRowHeight
// for a theme without one.
func menuThemeRowHeight(theme gfx.Theme) float32 {
if height := theme.Metrics.ControlHeight; height > 0 {
return height
}
return menuRowHeight
}

Expand Down Expand Up @@ -932,7 +946,7 @@ func menuLineHeight(size float32) float32 { return size + 7 }

func menuItemHeight(theme gfx.Theme, item menuItem, rowHeight float32) float32 {
if rowHeight <= 0 {
rowHeight = menuRowHeight
rowHeight = menuThemeRowHeight(theme)
}
if item.Description != "" {
lines := menuLineHeight(theme.Metrics.FontSizeM) + menuDescriptionGap + menuLineHeight(theme.Metrics.FontSizeS)
Expand Down
9 changes: 5 additions & 4 deletions components/context_menu.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,9 +68,10 @@ type ContextMenuOptions struct {
// Submenu makes this the content of a submenu, which has a smaller minimum
// width (128) than a context menu (144).
Submenu bool
// RowHeight is the height of an item row. Zero is the standard 32px; a menu
// with many items can ask for a denser one (24, say). Group labels, separators
// and everything else keep their size. An icon never grows past the row.
// RowHeight is the height of an item row. Zero is the theme's control height
// (32 at the default density, 26 at the compact one); a menu with many items
// can ask for a denser one (24, say). Group labels, separators and everything
// else keep their size. An icon never grows past the row.
RowHeight float32
}

Expand Down Expand Up @@ -121,7 +122,7 @@ func ContextMenuItemWidth(canvas *gfx.Canvas, theme gfx.Theme, item ContextMenuI
}

// ContextMenuItemHeight is the height AddItem reserves for item. rowHeight is
// ContextMenuOptions.RowHeight (zero selects the standard 32px label row).
// ContextMenuOptions.RowHeight (zero selects the theme's control height).
func ContextMenuItemHeight(theme gfx.Theme, item ContextMenuItem, rowHeight float32) float32 {
return menuItemHeight(theme, item.common(), rowHeight)
}
Expand Down
43 changes: 43 additions & 0 deletions components/context_menu_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,49 @@ func TestContextMenuRowHeightOptionPacksItemsDenser(t *testing.T) {
}
}

func TestMenuRowsFollowTheThemeControlHeight(t *testing.T) {
compact := gfx.LightTheme()
compact.Metrics = compact.Metrics.WithDensity(gfx.DensityCompact)
unsized := gfx.LightTheme()
unsized.Metrics.ControlHeight = 0
for _, test := range []struct {
name string
theme gfx.Theme
rowHeight float32
want float32
}{
{"default density", gfx.LightTheme(), 0, 32},
{"compact density", compact, 0, 26},
{"RowHeight wins over the theme", compact, 24, 24},
{"theme without a control height", unsized, 0, menuRowHeight},
} {
t.Run(test.name, func(t *testing.T) {
var canvas gfx.Canvas
menu := BeginContextMenu(&canvas, test.theme, gfx.Rect{X: 10, Y: 20, Width: 160, Height: 400}, ContextMenuOptions{Layer: 1, Scope: gfx.Item(2), DismissID: gfx.Item(3), RowHeight: test.rowHeight})
item := ContextMenuItem{ID: gfx.Item(11), Label: "Copy Path"}
first := menu.AddItem(item)
label := menu.AddLabel("Group", false)
second := menu.AddItem(ContextMenuItem{ID: gfx.Item(12), Label: "Delete…"})
if first.Height != test.want || second.Height != test.want || menu.NextItemBounds().Height != test.want {
t.Fatalf("rows = %+v %+v next %+v, want %v high", first, second, menu.NextItemBounds(), test.want)
}
if label.Height != menuLabelHeight {
t.Fatalf("label = %+v, want %v high whatever the rows", label, menuLabelHeight)
}
if got := ContextMenuItemHeight(test.theme, item, test.rowHeight); got != test.want {
t.Fatalf("ContextMenuItemHeight = %v, want %v", got, test.want)
}
})
}

var canvas gfx.Canvas
dropdown := BeginDropdownMenu(&canvas, compact, gfx.Rect{X: 10, Y: 20, Width: 160, Height: 400}, DropdownMenuOptions{Layer: 1, Scope: gfx.Item(4), DismissID: gfx.Item(5)})
row := DropdownMenuItem{ID: gfx.Item(21), Label: "Hide"}
if bounds := dropdown.AddItem(row); bounds.Height != 26 || DropdownMenuItemHeight(compact, row) != 26 {
t.Fatalf("compact dropdown row = %+v, reserved %v, want 26", bounds, DropdownMenuItemHeight(compact, row))
}
}

func TestContextMenuItemWidthKeepsTheLabelWhole(t *testing.T) {
var canvas gfx.Canvas
theme := gfx.LightTheme()
Expand Down
22 changes: 18 additions & 4 deletions components/dropdown_menu.go
Original file line number Diff line number Diff line change
Expand Up @@ -237,8 +237,8 @@ func BeginDropdownMenu(canvas *gfx.Canvas, theme gfx.Theme, bounds gfx.Rect, opt
// Bounds returns the normalized overlay geometry.
func (menu DropdownMenu) Bounds() gfx.Rect { return menu.surface.bounds }

// NextItemBounds returns the next 32px keyboard-navigation row without
// advancing the declaration cursor.
// NextItemBounds returns the next keyboard-navigation row, as tall as the
// theme's control height, without advancing the declaration cursor.
func (menu DropdownMenu) NextItemBounds() gfx.Rect {
return menu.surface.rowBounds(menu.surface.itemHeight())
}
Expand Down Expand Up @@ -606,6 +606,8 @@ func (scroll *MenuScroll) follow(canvas *gfx.Canvas) {
}

const (
// menuRowHeight is the height of an item row in a theme without a control
// height; a theme with one sizes its rows by it (menuThemeRowHeight).
menuRowHeight = float32(32)
menuLabelHeight = float32(28)
menuSeparatorHeight = float32(9)
Expand Down Expand Up @@ -675,7 +677,8 @@ type menuSurfaceOptions struct {
indicatorLeft bool
// tabCloses is MenuScroll.TabCloses for this surface.
tabCloses bool
// rowHeight is the height of an item row; zero is menuRowHeight.
// rowHeight is the height of an item row; zero is the theme's
// (menuThemeRowHeight).
rowHeight float32
}

Expand Down Expand Up @@ -833,6 +836,17 @@ func (menu *menuSurface) itemHeight() float32 {
if menu.options.rowHeight > 0 {
return menu.options.rowHeight
}
return menuThemeRowHeight(menu.theme)
}

// menuThemeRowHeight is the height of an item row in theme: its control
// height, so a compact theme packs its menus as densely as its buttons and
// inputs (32 at the default density, 26 at the compact one), and menuRowHeight
// for a theme without one.
func menuThemeRowHeight(theme gfx.Theme) float32 {
if height := theme.Metrics.ControlHeight; height > 0 {
return height
}
return menuRowHeight
}

Expand Down Expand Up @@ -932,7 +946,7 @@ func menuLineHeight(size float32) float32 { return size + 7 }

func menuItemHeight(theme gfx.Theme, item menuItem, rowHeight float32) float32 {
if rowHeight <= 0 {
rowHeight = menuRowHeight
rowHeight = menuThemeRowHeight(theme)
}
if item.Description != "" {
lines := menuLineHeight(theme.Metrics.FontSizeM) + menuDescriptionGap + menuLineHeight(theme.Metrics.FontSizeS)
Expand Down
Loading