blob: ab18cdf06047d10efb95d215595aca9d575f9976 (
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
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
|
// 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. It will clip its sublayers,
// if they exceed the boundary.
CALayer* layer = [CALayer layer];
layer.masksToBounds = YES;
imageLayer_ = [CALayer layer];
imageLayer_.autoresizingMask = kCALayerWidthSizable | kCALayerHeightSizable;
[layer addSublayer:imageLayer_];
imageLayer_.frame = layer.bounds;
[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];
}
}
- (void)viewDidMoveToWindow {
[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
if ([self window] && ![[self window] isMiniaturized] &&
![[notification name] isEqualToString:
NSWindowWillMiniaturizeNotification]) {
if ([imageLayer_ animationForKey:[spriteAnimation_ keyPath]] == nil) {
[imageLayer_ addAnimation:spriteAnimation_.get()
forKey:[spriteAnimation_ keyPath]];
}
} else {
[imageLayer_ removeAnimationForKey:[spriteAnimation_ keyPath]];
}
}
}
- (void)setImage:(NSImage*)image {
ScopedCAActionDisabler disabler;
if (spriteAnimation_.get()) {
[imageLayer_ removeAnimationForKey:[spriteAnimation_ keyPath]];
spriteAnimation_.reset();
}
[imageLayer_ 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.
[imageLayer_ 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 {
if (!animate || [imageLayer_ contents] == nil) {
[self setImage:image];
} else {
// Animate away the icon.
CABasicAnimation* animation =
[CABasicAnimation animationWithKeyPath:@"position.y"];
CGFloat height = CGRectGetHeight([imageLayer_ bounds]);
[animation setToValue:@(-height)];
[animation setDuration:kFrameDuration * height];
// Don't remove on completion to prevent the presentation layer from
// snapping back to the model layer's value.
// It will instead be removed when we add the return animation because they
// have the same key.
[animation setRemovedOnCompletion:NO];
[animation setFillMode:kCAFillModeForwards];
[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]];
[imageLayer_ addAnimation:reverseAnimation forKey:@"position"];
}];
[imageLayer_ addAnimation:animation forKey:@"position"];
[CATransaction commit];
}
}
- (BOOL)layer:(CALayer*)layer
shouldInheritContentsScale:(CGFloat)scale
fromWindow:(NSWindow*)window {
return YES;
}
@end
|