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/download/download_item_cell.h"
7 #include "base/strings/sys_string_conversions.h"
8 #include "chrome/browser/download/download_item_model.h"
9 #include "chrome/browser/download/download_shelf.h"
10 #import "chrome/browser/themes/theme_properties.h"
11 #import "chrome/browser/ui/cocoa/download/background_theme.h"
12 #import "chrome/browser/ui/cocoa/themed_window.h"
13 #include "content/public/browser/download_item.h"
14 #include "content/public/browser/download_manager.h"
15 #include "grit/theme_resources.h"
16 #import "third_party/google_toolbox_for_mac/src/AppKit/GTMNSAnimation+Duration.h"
17 #import "third_party/google_toolbox_for_mac/src/AppKit/GTMNSColor+Luminance.h"
18 #include "ui/base/l10n/l10n_util.h"
19 #include "ui/gfx/text_elider.h"
20 #include "ui/gfx/canvas_skia_paint.h"
21 #include "ui/gfx/font_list.h"
22 #include "ui/gfx/scoped_ns_graphics_context_save_gstate_mac.h"
24 // Distance from top border to icon.
25 const CGFloat kImagePaddingTop = 7;
27 // Distance from left border to icon.
28 const CGFloat kImagePaddingLeft = 9;
31 const CGFloat kImageWidth = 16;
34 const CGFloat kImageHeight = 16;
36 // x coordinate of download name string, in view coords.
37 const CGFloat kTextPosLeft = kImagePaddingLeft +
38 kImageWidth + DownloadShelf::kSmallProgressIconOffset;
40 // Distance from end of download name string to dropdown area.
41 const CGFloat kTextPaddingRight = 3;
43 // y coordinate of download name string, in view coords, when status message
45 const CGFloat kPrimaryTextPosTop = 3;
47 // y coordinate of download name string, in view coords, when status message
49 const CGFloat kPrimaryTextOnlyPosTop = 10;
51 // y coordinate of status message, in view coords.
52 const CGFloat kSecondaryTextPosTop = 18;
54 // Width of dropdown area on the right (includes 1px for the border on each
56 const CGFloat kDropdownAreaWidth = 14;
58 // Width of dropdown arrow.
59 const CGFloat kDropdownArrowWidth = 5;
61 // Height of dropdown arrow.
62 const CGFloat kDropdownArrowHeight = 3;
64 // Vertical displacement of dropdown area, relative to the "centered" position.
65 const CGFloat kDropdownAreaY = -2;
67 // Duration of the two-lines-to-one-line animation, in seconds.
68 NSTimeInterval kShowStatusDuration = 0.3;
69 NSTimeInterval kHideStatusDuration = 0.3;
71 // Duration of the 'download complete' animation, in seconds.
72 const CGFloat kCompleteAnimationDuration = 2.5;
74 // Duration of the 'download interrupted' animation, in seconds.
75 const CGFloat kInterruptedAnimationDuration = 2.5;
77 using content::DownloadItem;
79 // This is a helper class to animate the fading out of the status text.
80 @interface DownloadItemCellAnimation : NSAnimation {
82 DownloadItemCell* cell_;
84 - (id)initWithDownloadItemCell:(DownloadItemCell*)cell
85 duration:(NSTimeInterval)duration
86 animationCurve:(NSAnimationCurve)animationCurve;
90 // Timer used to animate indeterminate progress. An NSTimer retains its target.
91 // This means that the target must explicitly invalidate the timer before it
92 // can be deleted. This class keeps a weak reference to the target so the
93 // timer can be invalidated from the destructor.
94 @interface IndeterminateProgressTimer : NSObject {
96 DownloadItemCell* cell_;
97 base::scoped_nsobject<NSTimer> timer_;
100 - (id)initWithDownloadItemCell:(DownloadItemCell*)cell;
105 @interface DownloadItemCell(Private)
106 - (void)updateTrackingAreas:(id)sender;
107 - (void)setupToggleStatusVisibilityAnimation;
108 - (void)showSecondaryTitle;
109 - (void)hideSecondaryTitle;
110 - (void)animation:(NSAnimation*)animation
111 progressed:(NSAnimationProgress)progress;
112 - (void)updateIndeterminateDownload;
113 - (void)stopIndeterminateAnimation;
114 - (NSString*)elideTitle:(int)availableWidth;
115 - (NSString*)elideStatus:(int)availableWidth;
116 - (ui::ThemeProvider*)backgroundThemeWrappingProvider:
117 (ui::ThemeProvider*)provider;
118 - (BOOL)pressedWithDefaultThemeOnPart:(DownloadItemMousePosition)part;
119 - (NSColor*)titleColorForPart:(DownloadItemMousePosition)part;
120 - (void)drawSecondaryTitleInRect:(NSRect)innerFrame;
121 - (BOOL)isDefaultTheme;
124 @implementation DownloadItemCell
126 @synthesize secondaryTitle = secondaryTitle_;
127 @synthesize secondaryFont = secondaryFont_;
129 - (void)setInitialState {
130 isStatusTextVisible_ = NO;
131 titleY_ = kPrimaryTextPosTop;
133 indeterminateProgressAngle_ = DownloadShelf::kStartAngleDegrees;
135 [self setFont:[NSFont systemFontOfSize:
136 [NSFont systemFontSizeForControlSize:NSSmallControlSize]]];
137 [self setSecondaryFont:[NSFont systemFontOfSize:
138 [NSFont systemFontSizeForControlSize:NSSmallControlSize]]];
140 [self updateTrackingAreas:self];
141 [[NSNotificationCenter defaultCenter]
143 selector:@selector(updateTrackingAreas:)
144 name:NSViewFrameDidChangeNotification
145 object:[self controlView]];
148 // For nib instantiations
149 - (id)initWithCoder:(NSCoder*)decoder {
150 if ((self = [super initWithCoder:decoder])) {
151 [self setInitialState];
156 // For programmatic instantiations.
157 - (id)initTextCell:(NSString *)string {
158 if ((self = [super initTextCell:string])) {
159 [self setInitialState];
165 [[NSNotificationCenter defaultCenter] removeObserver:self];
166 if ([completionAnimation_ isAnimating])
167 [completionAnimation_ stopAnimation];
168 if ([toggleStatusVisibilityAnimation_ isAnimating])
169 [toggleStatusVisibilityAnimation_ stopAnimation];
170 if (trackingAreaButton_) {
171 [[self controlView] removeTrackingArea:trackingAreaButton_];
172 trackingAreaButton_.reset();
174 if (trackingAreaDropdown_) {
175 [[self controlView] removeTrackingArea:trackingAreaDropdown_];
176 trackingAreaDropdown_.reset();
178 [self stopIndeterminateAnimation];
179 [secondaryTitle_ release];
180 [secondaryFont_ release];
184 - (void)setStateFromDownload:(DownloadItemModel*)downloadModel {
185 // Set the name of the download.
186 downloadPath_ = downloadModel->download()->GetFileNameToReportUser();
188 base::string16 statusText = downloadModel->GetStatusText();
189 if (statusText.empty()) {
190 // Remove the status text label.
191 [self hideSecondaryTitle];
194 NSString* statusString = base::SysUTF16ToNSString(statusText);
195 [self setSecondaryTitle:statusString];
196 [self showSecondaryTitle];
199 switch (downloadModel->download()->GetState()) {
200 case DownloadItem::COMPLETE:
201 // Small downloads may start in a complete state due to asynchronous
202 // notifications. In this case, we'll get a second complete notification
203 // via the observers, so we ignore it and avoid creating a second complete
205 if (completionAnimation_.get())
207 completionAnimation_.reset([[DownloadItemCellAnimation alloc]
208 initWithDownloadItemCell:self
209 duration:kCompleteAnimationDuration
210 animationCurve:NSAnimationLinear]);
211 [completionAnimation_.get() setDelegate:self];
212 [completionAnimation_.get() startAnimation];
214 [self stopIndeterminateAnimation];
216 case DownloadItem::CANCELLED:
218 [self stopIndeterminateAnimation];
220 case DownloadItem::INTERRUPTED:
221 // Small downloads may start in an interrupted state due to asynchronous
222 // notifications. In this case, we'll get a second complete notification
223 // via the observers, so we ignore it and avoid creating a second complete
225 if (completionAnimation_.get())
227 completionAnimation_.reset([[DownloadItemCellAnimation alloc]
228 initWithDownloadItemCell:self
229 duration:kInterruptedAnimationDuration
230 animationCurve:NSAnimationLinear]);
231 [completionAnimation_.get() setDelegate:self];
232 [completionAnimation_.get() startAnimation];
234 [self stopIndeterminateAnimation];
236 case DownloadItem::IN_PROGRESS:
237 if (downloadModel->download()->IsPaused()) {
239 [self stopIndeterminateAnimation];
240 } else if (downloadModel->PercentComplete() == -1) {
242 if (!indeterminateProgressTimer_) {
243 indeterminateProgressTimer_.reset([[IndeterminateProgressTimer alloc]
244 initWithDownloadItemCell:self]);
247 percentDone_ = downloadModel->PercentComplete();
248 [self stopIndeterminateAnimation];
255 [[self controlView] setNeedsDisplay:YES];
258 - (void)updateTrackingAreas:(id)sender {
259 if (trackingAreaButton_) {
260 [[self controlView] removeTrackingArea:trackingAreaButton_.get()];
261 trackingAreaButton_.reset(nil);
263 if (trackingAreaDropdown_) {
264 [[self controlView] removeTrackingArea:trackingAreaDropdown_.get()];
265 trackingAreaDropdown_.reset(nil);
268 // Use two distinct tracking rects for left and right parts.
269 // The tracking areas are also used to decide how to handle clicks. They must
270 // always be active, so the click is handled correctly when a download item
271 // is clicked while chrome is not the active app ( http://crbug.com/21916 ).
272 NSRect bounds = [[self controlView] bounds];
273 NSRect buttonRect, dropdownRect;
274 NSDivideRect(bounds, &dropdownRect, &buttonRect,
275 kDropdownAreaWidth, NSMaxXEdge);
277 trackingAreaButton_.reset([[NSTrackingArea alloc]
278 initWithRect:buttonRect
279 options:(NSTrackingMouseEnteredAndExited |
280 NSTrackingActiveAlways)
283 [[self controlView] addTrackingArea:trackingAreaButton_.get()];
285 trackingAreaDropdown_.reset([[NSTrackingArea alloc]
286 initWithRect:dropdownRect
287 options:(NSTrackingMouseEnteredAndExited |
288 NSTrackingActiveAlways)
291 [[self controlView] addTrackingArea:trackingAreaDropdown_.get()];
294 - (void)setShowsBorderOnlyWhileMouseInside:(BOOL)showOnly {
295 // Override to make sure it doesn't do anything if it's called accidentally.
298 - (void)mouseEntered:(NSEvent*)theEvent {
300 if ([theEvent trackingArea] == trackingAreaButton_.get())
301 mousePosition_ = kDownloadItemMouseOverButtonPart;
302 else if ([theEvent trackingArea] == trackingAreaDropdown_.get())
303 mousePosition_ = kDownloadItemMouseOverDropdownPart;
304 [[self controlView] setNeedsDisplay:YES];
307 - (void)mouseExited:(NSEvent *)theEvent {
309 if (mouseInsideCount_ == 0)
310 mousePosition_ = kDownloadItemMouseOutside;
311 [[self controlView] setNeedsDisplay:YES];
314 - (BOOL)isMouseInside {
315 return mousePosition_ != kDownloadItemMouseOutside;
318 - (BOOL)isMouseOverButtonPart {
319 return mousePosition_ == kDownloadItemMouseOverButtonPart;
322 - (BOOL)isButtonPartPressed {
323 return [self isHighlighted]
324 && mousePosition_ == kDownloadItemMouseOverButtonPart;
327 - (BOOL)isMouseOverDropdownPart {
328 return mousePosition_ == kDownloadItemMouseOverDropdownPart;
331 - (BOOL)isDropdownPartPressed {
332 return [self isHighlighted]
333 && mousePosition_ == kDownloadItemMouseOverDropdownPart;
336 - (NSBezierPath*)leftRoundedPath:(CGFloat)radius inRect:(NSRect)rect {
338 NSPoint topLeft = NSMakePoint(NSMinX(rect), NSMaxY(rect));
339 NSPoint topRight = NSMakePoint(NSMaxX(rect), NSMaxY(rect));
340 NSPoint bottomRight = NSMakePoint(NSMaxX(rect) , NSMinY(rect));
342 NSBezierPath* path = [NSBezierPath bezierPath];
343 [path moveToPoint:topRight];
344 [path appendBezierPathWithArcFromPoint:topLeft
347 [path appendBezierPathWithArcFromPoint:rect.origin
350 [path lineToPoint:bottomRight];
354 - (NSBezierPath*)rightRoundedPath:(CGFloat)radius inRect:(NSRect)rect {
356 NSPoint topLeft = NSMakePoint(NSMinX(rect), NSMaxY(rect));
357 NSPoint topRight = NSMakePoint(NSMaxX(rect), NSMaxY(rect));
358 NSPoint bottomRight = NSMakePoint(NSMaxX(rect), NSMinY(rect));
360 NSBezierPath* path = [NSBezierPath bezierPath];
361 [path moveToPoint:rect.origin];
362 [path appendBezierPathWithArcFromPoint:bottomRight
365 [path appendBezierPathWithArcFromPoint:topRight
368 [path lineToPoint:topLeft];
372 - (NSString*)elideTitle:(int)availableWidth {
373 return base::SysUTF16ToNSString(gfx::ElideFilename(
374 downloadPath_, gfx::FontList(gfx::Font([self font])), availableWidth));
377 - (NSString*)elideStatus:(int)availableWidth {
378 return base::SysUTF16ToNSString(gfx::ElideText(
379 base::SysNSStringToUTF16([self secondaryTitle]),
380 gfx::FontList(gfx::Font([self secondaryFont])),
385 - (ui::ThemeProvider*)backgroundThemeWrappingProvider:
386 (ui::ThemeProvider*)provider {
387 if (!themeProvider_.get()) {
388 themeProvider_.reset(new BackgroundTheme(provider));
391 return themeProvider_.get();
394 // Returns if |part| was pressed while the default theme was active.
395 - (BOOL)pressedWithDefaultThemeOnPart:(DownloadItemMousePosition)part {
396 return [self isDefaultTheme] && [self isHighlighted] &&
397 mousePosition_ == part;
400 // Returns the text color that should be used to draw text on |part|.
401 - (NSColor*)titleColorForPart:(DownloadItemMousePosition)part {
402 ui::ThemeProvider* themeProvider =
403 [[[self controlView] window] themeProvider];
404 if ([self pressedWithDefaultThemeOnPart:part] || !themeProvider)
405 return [NSColor alternateSelectedControlTextColor];
406 return themeProvider->GetNSColor(ThemeProperties::COLOR_BOOKMARK_TEXT);
409 - (void)drawSecondaryTitleInRect:(NSRect)innerFrame {
410 if (![self secondaryTitle] || statusAlpha_ <= 0)
413 CGFloat textWidth = NSWidth(innerFrame) -
414 (kTextPosLeft + kTextPaddingRight + kDropdownAreaWidth);
415 NSString* secondaryText = [self elideStatus:textWidth];
416 NSColor* secondaryColor =
417 [self titleColorForPart:kDownloadItemMouseOverButtonPart];
419 // If text is light-on-dark, lightening it alone will do nothing.
420 // Therefore we mute luminance a wee bit before drawing in this case.
421 if (![secondaryColor gtm_isDarkColor])
422 secondaryColor = [secondaryColor gtm_colorByAdjustingLuminance:-0.2];
424 NSDictionary* secondaryTextAttributes =
425 [NSDictionary dictionaryWithObjectsAndKeys:
426 secondaryColor, NSForegroundColorAttributeName,
427 [self secondaryFont], NSFontAttributeName,
429 NSPoint secondaryPos =
430 NSMakePoint(innerFrame.origin.x + kTextPosLeft, kSecondaryTextPosTop);
432 gfx::ScopedNSGraphicsContextSaveGState contextSave;
433 NSGraphicsContext* nsContext = [NSGraphicsContext currentContext];
434 CGContextRef cgContext = (CGContextRef)[nsContext graphicsPort];
435 [nsContext setCompositingOperation:NSCompositeSourceOver];
436 CGContextSetAlpha(cgContext, statusAlpha_);
437 [secondaryText drawAtPoint:secondaryPos
438 withAttributes:secondaryTextAttributes];
441 - (BOOL)isDefaultTheme {
442 ui::ThemeProvider* themeProvider =
443 [[[self controlView] window] themeProvider];
446 return !themeProvider->HasCustomImage(IDR_THEME_BUTTON_BACKGROUND);
449 - (void)drawWithFrame:(NSRect)cellFrame inView:(NSView*)controlView {
450 NSRect drawFrame = NSInsetRect(cellFrame, 1.5, 1.5);
451 NSRect innerFrame = NSInsetRect(cellFrame, 2, 2);
453 const float radius = 3;
454 NSWindow* window = [controlView window];
455 BOOL active = [window isKeyWindow] || [window isMainWindow];
457 // In the default theme, draw download items with the bookmark button
458 // gradient. For some themes, this leads to unreadable text, so draw the item
459 // with a background that looks like windows (some transparent white) if a
460 // theme is used. Use custom theme object with a white color gradient to trick
461 // the superclass into drawing what we want.
462 ui::ThemeProvider* themeProvider =
463 [[[self controlView] window] themeProvider];
465 NSGradient* bgGradient = nil;
466 if (![self isDefaultTheme]) {
467 themeProvider = [self backgroundThemeWrappingProvider:themeProvider];
468 bgGradient = themeProvider->GetNSGradient(
469 active ? ThemeProperties::GRADIENT_TOOLBAR_BUTTON :
470 ThemeProperties::GRADIENT_TOOLBAR_BUTTON_INACTIVE);
473 NSRect buttonDrawRect, dropdownDrawRect;
474 NSDivideRect(drawFrame, &dropdownDrawRect, &buttonDrawRect,
475 kDropdownAreaWidth, NSMaxXEdge);
477 NSBezierPath* buttonInnerPath = [self
478 leftRoundedPath:radius inRect:buttonDrawRect];
479 NSBezierPath* dropdownInnerPath = [self
480 rightRoundedPath:radius inRect:dropdownDrawRect];
482 // Draw secondary title, if any. Do this before drawing the (transparent)
483 // fill so that the text becomes a bit lighter. The default theme's "pressed"
484 // gradient is not transparent, so only do this if a theme is active.
485 bool drawStatusOnTop =
486 [self pressedWithDefaultThemeOnPart:kDownloadItemMouseOverButtonPart];
487 if (!drawStatusOnTop)
488 [self drawSecondaryTitleInRect:innerFrame];
490 // Stroke the borders and appropriate fill gradient.
491 [self drawBorderAndFillForTheme:themeProvider
492 controlView:controlView
493 innerPath:buttonInnerPath
494 showClickedGradient:[self isButtonPartPressed]
495 showHighlightGradient:[self isMouseOverButtonPart]
499 defaultGradient:bgGradient];
501 [self drawBorderAndFillForTheme:themeProvider
502 controlView:controlView
503 innerPath:dropdownInnerPath
504 showClickedGradient:[self isDropdownPartPressed]
505 showHighlightGradient:[self isMouseOverDropdownPart]
509 defaultGradient:bgGradient];
511 [self drawInteriorWithFrame:innerFrame inView:controlView];
513 // For the default theme, draw the status text on top of the (opaque) button
516 [self drawSecondaryTitleInRect:innerFrame];
519 - (void)drawInteriorWithFrame:(NSRect)cellFrame inView:(NSView*)controlView {
521 CGFloat textWidth = NSWidth(cellFrame) -
522 (kTextPosLeft + kTextPaddingRight + kDropdownAreaWidth);
523 [self setTitle:[self elideTitle:textWidth]];
525 NSColor* color = [self titleColorForPart:kDownloadItemMouseOverButtonPart];
526 NSString* primaryText = [self title];
528 NSDictionary* primaryTextAttributes =
529 [NSDictionary dictionaryWithObjectsAndKeys:
530 color, NSForegroundColorAttributeName,
531 [self font], NSFontAttributeName,
533 NSPoint primaryPos = NSMakePoint(
534 cellFrame.origin.x + kTextPosLeft,
537 [primaryText drawAtPoint:primaryPos withAttributes:primaryTextAttributes];
539 // Draw progress disk
541 // CanvasSkiaPaint draws its content to the current NSGraphicsContext in its
542 // destructor, which needs to be invoked before the icon is drawn below -
543 // hence this nested block.
545 // Always repaint the whole disk.
546 NSPoint imagePosition = [self imageRectForBounds:cellFrame].origin;
547 int x = imagePosition.x - DownloadShelf::kSmallProgressIconOffset;
548 int y = imagePosition.y - DownloadShelf::kSmallProgressIconOffset;
549 NSRect dirtyRect = NSMakeRect(
551 DownloadShelf::kSmallProgressIconSize,
552 DownloadShelf::kSmallProgressIconSize);
554 gfx::CanvasSkiaPaint canvas(dirtyRect, false);
555 canvas.set_composite_alpha(true);
556 if (completionAnimation_.get()) {
557 if ([completionAnimation_ isAnimating]) {
558 if (percentDone_ == -1) {
559 DownloadShelf::PaintDownloadComplete(
563 [completionAnimation_ currentValue],
564 DownloadShelf::SMALL);
566 DownloadShelf::PaintDownloadInterrupted(
570 [completionAnimation_ currentValue],
571 DownloadShelf::SMALL);
574 } else if (percentDone_ >= 0 || indeterminateProgressTimer_) {
575 DownloadShelf::PaintDownloadProgress(&canvas,
578 indeterminateProgressAngle_,
580 DownloadShelf::SMALL);
585 [[self image] drawInRect:[self imageRectForBounds:cellFrame]
587 operation:NSCompositeSourceOver
588 fraction:[self isEnabled] ? 1.0 : 0.5
592 // Separator between button and popup parts
593 CGFloat lx = NSMaxX(cellFrame) - kDropdownAreaWidth + 0.5;
594 [[NSColor colorWithDeviceWhite:0.0 alpha:0.1] set];
595 [NSBezierPath strokeLineFromPoint:NSMakePoint(lx, NSMinY(cellFrame) + 1)
596 toPoint:NSMakePoint(lx, NSMaxY(cellFrame) - 1)];
597 [[NSColor colorWithDeviceWhite:1.0 alpha:0.1] set];
598 [NSBezierPath strokeLineFromPoint:NSMakePoint(lx + 1, NSMinY(cellFrame) + 1)
599 toPoint:NSMakePoint(lx + 1, NSMaxY(cellFrame) - 1)];
601 // Popup arrow. Put center of mass of the arrow in the center of the
603 CGFloat cx = NSMaxX(cellFrame) - kDropdownAreaWidth/2 + 0.5;
604 CGFloat cy = NSMidY(cellFrame);
605 NSPoint p1 = NSMakePoint(cx - kDropdownArrowWidth/2,
606 cy - kDropdownArrowHeight/3 + kDropdownAreaY);
607 NSPoint p2 = NSMakePoint(cx + kDropdownArrowWidth/2,
608 cy - kDropdownArrowHeight/3 + kDropdownAreaY);
609 NSPoint p3 = NSMakePoint(cx, cy + kDropdownArrowHeight*2/3 + kDropdownAreaY);
610 NSBezierPath *triangle = [NSBezierPath bezierPath];
611 [triangle moveToPoint:p1];
612 [triangle lineToPoint:p2];
613 [triangle lineToPoint:p3];
614 [triangle closePath];
616 gfx::ScopedNSGraphicsContextSaveGState scopedGState;
618 base::scoped_nsobject<NSShadow> shadow([[NSShadow alloc] init]);
619 [shadow.get() setShadowColor:[NSColor whiteColor]];
620 [shadow.get() setShadowOffset:NSMakeSize(0, -1)];
621 [shadow setShadowBlurRadius:0.0];
624 NSColor* fill = [self titleColorForPart:kDownloadItemMouseOverDropdownPart];
630 - (NSRect)imageRectForBounds:(NSRect)cellFrame {
631 return NSMakeRect(cellFrame.origin.x + kImagePaddingLeft,
632 cellFrame.origin.y + kImagePaddingTop,
637 - (void)setupToggleStatusVisibilityAnimation {
638 if (toggleStatusVisibilityAnimation_ &&
639 [toggleStatusVisibilityAnimation_ isAnimating]) {
640 // If the animation is running, cancel the animation and show/hide the
641 // status text immediately.
642 [toggleStatusVisibilityAnimation_ stopAnimation];
643 [self animation:toggleStatusVisibilityAnimation_ progressed:1.0];
644 toggleStatusVisibilityAnimation_.reset();
646 // Don't use core animation -- text in CA layers is not subpixel antialiased
647 toggleStatusVisibilityAnimation_.reset([[DownloadItemCellAnimation alloc]
648 initWithDownloadItemCell:self
649 duration:kShowStatusDuration
650 animationCurve:NSAnimationEaseIn]);
651 [toggleStatusVisibilityAnimation_.get() setDelegate:self];
652 [toggleStatusVisibilityAnimation_.get() startAnimation];
656 - (void)showSecondaryTitle {
657 if (isStatusTextVisible_)
659 isStatusTextVisible_ = YES;
660 [self setupToggleStatusVisibilityAnimation];
663 - (void)hideSecondaryTitle {
664 if (!isStatusTextVisible_)
666 isStatusTextVisible_ = NO;
667 [self setupToggleStatusVisibilityAnimation];
670 - (IndeterminateProgressTimer*)indeterminateProgressTimer {
671 return indeterminateProgressTimer_;
674 - (void)animation:(NSAnimation*)animation
675 progressed:(NSAnimationProgress)progress {
676 if (animation == toggleStatusVisibilityAnimation_) {
677 if (isStatusTextVisible_) {
678 titleY_ = (1 - progress)*kPrimaryTextOnlyPosTop + kPrimaryTextPosTop;
679 statusAlpha_ = progress;
681 titleY_ = progress*kPrimaryTextOnlyPosTop +
682 (1 - progress)*kPrimaryTextPosTop;
683 statusAlpha_ = 1 - progress;
685 [[self controlView] setNeedsDisplay:YES];
686 } else if (animation == completionAnimation_) {
687 [[self controlView] setNeedsDisplay:YES];
691 - (void)updateIndeterminateDownload {
692 indeterminateProgressAngle_ =
693 (indeterminateProgressAngle_ + DownloadShelf::kUnknownIncrementDegrees) %
694 DownloadShelf::kMaxDegrees;
695 [[self controlView] setNeedsDisplay:YES];
698 - (void)stopIndeterminateAnimation {
699 [indeterminateProgressTimer_ invalidate];
700 indeterminateProgressTimer_.reset();
703 - (void)animationDidEnd:(NSAnimation *)animation {
704 if (animation == toggleStatusVisibilityAnimation_)
705 toggleStatusVisibilityAnimation_.reset();
706 else if (animation == completionAnimation_)
707 completionAnimation_.reset();
710 - (BOOL)isStatusTextVisible {
711 return isStatusTextVisible_;
714 - (CGFloat)statusTextAlpha {
718 - (void)skipVisibilityAnimation {
719 [toggleStatusVisibilityAnimation_ setCurrentProgress:1.0];
724 @implementation DownloadItemCellAnimation
726 - (id)initWithDownloadItemCell:(DownloadItemCell*)cell
727 duration:(NSTimeInterval)duration
728 animationCurve:(NSAnimationCurve)animationCurve {
729 if ((self = [super gtm_initWithDuration:duration
730 eventMask:NSLeftMouseDownMask
731 animationCurve:animationCurve])) {
733 [self setAnimationBlockingMode:NSAnimationNonblocking];
738 - (void)setCurrentProgress:(NSAnimationProgress)progress {
739 [super setCurrentProgress:progress];
740 [cell_ animation:self progressed:progress];
745 @implementation IndeterminateProgressTimer
747 - (id)initWithDownloadItemCell:(DownloadItemCell*)cell {
748 if ((self = [super init])) {
750 timer_.reset([[NSTimer
751 scheduledTimerWithTimeInterval:DownloadShelf::kProgressRateMs / 1000.0
753 selector:@selector(onTimer:)
755 repeats:YES] retain]);
764 - (void)onTimer:(NSTimer*)timer {
765 [cell_ updateIndeterminateDownload];