mirror of
https://github.com/wailsapp/wails.git
synced 2025-05-02 04:59:38 +08:00
223 lines
5.1 KiB
Go
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], ¤tRadioGroup)
|
|
}
|
|
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], ¤tRadioGroup)
|
|
}
|
|
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
|
|
}
|