From d7dec9eda716c97cc65faab9dd8d71d391e26a07 Mon Sep 17 00:00:00 2001 From: Ian VanSchooten Date: Wed, 5 Mar 2025 14:36:43 -0500 Subject: [PATCH 01/14] Fix registration of hidden menuItem [mac] --- v3/examples/menu/main.go | 6 ++++++ v3/pkg/application/menu_darwin.go | 6 +++--- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/v3/examples/menu/main.go b/v3/examples/menu/main.go index 62bcb454b..1809fce0c 100644 --- a/v3/examples/menu/main.go +++ b/v3/examples/menu/main.go @@ -44,6 +44,12 @@ func main() { // Let's make a "Demo" menu myMenu := menu.AddSubmenu("Demo") + // Hidden menu item that can be unhidden + hidden := myMenu.Add("I was hidden").SetHidden(true) + myMenu.Add("Toggle hidden menu").OnClick(func(ctx *application.Context) { + hidden.SetHidden(!hidden.Hidden()) + }) + // Disabled menu item myMenu.Add("Not Enabled").SetEnabled(false) diff --git a/v3/pkg/application/menu_darwin.go b/v3/pkg/application/menu_darwin.go index 1e3fc7197..a17031489 100644 --- a/v3/pkg/application/menu_darwin.go +++ b/v3/pkg/application/menu_darwin.go @@ -84,9 +84,6 @@ func (m *macosMenu) update() { func (m *macosMenu) processMenu(parent unsafe.Pointer, menu *Menu) { for _, item := range menu.items { - if item.hidden { - continue - } switch item.itemType { case submenu: submenu := item.submenu @@ -102,6 +99,9 @@ func (m *macosMenu) processMenu(parent unsafe.Pointer, menu *Menu) { case text, checkbox, radio: menuItem := newMenuItemImpl(item) item.impl = menuItem + if item.hidden { + menuItem.setHidden(true) + } C.addMenuItem(parent, menuItem.nsMenuItem) case separator: C.addMenuSeparator(parent) From 39cdd1d1e437f2301c8287e42bf4744948ed81b0 Mon Sep 17 00:00:00 2001 From: Ian VanSchooten Date: Thu, 6 Mar 2025 08:49:22 -0500 Subject: [PATCH 02/14] Avoid adding duplicate menuitems (windows) Previously, when calling `.SetHidden(false)` on a menu item that was not hidden, a duplicate menu item was created. --- v3/examples/menu/main.go | 2 +- v3/examples/systray-custom/main.go | 11 +++++++++-- v3/pkg/application/menuitem_windows.go | 7 ++++--- 3 files changed, 14 insertions(+), 6 deletions(-) diff --git a/v3/examples/menu/main.go b/v3/examples/menu/main.go index 1809fce0c..a9ae843b8 100644 --- a/v3/examples/menu/main.go +++ b/v3/examples/menu/main.go @@ -46,7 +46,7 @@ func main() { // Hidden menu item that can be unhidden hidden := myMenu.Add("I was hidden").SetHidden(true) - myMenu.Add("Toggle hidden menu").OnClick(func(ctx *application.Context) { + myMenu.Add("Toggle the hidden menu").OnClick(func(ctx *application.Context) { hidden.SetHidden(!hidden.Hidden()) }) diff --git a/v3/examples/systray-custom/main.go b/v3/examples/systray-custom/main.go index 2aa244ba4..fee979406 100644 --- a/v3/examples/systray-custom/main.go +++ b/v3/examples/systray-custom/main.go @@ -2,11 +2,12 @@ package main import ( _ "embed" + "log" + "runtime" + "github.com/wailsapp/wails/v3/pkg/application" "github.com/wailsapp/wails/v3/pkg/events" "github.com/wailsapp/wails/v3/pkg/icons" - "log" - "runtime" ) var windowShowing bool @@ -52,6 +53,12 @@ func main() { systemTray := app.NewSystemTray() menu := app.NewMenu() + // Hidden menu item that can be unhidden + hidden := menu.Add("I was hidden").SetHidden(true) + menu.Add("Toggle the hidden menu").OnClick(func(ctx *application.Context) { + hidden.SetHidden(!hidden.Hidden()) + menu.Update() + }) menu.Add("Quit").OnClick(func(data *application.Context) { app.Quit() }) diff --git a/v3/pkg/application/menuitem_windows.go b/v3/pkg/application/menuitem_windows.go index 44d825f1b..31cf0f248 100644 --- a/v3/pkg/application/menuitem_windows.go +++ b/v3/pkg/application/menuitem_windows.go @@ -24,8 +24,8 @@ type windowsMenuItem struct { } func (m *windowsMenuItem) setHidden(hidden bool) { - m.hidden = hidden - if m.hidden { + if hidden && !m.hidden { + m.hidden = true // iterate the parent items and find the menu item before us for i, item := range m.parent.items { if item == m.menuItem { @@ -41,7 +41,8 @@ func (m *windowsMenuItem) setHidden(hidden bool) { // m.pos = w32.GetMenuItemPosition(m.hMenu, uint32(m.id)) // Remove from parent menu w32.RemoveMenu(m.hMenu, m.id, w32.MF_BYCOMMAND) - } else { + } else if !hidden && m.hidden { + m.hidden = false // Add to parent menu // Get the position of the item before us var pos int From 42c6c2b524572f379b6a1bfe7b20086a11821ecb Mon Sep 17 00:00:00 2001 From: Ian VanSchooten Date: Thu, 6 Mar 2025 08:52:46 -0500 Subject: [PATCH 03/14] Fix comment --- v3/pkg/application/menuitem_windows.go | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/v3/pkg/application/menuitem_windows.go b/v3/pkg/application/menuitem_windows.go index 31cf0f248..825b2f05c 100644 --- a/v3/pkg/application/menuitem_windows.go +++ b/v3/pkg/application/menuitem_windows.go @@ -26,7 +26,7 @@ type windowsMenuItem struct { func (m *windowsMenuItem) setHidden(hidden bool) { if hidden && !m.hidden { m.hidden = true - // iterate the parent items and find the menu item before us + // iterate the parent items and find the menu item after us for i, item := range m.parent.items { if item == m.menuItem { if i < len(m.parent.items)-1 { @@ -37,14 +37,11 @@ func (m *windowsMenuItem) setHidden(hidden bool) { break } } - // Get the position of this menu item in the parent menu - // m.pos = w32.GetMenuItemPosition(m.hMenu, uint32(m.id)) // Remove from parent menu w32.RemoveMenu(m.hMenu, m.id, w32.MF_BYCOMMAND) } else if !hidden && m.hidden { m.hidden = false - // Add to parent menu - // Get the position of the item before us + // Add to parent menu before the "itemAfter" var pos int if m.itemAfter != nil { for i, item := range m.parent.items { From a5fdc3a6deddc006a2c9ed35f517f023a7e314f3 Mon Sep 17 00:00:00 2001 From: Ian VanSchooten Date: Thu, 6 Mar 2025 13:58:32 -0500 Subject: [PATCH 04/14] Revert systray-custom example changes --- v3/examples/systray-custom/main.go | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/v3/examples/systray-custom/main.go b/v3/examples/systray-custom/main.go index fee979406..2aa244ba4 100644 --- a/v3/examples/systray-custom/main.go +++ b/v3/examples/systray-custom/main.go @@ -2,12 +2,11 @@ package main import ( _ "embed" - "log" - "runtime" - "github.com/wailsapp/wails/v3/pkg/application" "github.com/wailsapp/wails/v3/pkg/events" "github.com/wailsapp/wails/v3/pkg/icons" + "log" + "runtime" ) var windowShowing bool @@ -53,12 +52,6 @@ func main() { systemTray := app.NewSystemTray() menu := app.NewMenu() - // Hidden menu item that can be unhidden - hidden := menu.Add("I was hidden").SetHidden(true) - menu.Add("Toggle the hidden menu").OnClick(func(ctx *application.Context) { - hidden.SetHidden(!hidden.Hidden()) - menu.Update() - }) menu.Add("Quit").OnClick(func(data *application.Context) { app.Quit() }) From 0a6a4205035507795d3f2b32871f582327c8ade3 Mon Sep 17 00:00:00 2001 From: Ian VanSchooten Date: Thu, 6 Mar 2025 14:00:40 -0500 Subject: [PATCH 05/14] Fix hidden menuItem on windows And bring menu_windows into alignment with popupmenu_windows --- v3/pkg/application/menu_windows.go | 17 +++++++++++++---- v3/pkg/application/popupmenu_windows.go | 23 ++++++++++++++--------- 2 files changed, 27 insertions(+), 13 deletions(-) diff --git a/v3/pkg/application/menu_windows.go b/v3/pkg/application/menu_windows.go index 04a089982..b75ac74d1 100644 --- a/v3/pkg/application/menu_windows.go +++ b/v3/pkg/application/menu_windows.go @@ -36,6 +36,14 @@ func (w *windowsMenu) update() { func (w *windowsMenu) processMenu(parentMenu w32.HMENU, inputMenu *Menu) { for _, item := range inputMenu.items { + w.currentMenuID++ + itemID := w.currentMenuID + w.menuMapping[itemID] = item + + menuItemImpl := newMenuItemImpl(item, parentMenu, itemID) + menuItemImpl.parent = inputMenu + item.impl = menuItemImpl + if item.Hidden() { if item.accelerator != nil && item.callback != nil { if w.parentWindow != nil { @@ -44,11 +52,7 @@ func (w *windowsMenu) processMenu(parentMenu w32.HMENU, inputMenu *Menu) { globalApplication.removeKeyBinding(item.accelerator.String()) } } - continue } - w.currentMenuID++ - itemID := w.currentMenuID - w.menuMapping[itemID] = item flags := uint32(w32.MF_STRING) if item.disabled { @@ -84,6 +88,11 @@ func (w *windowsMenu) processMenu(parentMenu w32.HMENU, inputMenu *Menu) { } var menuText = w32.MustStringToUTF16Ptr(thisText) + // If the item is hidden, don't append + if item.Hidden() { + continue + } + w32.AppendMenu(parentMenu, flags, uintptr(itemID), menuText) if item.bitmap != nil { w32.SetMenuIcons(parentMenu, itemID, item.bitmap, nil) diff --git a/v3/pkg/application/popupmenu_windows.go b/v3/pkg/application/popupmenu_windows.go index fbfd5eef9..bad9ad71e 100644 --- a/v3/pkg/application/popupmenu_windows.go +++ b/v3/pkg/application/popupmenu_windows.go @@ -61,6 +61,14 @@ func (p *Win32Menu) newMenu() w32.HMENU { func (p *Win32Menu) buildMenu(parentMenu w32.HMENU, inputMenu *Menu) { currentRadioGroup := RadioGroup{} for _, item := range inputMenu.items { + p.currentMenuID++ + itemID := p.currentMenuID + p.menuMapping[itemID] = item + + menuItemImpl := newMenuItemImpl(item, parentMenu, itemID) + menuItemImpl.parent = inputMenu + item.impl = menuItemImpl + if item.Hidden() { if item.accelerator != nil { if p.parentWindow != nil { @@ -71,14 +79,7 @@ func (p *Win32Menu) buildMenu(parentMenu w32.HMENU, inputMenu *Menu) { globalApplication.removeKeyBinding(item.accelerator.String()) } } - continue } - p.currentMenuID++ - itemID := p.currentMenuID - p.menuMapping[itemID] = item - - menuItemImpl := newMenuItemImpl(item, parentMenu, itemID) - menuItemImpl.parent = inputMenu flags := uint32(w32.MF_STRING) if item.disabled { @@ -131,6 +132,12 @@ func (p *Win32Menu) buildMenu(parentMenu w32.HMENU, inputMenu *Menu) { } } } + + // If the item is hidden, don't append + if item.Hidden() { + continue + } + ok := w32.AppendMenu(parentMenu, flags, uintptr(itemID), w32.MustStringToUTF16Ptr(menuText)) if !ok { globalApplication.fatal("error adding menu item '%s'", menuText) @@ -141,8 +148,6 @@ func (p *Win32Menu) buildMenu(parentMenu w32.HMENU, inputMenu *Menu) { globalApplication.fatal("error setting menu icons: %w", err) } } - - item.impl = menuItemImpl } if len(currentRadioGroup) > 0 { for _, radioMember := range currentRadioGroup { From 5e529aed01af787c4b144cdf126356cf7f6980e1 Mon Sep 17 00:00:00 2001 From: Lea Anthony Date: Sat, 8 Mar 2025 11:23:24 +1100 Subject: [PATCH 06/14] Add fix for application menu. Add docs --- .../content/docs/learn/application-menu.mdx | 37 +++++++++++++++++++ v3/examples/menu/main.go | 14 ++----- v3/pkg/application/webview_window.go | 17 +++++++++ v3/pkg/application/webview_window_darwin.go | 7 ++-- v3/pkg/application/webview_window_linux.go | 9 +++++ v3/pkg/application/webview_window_windows.go | 7 ++++ v3/pkg/application/window.go | 1 + 7 files changed, 78 insertions(+), 14 deletions(-) diff --git a/docs/src/content/docs/learn/application-menu.mdx b/docs/src/content/docs/learn/application-menu.mdx index df344a02c..01f2d271d 100644 --- a/docs/src/content/docs/learn/application-menu.mdx +++ b/docs/src/content/docs/learn/application-menu.mdx @@ -16,6 +16,43 @@ Create a new application menu using the `NewMenu` method: menu := app.NewMenu() ``` +## Setting the Menu + +The way to set the menu varies on the platform: + + + + + On macOS, there is only one menu bar per application. Set the menu using the `SetMenu` method of the application: + + ```go + app.SetMenu(menu) + ``` + + + + + + On Windows, there is a menu bar per window. Set the menu using the `SetMenu` method of the window: + + ```go + window.SetMenu(menu) + ``` + + + + + + On Linux, the menu bar is typically per window. Set the menu using the `SetMenu` method of the window: + + ```go + window.SetMenu(menu) + ``` + + + + + ## Menu Roles Wails provides predefined menu roles that automatically create platform-appropriate menu structures: diff --git a/v3/examples/menu/main.go b/v3/examples/menu/main.go index a9ae843b8..218f93a2d 100644 --- a/v3/examples/menu/main.go +++ b/v3/examples/menu/main.go @@ -27,16 +27,7 @@ func main() { if runtime.GOOS == "darwin" { menu.AddRole(application.AppMenu) } - fileMenu := menu.AddRole(application.FileMenu) - _ = fileMenu - //fileMenu.FindByRole(application.Open).OnClick(func(context *application.Context) { - // selection, err := application.OpenFileDialog().PromptForSingleSelection() - // if err != nil { - // println("Error: " + err.Error()) - // return - // } - // println("You selected: " + selection) - //}) + menu.AddRole(application.FileMenu) menu.AddRole(application.EditMenu) menu.AddRole(application.WindowMenu) menu.AddRole(application.HelpMenu) @@ -124,7 +115,8 @@ func main() { }) app.SetMenu(menu) - app.NewWebviewWindow().SetBackgroundColour(application.NewRGB(33, 37, 41)) + window := app.NewWebviewWindow().SetBackgroundColour(application.NewRGB(33, 37, 41)) + window.SetMenu(menu) err := app.Run() diff --git a/v3/pkg/application/webview_window.go b/v3/pkg/application/webview_window.go index b5903e33c..374c423b7 100644 --- a/v3/pkg/application/webview_window.go +++ b/v3/pkg/application/webview_window.go @@ -108,6 +108,7 @@ type ( showMenuBar() hideMenuBar() toggleMenuBar() + setMenu(menu *Menu) } ) @@ -168,6 +169,22 @@ type WebviewWindow struct { unconditionallyClose bool } +func (w *WebviewWindow) SetMenu(menu *Menu) { + switch runtime.GOOS { + case "darwin": + return + case "windows": + w.options.Windows.Menu = menu + case "linux": + w.options.Linux.Menu = menu + } + if w.impl != nil { + InvokeSync(func() { + w.impl.setMenu(menu) + }) + } +} + // EmitEvent emits an event from the window func (w *WebviewWindow) EmitEvent(name string, data ...any) { globalApplication.emitEvent(&CustomEvent{ diff --git a/v3/pkg/application/webview_window_darwin.go b/v3/pkg/application/webview_window_darwin.go index 668d3c8e9..28e693a23 100644 --- a/v3/pkg/application/webview_window_darwin.go +++ b/v3/pkg/application/webview_window_darwin.go @@ -1427,6 +1427,7 @@ func (w *macosWebviewWindow) delete() { func (w *macosWebviewWindow) redo() { } -func (w *macosWebviewWindow) showMenuBar() {} -func (w *macosWebviewWindow) hideMenuBar() {} -func (w *macosWebviewWindow) toggleMenuBar() {} +func (w *macosWebviewWindow) showMenuBar() {} +func (w *macosWebviewWindow) hideMenuBar() {} +func (w *macosWebviewWindow) toggleMenuBar() {} +func (w *macosWebviewWindow) setMenu(_ *Menu) {} diff --git a/v3/pkg/application/webview_window_linux.go b/v3/pkg/application/webview_window_linux.go index 4bef34335..8c50c6117 100644 --- a/v3/pkg/application/webview_window_linux.go +++ b/v3/pkg/application/webview_window_linux.go @@ -235,6 +235,15 @@ func (w *linuxWebviewWindow) setPhysicalBounds(physicalBounds Rect) { w.setBounds(physicalBounds) } +func (w *linuxWebviewWindow) setMenu(menu *Menu) { + if menu == nil { + w.gtkmenu = nil + return + } + w.parent.options.Linux.Menu = menu + w.gtkmenu = (menu.impl).(*linuxMenu).native +} + func (w *linuxWebviewWindow) run() { for eventId := range w.parent.eventListeners { w.on(eventId) diff --git a/v3/pkg/application/webview_window_windows.go b/v3/pkg/application/webview_window_windows.go index 89bea1cbd..3ff9aff9c 100644 --- a/v3/pkg/application/webview_window_windows.go +++ b/v3/pkg/application/webview_window_windows.go @@ -73,6 +73,13 @@ type windowsWebviewWindow struct { isMinimizing bool } +func (w *windowsWebviewWindow) setMenu(menu *Menu) { + menu.Update() + w.menu = NewApplicationMenu(w, menu) + w.menu.parentWindow = w + w32.SetMenu(w.hwnd, w.menu.menu) +} + func (w *windowsWebviewWindow) cut() { w.execJS("document.execCommand('cut')") } diff --git a/v3/pkg/application/window.go b/v3/pkg/application/window.go index d1a4219ba..0d688126b 100644 --- a/v3/pkg/application/window.go +++ b/v3/pkg/application/window.go @@ -84,4 +84,5 @@ type Window interface { ZoomIn() ZoomOut() ZoomReset() Window + SetMenu(menu *Menu) } From 5fb1a14c36c931a5260216ef9c26b8ed84146880 Mon Sep 17 00:00:00 2001 From: Ian VanSchooten Date: Wed, 5 Mar 2025 14:36:43 -0500 Subject: [PATCH 07/14] Fix registration of hidden menuItem [mac] --- v3/examples/menu/main.go | 6 ++++++ v3/pkg/application/menu_darwin.go | 6 +++--- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/v3/examples/menu/main.go b/v3/examples/menu/main.go index 62bcb454b..1809fce0c 100644 --- a/v3/examples/menu/main.go +++ b/v3/examples/menu/main.go @@ -44,6 +44,12 @@ func main() { // Let's make a "Demo" menu myMenu := menu.AddSubmenu("Demo") + // Hidden menu item that can be unhidden + hidden := myMenu.Add("I was hidden").SetHidden(true) + myMenu.Add("Toggle hidden menu").OnClick(func(ctx *application.Context) { + hidden.SetHidden(!hidden.Hidden()) + }) + // Disabled menu item myMenu.Add("Not Enabled").SetEnabled(false) diff --git a/v3/pkg/application/menu_darwin.go b/v3/pkg/application/menu_darwin.go index 1e3fc7197..a17031489 100644 --- a/v3/pkg/application/menu_darwin.go +++ b/v3/pkg/application/menu_darwin.go @@ -84,9 +84,6 @@ func (m *macosMenu) update() { func (m *macosMenu) processMenu(parent unsafe.Pointer, menu *Menu) { for _, item := range menu.items { - if item.hidden { - continue - } switch item.itemType { case submenu: submenu := item.submenu @@ -102,6 +99,9 @@ func (m *macosMenu) processMenu(parent unsafe.Pointer, menu *Menu) { case text, checkbox, radio: menuItem := newMenuItemImpl(item) item.impl = menuItem + if item.hidden { + menuItem.setHidden(true) + } C.addMenuItem(parent, menuItem.nsMenuItem) case separator: C.addMenuSeparator(parent) From 35cc7fd569503f720a98f5eb9f68e6ffa43bfae7 Mon Sep 17 00:00:00 2001 From: Ian VanSchooten Date: Thu, 6 Mar 2025 08:49:22 -0500 Subject: [PATCH 08/14] Avoid adding duplicate menuitems (windows) Previously, when calling `.SetHidden(false)` on a menu item that was not hidden, a duplicate menu item was created. --- v3/examples/menu/main.go | 2 +- v3/examples/systray-custom/main.go | 11 +++++++++-- v3/pkg/application/menuitem_windows.go | 7 ++++--- 3 files changed, 14 insertions(+), 6 deletions(-) diff --git a/v3/examples/menu/main.go b/v3/examples/menu/main.go index 1809fce0c..a9ae843b8 100644 --- a/v3/examples/menu/main.go +++ b/v3/examples/menu/main.go @@ -46,7 +46,7 @@ func main() { // Hidden menu item that can be unhidden hidden := myMenu.Add("I was hidden").SetHidden(true) - myMenu.Add("Toggle hidden menu").OnClick(func(ctx *application.Context) { + myMenu.Add("Toggle the hidden menu").OnClick(func(ctx *application.Context) { hidden.SetHidden(!hidden.Hidden()) }) diff --git a/v3/examples/systray-custom/main.go b/v3/examples/systray-custom/main.go index 2aa244ba4..fee979406 100644 --- a/v3/examples/systray-custom/main.go +++ b/v3/examples/systray-custom/main.go @@ -2,11 +2,12 @@ package main import ( _ "embed" + "log" + "runtime" + "github.com/wailsapp/wails/v3/pkg/application" "github.com/wailsapp/wails/v3/pkg/events" "github.com/wailsapp/wails/v3/pkg/icons" - "log" - "runtime" ) var windowShowing bool @@ -52,6 +53,12 @@ func main() { systemTray := app.NewSystemTray() menu := app.NewMenu() + // Hidden menu item that can be unhidden + hidden := menu.Add("I was hidden").SetHidden(true) + menu.Add("Toggle the hidden menu").OnClick(func(ctx *application.Context) { + hidden.SetHidden(!hidden.Hidden()) + menu.Update() + }) menu.Add("Quit").OnClick(func(data *application.Context) { app.Quit() }) diff --git a/v3/pkg/application/menuitem_windows.go b/v3/pkg/application/menuitem_windows.go index 44d825f1b..31cf0f248 100644 --- a/v3/pkg/application/menuitem_windows.go +++ b/v3/pkg/application/menuitem_windows.go @@ -24,8 +24,8 @@ type windowsMenuItem struct { } func (m *windowsMenuItem) setHidden(hidden bool) { - m.hidden = hidden - if m.hidden { + if hidden && !m.hidden { + m.hidden = true // iterate the parent items and find the menu item before us for i, item := range m.parent.items { if item == m.menuItem { @@ -41,7 +41,8 @@ func (m *windowsMenuItem) setHidden(hidden bool) { // m.pos = w32.GetMenuItemPosition(m.hMenu, uint32(m.id)) // Remove from parent menu w32.RemoveMenu(m.hMenu, m.id, w32.MF_BYCOMMAND) - } else { + } else if !hidden && m.hidden { + m.hidden = false // Add to parent menu // Get the position of the item before us var pos int From 91c9c3f1476a22285f391664845957a88d671134 Mon Sep 17 00:00:00 2001 From: Ian VanSchooten Date: Thu, 6 Mar 2025 08:52:46 -0500 Subject: [PATCH 09/14] Fix comment --- v3/pkg/application/menuitem_windows.go | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/v3/pkg/application/menuitem_windows.go b/v3/pkg/application/menuitem_windows.go index 31cf0f248..825b2f05c 100644 --- a/v3/pkg/application/menuitem_windows.go +++ b/v3/pkg/application/menuitem_windows.go @@ -26,7 +26,7 @@ type windowsMenuItem struct { func (m *windowsMenuItem) setHidden(hidden bool) { if hidden && !m.hidden { m.hidden = true - // iterate the parent items and find the menu item before us + // iterate the parent items and find the menu item after us for i, item := range m.parent.items { if item == m.menuItem { if i < len(m.parent.items)-1 { @@ -37,14 +37,11 @@ func (m *windowsMenuItem) setHidden(hidden bool) { break } } - // Get the position of this menu item in the parent menu - // m.pos = w32.GetMenuItemPosition(m.hMenu, uint32(m.id)) // Remove from parent menu w32.RemoveMenu(m.hMenu, m.id, w32.MF_BYCOMMAND) } else if !hidden && m.hidden { m.hidden = false - // Add to parent menu - // Get the position of the item before us + // Add to parent menu before the "itemAfter" var pos int if m.itemAfter != nil { for i, item := range m.parent.items { From dc662b561a336e4ac3debee9670f9c712bc0bc05 Mon Sep 17 00:00:00 2001 From: Ian VanSchooten Date: Thu, 6 Mar 2025 13:58:32 -0500 Subject: [PATCH 10/14] Revert systray-custom example changes --- v3/examples/systray-custom/main.go | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/v3/examples/systray-custom/main.go b/v3/examples/systray-custom/main.go index fee979406..2aa244ba4 100644 --- a/v3/examples/systray-custom/main.go +++ b/v3/examples/systray-custom/main.go @@ -2,12 +2,11 @@ package main import ( _ "embed" - "log" - "runtime" - "github.com/wailsapp/wails/v3/pkg/application" "github.com/wailsapp/wails/v3/pkg/events" "github.com/wailsapp/wails/v3/pkg/icons" + "log" + "runtime" ) var windowShowing bool @@ -53,12 +52,6 @@ func main() { systemTray := app.NewSystemTray() menu := app.NewMenu() - // Hidden menu item that can be unhidden - hidden := menu.Add("I was hidden").SetHidden(true) - menu.Add("Toggle the hidden menu").OnClick(func(ctx *application.Context) { - hidden.SetHidden(!hidden.Hidden()) - menu.Update() - }) menu.Add("Quit").OnClick(func(data *application.Context) { app.Quit() }) From b733b1d3c4cea71c99d89da6363c1895c8a476ca Mon Sep 17 00:00:00 2001 From: Ian VanSchooten Date: Thu, 6 Mar 2025 14:00:40 -0500 Subject: [PATCH 11/14] Fix hidden menuItem on windows And bring menu_windows into alignment with popupmenu_windows --- v3/pkg/application/menu_windows.go | 17 +++++++++++++---- v3/pkg/application/popupmenu_windows.go | 23 ++++++++++++++--------- 2 files changed, 27 insertions(+), 13 deletions(-) diff --git a/v3/pkg/application/menu_windows.go b/v3/pkg/application/menu_windows.go index 04a089982..b75ac74d1 100644 --- a/v3/pkg/application/menu_windows.go +++ b/v3/pkg/application/menu_windows.go @@ -36,6 +36,14 @@ func (w *windowsMenu) update() { func (w *windowsMenu) processMenu(parentMenu w32.HMENU, inputMenu *Menu) { for _, item := range inputMenu.items { + w.currentMenuID++ + itemID := w.currentMenuID + w.menuMapping[itemID] = item + + menuItemImpl := newMenuItemImpl(item, parentMenu, itemID) + menuItemImpl.parent = inputMenu + item.impl = menuItemImpl + if item.Hidden() { if item.accelerator != nil && item.callback != nil { if w.parentWindow != nil { @@ -44,11 +52,7 @@ func (w *windowsMenu) processMenu(parentMenu w32.HMENU, inputMenu *Menu) { globalApplication.removeKeyBinding(item.accelerator.String()) } } - continue } - w.currentMenuID++ - itemID := w.currentMenuID - w.menuMapping[itemID] = item flags := uint32(w32.MF_STRING) if item.disabled { @@ -84,6 +88,11 @@ func (w *windowsMenu) processMenu(parentMenu w32.HMENU, inputMenu *Menu) { } var menuText = w32.MustStringToUTF16Ptr(thisText) + // If the item is hidden, don't append + if item.Hidden() { + continue + } + w32.AppendMenu(parentMenu, flags, uintptr(itemID), menuText) if item.bitmap != nil { w32.SetMenuIcons(parentMenu, itemID, item.bitmap, nil) diff --git a/v3/pkg/application/popupmenu_windows.go b/v3/pkg/application/popupmenu_windows.go index fbfd5eef9..bad9ad71e 100644 --- a/v3/pkg/application/popupmenu_windows.go +++ b/v3/pkg/application/popupmenu_windows.go @@ -61,6 +61,14 @@ func (p *Win32Menu) newMenu() w32.HMENU { func (p *Win32Menu) buildMenu(parentMenu w32.HMENU, inputMenu *Menu) { currentRadioGroup := RadioGroup{} for _, item := range inputMenu.items { + p.currentMenuID++ + itemID := p.currentMenuID + p.menuMapping[itemID] = item + + menuItemImpl := newMenuItemImpl(item, parentMenu, itemID) + menuItemImpl.parent = inputMenu + item.impl = menuItemImpl + if item.Hidden() { if item.accelerator != nil { if p.parentWindow != nil { @@ -71,14 +79,7 @@ func (p *Win32Menu) buildMenu(parentMenu w32.HMENU, inputMenu *Menu) { globalApplication.removeKeyBinding(item.accelerator.String()) } } - continue } - p.currentMenuID++ - itemID := p.currentMenuID - p.menuMapping[itemID] = item - - menuItemImpl := newMenuItemImpl(item, parentMenu, itemID) - menuItemImpl.parent = inputMenu flags := uint32(w32.MF_STRING) if item.disabled { @@ -131,6 +132,12 @@ func (p *Win32Menu) buildMenu(parentMenu w32.HMENU, inputMenu *Menu) { } } } + + // If the item is hidden, don't append + if item.Hidden() { + continue + } + ok := w32.AppendMenu(parentMenu, flags, uintptr(itemID), w32.MustStringToUTF16Ptr(menuText)) if !ok { globalApplication.fatal("error adding menu item '%s'", menuText) @@ -141,8 +148,6 @@ func (p *Win32Menu) buildMenu(parentMenu w32.HMENU, inputMenu *Menu) { globalApplication.fatal("error setting menu icons: %w", err) } } - - item.impl = menuItemImpl } if len(currentRadioGroup) > 0 { for _, radioMember := range currentRadioGroup { From 866fb36b67679b24023db5e4aa6b8c8fc321d17d Mon Sep 17 00:00:00 2001 From: Lea Anthony Date: Sat, 8 Mar 2025 11:23:24 +1100 Subject: [PATCH 12/14] Add fix for application menu. Add docs --- .../content/docs/learn/application-menu.mdx | 37 +++++++++++++++++++ v3/examples/menu/main.go | 14 ++----- v3/pkg/application/webview_window.go | 17 +++++++++ v3/pkg/application/webview_window_darwin.go | 7 ++-- v3/pkg/application/webview_window_linux.go | 9 +++++ v3/pkg/application/webview_window_windows.go | 7 ++++ v3/pkg/application/window.go | 1 + 7 files changed, 78 insertions(+), 14 deletions(-) diff --git a/docs/src/content/docs/learn/application-menu.mdx b/docs/src/content/docs/learn/application-menu.mdx index df344a02c..01f2d271d 100644 --- a/docs/src/content/docs/learn/application-menu.mdx +++ b/docs/src/content/docs/learn/application-menu.mdx @@ -16,6 +16,43 @@ Create a new application menu using the `NewMenu` method: menu := app.NewMenu() ``` +## Setting the Menu + +The way to set the menu varies on the platform: + + + + + On macOS, there is only one menu bar per application. Set the menu using the `SetMenu` method of the application: + + ```go + app.SetMenu(menu) + ``` + + + + + + On Windows, there is a menu bar per window. Set the menu using the `SetMenu` method of the window: + + ```go + window.SetMenu(menu) + ``` + + + + + + On Linux, the menu bar is typically per window. Set the menu using the `SetMenu` method of the window: + + ```go + window.SetMenu(menu) + ``` + + + + + ## Menu Roles Wails provides predefined menu roles that automatically create platform-appropriate menu structures: diff --git a/v3/examples/menu/main.go b/v3/examples/menu/main.go index a9ae843b8..218f93a2d 100644 --- a/v3/examples/menu/main.go +++ b/v3/examples/menu/main.go @@ -27,16 +27,7 @@ func main() { if runtime.GOOS == "darwin" { menu.AddRole(application.AppMenu) } - fileMenu := menu.AddRole(application.FileMenu) - _ = fileMenu - //fileMenu.FindByRole(application.Open).OnClick(func(context *application.Context) { - // selection, err := application.OpenFileDialog().PromptForSingleSelection() - // if err != nil { - // println("Error: " + err.Error()) - // return - // } - // println("You selected: " + selection) - //}) + menu.AddRole(application.FileMenu) menu.AddRole(application.EditMenu) menu.AddRole(application.WindowMenu) menu.AddRole(application.HelpMenu) @@ -124,7 +115,8 @@ func main() { }) app.SetMenu(menu) - app.NewWebviewWindow().SetBackgroundColour(application.NewRGB(33, 37, 41)) + window := app.NewWebviewWindow().SetBackgroundColour(application.NewRGB(33, 37, 41)) + window.SetMenu(menu) err := app.Run() diff --git a/v3/pkg/application/webview_window.go b/v3/pkg/application/webview_window.go index b5903e33c..374c423b7 100644 --- a/v3/pkg/application/webview_window.go +++ b/v3/pkg/application/webview_window.go @@ -108,6 +108,7 @@ type ( showMenuBar() hideMenuBar() toggleMenuBar() + setMenu(menu *Menu) } ) @@ -168,6 +169,22 @@ type WebviewWindow struct { unconditionallyClose bool } +func (w *WebviewWindow) SetMenu(menu *Menu) { + switch runtime.GOOS { + case "darwin": + return + case "windows": + w.options.Windows.Menu = menu + case "linux": + w.options.Linux.Menu = menu + } + if w.impl != nil { + InvokeSync(func() { + w.impl.setMenu(menu) + }) + } +} + // EmitEvent emits an event from the window func (w *WebviewWindow) EmitEvent(name string, data ...any) { globalApplication.emitEvent(&CustomEvent{ diff --git a/v3/pkg/application/webview_window_darwin.go b/v3/pkg/application/webview_window_darwin.go index 668d3c8e9..28e693a23 100644 --- a/v3/pkg/application/webview_window_darwin.go +++ b/v3/pkg/application/webview_window_darwin.go @@ -1427,6 +1427,7 @@ func (w *macosWebviewWindow) delete() { func (w *macosWebviewWindow) redo() { } -func (w *macosWebviewWindow) showMenuBar() {} -func (w *macosWebviewWindow) hideMenuBar() {} -func (w *macosWebviewWindow) toggleMenuBar() {} +func (w *macosWebviewWindow) showMenuBar() {} +func (w *macosWebviewWindow) hideMenuBar() {} +func (w *macosWebviewWindow) toggleMenuBar() {} +func (w *macosWebviewWindow) setMenu(_ *Menu) {} diff --git a/v3/pkg/application/webview_window_linux.go b/v3/pkg/application/webview_window_linux.go index 4bef34335..8c50c6117 100644 --- a/v3/pkg/application/webview_window_linux.go +++ b/v3/pkg/application/webview_window_linux.go @@ -235,6 +235,15 @@ func (w *linuxWebviewWindow) setPhysicalBounds(physicalBounds Rect) { w.setBounds(physicalBounds) } +func (w *linuxWebviewWindow) setMenu(menu *Menu) { + if menu == nil { + w.gtkmenu = nil + return + } + w.parent.options.Linux.Menu = menu + w.gtkmenu = (menu.impl).(*linuxMenu).native +} + func (w *linuxWebviewWindow) run() { for eventId := range w.parent.eventListeners { w.on(eventId) diff --git a/v3/pkg/application/webview_window_windows.go b/v3/pkg/application/webview_window_windows.go index 89bea1cbd..3ff9aff9c 100644 --- a/v3/pkg/application/webview_window_windows.go +++ b/v3/pkg/application/webview_window_windows.go @@ -73,6 +73,13 @@ type windowsWebviewWindow struct { isMinimizing bool } +func (w *windowsWebviewWindow) setMenu(menu *Menu) { + menu.Update() + w.menu = NewApplicationMenu(w, menu) + w.menu.parentWindow = w + w32.SetMenu(w.hwnd, w.menu.menu) +} + func (w *windowsWebviewWindow) cut() { w.execJS("document.execCommand('cut')") } diff --git a/v3/pkg/application/window.go b/v3/pkg/application/window.go index d1a4219ba..0d688126b 100644 --- a/v3/pkg/application/window.go +++ b/v3/pkg/application/window.go @@ -84,4 +84,5 @@ type Window interface { ZoomIn() ZoomOut() ZoomReset() Window + SetMenu(menu *Menu) } From fd9c37aa440cdc520af81279ff0586432a6d0d1d Mon Sep 17 00:00:00 2001 From: Lea Anthony Date: Sat, 8 Mar 2025 11:52:38 +1100 Subject: [PATCH 13/14] Update changelog --- docs/src/content/docs/changelog.mdx | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/src/content/docs/changelog.mdx b/docs/src/content/docs/changelog.mdx index fd18b76c6..f3f89a9af 100644 --- a/docs/src/content/docs/changelog.mdx +++ b/docs/src/content/docs/changelog.mdx @@ -75,6 +75,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Add method `Close` on `sqlite` service to close the DB manually by [@fbbdev](https://github.com/fbbdev) in [#4067](https://github.com/wailsapp/wails/pull/4067) - Add cancellation support for query methods on `sqlite` service by [@fbbdev](https://github.com/fbbdev) in [#4067](https://github.com/wailsapp/wails/pull/4067) - Add prepared statement support to `sqlite` service with JS bindings by [@fbbdev](https://github.com/fbbdev) in [#4067](https://github.com/wailsapp/wails/pull/4067) +- Add `SetMenu()` on window to allow for setting a menu on a window by [@leaanthony](https://github.com/leaanthony) ### Fixed From d30652a6e6e3c9ea5784caea5386db97850f32d3 Mon Sep 17 00:00:00 2001 From: Ian VanSchooten Date: Mon, 10 Mar 2025 09:20:21 -0400 Subject: [PATCH 14/14] Update changelog --- docs/src/content/docs/changelog.mdx | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/src/content/docs/changelog.mdx b/docs/src/content/docs/changelog.mdx index f3f89a9af..404120fc2 100644 --- a/docs/src/content/docs/changelog.mdx +++ b/docs/src/content/docs/changelog.mdx @@ -108,6 +108,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 -  Ensure menu updates occur on the main thread by [@leaanthony](https://github.com/leaanthony) - The dragging and resizing mechanism is now more robust and matches expected platform behaviour more closely by [@fbbdev](https://github.com/fbbdev) in [#4100](https://github.com/wailsapp/wails/pull/4100) - Fixed [#4097](https://github.com/wailsapp/wails/issues/4097) Webpack/angular discards runtime init code by [@fbbdev](https://github.com/fbbdev) in [#4100](https://github.com/wailsapp/wails/pull/4100) +- Fixed initially-hidden menu items by [@IanVS](https://github.com/IanVS) in [#4116](https://github.com/wailsapp/wails/pull/4116) ### Changed