summaryrefslogtreecommitdiffstats
path: root/chrome/browser/ui/cocoa/sprite_view.mm
blob: a29db0cbe64d1c1cfb572537a5e8c27bec526c3a (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
// Copyright 2014 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/sprite_view.h"

#import <QuartzCore/CAAnimation.h>
#import <QuartzCore/CATransaction.h>

#include "base/logging.h"
#include "ui/base/cocoa/animation_utils.h"

static const CGFloat kFrameDuration = 0.03;  // 30ms for each animation frame.

@implementation SpriteView

- (instancetype)initWithFrame:(NSRect)frame {
  if (self = [super initWithFrame:frame]) {
    // A layer-hosting view.
    CALayer* layer = [CALayer layer];
    [layer setDelegate:self];
    [self setLayer:layer];
    [self setWantsLayer:YES];
  }
  return self;
}

- (void)dealloc {
  [[NSNotificationCenter defaultCenter] removeObserver:self];
  [super dealloc];
}

- (void)viewWillMoveToWindow:(NSWindow*)newWindow {
  if ([self window]) {
    [[NSNotificationCenter defaultCenter]
        removeObserver:self
                  name:NSWindowWillMiniaturizeNotification
                object:[self window]];
    [[NSNotificationCenter defaultCenter]
        removeObserver:self
                  name:NSWindowDidDeminiaturizeNotification
                object:[self window]];
  }

  if (newWindow) {
    [[NSNotificationCenter defaultCenter]
        addObserver:self
           selector:@selector(updateAnimation:)
               name:NSWindowWillMiniaturizeNotification
             object:newWindow];
    [[NSNotificationCenter defaultCenter]
        addObserver:self
           selector:@selector(updateAnimation:)
               name:NSWindowDidDeminiaturizeNotification
             object:newWindow];
  }

  [self updateAnimation:nil];
}

- (void)updateAnimation:(NSNotification*)notification {
  if (spriteAnimation_.get()) {
    // Only animate the sprites if we are attached to a window, and that window
    // is not currently minimized or in the middle of a minimize animation.
    // http://crbug.com/350329
    CALayer* layer = [self layer];
    if ([self window] && ![[self window] isMiniaturized]) {
      if ([layer animationForKey:[spriteAnimation_ keyPath]] == nil)
        [layer addAnimation:spriteAnimation_.get()
                     forKey:[spriteAnimation_ keyPath]];
    } else {
      [layer removeAnimationForKey:[spriteAnimation_ keyPath]];
    }
  }
}

- (void)setImage:(NSImage*)image {
  ScopedCAActionDisabler disabler;
  CALayer* layer = [self layer];

  if (spriteAnimation_.get()) {
    [layer removeAnimationForKey:[spriteAnimation_ keyPath]];
    spriteAnimation_.reset();
  }

  [layer setContents:image];

  if (image != nil) {
    NSSize imageSize = [image size];
    NSSize spriteSize = NSMakeSize(imageSize.height, imageSize.height);
    [self setFrameSize:spriteSize];

    const NSUInteger spriteCount = imageSize.width / spriteSize.width;
    const CGFloat unitWidth = 1.0 / spriteCount;

    // Show the first (leftmost) sprite.
    [layer setContentsRect:CGRectMake(0, 0, unitWidth, 1.0)];

    if (spriteCount > 1) {
      // Animate the sprite offsets, we use a keyframe animation with discrete
      // calculation mode to prevent interpolation.
      NSMutableArray* xOffsets = [NSMutableArray arrayWithCapacity:spriteCount];
      for (NSUInteger i = 0; i < spriteCount; ++i) {
        [xOffsets addObject:@(i * unitWidth)];
      }
      CAKeyframeAnimation* animation =
          [CAKeyframeAnimation animationWithKeyPath:@"contentsRect.origin.x"];
      [animation setValues:xOffsets];
      [animation setCalculationMode:kCAAnimationDiscrete];
      [animation setRepeatCount:HUGE_VALF];
      [animation setDuration:kFrameDuration * [xOffsets count]];
      spriteAnimation_.reset([animation retain]);

      [self updateAnimation:nil];
    }
  }
}

- (void)setImage:(NSImage*)image withToastAnimation:(BOOL)animate {
  CALayer* layer = [self layer];
  if (!animate || [layer contents] == nil) {
    [self setImage:image];
  } else {
    // Animate away the icon.
    CABasicAnimation* animation =
        [CABasicAnimation animationWithKeyPath:@"position.y"];
    CGFloat height = CGRectGetHeight([layer bounds]);
    [animation setToValue:@(-height)];
    [animation setDuration:kFrameDuration * height];

    [CATransaction begin];
    [CATransaction setCompletionBlock:^{
        // At the end of the animation, change to the new image and animate
        // it back to position.
        [self setImage:image];

        CABasicAnimation* reverseAnimation =
            [CABasicAnimation animationWithKeyPath:[animation keyPath]];
        [reverseAnimation setFromValue:[animation toValue]];
        [reverseAnimation setToValue:[animation fromValue]];
        [reverseAnimation setDuration:[animation duration]];
        [layer addAnimation:reverseAnimation forKey:nil];
    }];
    [layer addAnimation:animation forKey:nil];
    [CATransaction commit];
  }
}

- (BOOL)layer:(CALayer*)layer
    shouldInheritContentsScale:(CGFloat)scale
                    fromWindow:(NSWindow*)window {
  return YES;
}

@end