Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
An extension for Sparrow that allows highly customizable activity indicators. (Bought to you by XIDA Design & Technik)
//
// XDSpinner.h
// Sparrow 2.X
//
// Created by Shilo White on 8/17/13.
// Copyright (c) 2013 XIDA Design & Technik. All rights reserved.
//
#import "SPDisplayObjectContainer.h"
typedef enum
{
XDSpinnerDirectionClockwise = 0,
XDSpinnerDirectionCounterClockwise,
} XDSpinnerDirection;
@interface XDSpinner : SPDisplayObjectContainer
+ (id)spinner;
@property (nonatomic, assign) uint lines;
@property (nonatomic, assign) float lineLength;
@property (nonatomic, assign) float lineWidth;
@property (nonatomic, assign) float lineRotation;
@property (nonatomic, assign) BOOL lineRound;
@property (nonatomic, assign) uint lineColor;
@property (nonatomic, assign) XDSpinnerDirection direction;
@property (nonatomic, assign) float radius;
@property (nonatomic, assign) float trailRotation;
@property (nonatomic, assign) float trailLength;
@property (nonatomic, assign) float trailStartAlpha;
@property (nonatomic, assign) float trailEndAlpha;
@property (nonatomic, assign) float speed;
@property (nonatomic, assign) BOOL shadow;
@property (nonatomic, assign) float shadowOffsetX;
@property (nonatomic, assign) float shadowOffsetY;
@property (nonatomic, assign) uint shadowColor;
@property (nonatomic, assign) float shadowBlur;
@property (nonatomic, assign) BOOL hidesWhenStopped;
@property (nonatomic, assign) BOOL animating;
@end
//
// XDSpinner.m
// Sparrow 2.X
//
// Created by Shilo White on 8/17/13.
// Copyright (c) 2013 XIDA Design & Technik. All rights reserved.
//
#define DEFAULT_LINES 13
#define DEFAULT_LINE_LENGTH 30.0f
#define DEFAULT_LINE_WIDTH 10.0f
#define DEFAULT_LINE_ROTATION 0.0f
#define DEFAULT_LINE_ROUND YES
#define DEFAULT_LINE_COLOR 0xFFFFFF
#define DEFAULT_DIRECTION XDSpinnerDirectionClockwise
#define DEFAULT_RADIUS 30.0f
#define DEFAULT_TRAIL_ROTATION 0.0f
#define DEFAULT_TRAIL_LENGTH 0.6f
#define DEFAULT_TRAIL_START_ALPHA 1.0f
#define DEFAULT_TRAIL_END_ALPHA 0.25f
#define DEFAULT_SPEED 1.0f
#define DEFAULT_SHADOW NO
#define DEFAULT_SHADOW_OFFSET_X 2.0f
#define DEFAULT_SHADOW_OFFSET_Y 2.0f
#define DEFAULT_SHADOW_COLOR 0xC0C0C0
#define DEFAULT_SHADOW_BLUR 2.0f
#define DEFAULT_HIDES_WHEN_STOPPED NO
#define DEFAULT_ANIMATING YES
#define LINE_TEXTURE_ANTI_ALIAS_PADDING 1.0f
#define SET_VALUE(variable, value) if ((variable) != (value)) { (variable) = (value); _requiresRedraw = YES; }
#define FLOAT_PERCENT(targetValue, minValue, maxValue) ((targetValue) - (minValue)) / ((maxValue) - (minValue))
#import "XDSpinner.h"
@implementation XDSpinner
{
uint _lines;
float _lineLength;
float _lineWidth;
float _lineRotation;
BOOL _lineRound;
uint _lineColor;
XDSpinnerDirection _direction;
float _radius;
float _trailRotation;
float _trailLength;
float _trailStartAlpha;
float _trailEndAlpha;
float _speed;
BOOL _shadow;
float _shadowOffsetX;
float _shadowOffsetY;
uint _shadowColor;
float _shadowBlur;
BOOL _hidesWhenStopped;
BOOL _animating;
BOOL _requiresRedraw;
SPJuggler *_juggler;
}
@synthesize lines = _lines;
@synthesize lineLength = _lineLength;
@synthesize lineWidth = _lineWidth;
@synthesize lineRotation = _lineRotation;
@synthesize lineRound = _lineRound;
@synthesize lineColor = _lineColor;
@synthesize direction = _direction;
@synthesize radius = _radius;
@synthesize trailRotation = _trailRotation;
@synthesize trailLength = _trailLength;
@synthesize trailStartAlpha = _trailStartAlpha;
@synthesize trailEndAlpha = _trailEndAlpha;
@synthesize speed = _speed;
@synthesize shadow = _shadow;
@synthesize shadowOffsetX = _shadowOffsetX;
@synthesize shadowOffsetY = _shadowOffsetY;
@synthesize shadowColor = _shadowColor;
@synthesize shadowBlur = _shadowBlur;
@synthesize hidesWhenStopped = _hidesWhenStopped;
@synthesize animating = _animating;
- (id)init
{
if ((self = [super init]))
{
_juggler = [SPJuggler juggler];
_lines = DEFAULT_LINES;
_lineLength = DEFAULT_LINE_LENGTH;
_lineWidth = DEFAULT_LINE_WIDTH;
_lineRotation = DEFAULT_LINE_ROTATION;
_lineRound = DEFAULT_LINE_ROUND;
_lineColor = DEFAULT_LINE_COLOR;
_radius = DEFAULT_RADIUS;
_direction = DEFAULT_DIRECTION;
_trailRotation = DEFAULT_TRAIL_ROTATION;
_trailLength = DEFAULT_TRAIL_LENGTH;
_trailStartAlpha = DEFAULT_TRAIL_START_ALPHA;
_trailEndAlpha = DEFAULT_TRAIL_END_ALPHA;
_speed = DEFAULT_SPEED;
_shadow = DEFAULT_SHADOW;
_shadowOffsetX = DEFAULT_SHADOW_OFFSET_X;
_shadowOffsetY = DEFAULT_SHADOW_OFFSET_Y;
_shadowColor = DEFAULT_SHADOW_COLOR;
_shadowBlur = DEFAULT_SHADOW_BLUR;
_hidesWhenStopped = DEFAULT_HIDES_WHEN_STOPPED;
self.animating = DEFAULT_ANIMATING;
_requiresRedraw = YES;
}
return self;
}
- (void)setLines:(uint)lines
{
lines = MAX(lines, 1);
SET_VALUE(_lines, lines);
}
- (void)setLineLength:(float)lineLength
{
SET_VALUE(_lineLength, lineLength);
}
- (void)setLineWidth:(float)lineWidth
{
SET_VALUE(_lineWidth, lineWidth);
}
- (void)setLineRotation:(float)lineRotation
{
SET_VALUE(_lineRotation, lineRotation);
}
- (void)setLineRound:(BOOL)lineRound
{
SET_VALUE(_lineRound, lineRound);
}
- (void)setLineColor:(uint)lineColor
{
SET_VALUE(_lineColor, lineColor);
}
- (void)setDirection:(XDSpinnerDirection)direction
{
direction = MIN(MAX(direction, XDSpinnerDirectionClockwise), XDSpinnerDirectionCounterClockwise);
SET_VALUE(_direction, direction);
}
- (void)setRadius:(float)radius
{
SET_VALUE(_radius, radius);
}
- (void)setTrailRotation:(float)trailRotation
{
trailRotation = fmod(trailRotation, TWO_PI);
if (_trailRotation != trailRotation)
{
_trailRotation = trailRotation;
for (SPDisplayObject *line in self)
[self setAlphaOfLine:line];
}
}
- (void)setTrailLength:(float)trailLength
{
trailLength = MIN(MAX(trailLength, 0.01f), 1.0f);
SET_VALUE(_trailLength, trailLength);
}
- (void)setTrailStartAlpha:(float)trailStartAlpha
{
trailStartAlpha = MIN(MAX(trailStartAlpha, 0.0f), 1.0f);
SET_VALUE(_trailStartAlpha, trailStartAlpha);
}
- (void)setTrailEndAlpha:(float)trailEndAlpha
{
trailEndAlpha = MIN(MAX(trailEndAlpha, 0.0f), 1.0f);
SET_VALUE(_trailEndAlpha, trailEndAlpha);
}
- (void)setSpeed:(float)speed
{
speed = MAX(speed, 0.0f);
if (_speed != speed)
{
_speed = speed;
[self updateAnimation];
}
}
- (void)setShadow:(BOOL)shadow
{
SET_VALUE(_shadow, shadow);
}
- (void)setShadowOffsetX:(float)shadowOffsetX
{
if (_shadowOffsetX != shadowOffsetX)
{
_shadowOffsetX = shadowOffsetX;
if (_shadow) _requiresRedraw = YES;
}
}
- (void)setShadowOffsetY:(float)shadowOffsetY
{
if (_shadowOffsetY != shadowOffsetY)
{
_shadowOffsetY = shadowOffsetY;
if (_shadow) _requiresRedraw = YES;
}
}
- (void)setShadowColor:(uint)shadowColor
{
if (_shadowColor != shadowColor)
{
_shadowColor = shadowColor;
if (_shadow) _requiresRedraw = YES;
}
}
- (void)setShadowBlur:(float)shadowBlur
{
shadowBlur = MAX(shadowBlur, 0.0f);
if (_shadowBlur != shadowBlur)
{
_shadowBlur = shadowBlur;
if (_shadow) _requiresRedraw = YES;
}
}
- (void)setHidesWhenStopped:(BOOL)hidesWhenStopped
{
SET_VALUE(_hidesWhenStopped, hidesWhenStopped);
self.visible = !(_hidesWhenStopped && !_animating);
}
- (void)setAnimating:(BOOL)animating
{
if (_animating != animating)
{
_animating = animating;
if (_animating)
[self addEventListener:@selector(animate:) atObject:self forType:SP_EVENT_TYPE_ENTER_FRAME];
else
[self removeEventListener:@selector(animate:) atObject:self forType:SP_EVENT_TYPE_ENTER_FRAME];
self.visible = !(_hidesWhenStopped && !_animating);
}
}
- (void)redraw
{
[_juggler removeAllObjects];
[self removeAllChildren];
float padding = [self texturePadding];
CGColorRef lineColorRef = CGColorRetain([self colorWithUInt:_lineColor].CGColor);
CGColorRef shadowColorRef = (shadow)?CGColorRetain([self colorWithUInt:_shadowColor].CGColor):NULL;
SPTexture *lineTexture;
if (!_shadow) lineTexture = [self lineTextureWithLength:_lineLength width:_lineWidth round:_lineRound padding:padding lineColorRef:lineColorRef shadowColorRef:shadowColorRef shadowRotation:0.0f];
for (uint i=0; i<_lines; ++i)
{
SPPoint *position = [[SPPoint alloc] initWithPolarLength:_radius angle:i*2*PI/_lines-SP_D2R(90.0f)];
float rotation = fmod(position.angle+SP_D2R(90.0f), TWO_PI);
if (_shadow) lineTexture = [self lineTextureWithLength:_lineLength width:_lineWidth round:_lineRound padding:padding lineColorRef:lineColorRef shadowColorRef:shadowColorRef shadowRotation:rotation];
SPImage *line = [SPImage imageWithTexture:lineTexture];
line.pivotX = line.width/2.0f;
line.pivotY = line.height-padding;
line.x = roundf(position.x);
line.y = roundf(position.y);
line.rotation = rotation;
[self setAlphaOfLine:line];
line.rotation += _lineRotation;
[self addChild:line];
}
[self updateAnimation];
CGColorRelease(lineColorRef);
CGColorRelease(shadowColorRef);
_requiresRedraw = NO;
}
- (void)updateAnimation
{
[_juggler removeAllObjects];
SPTween *tween = [SPTween tweenWithTarget:self time:1.0f/_speed];
tween.repeatCount = 0;
[tween animateProperty:@"trailRotation" targetValue:_trailRotation+((_direction == XDSpinnerDirectionClockwise)?TWO_PI:-TWO_PI)];
[_juggler addObject:tween];
}
- (void)setAlphaOfLine:(SPDisplayObject *)line
{
float absRotation = line.rotation+((line.rotation<=0.0f)?PI*2:0.0f);
absRotation = fmod(absRotation+fabs(TWO_PI-_trailRotation), TWO_PI);
if (absRotation == 0.0f) absRotation = TWO_PI;
float absAlpha = FLOAT_PERCENT(absRotation, 0.0f, PI*2);
if (_direction == XDSpinnerDirectionCounterClockwise && absAlpha < 1.0f)
absAlpha = 1.0f-absAlpha;
absAlpha = MAX(absAlpha-fabs(_trailLength-1.0f), 0.0f)/_trailLength;
line.alpha = _trailEndAlpha + (absAlpha * (_trailStartAlpha - _trailEndAlpha));
}
- (SPTexture *)lineTextureWithLength:(float)length width:(float)width round:(BOOL)round padding:(float)padding lineColorRef:(CGColorRef)lineColorRef shadowColorRef:(CGColorRef)shadowColorRef shadowRotation:(float)shadowRotation
{
return [[SPTexture alloc] initWithWidth:width+(padding*2) height:length+(padding*2) draw:^(CGContextRef context)
{
float halfWidth = width/2.0f;
float capWidth = round ? halfWidth : 0;
CGContextSetLineWidth(context, width);
if (round) CGContextSetLineCap(context, kCGLineCapRound);
CGContextMoveToPoint(context, halfWidth+padding, capWidth+padding);
CGContextAddLineToPoint(context, halfWidth+padding, length-capWidth+padding);
if (_shadow) CGContextSetShadowWithColor(context, [self shadowOffsetWithX:_shadowOffsetX y:_shadowOffsetY angle:shadowRotation], _shadowBlur, shadowColorRef);
CGContextSetStrokeColorWithColor(context, lineColorRef);
CGContextStrokePath(context);
}];
}
- (UIColor *)colorWithUInt:(uint)color
{
float red = SP_COLOR_PART_RED(color)/255.0f;
float green = SP_COLOR_PART_GREEN(color)/255.0f;
float blue = SP_COLOR_PART_BLUE(color)/255.0f;
return [UIColor colorWithRed:red green:green blue:blue alpha:1.0f];
}
- (CGSize)shadowOffsetWithX:(float)x y:(float)y angle:(float)angle
{
angle = -angle;
y = -y;
return CGSizeMake(y*sinf(angle) + x*cosf(angle), y*cosf(angle) - x*sinf(angle));
}
- (float)texturePadding
{
float padding = LINE_TEXTURE_ANTI_ALIAS_PADDING;
if (_shadow) padding += MAX(fabs(_shadowOffsetX), fabs(_shadowOffsetY))*1.5f + _shadowBlur/2;
return padding;
}
- (void)animate:(SPEnterFrameEvent *)event
{
[_juggler advanceTime:event.passedTime];
}
- (void)render:(SPRenderSupport *)support
{
if (_requiresRedraw) [self redraw];
[super render:support];
}
+ (id)spinner
{
return [[self alloc] init];
}
@end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.