Home | History | Annotate | Download | only in cocoa
      1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
      2 // Use of this source code is governed by a BSD-style license that can be
      3 // found in the LICENSE file.
      4 
      5 #import "chrome/browser/ui/cocoa/dock_icon.h"
      6 
      7 #include "base/logging.h"
      8 #include "base/mac/bundle_locations.h"
      9 #include "base/mac/scoped_nsobject.h"
     10 #include "content/public/browser/browser_thread.h"
     11 #include "ui/gfx/scoped_ns_graphics_context_save_gstate_mac.h"
     12 
     13 using content::BrowserThread;
     14 
     15 namespace {
     16 
     17 // The fraction of the size of the dock icon that the badge is.
     18 const float kBadgeFraction = 0.4f;
     19 
     20 // The indentation of the badge.
     21 const float kBadgeIndent = 5.0f;
     22 
     23 // The maximum update rate for the dock icon. 200ms = 5fps.
     24 const int64 kUpdateFrequencyMs = 200;
     25 
     26 }  // namespace
     27 
     28 // A view that draws our dock tile.
     29 @interface DockTileView : NSView {
     30  @private
     31   int downloads_;
     32   BOOL indeterminate_;
     33   float progress_;
     34 }
     35 
     36 // Indicates how many downloads are in progress.
     37 @property (nonatomic) int downloads;
     38 
     39 // Indicates whether the progress indicator should be in an indeterminate state
     40 // or not.
     41 @property (nonatomic) BOOL indeterminate;
     42 
     43 // Indicates the amount of progress made of the download. Ranges from [0..1].
     44 @property (nonatomic) float progress;
     45 
     46 @end
     47 
     48 @implementation DockTileView
     49 
     50 @synthesize downloads = downloads_;
     51 @synthesize indeterminate = indeterminate_;
     52 @synthesize progress = progress_;
     53 
     54 - (void)drawRect:(NSRect)dirtyRect {
     55   // Not -[NSApplication applicationIconImage]; that fails to return a pasted
     56   // custom icon.
     57   NSString* appPath = [base::mac::MainBundle() bundlePath];
     58   NSImage* appIcon = [[NSWorkspace sharedWorkspace] iconForFile:appPath];
     59   [appIcon drawInRect:[self bounds]
     60              fromRect:NSZeroRect
     61             operation:NSCompositeSourceOver
     62              fraction:1.0];
     63 
     64   if (downloads_ == 0)
     65     return;
     66 
     67   NSRect badgeRect = [self bounds];
     68   badgeRect.size.height = (int)(kBadgeFraction * badgeRect.size.height);
     69   int newWidth = kBadgeFraction * NSWidth(badgeRect);
     70   badgeRect.origin.x = NSWidth(badgeRect) - newWidth;
     71   badgeRect.size.width = newWidth;
     72 
     73   CGFloat badgeRadius = NSMidY(badgeRect);
     74 
     75   badgeRect.origin.x -= kBadgeIndent;
     76   badgeRect.origin.y += kBadgeIndent;
     77 
     78   NSPoint badgeCenter = NSMakePoint(NSMidX(badgeRect), NSMidY(badgeRect));
     79 
     80   // Background
     81   NSColor* backgroundColor = [NSColor colorWithCalibratedRed:0.85
     82                                                        green:0.85
     83                                                         blue:0.85
     84                                                        alpha:1.0];
     85   NSColor* backgroundHighlight =
     86       [backgroundColor blendedColorWithFraction:0.85
     87                                         ofColor:[NSColor whiteColor]];
     88   base::scoped_nsobject<NSGradient> backgroundGradient(
     89       [[NSGradient alloc] initWithStartingColor:backgroundHighlight
     90                                     endingColor:backgroundColor]);
     91   NSBezierPath* badgeEdge = [NSBezierPath bezierPathWithOvalInRect:badgeRect];
     92   {
     93     gfx::ScopedNSGraphicsContextSaveGState scopedGState;
     94     [badgeEdge addClip];
     95     [backgroundGradient drawFromCenter:badgeCenter
     96                                 radius:0.0
     97                               toCenter:badgeCenter
     98                                 radius:badgeRadius
     99                                options:0];
    100   }
    101 
    102   // Slice
    103   if (!indeterminate_) {
    104     NSColor* sliceColor = [NSColor colorWithCalibratedRed:0.45
    105                                                     green:0.8
    106                                                      blue:0.25
    107                                                     alpha:1.0];
    108     NSColor* sliceHighlight =
    109         [sliceColor blendedColorWithFraction:0.4
    110                                      ofColor:[NSColor whiteColor]];
    111     base::scoped_nsobject<NSGradient> sliceGradient(
    112         [[NSGradient alloc] initWithStartingColor:sliceHighlight
    113                                       endingColor:sliceColor]);
    114     NSBezierPath* progressSlice;
    115     if (progress_ >= 1.0) {
    116       progressSlice = [NSBezierPath bezierPathWithOvalInRect:badgeRect];
    117     } else {
    118       CGFloat endAngle = 90.0 - 360.0 * progress_;
    119       if (endAngle < 0.0)
    120         endAngle += 360.0;
    121       progressSlice = [NSBezierPath bezierPath];
    122       [progressSlice moveToPoint:badgeCenter];
    123       [progressSlice appendBezierPathWithArcWithCenter:badgeCenter
    124                                                 radius:badgeRadius
    125                                             startAngle:90.0
    126                                               endAngle:endAngle
    127                                              clockwise:YES];
    128       [progressSlice closePath];
    129     }
    130     gfx::ScopedNSGraphicsContextSaveGState scopedGState;
    131     [progressSlice addClip];
    132     [sliceGradient drawFromCenter:badgeCenter
    133                            radius:0.0
    134                          toCenter:badgeCenter
    135                            radius:badgeRadius
    136                           options:0];
    137   }
    138 
    139   // Edge
    140   {
    141     gfx::ScopedNSGraphicsContextSaveGState scopedGState;
    142     [[NSColor whiteColor] set];
    143     base::scoped_nsobject<NSShadow> shadow([[NSShadow alloc] init]);
    144     [shadow.get() setShadowOffset:NSMakeSize(0, -2)];
    145     [shadow setShadowBlurRadius:2];
    146     [shadow set];
    147     [badgeEdge setLineWidth:2];
    148     [badgeEdge stroke];
    149   }
    150 
    151   // Download count
    152   base::scoped_nsobject<NSNumberFormatter> formatter(
    153       [[NSNumberFormatter alloc] init]);
    154   NSString* countString =
    155       [formatter stringFromNumber:[NSNumber numberWithInt:downloads_]];
    156 
    157   base::scoped_nsobject<NSShadow> countShadow([[NSShadow alloc] init]);
    158   [countShadow setShadowBlurRadius:3.0];
    159   [countShadow.get() setShadowColor:[NSColor whiteColor]];
    160   [countShadow.get() setShadowOffset:NSMakeSize(0.0, 0.0)];
    161   NSMutableDictionary* countAttrsDict =
    162       [NSMutableDictionary dictionaryWithObjectsAndKeys:
    163           [NSColor blackColor], NSForegroundColorAttributeName,
    164           countShadow.get(), NSShadowAttributeName,
    165           nil];
    166   CGFloat countFontSize = badgeRadius;
    167   NSSize countSize = NSZeroSize;
    168   base::scoped_nsobject<NSAttributedString> countAttrString;
    169   while (1) {
    170     NSFont* countFont = [NSFont fontWithName:@"Helvetica-Bold"
    171                                         size:countFontSize];
    172 
    173     // This will generally be plain Helvetica.
    174     if (!countFont)
    175       countFont = [NSFont userFontOfSize:countFontSize];
    176 
    177     // Continued failure would generate an NSException.
    178     if (!countFont)
    179       break;
    180 
    181     [countAttrsDict setObject:countFont forKey:NSFontAttributeName];
    182     countAttrString.reset(
    183         [[NSAttributedString alloc] initWithString:countString
    184                                         attributes:countAttrsDict]);
    185     countSize = [countAttrString size];
    186     if (countSize.width > badgeRadius * 1.5) {
    187       countFontSize -= 1.0;
    188     } else {
    189       break;
    190     }
    191   }
    192 
    193   NSPoint countOrigin = badgeCenter;
    194   countOrigin.x -= countSize.width / 2;
    195   countOrigin.y -= countSize.height / 2.2;  // tweak; otherwise too low
    196 
    197   [countAttrString.get() drawAtPoint:countOrigin];
    198 }
    199 
    200 @end
    201 
    202 
    203 @implementation DockIcon
    204 
    205 + (DockIcon*)sharedDockIcon {
    206   static DockIcon* icon;
    207   if (!icon) {
    208     NSDockTile* dockTile = [[NSApplication sharedApplication] dockTile];
    209 
    210     base::scoped_nsobject<DockTileView> dockTileView(
    211         [[DockTileView alloc] init]);
    212     [dockTile setContentView:dockTileView];
    213 
    214     icon = [[DockIcon alloc] init];
    215   }
    216 
    217   return icon;
    218 }
    219 
    220 - (void)updateIcon {
    221   DCHECK_CURRENTLY_ON(BrowserThread::UI);
    222   static base::TimeDelta updateFrequency =
    223       base::TimeDelta::FromMilliseconds(kUpdateFrequencyMs);
    224 
    225   base::TimeTicks now = base::TimeTicks::Now();
    226   base::TimeDelta timeSinceLastUpdate = now - lastUpdate_;
    227   if (!forceUpdate_ && timeSinceLastUpdate < updateFrequency)
    228     return;
    229 
    230   lastUpdate_ = now;
    231   forceUpdate_ = NO;
    232 
    233   NSDockTile* dockTile = [[NSApplication sharedApplication] dockTile];
    234 
    235   [dockTile display];
    236 }
    237 
    238 - (void)setDownloads:(int)downloads {
    239   DCHECK_CURRENTLY_ON(BrowserThread::UI);
    240   NSDockTile* dockTile = [[NSApplication sharedApplication] dockTile];
    241   DockTileView* dockTileView = (DockTileView*)([dockTile contentView]);
    242 
    243   if (downloads != [dockTileView downloads]) {
    244     [dockTileView setDownloads:downloads];
    245     forceUpdate_ = YES;
    246   }
    247 }
    248 
    249 - (void)setIndeterminate:(BOOL)indeterminate {
    250   DCHECK_CURRENTLY_ON(BrowserThread::UI);
    251   NSDockTile* dockTile = [[NSApplication sharedApplication] dockTile];
    252   DockTileView* dockTileView = (DockTileView*)([dockTile contentView]);
    253 
    254   if (indeterminate != [dockTileView indeterminate]) {
    255     [dockTileView setIndeterminate:indeterminate];
    256     forceUpdate_ = YES;
    257   }
    258 }
    259 
    260 - (void)setProgress:(float)progress {
    261   DCHECK_CURRENTLY_ON(BrowserThread::UI);
    262   NSDockTile* dockTile = [[NSApplication sharedApplication] dockTile];
    263   DockTileView* dockTileView = (DockTileView*)([dockTile contentView]);
    264 
    265   [dockTileView setProgress:progress];
    266 }
    267 
    268 @end
    269