Revert of Add button to add new FSP services to Files app. (patchset #8 id:140001...
[chromium-blink-merge.git] / chrome / browser / ui / cocoa / spinner_view.mm
blob3063c57b93c7341f8679f094cd48bde71f2e835d
1 // Copyright 2015 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 "spinner_view.h"
7 #import <QuartzCore/QuartzCore.h>
9 #include "base/mac/scoped_cftyperef.h"
10 #include "base/mac/scoped_nsobject.h"
11 #include "skia/ext/skia_utils_mac.h"
13 namespace {
14   const CGFloat k90_Degrees           = (M_PI / 2);
15   const CGFloat k180_Degrees          = (M_PI);
16   const CGFloat k270_Degrees          = (3 * M_PI / 2);
17   const CGFloat k360_Degrees          = (2 * M_PI);
18   const CGFloat kDesign_Width         = 28.0;
19   const CGFloat kArc_Radius           = 12.5;
20   const CGFloat kArc_Length           = 58.9;
21   const CGFloat kArc_Stroke_Width     = 3.0;
22   const CGFloat kArc_Animation_Time   = 1.333;
23   const CGFloat kArc_Start_Angle      = k180_Degrees;
24   const CGFloat kArc_End_Angle        = (kArc_Start_Angle + k270_Degrees);
26   const SkColor kBlue   = SkColorSetRGB(66.0, 133.0, 244.0); // #4285f4.
27   const SkColor kRed    = SkColorSetRGB(219.0, 68.0, 55.0);  // #db4437.
28   const SkColor kYellow = SkColorSetRGB(244.0, 180.0, 0.0);  // #f4b400.
29   const SkColor kGreen  = SkColorSetRGB(15.0, 157.0, 88.0);  // #0f9d58.
32 @interface SpinnerView()
34   base::scoped_nsobject<CAAnimationGroup> spinner_animation_;
35   CAShapeLayer* shape_layer_; //  Weak.
37 @end
40 @implementation SpinnerView
42 - (instancetype)initWithFrame:(NSRect)frame {
43   if (self = [super initWithFrame:frame]) {
44     [self setWantsLayer:YES];
45   }
46   return self;
49 - (void)dealloc {
50   [[NSNotificationCenter defaultCenter] removeObserver:self];
51   [super dealloc];
54 // Return a custom CALayer for the view (called from setWantsLayer:).
55 - (CALayer *)makeBackingLayer {
56   CGRect bounds = [self bounds];
57   // The spinner was designed to be |kDesign_Width| points wide. Compute the
58   // scale factor needed to scale design parameters like |RADIUS| so that the
59   // spinner scales to fit the view's bounds.
60   CGFloat scale_factor = bounds.size.width / kDesign_Width;
62   shape_layer_ = [CAShapeLayer layer];
63   [shape_layer_ setBounds:bounds];
64   [shape_layer_ setLineWidth:kArc_Stroke_Width * scale_factor];
65   [shape_layer_ setLineCap:kCALineCapSquare];
66   [shape_layer_ setLineDashPattern:[NSArray arrayWithObject:
67       [NSNumber numberWithFloat:kArc_Length * scale_factor]]];
68   [shape_layer_ setFillColor:NULL];
70   // Create the arc that, when stroked, creates the spinner.
71   base::ScopedCFTypeRef<CGMutablePathRef> shape_path(CGPathCreateMutable());
72   CGPathAddArc(shape_path, NULL, bounds.size.width / 2.0,
73                bounds.size.height / 2.0, kArc_Radius * scale_factor,
74                kArc_Start_Angle, kArc_End_Angle, 0);
75   [shape_layer_ setPath:shape_path];
77   // Place |shape_layer_| in a parent layer so that it's easy to rotate
78   // |shape_layer_| around the center of the view.
79   CALayer* parent_layer = [CALayer layer];
80   [parent_layer setBounds:bounds];
81   [parent_layer addSublayer:shape_layer_];
82   [shape_layer_ setPosition:CGPointMake(bounds.size.width / 2.0,
83                                         bounds.size.height / 2.0)];
85   return parent_layer;
88 // The spinner animation consists of four cycles that it continuously repeats.
89 // Each cycle consists of one complete rotation of the spinner's arc drawn in
90 // blue, red, yellow, or green. The arc's length also grows and shrinks over the
91 // course of each cycle, which the spinner achieves by drawing the arc using
92 // a (solid) dashed line pattern and animating the "lineDashPhase" property.
93 - (void)initializeAnimation {
94   CGRect bounds = [self bounds];
95   CGFloat scale_factor = bounds.size.width / kDesign_Width;
97   // Create the first half of the arc animation, where it grows from a short
98   // block to its full length.
99   base::scoped_nsobject<CAMediaTimingFunction> timing_function(
100       [[CAMediaTimingFunction alloc] initWithControlPoints:0.4 :0.0 :0.2 :1]);
101   base::scoped_nsobject<CAKeyframeAnimation> first_half_animation(
102       [[CAKeyframeAnimation alloc] init]);
103   [first_half_animation setTimingFunction:timing_function];
104   [first_half_animation setKeyPath:@"lineDashPhase"];
105   NSMutableArray* animation_values = [NSMutableArray array];
106   // Begin the lineDashPhase animation just short of the full arc length,
107   // otherwise the arc will be zero length at start.
108   [animation_values addObject:
109       [NSNumber numberWithFloat:-(kArc_Length - 0.2) * scale_factor]];
110   [animation_values addObject:[NSNumber numberWithFloat:0.0]];
111   [first_half_animation setValues:animation_values];
112   NSMutableArray* key_times = [NSMutableArray array];
113   [key_times addObject:[NSNumber numberWithFloat:0.0]];
114   [key_times addObject:[NSNumber numberWithFloat:1.0]];
115   [first_half_animation setKeyTimes:key_times];
116   [first_half_animation setDuration:kArc_Animation_Time / 2.0];
117   [first_half_animation setRemovedOnCompletion:NO];
118   [first_half_animation setFillMode:kCAFillModeForwards];
120   // Create the second half of the arc animation, where it shrinks from full
121   // length back to a short block.
122   base::scoped_nsobject<CAKeyframeAnimation> second_half_animation(
123       [[CAKeyframeAnimation alloc] init]);
124   [second_half_animation setTimingFunction:timing_function];
125   [second_half_animation setKeyPath:@"lineDashPhase"];
126   animation_values = [NSMutableArray array];
127   [animation_values addObject:[NSNumber numberWithFloat:0.0]];
128   // Stop the lineDashPhase animation just before it reaches the full arc
129   // length, otherwise the arc will be zero length at the end.
130   [animation_values addObject:
131       [NSNumber numberWithFloat:(kArc_Length - 0.3) * scale_factor]];
132   [second_half_animation setValues:animation_values];
133   [second_half_animation setKeyTimes:key_times];
134   [second_half_animation setDuration:kArc_Animation_Time / 2.0];
135   [second_half_animation setRemovedOnCompletion:NO];
136   [second_half_animation setFillMode:kCAFillModeForwards];
138   // Make four copies of the arc animations, to cover the four complete cycles
139   // of the full animation.
140   NSMutableArray* animations = [NSMutableArray array];
141   NSUInteger i;
142   CGFloat begin_time = 0;
143   for (i = 0; i < 4; i++, begin_time += kArc_Animation_Time) {
144     [first_half_animation setBeginTime:begin_time];
145     [second_half_animation setBeginTime:begin_time + kArc_Animation_Time / 2.0];
146     [animations addObject:first_half_animation];
147     [animations addObject:second_half_animation];
148     first_half_animation.reset([first_half_animation copy]);
149     second_half_animation.reset([second_half_animation copy]);
150   }
152   // Create the rotation animation, which rotates the arc 360 degrees on each
153   // cycle. The animation also includes a separate 90 degree rotation in the
154   // opposite direction at the very end of each cycle. Ignoring the 360 degree
155   // rotation, each arc starts as a short block at degree 0 and ends as a
156   // short block at degree 270. Without a 90 degree rotation at the end of each
157   // cycle, the short block would appear to suddenly jump from 270 degrees to
158   // 360 degrees.
159   CAKeyframeAnimation *rotation_animation = [CAKeyframeAnimation animation];
160   [rotation_animation setTimingFunction:
161       [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear]];
162   [rotation_animation setKeyPath:@"transform.rotation"];
163   animation_values = [NSMutableArray array];
164   // Use a key frame animation to rotate 360 degrees on each cycle, and then
165   // jump back 90 degrees at the end of each cycle.
166   [animation_values addObject:[NSNumber numberWithFloat:0.0]];
167   [animation_values addObject:[NSNumber numberWithFloat:-1 * k360_Degrees]];
168   [animation_values addObject:
169       [NSNumber numberWithFloat:-1 * k360_Degrees + k90_Degrees]];
170   [animation_values addObject:
171       [NSNumber numberWithFloat:-2 * k360_Degrees + k90_Degrees]];
172   [animation_values addObject:
173       [NSNumber numberWithFloat:-2 * k360_Degrees + k180_Degrees]];
174   [animation_values addObject:
175       [NSNumber numberWithFloat:-3 * k360_Degrees + k180_Degrees]];
176   [animation_values addObject:
177       [NSNumber numberWithFloat:-3 * k360_Degrees + k270_Degrees]];
178   [animation_values addObject:
179       [NSNumber numberWithFloat:-4 * k360_Degrees + k270_Degrees]];
180   [rotation_animation setValues:animation_values];
181   key_times = [NSMutableArray array];
182   [key_times addObject:[NSNumber numberWithFloat:0.0]];
183   [key_times addObject:[NSNumber numberWithFloat:0.25]];
184   [key_times addObject:[NSNumber numberWithFloat:0.25]];
185   [key_times addObject:[NSNumber numberWithFloat:0.5]];
186   [key_times addObject:[NSNumber numberWithFloat:0.5]];
187   [key_times addObject:[NSNumber numberWithFloat:0.75]];
188   [key_times addObject:[NSNumber numberWithFloat:0.75]];
189   [key_times addObject:[NSNumber numberWithFloat:1.0]];
190   [rotation_animation setKeyTimes:key_times];
191   [rotation_animation setDuration:kArc_Animation_Time * 4.0];
192   [rotation_animation setRemovedOnCompletion:NO];
193   [rotation_animation setFillMode:kCAFillModeForwards];
194   [rotation_animation setRepeatCount:HUGE_VALF];
195   [animations addObject:rotation_animation];
197   // Create a four-cycle-long key frame animation to transition between
198   // successive colors at the end of each cycle.
199   CAKeyframeAnimation *color_animation = [CAKeyframeAnimation animation];
200   color_animation.timingFunction =
201       [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear];
202   color_animation.keyPath = @"strokeColor";
203   CGColorRef blueColor = gfx::CGColorCreateFromSkColor(kBlue);
204   CGColorRef redColor = gfx::CGColorCreateFromSkColor(kRed);
205   CGColorRef yellowColor = gfx::CGColorCreateFromSkColor(kYellow);
206   CGColorRef greenColor = gfx::CGColorCreateFromSkColor(kGreen);
207   animation_values = [NSMutableArray array];
208   [animation_values addObject:(id)blueColor];
209   [animation_values addObject:(id)blueColor];
210   [animation_values addObject:(id)redColor];
211   [animation_values addObject:(id)redColor];
212   [animation_values addObject:(id)yellowColor];
213   [animation_values addObject:(id)yellowColor];
214   [animation_values addObject:(id)greenColor];
215   [animation_values addObject:(id)greenColor];
216   [animation_values addObject:(id)blueColor];
217   [color_animation setValues:animation_values];
218   CGColorRelease(blueColor);
219   CGColorRelease(redColor);
220   CGColorRelease(yellowColor);
221   CGColorRelease(greenColor);
222   key_times = [NSMutableArray array];
223   // Begin the transition bewtween colors at T - 10% of the cycle.
224   const CGFloat transition_offset = 0.1 * 0.25;
225   [key_times addObject:[NSNumber numberWithFloat:0.0]];
226   [key_times addObject:[NSNumber numberWithFloat:0.25 - transition_offset]];
227   [key_times addObject:[NSNumber numberWithFloat:0.25]];
228   [key_times addObject:[NSNumber numberWithFloat:0.50 - transition_offset]];
229   [key_times addObject:[NSNumber numberWithFloat:0.5]];
230   [key_times addObject:[NSNumber numberWithFloat:0.75 - transition_offset]];
231   [key_times addObject:[NSNumber numberWithFloat:0.75]];
232   [key_times addObject:[NSNumber numberWithFloat:0.999 - transition_offset]];
233   [key_times addObject:[NSNumber numberWithFloat:0.999]];
234   [color_animation setKeyTimes:key_times];
235   [color_animation setDuration:kArc_Animation_Time * 4.0];
236   [color_animation setRemovedOnCompletion:NO];
237   [color_animation setFillMode:kCAFillModeForwards];
238   [color_animation setRepeatCount:HUGE_VALF];
239   [animations addObject:color_animation];
241   // Use an animation group so that the animations are easier to manage, and to
242   // give them the best chance of firing synchronously.
243   CAAnimationGroup* group = [CAAnimationGroup animation];
244   [group setDuration:kArc_Animation_Time * 4];
245   [group setRepeatCount:HUGE_VALF];
246   [group setFillMode:kCAFillModeForwards];
247   [group setRemovedOnCompletion:NO];
248   [group setAnimations:animations];
250   spinner_animation_.reset([group retain]);
253 - (void)updateAnimation:(NSNotification*)notification {
254   // Only animate the spinner if it's within a window, and that window is not
255   // currently minimized or being minimized.
256   if ([self window] && ![[self window] isMiniaturized] && ![self isHidden] &&
257       ![[notification name] isEqualToString:
258             NSWindowWillMiniaturizeNotification]) {
259     if (spinner_animation_.get() == nil) {
260       [self initializeAnimation];
261     }
262     // The spinner should never be animating at this point
263     DCHECK(!is_animating_);
264     if (!is_animating_) {
265       [shape_layer_ addAnimation:spinner_animation_.get() forKey:nil];
266       is_animating_ = true;
267     }
268   } else {
269     [shape_layer_ removeAllAnimations];
270     is_animating_ = false;
271   }
274 // Register/unregister for window miniaturization event notifications so that
275 // the spinner can stop animating if the window is minaturized
276 // (i.e. not visible).
277 - (void)viewWillMoveToWindow:(NSWindow*)newWindow {
278   if ([self window]) {
279     [[NSNotificationCenter defaultCenter]
280         removeObserver:self
281                   name:NSWindowWillMiniaturizeNotification
282                 object:[self window]];
283     [[NSNotificationCenter defaultCenter]
284         removeObserver:self
285                   name:NSWindowDidDeminiaturizeNotification
286                 object:[self window]];
287   }
289   if (newWindow) {
290     [[NSNotificationCenter defaultCenter]
291         addObserver:self
292      selector:@selector(updateAnimation:)
293                name:NSWindowWillMiniaturizeNotification
294              object:newWindow];
295     [[NSNotificationCenter defaultCenter]
296         addObserver:self
297      selector:@selector(updateAnimation:)
298                name:NSWindowDidDeminiaturizeNotification
299              object:newWindow];
300   }
303 // Start or stop the animation whenever the view is added to or removed from a
304 // window.
305 - (void)viewDidMoveToWindow {
306   [self updateAnimation:nil];
309 // Start or stop the animation whenever the view is unhidden or hidden.
310 - (void)setHidden:(BOOL)flag
312   [super setHidden:flag];
313   [self updateAnimation:nil];
317 @end