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/gfx/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 // 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 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 NSFont* font = [self font];
374 gfx::Font font_chr(base::SysNSStringToUTF8([font fontName]),
377 return base::SysUTF16ToNSString(
378 gfx::ElideFilename(downloadPath_, font_chr, availableWidth));
381 - (NSString*)elideStatus:(int)availableWidth {
382 NSFont* font = [self secondaryFont];
383 gfx::Font font_chr(base::SysNSStringToUTF8([font fontName]),
386 return base::SysUTF16ToNSString(gfx::ElideText(
387 base::SysNSStringToUTF16([self secondaryTitle]),
393 - (ui::ThemeProvider*)backgroundThemeWrappingProvider:
394 (ui::ThemeProvider*)provider {
395 if (!themeProvider_.get()) {
396 themeProvider_.reset(new BackgroundTheme(provider));
399 return themeProvider_.get();
402 // Returns if |part| was pressed while the default theme was active.
403 - (BOOL)pressedWithDefaultThemeOnPart:(DownloadItemMousePosition)part {
404 return [self isDefaultTheme] && [self isHighlighted] &&
405 mousePosition_ == part;
408 // Returns the text color that should be used to draw text on |part|.
409 - (NSColor*)titleColorForPart:(DownloadItemMousePosition)part {
410 ui::ThemeProvider* themeProvider =
411 [[[self controlView] window] themeProvider];
412 if ([self pressedWithDefaultThemeOnPart:part] || !themeProvider)
413 return [NSColor alternateSelectedControlTextColor];
414 return themeProvider->GetNSColor(ThemeProperties::COLOR_BOOKMARK_TEXT);
417 - (void)drawSecondaryTitleInRect:(NSRect)innerFrame {
418 if (![self secondaryTitle] || statusAlpha_ <= 0)
421 CGFloat textWidth = NSWidth(innerFrame) -
422 (kTextPosLeft + kTextPaddingRight + kDropdownAreaWidth);
423 NSString* secondaryText = [self elideStatus:textWidth];
424 NSColor* secondaryColor =
425 [self titleColorForPart:kDownloadItemMouseOverButtonPart];
427 // If text is light-on-dark, lightening it alone will do nothing.
428 // Therefore we mute luminance a wee bit before drawing in this case.
429 if (![secondaryColor gtm_isDarkColor])
430 secondaryColor = [secondaryColor gtm_colorByAdjustingLuminance:-0.2];
432 NSDictionary* secondaryTextAttributes =
433 [NSDictionary dictionaryWithObjectsAndKeys:
434 secondaryColor, NSForegroundColorAttributeName,
435 [self secondaryFont], NSFontAttributeName,
437 NSPoint secondaryPos =
438 NSMakePoint(innerFrame.origin.x + kTextPosLeft, kSecondaryTextPosTop);
440 gfx::ScopedNSGraphicsContextSaveGState contextSave;
441 NSGraphicsContext* nsContext = [NSGraphicsContext currentContext];
442 CGContextRef cgContext = (CGContextRef)[nsContext graphicsPort];
443 [nsContext setCompositingOperation:NSCompositeSourceOver];
444 CGContextSetAlpha(cgContext, statusAlpha_);
445 [secondaryText drawAtPoint:secondaryPos
446 withAttributes:secondaryTextAttributes];
449 - (BOOL)isDefaultTheme {
450 ui::ThemeProvider* themeProvider =
451 [[[self controlView] window] themeProvider];
454 return !themeProvider->HasCustomImage(IDR_THEME_BUTTON_BACKGROUND);
457 - (void)drawWithFrame:(NSRect)cellFrame inView:(NSView*)controlView {
458 NSRect drawFrame = NSInsetRect(cellFrame, 1.5, 1.5);
459 NSRect innerFrame = NSInsetRect(cellFrame, 2, 2);
461 const float radius = 3;
462 NSWindow* window = [controlView window];
463 BOOL active = [window isKeyWindow] || [window isMainWindow];
465 // In the default theme, draw download items with the bookmark button
466 // gradient. For some themes, this leads to unreadable text, so draw the item
467 // with a background that looks like windows (some transparent white) if a
468 // theme is used. Use custom theme object with a white color gradient to trick
469 // the superclass into drawing what we want.
470 ui::ThemeProvider* themeProvider =
471 [[[self controlView] window] themeProvider];
473 NSGradient* bgGradient = nil;
474 if (![self isDefaultTheme]) {
475 themeProvider = [self backgroundThemeWrappingProvider:themeProvider];
476 bgGradient = themeProvider->GetNSGradient(
477 active ? ThemeProperties::GRADIENT_TOOLBAR_BUTTON :
478 ThemeProperties::GRADIENT_TOOLBAR_BUTTON_INACTIVE);
481 NSRect buttonDrawRect, dropdownDrawRect;
482 NSDivideRect(drawFrame, &dropdownDrawRect, &buttonDrawRect,
483 kDropdownAreaWidth, NSMaxXEdge);
485 NSBezierPath* buttonInnerPath = [self
486 leftRoundedPath:radius inRect:buttonDrawRect];
487 NSBezierPath* dropdownInnerPath = [self
488 rightRoundedPath:radius inRect:dropdownDrawRect];
490 // Draw secondary title, if any. Do this before drawing the (transparent)
491 // fill so that the text becomes a bit lighter. The default theme's "pressed"
492 // gradient is not transparent, so only do this if a theme is active.
493 bool drawStatusOnTop =
494 [self pressedWithDefaultThemeOnPart:kDownloadItemMouseOverButtonPart];
495 if (!drawStatusOnTop)
496 [self drawSecondaryTitleInRect:innerFrame];
498 // Stroke the borders and appropriate fill gradient.
499 [self drawBorderAndFillForTheme:themeProvider
500 controlView:controlView
501 innerPath:buttonInnerPath
502 showClickedGradient:[self isButtonPartPressed]
503 showHighlightGradient:[self isMouseOverButtonPart]
507 defaultGradient:bgGradient];
509 [self drawBorderAndFillForTheme:themeProvider
510 controlView:controlView
511 innerPath:dropdownInnerPath
512 showClickedGradient:[self isDropdownPartPressed]
513 showHighlightGradient:[self isMouseOverDropdownPart]
517 defaultGradient:bgGradient];
519 [self drawInteriorWithFrame:innerFrame inView:controlView];
521 // For the default theme, draw the status text on top of the (opaque) button
524 [self drawSecondaryTitleInRect:innerFrame];
527 - (void)drawInteriorWithFrame:(NSRect)cellFrame inView:(NSView*)controlView {
529 CGFloat textWidth = NSWidth(cellFrame) -
530 (kTextPosLeft + kTextPaddingRight + kDropdownAreaWidth);
531 [self setTitle:[self elideTitle:textWidth]];
533 NSColor* color = [self titleColorForPart:kDownloadItemMouseOverButtonPart];
534 NSString* primaryText = [self title];
536 NSDictionary* primaryTextAttributes =
537 [NSDictionary dictionaryWithObjectsAndKeys:
538 color, NSForegroundColorAttributeName,
539 [self font], NSFontAttributeName,
541 NSPoint primaryPos = NSMakePoint(
542 cellFrame.origin.x + kTextPosLeft,
545 [primaryText drawAtPoint:primaryPos withAttributes:primaryTextAttributes];
547 // Draw progress disk
549 // CanvasSkiaPaint draws its content to the current NSGraphicsContext in its
550 // destructor, which needs to be invoked before the icon is drawn below -
551 // hence this nested block.
553 // Always repaint the whole disk.
554 NSPoint imagePosition = [self imageRectForBounds:cellFrame].origin;
555 int x = imagePosition.x - DownloadShelf::kSmallProgressIconOffset;
556 int y = imagePosition.y - DownloadShelf::kSmallProgressIconOffset;
557 NSRect dirtyRect = NSMakeRect(
559 DownloadShelf::kSmallProgressIconSize,
560 DownloadShelf::kSmallProgressIconSize);
562 gfx::CanvasSkiaPaint canvas(dirtyRect, false);
563 canvas.set_composite_alpha(true);
564 if (completionAnimation_.get()) {
565 if ([completionAnimation_ isAnimating]) {
566 if (percentDone_ == -1) {
567 DownloadShelf::PaintDownloadComplete(
571 [completionAnimation_ currentValue],
572 DownloadShelf::SMALL);
574 DownloadShelf::PaintDownloadInterrupted(
578 [completionAnimation_ currentValue],
579 DownloadShelf::SMALL);
582 } else if (percentDone_ >= 0 || indeterminateProgressTimer_) {
583 DownloadShelf::PaintDownloadProgress(&canvas,
586 indeterminateProgressAngle_,
588 DownloadShelf::SMALL);
593 [[self image] drawInRect:[self imageRectForBounds:cellFrame]
595 operation:NSCompositeSourceOver
596 fraction:[self isEnabled] ? 1.0 : 0.5
600 // Separator between button and popup parts
601 CGFloat lx = NSMaxX(cellFrame) - kDropdownAreaWidth + 0.5;
602 [[NSColor colorWithDeviceWhite:0.0 alpha:0.1] set];
603 [NSBezierPath strokeLineFromPoint:NSMakePoint(lx, NSMinY(cellFrame) + 1)
604 toPoint:NSMakePoint(lx, NSMaxY(cellFrame) - 1)];
605 [[NSColor colorWithDeviceWhite:1.0 alpha:0.1] set];
606 [NSBezierPath strokeLineFromPoint:NSMakePoint(lx + 1, NSMinY(cellFrame) + 1)
607 toPoint:NSMakePoint(lx + 1, NSMaxY(cellFrame) - 1)];
609 // Popup arrow. Put center of mass of the arrow in the center of the
611 CGFloat cx = NSMaxX(cellFrame) - kDropdownAreaWidth/2 + 0.5;
612 CGFloat cy = NSMidY(cellFrame);
613 NSPoint p1 = NSMakePoint(cx - kDropdownArrowWidth/2,
614 cy - kDropdownArrowHeight/3 + kDropdownAreaY);
615 NSPoint p2 = NSMakePoint(cx + kDropdownArrowWidth/2,
616 cy - kDropdownArrowHeight/3 + kDropdownAreaY);
617 NSPoint p3 = NSMakePoint(cx, cy + kDropdownArrowHeight*2/3 + kDropdownAreaY);
618 NSBezierPath *triangle = [NSBezierPath bezierPath];
619 [triangle moveToPoint:p1];
620 [triangle lineToPoint:p2];
621 [triangle lineToPoint:p3];
622 [triangle closePath];
624 gfx::ScopedNSGraphicsContextSaveGState scopedGState;
626 base::scoped_nsobject<NSShadow> shadow([[NSShadow alloc] init]);
627 [shadow.get() setShadowColor:[NSColor whiteColor]];
628 [shadow.get() setShadowOffset:NSMakeSize(0, -1)];
629 [shadow setShadowBlurRadius:0.0];
632 NSColor* fill = [self titleColorForPart:kDownloadItemMouseOverDropdownPart];
638 - (NSRect)imageRectForBounds:(NSRect)cellFrame {
639 return NSMakeRect(cellFrame.origin.x + kImagePaddingLeft,
640 cellFrame.origin.y + kImagePaddingTop,
645 - (void)setupToggleStatusVisibilityAnimation {
646 if (toggleStatusVisibilityAnimation_ &&
647 [toggleStatusVisibilityAnimation_ isAnimating]) {
648 // If the animation is running, cancel the animation and show/hide the
649 // status text immediately.
650 [toggleStatusVisibilityAnimation_ stopAnimation];
651 [self animation:toggleStatusVisibilityAnimation_ progressed:1.0];
652 toggleStatusVisibilityAnimation_.reset();
654 // Don't use core animation -- text in CA layers is not subpixel antialiased
655 toggleStatusVisibilityAnimation_.reset([[DownloadItemCellAnimation alloc]
656 initWithDownloadItemCell:self
657 duration:kShowStatusDuration
658 animationCurve:NSAnimationEaseIn]);
659 [toggleStatusVisibilityAnimation_.get() setDelegate:self];
660 [toggleStatusVisibilityAnimation_.get() startAnimation];
664 - (void)showSecondaryTitle {
665 if (isStatusTextVisible_)
667 isStatusTextVisible_ = YES;
668 [self setupToggleStatusVisibilityAnimation];
671 - (void)hideSecondaryTitle {
672 if (!isStatusTextVisible_)
674 isStatusTextVisible_ = NO;
675 [self setupToggleStatusVisibilityAnimation];
678 - (IndeterminateProgressTimer*)indeterminateProgressTimer {
679 return indeterminateProgressTimer_;
682 - (void)animation:(NSAnimation*)animation
683 progressed:(NSAnimationProgress)progress {
684 if (animation == toggleStatusVisibilityAnimation_) {
685 if (isStatusTextVisible_) {
686 titleY_ = (1 - progress)*kPrimaryTextOnlyPosTop + kPrimaryTextPosTop;
687 statusAlpha_ = progress;
689 titleY_ = progress*kPrimaryTextOnlyPosTop +
690 (1 - progress)*kPrimaryTextPosTop;
691 statusAlpha_ = 1 - progress;
693 [[self controlView] setNeedsDisplay:YES];
694 } else if (animation == completionAnimation_) {
695 [[self controlView] setNeedsDisplay:YES];
699 - (void)updateIndeterminateDownload {
700 indeterminateProgressAngle_ =
701 (indeterminateProgressAngle_ + DownloadShelf::kUnknownIncrementDegrees) %
702 DownloadShelf::kMaxDegrees;
703 [[self controlView] setNeedsDisplay:YES];
706 - (void)stopIndeterminateAnimation {
707 [indeterminateProgressTimer_ invalidate];
708 indeterminateProgressTimer_.reset();
711 - (void)animationDidEnd:(NSAnimation *)animation {
712 if (animation == toggleStatusVisibilityAnimation_)
713 toggleStatusVisibilityAnimation_.reset();
714 else if (animation == completionAnimation_)
715 completionAnimation_.reset();
718 - (BOOL)isStatusTextVisible {
719 return isStatusTextVisible_;
722 - (CGFloat)statusTextAlpha {
726 - (void)skipVisibilityAnimation {
727 [toggleStatusVisibilityAnimation_ setCurrentProgress:1.0];
732 @implementation DownloadItemCellAnimation
734 - (id)initWithDownloadItemCell:(DownloadItemCell*)cell
735 duration:(NSTimeInterval)duration
736 animationCurve:(NSAnimationCurve)animationCurve {
737 if ((self = [super gtm_initWithDuration:duration
738 eventMask:NSLeftMouseDownMask
739 animationCurve:animationCurve])) {
741 [self setAnimationBlockingMode:NSAnimationNonblocking];
746 - (void)setCurrentProgress:(NSAnimationProgress)progress {
747 [super setCurrentProgress:progress];
748 [cell_ animation:self progressed:progress];
753 @implementation IndeterminateProgressTimer
755 - (id)initWithDownloadItemCell:(DownloadItemCell*)cell {
756 if ((self = [super init])) {
758 timer_.reset([[NSTimer
759 scheduledTimerWithTimeInterval:DownloadShelf::kProgressRateMs / 1000.0
761 selector:@selector(onTimer:)
763 repeats:YES] retain]);
772 - (void)onTimer:(NSTimer*)timer {
773 [cell_ updateIndeterminateDownload];