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