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;
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;
28 // A view that draws our dock tile.
29 @interface DockTileView : NSView {
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
41 @property (nonatomic) BOOL indeterminate;
43 // Indicates the amount of progress made of the download. Ranges from [0..1].
44 @property (nonatomic) float progress;
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
57 NSString* appPath = [base::mac::MainBundle() bundlePath];
58 NSImage* appIcon = [[NSWorkspace sharedWorkspace] iconForFile:appPath];
59 [appIcon drawInRect:[self bounds]
61 operation:NSCompositeSourceOver
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));
81 NSColor* backgroundColor = [NSColor colorWithCalibratedRed:0.85
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];
93 gfx::ScopedNSGraphicsContextSaveGState scopedGState;
95 [backgroundGradient drawFromCenter:badgeCenter
103 if (!indeterminate_) {
104 NSColor* sliceColor = [NSColor colorWithCalibratedRed:0.45
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];
118 CGFloat endAngle = 90.0 - 360.0 * progress_;
121 progressSlice = [NSBezierPath bezierPath];
122 [progressSlice moveToPoint:badgeCenter];
123 [progressSlice appendBezierPathWithArcWithCenter:badgeCenter
128 [progressSlice closePath];
130 gfx::ScopedNSGraphicsContextSaveGState scopedGState;
131 [progressSlice addClip];
132 [sliceGradient drawFromCenter:badgeCenter
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];
147 [badgeEdge setLineWidth:2];
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,
166 CGFloat countFontSize = badgeRadius;
167 NSSize countSize = NSZeroSize;
168 base::scoped_nsobject<NSAttributedString> countAttrString;
170 NSFont* countFont = [NSFont fontWithName:@"Helvetica-Bold"
173 // This will generally be plain Helvetica.
175 countFont = [NSFont userFontOfSize:countFontSize];
177 // Continued failure would generate an NSException.
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;
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];
203 @implementation DockIcon
205 + (DockIcon*)sharedDockIcon {
206 static DockIcon* 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];
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)
233 NSDockTile* dockTile = [[NSApplication sharedApplication] dockTile];
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];
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];
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];