1 // Copyright (c) 2011 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. 4 // 5 // This file contains the Mac implementation the download animation, displayed 6 // at the start of a download. The animation produces an arrow pointing 7 // downwards and animates towards the bottom of the window where the new 8 // download appears in the download shelf. 9 10 #include "chrome/browser/download/download_started_animation.h" 11 12 #import <QuartzCore/QuartzCore.h> 13 14 #include "chrome/browser/tab_contents/tab_contents_view_mac.h" 15 #import "chrome/browser/ui/cocoa/animatable_image.h" 16 #include "content/browser/tab_contents/tab_contents.h" 17 #include "content/common/notification_details.h" 18 #include "content/common/notification_registrar.h" 19 #include "content/common/notification_source.h" 20 #include "grit/theme_resources.h" 21 #import "third_party/GTM/AppKit/GTMNSAnimation+Duration.h" 22 #include "third_party/skia/include/utils/mac/SkCGUtils.h" 23 #include "ui/base/resource/resource_bundle.h" 24 #include "ui/gfx/image.h" 25 26 class DownloadAnimationTabObserver; 27 28 // A class for managing the Core Animation download animation. 29 // Should be instantiated using +startAnimationWithTabContents:. 30 @interface DownloadStartedAnimationMac : NSObject { 31 @private 32 // The observer for the TabContents we are drawing on. 33 scoped_ptr<DownloadAnimationTabObserver> observer_; 34 CGFloat imageWidth_; 35 AnimatableImage* animation_; 36 }; 37 38 + (void)startAnimationWithTabContents:(TabContents*)tabContents; 39 40 // Called by the Observer if the tab is hidden or closed. 41 - (void)closeAnimation; 42 43 @end 44 45 // A helper class to monitor tab hidden and closed notifications. If we receive 46 // such a notification, we stop the animation. 47 class DownloadAnimationTabObserver : public NotificationObserver { 48 public: 49 DownloadAnimationTabObserver(DownloadStartedAnimationMac* owner, 50 TabContents* tab_contents) 51 : owner_(owner), 52 tab_contents_(tab_contents) { 53 registrar_.Add(this, 54 NotificationType::TAB_CONTENTS_HIDDEN, 55 Source<TabContents>(tab_contents_)); 56 registrar_.Add(this, 57 NotificationType::TAB_CONTENTS_DESTROYED, 58 Source<TabContents>(tab_contents_)); 59 } 60 61 // Runs when a tab is hidden or destroyed. Let our owner know we should end 62 // the animation. 63 void Observe(NotificationType type, 64 const NotificationSource& source, 65 const NotificationDetails& details) { 66 // This ends up deleting us. 67 [owner_ closeAnimation]; 68 } 69 70 private: 71 // The object we need to inform when we get a notification. Weak. 72 DownloadStartedAnimationMac* owner_; 73 74 // The tab we are observing. Weak. 75 TabContents* tab_contents_; 76 77 // Used for registering to receive notifications and automatic clean up. 78 NotificationRegistrar registrar_; 79 80 DISALLOW_COPY_AND_ASSIGN(DownloadAnimationTabObserver); 81 }; 82 83 @implementation DownloadStartedAnimationMac 84 85 - (id)initWithTabContents:(TabContents*)tabContents { 86 if ((self = [super init])) { 87 // Load the image of the download arrow. 88 ResourceBundle& bundle = ResourceBundle::GetSharedInstance(); 89 NSImage* image = bundle.GetNativeImageNamed(IDR_DOWNLOAD_ANIMATION_BEGIN); 90 91 // Figure out the positioning in the current tab. Try to position the layer 92 // against the left edge, and three times the download image's height from 93 // the bottom of the tab, assuming there is enough room. If there isn't 94 // enough, don't show the animation and let the shelf speak for itself. 95 gfx::Rect bounds; 96 tabContents->GetContainerBounds(&bounds); 97 imageWidth_ = [image size].width; 98 CGFloat imageHeight = [image size].height; 99 100 // Sanity check the size in case there's no room to display the animation. 101 if (bounds.height() < imageHeight) { 102 [self release]; 103 return nil; 104 } 105 106 NSView* tabContentsView = tabContents->GetNativeView(); 107 NSWindow* parentWindow = [tabContentsView window]; 108 if (!parentWindow) { 109 // The tab is no longer frontmost. 110 [self release]; 111 return nil; 112 } 113 114 NSPoint origin = [tabContentsView frame].origin; 115 origin = [tabContentsView convertPoint:origin toView:nil]; 116 origin = [parentWindow convertBaseToScreen:origin]; 117 118 // Create the animation object to assist in animating and fading. 119 CGFloat animationHeight = MIN(bounds.height(), 4 * imageHeight); 120 NSRect frame = NSMakeRect(origin.x, origin.y, imageWidth_, animationHeight); 121 animation_ = [[AnimatableImage alloc] initWithImage:image 122 animationFrame:frame]; 123 [parentWindow addChildWindow:animation_ ordered:NSWindowAbove]; 124 125 animationHeight = MIN(bounds.height(), 3 * imageHeight); 126 [animation_ setStartFrame:CGRectMake(0, animationHeight, 127 imageWidth_, imageHeight)]; 128 [animation_ setEndFrame:CGRectMake(0, imageHeight, 129 imageWidth_, imageHeight)]; 130 [animation_ setStartOpacity:1.0]; 131 [animation_ setEndOpacity:0.4]; 132 [animation_ setDuration:0.6]; 133 134 observer_.reset(new DownloadAnimationTabObserver(self, tabContents)); 135 136 // Set up to get notified about resize events on the parent window. 137 NSNotificationCenter* center = [NSNotificationCenter defaultCenter]; 138 [center addObserver:self 139 selector:@selector(parentWindowChanged:) 140 name:NSWindowDidResizeNotification 141 object:parentWindow]; 142 // When the animation window closes, it needs to be removed from the 143 // parent window. 144 [center addObserver:self 145 selector:@selector(windowWillClose:) 146 name:NSWindowWillCloseNotification 147 object:animation_]; 148 } 149 return self; 150 } 151 152 - (void)dealloc { 153 [[NSNotificationCenter defaultCenter] removeObserver:self]; 154 [super dealloc]; 155 } 156 157 // Called when the parent window is resized. 158 - (void)parentWindowChanged:(NSNotification*)notification { 159 NSWindow* parentWindow = [animation_ parentWindow]; 160 DCHECK([[notification object] isEqual:parentWindow]); 161 NSRect parentFrame = [parentWindow frame]; 162 NSRect frame = parentFrame; 163 frame.size.width = MIN(imageWidth_, NSWidth(parentFrame)); 164 [animation_ setFrame:frame display:YES]; 165 } 166 167 - (void)closeAnimation { 168 [animation_ close]; 169 } 170 171 // When the animation closes, release self. 172 - (void)windowWillClose:(NSNotification*)notification { 173 DCHECK([[notification object] isEqual:animation_]); 174 [[animation_ parentWindow] removeChildWindow:animation_]; 175 [self release]; 176 } 177 178 + (void)startAnimationWithTabContents:(TabContents*)contents { 179 // Will be deleted when the animation window closes. 180 DownloadStartedAnimationMac* controller = 181 [[self alloc] initWithTabContents:contents]; 182 // The initializer can return nil. 183 if (!controller) 184 return; 185 186 // The |animation_| releases itself when done. 187 [controller->animation_ startAnimation]; 188 } 189 190 @end 191 192 void DownloadStartedAnimation::Show(TabContents* tab_contents) { 193 DCHECK(tab_contents); 194 195 // Will be deleted when the animation is complete. 196 [DownloadStartedAnimationMac startAnimationWithTabContents:tab_contents]; 197 } 198