ANSI support for tray labels

This commit is contained in:
Lea Anthony
2021-04-13 05:43:37 +10:00
parent 1344638c52
commit 53a3638fa8
10 changed files with 167 additions and 8 deletions

View File

@@ -67,5 +67,6 @@ func main() {
err = app.Run()
if err != nil {
println("\n\nERROR: " + err.Error())
os.Exit(1)
}
}

View File

@@ -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

View File

@@ -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=

View File

@@ -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

View File

@@ -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

View File

@@ -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);

View File

@@ -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();

View File

@@ -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);
}

View File

@@ -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 {

View File

@@ -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)