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.
5 #import "chrome/browser/ui/cocoa/animatable_image.h"
7 #include "base/logging.h"
8 #import "third_party/google_toolbox_for_mac/src/AppKit/GTMNSAnimation+Duration.h"
10 @implementation AnimatableImage
12 @synthesize startFrame = startFrame_;
13 @synthesize endFrame = endFrame_;
14 @synthesize startOpacity = startOpacity_;
15 @synthesize endOpacity = endOpacity_;
16 @synthesize duration = duration_;
18 - (id)initWithImage:(NSImage*)image
19 animationFrame:(NSRect)animationFrame {
20 if ((self = [super initWithContentRect:animationFrame
21 styleMask:NSBorderlessWindowMask
22 backing:NSBackingStoreBuffered
25 image_.reset([image retain]);
31 [self setBackgroundColor:[NSColor clearColor]];
32 [self setIgnoresMouseEvents:YES];
34 // Must be set or else self will be leaked.
35 [self setReleasedWhenClosed:YES];
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];
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];
56 // Common timing function for all animations.
57 CAMediaTimingFunction* mediaFunction =
58 [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseOut];
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];
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];
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];
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.
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]];
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"];
119 [layer addAnimation:positionAnimation forKey:@"position"];
120 [layer addAnimation:opacityAnimation forKey:@"opacity"];
121 [CATransaction commit];
124 // CAAnimation delegate method called when the animation is complete.
125 - (void)animationDidStop:(CAAnimation*)animation finished:(BOOL)flag {
126 // Close the window, releasing self.