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