Popular sites on the NTP: check that experiment group StartsWith (rather than IS...
[chromium-blink-merge.git] / chrome / browser / ui / cocoa / dock_icon.mm
blob7b38acb4df6fdf6753a10112256151b40f4996a0
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.
5 #import "chrome/browser/ui/cocoa/dock_icon.h"
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"
13 using content::BrowserThread;
15 namespace {
17 // The fraction of the size of the dock icon that the badge is.
18 const float kBadgeFraction = 0.4f;
20 // The indentation of the badge.
21 const float kBadgeIndent = 5.0f;
23 // The maximum update rate for the dock icon. 200ms = 5fps.
24 const int64 kUpdateFrequencyMs = 200;
26 }  // namespace
28 // A view that draws our dock tile.
29 @interface DockTileView : NSView {
30  @private
31   int downloads_;
32   BOOL indeterminate_;
33   float progress_;
36 // Indicates how many downloads are in progress.
37 @property (nonatomic) int downloads;
39 // Indicates whether the progress indicator should be in an indeterminate state
40 // or not.
41 @property (nonatomic) BOOL indeterminate;
43 // Indicates the amount of progress made of the download. Ranges from [0..1].
44 @property (nonatomic) float progress;
46 @end
48 @implementation DockTileView
50 @synthesize downloads = downloads_;
51 @synthesize indeterminate = indeterminate_;
52 @synthesize progress = progress_;
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];
64   if (downloads_ == 0)
65     return;
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;
73   CGFloat badgeRadius = NSMidY(badgeRect);
75   badgeRect.origin.x -= kBadgeIndent;
76   badgeRect.origin.y += kBadgeIndent;
78   NSPoint badgeCenter = NSMakePoint(NSMidX(badgeRect), NSMidY(badgeRect));
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   }
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   }
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   }
151   // Download count
152   base::scoped_nsobject<NSNumberFormatter> formatter(
153       [[NSNumberFormatter alloc] init]);
154   NSString* countString =
155       [formatter stringFromNumber:[NSNumber numberWithInt:downloads_]];
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];
173     // This will generally be plain Helvetica.
174     if (!countFont)
175       countFont = [NSFont userFontOfSize:countFontSize];
177     // Continued failure would generate an NSException.
178     if (!countFont)
179       break;
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   }
193   NSPoint countOrigin = badgeCenter;
194   countOrigin.x -= countSize.width / 2;
195   countOrigin.y -= countSize.height / 2.2;  // tweak; otherwise too low
197   [countAttrString.get() drawAtPoint:countOrigin];
200 @end
203 @implementation DockIcon
205 + (DockIcon*)sharedDockIcon {
206   static DockIcon* icon;
207   if (!icon) {
208     NSDockTile* dockTile = [[NSApplication sharedApplication] dockTile];
210     base::scoped_nsobject<DockTileView> dockTileView(
211         [[DockTileView alloc] init]);
212     [dockTile setContentView:dockTileView];
214     icon = [[DockIcon alloc] init];
215   }
217   return icon;
220 - (void)updateIcon {
221   DCHECK_CURRENTLY_ON(BrowserThread::UI);
222   static base::TimeDelta updateFrequency =
223       base::TimeDelta::FromMilliseconds(kUpdateFrequencyMs);
225   base::TimeTicks now = base::TimeTicks::Now();
226   base::TimeDelta timeSinceLastUpdate = now - lastUpdate_;
227   if (!forceUpdate_ && timeSinceLastUpdate < updateFrequency)
228     return;
230   lastUpdate_ = now;
231   forceUpdate_ = NO;
233   NSDockTile* dockTile = [[NSApplication sharedApplication] dockTile];
235   [dockTile display];
238 - (void)setDownloads:(int)downloads {
239   DCHECK_CURRENTLY_ON(BrowserThread::UI);
240   NSDockTile* dockTile = [[NSApplication sharedApplication] dockTile];
241   DockTileView* dockTileView = (DockTileView*)([dockTile contentView]);
243   if (downloads != [dockTileView downloads]) {
244     [dockTileView setDownloads:downloads];
245     forceUpdate_ = YES;
246   }
249 - (void)setIndeterminate:(BOOL)indeterminate {
250   DCHECK_CURRENTLY_ON(BrowserThread::UI);
251   NSDockTile* dockTile = [[NSApplication sharedApplication] dockTile];
252   DockTileView* dockTileView = (DockTileView*)([dockTile contentView]);
254   if (indeterminate != [dockTileView indeterminate]) {
255     [dockTileView setIndeterminate:indeterminate];
256     forceUpdate_ = YES;
257   }
260 - (void)setProgress:(float)progress {
261   DCHECK_CURRENTLY_ON(BrowserThread::UI);
262   NSDockTile* dockTile = [[NSApplication sharedApplication] dockTile];
263   DockTileView* dockTileView = (DockTileView*)([dockTile contentView]);
265   [dockTileView setProgress:progress];
268 @end