tap.py 4.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140
  1. import time
  2. from threading import Thread
  3. from ..tracker import GestureTracker
  4. from ..geometry import Positionable
  5. from utils import PointGesture
  6. class TapGesture(PointGesture):
  7. """
  8. A tap gesture is triggered when a touch point releases from the screen
  9. within a certain time and distance from its 'point_down' event.
  10. """
  11. _type = 'tap'
  12. class SingleTapGesture(TapGesture):
  13. """
  14. A single tap gesture is triggered after a regular tap gesture, if no double
  15. tap is triggered for that gesture.
  16. """
  17. _type = 'single_tap'
  18. class DoubleTapGesture(TapGesture):
  19. """
  20. A double tap gesture is triggered if two sequential taps are triggered
  21. within a certain time and distance of eachother.
  22. """
  23. _type = 'double_tap'
  24. class TapTracker(GestureTracker):
  25. supported_gestures = [TapGesture, SingleTapGesture, DoubleTapGesture]
  26. configurable = ['tap_distance', 'tap_time', 'double_tap_time',
  27. 'double_tap_distance', 'update_rate', 'propagate_up_event']
  28. def __init__(self, window=None):
  29. super(TapTracker, self).__init__(window)
  30. # Map of touch object id to tuple (timestamp, position) of point down
  31. self.reg = {}
  32. # Maximum radius in which a touch point can move in order to be a tap
  33. # event in pixels
  34. self.tap_distance = 20
  35. # Maximum time between 'down' and 'up' of a tap event in seconds
  36. self.tap_time = .2
  37. # Maximum time in seconds and distance in pixels between two taps to
  38. # count as double tap
  39. self.double_tap_time = .3
  40. self.double_tap_distance = 30
  41. # Times per second to detect single taps
  42. self.update_rate = 30
  43. # Whether to stop propagation of the 'point_up' event to parent widgets
  44. # If False, this reserves tap events to child widgets
  45. self.propagate_up_event = True
  46. self.reset_last_tap()
  47. self.single_tap_thread = Thread(target=self.detect_single_tap)
  48. self.single_tap_thread.daemon = True
  49. self.single_tap_thread.start()
  50. def detect_single_tap(self):
  51. """
  52. Iteration function for single-tap detection thread.
  53. """
  54. while True:
  55. time_diff = time.time() - self.last_tap_time
  56. if self.last_tap and time_diff > self.double_tap_time:
  57. # Last tap is too long ago to be a double tap, so trigger a
  58. # single tap
  59. self.trigger(SingleTapGesture(None, self.last_tap))
  60. self.reset_last_tap()
  61. time.sleep(1. / self.update_rate)
  62. def reset_last_tap(self):
  63. self.last_tap_time = 0
  64. self.last_tap = None
  65. def on_point_down(self, event):
  66. self.reg[event.point.get_id()] = time.time(), event
  67. def on_point_move(self, event):
  68. oid = event.point.get_id()
  69. if oid not in self.reg:
  70. return
  71. # If a stationary point moves beyond a threshold, delete it so that the
  72. # 'up' event will not trigger a 'tap'
  73. t, down_event = self.reg[oid]
  74. if event.distance_to(down_event) > self.tap_distance:
  75. del self.reg[oid]
  76. def on_point_up(self, event):
  77. oid = event.point.get_id()
  78. # Assert that the point has not been deleted by a 'move' event yet
  79. if oid not in self.reg:
  80. return
  81. down_time = self.reg[oid][0]
  82. del self.reg[oid]
  83. # Only trigger a tap event if the 'up' is triggered within a certain
  84. # time afer the 'down'
  85. current_time = time.time()
  86. if current_time - down_time > self.tap_time:
  87. return
  88. if not self.propagate_up_event:
  89. event.stop_propagation()
  90. tap = TapGesture(event)
  91. self.trigger(tap)
  92. # Trigger double tap if the threshold has not not expired yet
  93. if self.last_tap:
  94. if self.last_tap.distance_to(tap) <= self.double_tap_distance:
  95. # Close enough to be a double tap
  96. self.trigger(DoubleTapGesture(event, self.last_tap))
  97. self.reset_last_tap()
  98. return
  99. # Generate a seperate single tap gesture for the last tap,
  100. # because the lat tap variable is overwritten now
  101. self.trigger(SingleTapGesture(event, self.last_tap))
  102. self.last_tap_time = current_time
  103. self.last_tap = tap