MyGo
Menu button

Actions

Menu button

A button opening a menu below it, the system's or one MyGo draws in the window with the theme's look.

Menu button

ui.MenuButton creates a button showing a label and an arrow, which opens a menu of the system's below it, as the system's pop-up buttons do: as the pointer goes down on it, or for Enter, Space, Up or Down while it has the keyboard focus. The function builds the items when the menu opens, and runs again in the frame after one was chosen, where its Chosen reports it:

ui.MenuButton(c, "Sort by", func(m *ui.Menu) {
	for _, by := range []string{"Name", "Date", "Size"} {
		if m.Item(by).Checked(app.sort == by).Chosen() {
			app.sort = by
		}
	}
})

Items

Menus take the same items as context menus: Item, with Checked, Radio, Disabled and Shortcut, Separator and Submenu.

Any button

Element.Menu gives the same menu to any button, as one showing only an icon:

ui.Button(c, "").Label("More").Children(func() { ui.Icon(c, dots) }).Menu(func(m *ui.Menu) {
	if m.Item("Duplicate").Chosen() {
		app.duplicate()
	}
	if m.Item("Delete").Chosen() {
		app.delete()
	}
})

In a toolbar, a menu button takes no face of its own until hovered. For a button that does an action of its own beside a menu of related ones, as Save beside Save As…, use a split button.

Drawn in the window

ui.PopoverMenuButton takes the same function, and MyGo draws the menu in the window with the theme's look, as PopoverMenu draws a context menu, where the system would show its own: beside the popovers and buttons of an app's own design, the system's menu is the one that looks out of place, and a right-click's drawn menu and a ⋯ button's system menu, side by side, look like two menus. Element.PopoverMenuButton gives such a menu to any button, as Element.Menu does, and takes the same function as PopoverMenu, so one function serves both. To draw every menu of a window so, Menu's included, see every menu of a window:

menu := func(m *ui.Menu) {
	if m.Item("Rename").Shortcut(0, ui.KeyF2).Chosen() {
		app.renaming = i
	}
	if m.Item("Pinned").Checked(note.pinned).Chosen() {
		note.pinned = !note.pinned
	}
	m.Separator()
	if m.Item("Delete").Disabled(note.locked).Chosen() {
		app.delete(i)
	}
}
row.PopoverMenu(menu)
row.Children(func() {
	ui.Text(c, note.title).Grow(1)
	ui.Button(c, "").Label("More").Children(func() { ui.Icon(c, dots) }).PopoverMenuButton(menu)
})

It opens as the system's would, as the pointer goes down on the button, and for Enter, Space, Up or Down while the button has the focus: below the button, lined up with its start, or above it where the window has no room. Pressing the button again closes it. It has the keys, the focus and the look of PopoverMenu's: it takes the focus as it opens, on its first item when the keyboard opened it, and gives it back to the button as it closes; Up, Down, Home, End and typing move among its items, Right and Left open and close submenus, Enter and Space choose, and Escape, Tab and presses outside close it. A choice reports Chosen as from PopoverMenu.

ui.PopoverMenuButtonOptions, after the function, place the menu elsewhere and let the app open and close it. At and Self attach the menu as AttachTo does, its point Self on the button's point At: AnchorBottomEnd and AnchorTopEnd line it up with the button's end, for a ⋯ button at the window's end, AnchorTopStart and AnchorBottomStart put it above, and AnchorTopEnd and AnchorTopStart after it. It still goes to the other side where the window has no room. Open, a *bool, is whether the menu shows, as MenuBase's: the button sets it as the user opens and closes the menu, and the app sets it to open the menu, or to close it, as when what it acts on goes away in the background:

more.PopoverMenuButton(menu, ui.PopoverMenuButtonOptions{
	At: ui.AnchorBottomEnd, Self: ui.AnchorTopEnd, Open: &app.moreOpen,
})

// Later, on an event from elsewhere, before the window updates:
app.moreOpen = false

A menu the app opens takes the focus, with no item highlighted, and gives it back to the element that had it as it closes. A window shows one drawn menu at a time: another opening closes it, setting Open to false.

Panel styles the menu's panel, and each submenu's, after the theme's look, for a menu that wears an app's own surface rather than the theme's Background, Border and shadow. Its background, border, radius and padding take the theme's place; Shadow adds a shadow, so NoShadow first takes off the theme's. The menu keeps its place, its gap from the button and its items' look:

