1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23 import math
24
25 from libavg import avg, Point2D
26 from clusteredEventList import ClusteredEventList
27 from mathutil import solveEquationMatrix, EquationSingular, EquationNotSolvable
28 from mathutil import getAngle, getDistance, getScaledDim, getOffsetForMovedPivot
29
30
31 NUM_SPEEDS = 5
32
33 g_player = avg.Player.get()
34
37
40
41
43 """Helper for multitouch object movement.
44 Grabbable will add the well-known multitouch gestures
45 to a node, i.e. moving with one finger, rotating,
46 resizing and moving with 2 fingers.
47 It also works with many more cursors/fingers, clustering
48 them.
49 """
50 - def __init__(self,
51 node,
52 source = avg.TOUCH | avg.MOUSE,
53 maxSize = None, minSize = None,
54 onResize = lambda: None,
55 onDrop = lambda: None,
56 onStop = lambda: None,
57 onAction = lambda: None,
58 onMotion = lambda pos, size, angle, pivot: None,
59 onResetMotion = lambda: None,
60 onDragStart = lambda: None,
61 initialDown = None,
62 inertia = 0.95,
63 torque = 0.95,
64 moveNode = True):
65
66 global g_player
67 self.__inertia = inertia
68 self.__torque = torque
69 self.__node = node
70 self.__minSize = minSize
71 self.__maxSize = maxSize
72 self.__moveNode = moveNode
73 self.__callback = {
74 'onResize': onResize,
75 'onDrop': onDrop,
76 'onStop': onStop,
77 'onAction': onAction,
78 'onMotion': onMotion,
79 'onResetMotion': onResetMotion,
80 'onDragStart': onDragStart,
81 }
82
83 self.__stopInertia()
84
85 self.__lastFixPoint = None
86 self.__lastNumFingers = 0
87 self.__onFrameHandler = g_player.setOnFrameHandler(self.__onFrame)
88
89 self.__eventList = ClusteredEventList (self.__node,
90 source = source,
91 onMotion = self.__onMotion,
92 onUp = self.__onUp,
93 onDown = self.__onDown,
94 resetMotion = self.__resetMotion
95 )
96 if initialDown:
97 self.__eventList.handleInitialDown (initialDown)
98 self.__lastPos = None
99 self.__stopped = True
100
102 return len(self.__eventList) > 0
103
106
109
115
117 global g_player
118 if len (self.__eventList) == 0:
119
120 if self.__speed.getNorm() < 1:
121 if not self.__stopped:
122 pos = Point2D(round(self.__node.pos.x), round(self.__node.pos.y))
123 size = Point2D(round(self.__node.size.x), round(self.__node.size.y))
124 self.__callback['onMotion'](pos, size, self.__node.angle,
125 self.__node.pivot)
126 self.__stopped = True
127 self.__callback['onStop']()
128 self.__stopInertia()
129 return
130
131 pos = self.__node.pos + self.__speed
132 angle = self.__node.angle + self.__rotSpeed
133 size = self.__node.size
134 pivot = self.__node.pivot
135
136 if self.__moveNode:
137 self.__node.pos = pos
138 self.__node.size = size
139 self.__node.angle = angle
140 self.__node.pivot = pivot
141
142 self.__callback['onMotion'](pos, size, angle, pivot)
143
144 self.__speed *= self.__inertia
145 self.__rotSpeed *= self.__torque
146 else:
147 self.__stopped = False
148
149 pos = self.__node.pos
150 angle = self.__node.angle
151 if self.__lastPos:
152 speed = pos - self.__lastPos
153 rotSpeed = angle - self.__lastAngle
154
155 if rotSpeed < -math.pi/2:
156 rotSpeed += math.pi*2
157 if rotSpeed > math.pi/2:
158 rotSpeed -= math.pi*2
159 self.__speeds = (self.__speeds + [speed])[-NUM_SPEEDS:]
160 self.__rotSpeeds = (self.__rotSpeeds + [rotSpeed])[-NUM_SPEEDS:]
161 self.__lastPos = pos
162 self.__lastAngle = angle
163
165 self.__speed = Point2D(0,0)
166 self.__rotSpeed = 0
167 self.__speeds = [Point2D(0,0)]
168 self.__rotSpeeds = [0.0]
169 self.__lastPos = None
170
176
177 self.__stopInertia()
178 self.__reshape()
179 if len(self.__eventList) == 1:
180 if self.__moveNode:
181 __gotoTopLayer()
182 self.__callback['onDragStart']()
183 self.__callback['onAction']()
184
186 self.__reshape()
187 self.__callback['onAction']()
188
190 self.__speed = sum(self.__speeds, Point2D(0,0)) / len(self.__speeds)
191 self.__rotSpeed = sum(self.__rotSpeeds, 0.0) / len(self.__rotSpeeds)
192 self.__callback['onDrop']()
193
195 if len(self.__eventList) == 0:
196 self.__drop()
197 self.__callback['onAction']()
198
200 def applyAffineTransformation (parameters, p):
201 """apply transformation to point:
202 M: v:
203 P' = P * (m -n) + (v0)
204 (n m) (v1) """
205 m, n, v0, v1 = parameters
206 nx = p[0] * m - p[1] * n + v0
207 ny = p[0] * n + p[1] * m + v1
208 return Point2D(nx, ny)
209
210 clusters = self.__eventList.getCursors()
211
212 if len(clusters) == 1:
213 cursor = self.__eventList.getCursors()[0]
214
215 pos = self.__oldpos + cursor.getDelta()
216 size = self.__node.size
217 angle = self.__node.angle
218 pivot = self.__node.pivot
219
220 elif len(clusters) == 2:
221
222
223 cursor1, cursor2 = clusters
224 equationMatrix = (
225 ((cursor1.getStartPos().x, -cursor1.getStartPos().y, 1, 0), cursor1.getPos().x),
226 ((cursor1.getStartPos().y, cursor1.getStartPos().x, 0, 1), cursor1.getPos().y),
227 ((cursor2.getStartPos().x, -cursor2.getStartPos().y, 1, 0), cursor2.getPos().x),
228 ((cursor2.getStartPos().y, cursor2.getStartPos().x, 0, 1), cursor2.getPos().y),
229 )
230 try:
231 param = solveEquationMatrix (equationMatrix)
232 except (EquationSingular, EquationNotSolvable):
233 print "WARNING: Grabbable: cannot solve equation, skipping movement"
234 return False
235
236 absTouchCenter = cursor2.getPos() + (cursor1.getPos() - cursor2.getPos()) / 2
237
238 n_tl = applyAffineTransformation (param, self.o_tl)
239 n_bl = applyAffineTransformation (param, self.o_bl)
240 n_tr = applyAffineTransformation (param, self.o_tr)
241
242 pos = n_tl
243 if pos.x > 1e6 and pos.x > self.o_tl.x:
244 pos = self.o_tl
245 if pos.y > 1e6 and pos.y > self.o_tl.y:
246 pos = self.o_tl
247 if pos.x < -1e6 and pos.x < self.o_tl.x:
248 pos = self.o_tl
249 if pos.y < -1e6 and pos.y < self.o_tl.y:
250 pos = self.o_tl
251
252 size = Point2D(getDistance (n_tl, n_tr), getDistance (n_tl, n_bl))
253 if size.x<=0.1:
254 size.x=0.1
255 if size.y<=0.1:
256 size.y=0.1
257 angle = getAngle(n_tl, n_tr)
258
259
260 relTouchCenter = getRelPos(absTouchCenter, pos, angle, Point2D(0,0))
261 pos += getOffsetForMovedPivot(
262 oldPivot = Point2D(0,0),
263 newPivot = relTouchCenter,
264 angle = angle)
265 pivot = relTouchCenter
266
267 def scaleMinMax(pos, size, angle, pivot):
268
269
270 oldAbsPivot = getAbsPos(pivot, pos, angle, pivot)
271
272 newSize = getScaledDim (size,
273 max = self.__maxSize,
274 min = self.__minSize)
275
276 ratio = newSize.x / size.x
277 newPivot = pivot * ratio
278
279 pos += getOffsetForMovedPivot(
280 oldPivot = pivot,
281 newPivot = newPivot,
282 angle = angle)
283 pivot = newPivot
284
285 size = newSize
286
287 newAbsPivot = getAbsPos(pivot, pos, angle, pivot)
288
289
290 pos -= (newAbsPivot - oldAbsPivot)
291
292 return (pos, size, angle, pivot)
293
294 (pos, size, angle, pivot) = scaleMinMax(pos, size, angle, pivot)
295
296 else:
297 assert False
298
299 if self.__moveNode:
300 self.__node.angle = angle
301 self.__node.size = size
302 self.__node.pos = pos
303 self.__node.pivot = pivot
304 self.__callback['onResize']()
305
306 self.__callback['onMotion'](pos, size, angle, pivot)
307
309 """ sets a new movement start point """
310
311
312 def getPos(pos):
313 return getAbsPos(pos, self.__node.pos, self.__node.angle, self.__node.pivot)
314 self.o_tl = getPos((0, 0))
315 self.o_bl = getPos((0, self.__node.height))
316 self.o_tr = getPos((self.__node.width, 0))
317
318
319 self.__oldpos = self.__node.pos
320
321 self.__lastFixPoint = None
322 self.__callback['onResetMotion']()
323