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.
5 #import "chrome/browser/ui/cocoa/sprite_view.h"
7 #import <QuartzCore/CAAnimation.h>
8 #import <QuartzCore/CATransaction.h>
10 #include "base/logging.h"
11 #include "ui/base/cocoa/animation_utils.h"
13 static const CGFloat kFrameDuration = 0.03; // 30ms for each animation frame.
15 @implementation SpriteView
17 - (instancetype)initWithFrame:(NSRect)frame {
18 if (self = [super initWithFrame:frame]) {
19 // A layer-hosting view.
20 CALayer* layer = [CALayer layer];
21 [layer setDelegate:self];
22 [self setLayer:layer];
23 [self setWantsLayer:YES];
29 [[NSNotificationCenter defaultCenter] removeObserver:self];
33 - (void)viewWillMoveToWindow:(NSWindow*)newWindow {
35 [[NSNotificationCenter defaultCenter]
37 name:NSWindowWillMiniaturizeNotification
38 object:[self window]];
39 [[NSNotificationCenter defaultCenter]
41 name:NSWindowDidDeminiaturizeNotification
42 object:[self window]];
46 [[NSNotificationCenter defaultCenter]
48 selector:@selector(updateAnimation:)
49 name:NSWindowWillMiniaturizeNotification
51 [[NSNotificationCenter defaultCenter]
53 selector:@selector(updateAnimation:)
54 name:NSWindowDidDeminiaturizeNotification
59 - (void)viewDidMoveToWindow {
60 [self updateAnimation:nil];
63 - (void)updateAnimation:(NSNotification*)notification {
64 if (spriteAnimation_.get()) {
65 // Only animate the sprites if we are attached to a window, and that window
66 // is not currently minimized or in the middle of a minimize animation.
67 // http://crbug.com/350329
68 CALayer* layer = [self layer];
69 if ([self window] && ![[self window] isMiniaturized]) {
70 if ([layer animationForKey:[spriteAnimation_ keyPath]] == nil)
71 [layer addAnimation:spriteAnimation_.get()
72 forKey:[spriteAnimation_ keyPath]];
74 [layer removeAnimationForKey:[spriteAnimation_ keyPath]];
79 - (void)setImage:(NSImage*)image {
80 ScopedCAActionDisabler disabler;
81 CALayer* layer = [self layer];
83 if (spriteAnimation_.get()) {
84 [layer removeAnimationForKey:[spriteAnimation_ keyPath]];
85 spriteAnimation_.reset();
88 [layer setContents:image];
91 NSSize imageSize = [image size];
92 NSSize spriteSize = NSMakeSize(imageSize.height, imageSize.height);
93 [self setFrameSize:spriteSize];
95 const NSUInteger spriteCount = imageSize.width / spriteSize.width;
96 const CGFloat unitWidth = 1.0 / spriteCount;
98 // Show the first (leftmost) sprite.
99 [layer setContentsRect:CGRectMake(0, 0, unitWidth, 1.0)];
101 if (spriteCount > 1) {
102 // Animate the sprite offsets, we use a keyframe animation with discrete
103 // calculation mode to prevent interpolation.
104 NSMutableArray* xOffsets = [NSMutableArray arrayWithCapacity:spriteCount];
105 for (NSUInteger i = 0; i < spriteCount; ++i) {
106 [xOffsets addObject:@(i * unitWidth)];
108 CAKeyframeAnimation* animation =
109 [CAKeyframeAnimation animationWithKeyPath:@"contentsRect.origin.x"];
110 [animation setValues:xOffsets];
111 [animation setCalculationMode:kCAAnimationDiscrete];
112 [animation setRepeatCount:HUGE_VALF];
113 [animation setDuration:kFrameDuration * [xOffsets count]];
114 spriteAnimation_.reset([animation retain]);
116 [self updateAnimation:nil];
121 - (void)setImage:(NSImage*)image withToastAnimation:(BOOL)animate {
122 CALayer* layer = [self layer];
123 if (!animate || [layer contents] == nil) {
124 [self setImage:image];
126 // Animate away the icon.
127 CABasicAnimation* animation =
128 [CABasicAnimation animationWithKeyPath:@"position.y"];
129 CGFloat height = CGRectGetHeight([layer bounds]);
130 [animation setToValue:@(-height)];
131 [animation setDuration:kFrameDuration * height];
133 // Don't remove on completion to prevent the presentation layer from
134 // snapping back to the model layer's value.
135 // It will instead be removed when we add the return animation because they
136 // have the same key.
137 [animation setRemovedOnCompletion:NO];
138 [animation setFillMode:kCAFillModeForwards];
140 [CATransaction begin];
141 [CATransaction setCompletionBlock:^{
142 // At the end of the animation, change to the new image and animate
143 // it back to position.
144 [self setImage:image];
146 CABasicAnimation* reverseAnimation =
147 [CABasicAnimation animationWithKeyPath:[animation keyPath]];
148 [reverseAnimation setFromValue:[animation toValue]];
149 [reverseAnimation setToValue:[animation fromValue]];
150 [reverseAnimation setDuration:[animation duration]];
151 [layer addAnimation:reverseAnimation forKey:@"position"];
153 [layer addAnimation:animation forKey:@"position"];
154 [CATransaction commit];
158 - (BOOL)layer:(CALayer*)layer
159 shouldInheritContentsScale:(CGFloat)scale
160 fromWindow:(NSWindow*)window {