MyGo
Menus and the tray

Guides

Menus and the tray

Application, context and Dock menus, keyboard shortcuts and tray icons.

Menus are built from templates of *mygo.MenuItem and used as the menu bar, context menus, the Dock menu and the menus of tray icons.

Templates

menu := mygo.NewMenu([]*mygo.MenuItem{
	{Role: mygo.RoleAppMenu},
	{Label: "File", Submenu: []*mygo.MenuItem{
		{Label: "New Note", Accelerator: "CmdOrCtrl+N", Click: func(item *mygo.MenuItem, win *mygo.Window) {
			newNote()
		}},
		{Label: "Export…", Accelerator: "CmdOrCtrl+E", Click: exportNotes},
		mygo.Separator(),
		{Role: mygo.RoleClose},
	}},
	{Role: mygo.RoleEditMenu},
	{Label: "View", Submenu: []*mygo.MenuItem{
		{ID: "sidebar", Label: "Show Sidebar", Type: mygo.MenuItemCheckbox, Checked: true, Click: toggleSidebar},
		mygo.Separator(),
		{Label: "List", Type: mygo.MenuItemRadio, Checked: true},
		{Label: "Grid", Type: mygo.MenuItemRadio},
		mygo.Separator(),
		{Role: mygo.RoleToggleDevTools},
		{Role: mygo.RoleToggleFullScreen},
	}},
	{Role: mygo.RoleWindowMenu},
})

A MenuItem has:

  • Label, and an Accelerator, its keyboard shortcut (see accelerators);
  • Click, called on the main thread when the item is chosen, with the item and the focused window, or nil;
  • Type: MenuItemNormal, MenuItemCheckbox, MenuItemRadio, MenuItemSeparator or MenuItemSubmenu; items with a Submenu are submenus, others normal items unless set. Adjacent radio items form a group, in which checking one unchecks the others. Checkbox and radio items toggle Checked before Click runs;
  • Disabled, Hidden and ToolTip;
  • ID, to find the item with Menu.ItemByID;
  • Role, a built-in behavior with its label and accelerator.

Roles

Roles do what users expect of standard items, with their usual label and shortcut, which Label and Accelerator override:

  • editing: RoleUndo, RoleRedo, RoleCut, RoleCopy, RolePaste, RoleDelete, RoleSelectAll, and on macOS RolePasteAndMatchStyle;
  • the page: RoleReload, RoleForceReload, RoleToggleDevTools, RoleResetZoom, RoleZoomIn, RoleZoomOut;
  • the window: RoleToggleFullScreen, RoleMinimize, RoleZoom, RoleClose;
  • the app: RoleQuit, and on macOS RoleAbout, RoleHide, RoleHideOthers, RoleUnhide, RoleFront, RoleServices, RoleStartSpeaking, RoleStopSpeaking;
  • whole menus: RoleFileMenu, RoleEditMenu, RoleViewMenu and RoleWindowMenu, and on macOS RoleAppMenu (the menu named after the app), RoleWindow (the menu listing the windows) and RoleHelp (the menu with the search field).

Items with the roles of macOS are left out on other platforms, so one template serves every platform.

Changing items

Change items of a menu in use with their methods, so that the native menu follows: SetLabel, SetEnabled, SetVisible, SetChecked and SetAccelerator:

item := menu.ItemByID("sidebar")
item.SetChecked(false)

Menu.Append and Menu.Insert add items.

The menu bar

mygo.App.SetMenu(menu)

On macOS the menu bar belongs to the app. Apps get a default one, with the app, File, Edit, View and Window menus, until they set their own: keep RoleAppMenu and RoleEditMenu in yours, which make Cmd+Q and copy and paste work.

On Linux and Windows menu bars belong to windows. App.SetMenu gives its menu to every window without one of its own, Window.SetMenu gives a window its own, and windows have none by default. App.SetMenu(nil) removes the menu.

A window with AutoHideMenuBar keeps its menu bar out of sight, and the room for the page. Alt pressed alone, or F10, shows the bar with the keyboard in it (Windows) or its first menu open (Linux), and the bar hides again once the user picks an item or leaves it. Its shortcuts work all along:

