Touch propogation

408 views
Skip to first unread message

GreyGnome

unread,
May 22, 2016, 2:00:03 PM5/22/16
to Kivy users support
I have some Label's that are not getting touch events. I believe that touch events propagate through widgets by default, correct?

The docs say, "In Kivy, events bubble up from the most recently added widget and then backwards through its children ...

If you want to reverse this order, you can raise events in the children before the parent by using the super command"


This says to me that a widget I just added should get an on_touch_down event most definitely. But it does not. Some existing widget does, however, and the event is not propagating from there at all. I have scoured my code looking for "return True" on any on_touch_down function, but I don't have any. The only way that on_touch_down events trickle down to the last widgets I've added is if I add super to all the existing widgets in the widget chain.


Does this seem like proper behavior?


Thanks.



ZenCODE

unread,
May 24, 2016, 11:29:37 PM5/24/16
to Kivy users support
Yes, if you override 'on_touch', you need to call super to preserve it's behaviour. The ordering description seems wrong though. I think the confusion comes by the default index being 0, thus the most recently added widget is the first in the tree. If the event bubbles backwards, then this is the last one to receive it....

Will double check the docs for accuracy...

Thaks

GreyGnome

unread,
May 24, 2016, 11:55:15 PM5/24/16
to Kivy users support
I have scoured my code and come up with the below order of added widgets. What I don't understand is, my events trickle all the way to the ScrollView (I have on_touch_down functions in my widgets that simply print "widgetname touched"). However, when I add an on_touch_down function to, say, the FloatLayout marked below with **, then my ScrollView doesn't see any more events. I don't know why the events are disappearing. I never see events in my Label's and Button's.

Object of this type : Adds widget of this type (I have 2 screens)
(ScreenManager)     : (ManageLayout)
(ScreenManager)     : (Screen)
(Screen)            : (BoxLayout, Overlay[subclass of widget])  (multiply-inherited)
(Screen)            : (FloatLayout)
(ScreenManager)     : (Screen)

Then
(FloatLayout)       : (FloatLayout) **
     --- the Scrollview is created here, before it's added to the FloatLayout ---
(FloatLayout)       : (ScrollView)

(ScrollView)        : (StackLayout)
(StackLayout)       : (Label) or (Button)

GreyGnome

unread,
May 24, 2016, 11:57:38 PM5/24/16
to Kivy users support
OK, I will look into that by adding super's. I didn't understand that if you didn't call super, behavio(u)r wouldn't be preserved. From the docs, it sounds like you need to use super only if you want to reverse the order of events.

ZenCODE

unread,
May 25, 2016, 12:14:20 AM5/25/16
to Kivy users support
No, sorry, I'm wrong here. The event bubbles without needing super. Let me investigate that and update the docs accordingly.. :-)

GreyGnome

unread,
May 25, 2016, 8:48:21 AM5/25/16
to Kivy users support
Ok, I have seen that the events bubble with the most recent widget added getting the events first. But I have a GridLayout that's added to a FloatLayout and the FloatLayout sees on_touch_down, the GridLayout doesn't and I don't know why. :-(

Alexander Taylor

unread,
May 25, 2016, 9:19:55 AM5/25/16
to Kivy users support
Did you override on_touch_down but not call the superclass method?

ZenCODE

unread,
May 25, 2016, 4:47:34 PM5/25/16
to Kivy users support
Okay, this was really educational for me :-) I have updated the docs with what I found after a bit of experimentation.

https://github.com/kivy/kivy/commit/986729787b4e123b9d4e9ad93049d73014babfed

I think this more accurately describes event propagation? One think I learned is that if you do not call super, the event still propagates to pier widgets but not to it's children...

Anyway, hope that helps. Thanks for pointing that out...

GreyGnome

