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/google_toolbox_for_mac/src/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