mirror of
https://github.com/wailsapp/wails.git
synced 2025-05-03 02:19:43 +08:00
234 lines
5.9 KiB
Go
234 lines
5.9 KiB
Go
//go:build windows
|
|
// +build windows
|
|
|
|
package ffenestri
|
|
|
|
import (
|
|
"fmt"
|
|
"github.com/wailsapp/wails/v2/internal/menumanager"
|
|
"github.com/wailsapp/wails/v2/pkg/menu"
|
|
"github.com/wailsapp/wails/v2/pkg/menu/keys"
|
|
"runtime"
|
|
"strings"
|
|
)
|
|
|
|
//-------------------- Types ------------------------
|
|
|
|
type win32MenuItemID uint32
|
|
type win32Menu uintptr
|
|
type win32Window uintptr
|
|
type wailsMenuItemID string // The internal menu ID
|
|
|
|
type Menu struct {
|
|
wailsMenu *menumanager.WailsMenu
|
|
menu win32Menu
|
|
menuType menuType
|
|
|
|
// A list of all checkbox and radio menuitems we
|
|
// create for this menu
|
|
checkboxes []win32MenuItemID
|
|
radioboxes []win32MenuItemID
|
|
initiallySelectedRadioItems []win32MenuItemID
|
|
}
|
|
|
|
func createMenu(wailsMenu *menumanager.WailsMenu, menuType menuType) (*Menu, error) {
|
|
|
|
mainMenu, err := createWin32Menu()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
result := &Menu{
|
|
wailsMenu: wailsMenu,
|
|
menu: mainMenu,
|
|
menuType: menuType,
|
|
}
|
|
|
|
// Process top level menus
|
|
for _, toplevelmenu := range applicationMenu.Menu.Items {
|
|
err := result.processMenuItem(result.menu, toplevelmenu)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
err = result.processRadioGroups()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return result, nil
|
|
}
|
|
|
|
func (m *Menu) processMenuItem(parent win32Menu, menuItem *menumanager.ProcessedMenuItem) error {
|
|
|
|
// Ignore hidden items
|
|
if menuItem.Hidden {
|
|
return nil
|
|
}
|
|
|
|
// Calculate the flags for this menu item
|
|
flags := uintptr(calculateFlags(menuItem))
|
|
|
|
switch menuItem.Type {
|
|
case menu.SubmenuType:
|
|
submenu, err := createWin32PopupMenu()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
for _, submenuItem := range menuItem.SubMenu.Items {
|
|
err = m.processMenuItem(submenu, submenuItem)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
err = appendWin32MenuItem(parent, flags, uintptr(submenu), menuItem.Label)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
case menu.TextType, menu.CheckboxType, menu.RadioType:
|
|
win32ID := addMenuCacheEntry(parent, m.menuType, menuItem, m.wailsMenu.Menu)
|
|
if menuItem.Accelerator != nil {
|
|
m.processAccelerator(menuItem)
|
|
}
|
|
label := menuItem.Label
|
|
//label := fmt.Sprintf("%s (%d)", menuItem.Label, win32ID)
|
|
err := appendWin32MenuItem(parent, flags, uintptr(win32ID), label)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if menuItem.Type == menu.CheckboxType {
|
|
// We need to maintain a list of this menu's checkboxes
|
|
m.checkboxes = append(m.checkboxes, win32ID)
|
|
globalCheckboxCache.addToCheckboxCache(m.wailsMenu.Menu, wailsMenuItemID(menuItem.ID), win32ID)
|
|
}
|
|
if menuItem.Type == menu.RadioType {
|
|
// We need to maintain a list of this menu's radioitems
|
|
m.radioboxes = append(m.radioboxes, win32ID)
|
|
globalRadioGroupMap.addRadioGroupMapping(m.wailsMenu.Menu, wailsMenuItemID(menuItem.ID), win32ID)
|
|
if menuItem.Checked {
|
|
m.initiallySelectedRadioItems = append(m.initiallySelectedRadioItems, win32ID)
|
|
}
|
|
}
|
|
case menu.SeparatorType:
|
|
err := appendWin32MenuItem(parent, flags, 0, "")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (m *Menu) processRadioGroups() error {
|
|
|
|
for _, rg := range applicationMenu.RadioGroups {
|
|
startWailsMenuID := wailsMenuItemID(rg.Members[0])
|
|
endWailsMenuID := wailsMenuItemID(rg.Members[len(rg.Members)-1])
|
|
|
|
startIDs := globalRadioGroupMap.getRadioGroupMapping(startWailsMenuID)
|
|
endIDs := globalRadioGroupMap.getRadioGroupMapping(endWailsMenuID)
|
|
|
|
var radioGroupMaps = []*radioGroupStartEnd{}
|
|
for index := range startIDs {
|
|
startID := startIDs[index]
|
|
endID := endIDs[index]
|
|
thisRadioGroup := &radioGroupStartEnd{
|
|
startID: startID,
|
|
endID: endID,
|
|
}
|
|
radioGroupMaps = append(radioGroupMaps, thisRadioGroup)
|
|
}
|
|
|
|
// Set this for each member
|
|
for _, member := range rg.Members {
|
|
id := wailsMenuItemID(member)
|
|
globalRadioGroupCache.addToRadioGroupCache(m.wailsMenu.Menu, id, radioGroupMaps)
|
|
}
|
|
}
|
|
|
|
// Enable all initially checked radio items
|
|
for _, win32MenuID := range m.initiallySelectedRadioItems {
|
|
menuItemDetails := getMenuCacheEntry(win32MenuID)
|
|
wailsMenuID := wailsMenuItemID(menuItemDetails.item.ID)
|
|
err := selectRadioItemFromWailsMenuID(wailsMenuID, win32MenuID)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (m *Menu) Destroy() error {
|
|
|
|
// Release the MenuIDs
|
|
releaseMenuIDsForProcessedMenu(m.wailsMenu.Menu)
|
|
|
|
// Unload this menu's checkboxes from the cache
|
|
globalCheckboxCache.removeMenuFromCheckboxCache(m.wailsMenu.Menu)
|
|
|
|
// Unload this menu's radio groups from the cache
|
|
globalRadioGroupCache.removeMenuFromRadioBoxCache(m.wailsMenu.Menu)
|
|
|
|
globalRadioGroupMap.removeMenuFromRadioGroupMapping(m.wailsMenu.Menu)
|
|
|
|
// Free up callbacks
|
|
resetCallbacks()
|
|
|
|
// Delete menu
|
|
return destroyWin32Menu(m.menu)
|
|
}
|
|
|
|
func (m *Menu) processAccelerator(menuitem *menumanager.ProcessedMenuItem) {
|
|
|
|
// Add in shortcut to label if there is no "\t" override
|
|
if !strings.Contains(menuitem.Label, "\t") {
|
|
menuitem.Label += "\t" + keys.Stringify(menuitem.Accelerator, runtime.GOOS)
|
|
}
|
|
|
|
// Calculate the modifier
|
|
var modifiers uint8
|
|
for _, mod := range menuitem.Accelerator.Modifiers {
|
|
switch mod {
|
|
case keys.ControlKey, keys.CmdOrCtrlKey:
|
|
modifiers |= 1
|
|
case keys.OptionOrAltKey:
|
|
modifiers |= 2
|
|
case keys.ShiftKey:
|
|
modifiers |= 4
|
|
//case keys.SuperKey:
|
|
// modifiers |= 8
|
|
}
|
|
}
|
|
|
|
var keycode = calculateKeycode(strings.ToLower(menuitem.Accelerator.Key))
|
|
if keycode == 0 {
|
|
fmt.Printf("WARNING: Key '%s' is unsupported in windows. Cannot bind callback.", menuitem.Accelerator.Key)
|
|
return
|
|
}
|
|
addMenuCallback(keycode, modifiers, menuitem.ID, m.menuType)
|
|
|
|
}
|
|
|
|
var flagMap = map[menu.Type]uint32{
|
|
menu.TextType: MF_STRING,
|
|
menu.SeparatorType: MF_SEPARATOR,
|
|
menu.SubmenuType: MF_STRING | MF_POPUP,
|
|
menu.CheckboxType: MF_STRING,
|
|
menu.RadioType: 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
|
|
}
|