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