562 lines
12 KiB
Text
562 lines
12 KiB
Text
---
|
|
title: Menu Reference
|
|
description: Complete reference for menu item types, properties, and methods
|
|
sidebar:
|
|
order: 4
|
|
---
|
|
|
|
import { Tabs, TabItem } from "@astrojs/starlight/components";
|
|
|
|
## Menu Reference
|
|
|
|
Complete reference for menu item types, properties, and dynamic behaviour. Build professional, responsive menus with checkboxes, radio groups, separators, and dynamic updates.
|
|
|
|
## Menu Item Types
|
|
|
|
### Regular Menu Items
|
|
|
|
The most common type—displays text and triggers an action:
|
|
|
|
```go
|
|
menuItem := menu.Add("Click Me")
|
|
menuItem.OnClick(func(ctx *application.Context) {
|
|
fmt.Println("Menu item clicked!")
|
|
})
|
|
```
|
|
|
|
**Use for:** Commands, actions, opening windows
|
|
|
|
### Checkboxes
|
|
|
|
Toggle-able menu items with checked/unchecked state:
|
|
|
|
```go
|
|
checkbox := menu.AddCheckbox("Enable Feature", true) // true = initially checked
|
|
checkbox.OnClick(func(ctx *application.Context) {
|
|
isChecked := ctx.ClickedMenuItem().Checked()
|
|
fmt.Printf("Feature is now: %v\n", isChecked)
|
|
})
|
|
```
|
|
|
|
**Use for:** Boolean settings, feature toggles, view options
|
|
|
|
**Important:** The checked state toggles automatically when clicked.
|
|
|
|
### Radio Groups
|
|
|
|
Mutually exclusive options—only one can be selected:
|
|
|
|
```go
|
|
menu.AddRadio("Small", true) // true = initially selected
|
|
menu.AddRadio("Medium", false)
|
|
menu.AddRadio("Large", false)
|
|
```
|
|
|
|
**Use for:** Mutually exclusive choices (size, theme, mode)
|
|
|
|
**How grouping works:**
|
|
- Adjacent radio items form a group automatically
|
|
- Selecting one deselects others in the group
|
|
- Separate groups with a separator or regular item
|
|
|
|
**Example with multiple groups:**
|
|
|
|
```go
|
|
// Group 1: Size
|
|
menu.AddRadio("Small", true)
|
|
menu.AddRadio("Medium", false)
|
|
menu.AddRadio("Large", false)
|
|
|
|
menu.AddSeparator()
|
|
|
|
// Group 2: Theme
|
|
menu.AddRadio("Light", true)
|
|
menu.AddRadio("Dark", false)
|
|
```
|
|
|
|
### Submenus
|
|
|
|
Nested menu structures for organisation:
|
|
|
|
```go
|
|
submenu := menu.AddSubmenu("More Options")
|
|
submenu.Add("Submenu Item 1").OnClick(func(ctx *application.Context) {
|
|
// Handle click
|
|
})
|
|
submenu.Add("Submenu Item 2")
|
|
```
|
|
|
|
**Use for:** Grouping related items, reducing clutter
|
|
|
|
**Nesting limit:** Most platforms support 2-3 levels. Avoid deeper nesting.
|
|
|
|
### Separators
|
|
|
|
Visual dividers between menu items:
|
|
|
|
```go
|
|
menu.Add("Item 1")
|
|
menu.AddSeparator()
|
|
menu.Add("Item 2")
|
|
```
|
|
|
|
**Use for:** Grouping related items visually
|
|
|
|
**Best practice:** Don't start or end menus with separators.
|
|
|
|
## Menu Item Properties
|
|
|
|
### Label
|
|
|
|
The text displayed for the menu item:
|
|
|
|
```go
|
|
menuItem := menu.Add("Initial Label")
|
|
menuItem.SetLabel("New Label")
|
|
|
|
// Get current label
|
|
label := menuItem.Label()
|
|
```
|
|
|
|
**Dynamic labels:**
|
|
|
|
```go
|
|
updateMenuItem := menu.Add("Check for Updates")
|
|
updateMenuItem.OnClick(func(ctx *application.Context) {
|
|
updateMenuItem.SetLabel("Checking...")
|
|
menu.Update() // Important on Windows!
|
|
|
|
// Perform update check
|
|
checkForUpdates()
|
|
|
|
updateMenuItem.SetLabel("Check for Updates")
|
|
menu.Update()
|
|
})
|
|
```
|
|
|
|
### Enabled State
|
|
|
|
Control whether the menu item can be interacted with:
|
|
|
|
```go
|
|
menuItem := menu.Add("Save")
|
|
menuItem.SetEnabled(false) // Greyed out, can't click
|
|
|
|
// Enable it later
|
|
menuItem.SetEnabled(true)
|
|
menu.Update() // Important: Call this after changing enabled state!
|
|
|
|
// Check current state
|
|
isEnabled := menuItem.Enabled()
|
|
```
|
|
|
|
:::caution[Windows Menu Behaviour]
|
|
On Windows, menus need to be reconstructed when their state changes. **Always call `menu.Update()` after enabling/disabling menu items**, especially if the item was created whilst disabled.
|
|
|
|
**Why:** Windows menus are rebuilt from scratch when updated. If you don't call `Update()`, click handlers won't fire properly.
|
|
:::
|
|
|
|
**Example: Dynamic enable/disable**
|
|
|
|
```go
|
|
var hasSelection bool
|
|
|
|
cutMenuItem := menu.Add("Cut")
|
|
cutMenuItem.SetEnabled(false) // Initially disabled
|
|
|
|
copyMenuItem := menu.Add("Copy")
|
|
copyMenuItem.SetEnabled(false)
|
|
|
|
// When selection changes
|
|
func onSelectionChanged(selected bool) {
|
|
hasSelection = selected
|
|
cutMenuItem.SetEnabled(hasSelection)
|
|
copyMenuItem.SetEnabled(hasSelection)
|
|
menu.Update() // Critical on Windows!
|
|
}
|
|
```
|
|
|
|
**Common pattern: Enable on condition**
|
|
|
|
```go
|
|
saveMenuItem := menu.Add("Save")
|
|
|
|
func updateSaveMenuItem() {
|
|
canSave := hasUnsavedChanges() && !isSaving()
|
|
saveMenuItem.SetEnabled(canSave)
|
|
menu.Update()
|
|
}
|
|
|
|
// Call whenever state changes
|
|
onDocumentChanged(func() {
|
|
updateSaveMenuItem()
|
|
})
|
|
```
|
|
|
|
### Checked State
|
|
|
|
For checkbox and radio items, control or query their checked state:
|
|
|
|
```go
|
|
checkbox := menu.AddCheckbox("Feature", false)
|
|
checkbox.SetChecked(true)
|
|
menu.Update()
|
|
|
|
// Query state
|
|
isChecked := checkbox.Checked()
|
|
```
|
|
|
|
**Auto-toggle:** Checkboxes toggle automatically when clicked. You don't need to call `SetChecked()` in the click handler.
|
|
|
|
**Manual control:**
|
|
|
|
```go
|
|
checkbox := menu.AddCheckbox("Auto-save", false)
|
|
|
|
// Sync with external state
|
|
func syncAutoSave(enabled bool) {
|
|
checkbox.SetChecked(enabled)
|
|
menu.Update()
|
|
}
|
|
```
|
|
|
|
### Accelerators (Keyboard Shortcuts)
|
|
|
|
Add keyboard shortcuts to menu items:
|
|
|
|
```go
|
|
saveMenuItem := menu.Add("Save")
|
|
saveMenuItem.SetAccelerator("CmdOrCtrl+S")
|
|
|
|
quitMenuItem := menu.Add("Quit")
|
|
quitMenuItem.SetAccelerator("CmdOrCtrl+Q")
|
|
```
|
|
|
|
**Accelerator format:**
|
|
- `CmdOrCtrl` - Cmd on macOS, Ctrl on Windows/Linux
|
|
- `Shift`, `Alt`, `Option` - Modifier keys
|
|
- `A-Z`, `0-9` - Letter/number keys
|
|
- `F1-F12` - Function keys
|
|
- `Enter`, `Space`, `Backspace`, etc. - Special keys
|
|
|
|
**Examples:**
|
|
|
|
```go
|
|
"CmdOrCtrl+S" // Save
|
|
"CmdOrCtrl+Shift+S" // Save As
|
|
"CmdOrCtrl+W" // Close Window
|
|
"CmdOrCtrl+Q" // Quit
|
|
"F5" // Refresh
|
|
"CmdOrCtrl+," // Preferences (macOS convention)
|
|
"Alt+F4" // Close (Windows convention)
|
|
```
|
|
|
|
**Platform-specific accelerators:**
|
|
|
|
```go
|
|
if runtime.GOOS == "darwin" {
|
|
prefsMenuItem.SetAccelerator("Cmd+,")
|
|
} else {
|
|
prefsMenuItem.SetAccelerator("Ctrl+P")
|
|
}
|
|
```
|
|
|
|
### Tooltip
|
|
|
|
Add hover text to menu items (platform support varies):
|
|
|
|
```go
|
|
menuItem := menu.Add("Advanced Options")
|
|
menuItem.SetTooltip("Configure advanced settings")
|
|
```
|
|
|
|
**Platform support:**
|
|
- **Windows:** ✅ Supported
|
|
- **macOS:** ❌ Not supported (tooltips not standard for menus)
|
|
- **Linux:** ⚠️ Varies by desktop environment
|
|
|
|
### Hidden State
|
|
|
|
Hide menu items without removing them:
|
|
|
|
```go
|
|
debugMenuItem := menu.Add("Debug Mode")
|
|
debugMenuItem.SetHidden(true) // Hidden
|
|
|
|
// Show in debug builds
|
|
if isDebugBuild {
|
|
debugMenuItem.SetHidden(false)
|
|
menu.Update()
|
|
}
|
|
```
|
|
|
|
**Use for:** Debug options, feature flags, conditional features
|
|
|
|
## Event Handling
|
|
|
|
### OnClick Handler
|
|
|
|
Execute code when menu item is clicked:
|
|
|
|
```go
|
|
menuItem := menu.Add("Click Me")
|
|
menuItem.OnClick(func(ctx *application.Context) {
|
|
// Handle click
|
|
fmt.Println("Clicked!")
|
|
})
|
|
```
|
|
|
|
**Context provides:**
|
|
- `ctx.ClickedMenuItem()` - The menu item that was clicked
|
|
- Window context (if from window menu)
|
|
- Application context
|
|
|
|
**Example: Access menu item in handler**
|
|
|
|
```go
|
|
checkbox := menu.AddCheckbox("Feature", false)
|
|
checkbox.OnClick(func(ctx *application.Context) {
|
|
item := ctx.ClickedMenuItem()
|
|
isChecked := item.Checked()
|
|
fmt.Printf("Feature is now: %v\n", isChecked)
|
|
})
|
|
```
|
|
|
|
### Multiple Handlers
|
|
|
|
You can set multiple handlers (last one wins):
|
|
|
|
```go
|
|
menuItem := menu.Add("Action")
|
|
menuItem.OnClick(func(ctx *application.Context) {
|
|
fmt.Println("First handler")
|
|
})
|
|
|
|
// This replaces the first handler
|
|
menuItem.OnClick(func(ctx *application.Context) {
|
|
fmt.Println("Second handler - this one runs")
|
|
})
|
|
```
|
|
|
|
**Best practice:** Set handler once, use conditional logic inside if needed.
|
|
|
|
## Dynamic Menus
|
|
|
|
### Updating Menu Items
|
|
|
|
**The golden rule:** Always call `menu.Update()` after changing menu state.
|
|
|
|
```go
|
|
// ✅ Correct
|
|
menuItem.SetEnabled(true)
|
|
menu.Update()
|
|
|
|
// ❌ Wrong (especially on Windows)
|
|
menuItem.SetEnabled(true)
|
|
// Forgot to call Update() - click handlers may not work!
|
|
```
|
|
|
|
**Why this matters:**
|
|
- **Windows:** Menus are reconstructed when updated
|
|
- **macOS/Linux:** Less critical but still recommended
|
|
- **Click handlers:** Won't fire properly without Update()
|
|
|
|
### Rebuilding Menus
|
|
|
|
For major changes, rebuild the entire menu:
|
|
|
|
```go
|
|
func rebuildFileMenu() {
|
|
menu := app.Menu.New()
|
|
|
|
menu.Add("New").OnClick(handleNew)
|
|
menu.Add("Open").OnClick(handleOpen)
|
|
|
|
if hasRecentFiles() {
|
|
recentMenu := menu.AddSubmenu("Open Recent")
|
|
for _, file := range getRecentFiles() {
|
|
recentMenu.Add(file).OnClick(func(ctx *application.Context) {
|
|
openFile(file)
|
|
})
|
|
}
|
|
}
|
|
|
|
menu.AddSeparator()
|
|
menu.Add("Quit").OnClick(handleQuit)
|
|
|
|
// Set the new menu
|
|
window.SetMenu(menu)
|
|
}
|
|
```
|
|
|
|
**When to rebuild:**
|
|
- Recent files list changes
|
|
- Plugin menus change
|
|
- Major state transitions
|
|
|
|
**When to update:**
|
|
- Enable/disable items
|
|
- Change labels
|
|
- Toggle checkboxes
|
|
|
|
### Context-Sensitive Menus
|
|
|
|
Adjust menus based on application state:
|
|
|
|
```go
|
|
func updateEditMenu() {
|
|
cutMenuItem.SetEnabled(hasSelection())
|
|
copyMenuItem.SetEnabled(hasSelection())
|
|
pasteMenuItem.SetEnabled(hasClipboardContent())
|
|
undoMenuItem.SetEnabled(canUndo())
|
|
redoMenuItem.SetEnabled(canRedo())
|
|
menu.Update()
|
|
}
|
|
|
|
// Call whenever state changes
|
|
onSelectionChanged(updateEditMenu)
|
|
onClipboardChanged(updateEditMenu)
|
|
onUndoStackChanged(updateEditMenu)
|
|
```
|
|
|
|
## Platform Differences
|
|
|
|
### Menu Bar Location
|
|
|
|
| Platform | Location | Notes |
|
|
|----------|----------|-------|
|
|
| **macOS** | Top of screen | Global menu bar |
|
|
| **Windows** | Top of window | Per-window menu |
|
|
| **Linux** | Top of window | Per-window (usually) |
|
|
|
|
### Standard Menus
|
|
|
|
**macOS:**
|
|
- Has "Application" menu (with app name)
|
|
- "Preferences" in Application menu
|
|
- "Quit" in Application menu
|
|
|
|
**Windows/Linux:**
|
|
- No Application menu
|
|
- "Preferences" in Edit or Tools menu
|
|
- "Exit" in File menu
|
|
|
|
**Example: Platform-appropriate structure**
|
|
|
|
```go
|
|
menu := app.Menu.New()
|
|
|
|
// macOS gets Application menu
|
|
if runtime.GOOS == "darwin" {
|
|
menu.AddRole(application.AppMenu)
|
|
}
|
|
|
|
// File menu
|
|
fileMenu := menu.AddSubmenu("File")
|
|
fileMenu.Add("New")
|
|
fileMenu.Add("Open")
|
|
|
|
// Preferences location varies
|
|
if runtime.GOOS == "darwin" {
|
|
// On macOS, preferences are in Application menu (added by AppMenu role)
|
|
} else {
|
|
// On Windows/Linux, add to Edit or Tools menu
|
|
editMenu := menu.AddSubmenu("Edit")
|
|
editMenu.Add("Preferences")
|
|
}
|
|
```
|
|
|
|
### Accelerator Conventions
|
|
|
|
**macOS:**
|
|
- `Cmd+` for most shortcuts
|
|
- `Cmd+,` for Preferences
|
|
- `Cmd+Q` for Quit
|
|
|
|
**Windows:**
|
|
- `Ctrl+` for most shortcuts
|
|
- `Ctrl+P` or `Ctrl+,` for Preferences
|
|
- `Alt+F4` for Exit (or `Ctrl+Q`)
|
|
|
|
**Linux:**
|
|
- Generally follows Windows conventions
|
|
- Desktop environment may override
|
|
|
|
## Best Practices
|
|
|
|
### ✅ Do
|
|
|
|
- **Call menu.Update()** after changing menu state (especially on Windows)
|
|
- **Use radio groups** for mutually exclusive options
|
|
- **Use checkboxes** for toggleable features
|
|
- **Add accelerators** to common actions
|
|
- **Group related items** with separators
|
|
- **Test on all platforms** - behaviour varies
|
|
|
|
### ❌ Don't
|
|
|
|
- **Don't forget menu.Update()** - Click handlers won't work properly
|
|
- **Don't nest too deeply** - 2-3 levels maximum
|
|
- **Don't start/end with separators** - Looks unprofessional
|
|
- **Don't use tooltips on macOS** - Not supported
|
|
- **Don't hardcode platform shortcuts** - Use `CmdOrCtrl`
|
|
|
|
## Troubleshooting
|
|
|
|
### Menu Items Not Responding
|
|
|
|
**Symptom:** Click handlers don't fire
|
|
|
|
**Cause:** Forgot to call `menu.Update()` after enabling item
|
|
|
|
**Solution:**
|
|
|
|
```go
|
|
menuItem.SetEnabled(true)
|
|
menu.Update() // Add this!
|
|
```
|
|
|
|
### Menu Items Greyed Out
|
|
|
|
**Symptom:** Can't click menu items
|
|
|
|
**Cause:** Items are disabled
|
|
|
|
**Solution:**
|
|
|
|
```go
|
|
menuItem.SetEnabled(true)
|
|
menu.Update()
|
|
```
|
|
|
|
### Accelerators Not Working
|
|
|
|
**Symptom:** Keyboard shortcuts don't trigger menu items
|
|
|
|
**Causes:**
|
|
1. Accelerator format incorrect
|
|
2. Conflict with system shortcuts
|
|
3. Window doesn't have focus
|
|
|
|
**Solution:**
|
|
|
|
```go
|
|
// Check format
|
|
menuItem.SetAccelerator("CmdOrCtrl+S") // ✅ Correct
|
|
menuItem.SetAccelerator("Ctrl+S") // ❌ Wrong (macOS uses Cmd)
|
|
|
|
// Avoid conflicts
|
|
// ❌ Cmd+H (Hide Window on macOS - system shortcut)
|
|
// ✅ Cmd+Shift+H (Custom shortcut)
|
|
```
|
|
|
|
## Next Steps
|
|
|
|
- [Application Menus](/features/menus/application) - Create application menu bars
|
|
- [Context Menus](/features/menus/context) - Right-click context menus
|
|
- [System Tray Menus](/features/menus/systray) - System tray/menu bar menus
|
|
- [Menu Patterns](/guides/patterns/menus) - Common menu patterns and best practices
|
|
|
|
---
|
|
|
|
**Questions?** Ask in [Discord](https://discord.gg/JDdSxwjhGf) or check the [menu examples](https://github.com/wailsapp/wails/tree/v3-alpha/v3/examples/menu).
|