unread,
May 25, 2016, 6:13:41 PM5/25/16
to Kivy users support
Thanks for addressing this! Couple of fixes/questions:
* The possession of "it" is "its", not "it's" (== "it is")  (the second and third "it's" on the page https://kivy.org/docs/api-kivy.uix.widget.html are incorrect)
* Incorrect text: “c” was actually the last added widget. It thus has index 0, “b” index 1 and “c” index 2. Should be "a" index 2... I think...
* I don't understand this: "If a widget has children, the event is passed through it’s[sic] children before being passed on to the widget after it.
".  Is the widget after it the next, unrelated widget? Or is it the parent? Maybe it would be stated, "If a widget has children, the event is passed through its children, then to its parent, and only then to the widget added just prior to the current widget." Perhaps a two- or three-parent-widget example would be in helpful? I'll make one up, because this is interesting to me.
* You mention above a "pier widget". I suppose that means, "another widget that doesn't have a parent/child relationship with the given widget"? Is that correct? I've not heard that terminology before.
* propagation is misspelled as propogation

ZenCODE

unread,
May 26, 2016, 6:28:13 AM5/26/16
to Kivy users support
Thanks, will fix those grammatical issues.

Re: "If a widget has children"
Widget can have children, especially layouts. When you add a widget to another widget using 'add_widget', it becomes that widgets child. Layouts work by manipulating the position of their children. Can you suggest a better wording?

Re: "Is the widget after it the next, unrelated widget?"
Well, they are not unrelated because they are both children of the same parent widget. Even screen is kivy has a root widget which contains all the widgets you see/get events. So, correctly speaking, no widgets on the screen are unrelated.

It's the widget following it in the list of children i.e. the next child

Re: "You mention above a "pier widget"
Yes, that's a typo. Should be "peer". Someone on the same level. Equal to you in rank or position. In this case, at the same level of hierarchy. I'll look at that to make it clearer

Cheers. Feedback appreciated -)

ZenCODE

unread,
May 26, 2016, 1:30:51 PM5/26/16
to Kivy users support

GreyGnome

unread,
May 28, 2016, 1:58:59 AM5/28/16
to Kivy users support
I have done some playing around, and created a simple window for evaluating and playing with how events work in Kivy.

Here is my example. As you mentioned ZenCode, as soon as I add on_touch_down() methods to my widgets, event bubbling doesn't take place. In this default example only the top level widget, a FloatLayout, gets touch_down events no matter where I touch:



...so as has been pointed out, touch event bubbling is prevented when you override on_touch_down(). This makes sense because  the Widget class' on_touch_down() method is defined in the kivy.uix.widget.py file, and it dispatches events to the Widget's children. Override that method: no dispatch is called.

Notice that I have placed "# TESTPOINT XYZ" comments ahead of the on_touch_down() methods so I can reference them here:

If I have a top-level Widget (MyFloatLayout) with no on_touch_down() method, touches pass to its children, as seen:
(see TESTPOINT FLOAT)

MyLabel: touched! Oddball
MyScrollView: touched! [2, 2]
MyScrollView: touched! [1, 2]
MyScrollView: touched! [2, 1]
MyScrollView: touched! [1, 1]

If I uncomment the same method, but leave commented the <code>super(MyFloatLayout, self).__init__(*args, **kwargs)</code>, I see only:

MyFloatLayout: touched! root

If I uncomment the <code>super(MyFloatLayout, self).__init__(*args, **kwargs)</code>, I see:

MyLabel: touched! Oddball
MyScrollView: touched! [2, 2]
MyScrollView: touched! [1, 2]
MyScrollView: touched! [2, 1]
MyScrollView: touched! [1, 1]
MyFloatLayout: touched! root

So the super() merely allows the event to bubble through, and then the remaining code (the print statements- eg, "...touch! root") in the on_touch_down() of my custom widgets are executed. 

Now let's take a look at the children of root's MyScrollView children, as I uncomment the super() call in the on_touch_down() method at TESTPOINT SCROLL. In my app, the last widget added is the odd_label. Then the ScrollViews are added; the last one is myscrollview named [2, 2]. Each ScrollView has a StackLayout added to it, and prior to that a series of Labels are added.

The order of adding widgets is, in reverse from LAST to FIRST, is:

