From 88f6a3d1b9e20f76717ee9f4d045cc4b79cd3901 Mon Sep 17 00:00:00 2001 From: Lea Anthony Date: Fri, 16 Dec 2022 21:09:31 +1100 Subject: [PATCH] Refactor and support more roles --- exp/examples/menu/main.go | 10 +- exp/pkg/application/app_delegate.h | 7 +- exp/pkg/application/application.go | 12 +- exp/pkg/application/application_darwin.go | 19 +- exp/pkg/application/keys.go | 6 +- exp/pkg/application/menu.go | 2 +- exp/pkg/application/menu_darwin.go | 2 +- exp/pkg/application/menuitem.go | 67 +++++-- exp/pkg/application/menuitem_darwin.go | 228 +++++++++++++++++----- exp/pkg/application/roles.go | 96 +++++++-- exp/pkg/application/window.go | 32 +++ exp/pkg/application/window_darwin.go | 31 ++- exp/pkg/application/window_delegate.h | 3 - exp/pkg/application/window_delegate.m | 2 + 14 files changed, 421 insertions(+), 96 deletions(-) diff --git a/exp/examples/menu/main.go b/exp/examples/menu/main.go index ea106a459..1556e9a09 100644 --- a/exp/examples/menu/main.go +++ b/exp/examples/menu/main.go @@ -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()) } } diff --git a/exp/pkg/application/app_delegate.h b/exp/pkg/application/app_delegate.h index 0a318f4ae..db8021543 100644 --- a/exp/pkg/application/app_delegate.h +++ b/exp/pkg/application/app_delegate.h @@ -1,8 +1,13 @@ //go:build darwin +#ifndef appdelegate_h +#define appdelegate_h + #import @interface AppDelegate : NSObject @property NSApplicationActivationPolicy activationPolicy; - (void)setApplicationActivationPolicy:(NSApplicationActivationPolicy)policy; -@end \ No newline at end of file +@end + +#endif diff --git a/exp/pkg/application/application.go b/exp/pkg/application/application.go index 7a32653f6..27274575e 100644 --- a/exp/pkg/application/application.go +++ b/exp/pkg/application/application.go @@ -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) diff --git a/exp/pkg/application/application_darwin.go b/exp/pkg/application/application_darwin.go index 61abb4736..4dd54d027 100644 --- a/exp/pkg/application/application_darwin.go +++ b/exp/pkg/application/application_darwin.go @@ -9,6 +9,7 @@ package application #include "application.h" #include "app_delegate.h" +#include "window_delegate.h" #include #import @@ -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 } diff --git a/exp/pkg/application/keys.go b/exp/pkg/application/keys.go index a7d0515b0..4670e1833 100644 --- a/exp/pkg/application/keys.go +++ b/exp/pkg/application/keys.go @@ -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 diff --git a/exp/pkg/application/menu.go b/exp/pkg/application/menu.go index 8bec6f01a..20c09e239 100644 --- a/exp/pkg/application/menu.go +++ b/exp/pkg/application/menu.go @@ -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 } diff --git a/exp/pkg/application/menu_darwin.go b/exp/pkg/application/menu_darwin.go index 0d25983d4..fccf85559 100644 --- a/exp/pkg/application/menu_darwin.go +++ b/exp/pkg/application/menu_darwin.go @@ -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 diff --git a/exp/pkg/application/menuitem.go b/exp/pkg/application/menuitem.go index 746f61139..8be6d9ea0 100644 --- a/exp/pkg/application/menuitem.go +++ b/exp/pkg/application/menuitem.go @@ -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() { diff --git a/exp/pkg/application/menuitem_darwin.go b/exp/pkg/application/menuitem_darwin.go index 9aca135b1..d4fa46296 100644 --- a/exp/pkg/application/menuitem_darwin.go +++ b/exp/pkg/application/menuitem_darwin.go @@ -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() + } + }) +} diff --git a/exp/pkg/application/roles.go b/exp/pkg/application/roles.go index dde21b5d1..03ee7c958 100644 --- a/exp/pkg/application/roles.go +++ b/exp/pkg/application/roles.go @@ -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 +} diff --git a/exp/pkg/application/window.go b/exp/pkg/application/window.go index 944739d33..9280e312f 100644 --- a/exp/pkg/application/window.go +++ b/exp/pkg/application/window.go @@ -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() +} diff --git a/exp/pkg/application/window_darwin.go b/exp/pkg/application/window_darwin.go index ba66210e6..aaa7e874f 100644 --- a/exp/pkg/application/window_darwin.go +++ b/exp/pkg/application/window_darwin.go @@ -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() { diff --git a/exp/pkg/application/window_delegate.h b/exp/pkg/application/window_delegate.h index f6e91c15c..c0446abee 100644 --- a/exp/pkg/application/window_delegate.h +++ b/exp/pkg/application/window_delegate.h @@ -1,14 +1,11 @@ //go:build darwin - #ifndef WindowDelegate_h #define WindowDelegate_h #import #import -extern void processMessage(unsigned int, const char*); - @interface WindowDelegate : NSObject @property bool hideOnClose; diff --git a/exp/pkg/application/window_delegate.m b/exp/pkg/application/window_delegate.m index a602500fc..5a5488c4d 100644 --- a/exp/pkg/application/window_delegate.m +++ b/exp/pkg/application/window_delegate.m @@ -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 ) {