// Copyright (c) 2009 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. // // This file contains the Mac implementation the download animation, displayed // at the start of a download. The animation produces an arrow pointing // downwards and animates towards the bottom of the window where the new // download appears in the download shelf. #include "chrome/browser/download/download_started_animation.h" #import #include "app/resource_bundle.h" #include "base/scoped_cftyperef.h" #include "chrome/browser/tab_contents/tab_contents.h" #include "chrome/browser/tab_contents/tab_contents_view_mac.h" #include "chrome/common/notification_registrar.h" #include "chrome/common/notification_service.h" #include "grit/theme_resources.h" #import "third_party/GTM/AppKit/GTMNSAnimation+Duration.h" #include "third_party/skia/include/utils/mac/SkCGUtils.h" class DownloadAnimationTabObserver; // A class for managing the Core Animation download animation. // Should be instantiated using +startAnimationWithTabContents:. @interface DownloadStartedAnimationMac : NSWindow { @private // The observer for the TabContents we are drawing on. scoped_ptr observer_; CGFloat imageWidth_; }; + (void)startAnimationWithTabContents:(TabContents*)tabContents; // Called by our DownloadAnimationTabObserver if the tab is hidden or closed. - (void)animationComplete; @end // A helper class to monitor tab hidden and closed notifications. If we receive // such a notification, we stop the animation. class DownloadAnimationTabObserver : public NotificationObserver { public: DownloadAnimationTabObserver(DownloadStartedAnimationMac* owner, TabContents* tab_contents) : owner_(owner), tab_contents_(tab_contents) { registrar_.Add(this, NotificationType::TAB_CONTENTS_HIDDEN, Source(tab_contents_)); registrar_.Add(this, NotificationType::TAB_CONTENTS_DESTROYED, Source(tab_contents_)); } // Runs when a tab is hidden or destroyed. Let our owner know we should end // the animation. void Observe(NotificationType type, const NotificationSource& source, const NotificationDetails& details) { registrar_.Remove(this, NotificationType::TAB_CONTENTS_HIDDEN, Source(tab_contents_)); registrar_.Remove(this, NotificationType::TAB_CONTENTS_DESTROYED, Source(tab_contents_)); [owner_ animationComplete]; } private: // The object we need to inform when we get a notification. Weak. DownloadStartedAnimationMac* owner_; // The tab we are observing. Weak. TabContents* tab_contents_; // Used for registering to receive notifications and automatic clean up. NotificationRegistrar registrar_; DISALLOW_COPY_AND_ASSIGN(DownloadAnimationTabObserver); }; @implementation DownloadStartedAnimationMac - (id)initWithTabContents:(TabContents*)tabContents { // Load the image of the download arrow. ResourceBundle& bundle = ResourceBundle::GetSharedInstance(); SkBitmap* imageBitmap = bundle.GetBitmapNamed(IDR_DOWNLOAD_ANIMATION_BEGIN); scoped_cftyperef image(SkCreateCGImageRef(*imageBitmap)); // Figure out the positioning in the current tab. Try to position the layer // against the left edge, and three times the download image's height from the // bottom of the tab, assuming there is enough room. If there isn't enough, // don't show the animation and let the shelf speak for itself. gfx::Rect bounds; tabContents->GetContainerBounds(&bounds); imageWidth_ = CGImageGetWidth(image); CGFloat imageHeight = CGImageGetHeight(image); CGRect imageBounds = CGRectMake(0, 0, imageWidth_, imageHeight); // Sanity check the size in case there's no room to display the animation. if (bounds.height() < imageHeight) { [self release]; return nil; } NSView* tabContentsView = tabContents->GetNativeView(); NSWindow* parentWindow = [tabContentsView window]; if (!parentWindow) { // The tab is no longer frontmost. [self release]; return nil; } NSPoint origin = [tabContentsView frame].origin; origin = [tabContentsView convertPoint:origin toView:nil]; origin = [parentWindow convertBaseToScreen:origin]; // Create a window to host a layer that animates the sliding and fading. CGFloat animationHeight = MIN(bounds.height(), 4 * imageHeight); NSRect frame = NSMakeRect(origin.x, origin.y, imageWidth_, animationHeight); if ((self = [super initWithContentRect:frame styleMask:NSBorderlessWindowMask backing:NSBackingStoreBuffered defer:NO])) { [self setOpaque:NO]; [self setBackgroundColor:[NSColor clearColor]]; [self setIgnoresMouseEvents:YES]; // Must be set or else self will be leaked. [self setReleasedWhenClosed:YES]; // Set up to get notified about resize events on the parent window. NSNotificationCenter* center = [NSNotificationCenter defaultCenter]; [center addObserver:self selector:@selector(parentWindowChanged:) name:NSWindowDidResizeNotification object:parentWindow]; [parentWindow addChildWindow:self ordered:NSWindowAbove]; // Set up the root layer. By calling -setLayer: followed by -setWantsLayer: // the view becomes a layer hosting view as opposed to a layer backed view. NSView* view = [self contentView]; CALayer* rootLayer = [CALayer layer]; [view setLayer:rootLayer]; [view setWantsLayer:YES]; // Create the layer that will be animated. CALayer* layer = [CALayer layer]; [layer setContents:(id)image.get()]; [layer setAnchorPoint:CGPointMake(0, 1)]; [layer setFrame:CGRectMake(0, 0, imageWidth_, imageHeight)]; [layer setNeedsDisplayOnBoundsChange:YES]; [rootLayer addSublayer:layer]; // Common timing function for all animations. CAMediaTimingFunction* mediaFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseOut]; // Positional animation. CABasicAnimation* positionAnimation = [CABasicAnimation animationWithKeyPath:@"position"]; CGFloat animationHeight = MIN(bounds.height(), 3 * imageHeight); NSPoint start = NSMakePoint(0, animationHeight); NSPoint stop = NSMakePoint(0, imageHeight); [positionAnimation setFromValue:[NSValue valueWithPoint:start]]; [positionAnimation setToValue:[NSValue valueWithPoint:stop]]; [positionAnimation gtm_setDuration:0.6 eventMask:NSLeftMouseDownMask]; [positionAnimation setTimingFunction:mediaFunction]; // Opacity animation. CABasicAnimation* opacityAnimation = [CABasicAnimation animationWithKeyPath:@"opacity"]; [opacityAnimation setFromValue:[NSNumber numberWithFloat:1.0]]; [opacityAnimation setToValue:[NSNumber numberWithFloat:0.4]]; [opacityAnimation gtm_setDuration:0.6 eventMask:NSLeftMouseDownMask]; [opacityAnimation setTimingFunction:mediaFunction]; // Group the animations together. CAAnimationGroup* animationGroup = [CAAnimationGroup animation]; NSArray* animations = [NSArray arrayWithObjects:positionAnimation, opacityAnimation, nil]; [animationGroup setAnimations:animations]; // Set self as delegate so self receives -animationDidStop:finished:; [animationGroup setDelegate:self]; [animationGroup setTimingFunction:mediaFunction]; [animationGroup gtm_setDuration:0.6 eventMask:NSLeftMouseDownMask]; [layer addAnimation:animationGroup forKey:@"downloadOpacityAndPosition"]; observer_.reset(new DownloadAnimationTabObserver(self, tabContents)); } return self; } - (void)dealloc { [[NSNotificationCenter defaultCenter] removeObserver:self]; [super dealloc]; } // Called when the parent window is resized. - (void)parentWindowChanged:(NSNotification*)notification { NSWindow* parentWindow = [self parentWindow]; DCHECK([[notification object] isEqual:parentWindow]); NSRect parentFrame = [parentWindow frame]; NSRect frame = parentFrame; frame.size.width = MIN(imageWidth_, NSWidth(parentFrame)); [self setFrame:frame display:YES]; } // CAAnimation delegate method called when the animation is complete. - (void)animationDidStop:(CAAnimation *)animation finished:(BOOL)flag { [self animationComplete]; } // Common clean up code. - (void)animationComplete { [[self parentWindow] removeChildWindow:self]; [self close]; } + (void)startAnimationWithTabContents:(TabContents*)contents { // Will be deleted when the animation is complete in -animationComplete. [[self alloc] initWithTabContents:contents]; } @end void DownloadStartedAnimation::Show(TabContents* tab_contents) { DCHECK(tab_contents); // Will be deleted when the animation is complete. [DownloadStartedAnimationMac startAnimationWithTabContents:tab_contents]; }