5
0
mirror of https://github.com/wailsapp/wails.git synced 2025-05-03 01:13:03 +08:00

Updated tray icon

This commit is contained in:
Lea Anthony 2020-12-16 08:55:46 +11:00
parent a1f9d9ca06
commit 34ac62e4ac
No known key found for this signature in database
GPG Key ID: 33DAF7BB90A58405
8 changed files with 341 additions and 26 deletions

View File

@ -65,6 +65,14 @@
// then right buttons are reported as left buttons
#define NSEventMaskLeftMouseDown 1 << 1
#define NSEventMaskLeftMouseUp 1 << 2
#define NSEventMaskRightMouseDown 1 << 3
#define NSEventMaskRightMouseUp 1 << 4
#define NSEventTypeLeftMouseDown 1
#define NSEventTypeLeftMouseUp 2
#define NSEventTypeRightMouseDown 3
#define NSEventTypeRightMouseUp 4
// References to assets
extern const unsigned char *assets[];
@ -90,6 +98,15 @@ struct hashmap_s menuItemMapForTrayMenu;
// RadioGroup map for the tray menu. Maps a menuitem id with its associated radio group items
struct hashmap_s radioGroupMapForTrayMenu;
// contextMenuMap is a hashmap of context menus keyed on a string ID
struct hashmap_s contextMenuMap;
// MenuItem map for the context menus
struct hashmap_s menuItemMapForContextMenus;
// RadioGroup map for the context menus. Maps a menuitem id with its associated radio group items
struct hashmap_s radioGroupMapForContextMenus;
// Dispatch Method
typedef void (^dispatchMethod)(void);
@ -205,6 +222,10 @@ struct Application {
JsonNode *processedTrayMenu;
id statusItem;
// Context Menus
const char *contextMenusAsJSON;
JsonNode *processedContextMenus;
// User Data
char *HTML;
@ -292,7 +313,32 @@ void applyWindowColour(struct Application *app) {
msg(app->mainWindow, s("setBackgroundColor:"), colour);
);
}
}
}
void showContextMenu(struct Application *app, const char *contextMenuID) {
// If no context menu ID was given
if( contextMenuID == NULL ) {
// Show default context menu if we have one
return;
}
ON_MAIN_THREAD (
// Look for the context menu for this ID
id contextMenu = (id)hashmap_get(&contextMenuMap, (char*)contextMenuID, strlen(contextMenuID));
// Grab the content view and show the menu
id contentView = msg(app->mainWindow, s("contentView"));
// Get the triggering event
id menuEvent = msg(app->mainWindow, s("currentEvent"));
// Show popup
msg(c("NSMenu"), s("popUpContextMenu:withEvent:forView:"), contextMenu, menuEvent, contentView);
);
}
void SetColour(struct Application *app, int red, int green, int blue, int alpha) {
app->red = red;
@ -342,6 +388,62 @@ void messageHandler(id self, SEL cmd, id contentController, id message) {
msg(app->mainWindow, s("performWindowDragWithEvent:"), app->mouseEvent);
);
}
} else if( strcmp(name, "contextMenu") == 0 ) {
// Did we get a context menu selector?
if( message == NULL) {
return;
}
const char *contextMenuMessage = cstr(msg(message, s("body")));
// Parse the message
JsonNode *contextMenuMessageJSON = json_decode(contextMenuMessage);
if( contextMenuMessageJSON == NULL ) {
Debug(app, "Error decoding context menu message: %s", contextMenuMessage);
return;
}
// Get menu ID
JsonNode *contextMenuIDNode = json_find_member(contextMenuMessageJSON, "id");
if( contextMenuIDNode == NULL ) {
Debug(app, "Error decoding context menu ID: %s", contextMenuMessage);
return;
}
if( contextMenuIDNode->tag != JSON_STRING ) {
Debug(app, "Error decoding context menu ID (Not a string): %s", contextMenuMessage);
return;
}
// TODO: Use the X & Y coordinates of the menu to programmatically open
// the context menu at that point rather than relying on the current NSEvent.
// I got it mostly working (IE not crashing) but the menu was invisible...
// Revisit later
// // Get menu X
// JsonNode *contextMenuXNode = json_find_member(contextMenuMessageJSON, "x");
// if( contextMenuXNode == NULL ) {
// Debug(app, "Error decoding context menu X: %s", contextMenuMessage);
// return;
// }
// if( contextMenuXNode->tag != JSON_NUMBER ) {
// Debug(app, "Error decoding context menu X (Not a number): %s", contextMenuMessage);
// return;
// }
// // Get menu Y
// JsonNode *contextMenuYNode = json_find_member(contextMenuMessageJSON, "y");
// if( contextMenuYNode == NULL ) {
// Debug(app, "Error decoding context menu Y: %s", contextMenuMessage);
// return;
// }
// if( contextMenuYNode->tag != JSON_NUMBER ) {
// Debug(app, "Error decoding context menu Y (Not a number): %s", contextMenuMessage);
// return;
// }
ON_MAIN_THREAD(
showContextMenu(app, contextMenuIDNode->string_);
);
} else {
// const char *m = (const char *)msg(msg(message, s("body")), s("UTF8String"));
const char *m = cstr(msg(message, s("body")));
@ -367,6 +469,16 @@ void menuItemPressedForTrayMenu(id self, SEL cmd, id sender) {
free((void*)message);
}
// Callback for context menu items
void menuItemPressedForContextMenus(id self, SEL cmd, id sender) {
const char *menuID = (const char *)msg(msg(sender, s("representedObject")), s("pointerValue"));
// Notify the backend
const char *message = concat("XC", menuID);
messageFromWindowCallback(message);
free((void*)message);
}
// Callback for menu items
void checkboxMenuItemPressedForApplicationMenu(id self, SEL cmd, id sender, struct hashmap_s *menuItemMap) {
const char *menuID = (const char *)msg(msg(sender, s("representedObject")), s("pointerValue"));
@ -405,6 +517,25 @@ void checkboxMenuItemPressedForTrayMenu(id self, SEL cmd, id sender, struct hash
free((void*)message);
}
// Callback for context menu items
void checkboxMenuItemPressedForContextMenus(id self, SEL cmd, id sender, struct hashmap_s *menuItemMap) {
const char *menuID = (const char *)msg(msg(sender, s("representedObject")), s("pointerValue"));
// Get the menu item from the menu item map
id menuItem = (id)hashmap_get(&menuItemMapForContextMenus, (char*)menuID, strlen(menuID));
// Get the current state
bool state = msg(menuItem, s("state"));
// Toggle the state
msg(menuItem, s("setState:"), (state? NSControlStateValueOff : NSControlStateValueOn));
// Notify the backend
const char *message = concat("XC", menuID);
messageFromWindowCallback(message);
free((void*)message);
}
// radioMenuItemPressedForApplicationMenu
void radioMenuItemPressedForApplicationMenu(id self, SEL cmd, id sender) {
const char *menuID = (const char *)msg(msg(sender, s("representedObject")), s("pointerValue"));
@ -478,6 +609,42 @@ void radioMenuItemPressedForTrayMenu(id self, SEL cmd, id sender) {
free((void*)message);
}
// radioMenuItemPressedForContextMenus
void radioMenuItemPressedForContextMenus(id self, SEL cmd, id sender) {
const char *menuID = (const char *)msg(msg(sender, s("representedObject")), s("pointerValue"));
// Get the menu item from the menu item map
id menuItem = (id)hashmap_get(&menuItemMapForContextMenus, (char*)menuID, strlen(menuID));
// Check the menu items' current state
bool selected = msg(menuItem, s("state"));
// If it's already selected, exit early
if (selected) {
return;
}
// Get this item's radio group members and turn them off
id *members = (id*)hashmap_get(&radioGroupMapForContextMenus, (char*)menuID, strlen(menuID));
// Uncheck all members of the group
id thisMember = members[0];
int count = 0;
while(thisMember != NULL) {
msg(thisMember, s("setState:"), NSControlStateValueOff);
count = count + 1;
thisMember = members[count];
}
// check the selected menu item
msg(menuItem, s("setState:"), NSControlStateValueOn);
// Notify the backend
const char *message = concat("XC", menuID);
messageFromWindowCallback(message);
free((void*)message);
}
// closeWindow is called when the close button is pressed
void closeWindow(id self, SEL cmd, id sender) {
struct Application *app = (struct Application *) objc_getAssociatedObject(self, "application");
@ -547,6 +714,28 @@ void allocateTrayHashMaps(struct Application *app) {
}
}
void allocateContextMenuHashMaps(struct Application *app) {
// Allocate new context menu map
if( 0 != hashmap_create((const unsigned)4, &contextMenuMap)) {
// Couldn't allocate map
Fatal(app, "Not enough memory to allocate contextMenuMap!");
}
// Allocate new menuItem map
if( 0 != hashmap_create((const unsigned)16, &menuItemMapForContextMenus)) {
// Couldn't allocate map
Fatal(app, "Not enough memory to allocate menuItemMapForContextMenus!");
}
// Allocate the Radio Group Cache
if( 0 != hashmap_create((const unsigned)4, &radioGroupMapForContextMenus)) {
// Couldn't allocate map
Fatal(app, "Not enough memory to allocate radioGroupMapForContextMenus!");
return;
}
}
void* NewApplication(const char *title, int width, int height, int resizable, int devtools, int fullscreen, int startHidden, int logLevel) {
// Setup main application struct
struct Application *result = malloc(sizeof(struct Application));
@ -594,6 +783,9 @@ void* NewApplication(const char *title, int width, int height, int resizable, in
result->processedTrayMenu = NULL;
result->statusItem = NULL;
// Context Menus
result->contextMenusAsJSON = NULL;
// Window Appearance
result->vibrancyLayer = NULL;
result->titlebarAppearsTransparent = 0;
@ -637,6 +829,34 @@ void destroyMenu(struct Application *app) {
}
}
void destroyContextMenus(struct Application *app) {
// If we don't have a context menu, return
if( app->contextMenusAsJSON == NULL ) {
return;
}
// Free menu item hashmap
hashmap_destroy(&menuItemMapForContextMenus);
// Free radio group members
if( hashmap_num_entries(&radioGroupMapForContextMenus) > 0 ) {
if (0!=hashmap_iterate_pairs(&radioGroupMapForContextMenus, freeHashmapItem, NULL)) {
Fatal(app, "failed to deallocate hashmap entries!");
}
}
//Free radio groups hashmap
hashmap_destroy(&radioGroupMapForContextMenus);
//Free context menu map
hashmap_destroy(&contextMenuMap);
// Destroy context menu JSON
free((void*)app->contextMenusAsJSON);
app->contextMenusAsJSON = NULL;
}
void destroyTray(struct Application *app) {
@ -658,11 +878,13 @@ void destroyTray(struct Application *app) {
//Free radio groups hashmap
hashmap_destroy(&radioGroupMapForTrayMenu);
// Release the menu json if we have it
if ( app->trayMenuAsJSON != NULL ) {
free((void*)app->trayMenuAsJSON);
app->trayMenuAsJSON = NULL;
}
// Free up the context menu map
hashmap_destroy(&contextMenuMap);
// Release the menu json
free((void*)app->trayMenuAsJSON);
app->trayMenuAsJSON = NULL;
// Release processed tray
if( app->processedTrayMenu != NULL) {
@ -699,7 +921,11 @@ void DestroyApplication(struct Application *app) {
// Destroy the tray
destroyTray(app);
// Destroy the context menus
destroyContextMenus(app);
// Remove script handlers
msg(app->manager, s("removeScriptMessageHandlerForName:"), str("contextMenu"));
msg(app->manager, s("removeScriptMessageHandlerForName:"), str("windowDrag"));
msg(app->manager, s("removeScriptMessageHandlerForName:"), str("external"));
@ -1055,6 +1281,11 @@ void SetTray(struct Application *app, const char *trayMenuAsJSON) {
app->trayMenuAsJSON = trayMenuAsJSON;
}
// SetContextMenus sets the context menu map for this application
void SetContextMenus(struct Application *app, const char *contextMenusAsJSON) {
app->contextMenusAsJSON = contextMenusAsJSON;
}
void SetBindings(struct Application *app, const char *bindings) {
const char* temp = concat("window.wailsbindings = \"", bindings);
const char* jscall = concat(temp, "\";");
@ -1149,7 +1380,9 @@ void createDelegate(struct Application *app) {
class_addMethod(delegateClass, s("menuCallbackForTrayMenu:"), (IMP)menuItemPressedForTrayMenu, "v@:@");
class_addMethod(delegateClass, s("checkboxMenuCallbackForTrayMenu:"), (IMP) checkboxMenuItemPressedForTrayMenu, "v@:@");
class_addMethod(delegateClass, s("radioMenuCallbackForTrayMenu:"), (IMP) radioMenuItemPressedForTrayMenu, "v@:@");
class_addMethod(delegateClass, s("menuCallbackForContextMenus:"), (IMP)menuItemPressedForContextMenus, "v@:@");
class_addMethod(delegateClass, s("checkboxMenuCallbackForContextMenus:"), (IMP) checkboxMenuItemPressedForContextMenus, "v@:@");
class_addMethod(delegateClass, s("radioMenuCallbackForContextMenus:"), (IMP) radioMenuItemPressedForContextMenus, "v@:@");
// Script handler
class_addMethod(delegateClass, s("userContentController:didReceiveScriptMessage:"), (IMP) messageHandler, "v@:@@");
@ -1575,15 +1808,15 @@ id processAcceleratorKey(const char *key) {
if( STREQ(key, "F35") ) {
return strunicode(0xf726);
}
if( STREQ(key, "Insert") ) {
return strunicode(0xf727);
}
if( STREQ(key, "PrintScreen") ) {
return strunicode(0xf72e);
}
if( STREQ(key, "ScrollLock") ) {
return strunicode(0xf72f);
}
// if( STREQ(key, "Insert") ) {
// return strunicode(0xf727);
// }
// if( STREQ(key, "PrintScreen") ) {
// return strunicode(0xf72e);
// }
// if( STREQ(key, "ScrollLock") ) {
// return strunicode(0xf72f);
// }
if( STREQ(key, "NumLock") ) {
return strunicode(0xf739);
}
@ -1917,6 +2150,36 @@ void UpdateMenu(struct Application *app, const char *menuAsJSON) {
);
}
void parseContextMenus(struct Application *app) {
// Allocation the hashmaps we need
allocateContextMenuHashMaps(app);
// Parse the context menu json
app->processedContextMenus = json_decode(app->contextMenusAsJSON);
if( app->processedContextMenus == NULL ) {
// Parse error!
Fatal(app, "Unable to parse Context Menus JSON: %s", app->contextMenusAsJSON);
return;
}
// Iterate context menus
JsonNode *contextMenu;
json_foreach(contextMenu, app->processedContextMenus) {
// Create a new menu
id menu = createMenu(str(""));
// parse the menu
parseMenu(app, menu, contextMenu, &menuItemMapForContextMenus,
"checkboxMenuCallbackForContextMenus:", "radioMenuCallbackForContextMenus:", "menuCallbackForContextMenus:");
// Store the item in the context menu map
hashmap_put(&contextMenuMap, (char*)contextMenu->key, strlen(contextMenu->key), menu);
}
}
void parseTrayData(struct Application *app) {
// Allocate the hashmaps we need
@ -1944,8 +2207,6 @@ void parseTrayData(struct Application *app) {
}
}
Debug(app, ">>>>>>>>>>> TRAY MENU: %s", app->trayMenuAsJSON);
// Parse the processed menu json
app->processedTrayMenu = json_decode(app->trayMenuAsJSON);
@ -1995,9 +2256,7 @@ void parseTrayData(struct Application *app) {
// UpdateTray replaces the current tray menu with the given one
void UpdateTray(struct Application *app, const char *trayMenuAsJSON) {
Debug(app, "tray is now: %s", trayMenuAsJSON);
ON_MAIN_THREAD (
// Free up memory
destroyTray(app);
@ -2010,16 +2269,18 @@ void UpdateTray(struct Application *app, const char *trayMenuAsJSON) {
void Run(struct Application *app, int argc, char **argv) {
// Process window decorations
processDecorations(app);
// Create the application
createApplication(app);
// Define delegate
createDelegate(app);
// Create the main window
createMainWindow(app);
// Create Content View
id contentView = msg( ALLOC("NSView"), s("init") );
msg(app->mainWindow, s("setContentView:"), contentView);
@ -2033,6 +2294,7 @@ void Run(struct Application *app, int argc, char **argv) {
// Set Colour
applyWindowColour(app);
// Process translucency
if (app->windowBackgroundIsTranslucent) {
makeWindowBackgroundTranslucent(app);
}
@ -2083,6 +2345,9 @@ void Run(struct Application *app, int argc, char **argv) {
return incomingEvent;
});
// Setup context menu message handler
msg(manager, s("addScriptMessageHandler:name:"), app->delegate, str("contextMenu"));
// Toolbar
if( app->useToolBar ) {
Debug(app, "Setting Toolbar");
@ -2139,7 +2404,7 @@ void Run(struct Application *app, int argc, char **argv) {
// Disable context menu if not in debug mode
if( debug != 1 ) {
temp = concat(internalCode, "wails._.DisableContextMenu();");
temp = concat(internalCode, "wails._.DisableDefaultContextMenu();");
free((void*)internalCode);
internalCode = temp;
}
@ -2179,6 +2444,11 @@ void Run(struct Application *app, int argc, char **argv) {
parseTrayData(app);
}
// If we have context menus, process them
if( app->contextMenusAsJSON != NULL ) {
parseContextMenus(app);
}
// Finally call run
Debug(app, "Run called");
msg(app->application, s("run"));

View File

@ -16,6 +16,7 @@ extern void WebviewIsTransparent(void *);
extern void SetWindowBackgroundIsTranslucent(void *);
extern void SetMenu(void *, const char *);
extern void SetTray(void *, const char *);
extern void SetContextMenus(void *, const char *);
*/
import "C"
import (
@ -111,5 +112,15 @@ func (a *Application) processPlatformSettings() error {
C.SetTray(a.app, a.string2CString(string(trayMenuJSON)))
}
// Process context menus
contextMenus := options.GetContextMenus(a.config)
if contextMenus != nil {
contextMenusJSON, err := json.Marshal(contextMenus)
if err != nil {
return err
}
C.SetContextMenus(a.app, a.string2CString(string(contextMenusJSON)))
}
return nil
}

View File

@ -21,7 +21,7 @@ func (d *DesktopBuilder) processTrayIcons(assetDir string, options *Options) err
// Setup target
targetFilename := "trayicon"
targetFile := filepath.Join(assetDir, targetFilename+".c")
//d.addFileToDelete(targetFile)
d.addFileToDelete(targetFile)
var dataBytes []byte

View File

@ -10,4 +10,5 @@ type Options struct {
WindowBackgroundIsTranslucent bool
Menu *menu.Menu
Tray *menu.Menu
ContextMenus map[string]*menu.Menu
}

View File

@ -24,6 +24,7 @@ type App struct {
StartHidden bool
DevTools bool
RGBA int
ContextMenus map[string]*menu.Menu
Tray *menu.Menu
Menu *menu.Menu
Mac *mac.Options
@ -95,3 +96,31 @@ func GetApplicationMenu(appoptions *App) *menu.Menu {
return result
}
func GetContextMenus(appoptions *App) map[string]*menu.Menu {
var result map[string]*menu.Menu
result = appoptions.ContextMenus
var contextMenuOverrides map[string]*menu.Menu
switch runtime.GOOS {
case "darwin":
if appoptions.Mac != nil {
contextMenuOverrides = appoptions.Mac.ContextMenus
}
//case "linux":
// if appoptions.Linux != nil {
// result = appoptions.Linux.Tray
// }
//case "windows":
// if appoptions.Windows != nil {
// result = appoptions.Windows.Tray
// }
}
// Overwrite defaults with OS Specific context menus
for id, contextMenu := range contextMenuOverrides {
result[id] = contextMenu
}
return result
}

View File

@ -43,7 +43,7 @@
<!-- Sticky alerts (toasts), empty container -->
<div class="sticky-alerts"></div>
<!-- Sidebar -->
<div class="sidebar noselect">
<div class="sidebar noselect" data-wails-context-menu-id="test">
<div data-wails-no-drag class="sidebar-menu">
<!-- Sidebar brand -->
<div on:click="{ homepageClicked }" class="sidebar-brand">
@ -53,7 +53,7 @@
<h5 class="sidebar-title">Runtime</h5>
<div class="sidebar-divider"></div>
{#each runtimePages as link}
<span on:click="{linkClicked}" class="sidebar-link" class:active="{$selectedPage == link}">{link}</span>
<span on:click="{linkClicked}" class="sidebar-link" class:active="{$selectedPage === link}">{link}</span>
{/each}
<br />
<h5 class="sidebar-title">Links</h5>
@ -65,7 +65,7 @@
<!-- Content wrapper -->
<div class="content-wrapper noselect" class:dark-content-wrapper="{$darkMode}">
<div class="inner-content">
<MainPage></MainPage>
<MainPage/>
</div>
</div>
</div>

View File

@ -3,6 +3,7 @@ package main
import (
"github.com/wailsapp/wails/v2"
"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/mac"
"log"
@ -19,6 +20,9 @@ func main() {
MinHeight: 600,
//Tray: menu.NewMenuFromItems(menu.AppMenu()),
//Menu: menu.NewMenuFromItems(menu.AppMenu()),
ContextMenus: map[string]*menu.Menu{
"test": menu.NewMenuFromItems(menu.Text("Test Menu", "Test Context Menu")),
},
Mac: &mac.Options{
WebviewIsTransparent: true,
WindowBackgroundIsTranslucent: true,

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB