mirror of
https://github.com/wailsapp/wails.git
synced 2026-03-14 14:45:49 +01:00
Support Menu Accelerators
This commit is contained in:
parent
d84cb5b190
commit
79af667b7a
5 changed files with 223 additions and 25 deletions
|
|
@ -18,7 +18,7 @@ var icon []byte
|
|||
var macosIcon []byte
|
||||
|
||||
func main() {
|
||||
app := application.New(&options.Application{
|
||||
app := application.NewWithOptions(&options.Application{
|
||||
Mac: &options.Mac{
|
||||
//ActivationPolicy: options.ActivationPolicyAccessory,
|
||||
},
|
||||
|
|
@ -60,23 +60,26 @@ func main() {
|
|||
file1 := myMenu.Add("File")
|
||||
file1.SetTooltip("Create New Tray Menu")
|
||||
file1.OnClick(menuCallback)
|
||||
myMenu.Add("Create New Tray Menu").SetTooltip("ROFLCOPTER!!!!").OnClick(func(ctx *application.Context) {
|
||||
mySystray := app.NewSystemTray()
|
||||
mySystray.SetLabel("Wails")
|
||||
if runtime.GOOS == "darwin" {
|
||||
mySystray.SetTemplateIcon(macosIcon)
|
||||
} else {
|
||||
mySystray.SetIcon(icon)
|
||||
}
|
||||
myMenu := app.NewMenu()
|
||||
myMenu.Add("Item 1")
|
||||
myMenu.AddSeparator()
|
||||
myMenu.Add("Kill this menu").OnClick(func(ctx *application.Context) {
|
||||
mySystray.Destroy()
|
||||
})
|
||||
mySystray.SetMenu(myMenu)
|
||||
myMenu.Add("Create New Tray Menu").
|
||||
SetAccelerator("CmdOrCtrl+N").
|
||||
SetTooltip("ROFLCOPTER!!!!").
|
||||
OnClick(func(ctx *application.Context) {
|
||||
mySystray := app.NewSystemTray()
|
||||
mySystray.SetLabel("Wails")
|
||||
if runtime.GOOS == "darwin" {
|
||||
mySystray.SetTemplateIcon(macosIcon)
|
||||
} else {
|
||||
mySystray.SetIcon(icon)
|
||||
}
|
||||
myMenu := app.NewMenu()
|
||||
myMenu.Add("Item 1")
|
||||
myMenu.AddSeparator()
|
||||
myMenu.Add("Kill this menu").OnClick(func(ctx *application.Context) {
|
||||
mySystray.Destroy()
|
||||
})
|
||||
mySystray.SetMenu(myMenu)
|
||||
|
||||
})
|
||||
})
|
||||
myMenu.Add("Not Enabled").SetEnabled(false)
|
||||
myMenu.AddSeparator()
|
||||
myMenu.AddCheckbox("My checkbox", true).OnClick(menuCallback)
|
||||
|
|
|
|||
114
exp/pkg/application/keys.go
Normal file
114
exp/pkg/application/keys.go
Normal file
|
|
@ -0,0 +1,114 @@
|
|||
package application
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/samber/lo"
|
||||
)
|
||||
|
||||
// modifier is actually a string
|
||||
type modifier int
|
||||
|
||||
const (
|
||||
// CmdOrCtrlKey represents Command on Mac and Control on other platforms
|
||||
CmdOrCtrlKey modifier = 0 << iota
|
||||
// OptionOrAltKey represents Option on Mac and Alt on other platforms
|
||||
OptionOrAltKey modifier = 1 << iota
|
||||
// ShiftKey represents the shift key on all systems
|
||||
ShiftKey modifier = 2 << iota
|
||||
// SuperKey represents Command on Mac and the Windows key on the other platforms
|
||||
SuperKey modifier = 3 << iota
|
||||
// ControlKey represents the control key on all systems
|
||||
ControlKey modifier = 4 << iota
|
||||
)
|
||||
|
||||
var modifierMap = map[string]modifier{
|
||||
"cmdorctrl": CmdOrCtrlKey,
|
||||
"optionoralt": OptionOrAltKey,
|
||||
"shift": ShiftKey,
|
||||
"super": SuperKey,
|
||||
"ctrl": ControlKey,
|
||||
}
|
||||
|
||||
// accelerator holds the keyboard shortcut for a menu item
|
||||
type accelerator struct {
|
||||
Key string
|
||||
Modifiers []modifier
|
||||
}
|
||||
|
||||
var namedKeys = []string{"backspace", "tab", "return", "enter", "escape", "left", "right", "up", "down", "space", "delete", "home", "end", "page up", "page down", "f1", "f2", "f3", "f4", "f5", "f6", "f7", "f8", "f9", "f10", "f11", "f12", "f13", "f14", "f15", "f16", "f17", "f18", "f19", "f20", "f21", "f22", "f23", "f24", "f25", "f26", "f27", "f28", "f29", "f30", "f31", "f32", "f33", "f34", "f35", "numlock"}
|
||||
|
||||
func parseKey(key string) (string, bool) {
|
||||
|
||||
// Lowercase!
|
||||
key = strings.ToLower(key)
|
||||
|
||||
// Check special case
|
||||
if key == "plus" {
|
||||
return "+", true
|
||||
}
|
||||
|
||||
// Handle named keys
|
||||
if lo.Contains(namedKeys, key) {
|
||||
return key, true
|
||||
}
|
||||
|
||||
// Check we only have a single character
|
||||
if len(key) != 1 {
|
||||
return "", false
|
||||
}
|
||||
|
||||
runeKey := rune(key[0])
|
||||
|
||||
// This may be too inclusive
|
||||
if strconv.IsPrint(runeKey) {
|
||||
return key, true
|
||||
}
|
||||
|
||||
return "", false
|
||||
|
||||
}
|
||||
|
||||
// parseAccelerator parses a string into an accelerator
|
||||
func parseAccelerator(shortcut string) (*accelerator, error) {
|
||||
|
||||
var result accelerator
|
||||
|
||||
// Split the shortcut by +
|
||||
components := strings.Split(shortcut, "+")
|
||||
|
||||
// If we only have one it should be a key
|
||||
// We require components
|
||||
if len(components) == 0 {
|
||||
return nil, fmt.Errorf("no components given to validateComponents")
|
||||
}
|
||||
|
||||
// Check components
|
||||
for index, component := range components {
|
||||
|
||||
// If last component
|
||||
if index == len(components)-1 {
|
||||
processedKey, validKey := parseKey(component)
|
||||
if !validKey {
|
||||
return nil, fmt.Errorf("'%s' is not a valid key", component)
|
||||
}
|
||||
result.Key = processedKey
|
||||
continue
|
||||
}
|
||||
|
||||
// Not last component - needs to be modifier
|
||||
lowercaseComponent := strings.ToLower(component)
|
||||
thisModifier, valid := modifierMap[lowercaseComponent]
|
||||
if !valid {
|
||||
return nil, fmt.Errorf("'%s' is not a valid modifier", component)
|
||||
}
|
||||
// Save this data
|
||||
result.Modifiers = append(result.Modifiers, thisModifier)
|
||||
}
|
||||
|
||||
result.Modifiers = lo.Uniq(result.Modifiers)
|
||||
|
||||
return &result, nil
|
||||
}
|
||||
28
exp/pkg/application/keys_darwin.go
Normal file
28
exp/pkg/application/keys_darwin.go
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
//go:build darwin
|
||||
|
||||
package application
|
||||
|
||||
const (
|
||||
NSEventModifierFlagShift = 1 << 17 // Set if Shift key is pressed.
|
||||
NSEventModifierFlagControl = 1 << 18 // Set if Control key is pressed.
|
||||
NSEventModifierFlagOption = 1 << 19 // Set if Option or Alternate key is pressed.
|
||||
NSEventModifierFlagCommand = 1 << 20 // Set if Command key is pressed.
|
||||
)
|
||||
|
||||
// macModifierMap maps accelerator modifiers to macOS modifiers.
|
||||
var macModifierMap = map[modifier]int{
|
||||
CmdOrCtrlKey: NSEventModifierFlagCommand,
|
||||
ControlKey: NSEventModifierFlagControl,
|
||||
OptionOrAltKey: NSEventModifierFlagOption,
|
||||
ShiftKey: NSEventModifierFlagShift,
|
||||
SuperKey: NSEventModifierFlagCommand,
|
||||
}
|
||||
|
||||
// toMacModifier converts the accelerator to a macOS modifier.
|
||||
func toMacModifier(modifiers []modifier) int {
|
||||
result := 0
|
||||
for _, modifier := range modifiers {
|
||||
result |= macModifierMap[modifier]
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
|
@ -36,17 +36,19 @@ type menuItemImpl interface {
|
|||
setLabel(s string)
|
||||
setDisabled(disabled bool)
|
||||
setChecked(checked bool)
|
||||
setAccelerator(accelerator *accelerator)
|
||||
}
|
||||
|
||||
type MenuItem struct {
|
||||
id uint
|
||||
label string
|
||||
tooltip string
|
||||
disabled bool
|
||||
checked bool
|
||||
submenu *Menu
|
||||
callback func(*Context)
|
||||
itemType menuItemType
|
||||
id uint
|
||||
label string
|
||||
tooltip string
|
||||
disabled bool
|
||||
checked bool
|
||||
submenu *Menu
|
||||
callback func(*Context)
|
||||
itemType menuItemType
|
||||
accelerator *accelerator
|
||||
|
||||
impl menuItemImpl
|
||||
radioGroupMembers []*MenuItem
|
||||
|
|
@ -123,6 +125,19 @@ func (m *MenuItem) handleClick() {
|
|||
}
|
||||
}
|
||||
|
||||
func (m *MenuItem) SetAccelerator(shortcut string) *MenuItem {
|
||||
accelerator, err := parseAccelerator(shortcut)
|
||||
if err != nil {
|
||||
println("ERROR: invalid accelerator", err)
|
||||
return m
|
||||
}
|
||||
m.accelerator = accelerator
|
||||
if m.impl != nil {
|
||||
m.impl.setAccelerator(accelerator)
|
||||
}
|
||||
return m
|
||||
}
|
||||
|
||||
func (m *MenuItem) SetTooltip(s string) *MenuItem {
|
||||
m.tooltip = s
|
||||
if m.impl != nil {
|
||||
|
|
@ -147,6 +162,18 @@ func (m *MenuItem) SetEnabled(enabled bool) *MenuItem {
|
|||
return m
|
||||
}
|
||||
|
||||
func (m *MenuItem) SetChecked(checked bool) *MenuItem {
|
||||
m.checked = checked
|
||||
if m.impl != nil {
|
||||
m.impl.setChecked(m.checked)
|
||||
}
|
||||
return m
|
||||
}
|
||||
|
||||
func (m *MenuItem) Checked() bool {
|
||||
return m.checked
|
||||
}
|
||||
|
||||
func (m *MenuItem) OnClick(f func(*Context)) *MenuItem {
|
||||
m.callback = f
|
||||
return m
|
||||
|
|
|
|||
|
|
@ -69,6 +69,15 @@ void setMenuItemChecked(void* nsMenuItem, bool checked) {
|
|||
menuItem.state = checked ? NSControlStateValueOn : NSControlStateValueOff;
|
||||
}
|
||||
|
||||
// Set the menuitem key equivalent
|
||||
void setMenuItemKeyEquivalent(void* nsMenuItem, char *key, int modifier) {
|
||||
MenuItem *menuItem = (MenuItem *)nsMenuItem;
|
||||
menuItem.keyEquivalent = [NSString stringWithUTF8String:key];
|
||||
menuItem.keyEquivalentModifierMask = modifier;
|
||||
free(key);
|
||||
}
|
||||
|
||||
|
||||
*/
|
||||
import "C"
|
||||
import (
|
||||
|
|
@ -97,6 +106,20 @@ func (m macosMenuItem) setChecked(checked bool) {
|
|||
C.setMenuItemChecked(m.nsMenuItem, C.bool(checked))
|
||||
}
|
||||
|
||||
func (m macosMenuItem) setAccelerator(accelerator *accelerator) {
|
||||
// Set the keyboard shortcut of the menu item
|
||||
var modifier C.int
|
||||
var key *C.char
|
||||
if accelerator != nil {
|
||||
modifier = C.int(toMacModifier(accelerator.Modifiers))
|
||||
key = C.CString(accelerator.Key)
|
||||
}
|
||||
|
||||
// Convert the key to a string
|
||||
C.setMenuItemKeyEquivalent(m.nsMenuItem, key, modifier)
|
||||
|
||||
}
|
||||
|
||||
func newMenuItemImpl(item *MenuItem) *macosMenuItem {
|
||||
result := &macosMenuItem{
|
||||
menuItem: item,
|
||||
|
|
@ -107,6 +130,9 @@ func newMenuItemImpl(item *MenuItem) *macosMenuItem {
|
|||
if item.itemType == checkbox || item.itemType == radio {
|
||||
C.setMenuItemChecked(result.nsMenuItem, C.bool(item.checked))
|
||||
}
|
||||
if item.accelerator != nil {
|
||||
result.setAccelerator(item.accelerator)
|
||||
}
|
||||
default:
|
||||
panic("WTF")
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue