| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204 |
- from __future__ import division
- from ..tracker import GestureTracker, Gesture
- from ..geometry import Positionable, MovingPositionable
- class RotationGesture(Gesture, Positionable):
- """
- A rotation gesture has a angle in radians and a rotational centroid.
- """
- _type = 'rotate'
- def __init__(self, event, centroid, angle, n=1):
- Gesture.__init__(self, event)
- Positionable.__init__(self, *centroid.get_position())
- self.angle = angle
- self.n = n
- def __str__(self):
- return '<RotationGesture at (%s, %s) angle=%s>' \
- % (self.x, self.y, self.angle)
- def get_angle(self):
- return -self.angle
- class PinchGesture(Gesture, Positionable):
- """
- A pinch gesture has a scale (1.0 means no scaling) and a centroid from
- which the scaling originates.
- """
- _type = 'pinch'
- def __init__(self, event, centroid, scale, n=1):
- Gesture.__init__(self, event)
- Positionable.__init__(self, *centroid.get_position())
- self.scale = scale
- self.n = n
- def __str__(self):
- return '<PinchGesture at (%s, %s) scale=%s>' \
- % (self.x, self.y, self.scale)
- def get_scale(self):
- return self.scale
- class DragGesture(Gesture, Positionable):
- """
- A momevent gesture has an initial position, and a translation from that
- position.
- """
- _type = 'drag'
- def __init__(self, event, initial_position, translation, n=1):
- Gesture.__init__(self, event)
- Positionable.__init__(self, *initial_position.get_position())
- self.translation = translation
- self.n = n
- def __str__(self):
- x, y = self
- tx, ty = self.translation
- return '<DragGesture at (%s, %s) translation=(%s, %s) n=%d>' \
- % (x, y, tx, ty, self.n)
- def get_translation(self):
- return self.translation
- class FlickGesture(Gesture, Positionable):
- _type = 'flick'
- def __init__(self, event, initial_position, translation):
- Gesture.__init__(self, event)
- Positionable.__init__(self, *initial_position.get_position())
- self.translation = translation
- def __str__(self):
- x, y = self.get_position()
- tx, ty = self.translation
- return '<FlickGesture at (%s, %s) translation=(%s, %s)>' \
- % (x, y, tx, ty)
- def get_translation(self):
- return self.translation
- class TransformationTracker(GestureTracker):
- """
- Tracker for linear transformations. This implementation detects rotation,
- scaling and translation using the centroid of all touch points.
- """
- supported_gestures = [RotationGesture, PinchGesture, DragGesture,
- FlickGesture]
- def __init__(self, area):
- super(TransformationTracker, self).__init__(area)
- # All touch points performing the transformation
- self.points = []
- # Current and previous centroid of all touch points
- self.centroid = None
- self.deleted = []
- def update_centroid(self):
- 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, event):
- self.points.append(event.point)
- self.update_centroid()
- event.stop_propagation()
- def on_point_move(self, event):
- point = event.point
- if point not in self.points:
- pid = point.get_id()
- if pid not in self.deleted:
- return
- self.debug('recovered %s' % point)
- self.deleted.remove(pid)
- self.points.append(point)
- self.update_centroid()
- event.stop_propagation()
- self.invalidate_points()
- l = len(self.points)
- if l > 1:
- offset_centroid = self.centroid - self.area.get_screen_offset()
- # Rotation (around the previous centroid)
- rotation = point.rotation_around(self.centroid) / l
- self.trigger(RotationGesture(event, offset_centroid, rotation, l))
- # Scale
- prev = self.centroid.distance_to(point.get_previous_position())
- dist = self.centroid.distance_to(point)
- dist = prev + (dist - prev) / l
- scale = dist / prev
- self.trigger(PinchGesture(event, offset_centroid, scale, l))
- # Update centroid before movement can be detected
- self.update_centroid()
- # Movement using multiple touch points
- self.trigger(DragGesture(event, self.centroid,
- self.centroid.translation(), l))
- def on_point_up(self, event):
- if event.point in self.points:
- self.points.remove(event.point)
- if not self.points:
- self.trigger(FlickGesture(event, self.centroid,
- self.centroid.translation()))
- self.update_centroid()
- event.stop_propagation()
- def invalidate_points(self):
- """
- Check if all points are still in the corresponding area, and remove
- those which are not.
- """
- delete = []
- if self.area.parent:
- ox, oy = self.area.parent.get_screen_offset()
- else:
- ox = oy = 0
- for i, p in enumerate(self.points):
- x, y = p.get_position()
- if not self.area.contains(x - ox, y - oy):
- self.debug('deleted %s' % p)
- delete.append(i)
- self.deleted.append(p.get_id())
- if delete:
- self.points = [p for i, p in enumerate(self.points)
- if i not in delete]
- self.update_centroid()
|