Upstream version 9.38.198.0
[platform/framework/web/crosswalk.git] / src / chrome / browser / ui / cocoa / sprite_view.mm
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