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/GTM/AppKit/GTMNSAnimation+Duration.h"
17 #import "third_party/GTM/AppKit/GTMNSColor+Luminance.h"
18 #include "ui/base/l10n/l10n_util.h"
19 #include "ui/base/text/text_elider.h"
20 #include "ui/gfx/canvas_skia_paint.h"
21 #include "ui/gfx/font.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 // Grey value of status text.
55 const CGFloat kSecondaryTextColor = 0.5;
57 // Width of dropdown area on the right (includes 1px for the border on each
59 const CGFloat kDropdownAreaWidth = 14;
61 // Width of dropdown arrow.
62 const CGFloat kDropdownArrowWidth = 5;
64 // Height of dropdown arrow.
65 const CGFloat kDropdownArrowHeight = 3;
67 // Vertical displacement of dropdown area, relative to the "centered" position.
68 const CGFloat kDropdownAreaY = -2;
70 // Duration of the two-lines-to-one-line animation, in seconds.
71 NSTimeInterval kShowStatusDuration = 0.3;
72 NSTimeInterval kHideStatusDuration = 0.3;
74 // Duration of the 'download complete' animation, in seconds.
75 const CGFloat kCompleteAnimationDuration = 2.5;
77 // Duration of the 'download interrupted' animation, in seconds.
78 const CGFloat kInterruptedAnimationDuration = 2.5;
80 using content::DownloadItem;
82 // This is a helper class to animate the fading out of the status text.
83 @interface DownloadItemCellAnimation : NSAnimation {
85 DownloadItemCell* cell_;
87 - (id)initWithDownloadItemCell:(DownloadItemCell*)cell
88 duration:(NSTimeInterval)duration
89 animationCurve:(NSAnimationCurve)animationCurve;
93 // Timer used to animate indeterminate progress. An NSTimer retains its target.
94 // This means that the target must explicitly invalidate the timer before it
95 // can be deleted. This class keeps a weak reference to the target so the
96 // timer can be invalidated from the destructor.
97 @interface IndeterminateProgressTimer : NSObject {
99 DownloadItemCell* cell_;
100 base::scoped_nsobject<NSTimer> timer_;
103 - (id)initWithDownloadItemCell:(DownloadItemCell*)cell;
108 @interface DownloadItemCell(Private)
109 - (void)updateTrackingAreas:(id)sender;
110 - (void)setupToggleStatusVisibilityAnimation;
111 - (void)showSecondaryTitle;
112 - (void)hideSecondaryTitle;
113 - (void)animation:(NSAnimation*)animation
114 progressed:(NSAnimationProgress)progress;
115 - (void)updateIndeterminateDownload;
116 - (void)stopIndeterminateAnimation;
117 - (NSString*)elideTitle:(int)availableWidth;
118 - (NSString*)elideStatus:(int)availableWidth;
119 - (ui::ThemeProvider*)backgroundThemeWrappingProvider:
120 (ui::ThemeProvider*)provider;
121 - (BOOL)pressedWithDefaultThemeOnPart:(DownloadItemMousePosition)part;
122 - (NSColor*)titleColorForPart:(DownloadItemMousePosition)part;
123 - (void)drawSecondaryTitleInRect:(NSRect)innerFrame;
124 - (BOOL)isDefaultTheme;
127 @implementation DownloadItemCell
129 @synthesize secondaryTitle = secondaryTitle_;
130 @synthesize secondaryFont = secondaryFont_;
132 - (void)setInitialState {
133 isStatusTextVisible_ = NO;
134 titleY_ = kPrimaryTextPosTop;
136 indeterminateProgressAngle_ = DownloadShelf::kStartAngleDegrees;
138 [self setFont:[NSFont systemFontOfSize:
139 [NSFont systemFontSizeForControlSize:NSSmallControlSize]]];
140 [self setSecondaryFont:[NSFont systemFontOfSize:
141 [NSFont systemFontSizeForControlSize:NSSmallControlSize]]];
143 [self updateTrackingAreas:self];
144 [[NSNotificationCenter defaultCenter]
146 selector:@selector(updateTrackingAreas:)
147 name:NSViewFrameDidChangeNotification
148 object:[self controlView]];
151 // For nib instantiations
152 - (id)initWithCoder:(NSCoder*)decoder {
153 if ((self = [super initWithCoder:decoder])) {
154 [self setInitialState];
159 // For programmatic instantiations.
160 - (id)initTextCell:(NSString *)string {
161 if ((self = [super initTextCell:string])) {
162 [self setInitialState];
168 [[NSNotificationCenter defaultCenter] removeObserver:self];
169 if ([completionAnimation_ isAnimating])
170 [completionAnimation_ stopAnimation];
171 if ([toggleStatusVisibilityAnimation_ isAnimating])
172 [toggleStatusVisibilityAnimation_ stopAnimation];
173 if (trackingAreaButton_) {
174 [[self controlView] removeTrackingArea:trackingAreaButton_];
175 trackingAreaButton_.reset();
177 if (trackingAreaDropdown_) {
178 [[self controlView] removeTrackingArea:trackingAreaDropdown_];
179 trackingAreaDropdown_.reset();
181 [self stopIndeterminateAnimation];
182 [secondaryTitle_ release];
183 [secondaryFont_ release];
187 - (void)setStateFromDownload:(DownloadItemModel*)downloadModel {
188 // Set the name of the download.
189 downloadPath_ = downloadModel->download()->GetFileNameToReportUser();
191 string16 statusText = downloadModel->GetStatusText();
192 if (statusText.empty()) {
193 // Remove the status text label.
194 [self hideSecondaryTitle];
197 NSString* statusString = base::SysUTF16ToNSString(statusText);
198 [self setSecondaryTitle:statusString];
199 [self showSecondaryTitle];
202 switch (downloadModel->download()->GetState()) {
203 case DownloadItem::COMPLETE:
204 // Small downloads may start in a complete state due to asynchronous
205 // notifications. In this case, we'll get a second complete notification
206 // via the observers, so we ignore it and avoid creating a second complete
208 if (completionAnimation_.get())
210 completionAnimation_.reset([[DownloadItemCellAnimation alloc]
211 initWithDownloadItemCell:self
212 duration:kCompleteAnimationDuration
213 animationCurve:NSAnimationLinear]);
214 [completionAnimation_.get() setDelegate:self];
215 [completionAnimation_.get() startAnimation];
217 [self stopIndeterminateAnimation];
219 case DownloadItem::CANCELLED:
221 [self stopIndeterminateAnimation];
223 case DownloadItem::INTERRUPTED:
224 // Small downloads may start in an interrupted state due to asynchronous
225 // notifications. In this case, we'll get a second complete notification
226 // via the observers, so we ignore it and avoid creating a second complete
228 if (completionAnimation_.get())
230 completionAnimation_.reset([[DownloadItemCellAnimation alloc]
231 initWithDownloadItemCell:self
232 duration:kInterruptedAnimationDuration
233 animationCurve:NSAnimationLinear]);
234 [completionAnimation_.get() setDelegate:self];
235 [completionAnimation_.get() startAnimation];
237 [self stopIndeterminateAnimation];
239 case DownloadItem::IN_PROGRESS:
240 if (downloadModel->download()->IsPaused()) {
242 [self stopIndeterminateAnimation];
243 } else if (downloadModel->PercentComplete() == -1) {
245 if (!indeterminateProgressTimer_) {
246 indeterminateProgressTimer_.reset([[IndeterminateProgressTimer alloc]
247 initWithDownloadItemCell:self]);
250 percentDone_ = downloadModel->PercentComplete();
251 [self stopIndeterminateAnimation];
258 [[self controlView] setNeedsDisplay:YES];
261 - (void)updateTrackingAreas:(id)sender {
262 if (trackingAreaButton_) {
263 [[self controlView] removeTrackingArea:trackingAreaButton_.get()];
264 trackingAreaButton_.reset(nil);
266 if (trackingAreaDropdown_) {
267 [[self controlView] removeTrackingArea:trackingAreaDropdown_.get()];
268 trackingAreaDropdown_.reset(nil);
271 // Use two distinct tracking rects for left and right parts.
272 // The tracking areas are also used to decide how to handle clicks. They must
273 // always be active, so the click is handled correctly when a download item
274 // is clicked while chrome is not the active app ( http://crbug.com/21916 ).
275 NSRect bounds = [[self controlView] bounds];
276 NSRect buttonRect, dropdownRect;
277 NSDivideRect(bounds, &dropdownRect, &buttonRect,
278 kDropdownAreaWidth, NSMaxXEdge);
280 trackingAreaButton_.reset([[NSTrackingArea alloc]
281 initWithRect:buttonRect
282 options:(NSTrackingMouseEnteredAndExited |
283 NSTrackingActiveAlways)
286 [[self controlView] addTrackingArea:trackingAreaButton_.get()];
288 trackingAreaDropdown_.reset([[NSTrackingArea alloc]
289 initWithRect:dropdownRect
290 options:(NSTrackingMouseEnteredAndExited |
291 NSTrackingActiveAlways)
294 [[self controlView] addTrackingArea:trackingAreaDropdown_.get()];
297 - (void)setShowsBorderOnlyWhileMouseInside:(BOOL)showOnly {
298 // Override to make sure it doesn't do anything if it's called accidentally.
301 - (void)mouseEntered:(NSEvent*)theEvent {
303 if ([theEvent trackingArea] == trackingAreaButton_.get())
304 mousePosition_ = kDownloadItemMouseOverButtonPart;
305 else if ([theEvent trackingArea] == trackingAreaDropdown_.get())
306 mousePosition_ = kDownloadItemMouseOverDropdownPart;
307 [[self controlView] setNeedsDisplay:YES];
310 - (void)mouseExited:(NSEvent *)theEvent {
312 if (mouseInsideCount_ == 0)
313 mousePosition_ = kDownloadItemMouseOutside;
314 [[self controlView] setNeedsDisplay:YES];
317 - (BOOL)isMouseInside {
318 return mousePosition_ != kDownloadItemMouseOutside;
321 - (BOOL)isMouseOverButtonPart {
322 return mousePosition_ == kDownloadItemMouseOverButtonPart;
325 - (BOOL)isButtonPartPressed {
326 return [self isHighlighted]
327 && mousePosition_ == kDownloadItemMouseOverButtonPart;
330 - (BOOL)isMouseOverDropdownPart {
331 return mousePosition_ == kDownloadItemMouseOverDropdownPart;
334 - (BOOL)isDropdownPartPressed {
335 return [self isHighlighted]
336 && mousePosition_ == kDownloadItemMouseOverDropdownPart;
339 - (NSBezierPath*)leftRoundedPath:(CGFloat)radius inRect:(NSRect)rect {
341 NSPoint topLeft = NSMakePoint(NSMinX(rect), NSMaxY(rect));
342 NSPoint topRight = NSMakePoint(NSMaxX(rect), NSMaxY(rect));
343 NSPoint bottomRight = NSMakePoint(NSMaxX(rect) , NSMinY(rect));
345 NSBezierPath* path = [NSBezierPath bezierPath];
346 [path moveToPoint:topRight];
347 [path appendBezierPathWithArcFromPoint:topLeft
350 [path appendBezierPathWithArcFromPoint:rect.origin
353 [path lineToPoint:bottomRight];
357 - (NSBezierPath*)rightRoundedPath:(CGFloat)radius inRect:(NSRect)rect {
359 NSPoint topLeft = NSMakePoint(NSMinX(rect), NSMaxY(rect));
360 NSPoint topRight = NSMakePoint(NSMaxX(rect), NSMaxY(rect));
361 NSPoint bottomRight = NSMakePoint(NSMaxX(rect), NSMinY(rect));
363 NSBezierPath* path = [NSBezierPath bezierPath];
364 [path moveToPoint:rect.origin];
365 [path appendBezierPathWithArcFromPoint:bottomRight
368 [path appendBezierPathWithArcFromPoint:topRight
371 [path lineToPoint:topLeft];
375 - (NSString*)elideTitle:(int)availableWidth {
376 NSFont* font = [self font];
377 gfx::Font font_chr(base::SysNSStringToUTF8([font fontName]),
380 return base::SysUTF16ToNSString(
381 ui::ElideFilename(downloadPath_, font_chr, availableWidth));
384 - (NSString*)elideStatus:(int)availableWidth {
385 NSFont* font = [self secondaryFont];
386 gfx::Font font_chr(base::SysNSStringToUTF8([font fontName]),
389 return base::SysUTF16ToNSString(ui::ElideText(
390 base::SysNSStringToUTF16([self secondaryTitle]),
396 - (ui::ThemeProvider*)backgroundThemeWrappingProvider:
397 (ui::ThemeProvider*)provider {
398 if (!themeProvider_.get()) {
399 themeProvider_.reset(new BackgroundTheme(provider));
402 return themeProvider_.get();
405 // Returns if |part| was pressed while the default theme was active.
406 - (BOOL)pressedWithDefaultThemeOnPart:(DownloadItemMousePosition)part {
407 return [self isDefaultTheme] && [self isHighlighted] &&
408 mousePosition_ == part;
411 // Returns the text color that should be used to draw text on |part|.
412 - (NSColor*)titleColorForPart:(DownloadItemMousePosition)part {
413 ui::ThemeProvider* themeProvider =
414 [[[self controlView] window] themeProvider];
415 if ([self pressedWithDefaultThemeOnPart:part] || !themeProvider)
416 return [NSColor alternateSelectedControlTextColor];
417 return themeProvider->GetNSColor(ThemeProperties::COLOR_BOOKMARK_TEXT);
420 - (void)drawSecondaryTitleInRect:(NSRect)innerFrame {
421 if (![self secondaryTitle] || statusAlpha_ <= 0)
424 CGFloat textWidth = NSWidth(innerFrame) -
425 (kTextPosLeft + kTextPaddingRight + kDropdownAreaWidth);
426 NSString* secondaryText = [self elideStatus:textWidth];
427 NSColor* secondaryColor =
428 [self titleColorForPart:kDownloadItemMouseOverButtonPart];
430 // If text is light-on-dark, lightening it alone will do nothing.
431 // Therefore we mute luminance a wee bit before drawing in this case.
432 if (![secondaryColor gtm_isDarkColor])
433 secondaryColor = [secondaryColor gtm_colorByAdjustingLuminance:-0.2];
435 NSDictionary* secondaryTextAttributes =
436 [NSDictionary dictionaryWithObjectsAndKeys:
437 secondaryColor, NSForegroundColorAttributeName,
438 [self secondaryFont], NSFontAttributeName,
440 NSPoint secondaryPos =
441 NSMakePoint(innerFrame.origin.x + kTextPosLeft, kSecondaryTextPosTop);
443 gfx::ScopedNSGraphicsContextSaveGState contextSave;
444 NSGraphicsContext* nsContext = [NSGraphicsContext currentContext];
445 CGContextRef cgContext = (CGContextRef)[nsContext graphicsPort];
446 [nsContext setCompositingOperation:NSCompositeSourceOver];
447 CGContextSetAlpha(cgContext, statusAlpha_);
448 [secondaryText drawAtPoint:secondaryPos
449 withAttributes:secondaryTextAttributes];
452 - (BOOL)isDefaultTheme {
453 ui::ThemeProvider* themeProvider =
454 [[[self controlView] window] themeProvider];
457 return !themeProvider->HasCustomImage(IDR_THEME_BUTTON_BACKGROUND);
460 - (void)drawWithFrame:(NSRect)cellFrame inView:(NSView*)controlView {
461 NSRect drawFrame = NSInsetRect(cellFrame, 1.5, 1.5);
462 NSRect innerFrame = NSInsetRect(cellFrame, 2, 2);
464 const float radius = 3;
465 NSWindow* window = [controlView window];
466 BOOL active = [window isKeyWindow] || [window isMainWindow];
468 // In the default theme, draw download items with the bookmark button
469 // gradient. For some themes, this leads to unreadable text, so draw the item
470 // with a background that looks like windows (some transparent white) if a
471 // theme is used. Use custom theme object with a white color gradient to trick
472 // the superclass into drawing what we want.
473 ui::ThemeProvider* themeProvider =
474 [[[self controlView] window] themeProvider];
476 NSGradient* bgGradient = nil;
477 if (![self isDefaultTheme]) {
478 themeProvider = [self backgroundThemeWrappingProvider:themeProvider];
479 bgGradient = themeProvider->GetNSGradient(
480 active ? ThemeProperties::GRADIENT_TOOLBAR_BUTTON :
481 ThemeProperties::GRADIENT_TOOLBAR_BUTTON_INACTIVE);
484 NSRect buttonDrawRect, dropdownDrawRect;
485 NSDivideRect(drawFrame, &dropdownDrawRect, &buttonDrawRect,
486 kDropdownAreaWidth, NSMaxXEdge);
488 NSBezierPath* buttonInnerPath = [self
489 leftRoundedPath:radius inRect:buttonDrawRect];
490 NSBezierPath* dropdownInnerPath = [self
491 rightRoundedPath:radius inRect:dropdownDrawRect];
493 // Draw secondary title, if any. Do this before drawing the (transparent)
494 // fill so that the text becomes a bit lighter. The default theme's "pressed"
495 // gradient is not transparent, so only do this if a theme is active.
496 bool drawStatusOnTop =
497 [self pressedWithDefaultThemeOnPart:kDownloadItemMouseOverButtonPart];
498 if (!drawStatusOnTop)
499 [self drawSecondaryTitleInRect:innerFrame];
501 // Stroke the borders and appropriate fill gradient.
502 [self drawBorderAndFillForTheme:themeProvider
503 controlView:controlView
504 innerPath:buttonInnerPath
505 showClickedGradient:[self isButtonPartPressed]
506 showHighlightGradient:[self isMouseOverButtonPart]
510 defaultGradient:bgGradient];
512 [self drawBorderAndFillForTheme:themeProvider
513 controlView:controlView
514 innerPath:dropdownInnerPath
515 showClickedGradient:[self isDropdownPartPressed]
516 showHighlightGradient:[self isMouseOverDropdownPart]
520 defaultGradient:bgGradient];
522 [self drawInteriorWithFrame:innerFrame inView:controlView];
524 // For the default theme, draw the status text on top of the (opaque) button
527 [self drawSecondaryTitleInRect:innerFrame];
530 - (void)drawInteriorWithFrame:(NSRect)cellFrame inView:(NSView*)controlView {
532 CGFloat textWidth = NSWidth(cellFrame) -
533 (kTextPosLeft + kTextPaddingRight + kDropdownAreaWidth);
534 [self setTitle:[self elideTitle:textWidth]];
536 NSColor* color = [self titleColorForPart:kDownloadItemMouseOverButtonPart];
537 NSString* primaryText = [self title];
539 NSDictionary* primaryTextAttributes =
540 [NSDictionary dictionaryWithObjectsAndKeys:
541 color, NSForegroundColorAttributeName,
542 [self font], NSFontAttributeName,
544 NSPoint primaryPos = NSMakePoint(
545 cellFrame.origin.x + kTextPosLeft,
548 [primaryText drawAtPoint:primaryPos withAttributes:primaryTextAttributes];
550 // Draw progress disk
552 // CanvasSkiaPaint draws its content to the current NSGraphicsContext in its
553 // destructor, which needs to be invoked before the icon is drawn below -
554 // hence this nested block.
556 // Always repaint the whole disk.
557 NSPoint imagePosition = [self imageRectForBounds:cellFrame].origin;
558 int x = imagePosition.x - DownloadShelf::kSmallProgressIconOffset;
559 int y = imagePosition.y - DownloadShelf::kSmallProgressIconOffset;
560 NSRect dirtyRect = NSMakeRect(
562 DownloadShelf::kSmallProgressIconSize,
563 DownloadShelf::kSmallProgressIconSize);
565 gfx::CanvasSkiaPaint canvas(dirtyRect, false);
566 canvas.set_composite_alpha(true);
567 if (completionAnimation_.get()) {
568 if ([completionAnimation_ isAnimating]) {
569 if (percentDone_ == -1) {
570 DownloadShelf::PaintDownloadComplete(
574 [completionAnimation_ currentValue],
575 DownloadShelf::SMALL);
577 DownloadShelf::PaintDownloadInterrupted(
581 [completionAnimation_ currentValue],
582 DownloadShelf::SMALL);
585 } else if (percentDone_ >= 0 || indeterminateProgressTimer_) {
586 DownloadShelf::PaintDownloadProgress(&canvas,
589 indeterminateProgressAngle_,
591 DownloadShelf::SMALL);
596 [[self image] drawInRect:[self imageRectForBounds:cellFrame]
598 operation:NSCompositeSourceOver
599 fraction:[self isEnabled] ? 1.0 : 0.5
603 // Separator between button and popup parts
604 CGFloat lx = NSMaxX(cellFrame) - kDropdownAreaWidth + 0.5;
605 [[NSColor colorWithDeviceWhite:0.0 alpha:0.1] set];
606 [NSBezierPath strokeLineFromPoint:NSMakePoint(lx, NSMinY(cellFrame) + 1)
607 toPoint:NSMakePoint(lx, NSMaxY(cellFrame) - 1)];
608 [[NSColor colorWithDeviceWhite:1.0 alpha:0.1] set];
609 [NSBezierPath strokeLineFromPoint:NSMakePoint(lx + 1, NSMinY(cellFrame) + 1)
610 toPoint:NSMakePoint(lx + 1, NSMaxY(cellFrame) - 1)];
612 // Popup arrow. Put center of mass of the arrow in the center of the
614 CGFloat cx = NSMaxX(cellFrame) - kDropdownAreaWidth/2 + 0.5;
615 CGFloat cy = NSMidY(cellFrame);
616 NSPoint p1 = NSMakePoint(cx - kDropdownArrowWidth/2,
617 cy - kDropdownArrowHeight/3 + kDropdownAreaY);
618 NSPoint p2 = NSMakePoint(cx + kDropdownArrowWidth/2,
619 cy - kDropdownArrowHeight/3 + kDropdownAreaY);
620 NSPoint p3 = NSMakePoint(cx, cy + kDropdownArrowHeight*2/3 + kDropdownAreaY);
621 NSBezierPath *triangle = [NSBezierPath bezierPath];
622 [triangle moveToPoint:p1];
623 [triangle lineToPoint:p2];
624 [triangle lineToPoint:p3];
625 [triangle closePath];
627 gfx::ScopedNSGraphicsContextSaveGState scopedGState;
629 base::scoped_nsobject<NSShadow> shadow([[NSShadow alloc] init]);
630 [shadow.get() setShadowColor:[NSColor whiteColor]];
631 [shadow.get() setShadowOffset:NSMakeSize(0, -1)];
632 [shadow setShadowBlurRadius:0.0];
635 NSColor* fill = [self titleColorForPart:kDownloadItemMouseOverDropdownPart];
641 - (NSRect)imageRectForBounds:(NSRect)cellFrame {
642 return NSMakeRect(cellFrame.origin.x + kImagePaddingLeft,
643 cellFrame.origin.y + kImagePaddingTop,
648 - (void)setupToggleStatusVisibilityAnimation {
649 if (toggleStatusVisibilityAnimation_ &&
650 [toggleStatusVisibilityAnimation_ isAnimating]) {
651 // If the animation is running, cancel the animation and show/hide the
652 // status text immediately.
653 [toggleStatusVisibilityAnimation_ stopAnimation];
654 [self animation:toggleStatusVisibilityAnimation_ progressed:1.0];
655 toggleStatusVisibilityAnimation_.reset();
657 // Don't use core animation -- text in CA layers is not subpixel antialiased
658 toggleStatusVisibilityAnimation_.reset([[DownloadItemCellAnimation alloc]
659 initWithDownloadItemCell:self
660 duration:kShowStatusDuration
661 animationCurve:NSAnimationEaseIn]);
662 [toggleStatusVisibilityAnimation_.get() setDelegate:self];
663 [toggleStatusVisibilityAnimation_.get() startAnimation];
667 - (void)showSecondaryTitle {
668 if (isStatusTextVisible_)
670 isStatusTextVisible_ = YES;
671 [self setupToggleStatusVisibilityAnimation];
674 - (void)hideSecondaryTitle {
675 if (!isStatusTextVisible_)
677 isStatusTextVisible_ = NO;
678 [self setupToggleStatusVisibilityAnimation];
681 - (IndeterminateProgressTimer*)indeterminateProgressTimer {
682 return indeterminateProgressTimer_;
685 - (void)animation:(NSAnimation*)animation
686 progressed:(NSAnimationProgress)progress {
687 if (animation == toggleStatusVisibilityAnimation_) {
688 if (isStatusTextVisible_) {
689 titleY_ = (1 - progress)*kPrimaryTextOnlyPosTop + kPrimaryTextPosTop;
690 statusAlpha_ = progress;
692 titleY_ = progress*kPrimaryTextOnlyPosTop +
693 (1 - progress)*kPrimaryTextPosTop;
694 statusAlpha_ = 1 - progress;
696 [[self controlView] setNeedsDisplay:YES];
697 } else if (animation == completionAnimation_) {
698 [[self controlView] setNeedsDisplay:YES];
702 - (void)updateIndeterminateDownload {
703 indeterminateProgressAngle_ =
704 (indeterminateProgressAngle_ + DownloadShelf::kUnknownIncrementDegrees) %
705 DownloadShelf::kMaxDegrees;
706 [[self controlView] setNeedsDisplay:YES];
709 - (void)stopIndeterminateAnimation {
710 [indeterminateProgressTimer_ invalidate];
711 indeterminateProgressTimer_.reset();
714 - (void)animationDidEnd:(NSAnimation *)animation {
715 if (animation == toggleStatusVisibilityAnimation_)
716 toggleStatusVisibilityAnimation_.reset();
717 else if (animation == completionAnimation_)
718 completionAnimation_.reset();
721 - (BOOL)isStatusTextVisible {
722 return isStatusTextVisible_;
725 - (CGFloat)statusTextAlpha {
729 - (void)skipVisibilityAnimation {
730 [toggleStatusVisibilityAnimation_ setCurrentProgress:1.0];
735 @implementation DownloadItemCellAnimation
737 - (id)initWithDownloadItemCell:(DownloadItemCell*)cell
738 duration:(NSTimeInterval)duration
739 animationCurve:(NSAnimationCurve)animationCurve {
740 if ((self = [super gtm_initWithDuration:duration
741 eventMask:NSLeftMouseDownMask
742 animationCurve:animationCurve])) {
744 [self setAnimationBlockingMode:NSAnimationNonblocking];
749 - (void)setCurrentProgress:(NSAnimationProgress)progress {
750 [super setCurrentProgress:progress];
751 [cell_ animation:self progressed:progress];
756 @implementation IndeterminateProgressTimer
758 - (id)initWithDownloadItemCell:(DownloadItemCell*)cell {
759 if ((self = [super init])) {
761 timer_.reset([[NSTimer
762 scheduledTimerWithTimeInterval:DownloadShelf::kProgressRateMs / 1000.0
764 selector:@selector(onTimer:)
766 repeats:YES] retain]);
775 - (void)onTimer:(NSTimer*)timer {
776 [cell_ updateIndeterminateDownload];