Home | History | Annotate | Download | only in cocoa
      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