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"
8 #include "base/strings/sys_string_conversions.h"
9 #include "chrome/browser/download/download_item_model.h"
10 #include "chrome/browser/download/download_shelf.h"
11 #import "chrome/browser/themes/theme_properties.h"
12 #import "chrome/browser/ui/cocoa/download/background_theme.h"
13 #import "chrome/browser/ui/cocoa/themed_window.h"
14 #include "content/public/browser/download_item.h"
15 #include "content/public/browser/download_manager.h"
16 #include "grit/theme_resources.h"
17 #import "third_party/google_toolbox_for_mac/src/AppKit/GTMNSAnimation+Duration.h"
18 #import "third_party/google_toolbox_for_mac/src/AppKit/GTMNSColor+Luminance.h"
19 #include "ui/base/default_theme_provider.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"
23 #include "ui/gfx/text_elider.h"
24 #include "ui/native_theme/native_theme.h"
26 // Distance from top border to icon.
27 const CGFloat kImagePaddingTop = 7;
29 // Distance from left border to icon.
30 const CGFloat kImagePaddingLeft = 9;
33 const CGFloat kImageWidth = 16;
36 const CGFloat kImageHeight = 16;
38 // x coordinate of download name string, in view coords.
39 const CGFloat kTextPosLeft = kImagePaddingLeft +
40 kImageWidth + DownloadShelf::kFiletypeIconOffset;
42 // Distance from end of download name string to dropdown area.
43 const CGFloat kTextPaddingRight = 3;
45 // y coordinate of download name string, in view coords, when status message
47 const CGFloat kPrimaryTextPosTop = 3;
49 // y coordinate of download name string, in view coords, when status message
51 const CGFloat kPrimaryTextOnlyPosTop = 10;
53 // y coordinate of status message, in view coords.
54 const CGFloat kSecondaryTextPosTop = 18;
56 // Width of dropdown area on the right (includes 1px for the border on each
58 const CGFloat kDropdownAreaWidth = 14;
60 // Width of dropdown arrow.
61 const CGFloat kDropdownArrowWidth = 5;
63 // Height of dropdown arrow.
64 const CGFloat kDropdownArrowHeight = 3;
66 // Vertical displacement of dropdown area, relative to the "centered" position.
67 const CGFloat kDropdownAreaY = -2;
69 // Duration of the two-lines-to-one-line animation, in seconds.
70 NSTimeInterval kShowStatusDuration = 0.3;
71 NSTimeInterval kHideStatusDuration = 0.3;
73 // Duration of the 'download complete' animation, in seconds.
74 const CGFloat kCompleteAnimationDuration = 2.5;
76 // Duration of the 'download interrupted' animation, in seconds.
77 const CGFloat kInterruptedAnimationDuration = 2.5;
79 using content::DownloadItem;
81 // This is a helper class to animate the fading out of the status text.
82 @interface DownloadItemCellAnimation : NSAnimation {
84 DownloadItemCell* cell_;
86 - (id)initWithDownloadItemCell:(DownloadItemCell*)cell
87 duration:(NSTimeInterval)duration
88 animationCurve:(NSAnimationCurve)animationCurve;
92 // Timer used to animate indeterminate progress. An NSTimer retains its target.
93 // This means that the target must explicitly invalidate the timer before it
94 // can be deleted. This class keeps a weak reference to the target so the
95 // timer can be invalidated from the destructor.
96 @interface IndeterminateProgressTimer : NSObject {
98 DownloadItemCell* cell_;
99 base::scoped_nsobject<NSTimer> timer_;
102 - (id)initWithDownloadItemCell:(DownloadItemCell*)cell;
107 @interface DownloadItemCell(Private)
108 - (void)updateTrackingAreas:(id)sender;
109 - (void)setupToggleStatusVisibilityAnimation;
110 - (void)showSecondaryTitle;
111 - (void)hideSecondaryTitle;
112 - (void)animation:(NSAnimation*)animation
113 progressed:(NSAnimationProgress)progress;
114 - (void)updateIndeterminateDownload;
115 - (void)stopIndeterminateAnimation;
116 - (NSString*)elideTitle:(int)availableWidth;
117 - (NSString*)elideStatus:(int)availableWidth;
118 - (ui::ThemeProvider*)backgroundThemeWrappingProvider:
119 (ui::ThemeProvider*)provider;
120 - (BOOL)pressedWithDefaultThemeOnPart:(DownloadItemMousePosition)part;
121 - (NSColor*)titleColorForPart:(DownloadItemMousePosition)part;
122 - (void)drawSecondaryTitleInRect:(NSRect)innerFrame;
123 - (BOOL)isDefaultTheme;
126 @implementation DownloadItemCell
128 @synthesize secondaryTitle = secondaryTitle_;
129 @synthesize secondaryFont = secondaryFont_;
131 - (void)setInitialState {
132 isStatusTextVisible_ = NO;
133 titleY_ = kPrimaryTextOnlyPosTop;
136 [self setFont:[NSFont systemFontOfSize:
137 [NSFont systemFontSizeForControlSize:NSSmallControlSize]]];
138 [self setSecondaryFont:[NSFont systemFontOfSize:
139 [NSFont systemFontSizeForControlSize:NSSmallControlSize]]];
141 [self updateTrackingAreas:self];
142 [[NSNotificationCenter defaultCenter]
144 selector:@selector(updateTrackingAreas:)
145 name:NSViewFrameDidChangeNotification
146 object:[self controlView]];
149 // For nib instantiations
150 - (id)initWithCoder:(NSCoder*)decoder {
151 if ((self = [super initWithCoder:decoder])) {
152 [self setInitialState];
157 // For programmatic instantiations.
158 - (id)initTextCell:(NSString *)string {
159 if ((self = [super initTextCell:string])) {
160 [self setInitialState];
166 [[NSNotificationCenter defaultCenter] removeObserver:self];
167 if ([completionAnimation_ isAnimating])
168 [completionAnimation_ stopAnimation];
169 if ([toggleStatusVisibilityAnimation_ isAnimating])
170 [toggleStatusVisibilityAnimation_ stopAnimation];
171 if (trackingAreaButton_) {
172 [[self controlView] removeTrackingArea:trackingAreaButton_];
173 trackingAreaButton_.reset();
175 if (trackingAreaDropdown_) {
176 [[self controlView] removeTrackingArea:trackingAreaDropdown_];
177 trackingAreaDropdown_.reset();
179 [self stopIndeterminateAnimation];
180 [secondaryTitle_ release];
181 [secondaryFont_ release];
185 - (void)setStateFromDownload:(DownloadItemModel*)downloadModel {
186 // Set the name of the download.
187 downloadPath_ = downloadModel->download()->GetFileNameToReportUser();
189 base::string16 statusText = downloadModel->GetStatusText();
190 if (statusText.empty()) {
191 // Remove the status text label.
192 [self hideSecondaryTitle];
195 NSString* statusString = base::SysUTF16ToNSString(statusText);
196 [self setSecondaryTitle:statusString];
197 [self showSecondaryTitle];
200 switch (downloadModel->download()->GetState()) {
201 case DownloadItem::COMPLETE:
202 // Small downloads may start in a complete state due to asynchronous
203 // notifications. In this case, we'll get a second complete notification
204 // via the observers, so we ignore it and avoid creating a second complete
206 if (completionAnimation_.get())
208 completionAnimation_.reset([[DownloadItemCellAnimation alloc]
209 initWithDownloadItemCell:self
210 duration:kCompleteAnimationDuration
211 animationCurve:NSAnimationLinear]);
212 [completionAnimation_.get() setDelegate:self];
213 [completionAnimation_.get() startAnimation];
215 [self stopIndeterminateAnimation];
217 case DownloadItem::CANCELLED:
219 [self stopIndeterminateAnimation];
221 case DownloadItem::INTERRUPTED:
222 // Small downloads may start in an interrupted state due to asynchronous
223 // notifications. In this case, we'll get a second complete notification
224 // via the observers, so we ignore it and avoid creating a second complete
226 if (completionAnimation_.get())
228 completionAnimation_.reset([[DownloadItemCellAnimation alloc]
229 initWithDownloadItemCell:self
230 duration:kInterruptedAnimationDuration
231 animationCurve:NSAnimationLinear]);
232 [completionAnimation_.get() setDelegate:self];
233 [completionAnimation_.get() startAnimation];
235 [self stopIndeterminateAnimation];
237 case DownloadItem::IN_PROGRESS:
238 if (downloadModel->download()->IsPaused()) {
240 [self stopIndeterminateAnimation];
241 } else if (downloadModel->PercentComplete() == -1) {
243 if (!indeterminateProgressTimer_) {
244 indeterminateProgressTimer_.reset([[IndeterminateProgressTimer alloc]
245 initWithDownloadItemCell:self]);
246 progressStartTime_ = base::TimeTicks::Now();
249 percentDone_ = downloadModel->PercentComplete();
250 [self stopIndeterminateAnimation];
257 [[self controlView] setNeedsDisplay:YES];
260 - (void)updateTrackingAreas:(id)sender {
261 if (trackingAreaButton_) {
262 [[self controlView] removeTrackingArea:trackingAreaButton_.get()];
263 trackingAreaButton_.reset(nil);
265 if (trackingAreaDropdown_) {
266 [[self controlView] removeTrackingArea:trackingAreaDropdown_.get()];
267 trackingAreaDropdown_.reset(nil);
270 // Use two distinct tracking rects for left and right parts.
271 // The tracking areas are also used to decide how to handle clicks. They must
272 // always be active, so the click is handled correctly when a download item
273 // is clicked while chrome is not the active app ( http://crbug.com/21916 ).
274 NSRect bounds = [[self controlView] bounds];
275 NSRect buttonRect, dropdownRect;
276 NSDivideRect(bounds, &dropdownRect, &buttonRect,
277 kDropdownAreaWidth, NSMaxXEdge);
279 trackingAreaButton_.reset([[NSTrackingArea alloc]
280 initWithRect:buttonRect
281 options:(NSTrackingMouseEnteredAndExited |
282 NSTrackingActiveAlways)
285 [[self controlView] addTrackingArea:trackingAreaButton_.get()];
287 trackingAreaDropdown_.reset([[NSTrackingArea alloc]
288 initWithRect:dropdownRect
289 options:(NSTrackingMouseEnteredAndExited |
290 NSTrackingActiveAlways)
293 [[self controlView] addTrackingArea:trackingAreaDropdown_.get()];
296 - (void)setShowsBorderOnlyWhileMouseInside:(BOOL)showOnly {
297 // Override to make sure it doesn't do anything if it's called accidentally.
300 - (void)mouseEntered:(NSEvent*)theEvent {
302 if ([theEvent trackingArea] == trackingAreaButton_.get())
303 mousePosition_ = kDownloadItemMouseOverButtonPart;
304 else if ([theEvent trackingArea] == trackingAreaDropdown_.get())
305 mousePosition_ = kDownloadItemMouseOverDropdownPart;
306 [[self controlView] setNeedsDisplay:YES];
309 - (void)mouseExited:(NSEvent *)theEvent {
311 if (mouseInsideCount_ == 0)
312 mousePosition_ = kDownloadItemMouseOutside;
313 [[self controlView] setNeedsDisplay:YES];
316 - (BOOL)isMouseInside {
317 return mousePosition_ != kDownloadItemMouseOutside;
320 - (BOOL)isMouseOverButtonPart {
321 return mousePosition_ == kDownloadItemMouseOverButtonPart;
324 - (BOOL)isButtonPartPressed {
325 return [self isHighlighted]
326 && mousePosition_ == kDownloadItemMouseOverButtonPart;
329 - (BOOL)isMouseOverDropdownPart {
330 return mousePosition_ == kDownloadItemMouseOverDropdownPart;
333 - (BOOL)isDropdownPartPressed {
334 return [self isHighlighted]
335 && mousePosition_ == kDownloadItemMouseOverDropdownPart;
338 - (NSBezierPath*)leftRoundedPath:(CGFloat)radius inRect:(NSRect)rect {
340 NSPoint topLeft = NSMakePoint(NSMinX(rect), NSMaxY(rect));
341 NSPoint topRight = NSMakePoint(NSMaxX(rect), NSMaxY(rect));
342 NSPoint bottomRight = NSMakePoint(NSMaxX(rect) , NSMinY(rect));
344 NSBezierPath* path = [NSBezierPath bezierPath];
345 [path moveToPoint:topRight];
346 [path appendBezierPathWithArcFromPoint:topLeft
349 [path appendBezierPathWithArcFromPoint:rect.origin
352 [path lineToPoint:bottomRight];
356 - (NSBezierPath*)rightRoundedPath:(CGFloat)radius inRect:(NSRect)rect {
358 NSPoint topLeft = NSMakePoint(NSMinX(rect), NSMaxY(rect));
359 NSPoint topRight = NSMakePoint(NSMaxX(rect), NSMaxY(rect));
360 NSPoint bottomRight = NSMakePoint(NSMaxX(rect), NSMinY(rect));
362 NSBezierPath* path = [NSBezierPath bezierPath];
363 [path moveToPoint:rect.origin];
364 [path appendBezierPathWithArcFromPoint:bottomRight
367 [path appendBezierPathWithArcFromPoint:topRight
370 [path lineToPoint:topLeft];
374 - (NSString*)elideTitle:(int)availableWidth {
375 return base::SysUTF16ToNSString(gfx::ElideFilename(
376 downloadPath_, gfx::FontList(gfx::Font([self font])), availableWidth));
379 - (NSString*)elideStatus:(int)availableWidth {
380 return base::SysUTF16ToNSString(gfx::ElideText(
381 base::SysNSStringToUTF16([self secondaryTitle]),
382 gfx::FontList(gfx::Font([self secondaryFont])),
383 availableWidth, gfx::ELIDE_TAIL));
386 - (ui::ThemeProvider*)backgroundThemeWrappingProvider:
387 (ui::ThemeProvider*)provider {
388 if (!themeProvider_.get()) {
389 themeProvider_.reset(new BackgroundTheme(provider));
392 return themeProvider_.get();
395 // Returns if |part| was pressed while the default theme was active.
396 - (BOOL)pressedWithDefaultThemeOnPart:(DownloadItemMousePosition)part {
397 return [self isDefaultTheme] && [self isHighlighted] &&
398 mousePosition_ == part;
401 // Returns the text color that should be used to draw text on |part|.
402 - (NSColor*)titleColorForPart:(DownloadItemMousePosition)part {
403 ui::ThemeProvider* themeProvider =
404 [[[self controlView] window] themeProvider];
405 if ([self pressedWithDefaultThemeOnPart:part] || !themeProvider)
406 return [NSColor alternateSelectedControlTextColor];
407 return themeProvider->GetNSColor(ThemeProperties::COLOR_BOOKMARK_TEXT);
410 - (void)drawSecondaryTitleInRect:(NSRect)innerFrame {
411 if (![self secondaryTitle] || statusAlpha_ <= 0)
414 CGFloat textWidth = NSWidth(innerFrame) -
415 (kTextPosLeft + kTextPaddingRight + kDropdownAreaWidth);
416 NSString* secondaryText = [self elideStatus:textWidth];
417 NSColor* secondaryColor =
418 [self titleColorForPart:kDownloadItemMouseOverButtonPart];
420 // If text is light-on-dark, lightening it alone will do nothing.
421 // Therefore we mute luminance a wee bit before drawing in this case.
422 if (![secondaryColor gtm_isDarkColor])
423 secondaryColor = [secondaryColor gtm_colorByAdjustingLuminance:-0.2];
425 NSDictionary* secondaryTextAttributes =
426 [NSDictionary dictionaryWithObjectsAndKeys:
427 secondaryColor, NSForegroundColorAttributeName,
428 [self secondaryFont], NSFontAttributeName,
430 NSPoint secondaryPos =
431 NSMakePoint(innerFrame.origin.x + kTextPosLeft, kSecondaryTextPosTop);
433 gfx::ScopedNSGraphicsContextSaveGState contextSave;
434 NSGraphicsContext* nsContext = [NSGraphicsContext currentContext];
435 CGContextRef cgContext = (CGContextRef)[nsContext graphicsPort];
436 [nsContext setCompositingOperation:NSCompositeSourceOver];
437 CGContextSetAlpha(cgContext, statusAlpha_);
438 [secondaryText drawAtPoint:secondaryPos
439 withAttributes:secondaryTextAttributes];
442 - (BOOL)isDefaultTheme {
443 ui::ThemeProvider* themeProvider =
444 [[[self controlView] window] themeProvider];
447 return !themeProvider->HasCustomImage(IDR_THEME_BUTTON_BACKGROUND);
450 - (void)drawWithFrame:(NSRect)cellFrame inView:(NSView*)controlView {
451 NSRect drawFrame = NSInsetRect(cellFrame, 1.5, 1.5);
452 NSRect innerFrame = NSInsetRect(cellFrame, 2, 2);
454 const float radius = 3;
455 NSWindow* window = [controlView window];
456 BOOL active = [window isKeyWindow] || [window isMainWindow];
458 // In the default theme, draw download items with the bookmark button
459 // gradient. For some themes, this leads to unreadable text, so draw the item
460 // with a background that looks like windows (some transparent white) if a
461 // theme is used. Use custom theme object with a white color gradient to trick
462 // the superclass into drawing what we want.
463 ui::ThemeProvider* themeProvider =
464 [[[self controlView] window] themeProvider];
466 NSGradient* bgGradient = nil;
467 if (![self isDefaultTheme]) {
468 themeProvider = [self backgroundThemeWrappingProvider:themeProvider];
469 bgGradient = themeProvider->GetNSGradient(
470 active ? ThemeProperties::GRADIENT_TOOLBAR_BUTTON :
471 ThemeProperties::GRADIENT_TOOLBAR_BUTTON_INACTIVE);
474 NSRect buttonDrawRect, dropdownDrawRect;
475 NSDivideRect(drawFrame, &dropdownDrawRect, &buttonDrawRect,
476 kDropdownAreaWidth, NSMaxXEdge);
478 NSBezierPath* buttonInnerPath = [self
479 leftRoundedPath:radius inRect:buttonDrawRect];
480 NSBezierPath* dropdownInnerPath = [self
481 rightRoundedPath:radius inRect:dropdownDrawRect];
483 // Draw secondary title, if any. Do this before drawing the (transparent)
484 // fill so that the text becomes a bit lighter. The default theme's "pressed"
485 // gradient is not transparent, so only do this if a theme is active.
486 bool drawStatusOnTop =
487 [self pressedWithDefaultThemeOnPart:kDownloadItemMouseOverButtonPart];
488 if (!drawStatusOnTop)
489 [self drawSecondaryTitleInRect:innerFrame];
491 // Stroke the borders and appropriate fill gradient.
492 [self drawBorderAndFillForTheme:themeProvider
493 controlView:controlView
494 innerPath:buttonInnerPath
495 showClickedGradient:[self isButtonPartPressed]
496 showHighlightGradient:[self isMouseOverButtonPart]
500 defaultGradient:bgGradient];
502 [self drawBorderAndFillForTheme:themeProvider
503 controlView:controlView
504 innerPath:dropdownInnerPath
505 showClickedGradient:[self isDropdownPartPressed]
506 showHighlightGradient:[self isMouseOverDropdownPart]
510 defaultGradient:bgGradient];
512 [self drawInteriorWithFrame:innerFrame inView:controlView];
514 // For the default theme, draw the status text on top of the (opaque) button
517 [self drawSecondaryTitleInRect:innerFrame];
520 - (void)drawInteriorWithFrame:(NSRect)cellFrame inView:(NSView*)controlView {
522 CGFloat textWidth = NSWidth(cellFrame) -
523 (kTextPosLeft + kTextPaddingRight + kDropdownAreaWidth);
524 [self setTitle:[self elideTitle:textWidth]];
526 NSColor* color = [self titleColorForPart:kDownloadItemMouseOverButtonPart];
527 NSString* primaryText = [self title];
529 NSDictionary* primaryTextAttributes =
530 [NSDictionary dictionaryWithObjectsAndKeys:
531 color, NSForegroundColorAttributeName,
532 [self font], NSFontAttributeName,
534 NSPoint primaryPos = NSMakePoint(
535 cellFrame.origin.x + kTextPosLeft,
538 [primaryText drawAtPoint:primaryPos withAttributes:primaryTextAttributes];
540 // Draw progress disk
542 // CanvasSkiaPaint draws its content to the current NSGraphicsContext in its
543 // destructor, which needs to be invoked before the icon is drawn below -
544 // hence this nested block.
546 // Always repaint the whole disk.
547 NSPoint imagePosition = [self imageRectForBounds:cellFrame].origin;
548 int x = imagePosition.x - DownloadShelf::kFiletypeIconOffset;
549 int y = imagePosition.y - DownloadShelf::kFiletypeIconOffset;
550 NSRect dirtyRect = NSMakeRect(
552 DownloadShelf::kProgressIndicatorSize,
553 DownloadShelf::kProgressIndicatorSize);
555 gfx::CanvasSkiaPaint canvas(dirtyRect, false);
556 canvas.set_composite_alpha(true);
557 canvas.Translate(gfx::Vector2d(x, y));
559 ui::ThemeProvider* themeProvider =
560 [[[self controlView] window] themeProvider];
561 ui::DefaultThemeProvider defaultTheme;
563 themeProvider = &defaultTheme;
565 if (completionAnimation_.get()) {
566 if ([completionAnimation_ isAnimating]) {
567 if (percentDone_ == -1) {
568 DownloadShelf::PaintDownloadComplete(
569 &canvas, *themeProvider,
570 [completionAnimation_ currentValue]);
572 DownloadShelf::PaintDownloadInterrupted(
573 &canvas, *themeProvider,
574 [completionAnimation_ currentValue]);
577 } else if (percentDone_ >= 0 || indeterminateProgressTimer_) {
578 DownloadShelf::PaintDownloadProgress(
579 &canvas, *themeProvider,
580 base::TimeTicks::Now() - progressStartTime_, percentDone_);
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 [[self controlView] setNeedsDisplay:YES];
695 - (void)stopIndeterminateAnimation {
696 [indeterminateProgressTimer_ invalidate];
697 indeterminateProgressTimer_.reset();
700 - (void)animationDidEnd:(NSAnimation *)animation {
701 if (animation == toggleStatusVisibilityAnimation_)
702 toggleStatusVisibilityAnimation_.reset();
703 else if (animation == completionAnimation_)
704 completionAnimation_.reset();
707 - (BOOL)isStatusTextVisible {
708 return isStatusTextVisible_;
711 - (CGFloat)statusTextAlpha {
719 - (void)skipVisibilityAnimation {
720 [toggleStatusVisibilityAnimation_ setCurrentProgress:1.0];
725 @implementation DownloadItemCellAnimation
727 - (id)initWithDownloadItemCell:(DownloadItemCell*)cell
728 duration:(NSTimeInterval)duration
729 animationCurve:(NSAnimationCurve)animationCurve {
730 if ((self = [super gtm_initWithDuration:duration
731 eventMask:NSLeftMouseDownMask
732 animationCurve:animationCurve])) {
734 [self setAnimationBlockingMode:NSAnimationNonblocking];
739 - (void)setCurrentProgress:(NSAnimationProgress)progress {
740 [super setCurrentProgress:progress];
741 [cell_ animation:self progressed:progress];
746 @implementation IndeterminateProgressTimer
748 - (id)initWithDownloadItemCell:(DownloadItemCell*)cell {
749 if ((self = [super init])) {
751 timer_.reset([[NSTimer
752 scheduledTimerWithTimeInterval:DownloadShelf::kProgressRateMs / 1000.0
754 selector:@selector(onTimer:)
756 repeats:YES] retain]);
765 - (void)onTimer:(NSTimer*)timer {
766 [cell_ updateIndeterminateDownload];