import time from threading import Thread from ..tracker import GestureTracker from ..geometry import Positionable from utils import PointGesture class TapGesture(PointGesture): """ A tap gesture is triggered when a touch point releases from the screen within a certain time and distance from its 'point_down' event. """ _type = 'tap' class SingleTapGesture(TapGesture): """ A single tap gesture is triggered after a regular tap gesture, if no double tap is triggered for that gesture. """ _type = 'single_tap' class DoubleTapGesture(TapGesture): """ A double tap gesture is triggered if two sequential taps are triggered within a certain time and distance of eachother. """ _type = 'double_tap' class TapTracker(GestureTracker): supported_gestures = [TapGesture, SingleTapGesture, DoubleTapGesture] configurable = ['tap_distance', 'tap_time', 'double_tap_time', 'double_tap_distance', 'update_rate', 'propagate_up_event'] def __init__(self, window=None): super(TapTracker, self).__init__(window) # Map of touch object id to tuple (timestamp, position) of point down self.reg = {} # Maximum radius in which a touch point can move in order to be a tap # event in pixels self.tap_distance = 20 # Maximum time between 'down' and 'up' of a tap event in seconds self.tap_time = .2 # Maximum time in seconds and distance in pixels between two taps to # count as double tap self.double_tap_time = .3 self.double_tap_distance = 30 # Times per second to detect single taps self.update_rate = 30 # Whether to stop propagation of the 'point_up' event to parent widgets # If False, this reserves tap events to child widgets self.propagate_up_event = True self.reset_last_tap() self.single_tap_thread = Thread(target=self.detect_single_tap) self.single_tap_thread.daemon = True self.single_tap_thread.start() def detect_single_tap(self): """ Iteration function for single-tap detection thread. """ while True: time_diff = time.time() - self.last_tap_time if self.last_tap and time_diff > self.double_tap_time: # Last tap is too long ago to be a double tap, so trigger a # single tap self.trigger(SingleTapGesture(None, self.last_tap)) self.reset_last_tap() time.sleep(1. / self.update_rate) def reset_last_tap(self): self.last_tap_time = 0 self.last_tap = None def on_point_down(self, event): self.reg[event.point.get_id()] = time.time(), event def on_point_move(self, event): oid = event.point.get_id() if oid not in self.reg: return # If a stationary point moves beyond a threshold, delete it so that the # 'up' event will not trigger a 'tap' t, down_event = self.reg[oid] if event.distance_to(down_event) > self.tap_distance: del self.reg[oid] def on_point_up(self, event): oid = event.point.get_id() # Assert that the point has not been deleted by a 'move' event yet if oid not in self.reg: return down_time = self.reg[oid][0] del self.reg[oid] # Only trigger a tap event if the 'up' is triggered within a certain # time afer the 'down' current_time = time.time() if current_time - down_time > self.tap_time: return if not self.propagate_up_event: event.stop_propagation() tap = TapGesture(event) self.trigger(tap) # Trigger double tap if the threshold has not not expired yet if self.last_tap: if self.last_tap.distance_to(tap) <= self.double_tap_distance: # Close enough to be a double tap self.trigger(DoubleTapGesture(event, self.last_tap)) self.reset_last_tap() return # Generate a seperate single tap gesture for the last tap, # because the lat tap variable is overwritten now self.trigger(SingleTapGesture(event, self.last_tap)) self.last_tap_time = current_time self.last_tap = tap