mirror of
https://github.com/taigrr/wails.git
synced 2026-04-02 05:08:54 -07:00
ANSI support for tray labels
This commit is contained in:
@@ -67,5 +67,6 @@ func main() {
|
||||
err = app.Run()
|
||||
if err != nil {
|
||||
println("\n\nERROR: " + err.Error())
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,13 +4,13 @@ go 1.16
|
||||
|
||||
require (
|
||||
github.com/Masterminds/semver v1.5.0
|
||||
github.com/davecgh/go-spew v1.1.1
|
||||
github.com/fatih/structtag v1.2.0
|
||||
github.com/fsnotify/fsnotify v1.4.9
|
||||
github.com/gorilla/websocket v1.4.1
|
||||
github.com/imdario/mergo v0.3.11
|
||||
github.com/jackmordaunt/icns v1.0.0
|
||||
github.com/leaanthony/clir v1.0.4
|
||||
github.com/leaanthony/go-ansi-parser v1.0.1
|
||||
github.com/leaanthony/gosod v0.0.4
|
||||
github.com/leaanthony/slicer v1.5.0
|
||||
github.com/matryer/is v1.4.0
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3QEww=
|
||||
github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/fatih/structtag v1.2.0 h1:/OdNE99OxoI/PqaW/SuSK9uxxT3f/tcSZgon/ssNSx4=
|
||||
github.com/fatih/structtag v1.2.0/go.mod h1:mBJUNpUnHmRKrKlQQlmCrh5PuhftFbNv8Ys4/aAZl94=
|
||||
@@ -42,6 +41,8 @@ github.com/klauspost/compress v1.10.3 h1:OP96hzwJVBIHYU52pVTI6CczrxPvrGfgqF9N5eT
|
||||
github.com/klauspost/compress v1.10.3/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
|
||||
github.com/leaanthony/clir v1.0.4 h1:Dov2y9zWJmZr7CjaCe86lKa4b5CSxskGAt2yBkoDyiU=
|
||||
github.com/leaanthony/clir v1.0.4/go.mod h1:k/RBkdkFl18xkkACMCLt09bhiZnrGORoxmomeMvDpE0=
|
||||
github.com/leaanthony/go-ansi-parser v1.0.1 h1:97v6c5kYppVsbScf4r/VZdXyQ21KQIfeQOk2DgKxGG4=
|
||||
github.com/leaanthony/go-ansi-parser v1.0.1/go.mod h1:7arTzgVI47srICYhvgUV4CGd063sGEeoSlych5yeSPM=
|
||||
github.com/leaanthony/gosod v0.0.4 h1:v4hepo4IyL8E8c9qzDsvYcA0KGh7Npf8As74K5ibQpI=
|
||||
github.com/leaanthony/gosod v0.0.4/go.mod h1:nGMCb1PJfXwBDbOAike78jEYlpqge+xUKFf0iBKjKxU=
|
||||
github.com/leaanthony/slicer v1.5.0 h1:aHYTN8xbCCLxJmkNKiLB6tgcMARl4eWmH9/F+S/0HtY=
|
||||
|
||||
@@ -579,6 +579,105 @@ id processCheckboxMenuItem(Menu *menu, id parentmenu, const char *title, const c
|
||||
return item;
|
||||
}
|
||||
|
||||
// getColour returns the colour from a styledLabel based on the key
|
||||
const char* getColour(JsonNode *styledLabelEntry, const char* key) {
|
||||
JsonNode* colEntry = getJSONObject(styledLabelEntry, key);
|
||||
if( colEntry == NULL ) {
|
||||
return NULL;
|
||||
}
|
||||
return getJSONString(colEntry, "hex");
|
||||
}
|
||||
|
||||
id createAttributedStringFromStyledLabel(JsonNode *styledLabel, const char* fontName, int fontSize) {
|
||||
|
||||
// Create result
|
||||
id attributedString = ALLOC_INIT("NSMutableAttributedString");
|
||||
msg_reg(attributedString, s("autorelease"));
|
||||
|
||||
// Create new Dictionary
|
||||
id dictionary = ALLOC_INIT("NSMutableDictionary");
|
||||
msg_reg(dictionary, s("autorelease"));
|
||||
|
||||
// Use default font
|
||||
CGFloat fontSizeFloat = (CGFloat)fontSize;
|
||||
id font = ((id(*)(id, SEL, CGFloat))objc_msgSend)(c("NSFont"), s("menuBarFontOfSize:"), fontSizeFloat);
|
||||
|
||||
// Check user supplied font
|
||||
if( STR_HAS_CHARS(fontName) ) {
|
||||
id fontNameAsNSString = str(fontName);
|
||||
id userFont = ((id(*)(id, SEL, id, CGFloat))objc_msgSend)(c("NSFont"), s("fontWithName:size:"), fontNameAsNSString, fontSizeFloat);
|
||||
if( userFont != NULL ) {
|
||||
font = userFont;
|
||||
}
|
||||
}
|
||||
|
||||
id fan = lookupStringConstant(str("NSFontAttributeName"));
|
||||
id NSForegroundColorAttributeName = lookupStringConstant(str("NSForegroundColorAttributeName"));
|
||||
id NSBackgroundColorAttributeName = lookupStringConstant(str("NSBackgroundColorAttributeName"));
|
||||
|
||||
// Loop over styled text creating NSAttributedText and appending to result
|
||||
JsonNode *styledLabelEntry;
|
||||
json_foreach(styledLabelEntry, styledLabel) {
|
||||
|
||||
// Clear dictionary
|
||||
msg_reg(dictionary, s("removeAllObjects"));
|
||||
|
||||
// Add font to dictionary
|
||||
msg_id_id(dictionary, s("setObject:forKey:"), font, fan);
|
||||
|
||||
// Get Text
|
||||
const char* thisLabel = mustJSONString(styledLabelEntry, "Label");
|
||||
|
||||
// Get foreground colour
|
||||
const char *hexColour = getColour(styledLabelEntry, "FgCol");
|
||||
if( hexColour != NULL) {
|
||||
unsigned short r, g, b, a;
|
||||
|
||||
// white by default
|
||||
r = g = b = a = 255;
|
||||
int count = sscanf(hexColour, "#%02hx%02hx%02hx%02hx", &r, &g, &b, &a);
|
||||
if (count > 0) {
|
||||
id colour = ((id(*)(id, SEL, CGFloat, CGFloat, CGFloat, CGFloat))objc_msgSend)(c("NSColor"), s("colorWithCalibratedRed:green:blue:alpha:"),
|
||||
(CGFloat)r / (CGFloat)255.0,
|
||||
(CGFloat)g / (CGFloat)255.0,
|
||||
(CGFloat)b / (CGFloat)255.0,
|
||||
(CGFloat)a / (CGFloat)255.0);
|
||||
msg_id_id(dictionary, s("setObject:forKey:"), colour, NSForegroundColorAttributeName);
|
||||
}
|
||||
}
|
||||
|
||||
// Get background colour
|
||||
hexColour = getColour(styledLabelEntry, "BgCol");
|
||||
if( hexColour != NULL) {
|
||||
unsigned short r, g, b, a;
|
||||
|
||||
// white by default
|
||||
r = g = b = a = 255;
|
||||
int count = sscanf(hexColour, "#%02hx%02hx%02hx%02hx", &r, &g, &b, &a);
|
||||
if (count > 0) {
|
||||
id colour = ((id(*)(id, SEL, CGFloat, CGFloat, CGFloat, CGFloat))objc_msgSend)(c("NSColor"), s("colorWithCalibratedRed:green:blue:alpha:"),
|
||||
(CGFloat)r / (CGFloat)255.0,
|
||||
(CGFloat)g / (CGFloat)255.0,
|
||||
(CGFloat)b / (CGFloat)255.0,
|
||||
(CGFloat)a / (CGFloat)255.0);
|
||||
msg_id_id(dictionary, s("setObject:forKey:"), colour, NSForegroundColorAttributeName);
|
||||
}
|
||||
}
|
||||
|
||||
// Create AttributedText
|
||||
id thisString = ALLOC("NSMutableAttributedString");
|
||||
msg_reg(thisString, s("autorelease"));
|
||||
msg_id_id(thisString, s("initWithString:attributes:"), str(thisLabel), dictionary);
|
||||
|
||||
// Append text to result
|
||||
msg_id(attributedString, s("appendAttributedString:"), thisString);
|
||||
}
|
||||
|
||||
return attributedString;
|
||||
|
||||
}
|
||||
|
||||
|
||||
id createAttributedString(const char* title, const char* fontName, int fontSize, const char* RGBA) {
|
||||
|
||||
// Create new Dictionary
|
||||
|
||||
@@ -112,5 +112,6 @@ void processMenuData(Menu *menu, JsonNode *menuData);
|
||||
void processRadioGroupJSON(Menu *menu, JsonNode *radioGroup);
|
||||
id GetMenu(Menu *menu);
|
||||
id createAttributedString(const char* title, const char* fontName, int fontSize, const char* RGBA);
|
||||
id createAttributedStringFromStyledLabel(JsonNode *styledLabel, const char* fontName, int fontSize);
|
||||
|
||||
#endif //ASSETS_C_MENU_DARWIN_H
|
||||
|
||||
@@ -42,6 +42,8 @@ TrayMenu* NewTrayMenu(const char* menuJSON) {
|
||||
result->disabled = false;
|
||||
getJSONBool(processedJSON, "Disabled", &result->disabled);
|
||||
|
||||
result->styledLabel = getJSONObject(processedJSON, "StyledLabel");
|
||||
|
||||
// Create the menu
|
||||
JsonNode* processedMenu = mustJSONObject(processedJSON, "ProcessedMenu");
|
||||
result->menu = NewMenu(processedMenu);
|
||||
@@ -63,7 +65,7 @@ void DumpTrayMenu(TrayMenu* trayMenu) {
|
||||
}
|
||||
|
||||
|
||||
void UpdateTrayLabel(TrayMenu *trayMenu, const char *label, const char *fontName, int fontSize, const char *RGBA, const char *tooltip, bool disabled) {
|
||||
void UpdateTrayLabel(TrayMenu *trayMenu, const char *label, const char *fontName, int fontSize, const char *RGBA, const char *tooltip, bool disabled, JsonNode *styledLabel) {
|
||||
|
||||
// Exit early if NULL
|
||||
if( trayMenu->label == NULL ) {
|
||||
@@ -71,7 +73,12 @@ void UpdateTrayLabel(TrayMenu *trayMenu, const char *label, const char *fontName
|
||||
}
|
||||
// Update button label
|
||||
id statusBarButton = msg_reg(trayMenu->statusbaritem, s("button"));
|
||||
id attributedString = createAttributedString(label, fontName, fontSize, RGBA);
|
||||
id attributedString = NULL;
|
||||
if( styledLabel != NULL) {
|
||||
attributedString = createAttributedStringFromStyledLabel(styledLabel, fontName, fontSize);
|
||||
} else {
|
||||
attributedString = createAttributedString(label, fontName, fontSize, RGBA);
|
||||
}
|
||||
|
||||
if( tooltip != NULL ) {
|
||||
msg_id(statusBarButton, s("setToolTip:"), str(tooltip));
|
||||
@@ -125,7 +132,7 @@ void ShowTrayMenu(TrayMenu* trayMenu) {
|
||||
UpdateTrayIcon(trayMenu);
|
||||
|
||||
// Update the label if needed
|
||||
UpdateTrayLabel(trayMenu, trayMenu->label, trayMenu->fontName, trayMenu->fontSize, trayMenu->RGBA, trayMenu->tooltip, trayMenu->disabled);
|
||||
UpdateTrayLabel(trayMenu, trayMenu->label, trayMenu->fontName, trayMenu->fontSize, trayMenu->RGBA, trayMenu->tooltip, trayMenu->disabled, trayMenu->styledLabel);
|
||||
|
||||
// Update the menu
|
||||
id menu = GetMenu(trayMenu->menu);
|
||||
@@ -161,6 +168,7 @@ void UpdateTrayMenuInPlace(TrayMenu* currentMenu, TrayMenu* newMenu) {
|
||||
// Copy the other data
|
||||
currentMenu->ID = newMenu->ID;
|
||||
currentMenu->label = newMenu->label;
|
||||
currentMenu->styledLabel = newMenu->styledLabel;
|
||||
currentMenu->trayIconPosition = newMenu->trayIconPosition;
|
||||
currentMenu->icon = newMenu->icon;
|
||||
|
||||
@@ -220,7 +228,7 @@ void LoadTrayIcons() {
|
||||
int length = atoi((const char *)lengthAsString);
|
||||
|
||||
// Create the icon and add to the hashmap
|
||||
id imageData = ((id(*)(id, SEL, id, int))objc_msgSend)(c("NSData"), s("dataWithBytes:length:"), data, length);
|
||||
id imageData = ((id(*)(id, SEL, id, int))objc_msgSend)(c("NSData"), s("dataWithBytes:length:"), (id)data, length);
|
||||
id trayImage = ALLOC("NSImage");
|
||||
msg_id(trayImage, s("initWithData:"), imageData);
|
||||
hashmap_put(&trayIconCache, (const char *)name, strlen((const char *)name), trayImage);
|
||||
|
||||
@@ -29,6 +29,8 @@ typedef struct {
|
||||
|
||||
JsonNode* processedJSON;
|
||||
|
||||
JsonNode* styledLabel;
|
||||
|
||||
id delegate;
|
||||
|
||||
} TrayMenu;
|
||||
@@ -38,7 +40,7 @@ void DumpTrayMenu(TrayMenu* trayMenu);
|
||||
void ShowTrayMenu(TrayMenu* trayMenu);
|
||||
void UpdateTrayMenuInPlace(TrayMenu* currentMenu, TrayMenu* newMenu);
|
||||
void UpdateTrayIcon(TrayMenu *trayMenu);
|
||||
void UpdateTrayLabel(TrayMenu *trayMenu, const char *label, const char *fontName, int fontSize, const char *RGBA, const char *tooltip, bool disabled);
|
||||
void UpdateTrayLabel(TrayMenu *trayMenu, const char *label, const char *fontName, int fontSize, const char *RGBA, const char *tooltip, bool disabled, JsonNode *styledLabel);
|
||||
|
||||
void LoadTrayIcons();
|
||||
void UnloadTrayIcons();
|
||||
|
||||
@@ -127,7 +127,9 @@ void UpdateTrayMenuLabelInStore(TrayMenuStore* store, const char* JSON) {
|
||||
bool disabled = false;
|
||||
getJSONBool(parsedUpdate, "Disabled", &disabled);
|
||||
|
||||
UpdateTrayLabel(menu, Label, fontName, fontSize, RGBA, tooltip, disabled);
|
||||
JsonNode *styledLabel = getJSONObject(parsedUpdate, "StyledLabel");
|
||||
|
||||
UpdateTrayLabel(menu, Label, fontName, fontSize, RGBA, tooltip, disabled, styledLabel);
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -2,6 +2,9 @@ package menumanager
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"strings"
|
||||
|
||||
"github.com/leaanthony/go-ansi-parser"
|
||||
|
||||
"github.com/wailsapp/wails/v2/pkg/menu"
|
||||
"github.com/wailsapp/wails/v2/pkg/menu/keys"
|
||||
@@ -41,11 +44,25 @@ type ProcessedMenuItem struct {
|
||||
|
||||
// Tooltip
|
||||
Tooltip string `json:",omitempty"`
|
||||
|
||||
// Styled label
|
||||
StyledLabel []*ansi.StyledText
|
||||
}
|
||||
|
||||
func NewProcessedMenuItem(menuItemMap *MenuItemMap, menuItem *menu.MenuItem) *ProcessedMenuItem {
|
||||
|
||||
ID := menuItemMap.menuItemToIDMap[menuItem]
|
||||
|
||||
// Parse ANSI text
|
||||
var styledLabel []*ansi.StyledText
|
||||
tempLabel := menuItem.Label
|
||||
if strings.Contains(tempLabel, "\033[") {
|
||||
parsedLabel, err := ansi.Parse(menuItem.Label)
|
||||
if err == nil {
|
||||
styledLabel = parsedLabel
|
||||
}
|
||||
}
|
||||
|
||||
result := &ProcessedMenuItem{
|
||||
ID: ID,
|
||||
Label: menuItem.Label,
|
||||
@@ -63,6 +80,7 @@ func NewProcessedMenuItem(menuItemMap *MenuItemMap, menuItem *menu.MenuItem) *Pr
|
||||
MacTemplateImage: menuItem.MacTemplateImage,
|
||||
MacAlternate: menuItem.MacAlternate,
|
||||
Tooltip: menuItem.Tooltip,
|
||||
StyledLabel: styledLabel,
|
||||
}
|
||||
|
||||
if menuItem.SubMenu != nil {
|
||||
|
||||
@@ -4,8 +4,11 @@ import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/leaanthony/go-ansi-parser"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/wailsapp/wails/v2/pkg/menu"
|
||||
)
|
||||
@@ -36,6 +39,7 @@ type TrayMenu struct {
|
||||
menu *menu.Menu
|
||||
ProcessedMenu *WailsMenu
|
||||
trayMenu *menu.TrayMenu
|
||||
StyledLabel []*ansi.StyledText
|
||||
}
|
||||
|
||||
func (t *TrayMenu) AsJSON() (string, error) {
|
||||
@@ -48,6 +52,16 @@ func (t *TrayMenu) AsJSON() (string, error) {
|
||||
|
||||
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,
|
||||
@@ -60,6 +74,7 @@ func NewTrayMenu(trayMenu *menu.TrayMenu) *TrayMenu {
|
||||
RGBA: trayMenu.RGBA,
|
||||
menuItemMap: NewMenuItemMap(),
|
||||
trayMenu: trayMenu,
|
||||
StyledLabel: styledLabel,
|
||||
}
|
||||
|
||||
result.menuItemMap.AddMenu(trayMenu.Menu)
|
||||
@@ -158,6 +173,17 @@ func (m *Manager) UpdateTrayMenuLabel(trayMenu *menu.TrayMenu) (string, error) {
|
||||
Tooltip string
|
||||
Image string
|
||||
MacTemplateImage bool
|
||||
StyledLabel []*ansi.StyledText
|
||||
}
|
||||
|
||||
// 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{
|
||||
@@ -170,6 +196,7 @@ func (m *Manager) UpdateTrayMenuLabel(trayMenu *menu.TrayMenu) (string, error) {
|
||||
Image: trayMenu.Image,
|
||||
MacTemplateImage: trayMenu.MacTemplateImage,
|
||||
RGBA: trayMenu.RGBA,
|
||||
StyledLabel: styledLabel,
|
||||
}
|
||||
|
||||
data, err := json.Marshal(update)
|
||||
|
||||
Reference in New Issue
Block a user