odd_label, 4 * [ myscrollview, mystacklayout, a_label (10 of them) ], MyFloatLayout. So the last is the parent of everyone.

A touch event thus looks like this. Notice that mystacklayout overrides the on_touch_down() method but does not have a call to super(), so its children do not see the events:

MyLabel: touched! Oddball
MyScrollView: touched! [2, 2]
MyScrollView: touched! [1, 2]
MyScrollView: touched! [2, 1]
MyScrollView: touched! [1, 1]
MyFloatLayout: touched! root
MyStackLayout: touched! [2, 2]

The event distribution goes like this:

The event took place in the parent, MyFloatLayout, so it is dispatched to its children.  odd_label gets it first, since it was the last widget added to MyFloatLayout. Now its peers will get them: all four of the myscrollview's, in reverse order of their adding. So far so good.

Next, the parent MyFloatLayout gets it. This is where it gets interesting: after the children of root see the event, the children of its children will get it- but only if I click within one of those widgets that contains the children. Here I have clicked in the upper right (position 2,2) of the four boxes of labels. So the event is dispatched to that mystacklayout instance. There is no super() to mystacklayout's on_touch_down method so the event stops bubbling.

Inteed, if I uncomment the super() to mystacklayout's on_touch_down method, and if I click into the 2,2 MyScrollView, I see:

MyLabel: touched! Oddball
MyScrollView: touched! [2, 2]
MyScrollView: touched! [1, 2]
MyScrollView: touched! [2, 1]
MyScrollView: touched! [1, 1]
MyFloatLayout: touched! root
MyLabel: touched! [2, 2]_lbl: 9
MyLabel: touched! [2, 2]_lbl: 8
MyLabel: touched! [2, 2]_lbl: 7
MyLabel: touched! [2, 2]_lbl: 6
MyLabel: touched! [2, 2]_lbl: 5
MyLabel: touched! [2, 2]_lbl: 4
MyLabel: touched! [2, 2]_lbl: 3
MyLabel: touched! [2, 2]_lbl: 2
MyLabel: touched! [2, 2]_lbl: 1
MyLabel: touched! [2, 2]_lbl: 0
MyStackLayout: touched! [2, 2]

Similarly for the other myscrollview's: The only label's that get the event are those that are children of the MyStackLayout which is a child of the MyScrollView in which the touch takes place. It appears that what happens is this; it's really quite simple:

A. If a touch happens on a widget, then that widget and all if its immediate children will see the touch event. 
B. Since the event is dispatched through a Python method, then if you override that method without calling super() you will lose the dispatch mechanism to that widget's list of children.
C. If a widget's event method returns True, Kivy will not dispatch the event any longer. Note that if you return true and override a method such as on_touch_down() and use super() in it, its children will still receive the event but other peers added to the parent widget before it will not get the event.
D. Once the event has been dispatched to the widget and all of its children, it is checked to see if it collides with any of the children. If so, go back to step A where "widget" is now that child.

All widgets at a level in the widget heirarchy receive the event in order of the latest widget added. This order is consistent so that peers receive the event in the order of when they were added, from the last peer added to the first.  Children receive the event in the same order, from the last child added to the first.

However, only children of the topmost touched child (if any) will see the event. Notice how the MyStackLayout and the MyLabels get the event, but only because I clicked in the MyScrollView parent of the MyStackLayout in position [2, 2].

If I click in the background OR on the oddball label- that is, not on any of the myscrollviews:

MyLabel: touched! Oddball
MyScrollView: touched! [2, 2]
MyScrollView: touched! [1, 2]
MyScrollView: touched! [2, 1]
MyScrollView: touched! [1, 1]
MyFloatLayout: touched! root

Again, all peers get the event, but it doesn't carry into all the children of the myscrollview's. The rule stands: the touch must happen on a widget (Step A, above). It and all its immediate children will see the touch event.

Children will only receive the event if their parent has super() in the event method that overrides the widget's event method, or if their parent doesn't override the widget's event method at all.

Reply all
Reply to author
Forward
0 new messages