• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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