|
@@ -0,0 +1,117 @@
|
|
|
|
|
+from __future__ import division
|
|
|
|
|
+
|
|
|
|
|
+from ..tracker import GestureTracker, Gesture
|
|
|
|
|
+from ..geometry import Positionable, MovingPositionable
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+class TransformationTracker(GestureTracker):
|
|
|
|
|
+ """
|
|
|
|
|
+ Tracker for linear transformations. This implementation detects rotation,
|
|
|
|
|
+ scaling and translation using the centroid of all touch points.
|
|
|
|
|
+ """
|
|
|
|
|
+ __gesture_types__ = ['rotate', 'pinch', 'move']
|
|
|
|
|
+
|
|
|
|
|
+ def __init__(self, window=None):
|
|
|
|
|
+ super(TransformationTracker, self).__init__(window)
|
|
|
|
|
+
|
|
|
|
|
+ # All touch points performing the transformation
|
|
|
|
|
+ self.points = []
|
|
|
|
|
+
|
|
|
|
|
+ # Current and previous centroid of all touch points
|
|
|
|
|
+ self.prev_centroid = self.centroid = None
|
|
|
|
|
+
|
|
|
|
|
+ def update_centroid(self):
|
|
|
|
|
+ self.prev_centroid = self.centroid
|
|
|
|
|
+
|
|
|
|
|
+ if not self.points:
|
|
|
|
|
+ self.centroid = None
|
|
|
|
|
+ return
|
|
|
|
|
+
|
|
|
|
|
+ # Calculate average touch point coordinates
|
|
|
|
|
+ l = len(self.points)
|
|
|
|
|
+ coords = [p.get_position() for p in self.points]
|
|
|
|
|
+ all_x, all_y = zip(*coords)
|
|
|
|
|
+ x = sum(all_x) / l
|
|
|
|
|
+ y = sum(all_y) / l
|
|
|
|
|
+
|
|
|
|
|
+ # Update centroid positionable
|
|
|
|
|
+ if self.centroid:
|
|
|
|
|
+ self.centroid.set_position(x, y)
|
|
|
|
|
+ else:
|
|
|
|
|
+ self.centroid = MovingPositionable(x, y)
|
|
|
|
|
+
|
|
|
|
|
+ def on_point_down(self, point):
|
|
|
|
|
+ self.points.append(point)
|
|
|
|
|
+ self.update_centroid()
|
|
|
|
|
+
|
|
|
|
|
+ def on_point_move(self, point):
|
|
|
|
|
+ if len(self.points) > 1:
|
|
|
|
|
+ # Rotation (around the previous centroid)
|
|
|
|
|
+ if self.is_type_bound('rotate'):
|
|
|
|
|
+ rotation = point.rotation_around(self.centroid)
|
|
|
|
|
+ self.trigger(RotationGesture(self.centroid, rotation))
|
|
|
|
|
+
|
|
|
|
|
+ # Scale
|
|
|
|
|
+ if self.is_type_bound('pinch'):
|
|
|
|
|
+ prev = point.get_previous_position().distance_to(self.centroid)
|
|
|
|
|
+ dist = point.distance_to(self.centroid)
|
|
|
|
|
+ scale = dist / prev
|
|
|
|
|
+ self.trigger(PinchGesture(self.centroid, scale))
|
|
|
|
|
+
|
|
|
|
|
+ # Update centroid before movement can be detected
|
|
|
|
|
+ self.update_centroid()
|
|
|
|
|
+
|
|
|
|
|
+ # Movement
|
|
|
|
|
+ self.trigger(MovementGesture(self.centroid,
|
|
|
|
|
+ self.centroid.translation()))
|
|
|
|
|
+
|
|
|
|
|
+ def on_point_up(self, point):
|
|
|
|
|
+ self.points.remove(point)
|
|
|
|
|
+ self.update_centroid()
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+class RotationGesture(Positionable, Gesture):
|
|
|
|
|
+ """
|
|
|
|
|
+ A rotation gesture has a angle in radians and a rotational centroid.
|
|
|
|
|
+ """
|
|
|
|
|
+ __type__ = 'rotate'
|
|
|
|
|
+
|
|
|
|
|
+ def __init__(self, centroid, angle):
|
|
|
|
|
+ Positionable.__init__(self, *centroid.get_position())
|
|
|
|
|
+ self.angle = angle
|
|
|
|
|
+
|
|
|
|
|
+ def __str__(self):
|
|
|
|
|
+ return '<RotationGesture at (%s, %s) angle=%s>' \
|
|
|
|
|
+ % (self.x, self.y, self.angle)
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+class PinchGesture(Positionable, Gesture):
|
|
|
|
|
+ """
|
|
|
|
|
+ A pinch gesture has a scale (1.0 means no scaling) and a centroid from
|
|
|
|
|
+ which the scaling originates.
|
|
|
|
|
+ """
|
|
|
|
|
+ __type__ = 'pinch'
|
|
|
|
|
+
|
|
|
|
|
+ def __init__(self, centroid, scale):
|
|
|
|
|
+ Positionable.__init__(self, *centroid.get_position())
|
|
|
|
|
+ self.scale = scale
|
|
|
|
|
+
|
|
|
|
|
+ def __str__(self):
|
|
|
|
|
+ return '<PinchGesture at (%s, %s) scale=%s>' \
|
|
|
|
|
+ % (self.x, self.y, self.scale)
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+class MovementGesture(Positionable, Gesture):
|
|
|
|
|
+ """
|
|
|
|
|
+ A momevent gesture has an initial position, and a translation from that
|
|
|
|
|
+ position.
|
|
|
|
|
+ """
|
|
|
|
|
+ __type__ = 'move'
|
|
|
|
|
+
|
|
|
|
|
+ def __init__(self, initial_position, translation):
|
|
|
|
|
+ Positionable.__init__(self, *initial_position.get_position())
|
|
|
|
|
+ self.translation = translation
|
|
|
|
|
+
|
|
|
|
|
+ def __str__(self):
|
|
|
|
|
+ return '<MovementGesture at (%s, %s) translation=(%s, %s)>' \
|
|
|
|
|
+ % (self.get_position() + self.translation.get_position())
|