Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

More control over menus #4031

Open
wants to merge 13 commits into
base: v3-alpha
Choose a base branch
from
1 change: 1 addition & 0 deletions docs/src/content/docs/changelog.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- New Menu guide by [@leaanthony](https://github.com/leaanthony)
- Add doc comments for Service API by [@fbbdev](https://github.com/fbbdev) in [#4024](https://github.com/wailsapp/wails/pull/4024)
- Add function `application.NewServiceWithOptions` to initialise services with additional configuration by [@leaanthony](https://github.com/leaanthony) in [#4024](https://github.com/wailsapp/wails/pull/4024)
- Add function `Menu.Clear` and `Menu.Prepend` for more menu control by [@FalcoG](https://github.com/FalcoG) in [#4031](https://github.com/wailsapp/wails/pull/4031)

### Fixed

Expand Down
59 changes: 59 additions & 0 deletions docs/src/content/docs/guides/menus.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,65 @@ submenu.Add("Open")
submenu.Add("Save")
```

#### Combining menus
A menu can be added into another menu by appending or prepending it.
```go
menu := application.NewMenu()
menu.Add("First Menu")

secondaryMenu := application.NewMenu()
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 := application.NewMenu()
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 := application.NewMenu()
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:
Expand Down
4 changes: 4 additions & 0 deletions v3/pkg/application/linux_cgo.go
Original file line number Diff line number Diff line change
Expand Up @@ -587,6 +587,10 @@ func menuItemNew(label string, bitmap []byte) pointer {
return menuItemAddProperties(C.gtk_menu_item_new(), label, bitmap)
}

func menuItemDestroy(widget pointer) {
C.gtk_widget_destroy((*C.GtkWidget)(widget))
}

func menuItemAddProperties(menuItem *C.GtkWidget, label string, bitmap []byte) pointer {
/*
// FIXME: Support accelerator configuration
Expand Down
18 changes: 18 additions & 0 deletions v3/pkg/application/menu.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,18 @@ func (m *Menu) Update() {
m.impl.update()
}

// Clear all menu items
func (m *Menu) Clear() {
m.items = nil
}

func (m *Menu) Destroy() {
for _, item := range m.items {
item.Destroy()
}
m.items = nil
}

func (m *Menu) AddSubmenu(s string) *Menu {
result := NewSubMenuItem(s)
m.items = append(m.items, result)
Expand Down Expand Up @@ -186,10 +198,16 @@ func (m *Menu) Clone() *Menu {
return result
}

// Insert menu after an existing menu
func (m *Menu) Append(in *Menu) {
m.items = append(m.items, in.items...)
}
leaanthony marked this conversation as resolved.
Show resolved Hide resolved

// Insert menu before an existing menu
func (m *Menu) Prepend(in *Menu) {
m.items = append(in.items, m.items...)
}
leaanthony marked this conversation as resolved.
Show resolved Hide resolved
leaanthony marked this conversation as resolved.
Show resolved Hide resolved

func (a *App) NewMenu() *Menu {
return &Menu{}
}
Expand Down
33 changes: 33 additions & 0 deletions v3/pkg/application/menuitem.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,12 @@ func getMenuItemByID(id uint) *MenuItem {
return menuItemMap[id]
}

func removeMenuItemByID(id uint) {
menuItemMapLock.Lock()
defer menuItemMapLock.Unlock()
delete(menuItemMap, id)
}

type menuItemImpl interface {
setTooltip(s string)
setLabel(s string)
Expand All @@ -41,6 +47,7 @@ type menuItemImpl interface {
setAccelerator(accelerator *accelerator)
setHidden(hidden bool)
setBitmap(bitmap []byte)
destroy()
}

type MenuItem struct {
Expand Down Expand Up @@ -425,3 +432,29 @@ func (m *MenuItem) Clone() *MenuItem {
}
return result
}

func (m *MenuItem) Destroy() {

removeMenuItemByID(m.id)

// Clean up resources
if m.impl != nil {
m.impl.destroy()
}
if m.submenu != nil {
m.submenu.Destroy()
m.submenu = nil
}

if m.contextMenuData != nil {
m.contextMenuData = nil
}

if m.accelerator != nil {
m.accelerator = nil
}

m.callback = nil
m.radioGroupMembers = nil

}
9 changes: 9 additions & 0 deletions v3/pkg/application/menuitem_darwin.go
Original file line number Diff line number Diff line change
Expand Up @@ -295,6 +295,11 @@ void setMenuItemBitmap(void* nsMenuItem, unsigned char *bitmap, int length) {
NSImage *image = [[NSImage alloc] initWithData:[NSData dataWithBytes:bitmap length:length]];
[menuItem setImage:image];
}

void destroyMenuItem(void* nsMenuItem) {
MenuItem *menuItem = (MenuItem *)nsMenuItem;
[menuItem release];
}
leaanthony marked this conversation as resolved.
Show resolved Hide resolved
*/
import "C"
import (
Expand Down Expand Up @@ -344,6 +349,10 @@ func (m macosMenuItem) setAccelerator(accelerator *accelerator) {
C.setMenuItemKeyEquivalent(m.nsMenuItem, key, modifier)
}

func (m macosMenuItem) destroy() {
C.destroyMenuItem(m.nsMenuItem)
}

func newMenuItemImpl(item *MenuItem) *macosMenuItem {
result := &macosMenuItem{
menuItem: item,
Expand Down
8 changes: 8 additions & 0 deletions v3/pkg/application/menuitem_linux.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,14 @@ func (l linuxMenuItem) setTooltip(tooltip string) {
})
}

func (l linuxMenuItem) destroy() {
InvokeSync(func() {
l.blockSignal()
defer l.unBlockSignal()
menuItemDestroy(l.native)
})
}

func (l linuxMenuItem) blockSignal() {
if l.handlerId != 0 {
menuItemSignalBlock(l.native, l.handlerId, true)
Expand Down
4 changes: 4 additions & 0 deletions v3/pkg/application/menuitem_windows.go
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,10 @@ func (m *windowsMenuItem) setChecked(checked bool) {
m.update()
}

func (m *windowsMenuItem) destroy() {
w32.RemoveMenu(m.hMenu, m.id, w32.MF_BYCOMMAND)
}

func (m *windowsMenuItem) setAccelerator(accelerator *accelerator) {
//// Set the keyboard shortcut of the menu item
//var modifier C.int
Expand Down