5
0
mirror of https://github.com/wailsapp/wails.git synced 2025-05-02 04:59:38 +08:00
wails/v2/internal/platform/systray/menu_windows.go
Lea Anthony b84a2e5255
Windows tray menus (#2181)
* Add example

* Add windows systray

* Add gitkeep

* use windows.GUID
2022-12-06 20:55:56 +11:00

223 lines
5.1 KiB
Go

//go:build windows
package systray
import (
"errors"
"fmt"
platformMenu "github.com/wailsapp/wails/v2/internal/platform/menu"
"github.com/wailsapp/wails/v2/internal/platform/win32"
"github.com/wailsapp/wails/v2/pkg/menu"
)
type RadioGroupMember struct {
ID int
MenuItem *menu.MenuItem
}
type RadioGroup []*RadioGroupMember
func (r *RadioGroup) Add(id int, item *menu.MenuItem) {
*r = append(*r, &RadioGroupMember{
ID: id,
MenuItem: item,
})
}
func (r *RadioGroup) Bounds() (int, int) {
p := *r
return p[0].ID, p[len(p)-1].ID
}
func (r *RadioGroup) MenuID(item *menu.MenuItem) int {
for _, member := range *r {
if member.MenuItem == item {
return member.ID
}
}
panic("RadioGroup.MenuID: item not found:")
}
type PopupMenu struct {
menu win32.PopupMenu
parent win32.HWND
menuMapping map[int]*menu.MenuItem
checkboxItems map[*menu.MenuItem][]int
radioGroups map[*menu.MenuItem][]*RadioGroup
menuData *menu.Menu
currentMenuID int
onMenuClose func()
onMenuOpen func()
}
func (p *PopupMenu) buildMenu(parentMenu win32.PopupMenu, inputMenu *menu.Menu) error {
var currentRadioGroup RadioGroup
for _, item := range inputMenu.Items {
if item.Hidden {
continue
}
var ret bool
p.currentMenuID++
itemID := p.currentMenuID
p.menuMapping[itemID] = item
flags := win32.MF_STRING
if item.Disabled {
flags = flags | win32.MF_GRAYED
}
if item.Checked {
flags = flags | win32.MF_CHECKED
}
//if item.BarBreak {
// flags = flags | win32.MF_MENUBARBREAK
//}
if item.IsSeparator() {
flags = flags | win32.MF_SEPARATOR
}
if item.IsCheckbox() {
p.checkboxItems[item] = append(p.checkboxItems[item], itemID)
}
if item.IsRadio() {
currentRadioGroup.Add(itemID, item)
} else {
if len(currentRadioGroup) > 0 {
for _, radioMember := range currentRadioGroup {
currentRadioGroup := currentRadioGroup
p.radioGroups[radioMember.MenuItem] = append(p.radioGroups[radioMember.MenuItem], &currentRadioGroup)
}
currentRadioGroup = RadioGroup{}
}
}
if item.SubMenu != nil {
flags = flags | win32.MF_POPUP
submenu := win32.CreatePopupMenu()
err := p.buildMenu(submenu, item.SubMenu)
if err != nil {
return err
}
itemID = int(submenu)
}
var menuText = item.Label
if item.Accelerator != nil {
shortcut := win32.AcceleratorToShortcut(item.Accelerator)
menuText = fmt.Sprintf("%s\t%s", menuText, shortcut)
// Popup Menus don't appear to support accelerators and I'm not
// sure they make sense either
}
ret = parentMenu.Append(uintptr(flags), uintptr(itemID), menuText)
if ret == false {
return errors.New("AppendMenu failed")
}
}
if len(currentRadioGroup) > 0 {
for _, radioMember := range currentRadioGroup {
currentRadioGroup := currentRadioGroup
p.radioGroups[radioMember.MenuItem] = append(p.radioGroups[radioMember.MenuItem], &currentRadioGroup)
}
currentRadioGroup = RadioGroup{}
}
return nil
}
func (p *PopupMenu) Update() error {
p.menu = win32.CreatePopupMenu()
p.menuMapping = make(map[int]*menu.MenuItem)
p.currentMenuID = win32.MenuItemMsgID
err := p.buildMenu(p.menu, p.menuData)
if err != nil {
return err
}
p.updateRadioGroups()
return nil
}
func NewPopupMenu(parent win32.HWND, inputMenu *menu.Menu) (*PopupMenu, error) {
result := &PopupMenu{
parent: parent,
menuData: inputMenu,
checkboxItems: make(map[*menu.MenuItem][]int),
radioGroups: make(map[*menu.MenuItem][]*RadioGroup),
}
err := result.Update()
platformMenu.MenuManager.AddMenu(inputMenu, result.UpdateMenuItem)
return result, err
}
func (p *PopupMenu) ShowAtCursor() error {
x, y, ok := win32.GetCursorPos()
if ok == false {
return errors.New("GetCursorPos failed")
}
if win32.SetForegroundWindow(p.parent) == false {
return errors.New("SetForegroundWindow failed")
}
if p.onMenuOpen != nil {
p.onMenuOpen()
}
if p.menu.Track(win32.TPM_LEFTALIGN, x, y-5, p.parent) == false {
return errors.New("TrackPopupMenu failed")
}
if p.onMenuClose != nil {
p.onMenuClose()
}
if win32.PostMessage(p.parent, win32.WM_NULL, 0, 0) == 0 {
return errors.New("PostMessage failed")
}
return nil
}
func (p *PopupMenu) ProcessCommand(cmdMsgID int) {
item := p.menuMapping[cmdMsgID]
platformMenu.MenuManager.ProcessClick(item)
}
func (p *PopupMenu) Destroy() {
p.menu.Destroy()
}
func (p *PopupMenu) UpdateMenuItem(item *menu.MenuItem) {
if item.IsCheckbox() {
for _, itemID := range p.checkboxItems[item] {
p.menu.Check(uintptr(itemID), item.Checked)
}
return
}
if item.IsRadio() && item.Checked == true {
p.updateRadioGroup(item)
}
}
func (p *PopupMenu) updateRadioGroups() {
for menuItem := range p.radioGroups {
if menuItem.Checked {
p.updateRadioGroup(menuItem)
}
}
}
func (p *PopupMenu) updateRadioGroup(item *menu.MenuItem) {
for _, radioGroup := range p.radioGroups[item] {
thisMenuID := radioGroup.MenuID(item)
startID, endID := radioGroup.Bounds()
p.menu.CheckRadio(startID, endID, thisMenuID)
}
}
func (p *PopupMenu) OnMenuOpen(fn func()) {
p.onMenuOpen = fn
}
func (p *PopupMenu) OnMenuClose(fn func()) {
p.onMenuClose = fn
}