Table Of Contents
Input management (翻訳済み)¶
Input architecture (入力のアーキテクチャ)¶
Kivyはほぼすべてのタイプの入力、たとえばマウス、タッチスクリーン、加速度センサ、ジャイロスコープといったデバイスからの入力を取り扱うことができます。さらに Tuio、WM_Touch、MacMultitouchSupport、MT Protocol A/BやAndroidといったプラットフォームのネイティヴなマルチタッチのプロトコルをも取り扱います。
入力に関する全体的なアーキテクチャは、次のようなものと見なすことができます:
Input providers -> Motion event -> Post processing -> Dispatch to Window
すべての入力イベントを取り扱うクラスが、 MotionEvent
です。これは2種類のイベントを生成します:
Touch events (タッチ・イベント): 少なくともx座標とy座標を含むような motion event です。すべての touch event は、ウィジェットツリーに向けてディスパッチされます。
No-touch events (ノー・タッチ・イベント):その名の通り、タッチ・イベントでないイベントです。たとえば、加速度センサは位置に依らない連続的なイベントです。それは開始することも停止することもありません。この種のイベントは、ウィジェットツリーに向けてディスパッチされることはありません。
Motion event は、 Input Provider
によって生成されます。この Input Provider が、OS、ネットワーク、あるいは他のアプリケーションから入力イベントを読みとる任務を担います。たとえば、次のような input provider が存在します:
TuioMotionEventProvider
: UDPサーバを生成し、TUIO/OSC メッセージをリッスンします。
WM_MotionEventProvider
: マルチタッチ情報を読み込み、それを Kivy に送るために、Windows APIを用います。
ProbeSysfsHardwareProbe
: Linuxにおいて、コンピュータに接続するすべてのハードウェアについてイテレートし、見つかったマルチタッチデバイスのそれぞれについて、マルチタッチ input provider を接続します。他にもたくさんあります!
あなたがアプリを作る時には、自分で input provider を作る必要はありません。Kivy は自動的に、利用可能なハードウェアの検知を試みます。しかし、もし自前のハードウェアをサポートしたいときには、うまくいくようにKivyを設定する必要があるでしょう。
新しく生成された Motion Event がユーザに渡される前に、Kivy は入力に対する後処理を行います。あらゆる Motion Event は分析され、その結果、下記のような意味ある解釈がなされることがあります。また誤った入力が検知されて修正されることもあります。
距離および時間の閾値に基づいた、ダブル/トリプルタップの検知。
ハードウェアが正確でないときにイベントをより正確なものに修正する。
ハードウェアがほぼ同じ位置からのイベントを送り続けているときは、生成するイベントの量を減らします。
上記のような処理を経て、Motion Event は Window にディスパッチされます。上で述べた通り、すべてのイベントがウィジェットツリーにディスパッチされるわけではありません。Window がそのフィルター役となります。与えられたイベントに対して:
それがただの Motion Event であれば、
on_motion()
にディスパッチされます。もしそれが Touch Event であれば、そのタッチの (x,y) 座標 (0から1の値を取る) がWindow のサイズにスケーリングされ (width/height 属性)、以下のどれかにディスパッチされます。
Motion event profiles (モーション・イベント・プロファイル)¶
使用しているハードウェアや input provider に応じて、より多くの情報が利用可能となるかもしれません。たとえば、タッチによる入力は (x,y) 座標を持ちますが、同時に圧力、ブロブの大きさ、加速度ベクトルなどの情報を持つかもしれません。
profile は、motion event の中で何の特徴が利用可能であるかを示す文字列です。次の on_touch_move
イベントを想像してください:
def on_touch_move(self, touch):
print(touch.profile)
return super(..., self).on_touch_move(touch)
この print 文が出力するのは:
['pos', 'angle']
警告
Profileの名前と対応するプロパティの名前を混同してはなりません。'angle'
が利用可能な profile だからと言って、”touch event が angle
プロパティを持つわけではないのです。
'pos'
profile については、 pos
、 x
、 y
という3つのプロパティが利用可能です。 'angle'
profile については、a
というプロパティが利用可能です。すでに述べた通り、touch event にとって 'pos'
は必須の profile ですが 'angle'
はそうではありません。'angle'
profile が存在するかどうかをチェックすることによりインタラクションを拡張できます:
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)
利用可能な profile のリストは、 motionevent
で確認できます。
Touch events (タッチ・イベント)¶
Touch event は、 is_touch
プロパティが True と評価される、特別な MotionEvent
です。すべての touch event について、Windowの幅と高さにスケーリングされた x,y座標が自動的に利用可能となります。言い換えれば、すべての touch event は 'pos'
profile を持ちます。
Touch event basics (タッチ・イベントの基本)¶
デフォルトでは、touch event はその時表示されているすべてのウィジェットにディスパッチされます。つまりウィジェットは、touch event がその内部で起きようが起きまいがお構いなしに、その touch event を受け取るのです。
もし他の GUI ツールキットを使った経験があるなら、これは直感に反することかもしれません。典型的な GUI ツールキットは、スクリーンを分割し、タッチやマウスの座標がウィジェットの内部にあるときのみ、そのウィジェットにイベントをディスパッチするからです。
しかしこの要求は、タッチによる入力を取り扱うときには極めて厳しいものです。スワイプ、ピンチ、長押しといった操作はウィジェットの外で発生するかもしれませんし、我々はウィジェットにそういった操作を検知させ、反応させたいと考えるかもしれません。
そこでKivyでは、柔軟性を最大限持たせるために、すべてのウィジェットにイベントをディスパッチするようになっています。そしてどのように反応するかを各ウィジェットで決めることができるのです。もしウィジェットの内部で起きた touch event にのみ反応したいのであれば、以下のように単純なチェックを行いましょう:
def on_touch_down(self, touch):
if self.collide_point(*touch.pos):
# The touch has occurred inside the widgets area. Do stuff!
pass
Coordinates (座標)¶
行列による座標変換をともなうウィジェットを用いる場合、タッチの座標変換に注意する必要があります。たとえば Scatter
クラスは独自の変換行列を持ちますが、タッチの位置を正しく Scatter の子孫に伝えるには、タッチにその行列を乗じなければならないのです。
親スペースの座標から、局所スペース (当該ウィジェットのスペース) の座標を得る:
to_local()
- Get coordinate from local space to parent space:
to_parent()
- Get coordinate from local space to window space:
to_window()
ウィンドウ上の座標から、局所スペースの座標を得る:
to_widget()
座標を意図したとおりにスケーリングするには、上記のうち1つを用いる必要があります。次の Scatter のインプリを見てください:
def on_touch_down(self, touch):
# push the current coordinate, to be able to restore it 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 is now in local space
ret = super(..., self).on_touch_down(touch)
# whatever the result, don't forget to pop your transformation
# after the call, so the coordinate will be back in parent space
touch.pop()
# return the result (depending what you want.)
return ret
Touch shapes (タッチの成す形)¶
もしタッチが形を成すならば、それは ‘shape’ プロパティに反映されるでしょう。現時点では、 ShapeRect
のみが公開されています:
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 (ダブル・タップ)¶
ダブル・タップとは、短い時間と距離の中で、2度タップを行うことです。ダブル・タップに関する事後処理モジュールによって計算されて今起こったタッチがダブル・タップなのか否かをテストできます:
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)
# ...
Triple tap (トリプル・タップ)¶
トリプル・タップとは、短い時間と距離の中で、3度タップを行うことです。トリプル・タップに関する事後処理モジュールによって計算され、今起こったタッチがトリプル・タップなのか否かをテストできます:
def on_touch_down(self, touch):
if touch.is_triple_tap:
print('Touch is a triple tap !')
print(' - interval is', touch.triple_tap_time)
print(' - distance between previous is', touch.triple_tap_distance)
# ...
Grabbing touch events (タッチ・イベントを、grabによってつかまえる)¶
親ウィジェットは、 on_touch_move
や on_touch_up
からではなく、 on_touch_down
の内部で、touch event をその子ウィジェットにディスパッチする、あるいはしないことを決定することが可能です。これは、たとえば親ウィジェットのバウンディングボックスの外でタッチの動きが始まったときに、その親は子にイベントをディスパッチしないことを決定するといったような文脈において有りえます。
しかし on_touch_up
において何かをしたいと考えるかもしれません。たとえばサウンドの再生だとかを on_touch_down
において開始し、 on_touch_up
においてそれを停止したい、と考えるかもしれません。その時必要なのが、grab です。
あなたがタッチを grab するとき、必ず move イベントと up イベントを受け取ることになります。しかし grab にはいくつかの制限があります:
少なくとも2回のイベントを受け取ることになります: 1回は親から (通常のイベント)、そしてもう1回はウィンドウから (grab)。
Grab されたタッチからのイベントを受け取るかもしれませんが、それは、親ウィジェットが grab された状態にあったときに、子供たちにタッチをディスパッチしたことによるものかもしれません。
タッチの座標はウィジェットスペースのものに変換されません。なぜならタッチはWindowから直接ディスパッチされたものだからです。ウィジェットスペースに変換することはあなたのやるべきことです
Grabの使用例を以下に示します:
def on_touch_down(self, touch):
if self.collide_point(*touch.pos):
# if the touch collides with 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 collides 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 side effects
touch.ungrab(self)
# and accept the last up
return True
Touch Event Management (タッチ・イベントの管理)¶
Touch event がどのように制御され、またどのようにウィジェット間を伝播するのかについては、Widget touch event bubbling を参照してください。
Joystick events(ジョイスティックのイベント)¶
ジョイスティックの入力は物理的または仮想的なコントローラからSDL2のプロバイダをかいして直接受け取った生の値を、以下のイベントをかいして表します。
- SDL_JOYAXISMOTION
- SDL_JOYHATMOTION
- SDL_JOYBALLMOTION
- SDL_JOYBUTTONDOWN
- SDL_JOYBUTTONUP
すべてのモーションイベントには最小値、最大値、デフォルト値があります:
イベント |
最小値 |
最大値 |
デフォルト値 |
---|---|---|---|
on_joy_axis | -32767 | 32767 | 0 |
on_joy_hat | (-1, -1) | (1, 1) | (0, 0) |
on_joy_ball | Unknown | Unknown | Unknown |
一方、ボタンイベントは基本的に各ボタンの状態のみ、すなわち up(押す) および down(押さない) のみを表し、そのような値は存在しない。
- on_joy_button_up
- on_joy_button_down
Joystick event basics(ジョイスティックとイベントの基本)¶
タッチイベントとは異なり、ジョイスティックイベントはWindowに直接送出されます。つまり指定された軸に値が1つだけ渡されます。複数の軸ではありません。入力を別々のウィジェットに分割したいですがこれは不可能ではありません。インスピレーションとして Multiple dropfile example を使用できます。
ジョイスティックのイベントを取得するにはまず次のようなWindowジョイスティックイベントにいくつかの関数をバインドする必要があります:
Window.bind(on_joy_axis=self.on_joy_axis)
次に、使用するイベントごとに Window
で指定されたパラメータを取得する必要があります。
def on_joy_axis(self, win, stickid, axisid, value):
print(win, stickid, axisid, value)
変数 stickid は値を送信したコントローラのIDです。`axisid`は、値が属する軸のIDです。
Joystick input(ジョイスティックの入力)¶
Kivyは、「ゲームパッド」、「ジョイスティック」、またはSDL2プロバイダが認識している基本的に他のタイプのゲームコントローラとして指定された任意のデバイスからの入力を取得できます。気楽に行うために共通のコントローラのレイアウトと各パーツのIDがあります
Xbox 360¶
# | ID | # | ID | |
1 | axis 1 | 2 | axis 0 | |
3 | hat Y | 4 | hat X | |
5 | axis 4 | 6 | axis 3 | |
7 | axis 2 | 8 | axis 5 | |
9 | button 4 | 10 | button 5 | |
X | button 2 | Y | button 3 | |
A | button 0 | B | button 1 | |
back | button 6 | start | button 7 | |
center | button 10 |