Issue
I have modified this example at the end of this tutorial a bit:
#!/usr/bin/env python
from kivy.app import App
from kivy.uix.widget import Widget
from kivy.uix.button import Button
from kivy.uix.boxlayout import BoxLayout
from kivy.properties import ListProperty
class RootWidget(BoxLayout):
def __init__(self, **kwargs):
super(RootWidget, self).__init__(**kwargs)
self.add_widget(Button(text='button 1'))
cb = CustomBtn()
cb.bind(pressed=self.btn_pressed)
self.add_widget(cb)
self.add_widget(Button(text='button 2'))
def btn_pressed(self, instance, pos):
print(f'4444 RootWidget.btn_pressed, instance: {instance} pos: {pos}')
class CustomBtn(Widget):
pressed = ListProperty([0, 0])
def on_touch_down(self, touch):
print(f'1111 CustomButton.on_touch_down, touch id: {id(touch)}, touch {touch}')
if self.collide_point(*touch.pos):
print(f'2222 CustomButton.on_touch_down, collision, self.pressed: {self.pressed}')
self.pressed = touch.pos
# we consumed the touch. return False here to propagate
# the touch further to the children.
return True
return super(CustomBtn, self).on_touch_down(touch)
def on_pressed(self, instance, pos):
print(f'3333 CustomButton.on_pressed, instance: {instance} pos: {pos}')
class TestApp(App):
def build(self):
return RootWidget()
if __name__ == '__main__':
TestApp().run()
Now, when I press button 1
, I get this:
1111 CustomButton.on_touch_down, touch id: 140523635041336, touch <MouseMotionEvent spos=(0.1875, 0.31999999999999995) pos=(150.0, 191.99999999999997)>
Why? This should be handled in Button(text='button 1')
, no?
Sure, since collide_point
call detects that this touch down has happened outside custom button's area, this does not backfire, but why is this happening in the first place?
Next, to make this even weirder, when I click button 2
, this is not happening (i.e. nothing gets printed).
Why (and how?) are events dispatched here?
Kivy: 1.11.1. OS: Debian 10 (buster) amd64 GUI: KDE Plasma. Python: 3.7.
Startup log:
[INFO ] [Logger ] Record log in /home/user/.kivy/logs/kivy_20-05-08_16.txt
[INFO ] [Kivy ] v1.11.1
[INFO ] [Kivy ] Installed at "/home/user/projects/kvapp/venv37/lib/python3.7/site-packages/kivy/__init__.py"
[INFO ] [Python ] v3.7.2 (default, Feb 22 2019, 18:56:07)
[GCC 6.3.0 20170516]
[INFO ] [Python ] Interpreter at "/home/user/projects/kvapp/venv37/bin/python"
[INFO ] [Factory ] 184 symbols loaded
[INFO ] [Image ] Providers: img_tex, img_dds, img_sdl2, img_gif (img_pil, img_ffpyplayer ignored)
[INFO ] [Text ] Provider: sdl2
[INFO ] [Window ] Provider: sdl2(['window_egl_rpi'] ignored)
[INFO ] [GL ] Using the "OpenGL" graphics system
[INFO ] [GL ] Backend used <sdl2>
[INFO ] [GL ] OpenGL version <b'4.6.0 NVIDIA 418.43'>
[INFO ] [GL ] OpenGL vendor <b'NVIDIA Corporation'>
[INFO ] [GL ] OpenGL renderer <b'GeForce GTX 1080 Ti/PCIe/SSE2'>
[INFO ] [GL ] OpenGL parsed version: 4, 6
[INFO ] [GL ] Shading version <b'4.60 NVIDIA'>
[INFO ] [GL ] Texture max size <32768>
[INFO ] [GL ] Texture max units <32>
[INFO ] [Window ] auto add sdl2 input provider
[INFO ] [Window ] virtual keyboard not allowed, single mode, not docked
[INFO ] [Base ] Start application main loop
[INFO ] [GL ] NPOT texture support is available
Solution
The events are propagated to all the Widgets
in the Window
until any one of them stops the propagation. The on_touch_down()
methods are called for each Button
in the reverse order that they were added, that is:
- Button 2
- CustomBtn
- Button 1
If the touch position is on Button 2, its on_touch_down()
returns True
and stops the event propagation, so no other Widget
gets that event. That is why pressing Button 2 produces no printing.
When you click on Button 1, Button 2 sees it first, but since it is not within Button 2, the event is ignored and allowed to propagate on to CustomBtn
. The CustomBtn
on_touch_down()
prints the 1111
message, but since the touch is outside the CustomBtn
, it also allows the propagation to continue. Now Button 1 gets the event and processes it, stopping the propagation.
When you click on the CustomBtn
, the Button 2 again sees it first, but allow the propagation to continue. Then CustomBtn
sees the event, prints the 1111
message, determines that the touch is on the CustomBtn
, so the 2222
message is printed, the pressed
property is modified (causing the on_pressed()
method to be triggered), and the bound btn_pressed()
method is also triggered. The propagation is then stopped (by the True
return), so Button 1 never sees this event.
Answered By - John Anderson Answer Checked By - Senaida (WPSolving Volunteer)