Buttons inside a Carousel

815 views
Skip to first unread message

Rico_Suave

unread,
Oct 8, 2012, 4:42:51 AM10/8/12
to kivy-...@googlegroups.com
Hi All,

I have a layout where I have a set of buttons inside a carousel, very similar to a launcher screen on a phone.
The problem I'm having is that it's not possible to swipe the carousel when there is a button inside. The button 'steals' the touch event, and registers a click, even when I swipe.

This is very counter-intuitive, users are used to being able to swipe a screen full of buttons, like they do on their phone.
I'm thinking it should be possible for the carousel to detect if there was a swipe or a click, and react only to a swipe. Vice versa for the button element, which should react to a tap, but not a swipe.

Unfortunately I'm relatively new at Python, so it's quite hard for me to figure out if and how I could fix this myself. Any help is appreciated.

Thanks,

Erik

Gabriel Pettier

unread,
Oct 8, 2012, 5:28:49 AM10/8/12
to kivy-...@googlegroups.com
> --
>
>
>

Touch events always go from parents to children… the on_touch_down

https://github.com/kivy/kivy/blob/tests_app/kivy/uix/carousel.py#L285-291

method of the caroussel pass the event to children before testing its
own condition (call to super), i guess you could subclass carousel and
change the on_touch_down method order. But maybe putting action on your
buttons on on_touch_release instead of on_touch_down would work as
well, i didn't try.

Rico_Suave

unread,
Oct 8, 2012, 5:56:56 AM10/8/12
to kivy-...@googlegroups.com

Touch events always go from parents to children… the on_touch_down

https://github.com/kivy/kivy/blob/tests_app/kivy/uix/carousel.py#L285-291

 method of the caroussel pass the event to children before testing its
own condition (call to super), i guess you could subclass carousel and
change the on_touch_down method order. But maybe putting action on your
buttons on on_touch_release instead of on_touch_down would work as
well, i didn't try.

Thank you for your reply, Gabriel,

I was experimenting with different events, but on_touch_release gives me the same result; The button stays pressed when I move my finger and the function is not executed. However, when I release (on a different part of the screen, outside the button), the function is still executed. 

I was thinking of a combination of on_touch_release and on_touch_move. (if there is a "move" when the button is pressed, don't execute the function for on_touch_release) . That will perhaps stop the button from triggering, but I'm not sure if it will move the carousel. 

Anyway, I will have a look at subclassing carousel, that is certainly a cleaner way to go. Hopefully I can figure that out with my (still) limited Python skills :)

Erik

Gabriel Pettier

unread,
Oct 8, 2012, 6:01:15 AM10/8/12
to kivy-...@googlegroups.com
Le lun. 08 oct. 2012 11:56:56 CEST, Rico_Suave a écrit :
>
> Touch events always go from parents to children… the on_touch_down
>
> https://github.com/kivy/kivy/blob/tests_app/kivy/uix/carousel.py#L285-291
> <https://github.com/kivy/kivy/blob/tests_app/kivy/uix/carousel.py#L285-291>
>
>
> method of the caroussel pass the event to children before testing
> its
> own condition (call to super), i guess you could subclass carousel
> and
> change the on_touch_down method order. But maybe putting action on
> your
> buttons on on_touch_release instead of on_touch_down would work as
> well, i didn't try.
>
>
> Thank you for your reply, Gabriel,
>
> I was experimenting with different events, but /on_touch_release/
> gives me the same result; The button stays pressed when I move my
> finger and the function is not executed. However, when I release (on a
> different part of the screen, outside the button), the function is
> still executed.
>
> I was thinking of a combination of /on_touch_release/ and
> /on_touch_move/. (if there is a "move" when the button is pressed,
> don't execute the function for /on_touch_release/) . That will perhaps
> stop the button from triggering, but I'm not sure if it will move the
> carousel.
>
> Anyway, I will have a look at subclassing carousel, that is certainly
> a cleaner way to go. Hopefully I can figure that out with my (still)
> limited Python skills :)
>
> Erik
>
> --
>
>
>

Sorry, i was thinking about on_release for buttons (as opposed to
on_press). But it may not prevent the button from blocking the move.

If you change on_touch_move to block the button, beware, it may be easy
not to move on a click with a mouse, but on tablet, it's nearly
impossible, even if kivy does some preprocessing to remove noise on the
movement, rarely any touch is done without any move.

