Chromium Code Reviews| Index: chrome/browser/cocoa/animatable_image.mm |
| diff --git a/chrome/browser/cocoa/animatable_image.mm b/chrome/browser/cocoa/animatable_image.mm |
| new file mode 100644 |
| index 0000000000000000000000000000000000000000..2a3a72fa00903146b38ee3ecef2d538bc9667383 |
| --- /dev/null |
| +++ b/chrome/browser/cocoa/animatable_image.mm |
| @@ -0,0 +1,164 @@ |
| +// 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/cocoa/animatable_image.h" |
| + |
| +#include "base/logging.h" |
| +#include "base/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. |
| + [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]; |
| +} |
| + |
| +// CALayer expects a CGImageRef contents. If the image is a PDF, 10.5 CGImage |
| +// cannot handle the conversion to bitmap. To get it to work, rasterize the |
| +// image into a bitmap CGImageRef. This is based loosely on |
| +// http://www.cocoadev.com/index.pl?CGImageRef. |
| +- (void)setLayerContents:(CALayer*)layer { |
| + NSSize size = [image_ size]; |
| + CGContextRef context = |
| + CGBitmapContextCreate(NULL, // Allow CG to allocate memory. |
| + size.width, |
| + size.height, |
| + 8, // bitsPerComponent |
| + 0, // bytesPerRow - CG will calculate by default. |
| + [[NSColorSpace genericRGBColorSpace] CGColorSpace], |
| + kCGBitmapByteOrder32Host | |
| + kCGImageAlphaPremultipliedFirst); |
| + |
| + [NSGraphicsContext saveGraphicsState]; |
| + [NSGraphicsContext setCurrentContext: |
| + [NSGraphicsContext graphicsContextWithGraphicsPort:context flipped:NO]]; |
| + [image_ drawInRect:NSMakeRect(0,0, size.width, size.height) |
| + fromRect:NSZeroRect |
| + operation:NSCompositeCopy |
| + fraction:1.0]; |
| + [NSGraphicsContext restoreGraphicsState]; |
|
Nico
2010/07/19 23:00:35
Please put this into a utility function somewhere
|
| + |
| + scoped_cftyperef<CGImageRef> cgImage(CGBitmapContextCreateImage(context)); |
| + CGContextRelease(context); |
| + |
| + // Create the layer that will be animated. |
| + [layer setContents:(id)cgImage.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 |