Browse Source

Merge pull request #11108 from oktapodia/feature/add-ansi-color-into-menu-title

Feature/add ansi color into menu title
Cheng Zhao 7 years ago
parent
commit
d09e8cc37f

+ 16 - 0
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 <Cocoa/Cocoa.h>
+
+@interface NSColor(Hex)
++ (NSColor*)colorWithHexColorString:(NSString*)hex;
+@end
+
+#endif  // ATOM_BROWSER_UI_COCOA_NSCOLOR_HEX_H_

+ 29 - 0
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

+ 17 - 0
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 <Foundation/Foundation.h>
+
+@interface NSString(ANSI)
+- (BOOL)containsANSICodes;
+- (NSMutableAttributedString*)attributedStringParsingANSICodes;
+@end
+
+#endif  // ATOM_BROWSER_UI_COCOA_NSSTRING_ANSI_H_

+ 146 - 0
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<NSMutableDictionary> 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

+ 46 - 22
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<NSImage> image_;
   base::scoped_nsobject<NSImage> alternateImage_;
   base::scoped_nsobject<NSString> title_;
+  base::scoped_nsobject<NSMutableAttributedString> attributedTitle_;
   base::scoped_nsobject<NSStatusItem> 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<NSAttributedString> 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;
 }

+ 1 - 1
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_
 

+ 4 - 0
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',

+ 2 - 0
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'),