Skip to content

Instantly share code, notes, and snippets.

@scottymac
Forked from Shilo/XDSpinner.h
Created August 28, 2013 16:56
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save scottymac/6368383 to your computer and use it in GitHub Desktop.
Save scottymac/6368383 to your computer and use it in GitHub Desktop.
//
// 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