[Windows] Handle menu accelerators

This commit is contained in:
Lea Anthony 2024-07-27 16:04:26 +10:00
commit d61bb9c79b
No known key found for this signature in database
GPG key ID: 33DAF7BB90A58405
8 changed files with 109 additions and 37 deletions

View file

@ -16,7 +16,7 @@ func main() {
app := application.New(application.Options{
Name: "Menu Demo",
Description: "A demo of the menu system",
//Assets: application.AlphaAssets,
Assets: application.AlphaAssets,
Mac: application.MacOptions{
ApplicationShouldTerminateAfterLastWindowClosed: true,
},
@ -35,7 +35,7 @@ func main() {
myMenu.Add("Not Enabled").SetEnabled(false)
// Click callbacks
myMenu.Add("Click Me!").SetBitmap(clickBitmap).OnClick(func(ctx *application.Context) {
myMenu.Add("Click Me!").SetAccelerator("CmdOrCtrl+l").SetBitmap(clickBitmap).OnClick(func(ctx *application.Context) {
switch ctx.ClickedMenuItem().Label() {
case "Click Me!":
ctx.ClickedMenuItem().SetLabel("Thanks mate!")

View file

@ -311,7 +311,8 @@ type App struct {
isDebugMode bool
// Keybindings
keyBindings map[string]func(window *WebviewWindow)
keyBindings map[string]func(window *WebviewWindow)
keyBindingsLock sync.RWMutex
// Shutdown
performingShutdown bool
@ -863,6 +864,9 @@ func (a *App) processKeyBinding(acceleratorString string, window *WebviewWindow)
return false
}
a.keyBindingsLock.RLock()
defer a.keyBindingsLock.RUnlock()
// Check key bindings
callback, ok := a.keyBindings[acceleratorString]
if !ok {
@ -875,6 +879,18 @@ func (a *App) processKeyBinding(acceleratorString string, window *WebviewWindow)
return true
}
func (a *App) addKeyBinding(acceleratorString string, callback func(window *WebviewWindow)) {
a.keyBindingsLock.Lock()
defer a.keyBindingsLock.Unlock()
a.keyBindings[acceleratorString] = callback
}
func (a *App) removeKeyBinding(acceleratorString string) {
a.keyBindingsLock.Lock()
defer a.keyBindingsLock.Unlock()
delete(a.keyBindings, acceleratorString)
}
func (a *App) handleWindowKeyEvent(event *windowKeyEvent) {
// Get window from window map
a.windowsLock.RLock()

View file

@ -83,9 +83,9 @@ func (a *accelerator) String() string {
}
slices.Sort(result)
if len(a.Key) > 0 {
result = append(result, a.Key)
result = append(result, strings.ToUpper(a.Key))
}
return strings.ToLower(strings.Join(result, "+"))
return strings.Join(result, "+")
}
var namedKeys = map[string]struct{}{

View file

@ -7,7 +7,8 @@ import (
)
type windowsMenu struct {
menu *Menu
menu *Menu
parentWindow *windowsWebviewWindow
hWnd w32.HWND
hMenu w32.HMENU
@ -36,6 +37,13 @@ func (w *windowsMenu) update() {
func (w *windowsMenu) processMenu(parentMenu w32.HMENU, inputMenu *Menu) {
for _, item := range inputMenu.items {
if item.Hidden() {
if item.accelerator != nil && item.callback != nil {
if w.parentWindow != nil {
w.parentWindow.parent.removeMenuBinding(item.accelerator)
} else {
globalApplication.removeKeyBinding(item.accelerator.String())
}
}
continue
}
w.currentMenuID++
@ -52,21 +60,6 @@ func (w *windowsMenu) processMenu(parentMenu w32.HMENU, inputMenu *Menu) {
if item.IsSeparator() {
flags = flags | w32.MF_SEPARATOR
}
//
//if item.IsCheckbox() {
// w.checkboxItems[item] = append(w.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 | w32.MF_POPUP
@ -75,7 +68,18 @@ func (w *windowsMenu) processMenu(parentMenu w32.HMENU, inputMenu *Menu) {
itemID = int(newSubmenu)
}
var menuText = w32.MustStringToUTF16Ptr(item.Label())
thisText := item.Label()
if item.accelerator != nil && item.callback != nil {
if w.parentWindow != nil {
w.parentWindow.parent.addMenuBinding(item.accelerator, item)
} else {
globalApplication.addKeyBinding(item.accelerator.String(), func(w *WebviewWindow) {
item.handleClick()
})
}
thisText = thisText + "\t" + item.accelerator.String()
}
var menuText = w32.MustStringToUTF16Ptr(thisText)
w32.AppendMenu(parentMenu, flags, uintptr(itemID), menuText)
if item.bitmap != nil {

View file

@ -336,15 +336,12 @@ func (m *windowsMenuItem) getMenuInfo() *w32.MENUITEMINFO {
mii.FType = w32.MFT_SEPARATOR
} else {
mii.FType = w32.MFT_STRING
//var text string
//if s := a.shortcut; s.Key != 0 {
// text = fmt.Sprintf("%s\t%s", a.text, s.String())
// shortcut2Action[a.shortcut] = a
//} else {
// text = a.text
//}
mii.DwTypeData = w32.MustStringToUTF16Ptr(m.label)
mii.Cch = uint32(len([]rune(m.label)))
thisText := m.label
if m.menuItem.accelerator != nil {
thisText += "\t" + m.menuItem.accelerator.String()
}
mii.DwTypeData = w32.MustStringToUTF16Ptr(thisText)
mii.Cch = uint32(len([]rune(thisText)))
}
mii.WID = uint32(m.id)
if m.Enabled() {

View file

@ -40,6 +40,7 @@ func (r *RadioGroup) MenuID(item *MenuItem) int {
type Win32Menu struct {
isPopup bool
menu w32.HMENU
parentWindow *windowsWebviewWindow
parent w32.HWND
menuMapping map[int]*MenuItem
checkboxItems map[*MenuItem][]int
@ -61,6 +62,15 @@ func (p *Win32Menu) buildMenu(parentMenu w32.HMENU, inputMenu *Menu) {
var currentRadioGroup RadioGroup
for _, item := range inputMenu.items {
if item.Hidden() {
if item.accelerator != nil {
if p.parentWindow != nil {
// Remove the accelerator from the keybindings
p.parentWindow.parent.removeMenuBinding(item.accelerator)
} else {
// Remove the global keybindings
globalApplication.removeKeyBinding(item.accelerator.String())
}
}
continue
}
p.currentMenuID++
@ -85,7 +95,9 @@ func (p *Win32Menu) buildMenu(parentMenu w32.HMENU, inputMenu *Menu) {
p.checkboxItems[item] = append(p.checkboxItems[item], itemID)
}
if item.IsRadio() {
currentRadioGroup.Add(itemID, item)
if currentRadioGroup != nil {
currentRadioGroup.Add(itemID, item)
}
} else {
if len(currentRadioGroup) > 0 {
for _, radioMember := range currentRadioGroup {
@ -105,6 +117,18 @@ func (p *Win32Menu) buildMenu(parentMenu w32.HMENU, inputMenu *Menu) {
}
var menuText = item.Label()
if item.accelerator != nil {
menuText = menuText + "\t" + item.accelerator.String()
if item.callback != nil {
if p.parentWindow != nil {
p.parentWindow.parent.addMenuBinding(item.accelerator, item)
} else {
globalApplication.addKeyBinding(item.accelerator.String(), func(w *WebviewWindow) {
item.handleClick()
})
}
}
}
ok := w32.AppendMenu(parentMenu, flags, uintptr(itemID), w32.MustStringToUTF16Ptr(menuText))
if !ok {
w32.Fatal(fmt.Sprintf("Error adding menu item: %s", menuText))
@ -146,9 +170,10 @@ func NewPopupMenu(parent w32.HWND, inputMenu *Menu) *Win32Menu {
result.Update()
return result
}
func NewApplicationMenu(parent w32.HWND, inputMenu *Menu) *Win32Menu {
func NewApplicationMenu(parent *windowsWebviewWindow, inputMenu *Menu) *Win32Menu {
result := &Win32Menu{
parent: parent,
parentWindow: parent,
parent: parent.hwnd,
menuData: inputMenu,
checkboxItems: make(map[*MenuItem][]int),
radioGroups: make(map[*MenuItem][]*RadioGroup),

View file

@ -132,7 +132,12 @@ type WebviewWindow struct {
cancellers []func()
// keyBindings holds the keybindings for the window
keyBindings map[string]func(window *WebviewWindow)
keyBindings map[string]func(*WebviewWindow)
keyBindingsLock sync.RWMutex
// menuBindings holds the menu bindings for the window
menuBindings map[string]*MenuItem
menuBindingsLock sync.RWMutex
// Indicates that the window is destroyed
destroyed bool
@ -211,6 +216,7 @@ func NewWindow(options WebviewWindowOptions) *WebviewWindow {
eventListeners: make(map[uint][]*WindowEventListener),
contextMenus: make(map[string]*Menu),
eventHooks: make(map[uint][]*WindowEventListener),
menuBindings: make(map[string]*MenuItem),
}
result.setupEventMapping()
@ -1176,12 +1182,23 @@ func (w *WebviewWindow) SetPosition(x int, y int) {
}
func (w *WebviewWindow) processKeyBinding(acceleratorString string) bool {
// Check menu bindings
if w.menuBindings != nil {
w.menuBindingsLock.RLock()
defer w.menuBindingsLock.RUnlock()
if menuItem := w.menuBindings[acceleratorString]; menuItem != nil {
menuItem.handleClick()
return true
}
}
// Check key bindings
if w.keyBindings != nil {
w.keyBindingsLock.RLock()
defer w.keyBindingsLock.RUnlock()
if callback := w.keyBindings[acceleratorString]; callback != nil {
// Execute callback
go callback(w)
return true
}
}
@ -1203,3 +1220,15 @@ func (w *WebviewWindow) isDestroyed() bool {
defer w.destroyedLock.RUnlock()
return w.destroyed
}
func (w *WebviewWindow) removeMenuBinding(a *accelerator) {
w.menuBindingsLock.Lock()
defer w.menuBindingsLock.Unlock()
w.menuBindings[a.String()] = nil
}
func (w *WebviewWindow) addMenuBinding(a *accelerator, menuItem *MenuItem) {
w.menuBindingsLock.Lock()
defer w.menuBindingsLock.Unlock()
w.menuBindings[a.String()] = menuItem
}

View file

@ -234,7 +234,8 @@ func (w *windowsWebviewWindow) run() {
theMenu = w.parent.options.Windows.Menu
}
if theMenu != nil {
w.menu = NewApplicationMenu(w.hwnd, theMenu)
w.menu = NewApplicationMenu(w, theMenu)
w.menu.parentWindow = w
appMenu = w.menu.menu
}
}