mirror of
https://github.com/taigrr/wails.git
synced 2026-04-02 05:08:54 -07:00
[windows] Preliminary support for application menu. More work TBD.
This commit is contained in:
@@ -142,6 +142,14 @@ LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) {
|
||||
|
||||
switch(msg) {
|
||||
|
||||
case WM_CREATE: {
|
||||
createApplicationMenu(hwnd);
|
||||
break;
|
||||
}
|
||||
case WM_COMMAND:
|
||||
menuClicked(LOWORD(wParam));
|
||||
break;
|
||||
|
||||
case WM_CLOSE: {
|
||||
DestroyWindow( app->window );
|
||||
break;
|
||||
|
||||
@@ -13,9 +13,73 @@ extern void DisableWindowIcon(struct Application* app);
|
||||
|
||||
*/
|
||||
import "C"
|
||||
import (
|
||||
"github.com/wailsapp/wails/v2/internal/menumanager"
|
||||
"github.com/wailsapp/wails/v2/pkg/menu"
|
||||
"golang.org/x/sys/windows"
|
||||
"log"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
var (
|
||||
// DLL stuff
|
||||
user32 = windows.NewLazySystemDLL("User32.dll")
|
||||
win32CreateMenu = user32.NewProc("CreateMenu")
|
||||
win32CreatePopupMenu = user32.NewProc("CreatePopupMenu")
|
||||
win32AppendMenuW = user32.NewProc("AppendMenuW")
|
||||
win32SetMenu = user32.NewProc("SetMenu")
|
||||
win32CheckMenuItem = user32.NewProc("CheckMenuItem")
|
||||
win32GetMenuState = user32.NewProc("GetMenuState")
|
||||
applicationMenu *menumanager.WailsMenu
|
||||
menuManager *menumanager.Manager
|
||||
)
|
||||
|
||||
const MF_BITMAP uint32 = 0x00000004
|
||||
const MF_CHECKED uint32 = 0x00000008
|
||||
const MF_DISABLED uint32 = 0x00000002
|
||||
const MF_ENABLED uint32 = 0x00000000
|
||||
const MF_GRAYED uint32 = 0x00000001
|
||||
const MF_MENUBARBREAK uint32 = 0x00000020
|
||||
const MF_MENUBREAK uint32 = 0x00000040
|
||||
const MF_OWNERDRAW uint32 = 0x00000100
|
||||
const MF_POPUP uint32 = 0x00000010
|
||||
const MF_SEPARATOR uint32 = 0x00000800
|
||||
const MF_STRING uint32 = 0x00000000
|
||||
const MF_UNCHECKED uint32 = 0x00000000
|
||||
const MF_BYCOMMAND uint32 = 0x00000000
|
||||
const MF_BYPOSITION uint32 = 0x00000400
|
||||
|
||||
type menuType int
|
||||
|
||||
// Credit: https://github.com/getlantern/systray/blob/2c0986dda9aea361e925f90e848d9036be7b5367/systray_windows.go
|
||||
type menuItemInfo struct {
|
||||
Size, Mask, Type, State uint32
|
||||
ID uint32
|
||||
SubMenu, Checked, Unchecked windows.Handle
|
||||
ItemData uintptr
|
||||
TypeData *uint16
|
||||
Cch uint32
|
||||
BMPItem windows.Handle
|
||||
}
|
||||
|
||||
const (
|
||||
appMenuType menuType = iota
|
||||
contextMenuType
|
||||
trayMenuType
|
||||
)
|
||||
|
||||
type menuCacheEntry struct {
|
||||
parent uintptr
|
||||
menuType menuType
|
||||
index int
|
||||
item *menumanager.ProcessedMenuItem
|
||||
}
|
||||
|
||||
var menuCache = []menuCacheEntry{}
|
||||
|
||||
func (a *Application) processPlatformSettings() error {
|
||||
|
||||
menuManager = a.menuManager
|
||||
config := a.config.Windows
|
||||
if config == nil {
|
||||
return nil
|
||||
@@ -34,13 +98,9 @@ func (a *Application) processPlatformSettings() error {
|
||||
C.DisableWindowIcon(a.app)
|
||||
}
|
||||
|
||||
// Process menu
|
||||
//applicationMenu := options.GetApplicationMenu(a.config)
|
||||
applicationMenu := a.menuManager.GetApplicationMenuJSON()
|
||||
println("Appmenu =", applicationMenu)
|
||||
if applicationMenu != "" {
|
||||
C.SetApplicationMenu(a.app, a.string2CString(applicationMenu))
|
||||
}
|
||||
// Unfortunately, we need to store this in the package variable so the C callback can see it
|
||||
applicationMenu = a.menuManager.GetProcessedApplicationMenu()
|
||||
|
||||
//
|
||||
//// Process tray
|
||||
//trays, err := a.menuManager.GetTrayMenus()
|
||||
@@ -71,3 +131,163 @@ func (a *Application) processPlatformSettings() error {
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func createMenu() (uintptr, error) {
|
||||
res, _, err := win32CreateMenu.Call()
|
||||
if res == 0 {
|
||||
return 0, err
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func createPopupMenu() (uintptr, error) {
|
||||
res, _, err := win32CreatePopupMenu.Call()
|
||||
if res == 0 {
|
||||
return 0, err
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func appendMenuItem(menu uintptr, flags uintptr, id uintptr, label string) error {
|
||||
menuText, err := windows.UTF16PtrFromString(label)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
res, _, err := win32AppendMenuW.Call(
|
||||
menu,
|
||||
flags,
|
||||
id,
|
||||
uintptr(unsafe.Pointer(menuText)),
|
||||
)
|
||||
if res == 0 {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
//export createApplicationMenu
|
||||
func createApplicationMenu(hwnd uintptr) {
|
||||
if applicationMenu == nil {
|
||||
return
|
||||
}
|
||||
|
||||
// Create top level menu bar
|
||||
menubar, err := createMenu()
|
||||
if err != nil {
|
||||
log.Fatal("createMenu:", err.Error())
|
||||
}
|
||||
|
||||
// Process top level menus
|
||||
for index, toplevelmenu := range applicationMenu.Menu.Items {
|
||||
err = processMenuItem(menubar, toplevelmenu, appMenuType, index)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
res, _, err := win32SetMenu.Call(hwnd, menubar)
|
||||
if res == 0 {
|
||||
log.Fatal("setmenu", err)
|
||||
}
|
||||
}
|
||||
|
||||
//export menuClicked
|
||||
func menuClicked(id uint32) {
|
||||
|
||||
// Get the menu from the cache
|
||||
menuitem := menuCache[id]
|
||||
|
||||
if menuitem.item.Type == menu.CheckboxType {
|
||||
|
||||
res, _, err := win32GetMenuState.Call(menuitem.parent, uintptr(id), uintptr(MF_BYCOMMAND))
|
||||
if int(res) == -1 {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
if uint32(res) == MF_CHECKED {
|
||||
res, _, err = win32CheckMenuItem.Call(menuitem.parent, uintptr(id), uintptr(MF_UNCHECKED))
|
||||
} else {
|
||||
res, _, err = win32CheckMenuItem.Call(menuitem.parent, uintptr(id), uintptr(MF_CHECKED))
|
||||
}
|
||||
if int(res) == -1 {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
// Print the click error - it's not fatal
|
||||
err := menuManager.ProcessClick(menuitem.item.ID, "", "ApplicationMenu", "")
|
||||
if err != nil {
|
||||
println(err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
var flagMap = map[menu.Type]uint32{
|
||||
menu.TextType: MF_STRING,
|
||||
menu.SeparatorType: MF_SEPARATOR,
|
||||
menu.SubmenuType: MF_STRING | MF_POPUP,
|
||||
menu.CheckboxType: MF_STRING,
|
||||
}
|
||||
|
||||
func calculateFlags(menuItem *menumanager.ProcessedMenuItem) uint32 {
|
||||
result := flagMap[menuItem.Type]
|
||||
|
||||
if menuItem.Disabled {
|
||||
result |= MF_DISABLED
|
||||
}
|
||||
|
||||
if menuItem.Type == menu.CheckboxType && menuItem.Checked {
|
||||
result |= MF_CHECKED
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
func processMenuItem(parent uintptr, menuItem *menumanager.ProcessedMenuItem, menuType menuType, index int) error {
|
||||
|
||||
// Ignore hidden items
|
||||
if menuItem.Hidden {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Add menuitem to cache
|
||||
ID := len(menuCache)
|
||||
|
||||
// Calculate the flags for this menu item
|
||||
flags := uintptr(calculateFlags(menuItem))
|
||||
|
||||
switch menuItem.Type {
|
||||
case menu.SubmenuType:
|
||||
submenu, err := createPopupMenu()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for index, submenuItem := range menuItem.SubMenu.Items {
|
||||
err = processMenuItem(submenu, submenuItem, menuType, index)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
err = appendMenuItem(parent, flags, submenu, menuItem.Label)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
case menu.TextType, menu.CheckboxType:
|
||||
err := appendMenuItem(parent, flags, uintptr(ID), menuItem.Label)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
menuCacheItem := menuCacheEntry{
|
||||
parent: parent,
|
||||
menuType: menuType,
|
||||
index: index,
|
||||
item: menuItem,
|
||||
}
|
||||
menuCache = append(menuCache, menuCacheItem)
|
||||
case menu.SeparatorType:
|
||||
err := appendMenuItem(parent, flags, 0, "")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -80,6 +80,8 @@ extern "C" {
|
||||
void DisableWindowIcon(struct Application* app);
|
||||
void messageFromWindowCallback(const char *);
|
||||
void* GetWindowHandle(struct Application*);
|
||||
void createApplicationMenu(HWND hwnd);
|
||||
void menuClicked(UINT id);
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -23,6 +23,10 @@ func (m *Manager) GetApplicationMenuJSON() string {
|
||||
return m.applicationMenuJSON
|
||||
}
|
||||
|
||||
func (m *Manager) GetProcessedApplicationMenu() *WailsMenu {
|
||||
return m.processedApplicationMenu
|
||||
}
|
||||
|
||||
// UpdateApplicationMenu reprocesses the application menu to pick up structure
|
||||
// changes etc
|
||||
// Returns the JSON representation of the updated menu
|
||||
@@ -36,8 +40,8 @@ func (m *Manager) UpdateApplicationMenu() (string, error) {
|
||||
func (m *Manager) processApplicationMenu() error {
|
||||
|
||||
// Process the menu
|
||||
processedApplicationMenu := NewWailsMenu(m.applicationMenuItemMap, m.applicationMenu)
|
||||
applicationMenuJSON, err := processedApplicationMenu.AsJSON()
|
||||
m.processedApplicationMenu = NewWailsMenu(m.applicationMenuItemMap, m.applicationMenu)
|
||||
applicationMenuJSON, err := m.processedApplicationMenu.AsJSON()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -8,8 +8,9 @@ import (
|
||||
type Manager struct {
|
||||
|
||||
// The application menu.
|
||||
applicationMenu *menu.Menu
|
||||
applicationMenuJSON string
|
||||
applicationMenu *menu.Menu
|
||||
applicationMenuJSON string
|
||||
processedApplicationMenu *WailsMenu
|
||||
|
||||
// Our application menu mappings
|
||||
applicationMenuItemMap *MenuItemMap
|
||||
|
||||
Reference in New Issue
Block a user