mygo.NewWindow(mygo.WindowOptions{URL: "/", AutoHideMenuBar: true})

A Windows window without a title bar, frameless or with a hidden title bar, has no room for a menu bar either: Alt and F10 open its menus in a popup from the top-left corner, below the title bar the page draws. On Linux the bar shows above the page.

Context menus

Menu.Popup shows a menu at the mouse over a window and returns once it closes; PopupAt shows it at a position in the page. Show one from a bound method, which the page calls on right-click:

// ShowNoteMenu shows the context menu of a note.
func (n *Notes) ShowNoteMenu(ctx context.Context, id int64) {
	menu := mygo.NewMenu([]*mygo.MenuItem{
		{Label: "Rename", Click: func(*mygo.MenuItem, *mygo.Window) { n.startRename(id) }},
		{Label: "Delete", Click: func(*mygo.MenuItem, *mygo.Window) { n.Delete(id) }},
	})
	menu.Popup(mygo.CallerWindow(ctx))
}
note.addEventListener("contextmenu", (e) => {
  e.preventDefault();
  Notes.showNoteMenu(id);
});

Without a handler of their own, pages show the webview's standard context menu, with Copy and Paste on text and Inspect Element in development builds.

The Dock menu

On macOS App.Dock.SetMenu adds items above the standard ones of the menu that right-clicking the Dock icon opens. Like App.SetMenu, it may be called in main before App.Run:

mygo.App.Dock.SetMenu(mygo.NewMenu([]*mygo.MenuItem{
	{Label: "New Window", Click: func(*mygo.MenuItem, *mygo.Window) { openMainWindow() }},
}))

Tray icons

A tray icon sits in the menu bar on macOS and in the notification area on Windows and Linux. Create one once the app is ready:

//go:embed resources/tray.png
var trayIcon []byte

mygo.App.WhenReady(func() {
	tray, err := mygo.NewTray(mygo.TrayOptions{
		Icon:           trayIcon,
		IconIsTemplate: true, // macOS tints it to match the menu bar
		ToolTip:        "Notes",
		Menu: mygo.NewMenu([]*mygo.MenuItem{
			{Label: "Open Notes", Click: func(*mygo.MenuItem, *mygo.Window) { openMainWindow() }},
			mygo.Separator(),
			{Role: mygo.RoleQuit},
		}),
	})
	if err != nil {
		log.Println("tray:", err)
	}
	_ = tray
})

The icon is a PNG, ideally 16×16 points (a 32×32 image for high-resolution displays); a template icon, black on transparent, lets macOS tint it for light and dark menu bars. A tray with a menu shows it on click. Without a menu, clicks reach OnClick, and OnRightClick gets right-clicks. Change the tray with SetIcon, SetMenu, SetToolTip and, on macOS, SetTitle, the text next to the icon; Destroy removes it.

On Linux tray icons use AppIndicator, which needs libayatana-appindicator3: they show a menu and report no clicks, and NewTray returns an error without the library.

Apps that live in the menu bar usually drop their Dock icon on macOS:

mygo.App.SetActivationPolicy(mygo.ActivationPolicyAccessory)

Accelerators

Accelerators are keys with modifiers joined by +, such as CmdOrCtrl+N or Alt+Shift+F4, in any case.

Modifiers:

Modifier Key
CmdOrCtrl (CommandOrControl) Command on macOS, Control elsewhere
Cmd (Command), Super, Meta, Win Command on macOS, the Windows or Super key elsewhere
Ctrl (Control) Control
Alt (Option, AltGr) Alt, or Option on macOS
Shift Shift

Keys:

  • a character: a letter, a digit or a sign such as , or / (Plus for +);
  • F1 to F24;
  • Enter (Return), Tab, Space, Backspace, Delete (Del), Insert (Ins), Escape (Esc);
  • Up, Down, Left, Right, Home, End, PageUp, PageDown;
  • Num0 to Num9, NumDec, NumAdd, NumSub, NumMult, NumDiv (the numeric keypad);
  • VolumeUp, VolumeDown, VolumeMute, MediaPlayPause, MediaNextTrack, MediaPreviousTrack, MediaStop, PrintScreen, CapsLock, NumLock, ScrollLock.

The same syntax registers global shortcuts.