mirror of https://github.com/kivy/kivy.git
227 lines
9.0 KiB
ReStructuredText
227 lines
9.0 KiB
ReStructuredText
Input management
|
|
================
|
|
|
|
Input architecture
|
|
------------------
|
|
|
|
Kivy is able to handle most types of input: mouse, touchscreen, accelerometer,
|
|
gyroscope, etc. It handles the native multitouch protocols on the following
|
|
platforms: Tuio, WM_Touch, MacMultitouchSupport, MT Protocol A/B, Android.
|
|
|
|
The global architecture can be viewed as::
|
|
|
|
Input providers -> Motion event -> Post processing -> Dispatch to Window
|
|
|
|
The class for all input events is the
|
|
:class:`~kivy.input.motionevent.MotionEvent`. From this, there are 2 kinds of
|
|
events:
|
|
|
|
- Touch events: a motion event that contains at least an X and Y position.
|
|
All the touch events are dispatched accross the Widget tree.
|
|
- No-touch events: all the rest. For example, the accelerometer is a
|
|
continuous event, without position. It never starts or stops. These events
|
|
are not dispatched accross the Widget tree.
|
|
|
|
|
|
A Motion event is generated by an Input Provider. An input provider is
|
|
responsible for reading the input event from the operating system, the network
|
|
or even from another application. Several input providers exist, like:
|
|
|
|
- :class:`~kivy.input.providers.tuio.TuioMotionEventProvider`: create a
|
|
UDP server and listen for TUIO/OSC messages.
|
|
- :class:`~kivy.input.providers.wm_touch.WM_MotionEventProvider`: use the
|
|
windows API for reading multitouch information and sending it to Kivy.
|
|
- :class:`~kivy.input.providers.probesysfs.ProbeSysfsHardwareProbe`:
|
|
In Linux, iterate over all the hardware connected to the computer, and
|
|
attach a multitouch input provider for each multitouch hardware found.
|
|
- and much more!
|
|
|
|
When you write an application, you don't need to create an input provider. Kivy
|
|
tries to automatically detect available hardware. However, if you want to
|
|
support custom hardware, you will need to configure kivy to make it work.
|
|
|
|
Before the newly-created Motion Event is passed to the user, Kivy applies
|
|
post-processing on the input. Every motion event is analyzed to detect and
|
|
correct faulty input, as well as make meaningful interpretations like:
|
|
|
|
- Double-tap detection, according to a distance and time threshold
|
|
- Making events more accurate when the hardware is not accurate
|
|
- Reducing the amount of generated events if the native touch hardware is
|
|
sending events with nearly the same position
|
|
|
|
Then, the motion event is dispatched to the Window. As explained at the start,
|
|
all events are not dispatched to the whole widget tree, the window filters them.
|
|
For a given event:
|
|
|
|
- if it's only a motion event, it will be dispatched to :meth:`~kivy.core.window.WindowBase.on_motion`
|
|
- if it's a touch event, the (x,y) position of the touch (0-1 range) will be scaled to the Window size (width/height), and dispatched to:
|
|
|
|
- :meth:`~kivy.uix.widget.Widget.on_touch_down`
|
|
- :meth:`~kivy.uix.widget.Widget.on_touch_move`
|
|
- :meth:`~kivy.uix.widget.Widget.on_touch_up`
|
|
|
|
|
|
Motion event profiles
|
|
---------------------
|
|
|
|
Depending on your hardware and the input providers used, more information may be
|
|
made available to you. For example, a touch input has an (x,y) position, but
|
|
might also have pressure information, blob size, an acceleration vector, etc.
|
|
|
|
A profile is a string that indicates what features are available inside the
|
|
motion event. Let's imagine that you are in an ``on_touch_move`` method::
|
|
|
|
def on_touch_move(self, touch):
|
|
print touch.profile
|
|
return super(..., self).on_touch_move(touch)
|
|
|
|
The print could output::
|
|
|
|
['pos', 'angle']
|
|
|
|
.. warning::
|
|
|
|
Most people mix up the profile's name and the name of the corresponding
|
|
property. Just because ``'angle'`` is in the available profile doesnt
|
|
mean that the touch event object will have an ``angle`` property.
|
|
|
|
For the ``'pos'`` profile, the properties ``pos``, ``x``, and ``y`` will be
|
|
available. With the ``'angle'`` profile, the property ``a`` will be available.
|
|
As we said, for touch events ``'pos'`` is a mandatory profile, but not
|
|
``'angle'``. You can extend your interaction by checking if the ``'angle'``
|
|
profile exists::
|
|
|
|
def on_touch_move(self, touch):
|
|
print 'The touch is at position', touch.pos
|
|
if 'angle' in touch.profile:
|
|
print 'The touch angle is', touch.a
|
|
|
|
You can find a list of available profiles in the :mod:`api-kivy.input.motionevent`
|
|
documentation.
|
|
|
|
Touch events
|
|
------------
|
|
|
|
A touch event is a specialized :class:`~kivy.input.motionevent.MotionEvent`
|
|
where the property :data:`~kivy.input.motionevent.MotionEvent.is_touch`
|
|
evaluates to True. For all touch events, you automatically have the X and Y
|
|
positions available, scaled to the Window width and height. In other words, all
|
|
touch events have the ``'pos'`` profile.
|
|
|
|
You must take care about matrix transformation in your touch as soon as you use
|
|
a widget with matrix transformation. Some widgets such as Scatter have their own
|
|
matrix transformation, meaning the touch must be multiplied by the scatter matrix to
|
|
be able to correctly dispatch touch positions to the Scatter's children.
|
|
|
|
- Get coordinate from parent space to local space:
|
|
:meth:`~kivy.uix.widget.Widget.to_local`
|
|
- Get coordinate from local space to parent space:
|
|
:meth:`~kivy.uix.widget.Widget.to_parent`
|
|
- Get coordinate from local space to window space:
|
|
:meth:`~kivy.uix.widget.Widget.to_window`
|
|
- Get coordinate from window space to local space:
|
|
:meth:`~kivy.uix.widget.Widget.to_widget`
|
|
|
|
You must use one of them to get the good coordinate. Let's take the scatter
|
|
implementation::
|
|
|
|
def on_touch_down(self, touch):
|
|
# push the current coordinate, to be able to restore them later.
|
|
touch.push()
|
|
|
|
# transform the touch coordinate to local space
|
|
touch.apply_transform_2d(self.to_local)
|
|
|
|
# dispatch the touch as usual to children
|
|
# the coordinate in the touch are now in local space
|
|
ret = super(..., self).on_touch_down(touch)
|
|
|
|
# whatever is the result, don't forget to pop the transformation
|
|
# after the call, the coordinate will be in parent space
|
|
touch.pop()
|
|
|
|
# return the result (depending what you want.)
|
|
return ret
|
|
|
|
|
|
Touch shapes
|
|
~~~~~~~~~~~~
|
|
|
|
If the touch has a shape, it will be reflected in the 'shape' property. Right
|
|
now, only a :class:`~kivy.input.shape.ShapeRect` can be exposed::
|
|
|
|
from kivy.input.shape import ShapeRect
|
|
|
|
def on_touch_move(self, touch):
|
|
if isinstance(touch.shape, ShapeRect):
|
|
print 'My touch have a rectangle shape of size', \
|
|
(touch.shape.width, touch.shape.height)
|
|
# ...
|
|
|
|
Double tap
|
|
~~~~~~~~~~
|
|
|
|
The double tap is the action of tapping twice within a time and a distance.
|
|
It's calculated by the doubletap post-processing module. You can test if the
|
|
current touch is one of a double tap or not::
|
|
|
|
def on_touch_down(self, touch):
|
|
if touch.is_double_tap:
|
|
print 'Touch is a double tap !'
|
|
print ' - interval is', touch.double_tap_time
|
|
print ' - distance between previous is', touch.double_tap_distance
|
|
# ...
|
|
|
|
|
|
Grabbing touch events
|
|
~~~~~~~~~~~~~~~~~~~~~
|
|
|
|
It's possible for the parent widget to dispatch a touch event to its child
|
|
widget from within ``on_touch_down``, but not from ``on_touch_move`` or
|
|
``on_touch_up``. This can happen in certain scenarios, like when a touch
|
|
movement is outside the bounding box of the parent, so the parent decides not to
|
|
notify its children of the movement.
|
|
|
|
But you might want to do something ``on_touch_up``. Say you started something on
|
|
the down event, like playing a sound, and you'd like to finish things on the up
|
|
event. Grabbing is what you need.
|
|
|
|
When you grab a touch, you will always receive the move and up event. But there
|
|
are some limitations to grabbing:
|
|
|
|
- You will receive the event at least twice: one time from your parent (the
|
|
normal event), and one time from the window (grab).
|
|
- You might receive an event with a grab touch, but not from you: it can be
|
|
because the parent has sent the touch to its children, while it was in
|
|
the grabbed state.
|
|
- The touch coordinate is not translated to your widget space, because the
|
|
touch is coming directly from the Window. It's your job to convert the
|
|
coordinate to your local space.
|
|
|
|
Here is an example on how to use grabbing::
|
|
|
|
def on_touch_down(self, touch):
|
|
if self.collide_point(*touch.pos):
|
|
|
|
# if the touch is colliding to our widget, let's grab it.
|
|
touch.grab(self)
|
|
|
|
# and accept the touch.
|
|
return True
|
|
|
|
def on_touch_up(self, touch):
|
|
# here, you don't check if the touch is colliding or things like that.
|
|
# you just need to check if it's a grabbed touch event
|
|
if touch.grab_current is self:
|
|
|
|
# ok, the current touch is dispatched for us.
|
|
# do something interesting here
|
|
print 'Hello world!'
|
|
|
|
# don't forget to ungrab ourself, or you might have counter effects
|
|
touch.ungrab(self)
|
|
|
|
# and accept the last up
|
|
return True
|
|
|