first commit
This commit is contained in:
commit
417e54da96
5696 changed files with 900003 additions and 0 deletions
|
@ -0,0 +1,27 @@
|
|||
'''
|
||||
Input Postprocessing
|
||||
====================
|
||||
|
||||
'''
|
||||
|
||||
__all__ = ('kivy_postproc_modules', )
|
||||
|
||||
import os
|
||||
from kivy.input.postproc.doubletap import InputPostprocDoubleTap
|
||||
from kivy.input.postproc.tripletap import InputPostprocTripleTap
|
||||
from kivy.input.postproc.ignorelist import InputPostprocIgnoreList
|
||||
from kivy.input.postproc.retaintouch import InputPostprocRetainTouch
|
||||
from kivy.input.postproc.dejitter import InputPostprocDejitter
|
||||
from kivy.input.postproc.calibration import InputPostprocCalibration
|
||||
|
||||
# Mapping of ID to module
|
||||
kivy_postproc_modules = {}
|
||||
|
||||
# Don't go further if we generate documentation
|
||||
if 'KIVY_DOC' not in os.environ:
|
||||
kivy_postproc_modules['calibration'] = InputPostprocCalibration()
|
||||
kivy_postproc_modules['retaintouch'] = InputPostprocRetainTouch()
|
||||
kivy_postproc_modules['ignorelist'] = InputPostprocIgnoreList()
|
||||
kivy_postproc_modules['doubletap'] = InputPostprocDoubleTap()
|
||||
kivy_postproc_modules['tripletap'] = InputPostprocTripleTap()
|
||||
kivy_postproc_modules['dejitter'] = InputPostprocDejitter()
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -0,0 +1,200 @@
|
|||
'''
|
||||
Calibration
|
||||
===========
|
||||
|
||||
.. versionadded:: 1.9.0
|
||||
|
||||
Recalibrate input device to a specific range / offset.
|
||||
|
||||
Let's say you have 3 1080p displays, the 2 firsts are multitouch. By default,
|
||||
both will have mixed touch, the range will conflict with each others: the 0-1
|
||||
range will goes to 0-5760 px (remember, 3 * 1920 = 5760.)
|
||||
|
||||
To fix it, you need to manually reference them. For example::
|
||||
|
||||
[input]
|
||||
left = mtdev,/dev/input/event17
|
||||
middle = mtdev,/dev/input/event15
|
||||
# the right screen is just a display.
|
||||
|
||||
Then, you can use the calibration postproc module::
|
||||
|
||||
[postproc:calibration]
|
||||
left = xratio=0.3333
|
||||
middle = xratio=0.3333,xoffset=0.3333
|
||||
|
||||
Now, the touches from the left screen will be within 0-0.3333 range, and the
|
||||
touches from the middle screen will be within 0.3333-0.6666 range.
|
||||
|
||||
You can also match calibration rules to devices based on their provider type.
|
||||
This is useful when probesysfs is used to match devices. For example::
|
||||
|
||||
[input]
|
||||
mtdev_%(name)s = probesysfs,provider=mtdev
|
||||
|
||||
Then to apply calibration to any mtdev device, you can assign rules to the
|
||||
provider name enclosed by parentheses::
|
||||
|
||||
[postproc:calibration]
|
||||
(mtdev) = xratio=0.3333,xoffset=0.3333
|
||||
|
||||
Calibrating devices like this means the device's path doesn't need to be
|
||||
configured ahead of time. Note that with this method, all mtdev inputs will
|
||||
have the same calibration applied to them. For this reason, matching by
|
||||
provider will typically be useful when expecting only one input device.
|
||||
'''
|
||||
|
||||
__all__ = ('InputPostprocCalibration', )
|
||||
|
||||
from kivy.config import Config
|
||||
from kivy.logger import Logger
|
||||
from kivy.input import providers
|
||||
from kivy.input.factory import MotionEventFactory
|
||||
from kivy.input.motionevent import MotionEvent
|
||||
|
||||
|
||||
class InputPostprocCalibration(object):
|
||||
'''Recalibrate the inputs.
|
||||
|
||||
The configuration must go within a section named `postproc:calibration`.
|
||||
Within the section, you must have a line like::
|
||||
|
||||
devicename = param=value,param=value
|
||||
|
||||
If you wish to match by provider, you must have a line like::
|
||||
|
||||
(provider) = param=value,param=value
|
||||
|
||||
:Parameters:
|
||||
`xratio`: float
|
||||
Value to multiply X
|
||||
`yratio`: float
|
||||
Value to multiply Y
|
||||
`xoffset`: float
|
||||
Value to add to X
|
||||
`yoffset`: float
|
||||
Value to add to Y
|
||||
`auto`: str
|
||||
If set, then the touch is transformed from screen-relative
|
||||
to window-relative The value is used as an indication of
|
||||
screen size, e.g for fullHD:
|
||||
|
||||
auto=1920x1080
|
||||
|
||||
If present, this setting overrides all the others.
|
||||
This assumes the input device exactly covers the display
|
||||
area, if they are different, the computations will be wrong.
|
||||
|
||||
.. versionchanged:: 1.11.0
|
||||
Added `auto` parameter
|
||||
'''
|
||||
|
||||
def __init__(self):
|
||||
super(InputPostprocCalibration, self).__init__()
|
||||
self.devices = {}
|
||||
self.frame = 0
|
||||
self.provider_map = self._get_provider_map()
|
||||
if not Config.has_section('postproc:calibration'):
|
||||
return
|
||||
default_params = {'xoffset': 0, 'yoffset': 0, 'xratio': 1, 'yratio': 1}
|
||||
for device_key, params_str in Config.items('postproc:calibration'):
|
||||
params = default_params.copy()
|
||||
for param in params_str.split(','):
|
||||
param = param.strip()
|
||||
if not param:
|
||||
continue
|
||||
key, value = param.split('=', 1)
|
||||
if key == 'auto':
|
||||
width, height = [float(x) for x in value.split('x')]
|
||||
params['auto'] = width, height
|
||||
break
|
||||
if key not in ('xoffset', 'yoffset', 'xratio', 'yratio'):
|
||||
Logger.error(
|
||||
'Calibration: invalid key provided: {}'.format(key))
|
||||
params[key] = float(value)
|
||||
self.devices[device_key] = params
|
||||
|
||||
def _get_provider_map(self):
|
||||
"""Iterates through all registered input provider names and finds the
|
||||
respective MotionEvent subclass for each. Returns a dict of MotionEvent
|
||||
subclasses mapped to their provider name.
|
||||
"""
|
||||
provider_map = {}
|
||||
for input_provider in MotionEventFactory.list():
|
||||
if not hasattr(providers, input_provider):
|
||||
continue
|
||||
|
||||
p = getattr(providers, input_provider)
|
||||
for m in p.__all__:
|
||||
event = getattr(p, m)
|
||||
if issubclass(event, MotionEvent):
|
||||
provider_map[event] = input_provider
|
||||
|
||||
return provider_map
|
||||
|
||||
def _get_provider_key(self, event):
|
||||
"""Returns the provider key for the event if the provider is configured
|
||||
for calibration.
|
||||
"""
|
||||
input_type = self.provider_map.get(event.__class__)
|
||||
key = '({})'.format(input_type)
|
||||
if input_type and key in self.devices:
|
||||
return key
|
||||
|
||||
def process(self, events):
|
||||
# avoid doing any processing if there is no device to calibrate at all.
|
||||
if not self.devices:
|
||||
return events
|
||||
|
||||
self.frame += 1
|
||||
frame = self.frame
|
||||
to_remove = []
|
||||
for etype, event in events:
|
||||
# frame-based logic below doesn't account for
|
||||
# end events having been already processed
|
||||
if etype == 'end':
|
||||
continue
|
||||
|
||||
if event.device in self.devices:
|
||||
dev = event.device
|
||||
else:
|
||||
dev = self._get_provider_key(event)
|
||||
if not dev:
|
||||
continue
|
||||
|
||||
# some providers use the same event to update and end
|
||||
if 'calibration:frame' not in event.ud:
|
||||
event.ud['calibration:frame'] = frame
|
||||
elif event.ud['calibration:frame'] == frame:
|
||||
continue
|
||||
event.ud['calibration:frame'] = frame
|
||||
|
||||
params = self.devices[dev]
|
||||
if 'auto' in params:
|
||||
event.sx, event.sy = self.auto_calibrate(
|
||||
event.sx, event.sy, params['auto'])
|
||||
if not (0 <= event.sx <= 1 and 0 <= event.sy <= 1):
|
||||
to_remove.append((etype, event))
|
||||
else:
|
||||
event.sx = event.sx * params['xratio'] + params['xoffset']
|
||||
event.sy = event.sy * params['yratio'] + params['yoffset']
|
||||
|
||||
for event in to_remove:
|
||||
events.remove(event)
|
||||
|
||||
return events
|
||||
|
||||
def auto_calibrate(self, sx, sy, size):
|
||||
from kivy.core.window import Window as W
|
||||
WIDTH, HEIGHT = size
|
||||
|
||||
xratio = WIDTH / W.width
|
||||
yratio = HEIGHT / W.height
|
||||
|
||||
xoffset = - W.left / W.width
|
||||
yoffset = - (HEIGHT - W.top - W.height) / W.height
|
||||
|
||||
sx = sx * xratio + xoffset
|
||||
sy = sy * yratio + yoffset
|
||||
|
||||
return sx, sy
|
|
@ -0,0 +1,74 @@
|
|||
'''
|
||||
Dejitter
|
||||
========
|
||||
|
||||
Prevent blob jittering.
|
||||
|
||||
A problem that is often faced (esp. in optical MT setups) is that of
|
||||
jitterish BLOBs caused by bad camera characteristics. With this module
|
||||
you can get rid of that jitter. You just define a threshold
|
||||
`jitter_distance` in your config, and all touch movements that move
|
||||
the touch by less than the jitter distance are considered 'bad'
|
||||
movements caused by jitter and will be discarded.
|
||||
'''
|
||||
|
||||
__all__ = ('InputPostprocDejitter', )
|
||||
|
||||
from kivy.config import Config
|
||||
|
||||
|
||||
class InputPostprocDejitter(object):
|
||||
'''
|
||||
Get rid of jitterish BLOBs.
|
||||
Example::
|
||||
|
||||
[postproc]
|
||||
jitter_distance = 0.004
|
||||
jitter_ignore_devices = mouse,mactouch
|
||||
|
||||
:Configuration:
|
||||
`jitter_distance`: float
|
||||
A float in range 0-1.
|
||||
`jitter_ignore_devices`: string
|
||||
A comma-separated list of device identifiers that
|
||||
should not be processed by dejitter (because they're
|
||||
very precise already).
|
||||
'''
|
||||
|
||||
def __init__(self):
|
||||
self.jitterdist = Config.getfloat('postproc', 'jitter_distance')
|
||||
ignore_devices = Config.get('postproc', 'jitter_ignore_devices')
|
||||
self.ignore_devices = ignore_devices.split(',')
|
||||
self.last_touches = {}
|
||||
|
||||
def taxicab_distance(self, p, q):
|
||||
# Get the taxicab/manhattan/citiblock distance for efficiency reasons
|
||||
return abs(p[0] - q[0]) + abs(p[1] - q[1])
|
||||
|
||||
def process(self, events):
|
||||
if not self.jitterdist:
|
||||
return events
|
||||
processed = []
|
||||
for etype, touch in events:
|
||||
if not touch.is_touch:
|
||||
continue
|
||||
if touch.device in self.ignore_devices:
|
||||
processed.append((etype, touch))
|
||||
continue
|
||||
if etype == 'begin':
|
||||
self.last_touches[touch.id] = touch.spos
|
||||
if etype == 'end':
|
||||
if touch.id in self.last_touches:
|
||||
del self.last_touches[touch.id]
|
||||
if etype != 'update':
|
||||
processed.append((etype, touch))
|
||||
continue
|
||||
# Check whether the touch moved more than the jitter distance
|
||||
last_spos = self.last_touches[touch.id]
|
||||
dist = self.taxicab_distance(last_spos, touch.spos)
|
||||
if dist > self.jitterdist:
|
||||
# Only if the touch has moved more than the jitter dist we take
|
||||
# it into account and dispatch it. Otherwise suppress it.
|
||||
self.last_touches[touch.id] = touch.spos
|
||||
processed.append((etype, touch))
|
||||
return processed
|
|
@ -0,0 +1,101 @@
|
|||
'''
|
||||
Double Tap
|
||||
==========
|
||||
|
||||
Search touch for a double tap
|
||||
'''
|
||||
|
||||
__all__ = ('InputPostprocDoubleTap', )
|
||||
|
||||
from time import time
|
||||
from kivy.config import Config
|
||||
from kivy.vector import Vector
|
||||
|
||||
|
||||
class InputPostprocDoubleTap(object):
|
||||
'''
|
||||
InputPostProcDoubleTap is a post-processor to check if
|
||||
a touch is a double tap or not.
|
||||
Double tap can be configured in the Kivy config file::
|
||||
|
||||
[postproc]
|
||||
double_tap_time = 250
|
||||
double_tap_distance = 20
|
||||
|
||||
Distance parameter is in the range 0-1000 and time is in milliseconds.
|
||||
'''
|
||||
|
||||
def __init__(self):
|
||||
dist = Config.getint('postproc', 'double_tap_distance')
|
||||
self.double_tap_distance = dist / 1000.0
|
||||
tap_time = Config.getint('postproc', 'double_tap_time')
|
||||
self.double_tap_time = tap_time / 1000.0
|
||||
self.touches = {}
|
||||
|
||||
def find_double_tap(self, ref):
|
||||
'''Find a double tap touch within self.touches.
|
||||
The touch must be not a previous double tap and the distance must be
|
||||
within the specified threshold. Additionally, the touch profiles
|
||||
must be the same kind of touch.
|
||||
'''
|
||||
ref_button = None
|
||||
if 'button' in ref.profile:
|
||||
ref_button = ref.button
|
||||
|
||||
for touchid in self.touches:
|
||||
if ref.uid == touchid:
|
||||
continue
|
||||
etype, touch = self.touches[touchid]
|
||||
if etype != 'end':
|
||||
continue
|
||||
if touch.is_double_tap:
|
||||
continue
|
||||
distance = Vector.distance(
|
||||
Vector(ref.sx, ref.sy),
|
||||
Vector(touch.osx, touch.osy))
|
||||
if distance > self.double_tap_distance:
|
||||
continue
|
||||
if touch.is_mouse_scrolling or ref.is_mouse_scrolling:
|
||||
continue
|
||||
touch_button = None
|
||||
if 'button' in touch.profile:
|
||||
touch_button = touch.button
|
||||
if touch_button != ref_button:
|
||||
continue
|
||||
touch.double_tap_distance = distance
|
||||
return touch
|
||||
|
||||
def process(self, events):
|
||||
if self.double_tap_distance == 0 or self.double_tap_time == 0:
|
||||
return events
|
||||
# first, check if a touch down have a double tap
|
||||
for etype, touch in events:
|
||||
if not touch.is_touch:
|
||||
continue
|
||||
if etype == 'begin':
|
||||
double_tap = self.find_double_tap(touch)
|
||||
if double_tap:
|
||||
touch.is_double_tap = True
|
||||
tap_time = touch.time_start - double_tap.time_start
|
||||
touch.double_tap_time = tap_time
|
||||
distance = double_tap.double_tap_distance
|
||||
touch.double_tap_distance = distance
|
||||
|
||||
# add the touch internally
|
||||
self.touches[touch.uid] = (etype, touch)
|
||||
|
||||
# second, check if up-touch is timeout for double tap
|
||||
time_current = time()
|
||||
to_delete = []
|
||||
for touchid in self.touches.keys():
|
||||
etype, touch = self.touches[touchid]
|
||||
if etype != 'end':
|
||||
continue
|
||||
if time_current - touch.time_start < self.double_tap_time:
|
||||
continue
|
||||
to_delete.append(touchid)
|
||||
|
||||
for touchid in to_delete:
|
||||
del self.touches[touchid]
|
||||
|
||||
return events
|
|
@ -0,0 +1,47 @@
|
|||
'''
|
||||
Ignore list
|
||||
===========
|
||||
|
||||
Ignore touch on some areas of the screen
|
||||
'''
|
||||
|
||||
__all__ = ('InputPostprocIgnoreList', )
|
||||
|
||||
from kivy.config import Config
|
||||
from kivy.utils import strtotuple
|
||||
|
||||
|
||||
class InputPostprocIgnoreList(object):
|
||||
'''
|
||||
InputPostprocIgnoreList is a post-processor which removes touches in the
|
||||
Ignore list. The Ignore list can be configured in the Kivy config file::
|
||||
|
||||
[postproc]
|
||||
# Format: [(xmin, ymin, xmax, ymax), ...]
|
||||
ignore = [(0.1, 0.1, 0.15, 0.15)]
|
||||
|
||||
The Ignore list coordinates are in the range 0-1, not in screen pixels.
|
||||
'''
|
||||
|
||||
def __init__(self):
|
||||
self.ignore_list = strtotuple(Config.get('postproc', 'ignore'))
|
||||
|
||||
def collide_ignore(self, touch):
|
||||
x, y = touch.sx, touch.sy
|
||||
for l in self.ignore_list:
|
||||
xmin, ymin, xmax, ymax = l
|
||||
if x > xmin and x < xmax and y > ymin and y < ymax:
|
||||
return True
|
||||
|
||||
def process(self, events):
|
||||
if not len(self.ignore_list):
|
||||
return events
|
||||
for etype, touch in events:
|
||||
if not touch.is_touch:
|
||||
continue
|
||||
if etype != 'begin':
|
||||
continue
|
||||
if self.collide_ignore(touch):
|
||||
touch.ud.__pp_ignore__ = True
|
||||
return [(etype, touch) for etype, touch in events
|
||||
if '__pp_ignore__' not in touch.ud]
|
|
@ -0,0 +1,93 @@
|
|||
'''
|
||||
Retain Touch
|
||||
============
|
||||
|
||||
Reuse touch to counter lost finger behavior
|
||||
'''
|
||||
|
||||
__all__ = ('InputPostprocRetainTouch', )
|
||||
|
||||
from kivy.config import Config
|
||||
from kivy.vector import Vector
|
||||
import time
|
||||
|
||||
|
||||
class InputPostprocRetainTouch(object):
|
||||
'''
|
||||
InputPostprocRetainTouch is a post-processor to delay the 'up' event of a
|
||||
touch, to reuse it under certains conditions. This module is designed to
|
||||
prevent lost finger touches on some hardware/setups.
|
||||
|
||||
Retain touch can be configured in the Kivy config file::
|
||||
|
||||
[postproc]
|
||||
retain_time = 100
|
||||
retain_distance = 50
|
||||
|
||||
The distance parameter is in the range 0-1000 and time is in milliseconds.
|
||||
'''
|
||||
|
||||
def __init__(self):
|
||||
self.timeout = Config.getint('postproc', 'retain_time') / 1000.0
|
||||
self.distance = Config.getint('postproc', 'retain_distance') / 1000.0
|
||||
self._available = []
|
||||
self._links = {}
|
||||
|
||||
def process(self, events):
|
||||
# check if module is disabled
|
||||
if self.timeout == 0:
|
||||
return events
|
||||
|
||||
d = time.time()
|
||||
for etype, touch in events[:]:
|
||||
if not touch.is_touch:
|
||||
continue
|
||||
if etype == 'end':
|
||||
events.remove((etype, touch))
|
||||
if touch.uid in self._links:
|
||||
selection = self._links[touch.uid]
|
||||
selection.ud.__pp_retain_time__ = d
|
||||
self._available.append(selection)
|
||||
del self._links[touch.uid]
|
||||
else:
|
||||
touch.ud.__pp_retain_time__ = d
|
||||
self._available.append(touch)
|
||||
elif etype == 'update':
|
||||
if touch.uid in self._links:
|
||||
selection = self._links[touch.uid]
|
||||
selection.x = touch.x
|
||||
selection.y = touch.y
|
||||
selection.sx = touch.sx
|
||||
selection.sy = touch.sy
|
||||
events.remove((etype, touch))
|
||||
events.append((etype, selection))
|
||||
else:
|
||||
pass
|
||||
elif etype == 'begin':
|
||||
# new touch, found the nearest one
|
||||
selection = None
|
||||
selection_distance = 99999
|
||||
for touch2 in self._available:
|
||||
touch_distance = Vector(touch2.spos).distance(touch.spos)
|
||||
if touch_distance > self.distance:
|
||||
continue
|
||||
if touch2.__class__ != touch.__class__:
|
||||
continue
|
||||
if touch_distance < selection_distance:
|
||||
# eligible for continuation
|
||||
selection_distance = touch_distance
|
||||
selection = touch2
|
||||
if selection is None:
|
||||
continue
|
||||
|
||||
self._links[touch.uid] = selection
|
||||
self._available.remove(selection)
|
||||
events.remove((etype, touch))
|
||||
|
||||
for touch in self._available[:]:
|
||||
t = touch.ud.__pp_retain_time__
|
||||
if d - t > self.timeout:
|
||||
self._available.remove(touch)
|
||||
events.append(('end', touch))
|
||||
|
||||
return events
|
|
@ -0,0 +1,106 @@
|
|||
'''
|
||||
Triple Tap
|
||||
==========
|
||||
|
||||
.. versionadded:: 1.7.0
|
||||
|
||||
Search touch for a triple tap
|
||||
'''
|
||||
|
||||
__all__ = ('InputPostprocTripleTap', )
|
||||
|
||||
from time import time
|
||||
from kivy.config import Config
|
||||
from kivy.vector import Vector
|
||||
|
||||
|
||||
class InputPostprocTripleTap(object):
|
||||
'''
|
||||
InputPostProcTripleTap is a post-processor to check if
|
||||
a touch is a triple tap or not.
|
||||
Triple tap can be configured in the Kivy config file::
|
||||
|
||||
[postproc]
|
||||
triple_tap_time = 250
|
||||
triple_tap_distance = 20
|
||||
|
||||
The distance parameter is in the range 0-1000 and time is in milliseconds.
|
||||
'''
|
||||
|
||||
def __init__(self):
|
||||
dist = Config.getint('postproc', 'triple_tap_distance')
|
||||
self.triple_tap_distance = dist / 1000.0
|
||||
time = Config.getint('postproc', 'triple_tap_time')
|
||||
self.triple_tap_time = time / 1000.0
|
||||
self.touches = {}
|
||||
|
||||
def find_triple_tap(self, ref):
|
||||
'''Find a triple tap touch within *self.touches*.
|
||||
The touch must be not be a previous triple tap and the distance
|
||||
must be within the bounds specified. Additionally, the touch profile
|
||||
must be the same kind of touch.
|
||||
'''
|
||||
ref_button = None
|
||||
if 'button' in ref.profile:
|
||||
ref_button = ref.button
|
||||
|
||||
for touchid in self.touches:
|
||||
if ref.uid == touchid:
|
||||
continue
|
||||
etype, touch = self.touches[touchid]
|
||||
if not touch.is_double_tap:
|
||||
continue
|
||||
if etype != 'end':
|
||||
continue
|
||||
if touch.is_triple_tap:
|
||||
continue
|
||||
distance = Vector.distance(
|
||||
Vector(ref.sx, ref.sy),
|
||||
Vector(touch.osx, touch.osy))
|
||||
if distance > self.triple_tap_distance:
|
||||
continue
|
||||
if touch.is_mouse_scrolling or ref.is_mouse_scrolling:
|
||||
continue
|
||||
touch_button = None
|
||||
if 'button' in touch.profile:
|
||||
touch_button = touch.button
|
||||
if touch_button != ref_button:
|
||||
continue
|
||||
touch.triple_tap_distance = distance
|
||||
return touch
|
||||
|
||||
def process(self, events):
|
||||
if self.triple_tap_distance == 0 or self.triple_tap_time == 0:
|
||||
return events
|
||||
# first, check if a touch down have a triple tap
|
||||
for etype, touch in events:
|
||||
if not touch.is_touch:
|
||||
continue
|
||||
if etype == 'begin':
|
||||
triple_tap = self.find_triple_tap(touch)
|
||||
if triple_tap:
|
||||
touch.is_double_tap = False
|
||||
touch.is_triple_tap = True
|
||||
tap_time = touch.time_start - triple_tap.time_start
|
||||
touch.triple_tap_time = tap_time
|
||||
distance = triple_tap.triple_tap_distance
|
||||
touch.triple_tap_distance = distance
|
||||
|
||||
# add the touch internally
|
||||
self.touches[touch.uid] = (etype, touch)
|
||||
|
||||
# second, check if up-touch is timeout for triple tap
|
||||
time_current = time()
|
||||
to_delete = []
|
||||
for touchid in self.touches.keys():
|
||||
etype, touch = self.touches[touchid]
|
||||
if etype != 'end':
|
||||
continue
|
||||
if time_current - touch.time_start < self.triple_tap_time:
|
||||
continue
|
||||
to_delete.append(touchid)
|
||||
|
||||
for touchid in to_delete:
|
||||
del self.touches[touchid]
|
||||
|
||||
return events
|
Loading…
Add table
Add a link
Reference in a new issue