blob: 189ea79d0fab4ebba6522ba656346d218365b92d [file] [log] [blame]
//Copyright 2013-2015 Ilija Tovilo
//
//Licensed under the Apache License, Version 2.0 (the "License");
//you may not use this file except in compliance with the License.
//You may obtain a copy of the License at
//
//http://www.apache.org/licenses/LICENSE-2.0
//
//Unless required by applicable law or agreed to in writing, software
//distributed under the License is distributed on an "AS IS" BASIS,
//WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//See the License for the specific language governing permissions and
//limitations under the License.
#if !__has_feature(objc_arc)
#error ARC needs to be enabled!
#endif
#import "ITProgressIndicator.h"
#pragma mark - Consts
#define kITSpinAnimationKey @"spinAnimation"
#define kITProgressPropertyKey @"progress"
// ----------------------------------------------------------------------------------------
#pragma mark - NSBezierPath+IT_Geometry
// ----------------------------------------------------------------------------------------
@interface NSBezierPath (IT_Geometry)
- (NSBezierPath*)it_rotatedBezierPath:(float) angle;
- (NSBezierPath*)it_rotatedBezierPath:(float) angle aboutPoint:(NSPoint)point;
@end
@implementation NSBezierPath (IT_Geometry)
- (NSBezierPath *)it_rotatedBezierPath:(float)angle {
return [self it_rotatedBezierPath:angle aboutPoint:NSMakePoint(NSMidX(self.bounds), NSMidY(self.bounds))];
}
- (NSBezierPath*)it_rotatedBezierPath:(float)angle aboutPoint:(NSPoint)point {
if(angle == 0.0) return self;
else
{
NSBezierPath* copy = [self copy];
NSAffineTransform *xfm = [self it_rotationTransformWithAngle:angle aboutPoint:point];
[copy transformUsingAffineTransform:xfm];
return copy;
}
}
- (NSAffineTransform *)it_rotationTransformWithAngle:(const float)angle aboutPoint:(const NSPoint)aboutPoint {
NSAffineTransform *xfm = [NSAffineTransform transform];
[xfm translateXBy:aboutPoint.x yBy:aboutPoint.y];
[xfm rotateByRadians:angle];
[xfm translateXBy:-aboutPoint.x yBy:-aboutPoint.y];
return xfm;
}
@end
// ----------------------------------------------------------------------------------------
#pragma mark - ITProgressIndicator
// ----------------------------------------------------------------------------------------
#pragma mark - Private Interface
@interface ITProgressIndicator ()
@property (nonatomic, strong, readonly) CALayer *rootLayer;
@property (nonatomic, strong, readonly) CALayer *progressIndicatorLayer;
@end
#pragma mark - Implementation
@implementation ITProgressIndicator
@synthesize progressIndicatorLayer = _progressIndicatorLayer;
#pragma mark - Init
- (id)initWithCoder:(NSCoder *)coder
{
self = [super initWithCoder:coder];
if (self) {
[self initLayers];
}
return self;
}
- (id)initWithFrame:(NSRect)frame
{
self = [super initWithFrame:frame];
if (self) {
[self initLayers];
}
return self;
}
- (void)initLayers {
// Setting initial values
self.color = [NSColor blackColor];
self.innerMargin = 4;
self.widthOfLine = 3;
self.lengthOfLine = 6;
self.numberOfLines = 8;
self.animationDuration = 0.6;
self.isIndeterminate = YES;
self.steppedAnimation = YES;
self.hideWhenStopped = YES;
self.animates = YES;
// Init layers
_rootLayer = [CALayer layer];
self.layer = _rootLayer;
[self setWantsLayer:YES];
self.progressIndicatorLayer.frame = _rootLayer.bounds;
[_rootLayer addSublayer:self.progressIndicatorLayer];
[self reloadIndicatorContent];
[self reloadAnimation];
}
- (void)awakeFromNib {
[self reloadAnimation];
}
- (void)reloadIndicatorContent {
self.progressIndicatorLayer.contents = [self progressImage];
}
- (void)reloadAnimation {
[self.progressIndicatorLayer removeAnimationForKey:kITSpinAnimationKey];
if (self.animates) {
[self.progressIndicatorLayer addAnimation:[self keyFrameAnimationForCurrentPreferences] forKey:kITSpinAnimationKey];
}
}
#pragma mark - Drawing
- (NSImage *)progressImage {
NSImage *progressImage = [[NSImage alloc] initWithSize:self.bounds.size];
[progressImage lockFocus];
{
[NSGraphicsContext saveGraphicsState];
{
[self.color set];
NSRect r = self.bounds;
NSBezierPath *line = [NSBezierPath bezierPathWithRoundedRect:
NSMakeRect((NSWidth(r) / 2) - (self.widthOfLine / 2),
(NSHeight(r) / 2) - self.innerMargin - self.lengthOfLine,
self.widthOfLine, self.lengthOfLine)
xRadius:self.widthOfLine / 2
yRadius:self.widthOfLine / 2];
void (^lineDrawingBlock)(NSUInteger line) =
^(NSUInteger lineNumber) {
NSBezierPath *lineInstance = [line copy];
lineInstance = [lineInstance it_rotatedBezierPath:((2 * M_PI) / self.numberOfLines * lineNumber) + M_PI
aboutPoint:NSMakePoint(NSWidth(r) / 2, NSHeight(r) / 2)];
if (_isIndeterminate) [[self.color colorWithAlphaComponent:1.0 - (1.0 / self.numberOfLines * lineNumber)] set];
[lineInstance fill];
};
if (!self.isIndeterminate) {
for (NSUInteger i = self.numberOfLines;
i > round(self.numberOfLines - (self.numberOfLines * self.progress));
i--)
{
lineDrawingBlock(i);
}
} else {
for (NSUInteger i = 0; i < self.numberOfLines; i++) {
lineDrawingBlock(i);
}
}
}
[NSGraphicsContext restoreGraphicsState];
}
[progressImage unlockFocus];
return progressImage;
}
#pragma mark - Helpers
- (CAKeyframeAnimation *)keyFrameAnimationForCurrentPreferences {
NSMutableArray* keyFrameValues = [NSMutableArray array];
NSMutableArray* keyTimeValues;
if (self.steppedAnimation) {
{
[keyFrameValues addObject:[NSNumber numberWithFloat:0.0]];
for (NSUInteger i = 0; i < self.numberOfLines; i++) {
[keyFrameValues addObject:[NSNumber numberWithFloat:-M_PI * (2.0 / self.numberOfLines * i)]];
[keyFrameValues addObject:[NSNumber numberWithFloat:-M_PI * (2.0 / self.numberOfLines * i)]];
}
[keyFrameValues addObject:[NSNumber numberWithFloat:-M_PI*2.0]];
}
keyTimeValues = [NSMutableArray array];
{
[keyTimeValues addObject:[NSNumber numberWithFloat:0.0]];
for (NSUInteger i = 0; i < (self.numberOfLines - 1); i++) {
[keyTimeValues addObject:[NSNumber numberWithFloat:1.0 / self.numberOfLines * i]];
[keyTimeValues addObject:[NSNumber numberWithFloat:1.0 / self.numberOfLines * (i + 1)]];
}
[keyTimeValues addObject:[NSNumber numberWithFloat:1.0 / self.numberOfLines * (self.numberOfLines - 1)]];
}
} else {
{
[keyFrameValues addObject:[NSNumber numberWithFloat:-M_PI*0.0]];
[keyFrameValues addObject:[NSNumber numberWithFloat:-M_PI*0.5]];
[keyFrameValues addObject:[NSNumber numberWithFloat:-M_PI*1.0]];
[keyFrameValues addObject:[NSNumber numberWithFloat:-M_PI*1.5]];
[keyFrameValues addObject:[NSNumber numberWithFloat:-M_PI*2.0]];
}
}
CAKeyframeAnimation* animation = [CAKeyframeAnimation animationWithKeyPath:@"transform"];
[animation setRepeatCount:HUGE_VALF];
[animation setValues:keyFrameValues];
[animation setKeyTimes:keyTimeValues];
[animation setValueFunction:[CAValueFunction functionWithName: kCAValueFunctionRotateZ]];
[animation setDuration:self.animationDuration];
return animation;
}
- (void)reloadVisibility {
if (_hideWhenStopped && !_animates && _isIndeterminate) {
[self setHidden:YES];
} else {
[self setHidden:NO];
}
}
#pragma mark - NSView methods
// Animatible proxy
+ (id)defaultAnimationForKey:(NSString *)key
{
if ([key isEqualToString:kITProgressPropertyKey]) {
return [CABasicAnimation animation];
} else {
return [super defaultAnimationForKey:key];
}
}
#pragma mark - Setters & Getters
- (void)setIndeterminate:(BOOL)isIndeterminate {
_isIndeterminate = isIndeterminate;
if (!_isIndeterminate) {
self.animates = NO;
}
}
- (void)setProgress:(CGFloat)progress {
if (progress < 0 || progress > 1) {
@throw [NSException exceptionWithName:@"Invalid `progress` property value"
reason:@"`progress` property needs to be between 0 and 1"
userInfo:nil];
}
_progress = progress;
if (!self.isIndeterminate) {
[self reloadIndicatorContent];
}
}
- (void)setAnimates:(BOOL)animates {
_animates = animates;
[self reloadIndicatorContent];
[self reloadAnimation];
[self reloadVisibility];
}
- (void)setHideWhenStopped:(BOOL)hideWhenStopped {
_hideWhenStopped = hideWhenStopped;
[self reloadVisibility];
}
- (CALayer *)progressIndicatorLayer {
if (!_progressIndicatorLayer) {
_progressIndicatorLayer = [CALayer layer];
}
return _progressIndicatorLayer;
}
- (void)setLengthOfLine:(CGFloat)lengthOfLine {
_lengthOfLine = lengthOfLine;
[self reloadIndicatorContent];
}
- (void)setWidthOfLine:(CGFloat)widthOfLine {
_widthOfLine = widthOfLine;
[self reloadIndicatorContent];
}
- (void)setInnerMargin:(CGFloat)innerMargin {
_innerMargin = innerMargin;
[self reloadIndicatorContent];
}
- (void)setAnimationDuration:(CGFloat)animationDuration {
_animationDuration = animationDuration;
[self reloadAnimation];
}
- (void)setNumberOfLines:(NSUInteger)numberOfLines {
_numberOfLines = numberOfLines;
[self reloadIndicatorContent];
[self reloadAnimation];
}
- (void)setSteppedAnimation:(BOOL)steppedAnimation {
_steppedAnimation = steppedAnimation;
[self reloadAnimation];
}
- (void)setColor:(NSColor *)color {
_color = color;
[self reloadIndicatorContent];
}
@end