Refactor and support more roles

This commit is contained in:
Lea Anthony 2022-12-16 21:09:31 +11:00
commit 88f6a3d1b9
No known key found for this signature in database
GPG key ID: 33DAF7BB90A58405
14 changed files with 421 additions and 96 deletions

View file

@ -1,7 +1,6 @@
package main
import (
_ "embed"
"log"
"github.com/wailsapp/wails/exp/pkg/application"
@ -10,15 +9,12 @@ import (
func main() {
app := application.New()
myMenu := app.NewMenu()
myMenu.AddRole(application.AppMenu)
myMenu.AddRole(application.EditMenu)
app.SetMenu(myMenu)
app.NewWindow()
app.NewWindow()
err := app.Run()
if err != nil {
log.Fatal(err)
log.Fatal(err.Error())
}
}

View file

@ -1,8 +1,13 @@
//go:build darwin
#ifndef appdelegate_h
#define appdelegate_h
#import <Cocoa/Cocoa.h>
@interface AppDelegate : NSObject <NSApplicationDelegate>
@property NSApplicationActivationPolicy activationPolicy;
- (void)setApplicationActivationPolicy:(NSApplicationActivationPolicy)policy;
@end
@end
#endif

View file

@ -7,7 +7,6 @@ import (
"sync"
"github.com/wailsapp/wails/exp/pkg/events"
"github.com/wailsapp/wails/exp/pkg/options"
)
@ -45,6 +44,7 @@ type platformApp interface {
destroy()
setApplicationMenu(menu *Menu)
name() string
getCurrentWindowID() uint
}
// Messages sent from javascript get routed here
@ -234,6 +234,16 @@ func (a *App) handleMenuItemClicked(menuItemID uint) {
menuItem.handleClick()
}
func (a *App) GetCurrentWindow() *Window {
if a.impl == nil {
return nil
}
id := a.impl.getCurrentWindowID()
a.windowsLock.Lock()
defer a.windowsLock.Unlock()
return a.windows[id]
}
func (a *App) Quit() {
var wg sync.WaitGroup
wg.Add(2)

View file

@ -9,6 +9,7 @@ package application
#include "application.h"
#include "app_delegate.h"
#include "window_delegate.h"
#include <stdlib.h>
#import <Cocoa/Cocoa.h>
@ -52,6 +53,14 @@ static char* getAppName(void) {
return strdup([appName UTF8String]);
}
// get the current window ID
static unsigned int getCurrentWindowID(void) {
NSWindow *window = [NSApp keyWindow];
// Get the window delegate
WindowDelegate *delegate = (WindowDelegate*)[window delegate];
return delegate.windowId;
}
*/
import "C"
import (
@ -71,6 +80,10 @@ func (m *macosApp) name() string {
return C.GoString(appName)
}
func (m *macosApp) getCurrentWindowID() uint {
return uint(C.getCurrentWindowID())
}
func (m *macosApp) setApplicationMenu(menu *Menu) {
if menu == nil {
// Create a default menu for mac
@ -94,8 +107,10 @@ func (m *macosApp) destroy() {
func (m *macosApp) createDefaultApplicationMenu() *Menu {
// Create a default menu for mac
menu := NewMenu()
newAppMenu(menu)
newEditMenu(menu)
menu.AddRole(AppMenu)
menu.AddRole(FileMenu)
menu.AddRole(EditMenu)
menu.AddRole(ViewMenu)
return menu
}

View file

@ -26,10 +26,14 @@ const (
var modifierMap = map[string]modifier{
"cmdorctrl": CmdOrCtrlKey,
"cmd": CmdOrCtrlKey,
"command": CmdOrCtrlKey,
"ctrl": CmdOrCtrlKey,
"optionoralt": OptionOrAltKey,
"alt": OptionOrAltKey,
"option": OptionOrAltKey,
"shift": ShiftKey,
"super": SuperKey,
"ctrl": ControlKey,
}
// accelerator holds the keyboard shortcut for a menu item

View file

@ -53,7 +53,7 @@ func (m *Menu) AddSubmenu(s string) *Menu {
}
func (m *Menu) AddRole(role Role) *Menu {
result := newRole(m, role)
result := newRole(role)
m.items = append(m.items, result)
return m
}

View file

@ -83,7 +83,7 @@ func (m *macosMenu) processMenu(parent unsafe.Pointer, menu *Menu) {
switch item.itemType {
case submenu:
submenu := item.submenu
nsSubmenu := C.createNSMenu(C.CString(submenu.label))
nsSubmenu := C.createNSMenu(C.CString(item.label))
m.processMenu(nsSubmenu, submenu)
menuItem := newMenuItemImpl(item)
item.impl = menuItem

View file

@ -1,6 +1,7 @@
package application
import (
"os"
"sync"
"sync/atomic"
)
@ -107,34 +108,74 @@ func newSubMenuItem(label string) *MenuItem {
return result
}
func newRole(parentMenu *Menu, role Role) *MenuItem {
var roleMenu *Menu
func newRole(role Role) *MenuItem {
switch role {
case AppMenu:
roleMenu = newAppMenu(parentMenu)
return newAppMenu()
case EditMenu:
roleMenu = newEditMenu(parentMenu)
return newEditMenu()
case FileMenu:
return newFileMenu()
case ViewMenu:
return newViewMenu()
case ServicesMenu:
roleMenu = newServicesMenu()
return newServicesMenu()
case SpeechMenu:
return newSpeechMenu()
case Hide:
return newHideMenuItem()
case HideOthers:
return newHideOthersMenuItem()
case UnHide:
return newUnhideMenuItem()
case Undo:
return newUndoMenuItem()
case Redo:
return newRedoMenuItem()
case Cut:
return newCutMenuItem()
case Copy:
return newCopyMenuItem()
case Paste:
return newPasteMenuItem()
case PasteAndMatchStyle:
return newPasteAndMatchStyleMenuItem()
case SelectAll:
return newSelectAllMenuItem()
case Delete:
return newDeleteMenuItem()
case Quit:
return newQuitMenuItem()
case Close:
return newCloseMenuItem()
case About:
return newAboutMenuItem()
case Reload:
return newReloadMenuItem()
case ForceReload:
return newForceReloadMenuItem()
case ToggleFullscreen:
return newToggleFullscreenMenuItem()
case ToggleDevTools:
return newToggleDevToolsMenuItem()
//case ResetZoom:
// return newResetZoomMenuItem()
//case ZoomIn:
// return newZoomInMenuItem()
//case ZoomOut:
// return newZoomOutMenuItem()
default:
println("No support for role:", role)
os.Exit(1)
}
return roleMenu.items[0]
return nil
}
func newServicesMenu() *Menu {
func newServicesMenu() *MenuItem {
serviceMenu := newSubMenuItem("Services")
serviceMenu.role = ServicesMenu
return &Menu{
label: "Services",
items: []*MenuItem{
serviceMenu,
},
}
return serviceMenu
}
func (m *MenuItem) handleClick() {

View file

@ -44,7 +44,6 @@ void setMenuItemLabel(void* nsMenuItem, char *label) {
menuItem.title = [NSString stringWithUTF8String:label];
}
// set menu item disabled
void setMenuItemDisabled(void* nsMenuItem, bool disabled) {
dispatch_async(dispatch_get_main_queue(), ^{
@ -324,9 +323,16 @@ static void showAll(void) {
[[NSApplication sharedApplication] unhideAllApplications:nil];
}
// closeWindow closes the current window
static void closeWindow(void) {
[NSApp sendAction:@selector(performClose:) to:nil from:nil];
}
*/
import "C"
import (
"runtime"
"unsafe"
)
@ -385,12 +391,9 @@ func newMenuItemImpl(item *MenuItem) *macosMenuItem {
return result
}
func newAppMenu(menu *Menu) *Menu {
appName := globalApplication.Name()
appMenu := menu.AddSubmenu(appName)
appMenu.Add("About " + appName).OnClick(func(*Context) {
//TODO: implement
})
func newAppMenu() *MenuItem {
appMenu := NewMenu()
appMenu.AddRole(About)
appMenu.AddSeparator()
appMenu.AddRole(ServicesMenu)
appMenu.AddSeparator()
@ -398,49 +401,45 @@ func newAppMenu(menu *Menu) *Menu {
appMenu.AddRole(HideOthers)
appMenu.AddRole(UnHide)
appMenu.AddSeparator()
appMenu.Add("Quit " + appName).SetAccelerator("CmdOrCtrl+q").OnClick(func(ctx *Context) {
globalApplication.Quit()
})
return appMenu
appMenu.AddRole(Quit)
subMenu := newSubMenuItem(globalApplication.Name())
subMenu.submenu = appMenu
return subMenu
}
func newEditMenu(menu *Menu) *Menu {
editMenu := menu.AddSubmenu("Edit")
editMenu.Add("Undo").SetAccelerator("CmdOrCtrl+z").OnClick(func(ctx *Context) {
C.undo()
})
editMenu.Add("Redo").SetAccelerator("CmdOrCtrl+Shift+z").OnClick(func(ctx *Context) {
C.redo()
})
func newEditMenu() *MenuItem {
editMenu := NewMenu()
editMenu.AddRole(Undo)
editMenu.AddRole(Redo)
editMenu.AddSeparator()
editMenu.Add("Cut").SetAccelerator("CmdOrCtrl+x").OnClick(func(ctx *Context) {
C.cut()
})
editMenu.Add("Copy").SetAccelerator("CmdOrCtrl+c").OnClick(func(ctx *Context) {
C.copy()
})
editMenu.Add("Paste").SetAccelerator("CmdOrCtrl+v").OnClick(func(ctx *Context) {
C.paste()
})
editMenu.Add("Paste and Match Style").SetAccelerator("CmdOrCtrl+OptionOrAlt+Shift+v").OnClick(func(ctx *Context) {
C.pasteAndMatchStyle()
})
editMenu.Add("Delete").SetAccelerator("backspace").OnClick(func(ctx *Context) {
C.delete()
})
editMenu.Add("Select All").SetAccelerator("CmdOrCtrl+a").OnClick(func(ctx *Context) {
C.selectAll()
})
editMenu.AddRole(Cut)
editMenu.AddRole(Copy)
editMenu.AddRole(Paste)
editMenu.AddRole(PasteAndMatchStyle)
editMenu.AddRole(Delete)
editMenu.AddRole(SelectAll)
editMenu.AddSeparator()
speechMenu := editMenu.AddSubmenu("Speech")
speechMenu.Add("Start Speaking").SetAccelerator("CmdOrCtrl+OptionOrAlt+Shift+.").OnClick(func(ctx *Context) {
C.startSpeaking()
})
speechMenu.Add("Stop Speaking").SetAccelerator("CmdOrCtrl+OptionOrAlt+Shift+,").OnClick(func(ctx *Context) {
C.stopSpeaking()
})
return editMenu
editMenu.AddRole(SpeechMenu)
subMenu := newSubMenuItem("Edit")
subMenu.submenu = editMenu
return subMenu
}
func newSpeechMenu() *MenuItem {
speechMenu := NewMenu()
speechMenu.Add("Start Speaking").
SetAccelerator("CmdOrCtrl+OptionOrAlt+Shift+.").
OnClick(func(ctx *Context) {
C.startSpeaking()
})
speechMenu.Add("Stop Speaking").
SetAccelerator("CmdOrCtrl+OptionOrAlt+Shift+,").
OnClick(func(ctx *Context) {
C.stopSpeaking()
})
subMenu := newSubMenuItem("Speech")
subMenu.submenu = speechMenu
return subMenu
}
func newHideMenuItem() *MenuItem {
@ -465,3 +464,140 @@ func newUnhideMenuItem() *MenuItem {
C.showAll()
})
}
func newUndoMenuItem() *MenuItem {
return newMenuItem("Undo").
SetAccelerator("CmdOrCtrl+z").
OnClick(func(ctx *Context) {
C.undo()
})
}
// newRedoMenuItem creates a new menu item for redoing the last action
func newRedoMenuItem() *MenuItem {
return newMenuItem("Redo").
SetAccelerator("CmdOrCtrl+Shift+z").
OnClick(func(ctx *Context) {
C.redo()
})
}
func newCutMenuItem() *MenuItem {
return newMenuItem("Cut").
SetAccelerator("CmdOrCtrl+x").
OnClick(func(ctx *Context) {
C.cut()
})
}
func newCopyMenuItem() *MenuItem {
return newMenuItem("Copy").
SetAccelerator("CmdOrCtrl+c").
OnClick(func(ctx *Context) {
C.copy()
})
}
func newPasteMenuItem() *MenuItem {
return newMenuItem("Paste").
SetAccelerator("CmdOrCtrl+v").
OnClick(func(ctx *Context) {
C.paste()
})
}
func newPasteAndMatchStyleMenuItem() *MenuItem {
return newMenuItem("Paste and Match Style").
SetAccelerator("CmdOrCtrl+OptionOrAlt+Shift+v").
OnClick(func(ctx *Context) {
C.pasteAndMatchStyle()
})
}
func newDeleteMenuItem() *MenuItem {
return newMenuItem("Delete").
SetAccelerator("backspace").
OnClick(func(ctx *Context) {
C.delete()
})
}
func newQuitMenuItem() *MenuItem {
return newMenuItem("Quit " + globalApplication.Name()).
SetAccelerator("CmdOrCtrl+q").
OnClick(func(ctx *Context) {
globalApplication.Quit()
})
}
func newSelectAllMenuItem() *MenuItem {
return newMenuItem("Select All").
SetAccelerator("CmdOrCtrl+a").
OnClick(func(ctx *Context) {
C.selectAll()
})
}
func newAboutMenuItem() *MenuItem {
return newMenuItem("About " + globalApplication.Name()).
OnClick(func(ctx *Context) {
// globalApplication.About()
})
}
func newCloseMenuItem() *MenuItem {
return newMenuItem("Close").
SetAccelerator("CmdOrCtrl+w").
OnClick(func(ctx *Context) {
C.closeWindow()
})
}
func newReloadMenuItem() *MenuItem {
return newMenuItem("Reload").
SetAccelerator("CmdOrCtrl+r").
OnClick(func(ctx *Context) {
currentWindow := globalApplication.GetCurrentWindow()
if currentWindow != nil {
currentWindow.Reload()
}
})
}
func newForceReloadMenuItem() *MenuItem {
return newMenuItem("Force Reload").
SetAccelerator("CmdOrCtrl+Shift+r").
OnClick(func(ctx *Context) {
currentWindow := globalApplication.GetCurrentWindow()
if currentWindow != nil {
currentWindow.ForceReload()
}
})
}
func newToggleFullscreenMenuItem() *MenuItem {
result := newMenuItem("Toggle Full Screen").
OnClick(func(ctx *Context) {
currentWindow := globalApplication.GetCurrentWindow()
if currentWindow != nil {
currentWindow.ToggleFullscreen()
}
})
if runtime.GOOS == "darwin" {
result.SetAccelerator("Ctrl+Command+F")
} else {
result.SetAccelerator("F11")
}
return result
}
func newToggleDevToolsMenuItem() *MenuItem {
return newMenuItem("Toggle Developer Tools").
SetAccelerator("Alt+Command+I").
OnClick(func(ctx *Context) {
currentWindow := globalApplication.GetCurrentWindow()
if currentWindow != nil {
currentWindow.ToggleDevTools()
}
})
}

View file

@ -1,5 +1,7 @@
package application
import "runtime"
// Heavily inspired by Electron (c) 2013-2020 Github Inc.
// Electron License: https://github.com/electron/electron/blob/master/LICENSE
@ -8,26 +10,38 @@ type Role uint
// These constants need to be kept in sync with `v2/internal/frontend/desktop/darwin/Role.h`
const (
NoRole Role = 0
AppMenu Role = 1
EditMenu Role = 2
ServicesMenu Role = 3
Hide Role = 4
HideOthers Role = 5
UnHide Role = 6
//AboutRole Role = "about"
//UndoRole Role = "undo"
//RedoRole Role = "redo"
//CutRole Role = "cut"
//CopyRole Role = "copy"
//PasteRole Role = "paste"
//PasteAndMatchStyleRole Role = "pasteAndMatchStyle"
//SelectAllRole Role = "selectAll"
//DeleteRole Role = "delete"
//MinimizeRole Role = "minimize"
//QuitRole Role = "quit"
NoRole Role = iota
AppMenu Role = iota
EditMenu Role = iota
ViewMenu Role = iota
ServicesMenu Role = iota
Hide Role = iota
HideOthers Role = iota
UnHide Role = iota
About Role = iota
Undo Role = iota
Redo Role = iota
Cut Role = iota
Copy Role = iota
Paste Role = iota
PasteAndMatchStyle Role = iota
SelectAll Role = iota
Delete Role = iota
SpeechMenu Role = iota
Quit Role = iota
FileMenu Role = iota
Close Role = iota
Reload Role = iota
ForceReload Role = iota
ToggleDevTools Role = iota
ResetZoom Role = iota
ZoomIn Role = iota
ZoomOut Role = iota
ToggleFullscreen Role = iota
//MinimizeRole Role =
//QuitRole Role =
//TogglefullscreenRole Role = "togglefullscreen"
//FileMenuRole Role = "fileMenu"
//ViewMenuRole Role = "viewMenu"
//WindowMenuRole Role = "windowMenu"
@ -37,3 +51,47 @@ const (
//HelpSubMenuRole Role = "helpSubMenu"
//SeparatorItemRole Role = "separatorItem"
)
func newFileMenu() *MenuItem {
fileMenu := NewMenu()
if runtime.GOOS == "darwin" {
fileMenu.AddRole(Close)
} else {
fileMenu.AddRole(Quit)
}
subMenu := newSubMenuItem("File")
subMenu.submenu = fileMenu
return subMenu
}
/*
{
label: 'View',
submenu: [
{ role: 'reload' },
{ role: 'forceReload' },
{ role: 'toggleDevTools' },
{ type: 'separator' },
{ role: 'resetZoom' },
{ role: 'zoomIn' },
{ role: 'zoomOut' },
{ type: 'separator' },
{ role: 'togglefullscreen' }
]
},
*/
func newViewMenu() *MenuItem {
viewMenu := NewMenu()
viewMenu.AddRole(Reload)
viewMenu.AddRole(ForceReload)
viewMenu.AddRole(ToggleDevTools)
viewMenu.AddSeparator()
//viewMenu.AddRole(ResetZoom)
//viewMenu.AddRole(ZoomIn)
//viewMenu.AddRole(ZoomOut)
viewMenu.AddSeparator()
viewMenu.AddRole(ToggleFullscreen)
subMenu := newSubMenuItem("View")
subMenu.submenu = viewMenu
return subMenu
}

View file

@ -33,6 +33,10 @@ type windowImpl interface {
height() int
position() (int, int)
destroy()
reload()
forceReload()
toggleFullscreen()
toggleDevTools()
}
type Window struct {
@ -291,3 +295,31 @@ func (w *Window) Destroy() {
}
w.impl.destroy()
}
func (w *Window) Reload() {
if w.impl == nil {
return
}
w.impl.reload()
}
func (w *Window) ForceReload() {
if w.impl == nil {
return
}
w.impl.forceReload()
}
func (w *Window) ToggleFullscreen() {
if w.impl == nil {
return
}
w.impl.toggleFullscreen()
}
func (w *Window) ToggleDevTools() {
if w.impl == nil {
return
}
w.impl.toggleDevTools()
}

View file

@ -248,6 +248,14 @@ void windowSetMaximised(void* nsWindow) {
});
}
// toggle fullscreen
void windowToggleFullscreen(void* nsWindow) {
// Toggle fullscreen on main thread
dispatch_async(dispatch_get_main_queue(), ^{
[(NSWindow*)nsWindow toggleFullScreen:nil];
});
}
// Set Window fullscreen
void windowSetFullscreen(void* nsWindow) {
// Set window fullscreen on main thread
@ -462,7 +470,6 @@ void windowDestroy(void* nsWindow) {
});
}
*/
import "C"
import (
@ -472,10 +479,32 @@ import (
"github.com/wailsapp/wails/exp/pkg/options"
)
var showDevTools = func(window unsafe.Pointer) {}
type macosWindow struct {
id uint
nsWindow unsafe.Pointer
options *options.Window
// devtools
}
func (w *macosWindow) toggleDevTools() {
showDevTools(w.nsWindow)
}
func (w *macosWindow) toggleFullscreen() {
C.windowToggleFullscreen(w.nsWindow)
}
func (w *macosWindow) reload() {
//TODO: Implement
println("reload called on Window", w.id)
}
func (w *macosWindow) forceReload() {
//TODO: Implement
println("forceReload called on Window", w.id)
}
func (w *macosWindow) center() {

View file

@ -1,14 +1,11 @@
//go:build darwin
#ifndef WindowDelegate_h
#define WindowDelegate_h
#import <Cocoa/Cocoa.h>
#import <WebKit/WebKit.h>
extern void processMessage(unsigned int, const char*);
@interface WindowDelegate : NSObject <NSWindowDelegate, WKScriptMessageHandler>
@property bool hideOnClose;

View file

@ -11,6 +11,8 @@
#import "window_delegate.h"
#import "../events/events.h"
extern void processMessage(unsigned int, const char*);
@implementation WindowDelegate
- (BOOL)windowShouldClose:(NSWindow *)sender {
if( self.hideOnClose ) {