Fixing Kivy event bubbling #14
Replies: 7 comments 14 replies
-
Below are a list of some issues that I believe are related, going back to 2015. I am not saying they will be fixed or must be fixed by your plans, but maybe worth seeing if they shed any light, or suggest related changes that can be fixed at the same time. kivy/kivy#8075 |
Beta Was this translation helpful? Give feedback.
-
Thanks @Julian-O for the extra contents. For the past 2days, i have investigated them and concluded that they all stem from the same issues as highlighted by my first post in this thread, i.e., inconsistencies in event propagation. I'm now trying to see if the What I'm trying to look for are some edge cases that might occur due to consuming or non consuming of the touch on a disabled widget based on its opacity. |
Beta Was this translation helpful? Give feedback.
-
Opened the first pr in this direction #8488 starting from the From the initial run, as expected the primary offenders are Pulled another pr #8498 after receiving advice from @akshayaurora to make the prs atomic. |
Beta Was this translation helpful? Give feedback.
-
I have success in making the While, trying to make it that I had stumbled upon a design choice which is better done after discussion with the community I believe. def on_touch_down(self,touch):
# within bounds check
if not self.collide_point(*touch.pos):
return
# disabled check
if self.disabled:
if self.opacity == 0:
return
else:
return True
# pass to children first
for child in self.children[:]:
if child.dispatch('on_touch_down', touch):
return True # please ignore the naming as they are just for example
def check_if_touch_is_within_bounds(self, touch):
if not self.collide_point(*touch.pos):
return False
def disabled_check(self, touch):
if self.disabled:
if self.opacity == 0:
return
else:
return True
def pass_to_children_first(self, touch):
for child in self.children[:]:
if child.dispatch('on_touch_down', touch):
return True
def on_touch_down(self,touch):
# within bounds check
if not check_if_touch_is_within_bounds( touch): return
# disabled check
if disabled_check(touch): return True
# pass to children first
if pass_to_children_first(touch): return True Well, the question is which one is better 1 or 2. For 1, we have the pros that this doesn't introduce any new methods and doesn't have function calling overhead , with the cons that if any changes are to be made by the end user in this behaviour then they have to have the raw functions implemented in their event handling functions and then make their custom changes, and can't rely on just a super call. This might unleash inconsistencies, but for internal team with some effort we can enforce the consistency, and for end users we have to rely on their sound judgement. This might lead them to have the need to read and learn the internal implementation, that might be little uncomfortable in terms of DX. The method 2, has the pros that the internal implementation is always the same and the user can override a particular piece and dont need to implement them in code in the touch handling functions, leading to a simpler super call , which ensures better consistency while exposing the internal behaviours for easy overriding. This might lead to little bit easier DX. The cons are that there's a slight function calling overhead and then double return handling overhead, this means that we handle return not only in the sub-functions but also need to handle the returns of these sub-functions in the touch handling function as highlighted by the above second code example. So, I would look forward for comments on which design pattern may be well suited. |
Beta Was this translation helpful? Give feedback.
-
Looks good to me. |
Beta Was this translation helpful? Give feedback.
-
A small update is that I have been able to fix Two improvements are I have been able to reduce the code in both of them for touch handling to just a few lines rather than the current convoluted programme flow. This in turn may increase the performance of these widgets.
I'm doing some more testing to find if any edge cases are there in my implementation. We have a lot of widgets/behaviors which consume events unnecessarily, currently investigating these widgets, checking the impact of rectifying their behaviour. |
Beta Was this translation helpful? Give feedback.
-
Pasting some resources in regards tp event bubbling and gesture recognition:
|
Beta Was this translation helpful? Give feedback.
-
The docs proclaim that Kivy events follow the below rules :
But on exploring various widgets it seems the rules aren't followed strictly. Event capturing shall always happen from top down to the leaves of widget tree receiving the events first and if they stop the event propagation only then the event is not bubbled up.
Though in essence, Kivy tries to follow this methodology of event propagation but there's inherent inconsistency and deviation in native widgets from this behaviour. As a well tested and industry accepted event propagation behaviour, its not bad to follow this like what browser events propagation mechanism.
Well, to highlight my point, in essence, the below points are discussed:
Window
:user-events
behaviour to cover overlapping widgets edge cases.on_touch_down
function ofWidget
class. Current implementation consumes the event if a widget is disabled and the event collides with the widget which ignores the edge case described above. Rather the event should just be ignored and not consumed by a disabled widget. This is done correctly inon_touch_move
,on_motion
andon_touch_up
, buton_touch_down
needs rectification.ScrollView
andCarousel
, which lets in weird behaviours such as not being able to use properly theCarousel
andScrollView
widgets as child of one another.ScrollView
as the child ofCarousel
, the swipe behaviours are buggy and lets say we have aTextInput
inside theScrollView
, then theTextInput
only receives focus when double tapped quickly or sometimes even more. This is not the expected UX behaviour. While on makingCarousel
,withTextInput
as a child, to be the child of theScrollView
, the tap behaviour is rectified but the scroll behaviour hinders withCarousel
and we have to hack theswipe_distance
ofCarousel
to have a botched up acceptable swiping behaviour.ScrollView
inside another, the top mostScrollView
doesn't receive the scrolling events in cases when the innerScrollView
doesn't have any more scrollable area in the direction. For example, lets say we have aScrollView
for vertical direction only and anotherScrollView
inside the first one for horizontal direction only. So the expected behaviour is that on vertical swipes the topmostScrollView
should scroll but what happens is that the innermostScrollView
consumes the event unnecessarily which leads to non-passing of the event to the topmostScrollView
. We should rectify this and make it behave like how scrolling behaves in web scenarios.All the above pain points mentioned can be easily solved by just following the event propagation model correctly and exactly. That is unless specified by the user the event is not consumed in the capturing (top to bottom) phase and normally the bubbling phase starts from the bottom to top and the event is consumed only if explicitly specified. Disabled widgets should not consume touch down events.
This needs some discussion before going for an extensive pr and hence I opened this discussion. Hoping for some good insights from the members.
Beta Was this translation helpful? Give feedback.
All reactions