Thomas Hansen

unread,
Oct 8, 2012, 7:24:00 AM10/8/12
to kivy-...@googlegroups.com
You could take a look at kivy/uix/scrollview.py, which does ignore
child events if swiping by using a timeout and minimum movement
distance at the beginning of a touch. Then you could use that or a
similar approach in a subclass of carousel?!

Sent from my iPhone
> --
>
>
>

Rico_Suave

unread,
Oct 8, 2012, 8:07:55 AM10/8/12
to kivy-...@googlegroups.com
Thank you Thomas, Gabriel,

Gabriel;

I have looked at making my own class based on Carousel, and overriding the on_touch_down, but I'm not quite sure now how that can help me.
The problem is that the button seems to catch the touch event, so I'm guessing I need to subclass the button to prevent this, and go from there?
I'm also a little puzzled by the events that are available; there is on_touch_down, on_touch_move, and on_touch_up, but I can see that on_touch_move reacts to every small movement of the mouse/finger/pen. 

Theoretically (in my head) I would need to overrule an "on_slide" event for my buttons, so that they don't react to a slide, but only to an "on_touch_up" or "click" - if the pointer has not moved. Is there such a "slide" event, or is this something you would write yourself, by looking at the coordinates for "on_touch_move" and looking at the distance of the move? In that case maybe I should create my own "button"-like class that has different events and implements a trigger for a "slide" (so, for example, if there is an on_touch_down, followed by a move over a distance greater than 20% of the element size, I trigger a "slide")?


Thomas;

Thank you for your input, I will have a look at the code for the scrollview, to see if I can learn something from there that might solve this. The code for calculating distance combined with the carousel sounds like it could be a good direction.


Erik


Rico_Suave

unread,
Oct 8, 2012, 10:00:11 AM10/8/12
to kivy-...@googlegroups.com
Sorry guys, 

I have been looking at the code for both the Carousel and the Scrollview for a while now, but I can't wrap my head around it.
I simply can't figure out what instructions like these below exactly mean or do:

if not self.collide_point(*touch.pos):

if not super(Carousel, self).on_touch_down(touch):

if touch.grab_current is self:

if self._viewport and 'button' in touch.profile and touch.button.startswith('scroll'):

if self._get_uid('svavoid') in touch.ud:

if self._touch is not touch:


I am afraid this will be a showstopper for our company to use Kivy. We have created a demo/prototype interface for the application we want to create for some customers and one of the biggest points in the feedback was that they should be able to swipe through the buttons like they are used to on their smartphones.
So before I confront my boss; is there a way to request a feature to get a solution for this in kivy, or any other way to get this done besides modifying the underlying framework ourselves?

Thanks for any info you can give me,

Erik

Thomas Hansen

unread,
Oct 8, 2012, 10:54:35 AM10/8/12
to kivy-...@googlegroups.com
you can see the carousel touch_down/move/up handlers here:
https://github.com/kivy/kivy/blob/master/kivy/uix/carousel.py#L285-310

note how each of them calls super(Carousel,
self).on_touch_XXXX(touch). The base widget class, has the touch
event handlers implemented like this:

for each child widget:
handled = child.on_touch_XXX(touch)
if handled:
break # done..one of the children handled the event, dont pass
it to others

the problem with a swype is...you dont really know whether its a swype
yet when the first touch_down event happens...so at that point, you
don't really know whether this whole touch sequence (from touch down,
each move, and touch up) is meant for the button, or as a swype to
move the carousel. So you either have to change the touch event
handlers for your carousel to

1. detect swype by looking at the sequence of move events, and then
make sure that any buttons that were handling the prior down/move
events, don't get the up event, and are restored to their previous
state

or

2. dont pass the touch_down event to the children to begin with, look
at the first couple of move events (for e.g. 0.5 seconds), and decide
if its a swype; if it was, just dont call any super method / dont pass
events to the children at all. If it wasn't however, you'll have to
retroactively pass the touch_down, and touch_move events that you held
back by creating faked events / dispatching stored events.

The scrollview widget, does this via 2 (the descripton here may not
cover all of it). If you find a really good way to handle it, I for
one would love to have it added to the carousel widget :)

--
Thomas
> --
>
>
>

Rico_Suave

unread,
Oct 8, 2012, 11:05:04 AM10/8/12
to kivy-...@googlegroups.com

That is a very clear explanation Thomas, thanks a lot.
Gabriel has also provided me with a quick example via IRC, which I will also be checking out (tomorrow, as I'm leaving for the day).
Hopefully it will all fall into place then. (maybe a good night of sleep will also help me :) )

Erik


Op maandag 8 oktober 2012 16:54:36 UTC+2 schreef Thomas Hansen het volgende:

Thomas Hansen

unread,
Oct 8, 2012, 11:16:42 AM10/8/12
to kivy-...@googlegroups.com
> if not self.collide_point(*touch.pos):
touch.pos is a tuple of x,y coordinate of the touch, so its like
calling self.collide_point(touch.x, touch.y). self.collide_point(x,y)
will return true if (x,y) is inside the bounding box / rectangle
defined by the widgets pos and height.

> if not super(Carousel, self).on_touch_down(touch):
super(Carousel, self).on_touch_down(touch) will call the base classes
on_touch_down handler (same behavior as if the method wasn't
overwritten in this class). The general widget behavior is to forward
the event to all child widgets, until one of them returns True
(meaning it handled the event)...so in this case:
> if not super(Carousel, self).on_touch_down(touch):
means unless a child widget handled the event, do the following...


> if touch.grab_current is self:
a widget can "grab" a touch on e.g. touch_down, which will cause it to
receive any subsequent events related to that touch, regardless of
whether the parent widget forwards them or not...checking if
touch.grab_current is self is done to check if this event is a normal
one forwarded form the parent...or if its a grabbed touch which the
widget specifically requested to get no matter what. Generally once
you grab a touch...you just want to keep track of it that way
(otherwise, you would e.g. get the same event from the normal parent
forwarding and from the grabbing.


> if self._viewport and 'button' in touch.profile and
> touch.button.startswith('scroll'):
> if self._get_uid('svavoid') in touch.ud:
touch.ud: ud stands for userdata, which is python dictionary attached
to a touch. this way you can store information with the touch itself
in e.g. on_touch_down, and then access it or check it later in a
touch_move or touch_up event handler, or even in another widget that
receives the event. All of these lines look like they are checking
the touch for some contect established earlier that could indicate
whether the touch is to be used for scrolling or not, etc..


> if self._touch is not touch:
checks if 'touch' (which I assume is the touch event passed to the
handler), is the same as is stored in self._touch before at some
point. These are the kind of things that make multi-touch /
multi-pointer programming a bit different than single cursor / mouse
based widgets...there are multiple independent cursor sessions, and
you often want to make sure the context for the interaction is based
on the same touch moving around, instead of a another one.


> I am afraid this will be a showstopper for our company to use Kivy. We have
> created a demo/prototype interface for the application we want to create for
> some customers and one of the biggest points in the feedback was that they
> should be able to swipe through the buttons like they are used to on their
> smartphones.
> So before I confront my boss; is there a way to request a feature to get a
> solution for this in kivy, or any other way to get this done besides
> modifying the underlying framework ourselves?

First, I would file an issue on github if you havent already.
Depending on your deadline, how fast you want it done, how much input
you want to give for having the feature implemented, or integrated /
tested specifically with your code. You can file the issue, and try
to get it done through the volunteer open source process. If you want
it done fast, and integrated with what you have, shoot me an email at
tho...@fresklabs.com; fresk is happy to take on this kind of thing as
a consulting job. I know Meltingrocks (http://meltingrocks.com/)
would at the very least be just as qualified, but I cant speak for
their availability or interest.

--
Thomas

Rico_Suave

unread,
Oct 9, 2012, 6:20:39 AM10/9/12
to kivy-...@googlegroups.com
Hi All,

First of all, thank you very much for all your help/time!

After a nights sleep, and a little exploration, I've gotten a bit further. I found it hard to port the code from ScrollView to Carousel. There are a lot of checks and dependencies in that code, so I opted for a quick hack in the Carousel.py file.

I was thinking the following:

1. I don't pass the "down" event from the Carousel to it's children. This way the buttons inside the Carousel don't get activated on_touch_down.
2. In the "up" event of the Carousel, I check if there was enough movement to trigger a swipe
3. If there was NOT enough movement - I pass the event on to it's children because in this case it's a tap on a button and not a swipe.
4. I bind the button events to on_touch_up

Here's my code: http://pastebin.com/iESiRCf5 

This all seems to work, except for point 4, it seems that the on_touch_up event doesn't propagate down to my buttons. I've seen this before and Gabriel's suggestion was that another element could be 'stealing' the event and prevent it from propagating. Is there any way to check what element it could be, or am I perhaps overlooking something else?

Erik




Rico_Suave

unread,
Oct 9, 2012, 7:13:54 AM10/9/12
to kivy-...@googlegroups.com

Aaah, nevermind, I figured it out. I'm calling  return super(Carousel, self).on_touch_move(touchin the on_touch_up() method...

It seems to work now :)



Op dinsdag 9 oktober 2012 12:20:39 UTC+2 schreef Rico_Suave het volgende:

Rico_Suave

unread,
Oct 12, 2012, 4:09:25 AM10/12/12
to kivy-...@googlegroups.com

Hmmmm.... Unfortunately, it seems doesn't work after all. Now I have a different problem;

I can swipe the carousel, and have a button react to an "on_touch_up", however, the "on_touch_up" triggers *all* of the buttons inside my grid, not just the one I tap...

Any help/tips are appreciated.

Erik





Op dinsdag 9 oktober 2012 13:13:54 UTC+2 schreef Rico_Suave het volgende:

Gabriel Pettier

unread,
Oct 12, 2012, 4:18:09 AM10/12/12
to kivy-...@googlegroups.com
On ven. 12 oct. 2012 10:09:25 CEST, Rico_Suave wrote:
>
> Hmmmm.... Unfortunately, it seems doesn't work after all. Now I have a
> different problem;
>
> I can swipe the carousel, and have a button react to an "on_touch_up",
> however, the "on_touch_up" triggers *all* of the buttons inside my
> grid, not just the one I tap...
>
> Any help/tips are appreciated.
>
> Erik
>
>
>
>
>
> Op dinsdag 9 oktober 2012 13:13:54 UTC+2 schreef Rico_Suave het volgende:
>
>
> Aaah, nevermind, I figured it out. I'm calling
> returnsuper(Carousel,self).*on_touch_move*(touch) in the
> *on_touch_up()* method...
>
> It seems to work now :)
>
> --
>
>

