mirror of
https://github.com/wailsapp/wails.git
synced 2025-05-16 17:09:28 +08:00
Initial support for menus
This commit is contained in:
parent
810b3c7440
commit
6f218264ed
@ -3,6 +3,9 @@
|
|||||||
package app
|
package app
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
|
goruntime "runtime"
|
||||||
|
|
||||||
"github.com/wailsapp/wails/v2/internal/binding"
|
"github.com/wailsapp/wails/v2/internal/binding"
|
||||||
"github.com/wailsapp/wails/v2/internal/ffenestri"
|
"github.com/wailsapp/wails/v2/internal/ffenestri"
|
||||||
"github.com/wailsapp/wails/v2/internal/logger"
|
"github.com/wailsapp/wails/v2/internal/logger"
|
||||||
@ -11,6 +14,7 @@ import (
|
|||||||
"github.com/wailsapp/wails/v2/internal/servicebus"
|
"github.com/wailsapp/wails/v2/internal/servicebus"
|
||||||
"github.com/wailsapp/wails/v2/internal/signal"
|
"github.com/wailsapp/wails/v2/internal/signal"
|
||||||
"github.com/wailsapp/wails/v2/internal/subsystem"
|
"github.com/wailsapp/wails/v2/internal/subsystem"
|
||||||
|
"github.com/wailsapp/wails/v2/pkg/menu"
|
||||||
"github.com/wailsapp/wails/v2/pkg/options"
|
"github.com/wailsapp/wails/v2/pkg/options"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -28,6 +32,7 @@ type App struct {
|
|||||||
event *subsystem.Event
|
event *subsystem.Event
|
||||||
binding *subsystem.Binding
|
binding *subsystem.Binding
|
||||||
call *subsystem.Call
|
call *subsystem.Call
|
||||||
|
menu *subsystem.Menu
|
||||||
dispatcher *messagedispatcher.Dispatcher
|
dispatcher *messagedispatcher.Dispatcher
|
||||||
|
|
||||||
// Indicates if the app is in debug mode
|
// Indicates if the app is in debug mode
|
||||||
@ -128,6 +133,25 @@ func (a *App) Run() error {
|
|||||||
a.event = event
|
a.event = event
|
||||||
a.event.Start()
|
a.event.Start()
|
||||||
|
|
||||||
|
// Start the menu subsystem
|
||||||
|
var platformMenu *menu.Menu
|
||||||
|
switch goruntime.GOOS {
|
||||||
|
case "darwin":
|
||||||
|
platformMenu = a.options.Mac.Menu
|
||||||
|
// case "linux":
|
||||||
|
// platformMenu = a.options.Linux.Menu
|
||||||
|
// case "windows":
|
||||||
|
// platformMenu = a.options.Windows.Menu
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("unsupported OS: %s", goruntime.GOOS)
|
||||||
|
}
|
||||||
|
menu, err := subsystem.NewMenu(platformMenu, a.servicebus, a.logger)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
a.menu = menu
|
||||||
|
a.menu.Start()
|
||||||
|
|
||||||
// Start the call subsystem
|
// Start the call subsystem
|
||||||
call, err := subsystem.NewCall(a.servicebus, a.logger, a.bindings.DB(), a.runtime.GoRuntime())
|
call, err := subsystem.NewCall(a.servicebus, a.logger, a.bindings.DB(), a.runtime.GoRuntime())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -20,6 +20,8 @@
|
|||||||
#define GET_FRAME(receiver) ((CGRect(*)(id, SEL))objc_msgSend_stret)(receiver, s("frame"))
|
#define GET_FRAME(receiver) ((CGRect(*)(id, SEL))objc_msgSend_stret)(receiver, s("frame"))
|
||||||
#define GET_BOUNDS(receiver) ((CGRect(*)(id, SEL))objc_msgSend_stret)(receiver, s("bounds"))
|
#define GET_BOUNDS(receiver) ((CGRect(*)(id, SEL))objc_msgSend_stret)(receiver, s("bounds"))
|
||||||
|
|
||||||
|
#define STREQ(a,b) strncmp(a, b, strlen(b)) == 0
|
||||||
|
|
||||||
#define ON_MAIN_THREAD(str) dispatch( ^{ str; } )
|
#define ON_MAIN_THREAD(str) dispatch( ^{ str; } )
|
||||||
#define MAIN_WINDOW_CALL(str) msg(app->mainWindow, s((str)))
|
#define MAIN_WINDOW_CALL(str) msg(app->mainWindow, s((str)))
|
||||||
|
|
||||||
@ -49,7 +51,7 @@
|
|||||||
|
|
||||||
#define NSEventModifierFlagCommand 1 << 20
|
#define NSEventModifierFlagCommand 1 << 20
|
||||||
#define NSEventModifierFlagOption 1 << 19
|
#define NSEventModifierFlagOption 1 << 19
|
||||||
|
#define NSEventModifierFlagShift 1 << 17
|
||||||
|
|
||||||
|
|
||||||
// Unbelievably, if the user swaps their button preference
|
// Unbelievably, if the user swaps their button preference
|
||||||
@ -157,6 +159,10 @@ struct Application {
|
|||||||
int hideToolbarSeparator;
|
int hideToolbarSeparator;
|
||||||
int windowBackgroundIsTranslucent;
|
int windowBackgroundIsTranslucent;
|
||||||
|
|
||||||
|
// Menu
|
||||||
|
const char *menuAsJSON;
|
||||||
|
id menubar;
|
||||||
|
|
||||||
// User Data
|
// User Data
|
||||||
char *HTML;
|
char *HTML;
|
||||||
|
|
||||||
@ -189,6 +195,16 @@ void Debug(struct Application *app, const char *message, ... ) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Fatal(struct Application *app, const char *message, ... ) {
|
||||||
|
const char *temp = concat("LFFfenestri (C) | ", message);
|
||||||
|
va_list args;
|
||||||
|
va_start(args, message);
|
||||||
|
vsnprintf(logbuffer, MAXMESSAGE, temp, args);
|
||||||
|
app->sendMessageToBackend(&logbuffer[0]);
|
||||||
|
free((void*)temp);
|
||||||
|
va_end(args);
|
||||||
|
}
|
||||||
|
|
||||||
void TitlebarAppearsTransparent(struct Application* app) {
|
void TitlebarAppearsTransparent(struct Application* app) {
|
||||||
app->titlebarAppearsTransparent = 1;
|
app->titlebarAppearsTransparent = 1;
|
||||||
}
|
}
|
||||||
@ -292,6 +308,15 @@ void messageHandler(id self, SEL cmd, id contentController, id message) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Callback for menu items
|
||||||
|
void menuItemPressed(id self, SEL cmd, id sender) {
|
||||||
|
const char *callbackID = (const char *)msg(msg(sender, s("representedObject")), s("pointerValue"));
|
||||||
|
printf("Got callback ID: %s\n", callbackID);
|
||||||
|
const char *message = concat("MC", callbackID);
|
||||||
|
messageFromWindowCallback(message);
|
||||||
|
free((void*)message);
|
||||||
|
}
|
||||||
|
|
||||||
// closeWindow is called when the close button is pressed
|
// closeWindow is called when the close button is pressed
|
||||||
void closeWindow(id self, SEL cmd, id sender) {
|
void closeWindow(id self, SEL cmd, id sender) {
|
||||||
printf("\n\n\ncloseWindow called!!!!\n\n\n");
|
printf("\n\n\ncloseWindow called!!!!\n\n\n");
|
||||||
@ -371,6 +396,8 @@ void* NewApplication(const char *title, int width, int height, int resizable, in
|
|||||||
result->vibrancyLayer = NULL;
|
result->vibrancyLayer = NULL;
|
||||||
result->delegate = NULL;
|
result->delegate = NULL;
|
||||||
|
|
||||||
|
// Menu
|
||||||
|
result->menuAsJSON = NULL;
|
||||||
|
|
||||||
result->titlebarAppearsTransparent = 0;
|
result->titlebarAppearsTransparent = 0;
|
||||||
result->webviewIsTranparent = 0;
|
result->webviewIsTranparent = 0;
|
||||||
@ -745,6 +772,11 @@ void SetDebug(void *applicationPointer, int flag) {
|
|||||||
debug = flag;
|
debug = flag;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SetMenu sets the initial menu for the application
|
||||||
|
void SetMenu(struct Application *app, const char *menuAsJSON) {
|
||||||
|
app->menuAsJSON = menuAsJSON;
|
||||||
|
}
|
||||||
|
|
||||||
void SetBindings(struct Application *app, const char *bindings) {
|
void SetBindings(struct Application *app, const char *bindings) {
|
||||||
const char* temp = concat("window.wailsbindings = \"", bindings);
|
const char* temp = concat("window.wailsbindings = \"", bindings);
|
||||||
const char* jscall = concat(temp, "\";");
|
const char* jscall = concat(temp, "\";");
|
||||||
@ -832,6 +864,9 @@ void createDelegate(struct Application *app) {
|
|||||||
class_addMethod(delegateClass, s("applicationShouldTerminateAfterLastWindowClosed:"), (IMP) yes, "c@:@");
|
class_addMethod(delegateClass, s("applicationShouldTerminateAfterLastWindowClosed:"), (IMP) yes, "c@:@");
|
||||||
class_addMethod(delegateClass, s("windowWillClose:"), (IMP) closeWindow, "v@:@");
|
class_addMethod(delegateClass, s("windowWillClose:"), (IMP) closeWindow, "v@:@");
|
||||||
|
|
||||||
|
// Menu Callbacks
|
||||||
|
class_addMethod(delegateClass, s("menuCallback:"), (IMP)menuItemPressed, "v@:@");
|
||||||
|
|
||||||
// Script handler
|
// Script handler
|
||||||
class_addMethod(delegateClass, s("userContentController:didReceiveScriptMessage:"), (IMP) messageHandler, "v@:@@");
|
class_addMethod(delegateClass, s("userContentController:didReceiveScriptMessage:"), (IMP) messageHandler, "v@:@@");
|
||||||
objc_registerClassPair(delegateClass);
|
objc_registerClassPair(delegateClass);
|
||||||
@ -901,70 +936,292 @@ id createMenuItemNoAutorelease( id title, const char *action, const char *key) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
id createMenu(id title) {
|
id createMenu(id title) {
|
||||||
id menu = ALLOC("NSMenu");
|
id menu = ALLOC("NSMenu");
|
||||||
msg(menu, s("initWithTitle:"), title);
|
msg(menu, s("initWithTitle:"), title);
|
||||||
msg(menu, s("autorelease"));
|
msg(menu, s("autorelease"));
|
||||||
return menu;
|
return menu;
|
||||||
}
|
}
|
||||||
|
|
||||||
id addMenuItem(id menu, const char *title, const char *action, const char *key) {
|
id addMenuItem(id menu, const char *title, const char *action, const char *key, bool enabled) {
|
||||||
id item = createMenuItem(str(title), action, key);
|
id item = createMenuItem(str(title), action, key);
|
||||||
|
msg(item, s("setEnabled:"), enabled);
|
||||||
msg(menu, s("addItem:"), item);
|
msg(menu, s("addItem:"), item);
|
||||||
return item;
|
return item;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
id addCallbackMenuItem(id menu, const char *title, const char *menuid, const char *key, bool enabled) {
|
||||||
|
id item = ALLOC("NSMenuItem");
|
||||||
|
id wrappedId = msg(c("NSValue"), s("valueWithPointer:"), menuid);
|
||||||
|
msg(item, s("setRepresentedObject:"), wrappedId);
|
||||||
|
msg(item, s("initWithTitle:action:keyEquivalent:"), str(title), s("menuCallback:"), str(key));
|
||||||
|
msg(item, s("setEnabled:"), enabled);
|
||||||
|
msg(item, s("autorelease"));
|
||||||
|
msg(menu, s("addItem:"), item);
|
||||||
|
return item;
|
||||||
|
}
|
||||||
|
|
||||||
void addSeparator(id menu) {
|
void addSeparator(id menu) {
|
||||||
id item = msg(c("NSMenuItem"), s("separatorItem"));
|
id item = msg(c("NSMenuItem"), s("separatorItem"));
|
||||||
msg(menu, s("addItem:"), item);
|
msg(menu, s("addItem:"), item);
|
||||||
}
|
}
|
||||||
|
|
||||||
void addDefaultMenu() {
|
void createDefaultAppMenu(id parentMenu) {
|
||||||
|
|
||||||
id menubar = createMenu(str(""));
|
|
||||||
|
|
||||||
id appName = msg(msg(c("NSProcessInfo"), s("processInfo")), s("processName"));
|
|
||||||
|
|
||||||
id appMenuItem = createMenuItemNoAutorelease(appName, NULL, "");
|
|
||||||
|
|
||||||
// App Menu
|
// App Menu
|
||||||
|
id appName = msg(msg(c("NSProcessInfo"), s("processInfo")), s("processName"));
|
||||||
|
id appMenuItem = createMenuItemNoAutorelease(appName, NULL, "");
|
||||||
id appMenu = createMenu(appName);
|
id appMenu = createMenu(appName);
|
||||||
|
|
||||||
msg(appMenuItem, s("setSubmenu:"), appMenu);
|
msg(appMenuItem, s("setSubmenu:"), appMenu);
|
||||||
msg(menubar, s("addItem:"), appMenuItem);
|
msg(parentMenu, s("addItem:"), appMenuItem);
|
||||||
|
|
||||||
id title = msg(str("Hide "), s("stringByAppendingString:"), appName);
|
id title = msg(str("Hide "), s("stringByAppendingString:"), appName);
|
||||||
id item = createMenuItem(title, "hide:", "h");
|
id item = createMenuItem(title, "hide:", "h");
|
||||||
msg(appMenu, s("addItem:"), item);
|
msg(appMenu, s("addItem:"), item);
|
||||||
|
|
||||||
id hideOthers = addMenuItem(appMenu, "Hide Others", "hideOtherApplications:", "h");
|
id hideOthers = addMenuItem(appMenu, "Hide Others", "hideOtherApplications:", "h", TRUE);
|
||||||
msg(hideOthers, s("setKeyEquivalentModifierMask:"), (NSEventModifierFlagOption | NSEventModifierFlagCommand));
|
msg(hideOthers, s("setKeyEquivalentModifierMask:"), (NSEventModifierFlagOption | NSEventModifierFlagCommand));
|
||||||
|
|
||||||
addMenuItem(appMenu, "Show All", "unhideAllApplications:", "");
|
addMenuItem(appMenu, "Show All", "unhideAllApplications:", "", TRUE);
|
||||||
|
|
||||||
addSeparator(appMenu);
|
addSeparator(appMenu);
|
||||||
|
|
||||||
title = msg(str("Quit "), s("stringByAppendingString:"), appName);
|
title = msg(str("Quit "), s("stringByAppendingString:"), appName);
|
||||||
item = createMenuItem(title, "terminate:", "q");
|
item = createMenuItem(title, "terminate:", "q");
|
||||||
msg(appMenu, s("addItem:"), item);
|
msg(appMenu, s("addItem:"), item);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void createDefaultEditMenu(id parentMenu) {
|
||||||
// Edit Menu
|
// Edit Menu
|
||||||
id editMenuItem = createMenuItemNoAutorelease(str("Edit"), NULL, "");
|
id editMenuItem = createMenuItemNoAutorelease(str("Edit"), NULL, "");
|
||||||
id editMenu = createMenu(str("Edit"));
|
id editMenu = createMenu(str("Edit"));
|
||||||
|
|
||||||
msg(editMenuItem, s("setSubmenu:"), editMenu);
|
msg(editMenuItem, s("setSubmenu:"), editMenu);
|
||||||
msg(menubar, s("addItem:"), editMenuItem);
|
msg(parentMenu, s("addItem:"), editMenuItem);
|
||||||
|
|
||||||
addMenuItem(editMenu, "Undo", "undo:", "z");
|
addMenuItem(editMenu, "Undo", "undo:", "z", TRUE);
|
||||||
addMenuItem(editMenu, "Redo", "redo:", "y");
|
addMenuItem(editMenu, "Redo", "redo:", "y", TRUE);
|
||||||
addSeparator(editMenu);
|
addSeparator(editMenu);
|
||||||
addMenuItem(editMenu, "Cut", "cut:", "x");
|
addMenuItem(editMenu, "Cut", "cut:", "x", TRUE);
|
||||||
addMenuItem(editMenu, "Copy", "copy:", "c");
|
addMenuItem(editMenu, "Copy", "copy:", "c", TRUE);
|
||||||
addMenuItem(editMenu, "Paste", "paste:", "v");
|
addMenuItem(editMenu, "Paste", "paste:", "v", TRUE);
|
||||||
addMenuItem(editMenu, "Select All", "selectAll:", "a");
|
addMenuItem(editMenu, "Select All", "selectAll:", "a", TRUE);
|
||||||
|
}
|
||||||
|
|
||||||
|
void parseMenuRole(struct Application *app, id parentMenu, JsonNode *item) {
|
||||||
|
const char *roleName = item->string_;
|
||||||
|
|
||||||
|
if ( STREQ(roleName, "appMenu") ) {
|
||||||
|
createDefaultAppMenu(parentMenu);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if ( STREQ(roleName, "editMenu")) {
|
||||||
|
createDefaultEditMenu(parentMenu);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if ( STREQ(roleName, "hide")) {
|
||||||
|
addMenuItem(parentMenu, "Hide Window", "hide:", "h", TRUE);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if ( STREQ(roleName, "hideothers")) {
|
||||||
|
id hideOthers = addMenuItem(parentMenu, "Hide Others", "hideOtherApplications:", "h", TRUE);
|
||||||
|
msg(hideOthers, s("setKeyEquivalentModifierMask:"), (NSEventModifierFlagOption | NSEventModifierFlagCommand));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if ( STREQ(roleName, "unhide")) {
|
||||||
|
addMenuItem(parentMenu, "Show All", "unhideAllApplications:", "", TRUE);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if ( STREQ(roleName, "front")) {
|
||||||
|
addMenuItem(parentMenu, "Bring All to Front", "arrangeInFront:", "", TRUE);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if ( STREQ(roleName, "undo")) {
|
||||||
|
addMenuItem(parentMenu, "Undo", "undo:", "z", TRUE);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if ( STREQ(roleName, "redo")) {
|
||||||
|
addMenuItem(parentMenu, "Redo", "redo:", "y", TRUE);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if ( STREQ(roleName, "cut")) {
|
||||||
|
addMenuItem(parentMenu, "Cut", "cut:", "x", TRUE);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if ( STREQ(roleName, "copy")) {
|
||||||
|
addMenuItem(parentMenu, "Copy", "copy:", "c", TRUE);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if ( STREQ(roleName, "paste")) {
|
||||||
|
addMenuItem(parentMenu, "Paste", "paste:", "v", TRUE);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if ( STREQ(roleName, "delete")) {
|
||||||
|
addMenuItem(parentMenu, "Delete", "delete:", "", TRUE);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if( STREQ(roleName, "pasteandmatchstyle")) {
|
||||||
|
id pasteandmatchstyle = addMenuItem(parentMenu, "Paste and Match Style", "pasteandmatchstyle:", "v", TRUE);
|
||||||
|
msg(pasteandmatchstyle, s("setKeyEquivalentModifierMask:"), (NSEventModifierFlagOption | NSEventModifierFlagShift | NSEventModifierFlagCommand));
|
||||||
|
}
|
||||||
|
if ( STREQ(roleName, "selectall")) {
|
||||||
|
addMenuItem(parentMenu, "Select All", "selectAll:", "a", TRUE);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if ( STREQ(roleName, "minimize")) {
|
||||||
|
addMenuItem(parentMenu, "Minimize", "miniaturize:", "m", TRUE);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if ( STREQ(roleName, "zoom")) {
|
||||||
|
addMenuItem(parentMenu, "Zoom", "performZoom:", "", TRUE);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if ( STREQ(roleName, "quit")) {
|
||||||
|
addMenuItem(parentMenu, "Quit (More work TBD)", "terminate:", "q", TRUE);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if ( STREQ(roleName, "togglefullscreen")) {
|
||||||
|
addMenuItem(parentMenu, "Toggle Full Screen", "toggleFullScreen:", "f", TRUE);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
const char* getJSONString(JsonNode *item, const char* key) {
|
||||||
|
// Get key
|
||||||
|
JsonNode *node = json_find_member(item, key);
|
||||||
|
const char *result = "";
|
||||||
|
if ( node != NULL && node->tag == JSON_STRING) {
|
||||||
|
result = node->string_;
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool getJSONBool(JsonNode *item, const char* key, bool *result) {
|
||||||
|
JsonNode *node = json_find_member(item, key);
|
||||||
|
if ( node != NULL && node->tag == JSON_BOOL) {
|
||||||
|
*result = node->bool_;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void parseNormalMenuItem(struct Application *app, id parentMenu, JsonNode *item) {
|
||||||
|
|
||||||
|
// Get the label
|
||||||
|
const char *label = getJSONString(item, "Label");
|
||||||
|
if ( label == NULL) {
|
||||||
|
label = "(empty)";
|
||||||
|
}
|
||||||
|
|
||||||
|
const char *menuid = getJSONString(item, "Id");
|
||||||
|
if ( menuid == NULL) {
|
||||||
|
menuid = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
bool enabled = true;
|
||||||
|
getJSONBool(item, "Enabled", &enabled);
|
||||||
|
|
||||||
|
const char *accelerator = "";
|
||||||
|
|
||||||
|
printf("Parsing Normal Menu Item %s!!!\n", label);
|
||||||
|
|
||||||
|
|
||||||
|
addCallbackMenuItem(parentMenu, label, menuid, accelerator, enabled);
|
||||||
|
}
|
||||||
|
|
||||||
|
void parseMenuItem(struct Application *app, id parentMenu, JsonNode *item) {
|
||||||
|
// Get the role
|
||||||
|
JsonNode *role = json_find_member(item, "Role");
|
||||||
|
if( role != NULL ) {
|
||||||
|
printf("Parsing MENU ROLE %s!!!\n", role->string_);
|
||||||
|
parseMenuRole(app, parentMenu, role);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if this is a submenu
|
||||||
|
JsonNode *submenu = json_find_member(item, "SubMenu");
|
||||||
|
if( submenu != NULL ) {
|
||||||
|
printf("Parsing SUBMENU!!!\n");
|
||||||
|
|
||||||
|
// Get the label
|
||||||
|
JsonNode *menuNameNode = json_find_member(item, "Label");
|
||||||
|
const char *name = "";
|
||||||
|
if ( menuNameNode != NULL) {
|
||||||
|
name = menuNameNode->string_;
|
||||||
|
}
|
||||||
|
|
||||||
|
id thisMenuItem = createMenuItemNoAutorelease(str(name), NULL, "");
|
||||||
|
id thisMenu = createMenu(str(name));
|
||||||
|
|
||||||
|
msg(thisMenuItem, s("setSubmenu:"), thisMenu);
|
||||||
|
msg(parentMenu, s("addItem:"), thisMenuItem);
|
||||||
|
|
||||||
|
// Loop over submenu items
|
||||||
|
JsonNode *item;
|
||||||
|
json_foreach(item, submenu) {
|
||||||
|
// Get item label
|
||||||
|
parseMenuItem(app, thisMenu, item);
|
||||||
|
printf("Parsing submenu item for '%s'!!!\n", name);
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the Type
|
||||||
|
JsonNode *type = json_find_member(item, "Type");
|
||||||
|
if( type != NULL ) {
|
||||||
|
if( STREQ(type->string_, "Normal")) {
|
||||||
|
parseNormalMenuItem(app, parentMenu, item);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( STREQ(type->string_, "Separator")) {
|
||||||
|
addSeparator(parentMenu);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void parseMenu(struct Application *app, id parentMenu, JsonNode *menu) {
|
||||||
|
JsonNode *items = json_find_member(menu, "Items");
|
||||||
|
if( items == NULL ) {
|
||||||
|
// Parse error!
|
||||||
|
Fatal(app, "Unable to find Items:", app->menuAsJSON);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Iterate items
|
||||||
|
JsonNode *item;
|
||||||
|
json_foreach(item, items) {
|
||||||
|
// Get item label
|
||||||
|
parseMenuItem(app, parentMenu, item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void parseMenuData(struct Application *app) {
|
||||||
|
|
||||||
|
// Create a new menu bar
|
||||||
|
id menubar = createMenu(str(""));
|
||||||
|
|
||||||
|
// Parse the menu json
|
||||||
|
JsonNode *menuData = json_decode(app->menuAsJSON);
|
||||||
|
|
||||||
|
if( menuData == NULL ) {
|
||||||
|
// Parse error!
|
||||||
|
Fatal(app, "Unable to parse Menu JSON:", app->menuAsJSON);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
parseMenu(app, menubar, menuData);
|
||||||
|
|
||||||
|
// Apply the menu bar
|
||||||
msg(msg(c("NSApplication"), s("sharedApplication")), s("setMainMenu:"), menubar);
|
msg(msg(c("NSApplication"), s("sharedApplication")), s("setMainMenu:"), menubar);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void Run(struct Application *app, int argc, char **argv) {
|
void Run(struct Application *app, int argc, char **argv) {
|
||||||
|
|
||||||
processDecorations(app);
|
processDecorations(app);
|
||||||
@ -1129,7 +1386,9 @@ void Run(struct Application *app, int argc, char **argv) {
|
|||||||
msg(wkwebview, s("setValue:forKey:"), msg(c("NSNumber"), s("numberWithBool:"), 0), str("drawsBackground"));
|
msg(wkwebview, s("setValue:forKey:"), msg(c("NSNumber"), s("numberWithBool:"), 0), str("drawsBackground"));
|
||||||
}
|
}
|
||||||
|
|
||||||
addDefaultMenu(app);
|
if( app->menuAsJSON != NULL ) {
|
||||||
|
parseMenuData(app);
|
||||||
|
}
|
||||||
|
|
||||||
// Finally call run
|
// Finally call run
|
||||||
Debug(app, "Run called");
|
Debug(app, "Run called");
|
||||||
|
@ -14,10 +14,12 @@ extern void DisableFrame(void *);
|
|||||||
extern void SetAppearance(void *, const char *);
|
extern void SetAppearance(void *, const char *);
|
||||||
extern void WebviewIsTransparent(void *);
|
extern void WebviewIsTransparent(void *);
|
||||||
extern void SetWindowBackgroundIsTranslucent(void *);
|
extern void SetWindowBackgroundIsTranslucent(void *);
|
||||||
|
extern void SetMenu(void *, const char *);
|
||||||
*/
|
*/
|
||||||
import "C"
|
import "C"
|
||||||
|
import "encoding/json"
|
||||||
|
|
||||||
func (a *Application) processPlatformSettings() {
|
func (a *Application) processPlatformSettings() error {
|
||||||
|
|
||||||
mac := a.config.Mac
|
mac := a.config.Mac
|
||||||
titlebar := mac.TitleBar
|
titlebar := mac.TitleBar
|
||||||
@ -64,4 +66,15 @@ func (a *Application) processPlatformSettings() {
|
|||||||
if mac.WindowBackgroundIsTranslucent {
|
if mac.WindowBackgroundIsTranslucent {
|
||||||
C.SetWindowBackgroundIsTranslucent(a.app)
|
C.SetWindowBackgroundIsTranslucent(a.app)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Process menu
|
||||||
|
if mac.Menu != nil {
|
||||||
|
menuJson, err := json.Marshal(mac.Menu)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
C.SetMenu(a.app, a.string2CString(string(menuJson)))
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
43
v2/internal/messagedispatcher/message/menu.go
Normal file
43
v2/internal/messagedispatcher/message/menu.go
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
package message
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/wailsapp/wails/v2/pkg/menu"
|
||||||
|
)
|
||||||
|
|
||||||
|
// MenuOnMessage is used to emit listener registration requests
|
||||||
|
// on the service bus
|
||||||
|
type MenuOnMessage struct {
|
||||||
|
// MenuID is the id of the menu item we are interested in
|
||||||
|
MenuID string
|
||||||
|
// Callback is called when the menu is clicked
|
||||||
|
Callback func(*menu.MenuItem)
|
||||||
|
}
|
||||||
|
|
||||||
|
// menuMessageParser does what it says on the tin!
|
||||||
|
func menuMessageParser(message string) (*parsedMessage, error) {
|
||||||
|
|
||||||
|
// Sanity check: Menu messages must be at least 2 bytes
|
||||||
|
if len(message) < 3 {
|
||||||
|
return nil, fmt.Errorf("event message was an invalid length")
|
||||||
|
}
|
||||||
|
|
||||||
|
var topic string
|
||||||
|
var data interface{}
|
||||||
|
|
||||||
|
// Switch the message type
|
||||||
|
switch message[1] {
|
||||||
|
case 'C':
|
||||||
|
callbackid := message[2:]
|
||||||
|
topic = "menu:clicked"
|
||||||
|
data = callbackid
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("invalid menu message: %s", message)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a new parsed message struct
|
||||||
|
parsedMessage := &parsedMessage{Topic: topic, Data: data}
|
||||||
|
|
||||||
|
return parsedMessage, nil
|
||||||
|
}
|
@ -18,6 +18,7 @@ var messageParsers = map[byte]func(string) (*parsedMessage, error){
|
|||||||
'W': windowMessageParser,
|
'W': windowMessageParser,
|
||||||
'D': dialogMessageParser,
|
'D': dialogMessageParser,
|
||||||
'S': systemMessageParser,
|
'S': systemMessageParser,
|
||||||
|
'M': menuMessageParser,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parse will attempt to parse the given message
|
// Parse will attempt to parse the given message
|
||||||
|
31
v2/internal/runtime/menu.go
Normal file
31
v2/internal/runtime/menu.go
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
package runtime
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/wailsapp/wails/v2/internal/messagedispatcher/message"
|
||||||
|
"github.com/wailsapp/wails/v2/internal/servicebus"
|
||||||
|
"github.com/wailsapp/wails/v2/pkg/menu"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Menu defines all Menu related operations
|
||||||
|
type Menu interface {
|
||||||
|
On(menuID string, callback func(*menu.MenuItem))
|
||||||
|
}
|
||||||
|
|
||||||
|
type menuRuntime struct {
|
||||||
|
bus *servicebus.ServiceBus
|
||||||
|
}
|
||||||
|
|
||||||
|
// newMenu creates a new Menu struct
|
||||||
|
func newMenu(bus *servicebus.ServiceBus) Menu {
|
||||||
|
return &menuRuntime{
|
||||||
|
bus: bus,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// On registers a listener for a particular event
|
||||||
|
func (m *menuRuntime) On(menuID string, callback func(*menu.MenuItem)) {
|
||||||
|
m.bus.Publish("menu:on", &message.MenuOnMessage{
|
||||||
|
MenuID: menuID,
|
||||||
|
Callback: callback,
|
||||||
|
})
|
||||||
|
}
|
@ -9,6 +9,7 @@ type Runtime struct {
|
|||||||
Window Window
|
Window Window
|
||||||
Dialog Dialog
|
Dialog Dialog
|
||||||
System System
|
System System
|
||||||
|
Menu Menu
|
||||||
Store *StoreProvider
|
Store *StoreProvider
|
||||||
Log Log
|
Log Log
|
||||||
bus *servicebus.ServiceBus
|
bus *servicebus.ServiceBus
|
||||||
@ -22,6 +23,7 @@ func New(serviceBus *servicebus.ServiceBus) *Runtime {
|
|||||||
Window: newWindow(serviceBus),
|
Window: newWindow(serviceBus),
|
||||||
Dialog: newDialog(serviceBus),
|
Dialog: newDialog(serviceBus),
|
||||||
System: newSystem(serviceBus),
|
System: newSystem(serviceBus),
|
||||||
|
Menu: newMenu(serviceBus),
|
||||||
Log: newLog(serviceBus),
|
Log: newLog(serviceBus),
|
||||||
bus: serviceBus,
|
bus: serviceBus,
|
||||||
}
|
}
|
||||||
|
171
v2/internal/subsystem/menu.go
Normal file
171
v2/internal/subsystem/menu.go
Normal file
@ -0,0 +1,171 @@
|
|||||||
|
package subsystem
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/wailsapp/wails/v2/internal/logger"
|
||||||
|
"github.com/wailsapp/wails/v2/internal/messagedispatcher/message"
|
||||||
|
"github.com/wailsapp/wails/v2/internal/servicebus"
|
||||||
|
"github.com/wailsapp/wails/v2/pkg/menu"
|
||||||
|
)
|
||||||
|
|
||||||
|
// eventListener holds a callback function which is invoked when
|
||||||
|
// the event listened for is emitted. It has a counter which indicates
|
||||||
|
// how the total number of events it is interested in. A value of zero
|
||||||
|
// means it does not expire (default).
|
||||||
|
// type eventListener struct {
|
||||||
|
// callback func(...interface{}) // Function to call with emitted event data
|
||||||
|
// counter int // The number of times this callback may be called. -1 = infinite
|
||||||
|
// delete bool // Flag to indicate that this listener should be deleted
|
||||||
|
// }
|
||||||
|
|
||||||
|
// Menu is the subsystem that handles the operation of menus. It manages all service bus messages
|
||||||
|
// starting with "menu".
|
||||||
|
type Menu struct {
|
||||||
|
quitChannel <-chan *servicebus.Message
|
||||||
|
menuChannel <-chan *servicebus.Message
|
||||||
|
running bool
|
||||||
|
|
||||||
|
// Event listeners
|
||||||
|
listeners map[string][]func(*menu.MenuItem)
|
||||||
|
menuItems map[string]*menu.MenuItem
|
||||||
|
notifyLock sync.RWMutex
|
||||||
|
|
||||||
|
// logger
|
||||||
|
logger logger.CustomLogger
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewMenu creates a new menu subsystem
|
||||||
|
func NewMenu(initialMenu *menu.Menu, bus *servicebus.ServiceBus, logger *logger.Logger) (*Menu, error) {
|
||||||
|
|
||||||
|
// Register quit channel
|
||||||
|
quitChannel, err := bus.Subscribe("quit")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Subscribe to menu messages
|
||||||
|
menuChannel, err := bus.Subscribe("menu")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
result := &Menu{
|
||||||
|
quitChannel: quitChannel,
|
||||||
|
menuChannel: menuChannel,
|
||||||
|
logger: logger.CustomLogger("Menu Subsystem"),
|
||||||
|
listeners: make(map[string][]func(*menu.MenuItem)),
|
||||||
|
menuItems: make(map[string]*menu.MenuItem),
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build up list of item/id pairs
|
||||||
|
result.processMenu(initialMenu)
|
||||||
|
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start the subsystem
|
||||||
|
func (m *Menu) Start() error {
|
||||||
|
|
||||||
|
m.logger.Trace("Starting")
|
||||||
|
|
||||||
|
m.running = true
|
||||||
|
|
||||||
|
// Spin off a go routine
|
||||||
|
go func() {
|
||||||
|
for m.running {
|
||||||
|
select {
|
||||||
|
case <-m.quitChannel:
|
||||||
|
m.running = false
|
||||||
|
break
|
||||||
|
case menuMessage := <-m.menuChannel:
|
||||||
|
splitTopic := strings.Split(menuMessage.Topic(), ":")
|
||||||
|
menuMessageType := splitTopic[1]
|
||||||
|
switch menuMessageType {
|
||||||
|
case "clicked":
|
||||||
|
if len(splitTopic) != 2 {
|
||||||
|
m.logger.Error("Received clicked message with invalid topic format. Expected 2 sections in topic, got %s", splitTopic)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
m.logger.Trace("Got Menu clicked Message: %s %+v", menuMessage.Topic(), menuMessage.Data())
|
||||||
|
callbackID := menuMessage.Data().(string)
|
||||||
|
m.notifyListeners(callbackID)
|
||||||
|
case "on":
|
||||||
|
listenerDetails := menuMessage.Data().(*message.MenuOnMessage)
|
||||||
|
id := listenerDetails.MenuID
|
||||||
|
// Check we have a menu with that id
|
||||||
|
if m.menuItems[id] == nil {
|
||||||
|
m.logger.Error("cannot register listener for unknown menu id '%s'", id)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// We do! Append the callback
|
||||||
|
m.listeners[id] = append(m.listeners[id], listenerDetails.Callback)
|
||||||
|
default:
|
||||||
|
m.logger.Error("unknown menu message: %+v", menuMessage)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Call shutdown
|
||||||
|
m.shutdown()
|
||||||
|
}()
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Menu) processMenu(menu *menu.Menu) {
|
||||||
|
for _, item := range menu.Items {
|
||||||
|
m.processMenuItem(item)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Menu) processMenuItem(item *menu.MenuItem) {
|
||||||
|
|
||||||
|
if item.SubMenu != nil {
|
||||||
|
for _, submenuitem := range item.SubMenu {
|
||||||
|
m.processMenuItem(submenuitem)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if item.Id != "" {
|
||||||
|
if m.menuItems[item.Id] != nil {
|
||||||
|
m.logger.Error("Menu id '%s' is used by multiple menu items: %s %s", m.menuItems[item.Id].Label, item.Label)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
m.menuItems[item.Id] = item
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Notifies listeners that the given menu was clicked
|
||||||
|
func (m *Menu) notifyListeners(menuid string) {
|
||||||
|
|
||||||
|
// Get the menu item
|
||||||
|
menuItem := m.menuItems[menuid]
|
||||||
|
if menuItem == nil {
|
||||||
|
m.logger.Trace("Cannot process menuid %s - unknown", menuid)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// Get list of menu listeners
|
||||||
|
listeners := m.listeners[menuid]
|
||||||
|
if listeners == nil {
|
||||||
|
m.logger.Trace("No listeners for %s", menuid)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Lock the listeners
|
||||||
|
m.notifyLock.Lock()
|
||||||
|
|
||||||
|
// Callback in goroutine
|
||||||
|
for _, listener := range listeners {
|
||||||
|
go listener(menuItem)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unlock
|
||||||
|
m.notifyLock.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Menu) shutdown() {
|
||||||
|
m.logger.Trace("Shutdown")
|
||||||
|
}
|
10
v2/pkg/menu/mac.go
Normal file
10
v2/pkg/menu/mac.go
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
package menu
|
||||||
|
|
||||||
|
// DefaultMacMenu returns a default menu including the default
|
||||||
|
// Application and Edit menus. Use `.Append()` to add to it.
|
||||||
|
func DefaultMacMenu() *Menu {
|
||||||
|
return NewMenuFromItems(
|
||||||
|
AppMenu(),
|
||||||
|
EditMenu(),
|
||||||
|
)
|
||||||
|
}
|
24
v2/pkg/menu/menu.go
Normal file
24
v2/pkg/menu/menu.go
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
package menu
|
||||||
|
|
||||||
|
type Menu struct {
|
||||||
|
Items []*MenuItem
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewMenu() *Menu {
|
||||||
|
return &Menu{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Menu) Append(item *MenuItem) {
|
||||||
|
m.Items = append(m.Items, item)
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewMenuFromItems(first *MenuItem, rest ...*MenuItem) *Menu {
|
||||||
|
|
||||||
|
var result = NewMenu()
|
||||||
|
result.Append(first)
|
||||||
|
for _, item := range rest {
|
||||||
|
result.Append(item)
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
30
v2/pkg/menu/menuitem.go
Normal file
30
v2/pkg/menu/menuitem.go
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
package menu
|
||||||
|
|
||||||
|
type MenuItem struct {
|
||||||
|
Id string `json:"Id,omitempty"`
|
||||||
|
Label string
|
||||||
|
Role Role `json:"Role,omitempty"`
|
||||||
|
Accelerator string `json:"Accelerator,omitempty"`
|
||||||
|
Type Type
|
||||||
|
Enabled bool
|
||||||
|
Visible bool
|
||||||
|
Checked bool
|
||||||
|
SubMenu []*MenuItem `json:"SubMenu,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func Text(label string, id string) *MenuItem {
|
||||||
|
return &MenuItem{
|
||||||
|
Id: id,
|
||||||
|
Label: label,
|
||||||
|
Type: NormalType,
|
||||||
|
Enabled: true,
|
||||||
|
Visible: true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Separator provides a menu separator
|
||||||
|
func Separator() *MenuItem {
|
||||||
|
return &MenuItem{
|
||||||
|
Type: SeparatorType,
|
||||||
|
}
|
||||||
|
}
|
204
v2/pkg/menu/menuroles.go
Normal file
204
v2/pkg/menu/menuroles.go
Normal file
@ -0,0 +1,204 @@
|
|||||||
|
// Package menu provides all the functions and structs related to menus in a Wails application.
|
||||||
|
// Heavily inspired by Electron (c) 2013-2020 Github Inc.
|
||||||
|
// Electron License: https://github.com/electron/electron/blob/master/LICENSE
|
||||||
|
package menu
|
||||||
|
|
||||||
|
type Role string
|
||||||
|
|
||||||
|
const (
|
||||||
|
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"
|
||||||
|
TogglefullscreenRole Role = "togglefullscreen"
|
||||||
|
FileMenuRole Role = "fileMenu"
|
||||||
|
EditMenuRole Role = "editMenu"
|
||||||
|
ViewMenuRole Role = "viewMenu"
|
||||||
|
WindowMenuRole Role = "windowMenu"
|
||||||
|
AppMenuRole Role = "appMenu"
|
||||||
|
HideRole Role = "hide"
|
||||||
|
HideOthersRole Role = "hideOthers"
|
||||||
|
UnhideRole Role = "unhide"
|
||||||
|
FrontRole Role = "front"
|
||||||
|
ZoomRole Role = "zoom"
|
||||||
|
WindowSubMenuRole Role = "windowSubMenu"
|
||||||
|
HelpSubMenuRole Role = "helpSubMenu"
|
||||||
|
SeparatorItemRole Role = "separatorItem"
|
||||||
|
)
|
||||||
|
|
||||||
|
// About provides a MenuItem with the About role
|
||||||
|
func About() *MenuItem {
|
||||||
|
return &MenuItem{
|
||||||
|
Role: AboutRole,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Undo provides a MenuItem with the Undo role
|
||||||
|
func Undo() *MenuItem {
|
||||||
|
return &MenuItem{
|
||||||
|
Role: UndoRole,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Redo provides a MenuItem with the Redo role
|
||||||
|
func Redo() *MenuItem {
|
||||||
|
return &MenuItem{
|
||||||
|
Role: RedoRole,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cut provides a MenuItem with the Cut role
|
||||||
|
func Cut() *MenuItem {
|
||||||
|
return &MenuItem{
|
||||||
|
Role: CutRole,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Copy provides a MenuItem with the Copy role
|
||||||
|
func Copy() *MenuItem {
|
||||||
|
return &MenuItem{
|
||||||
|
Role: CopyRole,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Paste provides a MenuItem with the Paste role
|
||||||
|
func Paste() *MenuItem {
|
||||||
|
return &MenuItem{
|
||||||
|
Role: PasteRole,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// PasteAndMatchStyle provides a MenuItem with the PasteAndMatchStyle role
|
||||||
|
func PasteAndMatchStyle() *MenuItem {
|
||||||
|
return &MenuItem{
|
||||||
|
Role: PasteAndMatchStyleRole,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// SelectAll provides a MenuItem with the SelectAll role
|
||||||
|
func SelectAll() *MenuItem {
|
||||||
|
return &MenuItem{
|
||||||
|
Role: SelectAllRole,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete provides a MenuItem with the Delete role
|
||||||
|
func Delete() *MenuItem {
|
||||||
|
return &MenuItem{
|
||||||
|
Role: DeleteRole,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Minimize provides a MenuItem with the Minimize role
|
||||||
|
func Minimize() *MenuItem {
|
||||||
|
return &MenuItem{
|
||||||
|
Role: MinimizeRole,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Quit provides a MenuItem with the Quit role
|
||||||
|
func Quit() *MenuItem {
|
||||||
|
return &MenuItem{
|
||||||
|
Role: QuitRole,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Togglefullscreen provides a MenuItem with the Togglefullscreen role
|
||||||
|
func Togglefullscreen() *MenuItem {
|
||||||
|
return &MenuItem{
|
||||||
|
Role: TogglefullscreenRole,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// FileMenu provides a MenuItem with the whole default "File" menu (Close / Quit)
|
||||||
|
func FileMenu() *MenuItem {
|
||||||
|
return &MenuItem{
|
||||||
|
Role: FileMenuRole,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// EditMenu provides a MenuItem with the whole default "Edit" menu (Undo, Copy, etc.).
|
||||||
|
func EditMenu() *MenuItem {
|
||||||
|
return &MenuItem{
|
||||||
|
Role: EditMenuRole,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ViewMenu provides a MenuItem with the whole default "View" menu (Reload, Toggle Developer Tools, etc.)
|
||||||
|
func ViewMenu() *MenuItem {
|
||||||
|
return &MenuItem{
|
||||||
|
Role: ViewMenuRole,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WindowMenu provides a MenuItem with the whole default "Window" menu (Minimize, Zoom, etc.).
|
||||||
|
func WindowMenu() *MenuItem {
|
||||||
|
return &MenuItem{
|
||||||
|
Role: WindowMenuRole,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// These roles are Mac only
|
||||||
|
|
||||||
|
// AppMenu provides a MenuItem with the whole default "App" menu (About, Services, etc.)
|
||||||
|
func AppMenu() *MenuItem {
|
||||||
|
return &MenuItem{
|
||||||
|
Role: AppMenuRole,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hide provides a MenuItem that maps to the hide action.
|
||||||
|
func Hide() *MenuItem {
|
||||||
|
return &MenuItem{
|
||||||
|
Role: HideRole,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// HideOthers provides a MenuItem that maps to the hideOtherApplications action.
|
||||||
|
func HideOthers() *MenuItem {
|
||||||
|
return &MenuItem{
|
||||||
|
Role: HideOthersRole,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unhide provides a MenuItem that maps to the unhideAllApplications action.
|
||||||
|
func Unhide() *MenuItem {
|
||||||
|
return &MenuItem{
|
||||||
|
Role: UnhideRole,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Front provides a MenuItem that maps to the arrangeInFront action.
|
||||||
|
func Front() *MenuItem {
|
||||||
|
return &MenuItem{
|
||||||
|
Role: FrontRole,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Zoom provides a MenuItem that maps to the performZoom action.
|
||||||
|
func Zoom() *MenuItem {
|
||||||
|
return &MenuItem{
|
||||||
|
Role: ZoomRole,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WindowSubMenu provides a MenuItem with the "Window" submenu.
|
||||||
|
func WindowSubMenu() *MenuItem {
|
||||||
|
return &MenuItem{
|
||||||
|
Role: WindowSubMenuRole,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// HelpSubMenu provides a MenuItem with the "Help" submenu.
|
||||||
|
func HelpSubMenu() *MenuItem {
|
||||||
|
return &MenuItem{
|
||||||
|
Role: HelpSubMenuRole,
|
||||||
|
}
|
||||||
|
}
|
10
v2/pkg/menu/submenu.go
Normal file
10
v2/pkg/menu/submenu.go
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
package menu
|
||||||
|
|
||||||
|
// SubMenu creates a new submenu which may be added to other
|
||||||
|
// menus
|
||||||
|
func SubMenu(label string, items []*MenuItem) *MenuItem {
|
||||||
|
return &MenuItem{
|
||||||
|
Label: label,
|
||||||
|
SubMenu: items,
|
||||||
|
}
|
||||||
|
}
|
17
v2/pkg/menu/type.go
Normal file
17
v2/pkg/menu/type.go
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
package menu
|
||||||
|
|
||||||
|
// Type of the menu item
|
||||||
|
type Type string
|
||||||
|
|
||||||
|
const (
|
||||||
|
// NormalType is the Normal menuitem type
|
||||||
|
NormalType Type = "Normal"
|
||||||
|
// SeparatorType is the Separator menuitem type
|
||||||
|
SeparatorType Type = "Separator"
|
||||||
|
// SubmenuType is the Submenu menuitem type
|
||||||
|
SubmenuType Type = "Submenu"
|
||||||
|
// CheckboxType is the Checkbox menuitem type
|
||||||
|
CheckboxType Type = "Checkbox"
|
||||||
|
// RadioType is the Radio menuitem type
|
||||||
|
RadioType Type = "Radio"
|
||||||
|
)
|
@ -2,6 +2,7 @@ package options
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/wailsapp/wails/v2/pkg/logger"
|
"github.com/wailsapp/wails/v2/pkg/logger"
|
||||||
|
"github.com/wailsapp/wails/v2/pkg/menu"
|
||||||
"github.com/wailsapp/wails/v2/pkg/options/mac"
|
"github.com/wailsapp/wails/v2/pkg/options/mac"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -17,6 +18,7 @@ var Default = &App{
|
|||||||
Appearance: mac.DefaultAppearance,
|
Appearance: mac.DefaultAppearance,
|
||||||
WebviewIsTransparent: false,
|
WebviewIsTransparent: false,
|
||||||
WindowBackgroundIsTranslucent: false,
|
WindowBackgroundIsTranslucent: false,
|
||||||
|
Menu: menu.DefaultMacMenu(),
|
||||||
},
|
},
|
||||||
Logger: logger.NewDefaultLogger(),
|
Logger: logger.NewDefaultLogger(),
|
||||||
LogLevel: logger.INFO,
|
LogLevel: logger.INFO,
|
||||||
|
@ -1,9 +1,12 @@
|
|||||||
package mac
|
package mac
|
||||||
|
|
||||||
// Options are options speific to Mac
|
import "github.com/wailsapp/wails/v2/pkg/menu"
|
||||||
|
|
||||||
|
// Options are options specific to Mac
|
||||||
type Options struct {
|
type Options struct {
|
||||||
TitleBar *TitleBar
|
TitleBar *TitleBar
|
||||||
Appearance AppearanceType
|
Appearance AppearanceType
|
||||||
WebviewIsTransparent bool
|
WebviewIsTransparent bool
|
||||||
WindowBackgroundIsTranslucent bool
|
WindowBackgroundIsTranslucent bool
|
||||||
|
Menu *menu.Menu
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,10 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
wails "github.com/wailsapp/wails/v2"
|
wails "github.com/wailsapp/wails/v2"
|
||||||
|
"github.com/wailsapp/wails/v2/pkg/menu"
|
||||||
"github.com/wailsapp/wails/v2/pkg/options"
|
"github.com/wailsapp/wails/v2/pkg/options"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -14,6 +17,11 @@ type Dialog struct {
|
|||||||
func (l *Dialog) WailsInit(runtime *wails.Runtime) error {
|
func (l *Dialog) WailsInit(runtime *wails.Runtime) error {
|
||||||
// Perform your setup here
|
// Perform your setup here
|
||||||
l.runtime = runtime
|
l.runtime = runtime
|
||||||
|
|
||||||
|
// Setup Menu Listeners
|
||||||
|
l.runtime.Menu.On("hello", func(m *menu.MenuItem) {
|
||||||
|
fmt.Printf("The '%s' menu was clicked\n", m.Label)
|
||||||
|
})
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3,12 +3,38 @@ package main
|
|||||||
import (
|
import (
|
||||||
wails "github.com/wailsapp/wails/v2"
|
wails "github.com/wailsapp/wails/v2"
|
||||||
"github.com/wailsapp/wails/v2/pkg/logger"
|
"github.com/wailsapp/wails/v2/pkg/logger"
|
||||||
|
"github.com/wailsapp/wails/v2/pkg/menu"
|
||||||
"github.com/wailsapp/wails/v2/pkg/options"
|
"github.com/wailsapp/wails/v2/pkg/options"
|
||||||
"github.com/wailsapp/wails/v2/pkg/options/mac"
|
"github.com/wailsapp/wails/v2/pkg/options/mac"
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
|
|
||||||
|
// Create menu
|
||||||
|
myMenu := menu.DefaultMacMenu()
|
||||||
|
|
||||||
|
windowMenu := menu.SubMenu("Test", []*menu.MenuItem{
|
||||||
|
menu.Togglefullscreen(),
|
||||||
|
menu.Minimize(),
|
||||||
|
menu.Zoom(),
|
||||||
|
|
||||||
|
menu.Separator(),
|
||||||
|
|
||||||
|
menu.Copy(),
|
||||||
|
menu.Cut(),
|
||||||
|
menu.Delete(),
|
||||||
|
|
||||||
|
menu.Separator(),
|
||||||
|
|
||||||
|
menu.Front(),
|
||||||
|
|
||||||
|
menu.SubMenu("Test Submenu", []*menu.MenuItem{
|
||||||
|
menu.Text("Hi!", "hello"), // Label = "Hi!", ID= "hello"
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
|
||||||
|
myMenu.Append(windowMenu)
|
||||||
|
|
||||||
// Create application with options
|
// Create application with options
|
||||||
app := wails.CreateAppWithOptions(&options.App{
|
app := wails.CreateAppWithOptions(&options.App{
|
||||||
Title: "Kitchen Sink",
|
Title: "Kitchen Sink",
|
||||||
@ -21,6 +47,7 @@ func main() {
|
|||||||
WindowBackgroundIsTranslucent: true,
|
WindowBackgroundIsTranslucent: true,
|
||||||
// Comment out line below to see Window.SetTitle() work
|
// Comment out line below to see Window.SetTitle() work
|
||||||
TitleBar: mac.TitleBarHiddenInset(),
|
TitleBar: mac.TitleBarHiddenInset(),
|
||||||
|
Menu: myMenu,
|
||||||
},
|
},
|
||||||
LogLevel: logger.TRACE,
|
LogLevel: logger.TRACE,
|
||||||
})
|
})
|
||||||
|
@ -17,7 +17,6 @@ func (w *Window) WailsInit(runtime *wails.Runtime) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (w *Window) SetTitle(title string) {
|
func (w *Window) SetTitle(title string) {
|
||||||
println("In SetTitle:", title)
|
|
||||||
w.runtime.Window.SetTitle(title)
|
w.runtime.Window.SetTitle(title)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user