Skip to content

Instantly share code, notes, and snippets.

@briandw
Created June 24, 2013 16:54
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save briandw/5851592 to your computer and use it in GitHub Desktop.
Save briandw/5851592 to your computer and use it in GitHub Desktop.
rantlab 1 full arrow view
//
// RLArrowView.m
//
// Created by brian on 6/20/13.
// Copyright (c) 2013 RantLab. All rights reserved.
//
#import "RLArrowView.h"
@implementation RLArrowView
- (void)awakeFromNib
{
[self setLayer:[CALayer new]];
[self setWantsLayer:YES];
}
- (BOOL)acceptsFirstMouse:(NSEvent *)theEvent
{
return YES;
}
- (void)mouseDown:(NSEvent *)event
{
if (arrow)
{
[self removeArrow];
}
CGPoint startPos = [self convertPoint:[event locationInWindow] fromView:nil];
[self fireArrow:startPos];
}
- (void)removeArrow
{
if (arrow)
{
[arrow removeFromSuperlayer];
}
arrow = nil;
}
- (void)fireArrow:(CGPoint)target
{
arrow = [CAShapeLayer layer];
CGPoint middle = CGPointMake(CGRectGetMidX(self.bounds), CGRectGetMidY(self.bounds));
CGPoint vector = CGPointMake(target.x-middle.x, target.y-middle.y);
CGFloat magnitude = sqrt(vector.x*vector.x+vector.y*vector.y);
CGPoint start = CGPointMake(middle.x+vector.x/magnitude*20, middle.y+vector.y/magnitude*20);
CGPathRef startPath = [RLArrowView createPathWithArrowFromPoint:middle
toPoint:start
tailWidth:2.0
headWidth:10.0
headLength:20.0
wiggle:0];
arrow.path = startPath;
arrow.fillColor = [NSColor redColor].CGColor;
arrow.lineWidth = 1;
arrow.fillRule = kCAFillRuleNonZero;
[self.layer addSublayer:arrow];
CGPathRef endPath = [RLArrowView createPathWithArrowFromPoint:middle
toPoint:target
tailWidth:2.0
headWidth:10.0
headLength:20.0
wiggle:0];
[NSAnimationContext runAnimationGroup:^(NSAnimationContext *context){
CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"path"];
animation.duration = 0.15;
animation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseIn];
animation.repeatCount = 0;
animation.autoreverses = NO;
animation.fromValue = (__bridge id)startPath;
animation.toValue = (__bridge id)endPath;
[arrow addAnimation:animation forKey:@"animatePath"];
arrow.path = endPath;
} completionHandler:^{
[self wiggleFromPath:endPath target:target amount:1.0];
}];
}
- (void)wiggleFromPath:(CGPathRef)path target:(CGPoint)target amount:(CGFloat)amount
{
CGPoint middle = CGPointMake(CGRectGetMidX(self.bounds), CGRectGetMidY(self.bounds));
CGPathRef endPath = [RLArrowView createPathWithArrowFromPoint:middle
toPoint:target
tailWidth:2.0
headWidth:10.0
headLength:20.0
wiggle:amount];
[NSAnimationContext runAnimationGroup:^(NSAnimationContext *context){
CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"path"];
animation.duration = 0.1;
animation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
animation.repeatCount = 0;
animation.autoreverses = NO;
animation.fromValue = (__bridge id)path;
animation.toValue = (__bridge id)endPath;
[arrow addAnimation:animation forKey:@"animatePath"];
arrow.path = endPath;
} completionHandler:^{
if (fabs(amount)>0.02) [self wiggleFromPath:endPath target:target amount:amount*-0.7];
}];
}
/************* Arrow drawing stuff ****************/
#define kArrowPointCount 9
+ (CGPathRef )createPathWithArrowFromPoint:(CGPoint)startPoint
toPoint:(CGPoint)endPoint
tailWidth:(CGFloat)tailWidth
headWidth:(CGFloat)headWidth
headLength:(CGFloat)headLength
wiggle:(CGFloat)wiggle
{
CGFloat length = hypotf(endPoint.x - startPoint.x, endPoint.y - startPoint.y);
CGPoint points[kArrowPointCount];
CGFloat deflection = wiggle*length/4.0;
//CGFloat headRotation = atan(deflection/length);
CGFloat tailLength = length - headLength;
CGFloat halfTail = tailWidth/2.0;
points[0] = CGPointMake(0, halfTail);
points[1] = CGPointMake(tailLength/2, deflection + halfTail);
points[2] = CGPointMake(tailLength, halfTail);
points[3] = CGPointMake(tailLength, headWidth / 2);
points[4] = CGPointMake(length, 0);
points[5] = CGPointMake(tailLength, -headWidth / 2);
points[6] = CGPointMake(tailLength, -halfTail);
points[7] = CGPointMake(tailLength/2, -halfTail + deflection);
points[8] = CGPointMake(0, -halfTail);
CGAffineTransform transform = [self transformForStartPoint:startPoint
endPoint:endPoint
length:length];
CGMutablePathRef cgPath = CGPathCreateMutable();
CGPathMoveToPoint(cgPath, &transform, points[0].x, points[0].y);
CGPathAddQuadCurveToPoint(cgPath, &transform, points[1].x, points[1].y, points[2].x, points[2].y);
CGPathAddLineToPoint(cgPath, &transform, points[3].x, points[3].y);
CGPathAddLineToPoint(cgPath, &transform, points[4].x, points[4].y);
CGPathAddLineToPoint(cgPath, &transform, points[5].x, points[5].y);
CGPathAddLineToPoint(cgPath, &transform, points[6].x, points[6].y);
CGPathAddQuadCurveToPoint(cgPath, &transform, points[7].x, points[7].y, points[8].x, points[8].y);
CGPathCloseSubpath(cgPath);
return cgPath;
}
+ (CGAffineTransform)transformForStartPoint:(CGPoint)startPoint
endPoint:(CGPoint)endPoint
length:(CGFloat)length
{
CGFloat cosine = (endPoint.x - startPoint.x) / length;
CGFloat sine = (endPoint.y - startPoint.y) / length;
return (CGAffineTransform){ cosine, sine, -sine, cosine, startPoint.x, startPoint.y };
}
@end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment