blob: 3c19af4d453f537cabe57a14056f50f3530624a2 [file] [log] [blame]
// 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 <stdint.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.
constexpr CGFloat kBadgeFraction = 0.375f;
constexpr CGFloat kBadgeMargin = 4;
constexpr CGFloat kBadgeStrokeWidth = 6;
constexpr struct {
CGFloat offset, radius, opacity;
} kBadgeShadows[] = {
{0, 2, 0.14},
{2, 2, 0.12},
{1, 3, 0.2},
};
// The maximum update rate for the dock icon. 200ms = 5fps.
constexpr int64_t 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;
const CGFloat badgeSize = NSWidth(self.bounds) * kBadgeFraction;
const NSRect badgeRect =
NSMakeRect(NSMaxX(self.bounds) - badgeSize - kBadgeMargin, kBadgeMargin,
badgeSize, badgeSize);
const CGFloat badgeRadius = badgeSize / 2;
const NSPoint badgeCenter = NSMakePoint(NSMidX(badgeRect), NSMidY(badgeRect));
NSBezierPath* backgroundPath =
[NSBezierPath bezierPathWithOvalInRect:badgeRect];
[[NSColor clearColor] setFill];
base::scoped_nsobject<NSShadow> shadow([[NSShadow alloc] init]);
shadow.get().shadowColor = [NSColor blackColor];
for (const auto shadowProps : kBadgeShadows) {
gfx::ScopedNSGraphicsContextSaveGState scopedGState;
shadow.get().shadowOffset = NSMakeSize(0, -shadowProps.offset);
shadow.get().shadowBlurRadius = shadowProps.radius;
[[NSColor colorWithCalibratedWhite:0 alpha:shadowProps.opacity] setFill];
[shadow set];
[backgroundPath fill];
}
[[NSColor colorWithCalibratedRed:0xec / 255.0
green:0xf3 / 255.0
blue:0xfe / 255.0
alpha:1] setFill];
[backgroundPath fill];
// Stroke
if (!indeterminate_) {
NSBezierPath* strokePath;
if (progress_ >= 1.0) {
strokePath = [NSBezierPath bezierPathWithOvalInRect:badgeRect];
} else {
CGFloat endAngle = 90.0 - 360.0 * progress_;
if (endAngle < 0.0)
endAngle += 360.0;
strokePath = [NSBezierPath bezierPath];
[strokePath
appendBezierPathWithArcWithCenter:badgeCenter
radius:badgeRadius - kBadgeStrokeWidth / 2
startAngle:90.0
endAngle:endAngle
clockwise:YES];
}
[strokePath setLineWidth:kBadgeStrokeWidth];
[[NSColor colorWithCalibratedRed:0x42 / 255.0
green:0x85 / 255.0
blue:0xf4 / 255.0
alpha:1] setStroke];
[strokePath stroke];
}
// Download count
base::scoped_nsobject<NSNumberFormatter> formatter(
[[NSNumberFormatter alloc] init]);
NSString* countString =
[formatter stringFromNumber:[NSNumber numberWithInt:downloads_]];
CGFloat countFontSize = 24;
NSSize countSize = NSZeroSize;
base::scoped_nsobject<NSAttributedString> countAttrString;
while (1) {
NSFont* countFont;
if (@available(macOS 10.11, *)) {
countFont =
[NSFont systemFontOfSize:countFontSize weight:NSFontWeightMedium];
} else {
countFont = [[NSFontManager sharedFontManager]
convertWeight:YES
ofFont:[NSFont systemFontOfSize:countFontSize]];
}
// This will generally be plain Helvetica.
if (!countFont)
countFont = [NSFont userFontOfSize:countFontSize];
// Continued failure would generate an NSException.
if (!countFont)
break;
countAttrString.reset([[NSAttributedString alloc]
initWithString:countString
attributes:@{
NSForegroundColorAttributeName :
[NSColor colorWithCalibratedWhite:0 alpha:0.65],
NSFontAttributeName : countFont,
}]);
countSize = [countAttrString size];
if (countSize.width > (badgeRadius - kBadgeStrokeWidth) * 1.5) {
countFontSize -= 1.0;
} else {
break;
}
}
NSPoint countOrigin = badgeCenter;
countOrigin.x -= countSize.width / 2;
countOrigin.y -= countSize.height / 2;
[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_CURRENTLY_ON(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_CURRENTLY_ON(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_CURRENTLY_ON(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_CURRENTLY_ON(BrowserThread::UI);
NSDockTile* dockTile = [[NSApplication sharedApplication] dockTile];
DockTileView* dockTileView = (DockTileView*)([dockTile contentView]);
[dockTileView setProgress:progress];
}
@end