more.PopoverMenuButton(menu, ui.PopoverMenuButtonOptions{
	Panel: func(panel ui.Element) {
		panel.Background(app.menuColor).Border(1, app.line).Radius(12).
			NoShadow().Shadow(0, 18, 50, 0, ui.RGBA(0, 0, 0, 0.45))
	},
})

Assistive technology sees the menu button, expanded while its menu shows, and the menu with its menu items, check box items and radio items, as it sees the system's. In a toolbar's overflow menu, choosing the button opens its drawn menu below the overflow button.

A drawn menu's items are elements: in tests, tt.Click on the button opens it, tt.HasText finds its items and tt.Click chooses one. tt.Menu, tt.ChooseMenuItem and tt.CloseMenu work on it as on the system's, so a test passes with either.

Without a look

ui.MenuBase is a menu button without a look, for a menu whose items have a layout of your own, as an avatar and a name, or a swatch: its Trigger is the button, which takes children, and the menu shows while a *bool is true. Popup builds the menu, as PopoverBase builds a panel: style the panel, and build in it the items, each a row taking children, that Item, CheckItem, RadioItem and SubmenuItem make, and the submenus with Submenu. An item is Highlighted while the pointer or the keys are on it, and its Clicked reports its choice, which closes the menu:

m := ui.MenuBase(c, &app.accountMenu)
m.Trigger.Gap(6).Padding(4, 8).Radius(999).Children(func() {
	ui.Avatar(c, app.user.Name, app.user.Photo).Size(24, 24)
	ui.Icon(c, chevron)
})
m.Popup(func(panel ui.Element) {
	panel.Margin(4, 0, 0, 0).Padding(4).Radius(10).Background(t.Background).Border(1, t.Border)
	for _, acct := range app.accounts {
		item := m.RadioItem(acct == app.user).Gap(8).Padding(6, 10).Radius(6)
		if item.Highlighted() {
			item.Background(t.SurfaceHover)
		}
		item.Children(func() {
			ui.Avatar(c, acct.Name, acct.Photo).Size(20, 20)
			ui.Text(c, acct.Name)
		})
		if item.Clicked() {
			app.switchTo(acct)
		}
	}
	more := m.SubmenuItem().Padding(6, 10).Children(func() { ui.Text(c, "Settings") })
	m.Submenu(more, func(panel ui.Element) {
		panel.Padding(4).Radius(10).Background(t.Background).Border(1, t.Border)
		if m.Item().Padding(6, 10).Children(func() { ui.Text(c, "Profile") }).Clicked() {
			app.openProfile()
		}
	})
})

It has the keys and the focus of PopoverMenuButton's, which is built on the same parts with the theme's look: it opens as the pointer goes down on the button, and for Enter, Space, Up or Down while the button has the focus, on its first item then; Up, Down, Home, End and typing the first letters of an item move the highlight, past items Disabled; Right and Left open and close submenus, the other way round right to left; Enter and Space choose, Escape closes the submenu on top, and Tab and presses outside close the menu, which gives the focus back to the button. Setting the *bool opens and closes it too. Assistive technology sees the menu button, expanded while its menu shows, a menu of menu items, check box items and radio items, checked as CheckItem and RadioItem say, and each submenu named by its item. Separators and headings are elements of your own between the items. See custom widgets, and ContextMenuBase for a context menu.

An item that KeepOpen leaves the menu open as it is chosen, by the pointer, Enter or Space: its Clicked reports the choice, and it stays highlighted, as Radix's menus do when onSelect prevents the default. Group makes a row of items that the keys take as one: Up and Down stop at it once, on its item checked, else its first, and Left and Right move among its items, round, the other way round right to left. A theme switcher in an account menu is such a row of radio items:

m.Group(func(row ui.Element) {
	row.Gap(2).Padding(4).Label("Theme")
	for _, th := range []string{"Light", "Dark", "System"} {
		item := m.RadioItem(app.theme == th).KeepOpen().Grow(1).Padding(4, 8).Radius(6)
		if item.Highlighted() {
			item.Background(t.SurfaceHover)
		}
		item.Children(func() { ui.Text(c, th) })
		if item.Clicked() {
			app.theme = th
		}
	}
})

Assistive technology sees a group, named by its Label, holding radio items counted on their own (the items outside groups are counted together).

Accessibility

Assistive technology sees a menu button: AXMenuButton on macOS, a push button with a menu on Linux, and a button that expands on Windows.