-
Notifications
You must be signed in to change notification settings - Fork 1
/
Twistable.coffee
123 lines (99 loc) · 3.62 KB
/
Twistable.coffee
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
# Add the following line to your project in Framer Studio.
# myModule = require "myModule"
# Reference the contents by name, like myModule.myFunction() or myModule.myVar
# Twistable
Layer.prototype.enableTwistable = (range,constrained)->
@canvasPinLocation = null # the point of rotation in canvas coordinates
@previousAngle = 0
@angularVel = 0 # Used for flicked spin
@twistRange = range || [0,360] # Used for twistValue
@twistValue = 0 # The rotation as percent of the twistRange
@twistConstrained = constrained || range != undefined # Whether or not to clamp twisting.
@spinDecay = 0.95
handleTouchMove = (e)=>
newAngle = Math.atan2(@canvasPinLocation.y-e.pageY,@canvasPinLocation.x-e.pageX)
newAngleDeg = (newAngle / Math.PI * 180)
newAngleDegUpIsZero = newAngleDeg - 90
angleDif = shortestDistanceBetweenAngles(@previousAngle,newAngleDegUpIsZero)
@rotation += angleDif
@angularVel = angleDif
if @twistConstrained
constrainedRotation = Math.min(Math.max(@rotation,@twistRange[0]),@twistRange[1])
if @rotation != constrainedRotation
@angularVel = 0
@rotation = constrainedRotation
if !@hasTwistBegan and @angularVel != 0
@hasTwistBegan = true
@emit "twistStart"
@previousAngle = newAngleDegUpIsZero
@twistValue = Utils.modulate(@rotation,@twistRange,[0,1])
@emit "twist"
handleSpinning = (e)=>
@rotation += @angularVel
@angularVel *= @spinDecay
if @twistConstrained
constrainedRotation = Math.min(Math.max(@rotation,@twistRange[0]),@twistRange[1])
if @rotation != constrainedRotation
@angularVel = 0
@rotation = constrainedRotation
@twistValue = Utils.modulate(@rotation,@twistRange,[0,1])
@emit "spin"
if Math.abs(angularVel) < 0.1
angularVel = 0
@emit "spinEnd"
Framer.Loop.off "update",handleSpinning
handleTouchEnd = =>
@isBeingTwisted = false
@emit "twistEnd"
Events.wrap(document).removeEventListener "touchmove",handleTouchMove
Events.wrap(document).removeEventListener "touchend",handleTouchEnd
Framer.Loop.on "update",handleSpinning
@onTouchStart (e)=>
localPinLocation =
x:@width * @originX
y:@height * @originY
@canvasPinLocation = @convertPointToCanvas(localPinLocation)
# calculate initial angle of click/tap
startAngle = Math.atan2(@canvasPinLocation.y-e.pageY,@canvasPinLocation.x-e.pageX)
startAngleDeg = (startAngle / Math.PI * 180)
startAngleDegUpIsZero = startAngleDeg - 90
@previousAngle = startAngleDegUpIsZero
@rotationTouchPoint = {x:e.pageX,y:e.pageY}
@hasTwistBegan = false
@isBeingTwisted = true
Events.wrap(document).addEventListener "touchmove",handleTouchMove
Events.wrap(document).addEventListener "touchend",handleTouchEnd
Framer.Loop.off "update",handleSpinning
@setTwistValue = (v)=>
@twistValue = Math.max(Math.min(v,1),0)
@rotation = Utils.modulate(v,[0,1],@twistRange)
@emit "twist"
Layer.prototype.disableTwistable = ->
print "NOT WORKING YET"
# Event handling shortcuts
Layer.prototype.onTwist = (fn)->
@on "twist",fn
Layer.prototype.onTwistStart = (fn)->
@on "twistStart",fn
Layer.prototype.onTwistEnd = (fn)->
@on "twistEnd",fn
Layer.prototype.onSpin = (fn)->
@on "spin",fn
Layer.prototype.onSpinEnd = (fn)->
@on "spinEnd",fn
#
# Helpers
#
# Convert angle to value between 0–360
normalizeAngle = (a)->
return (a + 360*10000)%360
# Should be named shortestDistance between NORMALIZED angles.
shortestDistanceBetweenAngles = (a1,a2)->
a1Norm = normalizeAngle(a1)
a2Norm = normalizeAngle(a2)
r1 = a2Norm-a1Norm
if Math.abs(r1) > 180
if r1 > 0 then return r1 - 360
else return r1 + 360
else
return r1