Skip to content

Latest commit

 

History

History
226 lines (166 loc) · 8.96 KB

inputs.rst

File metadata and controls

226 lines (166 loc) · 8.96 KB

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 ~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:

  • ~kivy.input.providers.tuio.TuioMotionEventProvider: create a UDP server and listen for TUIO/OSC messages.
  • ~kivy.input.providers.wm_touch.WM_MotionEventProvider: use the windows API for reading multitouch information and sending it to Kivy.
  • ~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 ~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:
    • ~kivy.uix.widget.Widget.on_touch_down
    • ~kivy.uix.widget.Widget.on_touch_move
    • ~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 api-kivy.input.motionevent documentation.

Touch events

A touch event is a specialized ~kivy.input.motionevent.MotionEvent where the property ~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: ~kivy.uix.widget.Widget.to_local
  • Get coordinate from local space to parent space: ~kivy.uix.widget.Widget.to_parent
  • Get coordinate from local space to window space: ~kivy.uix.widget.Widget.to_window
  • Get coordinate from window space to local space: ~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 ~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