| OLD | NEW |
| 1 // Copyright (c) 2009 The Chromium Authors. All rights reserved. | 1 // Copyright (c) 2009 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
| 4 // | 4 // |
| 5 // This file contains the Mac implementation the download animation, displayed | 5 // This file contains the Mac implementation the download animation, displayed |
| 6 // at the start of a download. The animation produces an arrow pointing | 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 | 7 // downwards and animates towards the bottom of the window where the new |
| 8 // download appears in the download shelf. | 8 // download appears in the download shelf. |
| 9 | 9 |
| 10 #include "chrome/browser/download/download_started_animation.h" | 10 #include "chrome/browser/download/download_started_animation.h" |
| 11 | 11 |
| 12 #import <QuartzCore/QuartzCore.h> | 12 #import <QuartzCore/QuartzCore.h> |
| 13 | 13 |
| 14 #include "app/resource_bundle.h" | 14 #include "app/resource_bundle.h" |
| 15 #include "base/scoped_cftyperef.h" | 15 #include "base/scoped_cftyperef.h" |
| 16 #include "base/scoped_nsobject.h" | |
| 17 #include "chrome/browser/tab_contents/tab_contents.h" | 16 #include "chrome/browser/tab_contents/tab_contents.h" |
| 18 #include "chrome/browser/tab_contents/tab_contents_view_mac.h" | 17 #include "chrome/browser/tab_contents/tab_contents_view_mac.h" |
| 19 #include "chrome/common/notification_registrar.h" | 18 #include "chrome/common/notification_registrar.h" |
| 20 #include "chrome/common/notification_service.h" | 19 #include "chrome/common/notification_service.h" |
| 21 #include "grit/theme_resources.h" | 20 #include "grit/theme_resources.h" |
| 22 #import "third_party/GTM/AppKit/GTMNSAnimation+Duration.h" | 21 #import "third_party/GTM/AppKit/GTMNSAnimation+Duration.h" |
| 23 #include "third_party/skia/include/utils/mac/SkCGUtils.h" | 22 #include "third_party/skia/include/utils/mac/SkCGUtils.h" |
| 24 | 23 |
| 25 class DownloadAnimationTabObserver; | 24 class DownloadAnimationTabObserver; |
| 26 | 25 |
| 27 // A class for managing the Core Animation download animation. | 26 // A class for managing the Core Animation download animation. |
| 28 @interface DownloadStartedAnimationMac : NSObject { | 27 // Should be instantiated using +startAnimationWithTabContents:. |
| 29 // The download arrow image which we are responsible for freeing. | 28 @interface DownloadStartedAnimationMac : NSWindow { |
| 30 scoped_cftyperef<CGImageRef> image_; | 29 @private |
| 31 | |
| 32 // The TabContents we will animate in (weak). | |
| 33 TabContents* tabContents_; | |
| 34 | |
| 35 // The cocoa view object for our TabContents (weak). | |
| 36 NSView* view_; | |
| 37 | |
| 38 // The observer for the TabContents we are drawing on. | 30 // The observer for the TabContents we are drawing on. |
| 39 scoped_ptr<DownloadAnimationTabObserver> observer_; | 31 scoped_ptr<DownloadAnimationTabObserver> observer_; |
| 40 | 32 CGFloat imageWidth_; |
| 41 // Our animation layer. | |
| 42 scoped_nsobject<CALayer> layer_; | |
| 43 | |
| 44 // Set once the animation is complete, either by interrupting (via window | |
| 45 // close) or through normal a end of the animation. | |
| 46 BOOL isComplete_; | |
| 47 }; | 33 }; |
| 48 | 34 |
| 35 + (void)startAnimationWithTabContents:(TabContents*)tabContents; |
| 49 // Called by our DownloadAnimationTabObserver if the tab is hidden or closed. | 36 // Called by our DownloadAnimationTabObserver if the tab is hidden or closed. |
| 50 - (void)animationComplete; | 37 - (void)animationComplete; |
| 51 | 38 |
| 52 @end // interface DownloadStartedAnimationMac. | 39 @end |
| 53 | |
| 54 | 40 |
| 55 // A helper class to monitor tab hidden and closed notifications. If we receive | 41 // A helper class to monitor tab hidden and closed notifications. If we receive |
| 56 // such a notification, we stop the animation. | 42 // such a notification, we stop the animation. |
| 57 class DownloadAnimationTabObserver : public NotificationObserver { | 43 class DownloadAnimationTabObserver : public NotificationObserver { |
| 58 public: | 44 public: |
| 59 DownloadAnimationTabObserver(DownloadStartedAnimationMac* owner, | 45 DownloadAnimationTabObserver(DownloadStartedAnimationMac* owner, |
| 60 TabContents* tab_contents) | 46 TabContents* tab_contents) |
| 61 : owner_(owner), | 47 : owner_(owner), |
| 62 tab_contents_(tab_contents) { | 48 tab_contents_(tab_contents) { |
| 63 registrar_.Add(this, | 49 registrar_.Add(this, |
| (...skipping 24 matching lines...) Expand all Loading... |
| 88 | 74 |
| 89 // The tab we are observing. Weak. | 75 // The tab we are observing. Weak. |
| 90 TabContents* tab_contents_; | 76 TabContents* tab_contents_; |
| 91 | 77 |
| 92 // Used for registering to receive notifications and automatic clean up. | 78 // Used for registering to receive notifications and automatic clean up. |
| 93 NotificationRegistrar registrar_; | 79 NotificationRegistrar registrar_; |
| 94 | 80 |
| 95 DISALLOW_COPY_AND_ASSIGN(DownloadAnimationTabObserver); | 81 DISALLOW_COPY_AND_ASSIGN(DownloadAnimationTabObserver); |
| 96 }; | 82 }; |
| 97 | 83 |
| 98 | |
| 99 @implementation DownloadStartedAnimationMac | 84 @implementation DownloadStartedAnimationMac |
| 100 | 85 |
| 101 // Load the image of the download arrow. | |
| 102 - (id)initWithTabContents:(TabContents*)tabContents { | 86 - (id)initWithTabContents:(TabContents*)tabContents { |
| 103 if ((self = [super init])) { | 87 // Load the image of the download arrow. |
| 104 SkBitmap* image_bitmap = | 88 ResourceBundle& bundle = ResourceBundle::GetSharedInstance(); |
| 105 ResourceBundle::GetSharedInstance().GetBitmapNamed( | 89 SkBitmap* imageBitmap = bundle.GetBitmapNamed(IDR_DOWNLOAD_ANIMATION_BEGIN); |
| 106 IDR_DOWNLOAD_ANIMATION_BEGIN); | 90 scoped_cftyperef<CGImageRef> image(SkCreateCGImageRef(*imageBitmap)); |
| 107 image_.reset(SkCreateCGImageRef(*image_bitmap)); | 91 |
| 108 tabContents_ = tabContents; | 92 // Figure out the positioning in the current tab. Try to position the layer |
| 109 view_ = tabContents_->GetContentNativeView(); | 93 // against the left edge, and three times the download image's height from the |
| 94 // bottom of the tab, assuming there is enough room. If there isn't enough, |
| 95 // don't show the animation and let the shelf speak for itself. |
| 96 gfx::Rect bounds; |
| 97 tabContents->GetContainerBounds(&bounds); |
| 98 imageWidth_ = CGImageGetWidth(image); |
| 99 CGFloat imageHeight = CGImageGetHeight(image); |
| 100 CGRect imageBounds = CGRectMake(0, 0, imageWidth_, imageHeight); |
| 101 |
| 102 // Sanity check the size in case there's no room to display the animation. |
| 103 if (bounds.height() < imageHeight) { |
| 104 [self release]; |
| 105 return nil; |
| 106 } |
| 107 |
| 108 NSView* tabContentsView = tabContents->GetNativeView(); |
| 109 NSWindow* parentWindow = [tabContentsView window]; |
| 110 if (!parentWindow) { |
| 111 // The tab is no longer frontmost. |
| 112 [self release]; |
| 113 return nil; |
| 114 } |
| 115 |
| 116 NSPoint origin = [tabContentsView frame].origin; |
| 117 origin = [tabContentsView convertPointToBase:origin]; |
| 118 origin = [parentWindow convertBaseToScreen:origin]; |
| 119 |
| 120 // Create a window to host a layer that animates the sliding and fading. |
| 121 CGFloat animationHeight = MIN(bounds.height(), 4 * imageHeight); |
| 122 NSRect frame = NSMakeRect(origin.x, origin.y, imageWidth_, animationHeight); |
| 123 if ((self = [super initWithContentRect:frame |
| 124 styleMask:NSBorderlessWindowMask |
| 125 backing:NSBackingStoreBuffered |
| 126 defer:NO])) { |
| 127 [self setOpaque:NO]; |
| 128 [self setBackgroundColor:[NSColor clearColor]]; |
| 129 [self setIgnoresMouseEvents:YES]; |
| 130 |
| 131 // Must be set or else self will be leaked. |
| 132 [self setReleasedWhenClosed:YES]; |
| 133 |
| 134 // Set up to get notified about resize events on the parent window. |
| 135 NSNotificationCenter* center = [NSNotificationCenter defaultCenter]; |
| 136 [center addObserver:self |
| 137 selector:@selector(parentWindowChanged:) |
| 138 name:NSWindowDidResizeNotification |
| 139 object:parentWindow]; |
| 140 [parentWindow addChildWindow:self ordered:NSWindowAbove]; |
| 141 |
| 142 // Set up the root layer. By calling -setLayer: followed by -setWantsLayer: |
| 143 // the view becomes a layer hosting view as opposed to a layer backed view. |
| 144 NSView* view = [self contentView]; |
| 145 CALayer* rootLayer = [CALayer layer]; |
| 146 [view setLayer:rootLayer]; |
| 147 [view setWantsLayer:YES]; |
| 148 |
| 149 // Create the layer that will be animated. |
| 150 CALayer* layer = [CALayer layer]; |
| 151 [layer setContents:(id)image.get()]; |
| 152 [layer setAnchorPoint:CGPointMake(0, 1)]; |
| 153 [layer setFrame:CGRectMake(0, 0, imageWidth_, imageHeight)]; |
| 154 [layer setNeedsDisplayOnBoundsChange:YES]; |
| 155 [rootLayer addSublayer:layer]; |
| 156 |
| 157 // Common timing function for all animations. |
| 158 CAMediaTimingFunction* mediaFunction = |
| 159 [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseOut]; |
| 160 |
| 161 // Positional animation. |
| 162 CABasicAnimation* positionAnimation = |
| 163 [CABasicAnimation animationWithKeyPath:@"position"]; |
| 164 CGFloat animationHeight = MIN(bounds.height(), 3 * imageHeight); |
| 165 NSPoint start = NSMakePoint(0, animationHeight); |
| 166 NSPoint stop = NSMakePoint(0, imageHeight); |
| 167 [positionAnimation setFromValue:[NSValue valueWithPoint:start]]; |
| 168 [positionAnimation setToValue:[NSValue valueWithPoint:stop]]; |
| 169 [positionAnimation gtm_setDuration:0.6]; |
| 170 [positionAnimation setTimingFunction:mediaFunction]; |
| 171 |
| 172 // Opacity animation. |
| 173 CABasicAnimation* opacityAnimation = |
| 174 [CABasicAnimation animationWithKeyPath:@"opacity"]; |
| 175 [opacityAnimation setFromValue:[NSNumber numberWithFloat:1.0]]; |
| 176 [opacityAnimation setToValue:[NSNumber numberWithFloat:0.4]]; |
| 177 [opacityAnimation gtm_setDuration:0.6]; |
| 178 [opacityAnimation setTimingFunction:mediaFunction]; |
| 179 |
| 180 // Group the animations together. |
| 181 CAAnimationGroup* animationGroup = [CAAnimationGroup animation]; |
| 182 NSArray* animations = |
| 183 [NSArray arrayWithObjects:positionAnimation, opacityAnimation, nil]; |
| 184 [animationGroup setAnimations:animations]; |
| 185 |
| 186 // Set self as delegate so self receives -animationDidStop:finished:; |
| 187 [animationGroup setDelegate:self]; |
| 188 [animationGroup setTimingFunction:mediaFunction]; |
| 189 [animationGroup gtm_setDuration:0.6]; |
| 190 [layer addAnimation:animationGroup forKey:@"downloadOpacityAndPosition"]; |
| 191 |
| 110 observer_.reset(new DownloadAnimationTabObserver(self, tabContents)); | 192 observer_.reset(new DownloadAnimationTabObserver(self, tabContents)); |
| 111 isComplete_ = NO; | |
| 112 } | 193 } |
| 113 return self; | 194 return self; |
| 114 } | 195 } |
| 115 | 196 |
| 116 // Common clean up code. | 197 - (void)dealloc { |
| 117 - (void)animationComplete { | 198 [[NSNotificationCenter defaultCenter] removeObserver:self]; |
| 118 if (isComplete_) | 199 [super dealloc]; |
| 119 return; | |
| 120 isComplete_ = YES; | |
| 121 [view_ setWantsLayer:NO]; | |
| 122 [layer_ removeAllAnimations]; | |
| 123 [layer_ removeFromSuperlayer]; | |
| 124 } | 200 } |
| 125 | 201 |
| 126 // Set up the animation and let Core Animation do all the hard work. | 202 // Called when the parent window is resized. |
| 127 - (void)animate { | 203 - (void)parentWindowChanged:(NSNotification*)notification { |
| 128 // Figure out the positioning in the current tab. We try to position ourselves | 204 NSWindow* parentWindow = [self parentWindow]; |
| 129 // against the left edge, and three times the download image's height from the | 205 DCHECK([[notification object] isEqual:parentWindow]); |
| 130 // bottom of the tab, assuming there is enough room. If there isn't enough, we | 206 NSRect parentFrame = [parentWindow frame]; |
| 131 // won't show the animation and let the shelf speak for itself. | 207 NSRect frame = parentFrame; |
| 132 gfx::Rect bounds; | 208 frame.size.width = MIN(imageWidth_, NSWidth(parentFrame)); |
| 133 tabContents_->GetContainerBounds(&bounds); | 209 [self setFrame:frame display:YES]; |
| 134 int imageWidth = CGImageGetWidth(image_); | |
| 135 int imageHeight = CGImageGetHeight(image_); | |
| 136 CGRect imageBounds = CGRectMake(0, 0, imageWidth, imageHeight); | |
| 137 | |
| 138 // Sanity check the size in case there's no room to display the animation. | |
| 139 if (bounds.height() < imageHeight) { | |
| 140 [self release]; | |
| 141 return; | |
| 142 } | |
| 143 | |
| 144 int animationHeight = std::min(bounds.height(), 3 * imageHeight); | |
| 145 NSPoint start = NSMakePoint(0, animationHeight); | |
| 146 NSPoint stop = NSMakePoint(0, 0); // Bottom of the tab. | |
| 147 | |
| 148 // Set for the duration of the animation, or we won't see our layer. We reset | |
| 149 // this in the completion callback. Layers by default scale their content to | |
| 150 // fit, but we need them to clip their content instead, or else renderer | |
| 151 // resizes will look janky. | |
| 152 [view_ setWantsLayer:YES]; | |
| 153 [[view_ layer] setContentsGravity:kCAGravityTopLeft]; | |
| 154 | |
| 155 // CALayer initalization. | |
| 156 layer_.reset([[CALayer layer] retain]); | |
| 157 [layer_ setNeedsDisplay]; | |
| 158 [layer_ setContents:(id)image_.get()]; | |
| 159 [layer_ setAnchorPoint:CGPointMake(0, 0)]; | |
| 160 [layer_ setFrame:imageBounds]; | |
| 161 [[view_ layer] addSublayer:layer_]; | |
| 162 | |
| 163 // Positional animation. | |
| 164 CABasicAnimation *animation = | |
| 165 [CABasicAnimation animationWithKeyPath:@"position"]; | |
| 166 [animation setFromValue:[NSValue valueWithPoint:start]]; | |
| 167 [animation setToValue:[NSValue valueWithPoint:stop]]; | |
| 168 [animation gtm_setDuration:0.6]; | |
| 169 CAMediaTimingFunction* mediaFunction = | |
| 170 [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseOut]; | |
| 171 [animation setTimingFunction:mediaFunction]; | |
| 172 [animation setDelegate:self]; | |
| 173 [layer_ addAnimation:animation forKey:@"downloadPosition"]; | |
| 174 | |
| 175 // Opacity animation. | |
| 176 animation = [CABasicAnimation animationWithKeyPath:@"opacity"]; | |
| 177 [animation setFromValue:[NSNumber numberWithFloat:1.0]]; | |
| 178 [animation setToValue:[NSNumber numberWithFloat:0.0]]; | |
| 179 [animation gtm_setDuration:1.5]; // Longer, so it doesn't fade too much. | |
| 180 mediaFunction = | |
| 181 [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseOut]; | |
| 182 [animation setTimingFunction:mediaFunction]; | |
| 183 [layer_ addAnimation:animation forKey:@"downloadOpacity"]; | |
| 184 } | 210 } |
| 185 | 211 |
| 186 // CAAnimation delegate method called when the animation is complete. | 212 // CAAnimation delegate method called when the animation is complete. |
| 187 - (void)animationDidStop:(CAAnimation *)animation finished:(BOOL)flag { | 213 - (void)animationDidStop:(CAAnimation *)animation finished:(BOOL)flag { |
| 188 [self animationComplete]; | 214 [self animationComplete]; |
| 189 [self release]; | |
| 190 } | 215 } |
| 191 | 216 |
| 192 @end // implementation DownloadStartedAnimationMac | 217 // Common clean up code. |
| 218 - (void)animationComplete { |
| 219 [[self parentWindow] removeChildWindow:self]; |
| 220 [self close]; |
| 221 } |
| 193 | 222 |
| 223 + (void)startAnimationWithTabContents:(TabContents*)contents { |
| 224 // Will be deleted when the animation is complete in -animationComplete. |
| 225 [[self alloc] initWithTabContents:contents]; |
| 226 } |
| 194 | 227 |
| 195 // static | 228 @end |
| 229 |
| 196 void DownloadStartedAnimation::Show(TabContents* tab_contents) { | 230 void DownloadStartedAnimation::Show(TabContents* tab_contents) { |
| 197 DCHECK(tab_contents); | 231 DCHECK(tab_contents); |
| 198 | 232 |
| 199 // Will be deleted when the animation is complete. | 233 // Will be deleted when the animation is complete. |
| 200 DownloadStartedAnimationMac* downloadArrow = | 234 [DownloadStartedAnimationMac startAnimationWithTabContents:tab_contents]; |
| 201 [[DownloadStartedAnimationMac alloc] initWithTabContents:tab_contents]; | |
| 202 | |
| 203 // Go! | |
| 204 [downloadArrow animate]; | |
| 205 } | 235 } |
| OLD | NEW |