531 lines
16 KiB
Text
531 lines
16 KiB
Text
---
|
|
title: Menus
|
|
description: A guide to creating and customising menus in Wails v3
|
|
---
|
|
|
|
Wails v3 provides a powerful menu system that allows you to create both application menus and context menus. This guide will walk you through the various features and capabilities of the menu system.
|
|
|
|
### Creating a Menu
|
|
|
|
To create a new menu, use the `New()` method from the Menus manager:
|
|
|
|
```go
|
|
menu := app.Menu.New()
|
|
```
|
|
|
|
### Adding Menu Items
|
|
|
|
Wails supports several types of menu items, each serving a specific purpose:
|
|
|
|
#### Regular Menu Items
|
|
Regular menu items are the basic building blocks of menus. They display text and can trigger actions when clicked:
|
|
|
|
```go
|
|
menuItem := menu.Add("Click Me")
|
|
```
|
|
|
|
#### Checkboxes
|
|
Checkbox menu items provide a toggleable state, useful for enabling/disabling features or settings:
|
|
|
|
```go
|
|
checkbox := menu.AddCheckbox("My checkbox", true) // true = initially checked
|
|
```
|
|
|
|
#### Radio Groups
|
|
Radio groups allow users to select one option from a set of mutually exclusive choices. They are automatically created when radio items are placed next to each other:
|
|
|
|
```go
|
|
menu.AddRadio("Option 1", true) // true = initially selected
|
|
menu.AddRadio("Option 2", false)
|
|
menu.AddRadio("Option 3", false)
|
|
```
|
|
|
|
#### Separators
|
|
Separators are horizontal lines that help organise menu items into logical groups:
|
|
|
|
```go
|
|
menu.AddSeparator()
|
|
```
|
|
|
|
#### Submenus
|
|
Submenus are nested menus that appear when hovering over or clicking a menu item. They're useful for organizing complex menu structures:
|
|
|
|
```go
|
|
submenu := menu.AddSubmenu("File")
|
|
submenu.Add("Open")
|
|
submenu.Add("Save")
|
|
```
|
|
|
|
#### Combining menus
|
|
A menu can be added into another menu by appending or prepending it.
|
|
```go
|
|
menu := app.Menu.New()
|
|
menu.Add("First Menu")
|
|
|
|
secondaryMenu := app.Menu.New()
|
|
secondaryMenu.Add("Second Menu")
|
|
|
|
// insert 'secondaryMenu' after 'menu'
|
|
menu.Append(secondaryMenu)
|
|
|
|
// insert 'secondaryMenu' before 'menu'
|
|
menu.Prepend(secondaryMenu)
|
|
|
|
// update the menu
|
|
menu.Update()
|
|
```
|
|
|
|
:::note
|
|
By default, `prepend` and `append` will share state with the original menu. If you want to create a new menu with its own state,
|
|
you can call `.Clone()` on the menu.
|
|
|
|
E.g: `menu.Append(secondaryMenu.Clone())`
|
|
:::
|
|
|
|
#### Clearing a menu
|
|
In some cases it'll be better to construct a whole new menu if you are working with a variable amount of menu items.
|
|
|
|
This will clear all items on an existing menu and allows you to add items again.
|
|
|
|
```go
|
|
menu := app.Menu.New()
|
|
menu.Add("Waiting for update...")
|
|
|
|
// after certain logic, the menu has to be updated
|
|
menu.Clear()
|
|
menu.Add("Update complete!")
|
|
menu.Update()
|
|
```
|
|
|
|
:::note
|
|
Clearing a menu simply clears the menu items at the top level. Whilst Submenus won't be visible, they will still occupy memory
|
|
so be sure to manage your menus carefully.
|
|
:::
|
|
|
|
#### Destroying a menu
|
|
|
|
If you want to clear and release a menu, use the `Destroy()` method:
|
|
|
|
```go
|
|
menu := app.Menu.New()
|
|
menu.Add("Waiting for update...")
|
|
|
|
// after certain logic, the menu has to be destroyed
|
|
menu.Destroy()
|
|
```
|
|
|
|
|
|
### Menu Item Properties
|
|
|
|
Menu items have several properties that can be configured:
|
|
|
|
| Property | Method | Description |
|
|
|-------------|--------------------------|-----------------------------------------------------|
|
|
| Label | `SetLabel(string)` | Sets the display text |
|
|
| Enabled | `SetEnabled(bool)` | Enables/disables the item |
|
|
| Checked | `SetChecked(bool)` | Sets the checked state (for checkboxes/radio items) |
|
|
| Tooltip | `SetTooltip(string)` | Sets the tooltip text |
|
|
| Hidden | `SetHidden(bool)` | Shows/hides the item |
|
|
| Accelerator | `SetAccelerator(string)` | Sets the keyboard shortcut |
|
|
|
|
### Menu Item States
|
|
|
|
Menu items can be in different states that control their visibility and interactivity:
|
|
|
|
#### Visibility
|
|
|
|
Menu items can be shown or hidden dynamically using the `SetHidden()` method:
|
|
|
|
```go
|
|
menuItem := menu.Add("Dynamic Item")
|
|
|
|
// Hide the menu item
|
|
menuItem.SetHidden(true)
|
|
|
|
// Show the menu item
|
|
menuItem.SetHidden(false)
|
|
|
|
// Check current visibility
|
|
isHidden := menuItem.Hidden()
|
|
```
|
|
|
|
Hidden menu items are completely removed from the menu until shown again. This is useful for contextual menu items that should only appear in certain application states.
|
|
|
|
#### Enabled State
|
|
|
|
Menu items can be enabled or disabled using the `SetEnabled()` method:
|
|
|
|
```go
|
|
menuItem := menu.Add("Save")
|
|
|
|
// Disable the menu item
|
|
menuItem.SetEnabled(false) // Item appears grayed out and cannot be clicked
|
|
|
|
// Enable the menu item
|
|
menuItem.SetEnabled(true) // Item becomes clickable again
|
|
|
|
// Check current enabled state
|
|
isEnabled := menuItem.Enabled()
|
|
```
|
|
|
|
Disabled menu items remain visible but appear grayed out and cannot be clicked. This is commonly used to indicate that an action is currently unavailable, such as:
|
|
- Disabling "Save" when there are no changes to save
|
|
- Disabling "Copy" when nothing is selected
|
|
- Disabling "Undo" when there's no action to undo
|
|
|
|
#### Dynamic State Management
|
|
|
|
You can combine these states with event handlers to create dynamic menus:
|
|
|
|
```go
|
|
saveMenuItem := menu.Add("Save")
|
|
|
|
// Initially disable the Save menu item
|
|
saveMenuItem.SetEnabled(false)
|
|
|
|
// Enable Save only when there are unsaved changes
|
|
documentChanged := func() {
|
|
saveMenuItem.SetEnabled(true)
|
|
menu.Update() // Remember to update the menu after changing states
|
|
}
|
|
|
|
// Disable Save after saving
|
|
documentSaved := func() {
|
|
saveMenuItem.SetEnabled(false)
|
|
menu.Update()
|
|
}
|
|
```
|
|
|
|
### Event Handling
|
|
|
|
Menu items can respond to click events using the `OnClick` method:
|
|
|
|
```go
|
|
menuItem.OnClick(func(ctx *application.Context) {
|
|
// Handle the click event
|
|
println("Menu item clicked!")
|
|
})
|
|
```
|
|
|
|
The context provides information about the clicked menu item:
|
|
|
|
```go
|
|
menuItem.OnClick(func(ctx *application.Context) {
|
|
// Get the clicked menu item
|
|
clickedItem := ctx.ClickedMenuItem()
|
|
// Get its current state
|
|
isChecked := clickedItem.Checked()
|
|
})
|
|
```
|
|
|
|
### Role-Based Menu Items
|
|
|
|
Wails provides a set of predefined menu roles that automatically create menu items with standard functionality. Here are the supported menu roles:
|
|
|
|
#### Complete Menu Structures
|
|
These roles create entire menu structures with common functionality:
|
|
|
|
| Role | Description | Platform Notes |
|
|
|------|-------------|----------------|
|
|
| `AppMenu` | Application menu with About, Services, Hide/Show, and Quit | macOS only |
|
|
| `EditMenu` | Standard Edit menu with Undo, Redo, Cut, Copy, Paste, etc. | All platforms |
|
|
| `ViewMenu` | View menu with Reload, Zoom, and Fullscreen controls | All platforms |
|
|
| `WindowMenu` | Window controls (Minimise, Zoom, etc.) | All platforms |
|
|
| `HelpMenu` | Help menu with "Learn More" link to Wails website | All platforms |
|
|
|
|
#### Individual Menu Items
|
|
These roles can be used to add individual menu items:
|
|
|
|
| Role | Description | Platform Notes |
|
|
|------|-------------|----------------|
|
|
| `About` | Show application About dialog | All platforms |
|
|
| `Hide` | Hide application | macOS only |
|
|
| `HideOthers` | Hide other applications | macOS only |
|
|
| `UnHide` | Show hidden application | macOS only |
|
|
| `CloseWindow` | Close current window | All platforms |
|
|
| `Minimise` | Minimise window | All platforms |
|
|
| `Zoom` | Zoom window | macOS only |
|
|
| `Front` | Bring window to front | macOS only |
|
|
| `Quit` | Quit application | All platforms |
|
|
| `Undo` | Undo last action | All platforms |
|
|
| `Redo` | Redo last action | All platforms |
|
|
| `Cut` | Cut selection | All platforms |
|
|
| `Copy` | Copy selection | All platforms |
|
|
| `Paste` | Paste from clipboard | All platforms |
|
|
| `PasteAndMatchStyle` | Paste and match style | macOS only |
|
|
| `SelectAll` | Select all | All platforms |
|
|
| `Delete` | Delete selection | All platforms |
|
|
| `Reload` | Reload current page | All platforms |
|
|
| `ForceReload` | Force reload current page | All platforms |
|
|
| `ToggleFullscreen` | Toggle fullscreen mode | All platforms |
|
|
| `ResetZoom` | Reset zoom level | All platforms |
|
|
| `ZoomIn` | Increase zoom | All platforms |
|
|
| `ZoomOut` | Decrease zoom | All platforms |
|
|
|
|
Here's an example showing how to use both complete menus and individual roles:
|
|
|
|
```go
|
|
menu := app.Menu.New()
|
|
|
|
// Add complete menu structures
|
|
menu.AddRole(application.AppMenu) // macOS only
|
|
menu.AddRole(application.EditMenu) // Common edit operations
|
|
menu.AddRole(application.ViewMenu) // View controls
|
|
menu.AddRole(application.WindowMenu) // Window controls
|
|
|
|
// Add individual role-based items to a custom menu
|
|
fileMenu := menu.AddSubmenu("File")
|
|
fileMenu.AddRole(application.CloseWindow)
|
|
fileMenu.AddSeparator()
|
|
fileMenu.AddRole(application.Quit)
|
|
```
|
|
|
|
## Application Menus
|
|
|
|
Application menus are the menus that appear at the top of your application window (Windows/Linux) or at the top of the screen (macOS).
|
|
|
|
|
|
### Application Menu Behaviour
|
|
|
|
When you set an application menu using `app.Menu.Set()`, it becomes the main menu on macOS.
|
|
Menus are set on a per-window basis for Windows/Linux.
|
|
|
|
```go
|
|
app.Window.NewWithOptions(application.WebviewWindowOptions{
|
|
Title: "Custom Menu Window",
|
|
Windows: application.WindowsWindow{
|
|
Menu: customMenu, // Override application menu for this window
|
|
},
|
|
})
|
|
```
|
|
|
|
Here's a complete example showing these different menu behaviours:
|
|
|
|
```go
|
|
func main() {
|
|
app := application.New(application.Options{})
|
|
|
|
// Create application menu
|
|
appMenu := app.Menu.New()
|
|
fileMenu := appMenu.AddSubmenu("File")
|
|
fileMenu.Add("New").OnClick(func(ctx *application.Context) {
|
|
// This will be available in all windows unless overridden
|
|
window := app.Windows.Current()
|
|
window.SetTitle("New Window")
|
|
})
|
|
|
|
// Set as application menu - this is for macOS
|
|
app.Menu.Set(appMenu)
|
|
|
|
// Window with custom menu on Windows
|
|
customMenu := app.Menu.New()
|
|
customMenu.Add("Custom Action")
|
|
app.Window.NewWithOptions(application.WebviewWindowOptions{
|
|
Title: "Custom Menu",
|
|
Windows: application.WindowsWindow{
|
|
Menu: customMenu,
|
|
},
|
|
})
|
|
|
|
app.Run()
|
|
}
|
|
```
|
|
|
|
## Context Menus
|
|
|
|
Context menus are popup menus that appear when right-clicking elements in your application. They provide quick access to relevant actions for the clicked element.
|
|
|
|
### Default Context Menu
|
|
|
|
The default context menu is the webview's built-in context menu that provides system-level operations such as:
|
|
- Copy, Cut, and Paste for text manipulation
|
|
- Text selection controls
|
|
- Spell checking options
|
|
|
|
#### Controlling the Default Context Menu
|
|
|
|
You can control when the default context menu appears using the `--default-contextmenu` CSS property:
|
|
|
|
```html
|
|
<!-- Always show default context menu -->
|
|
<div style="--default-contextmenu: show">
|
|
<input type="text" placeholder="Right-click for text operations"/>
|
|
<textarea>Standard text operations available here</textarea>
|
|
</div>
|
|
|
|
<!-- Hide default context menu -->
|
|
<div style="--default-contextmenu: hide">
|
|
<div class="custom-component">Custom context menu only</div>
|
|
</div>
|
|
|
|
<!-- Smart context menu behaviour (default) -->
|
|
<div style="--default-contextmenu: auto">
|
|
<!-- Shows default menu when text is selected or in input fields -->
|
|
<p>Select this text to see the default menu</p>
|
|
<input type="text" placeholder="Default menu for input operations"/>
|
|
</div>
|
|
```
|
|
|
|
:::note
|
|
This feature will only work as expected after the runtime [has been initialised](../../learn/runtime#initialisation).
|
|
:::
|
|
|
|
#### Nested Context Menu Behavior
|
|
|
|
When using the `--default-contextmenu` property on nested elements, the following rules apply:
|
|
|
|
1. Child elements inherit their parent's context menu setting unless explicitly overridden
|
|
2. The most specific (closest) setting takes precedence
|
|
3. The `auto` value can be used to reset to default behaviour
|
|
|
|
Example of nested context menu behaviour:
|
|
|
|
```html
|
|
<!-- Parent sets hide -->
|
|
<div style="--default-contextmenu: hide">
|
|
<!-- This inherits hide -->
|
|
<p>No context menu here</p>
|
|
|
|
<!-- This overrides to show -->
|
|
<div style="--default-contextmenu: show">
|
|
<p>Context menu shown here</p>
|
|
|
|
<!-- This inherits show -->
|
|
<span>Also has context menu</span>
|
|
|
|
<!-- This resets to automatic behaviour -->
|
|
<div style="--default-contextmenu: auto">
|
|
<p>Shows menu only when text is selected</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
```
|
|
|
|
### Custom Context Menus
|
|
|
|
Custom context menus allow you to provide application-specific actions that are relevant to the element being clicked. They're particularly useful for:
|
|
- File operations in a document manager
|
|
- Image manipulation tools
|
|
- Custom actions in a data grid
|
|
- Component-specific operations
|
|
|
|
#### Creating a Custom Context Menu
|
|
|
|
When creating a custom context menu, you provide a unique identifier (name) that links the menu to HTML elements:
|
|
|
|
```go
|
|
// Create a context menu with identifier "imageMenu"
|
|
contextMenu := application.NewContextMenu()
|
|
app.ContextMenus.Add("imageMenu", contextMenu)
|
|
```
|
|
|
|
The name parameter ("imageMenu" in this example) serves as a unique identifier that will be used to:
|
|
1. Link HTML elements to this specific context menu
|
|
2. Identify which menu should be shown when right-clicking
|
|
3. Allow menu updates and cleanup
|
|
|
|
#### Context Data
|
|
|
|
When handling context menu events, you can access both the clicked menu item and its associated context data:
|
|
|
|
```go
|
|
contextMenu.Add("Process").OnClick(func(ctx *application.Context) {
|
|
// Get the clicked menu item
|
|
menuItem := ctx.ClickedMenuItem()
|
|
|
|
// Get the context data as a string
|
|
contextData := ctx.ContextMenuData()
|
|
|
|
// Check if the menu item is checked (for checkbox/radio items)
|
|
isChecked := ctx.IsChecked()
|
|
|
|
// Use the data
|
|
if contextData != "" {
|
|
processItem(contextData)
|
|
}
|
|
})
|
|
```
|
|
|
|
The context data is passed from the HTML element's `--custom-contextmenu-data` property and is available in the click handler through `ctx.ContextMenuData()`. This is particularly useful when:
|
|
|
|
- Working with lists or grids where each item needs unique identification
|
|
- Handling operations on specific components or elements
|
|
- Passing state or metadata from the frontend to the backend
|
|
|
|
#### Context Menu Management
|
|
|
|
After making changes to a context menu, call the `Update()` method to apply the changes:
|
|
|
|
```go
|
|
contextMenu.Update()
|
|
```
|
|
|
|
When you no longer need a context menu, you can destroy it:
|
|
|
|
```go
|
|
contextMenu.Destroy()
|
|
```
|
|
:::danger[Warning]
|
|
After calling `Destroy()`, using the context menu reference again will result in a panic.
|
|
:::
|
|
|
|
### Real-World Example: Image Gallery
|
|
|
|
Here's a complete example of implementing a custom context menu for an image gallery:
|
|
|
|
```go
|
|
// Backend: Create the context menu
|
|
imageMenu := application.NewContextMenu()
|
|
app.ContextMenus.Add("imageMenu", imageMenu)
|
|
|
|
// Add relevant operations
|
|
imageMenu.Add("View Full Size").OnClick(func(ctx *application.Context) {
|
|
// Get the image ID from context data
|
|
if imageID := ctx.ContextMenuData(); imageID != "" {
|
|
openFullSizeImage(imageID)
|
|
}
|
|
})
|
|
|
|
imageMenu.Add("Download").OnClick(func(ctx *application.Context) {
|
|
if imageID := ctx.ContextMenuData(); imageID != "" {
|
|
downloadImage(imageID)
|
|
}
|
|
})
|
|
|
|
imageMenu.Add("Share").OnClick(func(ctx *application.Context) {
|
|
if imageID := ctx.ContextMenuData(); imageID != "" {
|
|
showShareDialog(imageID)
|
|
}
|
|
})
|
|
```
|
|
|
|
```html
|
|
<!-- Frontend: Image gallery implementation -->
|
|
<div class="gallery">
|
|
<!-- Each image container with context menu -->
|
|
<div class="image-container"
|
|
style="--custom-contextmenu: imageMenu; --custom-contextmenu-data: img_123">
|
|
<img src="/images/img_123.jpg" alt="Gallery Image"/>
|
|
<span class="caption">Nature Photo</span>
|
|
</div>
|
|
|
|
<div class="image-container"
|
|
style="--custom-contextmenu: imageMenu; --custom-contextmenu-data: img_124">
|
|
<img src="/images/img_124.jpg" alt="Gallery Image"/>
|
|
<span class="caption">City Photo</span>
|
|
</div>
|
|
</div>
|
|
```
|
|
|
|
In this example:
|
|
1. The context menu is created with the identifier "imageMenu"
|
|
2. Each image container is linked to the menu using `--custom-contextmenu: imageMenu`
|
|
3. Each container provides its image ID as context data using `--custom-contextmenu-data`
|
|
4. The backend receives the image ID in click handlers and can perform specific operations
|
|
5. The same menu is reused for all images, but the context data tells us which image to operate on
|
|
|
|
This pattern is particularly powerful for:
|
|
- Data grids where rows need specific operations
|
|
- File managers where files need context-specific actions
|
|
- Design tools where different elements need different operations
|
|
- Any component where the same operations apply to multiple instances
|