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): l = len(self.points) if l > 1: # Rotation (around the previous centroid) if self.is_type_bound('rotate'): rotation = point.rotation_around(self.centroid) / l 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) dist = prev + (dist - prev) / l 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 '' \ % (self.x, self.y, self.angle) def get_angle(self): return 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 '' \ % (self.x, self.y, self.scale) def get_scale(self): return 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 '' \ % (self.get_position() + self.translation.get_position())