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