blob: 189ea79d0fab4ebba6522ba656346d218365b92d [file] [log] [blame]
Alexandre Lisionf47a2562015-06-15 15:48:29 -04001//Copyright 2013-2015 Ilija Tovilo
2//
3//Licensed under the Apache License, Version 2.0 (the "License");
4//you may not use this file except in compliance with the License.
5//You may obtain a copy of the License at
6//
7//http://www.apache.org/licenses/LICENSE-2.0
8//
9//Unless required by applicable law or agreed to in writing, software
10//distributed under the License is distributed on an "AS IS" BASIS,
11//WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12//See the License for the specific language governing permissions and
13//limitations under the License.
14
15#if !__has_feature(objc_arc)
16#error ARC needs to be enabled!
17#endif
18
19
20#import "ITProgressIndicator.h"
21
22
23#pragma mark - Consts
24#define kITSpinAnimationKey @"spinAnimation"
25#define kITProgressPropertyKey @"progress"
26
27
28// ----------------------------------------------------------------------------------------
29#pragma mark - NSBezierPath+IT_Geometry
30// ----------------------------------------------------------------------------------------
31
32@interface NSBezierPath (IT_Geometry)
33
34- (NSBezierPath*)it_rotatedBezierPath:(float) angle;
35- (NSBezierPath*)it_rotatedBezierPath:(float) angle aboutPoint:(NSPoint)point;
36
37@end
38
39@implementation NSBezierPath (IT_Geometry)
40
41- (NSBezierPath *)it_rotatedBezierPath:(float)angle {
42 return [self it_rotatedBezierPath:angle aboutPoint:NSMakePoint(NSMidX(self.bounds), NSMidY(self.bounds))];
43}
44
45- (NSBezierPath*)it_rotatedBezierPath:(float)angle aboutPoint:(NSPoint)point {
46 if(angle == 0.0) return self;
47 else
48 {
49 NSBezierPath* copy = [self copy];
50 NSAffineTransform *xfm = [self it_rotationTransformWithAngle:angle aboutPoint:point];
51 [copy transformUsingAffineTransform:xfm];
52
53 return copy;
54 }
55}
56
57- (NSAffineTransform *)it_rotationTransformWithAngle:(const float)angle aboutPoint:(const NSPoint)aboutPoint {
58 NSAffineTransform *xfm = [NSAffineTransform transform];
59 [xfm translateXBy:aboutPoint.x yBy:aboutPoint.y];
60 [xfm rotateByRadians:angle];
61 [xfm translateXBy:-aboutPoint.x yBy:-aboutPoint.y];
62
63 return xfm;
64}
65
66@end
67
68
69
70
71// ----------------------------------------------------------------------------------------
72#pragma mark - ITProgressIndicator
73// ----------------------------------------------------------------------------------------
74
75#pragma mark - Private Interface
76
77@interface ITProgressIndicator ()
78@property (nonatomic, strong, readonly) CALayer *rootLayer;
79@property (nonatomic, strong, readonly) CALayer *progressIndicatorLayer;
80@end
81
82
83#pragma mark - Implementation
84
85@implementation ITProgressIndicator
86@synthesize progressIndicatorLayer = _progressIndicatorLayer;
87
88
89#pragma mark - Init
90
91- (id)initWithCoder:(NSCoder *)coder
92{
93 self = [super initWithCoder:coder];
94 if (self) {
95 [self initLayers];
96 }
97 return self;
98}
99
100- (id)initWithFrame:(NSRect)frame
101{
102 self = [super initWithFrame:frame];
103 if (self) {
104 [self initLayers];
105 }
106 return self;
107}
108
109- (void)initLayers {
110 // Setting initial values
111 self.color = [NSColor blackColor];
112 self.innerMargin = 4;
113 self.widthOfLine = 3;
114 self.lengthOfLine = 6;
115 self.numberOfLines = 8;
116 self.animationDuration = 0.6;
117 self.isIndeterminate = YES;
118 self.steppedAnimation = YES;
119 self.hideWhenStopped = YES;
120 self.animates = YES;
121
122 // Init layers
123 _rootLayer = [CALayer layer];
124 self.layer = _rootLayer;
125 [self setWantsLayer:YES];
126 self.progressIndicatorLayer.frame = _rootLayer.bounds;
127 [_rootLayer addSublayer:self.progressIndicatorLayer];
128
129 [self reloadIndicatorContent];
130 [self reloadAnimation];
131}
132
133- (void)awakeFromNib {
134 [self reloadAnimation];
135}
136
137- (void)reloadIndicatorContent {
138 self.progressIndicatorLayer.contents = [self progressImage];
139}
140
141- (void)reloadAnimation {
142 [self.progressIndicatorLayer removeAnimationForKey:kITSpinAnimationKey];
143
144 if (self.animates) {
145 [self.progressIndicatorLayer addAnimation:[self keyFrameAnimationForCurrentPreferences] forKey:kITSpinAnimationKey];
146 }
147}
148
149
150#pragma mark - Drawing
151
152- (NSImage *)progressImage {
153 NSImage *progressImage = [[NSImage alloc] initWithSize:self.bounds.size];
154 [progressImage lockFocus];
155 {
156 [NSGraphicsContext saveGraphicsState];
157 {
158 [self.color set];
159
160 NSRect r = self.bounds;
161 NSBezierPath *line = [NSBezierPath bezierPathWithRoundedRect:
162 NSMakeRect((NSWidth(r) / 2) - (self.widthOfLine / 2),
163 (NSHeight(r) / 2) - self.innerMargin - self.lengthOfLine,
164 self.widthOfLine, self.lengthOfLine)
165 xRadius:self.widthOfLine / 2
166 yRadius:self.widthOfLine / 2];
167
168 void (^lineDrawingBlock)(NSUInteger line) =
169 ^(NSUInteger lineNumber) {
170 NSBezierPath *lineInstance = [line copy];
171 lineInstance = [lineInstance it_rotatedBezierPath:((2 * M_PI) / self.numberOfLines * lineNumber) + M_PI
172 aboutPoint:NSMakePoint(NSWidth(r) / 2, NSHeight(r) / 2)];
173
174 if (_isIndeterminate) [[self.color colorWithAlphaComponent:1.0 - (1.0 / self.numberOfLines * lineNumber)] set];
175
176 [lineInstance fill];
177 };
178
179 if (!self.isIndeterminate) {
180 for (NSUInteger i = self.numberOfLines;
181 i > round(self.numberOfLines - (self.numberOfLines * self.progress));
182 i--)
183 {
184 lineDrawingBlock(i);
185 }
186 } else {
187 for (NSUInteger i = 0; i < self.numberOfLines; i++) {
188 lineDrawingBlock(i);
189 }
190 }
191 }
192 [NSGraphicsContext restoreGraphicsState];
193 }
194 [progressImage unlockFocus];
195
196 return progressImage;
197}
198
199
200#pragma mark - Helpers
201
202- (CAKeyframeAnimation *)keyFrameAnimationForCurrentPreferences {
203 NSMutableArray* keyFrameValues = [NSMutableArray array];
204 NSMutableArray* keyTimeValues;
205
206 if (self.steppedAnimation) {
207 {
208 [keyFrameValues addObject:[NSNumber numberWithFloat:0.0]];
209 for (NSUInteger i = 0; i < self.numberOfLines; i++) {
210 [keyFrameValues addObject:[NSNumber numberWithFloat:-M_PI * (2.0 / self.numberOfLines * i)]];
211 [keyFrameValues addObject:[NSNumber numberWithFloat:-M_PI * (2.0 / self.numberOfLines * i)]];
212 }
213 [keyFrameValues addObject:[NSNumber numberWithFloat:-M_PI*2.0]];
214 }
215
216 keyTimeValues = [NSMutableArray array];
217 {
218 [keyTimeValues addObject:[NSNumber numberWithFloat:0.0]];
219 for (NSUInteger i = 0; i < (self.numberOfLines - 1); i++) {
220 [keyTimeValues addObject:[NSNumber numberWithFloat:1.0 / self.numberOfLines * i]];
221 [keyTimeValues addObject:[NSNumber numberWithFloat:1.0 / self.numberOfLines * (i + 1)]];
222 }
223 [keyTimeValues addObject:[NSNumber numberWithFloat:1.0 / self.numberOfLines * (self.numberOfLines - 1)]];
224 }
225 } else {
226 {
227 [keyFrameValues addObject:[NSNumber numberWithFloat:-M_PI*0.0]];
228 [keyFrameValues addObject:[NSNumber numberWithFloat:-M_PI*0.5]];
229 [keyFrameValues addObject:[NSNumber numberWithFloat:-M_PI*1.0]];
230 [keyFrameValues addObject:[NSNumber numberWithFloat:-M_PI*1.5]];
231 [keyFrameValues addObject:[NSNumber numberWithFloat:-M_PI*2.0]];
232 }
233 }
234
235
236 CAKeyframeAnimation* animation = [CAKeyframeAnimation animationWithKeyPath:@"transform"];
237
238 [animation setRepeatCount:HUGE_VALF];
239 [animation setValues:keyFrameValues];
240 [animation setKeyTimes:keyTimeValues];
241 [animation setValueFunction:[CAValueFunction functionWithName: kCAValueFunctionRotateZ]];
242 [animation setDuration:self.animationDuration];
243
244 return animation;
245}
246
247- (void)reloadVisibility {
248 if (_hideWhenStopped && !_animates && _isIndeterminate) {
249 [self setHidden:YES];
250 } else {
251 [self setHidden:NO];
252 }
253}
254
255
256#pragma mark - NSView methods
257
258// Animatible proxy
259+ (id)defaultAnimationForKey:(NSString *)key
260{
261 if ([key isEqualToString:kITProgressPropertyKey]) {
262 return [CABasicAnimation animation];
263 } else {
264 return [super defaultAnimationForKey:key];
265 }
266}
267
268
269#pragma mark - Setters & Getters
270
271- (void)setIndeterminate:(BOOL)isIndeterminate {
272 _isIndeterminate = isIndeterminate;
273
274 if (!_isIndeterminate) {
275 self.animates = NO;
276 }
277}
278
279- (void)setProgress:(CGFloat)progress {
280 if (progress < 0 || progress > 1) {
281 @throw [NSException exceptionWithName:@"Invalid `progress` property value"
282 reason:@"`progress` property needs to be between 0 and 1"
283 userInfo:nil];
284 }
285
286 _progress = progress;
287
288 if (!self.isIndeterminate) {
289 [self reloadIndicatorContent];
290 }
291}
292
293- (void)setAnimates:(BOOL)animates {
294 _animates = animates;
295 [self reloadIndicatorContent];
296 [self reloadAnimation];
297 [self reloadVisibility];
298}
299
300- (void)setHideWhenStopped:(BOOL)hideWhenStopped {
301 _hideWhenStopped = hideWhenStopped;
302 [self reloadVisibility];
303}
304
305- (CALayer *)progressIndicatorLayer {
306 if (!_progressIndicatorLayer) {
307 _progressIndicatorLayer = [CALayer layer];
308 }
309
310 return _progressIndicatorLayer;
311}
312
313- (void)setLengthOfLine:(CGFloat)lengthOfLine {
314 _lengthOfLine = lengthOfLine;
315 [self reloadIndicatorContent];
316}
317
318- (void)setWidthOfLine:(CGFloat)widthOfLine {
319 _widthOfLine = widthOfLine;
320 [self reloadIndicatorContent];
321}
322
323- (void)setInnerMargin:(CGFloat)innerMargin {
324 _innerMargin = innerMargin;
325 [self reloadIndicatorContent];
326}
327
328- (void)setAnimationDuration:(CGFloat)animationDuration {
329 _animationDuration = animationDuration;
330 [self reloadAnimation];
331}
332
333- (void)setNumberOfLines:(NSUInteger)numberOfLines {
334 _numberOfLines = numberOfLines;
335 [self reloadIndicatorContent];
336 [self reloadAnimation];
337}
338
339- (void)setSteppedAnimation:(BOOL)steppedAnimation {
340 _steppedAnimation = steppedAnimation;
341 [self reloadAnimation];
342}
343
344- (void)setColor:(NSColor *)color {
345 _color = color;
346 [self reloadIndicatorContent];
347}
348
349@end