transform.py 3.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126
  1. from __future__ import division
  2. from ..tracker import GestureTracker, Gesture
  3. from ..geometry import Positionable, MovingPositionable
  4. class TransformationTracker(GestureTracker):
  5. """
  6. Tracker for linear transformations. This implementation detects rotation,
  7. scaling and translation using the centroid of all touch points.
  8. """
  9. __gesture_types__ = ['rotate', 'pinch', 'move']
  10. def __init__(self, window=None):
  11. super(TransformationTracker, self).__init__(window)
  12. # All touch points performing the transformation
  13. self.points = []
  14. # Current and previous centroid of all touch points
  15. self.prev_centroid = self.centroid = None
  16. def update_centroid(self):
  17. self.prev_centroid = self.centroid
  18. if not self.points:
  19. self.centroid = None
  20. return
  21. # Calculate average touch point coordinates
  22. l = len(self.points)
  23. coords = [p.get_position() for p in self.points]
  24. all_x, all_y = zip(*coords)
  25. x = sum(all_x) / l
  26. y = sum(all_y) / l
  27. # Update centroid positionable
  28. if self.centroid:
  29. self.centroid.set_position(x, y)
  30. else:
  31. self.centroid = MovingPositionable(x, y)
  32. def on_point_down(self, point):
  33. self.points.append(point)
  34. self.update_centroid()
  35. def on_point_move(self, point):
  36. l = len(self.points)
  37. if l > 1:
  38. # Rotation (around the previous centroid)
  39. if self.is_type_bound('rotate'):
  40. rotation = point.rotation_around(self.centroid) / l
  41. self.trigger(RotationGesture(self.centroid, rotation))
  42. # Scale
  43. if self.is_type_bound('pinch'):
  44. prev = point.get_previous_position().distance_to(self.centroid)
  45. dist = point.distance_to(self.centroid)
  46. dist = prev + (dist - prev) / l
  47. scale = dist / prev
  48. self.trigger(PinchGesture(self.centroid, scale))
  49. # Update centroid before movement can be detected
  50. self.update_centroid()
  51. # Movement
  52. self.trigger(MovementGesture(self.centroid,
  53. self.centroid.translation()))
  54. def on_point_up(self, point):
  55. self.points.remove(point)
  56. self.update_centroid()
  57. class RotationGesture(Positionable, Gesture):
  58. """
  59. A rotation gesture has a angle in radians and a rotational centroid.
  60. """
  61. __type__ = 'rotate'
  62. def __init__(self, centroid, angle):
  63. Positionable.__init__(self, *centroid.get_position())
  64. self.angle = angle
  65. def __str__(self):
  66. return '<RotationGesture at (%s, %s) angle=%s>' \
  67. % (self.x, self.y, self.angle)
  68. def get_angle(self):
  69. return self.angle
  70. class PinchGesture(Positionable, Gesture):
  71. """
  72. A pinch gesture has a scale (1.0 means no scaling) and a centroid from
  73. which the scaling originates.
  74. """
  75. __type__ = 'pinch'
  76. def __init__(self, centroid, scale):
  77. Positionable.__init__(self, *centroid.get_position())
  78. self.scale = scale
  79. def __str__(self):
  80. return '<PinchGesture at (%s, %s) scale=%s>' \
  81. % (self.x, self.y, self.scale)
  82. def get_scale(self):
  83. return self.scale
  84. class MovementGesture(Positionable, Gesture):
  85. """
  86. A momevent gesture has an initial position, and a translation from that
  87. position.
  88. """
  89. __type__ = 'move'
  90. def __init__(self, initial_position, translation):
  91. Positionable.__init__(self, *initial_position.get_position())
  92. self.translation = translation
  93. def __str__(self):
  94. return '<MovementGesture at (%s, %s) translation=(%s, %s)>' \
  95. % (self.get_position() + self.translation.get_position())