// Copyright (c) 2012 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #import "chrome/browser/ui/cocoa/dock_icon.h" #include "base/logging.h" #include "base/mac/bundle_locations.h" #include "base/mac/scoped_nsobject.h" #include "content/public/browser/browser_thread.h" #include "ui/gfx/scoped_ns_graphics_context_save_gstate_mac.h" using content::BrowserThread; namespace { // The fraction of the size of the dock icon that the badge is. const float kBadgeFraction = 0.4f; // The indentation of the badge. const float kBadgeIndent = 5.0f; // The maximum update rate for the dock icon. 200ms = 5fps. const int64 kUpdateFrequencyMs = 200; } // namespace // A view that draws our dock tile. @interface DockTileView : NSView { @private int downloads_; BOOL indeterminate_; float progress_; } // Indicates how many downloads are in progress. @property (nonatomic) int downloads; // Indicates whether the progress indicator should be in an indeterminate state // or not. @property (nonatomic) BOOL indeterminate; // Indicates the amount of progress made of the download. Ranges from [0..1]. @property (nonatomic) float progress; @end @implementation DockTileView @synthesize downloads = downloads_; @synthesize indeterminate = indeterminate_; @synthesize progress = progress_; - (void)drawRect:(NSRect)dirtyRect { // Not -[NSApplication applicationIconImage]; that fails to return a pasted // custom icon. NSString* appPath = [base::mac::MainBundle() bundlePath]; NSImage* appIcon = [[NSWorkspace sharedWorkspace] iconForFile:appPath]; [appIcon drawInRect:[self bounds] fromRect:NSZeroRect operation:NSCompositeSourceOver fraction:1.0]; if (downloads_ == 0) return; NSRect badgeRect = [self bounds]; badgeRect.size.height = (int)(kBadgeFraction * badgeRect.size.height); int newWidth = kBadgeFraction * NSWidth(badgeRect); badgeRect.origin.x = NSWidth(badgeRect) - newWidth; badgeRect.size.width = newWidth; CGFloat badgeRadius = NSMidY(badgeRect); badgeRect.origin.x -= kBadgeIndent; badgeRect.origin.y += kBadgeIndent; NSPoint badgeCenter = NSMakePoint(NSMidX(badgeRect), NSMidY(badgeRect)); // Background NSColor* backgroundColor = [NSColor colorWithCalibratedRed:0.85 green:0.85 blue:0.85 alpha:1.0]; NSColor* backgroundHighlight = [backgroundColor blendedColorWithFraction:0.85 ofColor:[NSColor whiteColor]]; base::scoped_nsobject<NSGradient> backgroundGradient( [[NSGradient alloc] initWithStartingColor:backgroundHighlight endingColor:backgroundColor]); NSBezierPath* badgeEdge = [NSBezierPath bezierPathWithOvalInRect:badgeRect]; { gfx::ScopedNSGraphicsContextSaveGState scopedGState; [badgeEdge addClip]; [backgroundGradient drawFromCenter:badgeCenter radius:0.0 toCenter:badgeCenter radius:badgeRadius options:0]; } // Slice if (!indeterminate_) { NSColor* sliceColor = [NSColor colorWithCalibratedRed:0.45 green:0.8 blue:0.25 alpha:1.0]; NSColor* sliceHighlight = [sliceColor blendedColorWithFraction:0.4 ofColor:[NSColor whiteColor]]; base::scoped_nsobject<NSGradient> sliceGradient( [[NSGradient alloc] initWithStartingColor:sliceHighlight endingColor:sliceColor]); NSBezierPath* progressSlice; if (progress_ >= 1.0) { progressSlice = [NSBezierPath bezierPathWithOvalInRect:badgeRect]; } else { CGFloat endAngle = 90.0 - 360.0 * progress_; if (endAngle < 0.0) endAngle += 360.0; progressSlice = [NSBezierPath bezierPath]; [progressSlice moveToPoint:badgeCenter]; [progressSlice appendBezierPathWithArcWithCenter:badgeCenter radius:badgeRadius startAngle:90.0 endAngle:endAngle clockwise:YES]; [progressSlice closePath]; } gfx::ScopedNSGraphicsContextSaveGState scopedGState; [progressSlice addClip]; [sliceGradient drawFromCenter:badgeCenter radius:0.0 toCenter:badgeCenter radius:badgeRadius options:0]; } // Edge { gfx::ScopedNSGraphicsContextSaveGState scopedGState; [[NSColor whiteColor] set]; base::scoped_nsobject<NSShadow> shadow([[NSShadow alloc] init]); [shadow.get() setShadowOffset:NSMakeSize(0, -2)]; [shadow setShadowBlurRadius:2]; [shadow set]; [badgeEdge setLineWidth:2]; [badgeEdge stroke]; } // Download count base::scoped_nsobject<NSNumberFormatter> formatter( [[NSNumberFormatter alloc] init]); NSString* countString = [formatter stringFromNumber:[NSNumber numberWithInt:downloads_]]; base::scoped_nsobject<NSShadow> countShadow([[NSShadow alloc] init]); [countShadow setShadowBlurRadius:3.0]; [countShadow.get() setShadowColor:[NSColor whiteColor]]; [countShadow.get() setShadowOffset:NSMakeSize(0.0, 0.0)]; NSMutableDictionary* countAttrsDict = [NSMutableDictionary dictionaryWithObjectsAndKeys: [NSColor blackColor], NSForegroundColorAttributeName, countShadow.get(), NSShadowAttributeName, nil]; CGFloat countFontSize = badgeRadius; NSSize countSize = NSZeroSize; base::scoped_nsobject<NSAttributedString> countAttrString; while (1) { NSFont* countFont = [NSFont fontWithName:@"Helvetica-Bold" size:countFontSize]; // This will generally be plain Helvetica. if (!countFont) countFont = [NSFont userFontOfSize:countFontSize]; // Continued failure would generate an NSException. if (!countFont) break; [countAttrsDict setObject:countFont forKey:NSFontAttributeName]; countAttrString.reset( [[NSAttributedString alloc] initWithString:countString attributes:countAttrsDict]); countSize = [countAttrString size]; if (countSize.width > badgeRadius * 1.5) { countFontSize -= 1.0; } else { break; } } NSPoint countOrigin = badgeCenter; countOrigin.x -= countSize.width / 2; countOrigin.y -= countSize.height / 2.2; // tweak; otherwise too low [countAttrString.get() drawAtPoint:countOrigin]; } @end @implementation DockIcon + (DockIcon*)sharedDockIcon { static DockIcon* icon; if (!icon) { NSDockTile* dockTile = [[NSApplication sharedApplication] dockTile]; base::scoped_nsobject<DockTileView> dockTileView( [[DockTileView alloc] init]); [dockTile setContentView:dockTileView]; icon = [[DockIcon alloc] init]; } return icon; } - (void)updateIcon { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); static base::TimeDelta updateFrequency = base::TimeDelta::FromMilliseconds(kUpdateFrequencyMs); base::TimeTicks now = base::TimeTicks::Now(); base::TimeDelta timeSinceLastUpdate = now - lastUpdate_; if (!forceUpdate_ && timeSinceLastUpdate < updateFrequency) return; lastUpdate_ = now; forceUpdate_ = NO; NSDockTile* dockTile = [[NSApplication sharedApplication] dockTile]; [dockTile display]; } - (void)setDownloads:(int)downloads { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); NSDockTile* dockTile = [[NSApplication sharedApplication] dockTile]; DockTileView* dockTileView = (DockTileView*)([dockTile contentView]); if (downloads != [dockTileView downloads]) { [dockTileView setDownloads:downloads]; forceUpdate_ = YES; } } - (void)setIndeterminate:(BOOL)indeterminate { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); NSDockTile* dockTile = [[NSApplication sharedApplication] dockTile]; DockTileView* dockTileView = (DockTileView*)([dockTile contentView]); if (indeterminate != [dockTileView indeterminate]) { [dockTileView setIndeterminate:indeterminate]; forceUpdate_ = YES; } } - (void)setProgress:(float)progress { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); NSDockTile* dockTile = [[NSApplication sharedApplication] dockTile]; DockTileView* dockTileView = (DockTileView*)([dockTile contentView]); [dockTileView setProgress:progress]; } @end