blob: d2fd2da4a0ac7d5b588e167dcc0d71bd1b21e0e1 [file] [log] [blame]
// 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 "base/mac/mac_util.h"
#include "base/mac/scoped_cftyperef.h"
#import "third_party/GTM/AppKit/GTMNSAnimation+Duration.h"
@interface AnimatableImage (Private)
- (void)setLayerContents:(CALayer*)layer;
@end
@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];
[self setLayerContents:layer];
[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];
}
// Sets the layer contents by converting the NSImage to a CGImageRef. This will
// rasterize PDF resources.
- (void)setLayerContents:(CALayer*)layer {
base::ScopedCFTypeRef<CGImageRef> image(
base::mac::CopyNSImageToCGImage(image_.get()));
// Create the layer that will be animated.
[layer setContents:(id)image.get()];
}
// CAAnimation delegate method called when the animation is complete.
- (void)animationDidStop:(CAAnimation*)animation finished:(BOOL)flag {
// Close the window, releasing self.
[self close];
}
@end