diff --git a/atom/browser/ui/cocoa/NSColor+Hex.h b/atom/browser/ui/cocoa/NSColor+Hex.h new file mode 100644 index 000000000000..70847b42173c --- /dev/null +++ b/atom/browser/ui/cocoa/NSColor+Hex.h @@ -0,0 +1,16 @@ +// Created by Mathias Leppich on 03/02/14. +// Copyright (c) 2014 Bit Bar. All rights reserved. +// Copyright (c) 2017 GitHub, Inc. +// Use of this source code is governed by the MIT license that can be +// found in the LICENSE file. + +#ifndef ATOM_BROWSER_UI_COCOA_NSCOLOR_HEX_H_ +#define ATOM_BROWSER_UI_COCOA_NSCOLOR_HEX_H_ + +#import + +@interface NSColor(Hex) ++ (NSColor*)colorWithHexColorString:(NSString*)hex; +@end + +#endif // ATOM_BROWSER_UI_COCOA_NSCOLOR_HEX_H_ diff --git a/atom/browser/ui/cocoa/NSColor+Hex.mm b/atom/browser/ui/cocoa/NSColor+Hex.mm new file mode 100644 index 000000000000..b55447ca7e26 --- /dev/null +++ b/atom/browser/ui/cocoa/NSColor+Hex.mm @@ -0,0 +1,29 @@ +// Created by Mathias Leppich on 03/02/14. +// Copyright (c) 2014 Bit Bar. All rights reserved. +// Copyright (c) 2017 GitHub, Inc. +// Use of this source code is governed by the MIT license that can be +// found in the LICENSE file. + +#include "atom/browser/ui/cocoa/NSColor+Hex.h" + +@implementation NSColor (Hex) + ++ (NSColor*)colorWithHexColorString:(NSString*)inColorString { + unsigned colorCode = 0; + unsigned char redByte, greenByte, blueByte; + + if (inColorString) { + NSScanner* scanner = [NSScanner scannerWithString:inColorString]; + (void) [scanner scanHexInt:&colorCode]; // ignore error + } + redByte = (unsigned char)(colorCode >> 16); + greenByte = (unsigned char)(colorCode >> 8); + blueByte = (unsigned char)(colorCode); // masks off high bits + + return [NSColor colorWithCalibratedRed:(CGFloat)redByte / 0xff + green:(CGFloat)greenByte / 0xff + blue:(CGFloat)blueByte / 0xff + alpha:1.0]; +} + +@end diff --git a/atom/browser/ui/cocoa/NSString+ANSI.h b/atom/browser/ui/cocoa/NSString+ANSI.h new file mode 100644 index 000000000000..93e76b59e9bb --- /dev/null +++ b/atom/browser/ui/cocoa/NSString+ANSI.h @@ -0,0 +1,17 @@ +// Created by Kent Karlsson on 3/11/16. +// Copyright (c) 2016 Bit Bar. All rights reserved. +// Copyright (c) 2017 GitHub, Inc. +// Use of this source code is governed by the MIT license that can be +// found in the LICENSE file. + +#ifndef ATOM_BROWSER_UI_COCOA_NSSTRING_ANSI_H_ +#define ATOM_BROWSER_UI_COCOA_NSSTRING_ANSI_H_ + +#import + +@interface NSString(ANSI) +- (BOOL)containsANSICodes; +- (NSMutableAttributedString*)attributedStringParsingANSICodes; +@end + +#endif // ATOM_BROWSER_UI_COCOA_NSSTRING_ANSI_H_ diff --git a/atom/browser/ui/cocoa/NSString+ANSI.mm b/atom/browser/ui/cocoa/NSString+ANSI.mm new file mode 100644 index 000000000000..be1bc5054368 --- /dev/null +++ b/atom/browser/ui/cocoa/NSString+ANSI.mm @@ -0,0 +1,146 @@ +// Created by Kent Karlsson on 3/11/16. +// Copyright (c) 2016 Bit Bar. All rights reserved. +// Copyright (c) 2017 GitHub, Inc. +// Use of this source code is governed by the MIT license that can be +// found in the LICENSE file. + +#include "atom/browser/ui/cocoa/NSString+ANSI.h" +#include "atom/browser/ui/cocoa/NSColor+Hex.h" +#include "base/mac/scoped_nsobject.h" + +@implementation NSMutableDictionary (ANSI) + +- (NSMutableDictionary*)modifyAttributesForANSICodes:(NSString*)codes { + BOOL bold = NO; + NSFont* font = self[NSFontAttributeName]; + + NSArray* codeArray = [codes componentsSeparatedByString:@";"]; + + for (NSString* codeString in codeArray) { + int code = codeString.intValue; + switch (code) { + case 0: + [self removeAllObjects]; + // remove italic and bold from font here + if (font) self[NSFontAttributeName] = font; + break; + + case 1: + case 22: + bold = (code == 1); + break; + + // case 3: italic + // case 23: italic off + // case 4: underlined + // case 24: underlined off + + case 30: + self[NSForegroundColorAttributeName] = [NSColor colorWithHexColorString:bold ? @"7f7f7f" : @"000000"]; + break; + case 31: + self[NSForegroundColorAttributeName] = [NSColor colorWithHexColorString:bold ? @"cd0000" : @"ff0000"]; + break; + case 32: + self[NSForegroundColorAttributeName] = [NSColor colorWithHexColorString:bold ? @"00cd00" : @"00ff00"]; + break; + case 33: + self[NSForegroundColorAttributeName] = [NSColor colorWithHexColorString:bold ? @"cdcd00" : @"ffff00"]; + break; + case 34: + self[NSForegroundColorAttributeName] = [NSColor colorWithHexColorString:bold ? @"0000ee" : @"5c5cff"]; + break; + case 35: + self[NSForegroundColorAttributeName] = [NSColor colorWithHexColorString:bold ? @"cd00cd" : @"ff00ff"]; + break; + case 36: + self[NSForegroundColorAttributeName] = [NSColor colorWithHexColorString:bold ? @"00cdcd" : @"00ffff"]; + break; + case 37: + self[NSForegroundColorAttributeName] = [NSColor colorWithHexColorString:bold ? @"e5e5e5" : @"ffffff"]; + break; + + case 39: + [self removeObjectForKey:NSForegroundColorAttributeName]; + break; + + case 40: + self[NSBackgroundColorAttributeName] = [NSColor colorWithHexColorString:@"7f7f7f"]; + break; + case 41: + self[NSBackgroundColorAttributeName] = [NSColor colorWithHexColorString:@"cd0000"]; + break; + case 42: + self[NSBackgroundColorAttributeName] = [NSColor colorWithHexColorString:@"00cd00"]; + break; + case 43: + self[NSBackgroundColorAttributeName] = [NSColor colorWithHexColorString:@"cdcd00"]; + break; + case 44: + self[NSBackgroundColorAttributeName] = [NSColor colorWithHexColorString:@"0000ee"]; + break; + case 45: + self[NSBackgroundColorAttributeName] = [NSColor colorWithHexColorString:@"cd00cd"]; + break; + case 46: + self[NSBackgroundColorAttributeName] = [NSColor colorWithHexColorString:@"00cdcd"]; + break; + case 47: + self[NSBackgroundColorAttributeName] = [NSColor colorWithHexColorString:@"e5e5e5"]; + break; + + case 49: + [self removeObjectForKey:NSBackgroundColorAttributeName]; + break; + + default: + break; + } + } + + return self; +} + +@end + +@implementation NSString (ANSI) + +- (BOOL)containsANSICodes { + return [self rangeOfString:@"\033["].location != NSNotFound; +} + +- (NSMutableAttributedString*)attributedStringParsingANSICodes { + NSMutableAttributedString* result = [[NSMutableAttributedString alloc] init]; + + base::scoped_nsobject attributes( + [[NSMutableDictionary alloc] init]); + NSArray* parts = [self componentsSeparatedByString:@"\033["]; + [result appendAttributedString:[[[NSAttributedString alloc] + initWithString:parts.firstObject + attributes:nil] autorelease]]; + + for (NSString* part in [parts subarrayWithRange:NSMakeRange(1, parts.count - 1)]) { + if (part.length == 0) + continue; + + NSArray* sequence = [part componentsSeparatedByString:@"m"]; + NSString* text = sequence.lastObject; + + if (sequence.count < 2) { + [result appendAttributedString:[[[NSAttributedString alloc] + initWithString:text + attributes:attributes] autorelease]]; + } else if (sequence.count >= 2) { + text = [[sequence subarrayWithRange:NSMakeRange(1, sequence.count - 1)] + componentsJoinedByString:@"m"]; + [attributes modifyAttributesForANSICodes:sequence[0]]; + [result appendAttributedString:[[[NSAttributedString alloc] + initWithString:text + attributes:attributes] autorelease]]; + } + } + + return result; +} + +@end diff --git a/atom/browser/ui/tray_icon_cocoa.mm b/atom/browser/ui/tray_icon_cocoa.mm index 75d4fe908a4d..9c5e100a2a0c 100644 --- a/atom/browser/ui/tray_icon_cocoa.mm +++ b/atom/browser/ui/tray_icon_cocoa.mm @@ -5,6 +5,7 @@ #include "atom/browser/ui/tray_icon_cocoa.h" #include "atom/browser/ui/cocoa/atom_menu_controller.h" +#include "atom/browser/ui/cocoa/NSString+ANSI.h" #include "base/strings/sys_string_conversions.h" #include "ui/events/cocoa/cocoa_event_utils.h" #include "ui/gfx/image/image.h" @@ -26,9 +27,11 @@ const CGFloat kVerticalTitleMargin = 2; atom::TrayIcon::HighlightMode highlight_mode_; BOOL forceHighlight_; BOOL inMouseEventSequence_; + BOOL ANSI_; base::scoped_nsobject image_; base::scoped_nsobject alternateImage_; base::scoped_nsobject title_; + base::scoped_nsobject attributedTitle_; base::scoped_nsobject statusItem_; } @@ -85,12 +88,11 @@ const CGFloat kVerticalTitleMargin = 2; // | icon | title | /// ---------------- - BOOL highlight = [self shouldHighlight]; - BOOL highlightContent = highlight | [self isDarkMode]; CGFloat thickness = [[statusItem_ statusBar] thickness]; // Draw the system bar background. - [statusItem_ drawStatusBarBackgroundInRect:self.bounds withHighlight:highlight]; + [statusItem_ drawStatusBarBackgroundInRect:self.bounds + withHighlight:[self isHighlighted]]; // Determine which image to use. NSImage* image = image_.get(); @@ -102,7 +104,7 @@ const CGFloat kVerticalTitleMargin = 2; if ([image isTemplate] == YES) { NSImage* imageWithColor = [[image copy] autorelease]; [imageWithColor lockFocus]; - [[self colorWithHighlight: highlightContent] set]; + [[self colorWithHighlight: [self isHighlighted]] set]; CGRect imageBounds = CGRectMake(0,0, image.size.width, image.size.height); NSRectFillUsingOperation(imageBounds, NSCompositeSourceAtop); [imageWithColor unlockFocus]; @@ -121,8 +123,7 @@ const CGFloat kVerticalTitleMargin = 2; // Draw title. NSRect titleDrawRect = NSMakeRect( [self iconWidth], -kVerticalTitleMargin, [self titleWidth], thickness); - [title_ drawInRect:titleDrawRect - withAttributes:[self titleAttributesWithHighlight:highlightContent]]; + [attributedTitle_ drawInRect:titleDrawRect]; } } @@ -132,6 +133,12 @@ const CGFloat kVerticalTitleMargin = 2; return mode && [mode isEqualToString:@"Dark"]; } + +- (BOOL)isHighlighted { + BOOL highlight = [self shouldHighlight]; + return highlight | [self isDarkMode]; +} + // The width of the full status item. - (CGFloat)fullWidth { if (title_) @@ -166,11 +173,8 @@ const CGFloat kVerticalTitleMargin = 2; // The width of the title. - (CGFloat)titleWidth { if (!title_) - return 0; - base::scoped_nsobject attributes( - [[NSAttributedString alloc] initWithString:title_ - attributes:[self titleAttributes]]); - return [attributes size].width; + return 0; + return [attributedTitle_ size].width; } - (NSColor*)colorWithHighlight:(BOOL)highlight { @@ -179,17 +183,6 @@ const CGFloat kVerticalTitleMargin = 2; [NSColor colorWithRed:0.265625 green:0.25390625 blue:0.234375 alpha:1.0]; } -- (NSDictionary*)titleAttributesWithHighlight:(BOOL)highlight { - return @{ - NSFontAttributeName:[NSFont menuBarFontOfSize:0], - NSForegroundColorAttributeName:[self colorWithHighlight: highlight] - }; -} - -- (NSDictionary*)titleAttributes { - return [self titleAttributesWithHighlight:[self isDarkMode]]; -} - - (void)setImage:(NSImage*)image { image_.reset([image copy]); [self updateDimensions]; @@ -207,12 +200,43 @@ const CGFloat kVerticalTitleMargin = 2; - (void)setTitle:(NSString*)title { if (title.length > 0) { title_.reset([title copy]); + ANSI_ = [title containsANSICodes]; } else { title_.reset(); + ANSI_ = NO; } + [self updateAttributedTitle]; [self updateDimensions]; } + +- (void)updateAttributedTitle { + NSDictionary* attributes = @{ + NSFontAttributeName:[NSFont menuBarFontOfSize:0] + }; + + if (ANSI_) { + NSCharacterSet* whites = [NSCharacterSet whitespaceCharacterSet]; + NSString* title = [title_ stringByTrimmingCharactersInSet:whites]; + attributedTitle_.reset([title attributedStringParsingANSICodes]); + [attributedTitle_ addAttributes:attributes + range:NSMakeRange(0, [attributedTitle_ length])]; + return; + } + + attributedTitle_.reset([[NSMutableAttributedString alloc] + initWithString:title_ + attributes:attributes]); + + //NSFontAttributeName:[NSFont menuBarFontOfSize:0], + //NSForegroundColorAttributeName:[self colorWithHighlight: highlight] + [attributedTitle_ addAttributes:attributes + range:NSMakeRange(0, [attributedTitle_ length])]; + [attributedTitle_ addAttribute:NSForegroundColorAttributeName + value:[self colorWithHighlight: [self isHighlighted]] + range:NSMakeRange(0, [attributedTitle_ length])]; +} + - (void)setMenuController:(AtomMenuController*)menu { menuController_ = menu; } diff --git a/docs/api/tray.md b/docs/api/tray.md index 52147bcc292d..db16523ab0a3 100644 --- a/docs/api/tray.md +++ b/docs/api/tray.md @@ -208,7 +208,7 @@ Sets the hover text for this tray icon. * `title` String -Sets the title displayed aside of the tray icon in the status bar. +Sets the title displayed aside of the tray icon in the status bar (Support ANSI colors). #### `tray.setHighlightMode(mode)` _macOS_ diff --git a/filenames.gypi b/filenames.gypi index f83be716af21..2559aeb61255 100644 --- a/filenames.gypi +++ b/filenames.gypi @@ -312,6 +312,10 @@ 'atom/browser/ui/message_box_gtk.cc', 'atom/browser/ui/message_box_mac.mm', 'atom/browser/ui/message_box_win.cc', + 'atom/browser/ui/cocoa/NSColor+Hex.mm', + 'atom/browser/ui/cocoa/NSColor+Hex.h', + 'atom/browser/ui/cocoa/NSString+ANSI.mm', + 'atom/browser/ui/cocoa/NSString+ANSI.h', 'atom/browser/ui/tray_icon.cc', 'atom/browser/ui/tray_icon.h', 'atom/browser/ui/tray_icon_gtk.cc', diff --git a/script/cpplint.py b/script/cpplint.py index 5dfca8f76ece..0b0310243839 100755 --- a/script/cpplint.py +++ b/script/cpplint.py @@ -14,6 +14,8 @@ IGNORE_FILES = [ os.path.join('atom', 'browser', 'ui', 'cocoa', 'atom_touch_bar.h'), os.path.join('atom', 'browser', 'ui', 'cocoa', 'touch_bar_forward_declarations.h'), + os.path.join('atom', 'browser', 'ui', 'cocoa', 'NSColor+Hex.h'), + os.path.join('atom', 'browser', 'ui', 'cocoa', 'NSString+ANSI.h'), os.path.join('atom', 'common', 'api', 'api_messages.h'), os.path.join('atom', 'common', 'common_message_generator.cc'), os.path.join('atom', 'common', 'common_message_generator.h'),