on_touch_* events are not restricted geographicaly, you need to do the
check yourself.

def on_touch_up(self, touch):
if self.collide_point(*touch.pos):
# do your stuff
else:
return super(MyClass, self).on_touch_up(touch)

alex zhang

unread,
Jun 5, 2013, 8:48:58 PM6/5/13
to kivy-...@googlegroups.com
About the ScrollView, I have an IDEA.

the current Scroll View is very confused. the button will react the click for a time. the feeling is bad.

My idea:

1,when touch_down, record the touch.pos as "originPos"
2,if the touch is moving, then scroll the ScrollView
3,when the touch_up, the pos is "lastPos", if the Distance(originPos,lastPos)<=a certain number, then the button under the touch point is clicked. 

I hope you can have a try with this idea in the ScrollView.

Thanks 

Alex

在 2012年10月8日星期一UTC+8下午10时54分36秒,Thomas Hansen写道:

Mathieu Virbel

unread,
Jun 16, 2013, 5:24:25 AM6/16/13
to kivy-...@googlegroups.com
Hi,

Did you actually read the documentation of the scrollview?
Your simple idea is not the one thing to take care about to determine if the event must be dispatched to children, but the timing matters too.

And we take care of both already.

I guess you might do something wrong in your code, or you have an issue with the hardware, which i would be pleased to check.

Regards,

Mathieu
--
You received this message because you are subscribed to the Google Groups "Kivy users support" group.
To unsubscribe from this group and stop receiving emails from it, send an email to kivy-users+...@googlegroups.com.
For more options, visit https://groups.google.com/groups/opt_out.
 
 

Miachael Annie

unread,
Mar 24, 2014, 2:17:45 AM3/24/14
to kivy-...@googlegroups.com
I am also having the similar issue as yours on carousel with buttons, but mine is on another UI carousel control, don't know to read some carousel control tutorial would be helpful or not.
Reply all
Reply to author
Forward
0 new messages