|
@@ -1,5 +1,5 @@
|
|
|
#!/usr/bin/env python
|
|
#!/usr/bin/env python
|
|
|
-from events import TapEvent, FlickEvent
|
|
|
|
|
|
|
+from events import TapEvent, FlickEvent, RotateEvent, PinchEvent, PanEvent
|
|
|
import time
|
|
import time
|
|
|
from math import atan2
|
|
from math import atan2
|
|
|
from threading import Thread
|
|
from threading import Thread
|
|
@@ -32,8 +32,11 @@ class TouchPoint(object):
|
|
|
self.x = x
|
|
self.x = x
|
|
|
self.y = y
|
|
self.y = y
|
|
|
|
|
|
|
|
|
|
+ def distance_to(self, other_x, other_y):
|
|
|
|
|
+ return distance(self.x, self.y, other_x, other_y)
|
|
|
|
|
+
|
|
|
def init_gesture_data(self, cx, cy):
|
|
def init_gesture_data(self, cx, cy):
|
|
|
- self.pinch = self.old_pinch = distance(self.x, self.y, cx, cy)
|
|
|
|
|
|
|
+ self.pinch = self.old_pinch = self.distance_to(cx, cy)
|
|
|
self.angle = self.old_angle = atan2(self.y - cy, self.x - cx)
|
|
self.angle = self.old_angle = atan2(self.y - cy, self.x - cx)
|
|
|
|
|
|
|
|
def set_angle(self, angle):
|
|
def set_angle(self, angle):
|
|
@@ -44,6 +47,9 @@ class TouchPoint(object):
|
|
|
self.old_pinch = self.pinch
|
|
self.old_pinch = self.pinch
|
|
|
self.pinch = pinch
|
|
self.pinch = pinch
|
|
|
|
|
|
|
|
|
|
+ def angle_diff(self):
|
|
|
|
|
+ return self.angle - self.old_angle
|
|
|
|
|
+
|
|
|
def dx(self):
|
|
def dx(self):
|
|
|
return int(self.x - self.px)
|
|
return int(self.x - self.px)
|
|
|
|
|
|
|
@@ -51,11 +57,14 @@ class TouchPoint(object):
|
|
|
return int(self.y - self.py)
|
|
return int(self.y - self.py)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
+# Heuristic constants
|
|
|
|
|
+# TODO: Encapsulate DPI resolution in distance heuristics
|
|
|
SUPPORTED_GESTURES = ('tap', 'pan', 'flick', 'rotate', 'pinch')
|
|
SUPPORTED_GESTURES = ('tap', 'pan', 'flick', 'rotate', 'pinch')
|
|
|
TUIO_ADDRESS = ('localhost', 3333)
|
|
TUIO_ADDRESS = ('localhost', 3333)
|
|
|
DOUBLE_TAP_DISTANCE = 30
|
|
DOUBLE_TAP_DISTANCE = 30
|
|
|
TAP_INTERVAL = .200
|
|
TAP_INTERVAL = .200
|
|
|
TAP_TIMEOUT = .200
|
|
TAP_TIMEOUT = .200
|
|
|
|
|
+MAX_MULTI_DRAG_DISTANCE = 100
|
|
|
|
|
|
|
|
|
|
|
|
|
class MultiTouchListener(object):
|
|
class MultiTouchListener(object):
|
|
@@ -84,11 +93,10 @@ class MultiTouchListener(object):
|
|
|
if self.points_changed:
|
|
if self.points_changed:
|
|
|
self.update_centroid()
|
|
self.update_centroid()
|
|
|
|
|
|
|
|
- # Do not try to rotate or pinch while dragging
|
|
|
|
|
|
|
+ # Do not try to rotate or pinch while panning
|
|
|
# This gets rid of a lot of jittery events
|
|
# This gets rid of a lot of jittery events
|
|
|
- if not self.detect_drag():
|
|
|
|
|
- self.detect_rotation()
|
|
|
|
|
- self.detect_pinch()
|
|
|
|
|
|
|
+ if not self.detect_pan():
|
|
|
|
|
+ self.detect_rotation_and_pinch()
|
|
|
|
|
|
|
|
self.points_changed = False
|
|
self.points_changed = False
|
|
|
|
|
|
|
@@ -110,14 +118,63 @@ class MultiTouchListener(object):
|
|
|
self.trigger(TapEvent(*self.taps[0]))
|
|
self.trigger(TapEvent(*self.taps[0]))
|
|
|
self.taps = []
|
|
self.taps = []
|
|
|
|
|
|
|
|
- def detect_rotation(self):
|
|
|
|
|
- if 'rotate' not in self.handlers:
|
|
|
|
|
- return
|
|
|
|
|
|
|
+ def detect_rotation_and_pinch(self):
|
|
|
|
|
+ """
|
|
|
|
|
+ Rotation is the average angle change between each point and the
|
|
|
|
|
+ centroid. Pinch is the average distance change from the points to the
|
|
|
|
|
+ centroid.
|
|
|
|
|
+ """
|
|
|
|
|
+ l = len(self.points)
|
|
|
|
|
|
|
|
- def detect_pinch(self):
|
|
|
|
|
- if 'pinch' not in self.handlers:
|
|
|
|
|
|
|
+ if 'pinch' not in self.handlers or l < 2:
|
|
|
return
|
|
return
|
|
|
|
|
|
|
|
|
|
+ rotation = pinch = 0
|
|
|
|
|
+
|
|
|
|
|
+ for p in self.points:
|
|
|
|
|
+ p.set_angle(atan2(p.y - cy, p.x - cx))
|
|
|
|
|
+ da = p.angle_diff()
|
|
|
|
|
+
|
|
|
|
|
+ # Assert that angle is in [-PI, PI]
|
|
|
|
|
+ if da > PI:
|
|
|
|
|
+ da -= 2 * PI
|
|
|
|
|
+ elif da < PI:
|
|
|
|
|
+ da += 2 * PI
|
|
|
|
|
+
|
|
|
|
|
+ rotation += da
|
|
|
|
|
+
|
|
|
|
|
+ p.set_pinch(distance(p.x, p.y, cx, cy))
|
|
|
|
|
+ pinch += p.pinch_diff()
|
|
|
|
|
+
|
|
|
|
|
+ if rotation:
|
|
|
|
|
+ self.trigger(RotateEvent(cx, cy, rotation / l, l))
|
|
|
|
|
+
|
|
|
|
|
+ if pinch:
|
|
|
|
|
+ self.trigger(PinchEvent(cx, cy, pinch / l, l))
|
|
|
|
|
+
|
|
|
|
|
+ def detect_pan(self):
|
|
|
|
|
+ """
|
|
|
|
|
+ Look for multi-finger drag events. Multi-drag is defined as all the
|
|
|
|
|
+ fingers moving close-ish together in the same direction.
|
|
|
|
|
+ """
|
|
|
|
|
+ l = len(self.points)
|
|
|
|
|
+ m = MAX_MULTI_DRAG_DISTANCE
|
|
|
|
|
+ clustered = l == 1 or all([p.distance_to(*self.centroid) <= m \
|
|
|
|
|
+ for p in self.points])
|
|
|
|
|
+ directions = [(cmp(p.dx(), 0), cmp(p.dy(), 0)) for p in self.points]
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+ if any(map(all, zip(*directions))) and clustered:
|
|
|
|
|
+ if l == 1:
|
|
|
|
|
+ p = self.points[0]
|
|
|
|
|
+ cx, cy, dx, dy = p.x, p.y, p.dx(), p.dy()
|
|
|
|
|
+ else:
|
|
|
|
|
+ cx, cy = self.centroid
|
|
|
|
|
+ old_cx, old_cy = self.old_centroid
|
|
|
|
|
+ dx, dy = cx - old_cx, cy - old_cy
|
|
|
|
|
+
|
|
|
|
|
+ self.trigger(PanEvent(cx, cy, dx, dy, l))
|
|
|
|
|
+
|
|
|
def point_down(self, sid, x, y):
|
|
def point_down(self, sid, x, y):
|
|
|
if sid in self.points:
|
|
if sid in self.points:
|
|
|
raise KeyError('Point with session id "%s" already exists.' % sid)
|
|
raise KeyError('Point with session id "%s" already exists.' % sid)
|
|
@@ -198,7 +255,7 @@ class MultiTouchListener(object):
|
|
|
|
|
|
|
|
def bind(self, gesture, handler):
|
|
def bind(self, gesture, handler):
|
|
|
if gesture not in SUPPORTED_GESTURES:
|
|
if gesture not in SUPPORTED_GESTURES:
|
|
|
- raise ValueError('Unsopported gesture "%s".' % gesture)
|
|
|
|
|
|
|
+ raise ValueError('Unsupported gesture "%s".' % gesture)
|
|
|
|
|
|
|
|
if gesture not in self.handlers:
|
|
if gesture not in self.handlers:
|
|
|
self.handlers[gesture] = []
|
|
self.handlers[gesture] = []
|
|
@@ -207,7 +264,10 @@ class MultiTouchListener(object):
|
|
|
|
|
|
|
|
def trigger(self, event):
|
|
def trigger(self, event):
|
|
|
if event.gesture in self.handlers:
|
|
if event.gesture in self.handlers:
|
|
|
- for handler in self.handlers[event.gesture]:
|
|
|
|
|
|
|
+ h = self.handlers[event.gesture]
|
|
|
|
|
+ self.log('Event triggered: "%s" (%d handlers)' % (event, len(h)))
|
|
|
|
|
+
|
|
|
|
|
+ for handler in h:
|
|
|
handler(event)
|
|
handler(event)
|
|
|
|
|
|
|
|
|
|
|