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
|
// 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]) {
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
|