mirror of
https://github.com/wailsapp/wails.git
synced 2026-03-14 14:45:49 +01:00
Merge branch 'exp' into feature/1521_Support_Tray_Menus
# Conflicts: # .gitignore # v2/internal/frontend/desktop/windows/win32/consts.go # v2/internal/frontend/devserver/devserver.go # website/docs/introduction.mdx
This commit is contained in:
commit
e5a1ddb3f3
26 changed files with 1217 additions and 280 deletions
2
.gitignore
vendored
2
.gitignore
vendored
|
|
@ -31,3 +31,5 @@ v2/test/kitchensink/frontend/package.json.md5
|
|||
v2/cmd/wails/internal/commands/initialise/templates/testtemplates/
|
||||
.env
|
||||
/website/static/img/.cache.json
|
||||
|
||||
v2/internal/frontend/desktop/darwin/test.xcodeproj
|
||||
|
|
@ -17,6 +17,7 @@
|
|||
@property bool startHidden;
|
||||
@property bool startFullscreen;
|
||||
@property (retain) WailsWindow* mainWindow;
|
||||
@property int activationPolicy;
|
||||
|
||||
@end
|
||||
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@
|
|||
#import <Cocoa/Cocoa.h>
|
||||
|
||||
#import "AppDelegate.h"
|
||||
#import "message.h"
|
||||
|
||||
@implementation AppDelegate
|
||||
- (BOOL)applicationShouldTerminateAfterLastWindowClosed:(NSApplication *)sender {
|
||||
|
|
@ -25,6 +26,8 @@
|
|||
}
|
||||
|
||||
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
|
||||
processNotification(0); // Notify Go
|
||||
[NSApp setActivationPolicy:self.activationPolicy];
|
||||
[NSApp activateIgnoringOtherApps:YES];
|
||||
if ( self.startFullscreen ) {
|
||||
NSWindowCollectionBehavior behaviour = [self.mainWindow collectionBehavior];
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@
|
|||
#define WindowStartsFullscreen 3
|
||||
|
||||
WailsContext* Create(const char* title, int width, int height, int frameless, int resizable, int fullscreen, int fullSizeContent, int hideTitleBar, int titlebarAppearsTransparent, int hideTitle, int useToolbar, int hideToolbarSeparator, int webviewIsTransparent, int alwaysOnTop, int hideWindowOnClose, const char *appearance, int windowIsTranslucent, int debug, int windowStartState, int startsHidden, int minWidth, int minHeight, int maxWidth, int maxHeight);
|
||||
void Run(void*, const char* url);
|
||||
void Run(void *inctx, const char* url, int activationPolicy);
|
||||
|
||||
void SetTitle(void* ctx, const char *title);
|
||||
void Center(void* ctx);
|
||||
|
|
@ -62,6 +62,16 @@ void AppendSubmenu(void* parent, void* child);
|
|||
void AppendRole(void *inctx, void *inMenu, int role);
|
||||
void SetAsApplicationMenu(void *inctx, void *inMenu);
|
||||
void UpdateApplicationMenu(void *inctx);
|
||||
void SetMenuItemChecked(void* nsMenuItem, int checked);
|
||||
|
||||
/* Tray Menu */
|
||||
void NewNSStatusItem(int id, int length);
|
||||
void SetTrayMenu(void *nsStatusItem, void* nsMenu);
|
||||
void SetTrayMenuLabel(void *nsStatusItem, const char *label);
|
||||
void SetTrayImage(void *nsStatusItem, void *imageData, int imageDataLength, int template, int position);
|
||||
|
||||
/* MenuItems */
|
||||
void SetMenuItemLabel(void *nsStatusItem, const char *label);
|
||||
|
||||
void SetAbout(void *inctx, const char* title, const char* description, void* imagedata, int datalen);
|
||||
void* AppendMenuItem(void* inctx, void* nsmenu, const char* label, const char* shortcutKey, int modifiers, int disabled, int checked, int menuItemID);
|
||||
|
|
@ -70,4 +80,7 @@ void UpdateMenuItem(void* nsmenuitem, int checked);
|
|||
|
||||
NSString* safeInit(const char* input);
|
||||
|
||||
|
||||
int ScalingFactor(void *ctx);
|
||||
|
||||
#endif /* Application_h */
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@
|
|||
#import "AppDelegate.h"
|
||||
#import "WailsMenu.h"
|
||||
#import "WailsMenuItem.h"
|
||||
#import "message.h"
|
||||
|
||||
WailsContext* Create(const char* title, int width, int height, int frameless, int resizable, int fullscreen, int fullSizeContent, int hideTitleBar, int titlebarAppearsTransparent, int hideTitle, int useToolbar, int hideToolbarSeparator, int webviewIsTransparent, int alwaysOnTop, int hideWindowOnClose, const char *appearance, int windowIsTranslucent, int debug, int windowStartState, int startsHidden, int minWidth, int minHeight, int maxWidth, int maxHeight) {
|
||||
|
||||
|
|
@ -20,7 +21,7 @@ WailsContext* Create(const char* title, int width, int height, int frameless, in
|
|||
WailsContext *result = [WailsContext new];
|
||||
|
||||
result.debug = debug;
|
||||
|
||||
|
||||
if ( windowStartState == WindowStartsFullscreen ) {
|
||||
fullscreen = 1;
|
||||
}
|
||||
|
|
@ -28,7 +29,7 @@ WailsContext* Create(const char* title, int width, int height, int frameless, in
|
|||
[result CreateWindow:width :height :frameless :resizable :fullscreen :fullSizeContent :hideTitleBar :titlebarAppearsTransparent :hideTitle :useToolbar :hideToolbarSeparator :webviewIsTransparent :hideWindowOnClose :safeInit(appearance) :windowIsTranslucent :minWidth :minHeight :maxWidth :maxHeight];
|
||||
[result SetTitle:safeInit(title)];
|
||||
[result Center];
|
||||
|
||||
|
||||
switch( windowStartState ) {
|
||||
case WindowStartsMaximised:
|
||||
[result.mainWindow zoom:nil];
|
||||
|
|
@ -170,6 +171,10 @@ void ToggleMaximise(void* inctx) {
|
|||
);
|
||||
}
|
||||
|
||||
void SetMenuItemChecked(void* nsMenuItem, int checked) {
|
||||
[(NSMenuItem*)nsMenuItem setState:(checked == 0 ? NSOffState : NSOnState)];
|
||||
}
|
||||
|
||||
const char* GetSize(void *inctx) {
|
||||
WailsContext *ctx = (__bridge WailsContext*) inctx;
|
||||
NSRect frame = [ctx.mainWindow frame];
|
||||
|
|
@ -304,6 +309,55 @@ void AppendRole(void *inctx, void *inMenu, int role) {
|
|||
[menu appendRole :ctx :role];
|
||||
}
|
||||
|
||||
void NewNSStatusItem(int id, int length) {
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
|
||||
NSStatusBar *statusBar = [NSStatusBar systemStatusBar];
|
||||
// Map Go to Cocoa length. 0 = NSVariableStatusItemLength.
|
||||
CGFloat length = NSVariableStatusItemLength;
|
||||
if( length == 1 ) {
|
||||
length = NSSquareStatusItemLength;
|
||||
}
|
||||
NSStatusItem *result = [[statusBar statusItemWithLength:length] retain];
|
||||
objectCreated(id,result);
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
void DeleteStatusItem(void *_nsStatusItem) {
|
||||
NSStatusItem *nsStatusItem = (NSStatusItem*) _nsStatusItem;
|
||||
[nsStatusItem release];
|
||||
}
|
||||
|
||||
void on_main_thread(void (^l)(void)) {
|
||||
dispatch_async(dispatch_get_main_queue(), l);
|
||||
}
|
||||
|
||||
void SetTrayMenuLabel(void *_nsStatusItem, const char *label) {
|
||||
on_main_thread(^{
|
||||
NSStatusItem *nsStatusItem = (NSStatusItem*) _nsStatusItem;
|
||||
nsStatusItem.button.title = safeInit(label);
|
||||
free((void*)label);
|
||||
});
|
||||
}
|
||||
|
||||
void SetTrayMenu(void *nsStatusItem, void* nsMenu) {
|
||||
ON_MAIN_THREAD(
|
||||
[(NSStatusItem*)nsStatusItem setMenu:(NSMenu *)nsMenu];
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
/**** Menu Item ****/
|
||||
|
||||
void SetMenuItemLabel(void *_nsMenuItem, const char *label) {
|
||||
on_main_thread(^{
|
||||
NSMenuItem *nsMenuItem = (NSMenuItem*) _nsMenuItem;
|
||||
[ nsMenuItem setTitle:safeInit(label) ];
|
||||
free((void*)label);
|
||||
});
|
||||
}
|
||||
|
||||
void* NewMenu(const char *name) {
|
||||
NSString *title = @"";
|
||||
if (name != nil) {
|
||||
|
|
@ -328,8 +382,8 @@ void SetAsApplicationMenu(void *inctx, void *inMenu) {
|
|||
void UpdateApplicationMenu(void *inctx) {
|
||||
WailsContext *ctx = (__bridge WailsContext*) inctx;
|
||||
ON_MAIN_THREAD(
|
||||
NSApplication *app = [NSApplication sharedApplication];
|
||||
[app setMainMenu:ctx.applicationMenu];
|
||||
NSApplication *app = [NSApplication sharedApplication];
|
||||
[app setMainMenu:ctx.applicationMenu];
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -354,7 +408,7 @@ void UpdateMenuItem(void* nsmenuitem, int checked) {
|
|||
ON_MAIN_THREAD(
|
||||
WailsMenuItem *menuItem = (__bridge WailsMenuItem*) nsmenuitem;
|
||||
[menuItem setState:(checked == 1?NSControlStateValueOn:NSControlStateValueOff)];
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -363,12 +417,39 @@ void AppendSeparator(void* inMenu) {
|
|||
[menu AppendSeparator];
|
||||
}
|
||||
|
||||
void SetTrayImage(void *nsStatusItem, void *imageData, int imageDataLength, int template, int position) {
|
||||
ON_MAIN_THREAD(
|
||||
NSStatusItem *statusItem = (NSStatusItem*) nsStatusItem;
|
||||
NSData *nsdata = [NSData dataWithBytes:imageData length:imageDataLength];
|
||||
NSImage *image = [[[NSImage alloc] initWithData:nsdata] autorelease];
|
||||
if(template) {
|
||||
image.template = true;
|
||||
}
|
||||
image.size = NSMakeSize(22.0, 22.0);
|
||||
statusItem.button.image = image;
|
||||
|
||||
// Swap NSNoImage and NSImageLeading because we wanted NSImageLeading to be default in Go
|
||||
int actualPosition = position;
|
||||
if( position == 7) {
|
||||
actualPosition = 0;
|
||||
} else if (position == 0) {
|
||||
actualPosition = 7;
|
||||
}
|
||||
[statusItem.button setImagePosition:actualPosition];
|
||||
|
||||
)
|
||||
}
|
||||
|
||||
int ScalingFactor(void *ctx) {
|
||||
CGFloat scale = [((WailsContext*)ctx).mainWindow backingScaleFactor];
|
||||
return (int)scale;
|
||||
}
|
||||
|
||||
void Run(void *inctx, const char* url) {
|
||||
void Run(void *inctx, const char* url, int activationPolicy) {
|
||||
WailsContext *ctx = (__bridge WailsContext*) inctx;
|
||||
NSApplication *app = [NSApplication sharedApplication];
|
||||
AppDelegate* delegate = [AppDelegate new];
|
||||
delegate.activationPolicy = activationPolicy;
|
||||
[app setDelegate:(id)delegate];
|
||||
ctx.appdelegate = delegate;
|
||||
delegate.mainWindow = ctx.mainWindow;
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@
|
|||
|
||||
@property NSSize userMinSize;
|
||||
@property NSSize userMaxSize;
|
||||
@property int activationPolicy;
|
||||
|
||||
- (BOOL) canBecomeKeyWindow;
|
||||
- (void) applyWindowConstraints;
|
||||
|
|
|
|||
|
|
@ -3,15 +3,6 @@
|
|||
|
||||
package darwin
|
||||
|
||||
/*
|
||||
#cgo CFLAGS: -x objective-c
|
||||
#cgo LDFLAGS: -framework Foundation -framework Cocoa -framework WebKit
|
||||
#import <Foundation/Foundation.h>
|
||||
#import "Application.h"
|
||||
#import "WailsContext.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
*/
|
||||
import "C"
|
||||
import (
|
||||
"bytes"
|
||||
|
|
@ -31,14 +22,33 @@ import (
|
|||
"github.com/wailsapp/wails/v2/internal/frontend"
|
||||
"github.com/wailsapp/wails/v2/internal/frontend/assetserver"
|
||||
"github.com/wailsapp/wails/v2/internal/logger"
|
||||
"github.com/wailsapp/wails/v2/pkg/menu"
|
||||
"github.com/wailsapp/wails/v2/pkg/options"
|
||||
)
|
||||
|
||||
/*
|
||||
#cgo CFLAGS: -x objective-c
|
||||
#cgo LDFLAGS: -framework Foundation -framework Cocoa -framework WebKit
|
||||
#import <Foundation/Foundation.h>
|
||||
#import "Application.h"
|
||||
#import "WailsContext.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
*/
|
||||
import "C"
|
||||
|
||||
const startURL = "wails://wails/"
|
||||
|
||||
type NotificationType uint8
|
||||
|
||||
const (
|
||||
ApplicationDidFinishLaunching NotificationType = 0
|
||||
)
|
||||
|
||||
var messageBuffer = make(chan string, 100)
|
||||
var requestBuffer = make(chan *request, 100)
|
||||
var callbackBuffer = make(chan uint, 10)
|
||||
var notificationBuffer = make(chan NotificationType, 10)
|
||||
|
||||
type Frontend struct {
|
||||
|
||||
|
|
@ -57,15 +67,22 @@ type Frontend struct {
|
|||
mainWindow *Window
|
||||
bindings *binding.Bindings
|
||||
dispatcher frontend.Dispatcher
|
||||
trayMenus map[*menu.TrayMenu]*NSTrayMenu
|
||||
|
||||
applicationDidFinishLaunching bool
|
||||
notificationCallbacks map[NotificationType][]func()
|
||||
trayMenusBuffer []*menu.TrayMenu
|
||||
}
|
||||
|
||||
func NewFrontend(ctx context.Context, appoptions *options.App, myLogger *logger.Logger, appBindings *binding.Bindings, dispatcher frontend.Dispatcher) *Frontend {
|
||||
result := &Frontend{
|
||||
frontendOptions: appoptions,
|
||||
logger: myLogger,
|
||||
bindings: appBindings,
|
||||
dispatcher: dispatcher,
|
||||
ctx: ctx,
|
||||
frontendOptions: appoptions,
|
||||
logger: myLogger,
|
||||
bindings: appBindings,
|
||||
dispatcher: dispatcher,
|
||||
ctx: ctx,
|
||||
trayMenus: make(map[*menu.TrayMenu]*NSTrayMenu),
|
||||
notificationCallbacks: make(map[NotificationType][]func()),
|
||||
}
|
||||
result.startURL, _ = url.Parse(startURL)
|
||||
|
||||
|
|
@ -93,10 +110,17 @@ func NewFrontend(ctx context.Context, appoptions *options.App, myLogger *logger.
|
|||
|
||||
go result.startMessageProcessor()
|
||||
go result.startCallbackProcessor()
|
||||
go result.startNotificationsProcessor()
|
||||
|
||||
result.registerNotificationCallback(ApplicationDidFinishLaunching, result.processTrayMenus)
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
func (f *Frontend) registerNotificationCallback(notificationType NotificationType, callback func()) {
|
||||
f.notificationCallbacks[notificationType] = append(f.notificationCallbacks[notificationType], callback)
|
||||
}
|
||||
|
||||
func (f *Frontend) startMessageProcessor() {
|
||||
for message := range messageBuffer {
|
||||
f.processMessage(message)
|
||||
|
|
@ -115,6 +139,11 @@ func (f *Frontend) startCallbackProcessor() {
|
|||
}
|
||||
}
|
||||
}
|
||||
func (f *Frontend) startNotificationsProcessor() {
|
||||
for notification := range notificationBuffer {
|
||||
f.handleNotification(notification)
|
||||
}
|
||||
}
|
||||
|
||||
func (f *Frontend) WindowReload() {
|
||||
f.ExecJS("runtime.WindowReload();")
|
||||
|
|
@ -154,7 +183,11 @@ func (f *Frontend) Run(ctx context.Context) error {
|
|||
f.frontendOptions.OnStartup(f.ctx)
|
||||
}
|
||||
}()
|
||||
mainWindow.Run(f.startURL.String())
|
||||
var activationPolicy C.int
|
||||
if f.frontendOptions != nil && f.frontendOptions.Mac != nil {
|
||||
activationPolicy = C.int(f.frontendOptions.Mac.ActivationPolicy)
|
||||
}
|
||||
mainWindow.Run(f.startURL.String(), activationPolicy)
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
@ -438,6 +471,11 @@ func processMessage(message *C.char) {
|
|||
messageBuffer <- goMessage
|
||||
}
|
||||
|
||||
//export processNotification
|
||||
func processNotification(notification NotificationType) {
|
||||
notificationBuffer <- notification
|
||||
}
|
||||
|
||||
//export processURLRequest
|
||||
func processURLRequest(ctx unsafe.Pointer, requestId C.ulonglong, url *C.char, method *C.char, headers *C.char, body unsafe.Pointer, bodyLen C.int) {
|
||||
var goBody []byte
|
||||
|
|
@ -459,3 +497,19 @@ func processURLRequest(ctx unsafe.Pointer, requestId C.ulonglong, url *C.char, m
|
|||
func processCallback(callbackID uint) {
|
||||
callbackBuffer <- callbackID
|
||||
}
|
||||
|
||||
func (f *Frontend) handleNotification(notification NotificationType) {
|
||||
switch notification {
|
||||
case ApplicationDidFinishLaunching:
|
||||
f.applicationDidFinishLaunching = true
|
||||
for _, callback := range f.notificationCallbacks[notification] {
|
||||
go callback()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (f *Frontend) processTrayMenus() {
|
||||
for _, trayMenu := range f.trayMenusBuffer {
|
||||
f.mainWindow.TrayMenuAdd(trayMenu)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,12 +14,86 @@ package darwin
|
|||
*/
|
||||
import "C"
|
||||
import (
|
||||
"fmt"
|
||||
"sync"
|
||||
"unsafe"
|
||||
|
||||
"github.com/google/uuid"
|
||||
|
||||
"github.com/wailsapp/wails/v2/pkg/menu"
|
||||
"github.com/wailsapp/wails/v2/pkg/menu/keys"
|
||||
)
|
||||
|
||||
var createNSObjectMap = make(map[uint32]chan unsafe.Pointer)
|
||||
var createNSObjectMapLock sync.RWMutex
|
||||
|
||||
func waitNSObjectCreate(id uint32, fn func()) unsafe.Pointer {
|
||||
waitchan := make(chan unsafe.Pointer)
|
||||
createNSObjectMapLock.Lock()
|
||||
createNSObjectMap[id] = waitchan
|
||||
createNSObjectMapLock.Unlock()
|
||||
fn()
|
||||
result := <-waitchan
|
||||
createNSObjectMapLock.Lock()
|
||||
createNSObjectMap[id] = nil
|
||||
createNSObjectMapLock.Unlock()
|
||||
return result
|
||||
}
|
||||
|
||||
//export objectCreated
|
||||
func objectCreated(id uint32, pointer unsafe.Pointer) {
|
||||
createNSObjectMapLock.Lock()
|
||||
createNSObjectMap[id] <- pointer
|
||||
createNSObjectMapLock.Unlock()
|
||||
}
|
||||
|
||||
func NewNSTrayMenu(context unsafe.Pointer, trayMenu *menu.TrayMenu, scalingFactor int) *NSTrayMenu {
|
||||
c := NewCalloc()
|
||||
defer c.Free()
|
||||
|
||||
id := uuid.New().ID()
|
||||
nsStatusItem := waitNSObjectCreate(id, func() {
|
||||
C.NewNSStatusItem(C.int(id), C.int(trayMenu.Sizing))
|
||||
})
|
||||
result := &NSTrayMenu{
|
||||
context: context,
|
||||
nsStatusItem: nsStatusItem,
|
||||
scalingFactor: scalingFactor,
|
||||
}
|
||||
|
||||
result.SetLabel(trayMenu.Label)
|
||||
result.SetMenu(trayMenu.Menu)
|
||||
result.SetImage(trayMenu.Image)
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
func (n *NSTrayMenu) SetImage(image *menu.TrayImage) {
|
||||
if image == nil {
|
||||
return
|
||||
}
|
||||
bitmap := image.GetBestBitmap(n.scalingFactor, false)
|
||||
if bitmap == nil {
|
||||
fmt.Printf("[Warning] No TrayMenu Image available for scaling factor %dx\n", n.scalingFactor)
|
||||
return
|
||||
}
|
||||
C.SetTrayImage(n.nsStatusItem,
|
||||
unsafe.Pointer(&bitmap[0]),
|
||||
C.int(len(bitmap)),
|
||||
bool2Cint(image.IsTemplate),
|
||||
C.int(image.Position),
|
||||
)
|
||||
}
|
||||
|
||||
func (n *NSTrayMenu) SetMenu(menu *menu.Menu) {
|
||||
if menu == nil {
|
||||
return
|
||||
}
|
||||
theMenu := NewNSMenu(n.context, "")
|
||||
processMenu(theMenu, menu)
|
||||
C.SetTrayMenu(n.nsStatusItem, theMenu.nsmenu)
|
||||
}
|
||||
|
||||
type NSMenu struct {
|
||||
context unsafe.Pointer
|
||||
nsmenu unsafe.Pointer
|
||||
|
|
@ -53,6 +127,15 @@ type MenuItem struct {
|
|||
radioGroupMembers []*MenuItem
|
||||
}
|
||||
|
||||
func (m *MenuItem) SetChecked(value bool) {
|
||||
C.SetMenuItemChecked(m.nsmenuitem, bool2Cint(value))
|
||||
}
|
||||
|
||||
func (m *MenuItem) SetLabel(label string) {
|
||||
cLabel := C.CString(label)
|
||||
C.SetMenuItemLabel(m.nsmenuitem, cLabel)
|
||||
}
|
||||
|
||||
func (m *NSMenu) AddMenuItem(menuItem *menu.MenuItem) *MenuItem {
|
||||
c := NewCalloc()
|
||||
defer c.Free()
|
||||
|
|
@ -69,6 +152,7 @@ func (m *NSMenu) AddMenuItem(menuItem *menu.MenuItem) *MenuItem {
|
|||
|
||||
result.id = createMenuItemID(result)
|
||||
result.nsmenuitem = C.AppendMenuItem(m.context, m.nsmenu, c.String(menuItem.Label), key, modifier, bool2Cint(menuItem.Disabled), bool2Cint(menuItem.Checked), C.int(result.id))
|
||||
menuItem.Impl = result
|
||||
return result
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -20,6 +20,8 @@ void processMessageDialogResponse(int);
|
|||
void processOpenFileDialogResponse(const char*);
|
||||
void processSaveFileDialogResponse(const char*);
|
||||
void processCallback(int);
|
||||
void processNotification(int);
|
||||
void objectCreated(int, void*);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
|
|
|
|||
44
v2/internal/frontend/desktop/darwin/traymenu.go
Normal file
44
v2/internal/frontend/desktop/darwin/traymenu.go
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
//go:build darwin
|
||||
// +build darwin
|
||||
|
||||
package darwin
|
||||
|
||||
/*
|
||||
#cgo CFLAGS: -x objective-c
|
||||
#cgo LDFLAGS: -framework Foundation -framework Cocoa -framework WebKit
|
||||
#import <Foundation/Foundation.h>
|
||||
#import "Application.h"
|
||||
#import "WailsContext.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
*/
|
||||
import "C"
|
||||
import (
|
||||
"unsafe"
|
||||
|
||||
"github.com/wailsapp/wails/v2/pkg/menu"
|
||||
)
|
||||
|
||||
func (f *Frontend) TrayMenuAdd(trayMenu *menu.TrayMenu) menu.TrayMenuImpl {
|
||||
nsTrayMenu := f.mainWindow.TrayMenuAdd(trayMenu)
|
||||
f.trayMenus[trayMenu] = nsTrayMenu
|
||||
return nsTrayMenu
|
||||
}
|
||||
|
||||
type NSTrayMenu struct {
|
||||
context unsafe.Pointer
|
||||
nsStatusItem unsafe.Pointer // NSStatusItem
|
||||
scalingFactor int
|
||||
}
|
||||
|
||||
func (n *NSTrayMenu) SetLabel(label string) {
|
||||
if label == "" {
|
||||
return
|
||||
}
|
||||
cLabel := C.CString(label)
|
||||
C.SetTrayMenuLabel(n.nsStatusItem, cLabel)
|
||||
}
|
||||
|
||||
func (w *Window) TrayMenuAdd(trayMenu *menu.TrayMenu) *NSTrayMenu {
|
||||
return NewNSTrayMenu(w.context, trayMenu, ScalingFactor(w))
|
||||
}
|
||||
|
|
@ -40,6 +40,10 @@ func bool2Cint(value bool) C.int {
|
|||
return C.int(0)
|
||||
}
|
||||
|
||||
func ScalingFactor(window *Window) int {
|
||||
return int(C.ScalingFactor(window.context))
|
||||
}
|
||||
|
||||
func NewWindow(frontendOptions *options.App, debugMode bool) *Window {
|
||||
|
||||
c := NewCalloc()
|
||||
|
|
@ -119,9 +123,9 @@ func (w *Window) Center() {
|
|||
C.Center(w.context)
|
||||
}
|
||||
|
||||
func (w *Window) Run(url string) {
|
||||
func (w *Window) Run(url string, activationPolicy C.int) {
|
||||
_url := C.CString(url)
|
||||
C.Run(w.context, _url)
|
||||
C.Run(w.context, _url, activationPolicy)
|
||||
C.free(unsafe.Pointer(_url))
|
||||
}
|
||||
|
||||
|
|
|
|||
36
v2/internal/frontend/desktop/windows/png.go
Normal file
36
v2/internal/frontend/desktop/windows/png.go
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
package windows
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"golang.org/x/image/draw"
|
||||
"image"
|
||||
"image/png"
|
||||
)
|
||||
|
||||
func ResizePNG(in []byte, size int) ([]byte, error) {
|
||||
imagedata, _, err := image.Decode(bytes.NewReader(in))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Scale image
|
||||
rect := image.Rect(0, 0, size, size)
|
||||
rawdata := image.NewRGBA(rect)
|
||||
scale := draw.CatmullRom
|
||||
scale.Scale(rawdata, rect, imagedata, imagedata.Bounds(), draw.Over, nil)
|
||||
|
||||
// Convert back to PNG
|
||||
icondata := new(bytes.Buffer)
|
||||
writer := bufio.NewWriter(icondata)
|
||||
err = png.Encode(writer, rawdata)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = writer.Flush()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Save image data
|
||||
return icondata.Bytes(), nil
|
||||
}
|
||||
79
v2/internal/frontend/desktop/windows/traymenu.go
Normal file
79
v2/internal/frontend/desktop/windows/traymenu.go
Normal file
|
|
@ -0,0 +1,79 @@
|
|||
//go:build windows
|
||||
// +build windows
|
||||
|
||||
package windows
|
||||
|
||||
import (
|
||||
"github.com/wailsapp/wails/v2/internal/frontend/desktop/windows/win32"
|
||||
"github.com/wailsapp/wails/v2/pkg/menu"
|
||||
"log"
|
||||
"sync"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
var uids uint32
|
||||
var lock sync.RWMutex
|
||||
|
||||
func newUID() uint32 {
|
||||
lock.Lock()
|
||||
result := uids
|
||||
uids++
|
||||
lock.Unlock()
|
||||
return result
|
||||
|
||||
}
|
||||
|
||||
type Win32TrayMenu struct {
|
||||
hwnd uintptr
|
||||
uid uint32
|
||||
icon uintptr
|
||||
}
|
||||
|
||||
func (w *Win32TrayMenu) SetLabel(label string) {}
|
||||
|
||||
func (w *Win32TrayMenu) SetMenu(menu *menu.Menu) {}
|
||||
|
||||
func (w *Win32TrayMenu) SetImage(image *menu.TrayImage) {
|
||||
data := w.newNotifyIconData()
|
||||
bitmap := image.GetBestBitmap(1, false)
|
||||
icon, err := win32.CreateIconFromResourceEx(uintptr(unsafe.Pointer(&bitmap[0])), uint32(len(bitmap)), true, 0x30000, 0, 0, 0)
|
||||
if err != nil {
|
||||
log.Fatal(err.Error())
|
||||
}
|
||||
data.UFlags |= win32.NIF_ICON
|
||||
data.HIcon = icon
|
||||
if _, err := win32.NotifyIcon(win32.NIM_MODIFY, data); err != nil {
|
||||
log.Fatal(err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
func (f *Frontend) NewWin32TrayMenu(trayMenu *menu.TrayMenu) *Win32TrayMenu {
|
||||
|
||||
result := &Win32TrayMenu{
|
||||
hwnd: f.mainWindow.Handle(),
|
||||
uid: newUID(),
|
||||
}
|
||||
|
||||
data := result.newNotifyIconData()
|
||||
data.UFlags |= win32.NIF_MESSAGE | win32.NIF_ICON
|
||||
data.UCallbackMessage = win32.WM_APP + result.uid
|
||||
if _, err := win32.NotifyIcon(win32.NIM_ADD, data); err != nil {
|
||||
log.Fatal(err.Error())
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
func (w *Win32TrayMenu) newNotifyIconData() *win32.NOTIFYICONDATA {
|
||||
var data win32.NOTIFYICONDATA
|
||||
data.CbSize = uint32(unsafe.Sizeof(data))
|
||||
data.UFlags = win32.NIF_GUID
|
||||
data.HWnd = w.hwnd
|
||||
data.UID = w.uid
|
||||
return &data
|
||||
}
|
||||
|
||||
func (f *Frontend) TrayMenuAdd(trayMenu *menu.TrayMenu) menu.TrayMenuImpl {
|
||||
win32TrayMenu := f.NewWin32TrayMenu(trayMenu)
|
||||
return win32TrayMenu
|
||||
}
|
||||
|
|
@ -23,6 +23,15 @@ var (
|
|||
procGetWindowRect = moduser32.NewProc("GetWindowRect")
|
||||
procGetMonitorInfo = moduser32.NewProc("GetMonitorInfoW")
|
||||
procMonitorFromWindow = moduser32.NewProc("MonitorFromWindow")
|
||||
procLookupIconIdFromDirectoryEx = moduser32.NewProc("LookupIconIdFromDirectoryEx")
|
||||
procCreateIconFromResourceEx = moduser32.NewProc("CreateIconFromResourceEx")
|
||||
procCreateIconIndirect = moduser32.NewProc("CreateIconIndirect")
|
||||
procLoadImageW = moduser32.NewProc("LoadImageW")
|
||||
)
|
||||
|
||||
var (
|
||||
modshell32 = syscall.NewLazyDLL("shell32.dll")
|
||||
procShellNotifyIcon = modshell32.NewProc("Shell_NotifyIconW")
|
||||
)
|
||||
var (
|
||||
moddwmapi = syscall.NewLazyDLL("dwmapi.dll")
|
||||
|
|
@ -41,3 +50,217 @@ func IsWindowsVersionAtLeast(major, minor, buildNumber int) bool {
|
|||
windowsVersion.Minor >= minor &&
|
||||
windowsVersion.Build >= buildNumber
|
||||
}
|
||||
|
||||
// http://msdn.microsoft.com/en-us/library/windows/desktop/aa373931.aspx
|
||||
type GUID struct {
|
||||
Data1 uint32
|
||||
Data2 uint16
|
||||
Data3 uint16
|
||||
Data4 [8]byte
|
||||
}
|
||||
|
||||
const (
|
||||
WM_APP = 32768
|
||||
WM_ACTIVATE = 6
|
||||
WM_ACTIVATEAPP = 28
|
||||
WM_AFXFIRST = 864
|
||||
WM_AFXLAST = 895
|
||||
WM_ASKCBFORMATNAME = 780
|
||||
WM_CANCELJOURNAL = 75
|
||||
WM_CANCELMODE = 31
|
||||
WM_CAPTURECHANGED = 533
|
||||
WM_CHANGECBCHAIN = 781
|
||||
WM_CHAR = 258
|
||||
WM_CHARTOITEM = 47
|
||||
WM_CHILDACTIVATE = 34
|
||||
WM_CLEAR = 771
|
||||
WM_CLOSE = 16
|
||||
WM_COMMAND = 273
|
||||
WM_COMMNOTIFY = 68 /* OBSOLETE */
|
||||
WM_COMPACTING = 65
|
||||
WM_COMPAREITEM = 57
|
||||
WM_CONTEXTMENU = 123
|
||||
WM_COPY = 769
|
||||
WM_COPYDATA = 74
|
||||
WM_CREATE = 1
|
||||
WM_CTLCOLORBTN = 309
|
||||
WM_CTLCOLORDLG = 310
|
||||
WM_CTLCOLOREDIT = 307
|
||||
WM_CTLCOLORLISTBOX = 308
|
||||
WM_CTLCOLORMSGBOX = 306
|
||||
WM_CTLCOLORSCROLLBAR = 311
|
||||
WM_CTLCOLORSTATIC = 312
|
||||
WM_CUT = 768
|
||||
WM_DEADCHAR = 259
|
||||
WM_DELETEITEM = 45
|
||||
WM_DESTROY = 2
|
||||
WM_DESTROYCLIPBOARD = 775
|
||||
WM_DEVICECHANGE = 537
|
||||
WM_DEVMODECHANGE = 27
|
||||
WM_DISPLAYCHANGE = 126
|
||||
WM_DRAWCLIPBOARD = 776
|
||||
WM_DRAWITEM = 43
|
||||
WM_DROPFILES = 563
|
||||
WM_ENABLE = 10
|
||||
WM_ENDSESSION = 22
|
||||
WM_ENTERIDLE = 289
|
||||
WM_ENTERMENULOOP = 529
|
||||
WM_ENTERSIZEMOVE = 561
|
||||
WM_ERASEBKGND = 20
|
||||
WM_EXITMENULOOP = 530
|
||||
WM_EXITSIZEMOVE = 562
|
||||
WM_FONTCHANGE = 29
|
||||
WM_GETDLGCODE = 135
|
||||
WM_GETFONT = 49
|
||||
WM_GETHOTKEY = 51
|
||||
WM_GETICON = 127
|
||||
WM_GETMINMAXINFO = 36
|
||||
WM_GETTEXT = 13
|
||||
WM_GETTEXTLENGTH = 14
|
||||
WM_HANDHELDFIRST = 856
|
||||
WM_HANDHELDLAST = 863
|
||||
WM_HELP = 83
|
||||
WM_HOTKEY = 786
|
||||
WM_HSCROLL = 276
|
||||
WM_HSCROLLCLIPBOARD = 782
|
||||
WM_ICONERASEBKGND = 39
|
||||
WM_INITDIALOG = 272
|
||||
WM_INITMENU = 278
|
||||
WM_INITMENUPOPUP = 279
|
||||
WM_INPUT = 0x00FF
|
||||
WM_INPUTLANGCHANGE = 81
|
||||
WM_INPUTLANGCHANGEREQUEST = 80
|
||||
WM_KEYDOWN = 256
|
||||
WM_KEYUP = 257
|
||||
WM_KILLFOCUS = 8
|
||||
WM_MDIACTIVATE = 546
|
||||
WM_MDICASCADE = 551
|
||||
WM_MDICREATE = 544
|
||||
WM_MDIDESTROY = 545
|
||||
WM_MDIGETACTIVE = 553
|
||||
WM_MDIICONARRANGE = 552
|
||||
WM_MDIMAXIMIZE = 549
|
||||
WM_MDINEXT = 548
|
||||
WM_MDIREFRESHMENU = 564
|
||||
WM_MDIRESTORE = 547
|
||||
WM_MDISETMENU = 560
|
||||
WM_MDITILE = 550
|
||||
WM_MEASUREITEM = 44
|
||||
WM_GETOBJECT = 0x003D
|
||||
WM_CHANGEUISTATE = 0x0127
|
||||
WM_UPDATEUISTATE = 0x0128
|
||||
WM_QUERYUISTATE = 0x0129
|
||||
WM_UNINITMENUPOPUP = 0x0125
|
||||
WM_MENURBUTTONUP = 290
|
||||
WM_MENUCOMMAND = 0x0126
|
||||
WM_MENUGETOBJECT = 0x0124
|
||||
WM_MENUDRAG = 0x0123
|
||||
WM_APPCOMMAND = 0x0319
|
||||
WM_MENUCHAR = 288
|
||||
WM_MENUSELECT = 287
|
||||
WM_MOVE = 3
|
||||
WM_MOVING = 534
|
||||
WM_NCACTIVATE = 134
|
||||
WM_NCCALCSIZE = 131
|
||||
WM_NCCREATE = 129
|
||||
WM_NCDESTROY = 130
|
||||
WM_NCHITTEST = 132
|
||||
WM_NCLBUTTONDBLCLK = 163
|
||||
WM_NCLBUTTONDOWN = 161
|
||||
WM_NCLBUTTONUP = 162
|
||||
WM_NCMBUTTONDBLCLK = 169
|
||||
WM_NCMBUTTONDOWN = 167
|
||||
WM_NCMBUTTONUP = 168
|
||||
WM_NCXBUTTONDOWN = 171
|
||||
WM_NCXBUTTONUP = 172
|
||||
WM_NCXBUTTONDBLCLK = 173
|
||||
WM_NCMOUSEHOVER = 0x02A0
|
||||
WM_NCMOUSELEAVE = 0x02A2
|
||||
WM_NCMOUSEMOVE = 160
|
||||
WM_NCPAINT = 133
|
||||
WM_NCRBUTTONDBLCLK = 166
|
||||
WM_NCRBUTTONDOWN = 164
|
||||
WM_NCRBUTTONUP = 165
|
||||
WM_NEXTDLGCTL = 40
|
||||
WM_NEXTMENU = 531
|
||||
WM_NOTIFY = 78
|
||||
WM_NOTIFYFORMAT = 85
|
||||
WM_NULL = 0
|
||||
WM_PAINT = 15
|
||||
WM_PAINTCLIPBOARD = 777
|
||||
WM_PAINTICON = 38
|
||||
WM_PALETTECHANGED = 785
|
||||
WM_PALETTEISCHANGING = 784
|
||||
WM_PARENTNOTIFY = 528
|
||||
WM_PASTE = 770
|
||||
WM_PENWINFIRST = 896
|
||||
WM_PENWINLAST = 911
|
||||
WM_POWER = 72
|
||||
WM_PRINT = 791
|
||||
WM_PRINTCLIENT = 792
|
||||
WM_QUERYDRAGICON = 55
|
||||
WM_QUERYENDSESSION = 17
|
||||
WM_QUERYNEWPALETTE = 783
|
||||
WM_QUERYOPEN = 19
|
||||
WM_QUEUESYNC = 35
|
||||
WM_QUIT = 18
|
||||
WM_RENDERALLFORMATS = 774
|
||||
WM_RENDERFORMAT = 773
|
||||
WM_SETCURSOR = 32
|
||||
WM_SETFOCUS = 7
|
||||
WM_SETFONT = 48
|
||||
WM_SETHOTKEY = 50
|
||||
WM_SETICON = 128
|
||||
WM_SETREDRAW = 11
|
||||
WM_SETTEXT = 12
|
||||
WM_SETTINGCHANGE = 26
|
||||
WM_SHOWWINDOW = 24
|
||||
WM_SIZE = 5
|
||||
WM_SIZECLIPBOARD = 779
|
||||
WM_SIZING = 532
|
||||
WM_SPOOLERSTATUS = 42
|
||||
WM_STYLECHANGED = 125
|
||||
WM_STYLECHANGING = 124
|
||||
WM_SYSCHAR = 262
|
||||
WM_SYSCOLORCHANGE = 21
|
||||
WM_SYSCOMMAND = 274
|
||||
WM_SYSDEADCHAR = 263
|
||||
WM_SYSKEYDOWN = 260
|
||||
WM_SYSKEYUP = 261
|
||||
WM_TCARD = 82
|
||||
WM_THEMECHANGED = 794
|
||||
WM_TIMECHANGE = 30
|
||||
WM_TIMER = 275
|
||||
WM_UNDO = 772
|
||||
WM_USER = 1024
|
||||
WM_USERCHANGED = 84
|
||||
WM_VKEYTOITEM = 46
|
||||
WM_VSCROLL = 277
|
||||
WM_VSCROLLCLIPBOARD = 778
|
||||
WM_WINDOWPOSCHANGED = 71
|
||||
WM_WINDOWPOSCHANGING = 70
|
||||
WM_WININICHANGE = 26
|
||||
WM_KEYFIRST = 256
|
||||
WM_KEYLAST = 264
|
||||
WM_SYNCPAINT = 136
|
||||
WM_MOUSEACTIVATE = 33
|
||||
WM_MOUSEMOVE = 512
|
||||
WM_LBUTTONDOWN = 513
|
||||
WM_LBUTTONUP = 514
|
||||
WM_LBUTTONDBLCLK = 515
|
||||
WM_RBUTTONDOWN = 516
|
||||
WM_RBUTTONUP = 517
|
||||
WM_RBUTTONDBLCLK = 518
|
||||
WM_MBUTTONDOWN = 519
|
||||
WM_MBUTTONUP = 520
|
||||
WM_MBUTTONDBLCLK = 521
|
||||
WM_MOUSEWHEEL = 522
|
||||
WM_MOUSEFIRST = 512
|
||||
WM_XBUTTONDOWN = 523
|
||||
WM_XBUTTONUP = 524
|
||||
WM_XBUTTONDBLCLK = 525
|
||||
WM_MOUSELAST = 525
|
||||
WM_MOUSEHOVER = 0x2A1
|
||||
WM_MOUSELEAVE = 0x2A3
|
||||
WM_CLIPBOARDUPDATE = 0x031D
|
||||
)
|
||||
|
|
|
|||
130
v2/internal/frontend/desktop/windows/win32/tray.go
Normal file
130
v2/internal/frontend/desktop/windows/win32/tray.go
Normal file
|
|
@ -0,0 +1,130 @@
|
|||
package win32
|
||||
|
||||
import "unsafe"
|
||||
|
||||
const (
|
||||
NIF_MESSAGE = 0x00000001
|
||||
NIF_ICON = 0x00000002
|
||||
NIF_TIP = 0x00000004
|
||||
NIF_STATE = 0x00000008
|
||||
NIF_INFO = 0x00000010
|
||||
NIF_GUID = 0x00000020
|
||||
NIF_REALTIME = 0x00000040
|
||||
NIF_SHOWTIP = 0x00000080
|
||||
NIM_ADD = 0x00000000
|
||||
NIM_MODIFY = 0x00000001
|
||||
NIM_DELETE = 0x00000002
|
||||
NIM_SETFOCUS = 0x00000003
|
||||
NIM_SETVERSION = 0x00000004
|
||||
NIS_HIDDEN = 0x00000001
|
||||
NIS_SHAREDICON = 0x00000002
|
||||
NIN_BALLOONSHOW = 0x0402
|
||||
NIN_BALLOONTIMEOUT = 0x0404
|
||||
NIN_BALLOONUSERCLICK = 0x0405
|
||||
NIIF_NONE = 0x00000000
|
||||
NIIF_INFO = 0x00000001
|
||||
NIIF_WARNING = 0x00000002
|
||||
NIIF_ERROR = 0x00000003
|
||||
NIIF_USER = 0x00000004
|
||||
NIIF_NOSOUND = 0x00000010
|
||||
NIIF_LARGE_ICON = 0x00000020
|
||||
NIIF_RESPECT_QUIET_TIME = 0x00000080
|
||||
NIIF_ICON_MASK = 0x0000000F
|
||||
)
|
||||
|
||||
type NOTIFYICONDATA struct {
|
||||
CbSize uint32
|
||||
HWnd uintptr
|
||||
UID uint32
|
||||
UFlags uint32
|
||||
UCallbackMessage uint32
|
||||
HIcon uintptr
|
||||
SzTip [128]uint16
|
||||
DwState uint32
|
||||
DwStateMask uint32
|
||||
SzInfo [256]uint16
|
||||
UVersion uint32
|
||||
SzInfoTitle [64]uint16
|
||||
DwInfoFlags uint32
|
||||
GUIDItem GUID
|
||||
HBalloonIcon uintptr
|
||||
}
|
||||
|
||||
func NotifyIcon(msg uint32, lpData *NOTIFYICONDATA) (int32, error) {
|
||||
r, _, err := procShellNotifyIcon.Call(
|
||||
uintptr(msg),
|
||||
uintptr(unsafe.Pointer(lpData)))
|
||||
if r == 0 {
|
||||
return 0, err
|
||||
}
|
||||
return int32(r), nil
|
||||
}
|
||||
|
||||
func LookupIconIdFromDirectoryEx(presbits uintptr, isIcon bool, cxDesired int, cyDesired int, flags uint) (int32, error) {
|
||||
var icon uint32 = 0
|
||||
if isIcon {
|
||||
icon = 1
|
||||
}
|
||||
r, _, err := procLookupIconIdFromDirectoryEx.Call(
|
||||
presbits,
|
||||
uintptr(icon),
|
||||
uintptr(cxDesired),
|
||||
uintptr(cyDesired),
|
||||
uintptr(flags),
|
||||
)
|
||||
if r == 0 {
|
||||
return 0, err
|
||||
}
|
||||
return int32(r), nil
|
||||
}
|
||||
|
||||
func CreateIconIndirect(data uintptr) (uintptr, error) {
|
||||
r, _, err := procCreateIconIndirect.Call(
|
||||
data,
|
||||
)
|
||||
|
||||
if r == 0 {
|
||||
return 0, err
|
||||
}
|
||||
return r, nil
|
||||
}
|
||||
|
||||
func CreateIconFromResourceEx(presbits uintptr, dwResSize uint32, isIcon bool, version uint32, cxDesired int, cyDesired int, flags uint) (uintptr, error) {
|
||||
icon := 0
|
||||
if isIcon {
|
||||
icon = 1
|
||||
}
|
||||
r, _, err := procCreateIconFromResourceEx.Call(
|
||||
presbits,
|
||||
uintptr(dwResSize),
|
||||
uintptr(icon),
|
||||
uintptr(version),
|
||||
uintptr(cxDesired),
|
||||
uintptr(cyDesired),
|
||||
uintptr(flags),
|
||||
)
|
||||
|
||||
if r == 0 {
|
||||
return 0, err
|
||||
}
|
||||
return r, nil
|
||||
}
|
||||
|
||||
func LoadImage(
|
||||
hInst uintptr,
|
||||
name *uint16,
|
||||
type_ uint32,
|
||||
cx, cy int32,
|
||||
fuLoad uint32) (uintptr, error) {
|
||||
r, _, err := procLoadImageW.Call(
|
||||
hInst,
|
||||
uintptr(unsafe.Pointer(name)),
|
||||
uintptr(type_),
|
||||
uintptr(cx),
|
||||
uintptr(cy),
|
||||
uintptr(fuLoad))
|
||||
if r == 0 {
|
||||
return 0, err
|
||||
}
|
||||
return r, nil
|
||||
}
|
||||
|
|
@ -56,6 +56,10 @@ func (d *DevWebServer) Show() {
|
|||
d.desktopFrontend.Show()
|
||||
}
|
||||
|
||||
func (d *DevWebServer) TrayMenuAdd(trayMenu *menu.TrayMenu) menu.TrayMenuImpl {
|
||||
return d.desktopFrontend.TrayMenuAdd(trayMenu)
|
||||
}
|
||||
|
||||
func (d *DevWebServer) WindowSetSystemDefaultTheme() {
|
||||
d.desktopFrontend.WindowSetSystemDefaultTheme()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -118,4 +118,7 @@ type Frontend interface {
|
|||
|
||||
// Browser
|
||||
BrowserOpenURL(url string)
|
||||
|
||||
// Tray Menu
|
||||
TrayMenuAdd(trayMenu *menu.TrayMenu) menu.TrayMenuImpl
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,12 +1,15 @@
|
|||
package menumanager
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
"github.com/wailsapp/wails/v2/pkg/menu"
|
||||
)
|
||||
import "github.com/wailsapp/wails/v2/pkg/menu"
|
||||
|
||||
//
|
||||
//import (
|
||||
// "encoding/json"
|
||||
// "fmt"
|
||||
//
|
||||
// "github.com/wailsapp/wails/v2/pkg/menu"
|
||||
//)
|
||||
//
|
||||
type ContextMenu struct {
|
||||
ID string
|
||||
ProcessedMenu *WailsMenu
|
||||
|
|
@ -14,48 +17,49 @@ type ContextMenu struct {
|
|||
menu *menu.Menu
|
||||
}
|
||||
|
||||
func (t *ContextMenu) AsJSON() (string, error) {
|
||||
data, err := json.Marshal(t)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return string(data), nil
|
||||
}
|
||||
|
||||
func NewContextMenu(contextMenu *menu.ContextMenu) *ContextMenu {
|
||||
|
||||
result := &ContextMenu{
|
||||
ID: contextMenu.ID,
|
||||
menu: contextMenu.Menu,
|
||||
menuItemMap: NewMenuItemMap(),
|
||||
}
|
||||
|
||||
result.menuItemMap.AddMenu(contextMenu.Menu)
|
||||
result.ProcessedMenu = NewWailsMenu(result.menuItemMap, result.menu)
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
func (m *Manager) AddContextMenu(contextMenu *menu.ContextMenu) {
|
||||
|
||||
newContextMenu := NewContextMenu(contextMenu)
|
||||
|
||||
// Save the references
|
||||
m.contextMenus[contextMenu.ID] = newContextMenu
|
||||
m.contextMenuPointers[contextMenu] = contextMenu.ID
|
||||
}
|
||||
|
||||
func (m *Manager) UpdateContextMenu(contextMenu *menu.ContextMenu) (string, error) {
|
||||
contextMenuID, contextMenuKnown := m.contextMenuPointers[contextMenu]
|
||||
if !contextMenuKnown {
|
||||
return "", fmt.Errorf("unknown Context Menu '%s'. Please add the context menu using AddContextMenu()", contextMenu.ID)
|
||||
}
|
||||
|
||||
// Create the updated context menu
|
||||
updatedContextMenu := NewContextMenu(contextMenu)
|
||||
|
||||
// Save the reference
|
||||
m.contextMenus[contextMenuID] = updatedContextMenu
|
||||
|
||||
return updatedContextMenu.AsJSON()
|
||||
}
|
||||
//
|
||||
//func (t *ContextMenu) AsJSON() (string, error) {
|
||||
// data, err := json.Marshal(t)
|
||||
// if err != nil {
|
||||
// return "", err
|
||||
// }
|
||||
// return string(data), nil
|
||||
//}
|
||||
//
|
||||
//func NewContextMenu(contextMenu *menu.ContextMenu) *ContextMenu {
|
||||
//
|
||||
// result := &ContextMenu{
|
||||
// ID: contextMenu.ID,
|
||||
// menu: contextMenu.Menu,
|
||||
// menuItemMap: NewMenuItemMap(),
|
||||
// }
|
||||
//
|
||||
// result.menuItemMap.AddMenu(contextMenu.Menu)
|
||||
// result.ProcessedMenu = NewWailsMenu(result.menuItemMap, result.menu)
|
||||
//
|
||||
// return result
|
||||
//}
|
||||
//
|
||||
//func (m *Manager) AddContextMenu(contextMenu *menu.ContextMenu) {
|
||||
//
|
||||
// newContextMenu := NewContextMenu(contextMenu)
|
||||
//
|
||||
// // Save the references
|
||||
// m.contextMenus[contextMenu.ID] = newContextMenu
|
||||
// m.contextMenuPointers[contextMenu] = contextMenu.ID
|
||||
//}
|
||||
//
|
||||
//func (m *Manager) UpdateContextMenu(contextMenu *menu.ContextMenu) (string, error) {
|
||||
// contextMenuID, contextMenuKnown := m.contextMenuPointers[contextMenu]
|
||||
// if !contextMenuKnown {
|
||||
// return "", fmt.Errorf("unknown Context Menu '%s'. Please add the context menu using AddContextMenu()", contextMenu.ID)
|
||||
// }
|
||||
//
|
||||
// // Create the updated context menu
|
||||
// updatedContextMenu := NewContextMenu(contextMenu)
|
||||
//
|
||||
// // Save the reference
|
||||
// m.contextMenus[contextMenuID] = updatedContextMenu
|
||||
//
|
||||
// return updatedContextMenu.AsJSON()
|
||||
//}
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ package menumanager
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/wailsapp/wails/v2/pkg/menu"
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -1,15 +1,11 @@
|
|||
package menumanager
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/leaanthony/go-ansi-parser"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/wailsapp/wails/v2/pkg/menu"
|
||||
)
|
||||
|
||||
|
|
@ -32,7 +28,7 @@ type TrayMenu struct {
|
|||
FontName string
|
||||
Disabled bool
|
||||
Tooltip string `json:",omitempty"`
|
||||
Image string
|
||||
Image []byte
|
||||
MacTemplateImage bool
|
||||
RGBA string
|
||||
menuItemMap *MenuItemMap
|
||||
|
|
@ -42,181 +38,182 @@ type TrayMenu struct {
|
|||
StyledLabel []*ansi.StyledText `json:",omitempty"`
|
||||
}
|
||||
|
||||
func (t *TrayMenu) AsJSON() (string, error) {
|
||||
data, err := json.Marshal(t)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return string(data), nil
|
||||
}
|
||||
|
||||
func NewTrayMenu(trayMenu *menu.TrayMenu) *TrayMenu {
|
||||
|
||||
// Parse ANSI text
|
||||
var styledLabel []*ansi.StyledText
|
||||
tempLabel := trayMenu.Label
|
||||
if strings.Contains(tempLabel, "\033[") {
|
||||
parsedLabel, err := ansi.Parse(tempLabel)
|
||||
if err == nil {
|
||||
styledLabel = parsedLabel
|
||||
}
|
||||
}
|
||||
|
||||
result := &TrayMenu{
|
||||
Label: trayMenu.Label,
|
||||
FontName: trayMenu.FontName,
|
||||
FontSize: trayMenu.FontSize,
|
||||
Disabled: trayMenu.Disabled,
|
||||
Tooltip: trayMenu.Tooltip,
|
||||
Image: trayMenu.Image,
|
||||
MacTemplateImage: trayMenu.MacTemplateImage,
|
||||
menu: trayMenu.Menu,
|
||||
RGBA: trayMenu.RGBA,
|
||||
menuItemMap: NewMenuItemMap(),
|
||||
trayMenu: trayMenu,
|
||||
StyledLabel: styledLabel,
|
||||
}
|
||||
|
||||
result.menuItemMap.AddMenu(trayMenu.Menu)
|
||||
result.ProcessedMenu = NewWailsMenu(result.menuItemMap, result.menu)
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
func (m *Manager) OnTrayMenuOpen(id string) {
|
||||
trayMenu, ok := m.trayMenus[id]
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
if trayMenu.trayMenu.OnOpen == nil {
|
||||
return
|
||||
}
|
||||
go trayMenu.trayMenu.OnOpen()
|
||||
}
|
||||
|
||||
func (m *Manager) OnTrayMenuClose(id string) {
|
||||
trayMenu, ok := m.trayMenus[id]
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
if trayMenu.trayMenu.OnClose == nil {
|
||||
return
|
||||
}
|
||||
go trayMenu.trayMenu.OnClose()
|
||||
}
|
||||
|
||||
func (m *Manager) AddTrayMenu(trayMenu *menu.TrayMenu) (string, error) {
|
||||
newTrayMenu := NewTrayMenu(trayMenu)
|
||||
|
||||
// Hook up a new ID
|
||||
trayID := generateTrayID()
|
||||
newTrayMenu.ID = trayID
|
||||
|
||||
// Save the references
|
||||
m.trayMenus[trayID] = newTrayMenu
|
||||
m.trayMenuPointers[trayMenu] = trayID
|
||||
|
||||
return newTrayMenu.AsJSON()
|
||||
}
|
||||
|
||||
func (m *Manager) GetTrayID(trayMenu *menu.TrayMenu) (string, error) {
|
||||
trayID, exists := m.trayMenuPointers[trayMenu]
|
||||
if !exists {
|
||||
return "", fmt.Errorf("Unable to find menu ID for tray menu!")
|
||||
}
|
||||
return trayID, nil
|
||||
}
|
||||
|
||||
// SetTrayMenu updates or creates a menu
|
||||
func (m *Manager) SetTrayMenu(trayMenu *menu.TrayMenu) (string, error) {
|
||||
trayID, trayMenuKnown := m.trayMenuPointers[trayMenu]
|
||||
if !trayMenuKnown {
|
||||
return m.AddTrayMenu(trayMenu)
|
||||
}
|
||||
|
||||
// Create the updated tray menu
|
||||
updatedTrayMenu := NewTrayMenu(trayMenu)
|
||||
updatedTrayMenu.ID = trayID
|
||||
|
||||
// Save the reference
|
||||
m.trayMenus[trayID] = updatedTrayMenu
|
||||
|
||||
return updatedTrayMenu.AsJSON()
|
||||
}
|
||||
|
||||
func (m *Manager) GetTrayMenus() ([]string, error) {
|
||||
result := []string{}
|
||||
for _, trayMenu := range m.trayMenus {
|
||||
JSON, err := trayMenu.AsJSON()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
result = append(result, JSON)
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (m *Manager) UpdateTrayMenuLabel(trayMenu *menu.TrayMenu) (string, error) {
|
||||
trayID, trayMenuKnown := m.trayMenuPointers[trayMenu]
|
||||
if !trayMenuKnown {
|
||||
return "", fmt.Errorf("[UpdateTrayMenuLabel] unknown tray id for tray %s", trayMenu.Label)
|
||||
}
|
||||
|
||||
type LabelUpdate struct {
|
||||
ID string
|
||||
Label string `json:",omitempty"`
|
||||
FontName string `json:",omitempty"`
|
||||
FontSize int
|
||||
RGBA string `json:",omitempty"`
|
||||
Disabled bool
|
||||
Tooltip string `json:",omitempty"`
|
||||
Image string `json:",omitempty"`
|
||||
MacTemplateImage bool
|
||||
StyledLabel []*ansi.StyledText `json:",omitempty"`
|
||||
}
|
||||
|
||||
// Parse ANSI text
|
||||
var styledLabel []*ansi.StyledText
|
||||
tempLabel := trayMenu.Label
|
||||
if strings.Contains(tempLabel, "\033[") {
|
||||
parsedLabel, err := ansi.Parse(tempLabel)
|
||||
if err == nil {
|
||||
styledLabel = parsedLabel
|
||||
}
|
||||
}
|
||||
|
||||
update := &LabelUpdate{
|
||||
ID: trayID,
|
||||
Label: trayMenu.Label,
|
||||
FontName: trayMenu.FontName,
|
||||
FontSize: trayMenu.FontSize,
|
||||
Disabled: trayMenu.Disabled,
|
||||
Tooltip: trayMenu.Tooltip,
|
||||
Image: trayMenu.Image,
|
||||
MacTemplateImage: trayMenu.MacTemplateImage,
|
||||
RGBA: trayMenu.RGBA,
|
||||
StyledLabel: styledLabel,
|
||||
}
|
||||
|
||||
data, err := json.Marshal(update)
|
||||
if err != nil {
|
||||
return "", errors.Wrap(err, "[UpdateTrayMenuLabel] ")
|
||||
}
|
||||
|
||||
return string(data), nil
|
||||
|
||||
}
|
||||
|
||||
func (m *Manager) GetContextMenus() ([]string, error) {
|
||||
result := []string{}
|
||||
for _, contextMenu := range m.contextMenus {
|
||||
JSON, err := contextMenu.AsJSON()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
result = append(result, JSON)
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
//
|
||||
//func (t *TrayMenu) AsJSON() (string, error) {
|
||||
// data, err := json.Marshal(t)
|
||||
// if err != nil {
|
||||
// return "", err
|
||||
// }
|
||||
// return string(data), nil
|
||||
//}
|
||||
//
|
||||
//func NewTrayMenu(trayMenu *menu.TrayMenu) *TrayMenu {
|
||||
//
|
||||
// // Parse ANSI text
|
||||
// var styledLabel []*ansi.StyledText
|
||||
// tempLabel := trayMenu.Label
|
||||
// if strings.Contains(tempLabel, "\033[") {
|
||||
// parsedLabel, err := ansi.Parse(tempLabel)
|
||||
// if err == nil {
|
||||
// styledLabel = parsedLabel
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// result := &TrayMenu{
|
||||
// Label: trayMenu.Label,
|
||||
// FontName: trayMenu.FontName,
|
||||
// FontSize: trayMenu.FontSize,
|
||||
// Disabled: trayMenu.Disabled,
|
||||
// Tooltip: trayMenu.Tooltip,
|
||||
// Image: trayMenu.Image,
|
||||
// MacTemplateImage: trayMenu.MacTemplateImage,
|
||||
// menu: trayMenu.Menu,
|
||||
// RGBA: trayMenu.RGBA,
|
||||
// menuItemMap: NewMenuItemMap(),
|
||||
// trayMenu: trayMenu,
|
||||
// StyledLabel: styledLabel,
|
||||
// }
|
||||
//
|
||||
// result.menuItemMap.AddMenu(trayMenu.Menu)
|
||||
// result.ProcessedMenu = NewWailsMenu(result.menuItemMap, result.menu)
|
||||
//
|
||||
// return result
|
||||
//}
|
||||
//
|
||||
//func (m *Manager) OnTrayMenuOpen(id string) {
|
||||
// trayMenu, ok := m.trayMenus[id]
|
||||
// if !ok {
|
||||
// return
|
||||
// }
|
||||
// if trayMenu.trayMenu.OnOpen == nil {
|
||||
// return
|
||||
// }
|
||||
// go trayMenu.trayMenu.OnOpen()
|
||||
//}
|
||||
//
|
||||
//func (m *Manager) OnTrayMenuClose(id string) {
|
||||
// trayMenu, ok := m.trayMenus[id]
|
||||
// if !ok {
|
||||
// return
|
||||
// }
|
||||
// if trayMenu.trayMenu.OnClose == nil {
|
||||
// return
|
||||
// }
|
||||
// go trayMenu.trayMenu.OnClose()
|
||||
//}
|
||||
//
|
||||
//func (m *Manager) AddTrayMenu(trayMenu *menu.TrayMenu) (string, error) {
|
||||
// newTrayMenu := NewTrayMenu(trayMenu)
|
||||
//
|
||||
// // Hook up a new ID
|
||||
// trayID := generateTrayID()
|
||||
// newTrayMenu.ID = trayID
|
||||
//
|
||||
// // Save the references
|
||||
// m.trayMenus[trayID] = newTrayMenu
|
||||
// m.trayMenuPointers[trayMenu] = trayID
|
||||
//
|
||||
// return newTrayMenu.AsJSON()
|
||||
//}
|
||||
//
|
||||
//func (m *Manager) GetTrayID(trayMenu *menu.TrayMenu) (string, error) {
|
||||
// trayID, exists := m.trayMenuPointers[trayMenu]
|
||||
// if !exists {
|
||||
// return "", fmt.Errorf("Unable to find menu ID for tray menu!")
|
||||
// }
|
||||
// return trayID, nil
|
||||
//}
|
||||
//
|
||||
//// SetTrayMenu updates or creates a menu
|
||||
//func (m *Manager) SetTrayMenu(trayMenu *menu.TrayMenu) (string, error) {
|
||||
// trayID, trayMenuKnown := m.trayMenuPointers[trayMenu]
|
||||
// if !trayMenuKnown {
|
||||
// return m.AddTrayMenu(trayMenu)
|
||||
// }
|
||||
//
|
||||
// // Create the updated tray menu
|
||||
// updatedTrayMenu := NewTrayMenu(trayMenu)
|
||||
// updatedTrayMenu.ID = trayID
|
||||
//
|
||||
// // Save the reference
|
||||
// m.trayMenus[trayID] = updatedTrayMenu
|
||||
//
|
||||
// return updatedTrayMenu.AsJSON()
|
||||
//}
|
||||
//
|
||||
//func (m *Manager) GetTrayMenus() ([]string, error) {
|
||||
// result := []string{}
|
||||
// for _, trayMenu := range m.trayMenus {
|
||||
// JSON, err := trayMenu.AsJSON()
|
||||
// if err != nil {
|
||||
// return nil, err
|
||||
// }
|
||||
// result = append(result, JSON)
|
||||
// }
|
||||
//
|
||||
// return result, nil
|
||||
//}
|
||||
//
|
||||
//func (m *Manager) UpdateTrayMenuLabel(trayMenu *menu.TrayMenu) (string, error) {
|
||||
// trayID, trayMenuKnown := m.trayMenuPointers[trayMenu]
|
||||
// if !trayMenuKnown {
|
||||
// return "", fmt.Errorf("[UpdateTrayMenuLabel] unknown tray id for tray %s", trayMenu.Label)
|
||||
// }
|
||||
//
|
||||
// type LabelUpdate struct {
|
||||
// ID string
|
||||
// Label string `json:",omitempty"`
|
||||
// FontName string `json:",omitempty"`
|
||||
// FontSize int
|
||||
// RGBA string `json:",omitempty"`
|
||||
// Disabled bool
|
||||
// Tooltip string `json:",omitempty"`
|
||||
// Image []byte `json:",omitempty"`
|
||||
// MacTemplateImage bool
|
||||
// StyledLabel []*ansi.StyledText `json:",omitempty"`
|
||||
// }
|
||||
//
|
||||
// // Parse ANSI text
|
||||
// var styledLabel []*ansi.StyledText
|
||||
// tempLabel := trayMenu.Label
|
||||
// if strings.Contains(tempLabel, "\033[") {
|
||||
// parsedLabel, err := ansi.Parse(tempLabel)
|
||||
// if err == nil {
|
||||
// styledLabel = parsedLabel
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// update := &LabelUpdate{
|
||||
// ID: trayID,
|
||||
// Label: trayMenu.Label,
|
||||
// FontName: trayMenu.FontName,
|
||||
// FontSize: trayMenu.FontSize,
|
||||
// Disabled: trayMenu.Disabled,
|
||||
// Tooltip: trayMenu.Tooltip,
|
||||
// Image: trayMenu.Image,
|
||||
// MacTemplateImage: trayMenu.MacTemplateImage,
|
||||
// RGBA: trayMenu.RGBA,
|
||||
// StyledLabel: styledLabel,
|
||||
// }
|
||||
//
|
||||
// data, err := json.Marshal(update)
|
||||
// if err != nil {
|
||||
// return "", errors.Wrap(err, "[UpdateTrayMenuLabel] ")
|
||||
// }
|
||||
//
|
||||
// return string(data), nil
|
||||
//
|
||||
//}
|
||||
//
|
||||
//func (m *Manager) GetContextMenus() ([]string, error) {
|
||||
// result := []string{}
|
||||
// for _, contextMenu := range m.contextMenus {
|
||||
// JSON, err := contextMenu.AsJSON()
|
||||
// if err != nil {
|
||||
// return nil, err
|
||||
// }
|
||||
// result = append(result, JSON)
|
||||
// }
|
||||
//
|
||||
// return result, nil
|
||||
//}
|
||||
|
|
|
|||
3
v2/pkg/events/events.go
Normal file
3
v2/pkg/events/events.go
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
package events
|
||||
|
||||
const ThemeChanged = ":wails:themechanged"
|
||||
|
|
@ -44,16 +44,23 @@ func (m *Menu) AddRadio(label string, checked bool, accelerator *keys.Accelerato
|
|||
}
|
||||
|
||||
// AddSeparator adds a separator to the menu
|
||||
func (m *Menu) AddSeparator() {
|
||||
func (m *Menu) AddSeparator() *MenuItem {
|
||||
item := Separator()
|
||||
m.Append(item)
|
||||
return item
|
||||
}
|
||||
|
||||
func (m *Menu) AddSubmenu(label string) *Menu {
|
||||
func (m *Menu) AddSubmenu(label string) *MenuItem {
|
||||
submenu := NewMenu()
|
||||
item := SubMenu(label, submenu)
|
||||
m.Append(item)
|
||||
return submenu
|
||||
return item
|
||||
}
|
||||
|
||||
func (m *Menu) InsertSubmenu(label string, submenu *Menu) *MenuItem {
|
||||
item := SubMenu(label, submenu)
|
||||
m.Append(item)
|
||||
return item
|
||||
}
|
||||
|
||||
func (m *Menu) Prepend(item *MenuItem) {
|
||||
|
|
|
|||
|
|
@ -6,6 +6,11 @@ import (
|
|||
"github.com/wailsapp/wails/v2/pkg/menu/keys"
|
||||
)
|
||||
|
||||
type MenuItemImpl interface {
|
||||
SetChecked(bool)
|
||||
SetLabel(string)
|
||||
}
|
||||
|
||||
// MenuItem represents a menuitem contained in a menu
|
||||
type MenuItem struct {
|
||||
// Label is what appears as the menu text
|
||||
|
|
@ -53,6 +58,9 @@ type MenuItem struct {
|
|||
|
||||
// Used for locking when removing elements
|
||||
removeLock sync.Mutex
|
||||
|
||||
// Implementation of the runtime methods
|
||||
Impl MenuItemImpl
|
||||
}
|
||||
|
||||
// Parent returns the parent of the menu item.
|
||||
|
|
@ -216,6 +224,21 @@ func (m *MenuItem) insertItemAtIndex(index int, target *MenuItem) bool {
|
|||
return true
|
||||
}
|
||||
|
||||
func (m *MenuItem) SetChecked(b bool) {
|
||||
if m.Checked != b {
|
||||
m.Checked = b
|
||||
m.Impl.SetChecked(b)
|
||||
}
|
||||
}
|
||||
|
||||
func (m *MenuItem) SetLabel(name string) {
|
||||
if m.Label == name {
|
||||
return
|
||||
}
|
||||
m.Label = name
|
||||
m.Impl.SetLabel(name)
|
||||
}
|
||||
|
||||
// Text is a helper to create basic Text menu items
|
||||
func Text(label string, accelerator *keys.Accelerator, click Callback) *MenuItem {
|
||||
return &MenuItem{
|
||||
|
|
|
|||
|
|
@ -1,20 +1,95 @@
|
|||
package menu
|
||||
|
||||
import (
|
||||
"context"
|
||||
"log"
|
||||
goruntime "runtime"
|
||||
|
||||
"github.com/wailsapp/wails/v2/pkg/events"
|
||||
)
|
||||
|
||||
type TrayMenuAdd interface {
|
||||
TrayMenuAdd(menu *TrayMenu) TrayMenuImpl
|
||||
}
|
||||
|
||||
type TrayMenuImpl interface {
|
||||
SetLabel(string)
|
||||
SetImage(*TrayImage)
|
||||
SetMenu(*Menu)
|
||||
}
|
||||
|
||||
type EventsImpl interface {
|
||||
On(eventName string, callback func(...interface{}))
|
||||
}
|
||||
|
||||
type ImagePosition int
|
||||
|
||||
const (
|
||||
ImageLeading ImagePosition = 0
|
||||
ImageOnly ImagePosition = 1
|
||||
ImageLeft ImagePosition = 2
|
||||
ImageRight ImagePosition = 3
|
||||
ImageBelow ImagePosition = 4
|
||||
ImageAbove ImagePosition = 5
|
||||
ImageOverlaps ImagePosition = 6
|
||||
NoImage ImagePosition = 7
|
||||
ImageTrailing ImagePosition = 8
|
||||
)
|
||||
|
||||
type TraySizing int
|
||||
|
||||
const (
|
||||
Variable TraySizing = 0
|
||||
Square TraySizing = 1
|
||||
)
|
||||
|
||||
type TrayImage struct {
|
||||
// Bitmaps hold images for different scaling factors
|
||||
// First = 1x, Second = 2x, etc
|
||||
Bitmaps [][]byte
|
||||
BitmapsDark [][]byte
|
||||
IsTemplate bool
|
||||
Position ImagePosition
|
||||
}
|
||||
|
||||
func (t *TrayImage) getBestBitmap(scale int, isDarkMode bool) []byte {
|
||||
bitmapsToCheck := t.Bitmaps
|
||||
if isDarkMode {
|
||||
bitmapsToCheck = t.BitmapsDark
|
||||
}
|
||||
if scale < 1 || scale >= len(bitmapsToCheck) {
|
||||
return nil
|
||||
}
|
||||
for i := scale; i > 0; i-- {
|
||||
if bitmapsToCheck[i] != nil {
|
||||
return bitmapsToCheck[i]
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetBestBitmap will attempt to return the best bitmap for the theme
|
||||
// If dark theme is used and no dark theme bitmap exists, then it will
|
||||
// revert to light theme bitmaps
|
||||
func (t *TrayImage) GetBestBitmap(scale int, isDarkMode bool) []byte {
|
||||
var result []byte
|
||||
if isDarkMode {
|
||||
result = t.getBestBitmap(scale, true)
|
||||
if result != nil {
|
||||
return result
|
||||
}
|
||||
}
|
||||
return t.getBestBitmap(scale, false)
|
||||
}
|
||||
|
||||
// TrayMenu are the options
|
||||
type TrayMenu struct {
|
||||
ctx context.Context
|
||||
|
||||
// Label is the text we wish to display in the tray
|
||||
Label string
|
||||
|
||||
// Image is the name of the tray icon we wish to display.
|
||||
// These are read up during build from <projectdir>/trayicons and
|
||||
// the filenames are used as IDs, minus the extension
|
||||
// EG: <projectdir>/trayicons/main.png can be referenced here with "main"
|
||||
// If the image is not a filename, it will be treated as base64 image data
|
||||
Image string
|
||||
|
||||
// MacTemplateImage indicates that on a Mac, this image is a template image
|
||||
MacTemplateImage bool
|
||||
Image *TrayImage
|
||||
|
||||
// Text Colour
|
||||
RGBA string
|
||||
|
|
@ -27,7 +102,7 @@ type TrayMenu struct {
|
|||
Tooltip string
|
||||
|
||||
// Callback function when menu clicked
|
||||
//Click Callback `json:"-"`
|
||||
Click Callback
|
||||
|
||||
// Disabled makes the item unselectable
|
||||
Disabled bool
|
||||
|
|
@ -40,4 +115,67 @@ type TrayMenu struct {
|
|||
|
||||
// OnClose is called when the Menu is closed
|
||||
OnClose func()
|
||||
|
||||
/* Mac Options */
|
||||
Sizing TraySizing
|
||||
|
||||
// This is the reference to the OS specific implementation
|
||||
impl TrayMenuImpl
|
||||
|
||||
// Theme change callback
|
||||
themeChangeCallback func(data ...interface{})
|
||||
}
|
||||
|
||||
func NewTrayMenu() *TrayMenu {
|
||||
return &TrayMenu{}
|
||||
}
|
||||
|
||||
func (t *TrayMenu) Show(ctx context.Context) {
|
||||
if ctx == nil {
|
||||
log.Fatal("TrayMenu.Show() called before Run()")
|
||||
}
|
||||
t.ctx = ctx
|
||||
result := ctx.Value("frontend")
|
||||
if result == nil {
|
||||
pc, _, _, _ := goruntime.Caller(1)
|
||||
funcName := goruntime.FuncForPC(pc).Name()
|
||||
log.Fatalf("invalid context at '%s'", funcName)
|
||||
}
|
||||
t.impl = result.(TrayMenuAdd).TrayMenuAdd(t)
|
||||
|
||||
if t.themeChangeCallback == nil {
|
||||
t.themeChangeCallback = func(data ...interface{}) {
|
||||
println("Update button image")
|
||||
if t.Image != nil {
|
||||
// Update the image
|
||||
t.SetImage(t.Image)
|
||||
}
|
||||
}
|
||||
result := ctx.Value("events")
|
||||
if result != nil {
|
||||
result.(EventsImpl).On(events.ThemeChanged, t.themeChangeCallback)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func (t *TrayMenu) SetLabel(label string) {
|
||||
t.Label = label
|
||||
if t.impl != nil {
|
||||
t.impl.SetLabel(label)
|
||||
}
|
||||
}
|
||||
|
||||
func (t *TrayMenu) SetImage(image *TrayImage) {
|
||||
t.Image = image
|
||||
if t.impl != nil {
|
||||
t.impl.SetImage(image)
|
||||
}
|
||||
}
|
||||
|
||||
func (t *TrayMenu) SetMenu(menu *Menu) {
|
||||
t.Menu = menu
|
||||
if t.impl != nil {
|
||||
t.impl.SetMenu(menu)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,12 +1,12 @@
|
|||
package mac
|
||||
|
||||
//type ActivationPolicy int
|
||||
//
|
||||
//const (
|
||||
// NSApplicationActivationPolicyRegular ActivationPolicy = 0
|
||||
// NSApplicationActivationPolicyAccessory ActivationPolicy = 1
|
||||
// NSApplicationActivationPolicyProhibited ActivationPolicy = 2
|
||||
//)
|
||||
type ActivationPolicy int
|
||||
|
||||
const (
|
||||
NSApplicationActivationPolicyRegular ActivationPolicy = 0
|
||||
NSApplicationActivationPolicyAccessory ActivationPolicy = 1
|
||||
NSApplicationActivationPolicyProhibited ActivationPolicy = 2
|
||||
)
|
||||
|
||||
type AboutInfo struct {
|
||||
Title string
|
||||
|
|
@ -20,7 +20,7 @@ type Options struct {
|
|||
Appearance AppearanceType
|
||||
WebviewIsTransparent bool
|
||||
WindowIsTranslucent bool
|
||||
//ActivationPolicy ActivationPolicy
|
||||
About *AboutInfo
|
||||
About *AboutInfo
|
||||
ActivationPolicy ActivationPolicy
|
||||
//URLHandlers map[string]func(string)
|
||||
}
|
||||
|
|
|
|||
BIN
website/static/img/varly1.png
Normal file
BIN
website/static/img/varly1.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 220 KiB |
Loading…
Add table
Add a link
Reference in a new issue