Home | History | Annotate | Download | only in cocoa
      1 // Copyright 2014 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/sprite_view.h"
      6 
      7 #import <QuartzCore/CAAnimation.h>
      8 #import <QuartzCore/CATransaction.h>
      9 
     10 #include "base/logging.h"
     11 #include "ui/base/cocoa/animation_utils.h"
     12 
     13 static const CGFloat kFrameDuration = 0.03;  // 30ms for each animation frame.
     14 
     15 @implementation SpriteView
     16 
     17 - (instancetype)initWithFrame:(NSRect)frame {
     18   if (self = [super initWithFrame:frame]) {
     19     // A layer-hosting view. It will clip its sublayers,
     20     // if they exceed the boundary.
     21     CALayer* layer = [CALayer layer];
     22     layer.masksToBounds = YES;
     23 
     24     imageLayer_ = [CALayer layer];
     25     imageLayer_.autoresizingMask = kCALayerWidthSizable | kCALayerHeightSizable;
     26 
     27     [layer addSublayer:imageLayer_];
     28     imageLayer_.frame = layer.bounds;
     29     [layer setDelegate:self];
     30     [self setLayer:layer];
     31     [self setWantsLayer:YES];
     32   }
     33   return self;
     34 }
     35 
     36 - (void)dealloc {
     37   [[NSNotificationCenter defaultCenter] removeObserver:self];
     38   [super dealloc];
     39 }
     40 
     41 - (void)viewWillMoveToWindow:(NSWindow*)newWindow {
     42   if ([self window]) {
     43     [[NSNotificationCenter defaultCenter]
     44         removeObserver:self
     45                   name:NSWindowWillMiniaturizeNotification
     46                 object:[self window]];
     47     [[NSNotificationCenter defaultCenter]
     48         removeObserver:self
     49                   name:NSWindowDidDeminiaturizeNotification
     50                 object:[self window]];
     51   }
     52 
     53   if (newWindow) {
     54     [[NSNotificationCenter defaultCenter]
     55         addObserver:self
     56            selector:@selector(updateAnimation:)
     57                name:NSWindowWillMiniaturizeNotification
     58              object:newWindow];
     59     [[NSNotificationCenter defaultCenter]
     60         addObserver:self
     61            selector:@selector(updateAnimation:)
     62                name:NSWindowDidDeminiaturizeNotification
     63              object:newWindow];
     64   }
     65 }
     66 
     67 - (void)viewDidMoveToWindow {
     68   [self updateAnimation:nil];
     69 }
     70 
     71 - (void)updateAnimation:(NSNotification*)notification {
     72   if (spriteAnimation_.get()) {
     73     // Only animate the sprites if we are attached to a window, and that window
     74     // is not currently minimized or in the middle of a minimize animation.
     75     // http://crbug.com/350329
     76     if ([self window] && ![[self window] isMiniaturized]) {
     77       if ([imageLayer_ animationForKey:[spriteAnimation_ keyPath]] == nil)
     78         [imageLayer_ addAnimation:spriteAnimation_.get()
     79                      forKey:[spriteAnimation_ keyPath]];
     80     } else {
     81       [imageLayer_ removeAnimationForKey:[spriteAnimation_ keyPath]];
     82     }
     83   }
     84 }
     85 
     86 - (void)setImage:(NSImage*)image {
     87   ScopedCAActionDisabler disabler;
     88 
     89   if (spriteAnimation_.get()) {
     90     [imageLayer_ removeAnimationForKey:[spriteAnimation_ keyPath]];
     91     spriteAnimation_.reset();
     92   }
     93 
     94   [imageLayer_ setContents:image];
     95 
     96   if (image != nil) {
     97     NSSize imageSize = [image size];
     98     NSSize spriteSize = NSMakeSize(imageSize.height, imageSize.height);
     99     [self setFrameSize:spriteSize];
    100 
    101     const NSUInteger spriteCount = imageSize.width / spriteSize.width;
    102     const CGFloat unitWidth = 1.0 / spriteCount;
    103 
    104     // Show the first (leftmost) sprite.
    105     [imageLayer_ setContentsRect:CGRectMake(0, 0, unitWidth, 1.0)];
    106 
    107     if (spriteCount > 1) {
    108       // Animate the sprite offsets, we use a keyframe animation with discrete
    109       // calculation mode to prevent interpolation.
    110       NSMutableArray* xOffsets = [NSMutableArray arrayWithCapacity:spriteCount];
    111       for (NSUInteger i = 0; i < spriteCount; ++i) {
    112         [xOffsets addObject:@(i * unitWidth)];
    113       }
    114       CAKeyframeAnimation* animation =
    115           [CAKeyframeAnimation animationWithKeyPath:@"contentsRect.origin.x"];
    116       [animation setValues:xOffsets];
    117       [animation setCalculationMode:kCAAnimationDiscrete];
    118       [animation setRepeatCount:HUGE_VALF];
    119       [animation setDuration:kFrameDuration * [xOffsets count]];
    120       spriteAnimation_.reset([animation retain]);
    121 
    122       [self updateAnimation:nil];
    123     }
    124   }
    125 }
    126 
    127 - (void)setImage:(NSImage*)image withToastAnimation:(BOOL)animate {
    128   if (!animate || [imageLayer_ contents] == nil) {
    129     [self setImage:image];
    130   } else {
    131     // Animate away the icon.
    132     CABasicAnimation* animation =
    133         [CABasicAnimation animationWithKeyPath:@"position.y"];
    134     CGFloat height = CGRectGetHeight([imageLayer_ bounds]);
    135     [animation setToValue:@(-height)];
    136     [animation setDuration:kFrameDuration * height];
    137 
    138     // Don't remove on completion to prevent the presentation layer from
    139     // snapping back to the model layer's value.
    140     // It will instead be removed when we add the return animation because they
    141     // have the same key.
    142     [animation setRemovedOnCompletion:NO];
    143     [animation setFillMode:kCAFillModeForwards];
    144 
    145     [CATransaction begin];
    146     [CATransaction setCompletionBlock:^{
    147         // At the end of the animation, change to the new image and animate
    148         // it back to position.
    149         [self setImage:image];
    150 
    151         CABasicAnimation* reverseAnimation =
    152             [CABasicAnimation animationWithKeyPath:[animation keyPath]];
    153         [reverseAnimation setFromValue:[animation toValue]];
    154         [reverseAnimation setToValue:[animation fromValue]];
    155         [reverseAnimation setDuration:[animation duration]];
    156         [imageLayer_ addAnimation:reverseAnimation forKey:@"position"];
    157     }];
    158     [imageLayer_ addAnimation:animation forKey:@"position"];
    159     [CATransaction commit];
    160   }
    161 }
    162 
    163 - (BOOL)layer:(CALayer*)layer
    164     shouldInheritContentsScale:(CGFloat)scale
    165                     fromWindow:(NSWindow*)window {
    166   return YES;
    167 }
    168 
    169 @end
    170