1// Copyright (c) 2010 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#import "chrome/browser/ui/cocoa/animatable_image.h" 6 7#include "base/logging.h" 8#import "base/mac/mac_util.h" 9#include "base/mac/scoped_cftyperef.h" 10#import "third_party/GTM/AppKit/GTMNSAnimation+Duration.h" 11 12@interface AnimatableImage (Private) 13- (void)setLayerContents:(CALayer*)layer; 14@end 15 16@implementation AnimatableImage 17 18@synthesize startFrame = startFrame_; 19@synthesize endFrame = endFrame_; 20@synthesize startOpacity = startOpacity_; 21@synthesize endOpacity = endOpacity_; 22@synthesize duration = duration_; 23 24- (id)initWithImage:(NSImage*)image 25 animationFrame:(NSRect)animationFrame { 26 if ((self = [super initWithContentRect:animationFrame 27 styleMask:NSBorderlessWindowMask 28 backing:NSBackingStoreBuffered 29 defer:NO])) { 30 DCHECK(image); 31 image_.reset([image retain]); 32 duration_ = 1.0; 33 startOpacity_ = 1.0; 34 endOpacity_ = 1.0; 35 36 [self setOpaque:NO]; 37 [self setBackgroundColor:[NSColor clearColor]]; 38 [self setIgnoresMouseEvents:YES]; 39 40 // Must be set or else self will be leaked. 41 [self setReleasedWhenClosed:YES]; 42 } 43 return self; 44} 45 46- (void)startAnimation { 47 // Set up the root layer. By calling -setLayer: followed by -setWantsLayer: 48 // the view becomes a layer hosting view as opposed to a layer backed view. 49 NSView* view = [self contentView]; 50 CALayer* rootLayer = [CALayer layer]; 51 [view setLayer:rootLayer]; 52 [view setWantsLayer:YES]; 53 54 // Create the layer that will be animated. 55 CALayer* layer = [CALayer layer]; 56 [self setLayerContents:layer]; 57 [layer setAnchorPoint:CGPointMake(0, 1)]; 58 [layer setFrame:[self startFrame]]; 59 [layer setNeedsDisplayOnBoundsChange:YES]; 60 [rootLayer addSublayer:layer]; 61 62 // Common timing function for all animations. 63 CAMediaTimingFunction* mediaFunction = 64 [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseOut]; 65 66 // Animate the bounds only if the image is resized. 67 CABasicAnimation* boundsAnimation = nil; 68 if (CGRectGetWidth([self startFrame]) != CGRectGetWidth([self endFrame]) || 69 CGRectGetHeight([self startFrame]) != CGRectGetHeight([self endFrame])) { 70 boundsAnimation = [CABasicAnimation animationWithKeyPath:@"bounds"]; 71 NSRect startRect = NSMakeRect(0, 0, 72 CGRectGetWidth([self startFrame]), 73 CGRectGetHeight([self startFrame])); 74 [boundsAnimation setFromValue:[NSValue valueWithRect:startRect]]; 75 NSRect endRect = NSMakeRect(0, 0, 76 CGRectGetWidth([self endFrame]), 77 CGRectGetHeight([self endFrame])); 78 [boundsAnimation setToValue:[NSValue valueWithRect:endRect]]; 79 [boundsAnimation gtm_setDuration:[self duration] 80 eventMask:NSLeftMouseUpMask]; 81 [boundsAnimation setTimingFunction:mediaFunction]; 82 } 83 84 // Positional animation. 85 CABasicAnimation* positionAnimation = 86 [CABasicAnimation animationWithKeyPath:@"position"]; 87 [positionAnimation setFromValue: 88 [NSValue valueWithPoint:NSPointFromCGPoint([self startFrame].origin)]]; 89 [positionAnimation setToValue: 90 [NSValue valueWithPoint:NSPointFromCGPoint([self endFrame].origin)]]; 91 [positionAnimation gtm_setDuration:[self duration] 92 eventMask:NSLeftMouseUpMask]; 93 [positionAnimation setTimingFunction:mediaFunction]; 94 95 // Opacity animation. 96 CABasicAnimation* opacityAnimation = 97 [CABasicAnimation animationWithKeyPath:@"opacity"]; 98 [opacityAnimation setFromValue: 99 [NSNumber numberWithFloat:[self startOpacity]]]; 100 [opacityAnimation setToValue:[NSNumber numberWithFloat:[self endOpacity]]]; 101 [opacityAnimation gtm_setDuration:[self duration] 102 eventMask:NSLeftMouseUpMask]; 103 [opacityAnimation setTimingFunction:mediaFunction]; 104 // Set the delegate just for one of the animations so that this window can 105 // be closed upon completion. 106 [opacityAnimation setDelegate:self]; 107 108 // The CAAnimations only affect the presentational value of a layer, not the 109 // model value. This means that after the animation is done, it can flicker 110 // back to the original values. To avoid this, create an implicit animation of 111 // the values, which are then overridden with the CABasicAnimations. 112 // 113 // Ideally, a call to |-setBounds:| should be here, but, for reasons that 114 // are not understood, doing so causes the animation to break. 115 [layer setPosition:[self endFrame].origin]; 116 [layer setOpacity:[self endOpacity]]; 117 118 // Start the animations. 119 [CATransaction begin]; 120 [CATransaction setValue:[NSNumber numberWithFloat:[self duration]] 121 forKey:kCATransactionAnimationDuration]; 122 if (boundsAnimation) { 123 [layer addAnimation:boundsAnimation forKey:@"bounds"]; 124 } 125 [layer addAnimation:positionAnimation forKey:@"position"]; 126 [layer addAnimation:opacityAnimation forKey:@"opacity"]; 127 [CATransaction commit]; 128} 129 130// Sets the layer contents by converting the NSImage to a CGImageRef. This will 131// rasterize PDF resources. 132- (void)setLayerContents:(CALayer*)layer { 133 base::mac::ScopedCFTypeRef<CGImageRef> image( 134 base::mac::CopyNSImageToCGImage(image_.get())); 135 // Create the layer that will be animated. 136 [layer setContents:(id)image.get()]; 137} 138 139// CAAnimation delegate method called when the animation is complete. 140- (void)animationDidStop:(CAAnimation*)animation finished:(BOOL)flag { 141 // Close the window, releasing self. 142 [self close]; 143} 144 145@end 146