12

I have two Screens in a ScreenManager that both contains a number of buttons in a ScrollView. The idea is that one steps forward (right) by clicking a button. And step back (left) by swiping back. So I am trying to add a Carousel to implement that one swipe on the second page. This is what I have tried:

self.root = ScreenManager(id = 'screen_manager')

main_screen = Screen(name = 'main_screen')

scroller = Scroller()
button_text = ['teach', 'move', 'b3', 'b4', 'b5', 'b6', 'b7', 'b8']
for text in button_text:
    scroller.view.add_widget(Field(name=text, direction='left', current='teach'))
main_screen.add_widget(scroller)
self.root.add_widget(main_screen)

carousel = Carousel(direction='left', id='carousel')

teach = Screen(name = 'teach')
scroller2 = Scroller()
button_text = ['vocab', 'drills']
for text in button_text:
    scroller2.view.add_widget(Field(name=text, direction='right', current='main_screen'))
carousel.add_widget(scroller2)
teach.add_widget(carousel)
self.root.add_widget(teach)

But since I have only added the second Screen, it's not possible to swipe in either direction. Carousel's load_slide() method takes a slide as argument. Assuming by slide they mean a Carousel. Given I am going to have a lot pages, I probably need the Carousel to be loaded dynamically, using add_widget() and remove_widget(). Would appreciate some pointers.

Working example of the code I have so far: http://dpaste.com/33464R2

dan-klasson
  • 13,734
  • 14
  • 63
  • 101
  • I went to the source code, saw this property `min_move` for **Carousel** if you set this as `min_move= 1` then on swipe it won't change that screen. maybe that could work for you. here it is: https://github.com/kivy/kivy/blob/master/kivy/uix/carousel.py – kiok46 Jun 19 '15 at 10:51
  • Thanks, I can probably use that. Can't get the Carousel to add several screens though. – dan-klasson Jun 19 '15 at 10:53
  • found this tutorial, might be helpful and give you some other idea :) http://davideddu.org/blog/posts/kivy-back-btn-navigation/ – kiok46 Jun 19 '15 at 11:50
  • hey! did you figure out some way? – kiok46 Jun 21 '15 at 06:17

3 Answers3

15

You can do this by using ScreenManager and Gestures. (../kivy/examples/gestures/)

See here kivy-github-gestures

I have explained everything in the code in comments.

First you need create a new Python file named gesture_box.py.

gesture_strings copy from here

from kivy.gesture import GestureDatabase
from kivy.uix.boxlayout import BoxLayout
from kivy.gesture import Gesture 

[Paste gesture_strings here]

#This database can compare gestures the user makes to its stored     gestures 
#and tell us if the user input matches any of them.
gestures = GestureDatabase()
for name, gesture_string in gesture_strings.items():
    gesture = gestures.str_to_gesture(gesture_string)
    gesture.name = name
    gestures.add_gesture(gesture)

class GestureBox(BoxLayout):

    def __init__(self, **kwargs):
        for name in gesture_strings:
            self.register_event_type('on_{}'.format(name))
        super(GestureBox, self).__init__(**kwargs)

    def on_left_to_right_line(self):
        pass

#To recognize a gesture, you’ll need to start recording each individual event in the
#touch_down handler, add the data points for each call to touch_move , and then do the
#gesture calculations when all data points have been received in the touch_up handler.

    def on_touch_down(self, touch):
        #create an user defined variable and add the touch coordinates 
        touch.ud['gesture_path'] = [(touch.x, touch.y)]    
        super(GestureBox, self).on_touch_down(touch)

    def on_touch_move(self, touch):
        touch.ud['gesture_path'].append((touch.x, touch.y))
        super(GestureBox, self).on_touch_move(touch)

    def on_touch_up(self, touch):
        if 'gesture_path' in touch.ud:
            #create a gesture object
            gesture = Gesture()    
            #add the movement coordinates 
            gesture.add_stroke(touch.ud['gesture_path'])
            #normalize so thwu willtolerate size variations
            gesture.normalize()
            #minscore to be attained for a match to be true
            match = gestures.find(gesture, minscore=0.3)
            if match:
                print("{} happened".format(match[1].name))
                self.dispatch('on_{}'.format(match[1].name))
        super(GestureBox, self).on_touch_up(touch)

Now create your main.py file.

import gesture_box as gesture
from kivy.app import App
from kivy.uix.boxlayout import BoxLayout

class Runner(gesture.GestureBox):
    pass

class MainApp(App):
    def build(self):
        return Runner()

if __name__ == '__main__':
    app = MainApp()
    app.run()

And your main.kv file

<Runner>:
    #Handling the gesture event.
    on_left_to_right_line: manager.current = 'main_screen';  manager.transition.direction = 'right'
    ScreenManager:
        id: manager
        Screen:
            name: "main_screen"
            BoxLayout:
                orientation: 'vertical'
                Label:
                Button:
                    text: "Child 1"
                    on_release: 
                        manager.current = "child1"
                        manager.transition.direction = 'left'

                Label:
                Button:
                    text: "Child 2"
                    on_release: 
                        manager.current = "child1"
                        manager.transition.direction = 'left'
                Label:

        Screen:
            name: "child1"
            Label:
                text: "Swipe from left to right to go to main screen (CHILD 1)"

        Screen:
            name: "child2"
            Label:
                text: "Swipe from left to right to go to main screen (CHILD 1)"

EDIT: Many people have asked me that how these gesture string are generated.

Kivy guys provide this in their examples.

see here https://github.com/kivy/kivy/blob/master/examples/gestures/gesture_board.py run this file.

python gesture_board.py

It should open a blank window.

make a gesture on it using mouse or touch.

When the on_touch_up event is triggered, It will output the gesture string in the terminal.

for example the output for right_to_left_line would be. this

('gesture representation:', 'eNq1WMtSHEcQvM+PiIs36t3dP4CujuADHFhsACEZNmBlW3/vnOrZxyCWmYPFQQu5OdlVldXVPbp6/Pr494/N/fZ1//1lO3yePnc0XN3teLj59HT71/bTsBP8ig8dXm8+ve5fnr9uX/GnDVffdj5cvStyk7RhF6NUwfO758en/fhYHR9rFx77fWQNO+4RjCH8wCMsw/VvtCFhZWuspJXZfQzn3/FrHa5pE6WIca0NH00agv3z9uNFLBfx4f6i/v0krRKspZE7PnyFdKbNZYU0mykjZo3mXlZI15Ruy9Lu4ZWKSiFi9bIoLVl14RXSHI3xHQuLqyxHLZLS+iuk00ZZYaMFmqOYYAEn5hUFSRtlhY0mptZQ6mJsXpeV00Vp/7+ypom6wkQ0dEGRpXqgtW250pom6goT1biQRWth0Ddflk4T9cxErkSNpTRG3vVcm4TUpVR2GMoUy+Jpo57ZyFZI1aCOddjbSV0CO9FRkohCpdQVoaeV2n6NuqWddmYntWIW4UwmCvtO4oLODlPn0qyYy7J4GmpnhhKer7W0VkngXZSjOjdz9EtwiBf3FZ1o6amdeYrKQJdEGmae1ob9dZQPTEGBtw0UjMIV8umqTa6mehXEJVVauHLBpDyqq1NDsZpiZUeSy+rpqrWTulqphuHfEGr4eL4cxJmbSyiaEt5ili+Ke5rqfBLHucKKbega3BqdxOlNx7Rl8TTV9SQeCFwCa3hg59ip6KRNMGSDYLhSkxWRp6fua8Q5qFoNDC/sVCq8LJ6OelkU76MHuhHaGrQxJJa73dNSb6vkrWrBNrJWYSm226J6pKdx8pSxU4nMEaIX8jPtWsmKRuAfzMm2bGmkpXGyFIeXoZmrEKaukcpJnXGhaAptw4hwXZ4wkZaGrxJHw1QMN6/NcBtaUZW0NMoqcVyCzDB9WA0jhpYdjXQ02nLNhfAnYEyBEAzmRemSdhZe0yzYobhnRcMSaEpaHi4l/Sy6uP9HcVw+MXQKegYt4ysiTz+LL8/FUb1h7uJ72KoYYHkHGC/5X16226fjlb3EeGcvZbi6xiStGxqug6E87HelDreJ2gxtHTU5Ryt1VGYKlTtKM4UqiSLoGaodtbmCdZTnXE+UY64bHaWYoSVR8rlC5oazcAZmam3ObJkZfNu085+RYJ2QSWKM96dqBzPHSl1fJmamiNteBydmZoiZkaBOzEywUI9EwTz74ZGQucYkZVOomWpYlzJ+EzEImXUP/H1CVsDrpNCDwc5LdOqOI5p54x4/RzNx5zdoZo53tzmaqeNSMEczd3OfR2fDlNlIyeztsHSd0EzfpqWdfioaU+ZvRJcZWQCtlzU4i6HlsgZnYXRqiHcZ0hkfaGTB1D5gZPFUP2BkIVXmVeKsnR564IBm7XTaOkc06yXTLjmiWSOpc1SoozRHsxYyVevgn2T+uDHP0cxZfN4kknmKzVtSvKPz1pHMTdTmaOnom3Yv55SeqLQPKD1rKe9Qft5ImHmd7ivpvU7joDj/0Uv0XkChlfReWa4r6b3kl8cEzoTOoMuMbsW01aYBxdqH8WEOHNB+0Eyt8860w90kGSUuMqwfQNOJMI1RvF8m6jFH+wE0bb8j2g+g6fw5oqhFPzgfto/3D/vx/6Tw3nNtbzYctiM4/zze7R+SEsN4ao0rAN4/f9u+3D592eZXJd8sRnw65f/YvTzfff/StetwrRu8huCVAFT0PS484+V98x/WYONd') ('cross:', -1.2199187170964643) ('check:', -2.052277818300959) ('circle:', 0.4973932910874005) ('square:', 0.2907537534396739)

That's it, you have your string :D

kiok46
  • 1,704
  • 1
  • 12
  • 28
  • You just don't give up do you ;) I like it. Gonna have a look at this on the weekend. Thanks. – dan-klasson Jun 26 '15 at 11:58
  • Solving new challenges, makes me learn quickly! Thanks to you too! I never used gestures before :) – kiok46 Jun 26 '15 at 12:23
  • Awesome. It works. I changed your answer a bit. Put `miniscore` to 0.3 to make it more susceptible to swipes. And specified the transition direction on the main buttons. Still the code only supports going back to main, when the child page should take you back to the parent page, and not main. But going to award you the bounty anyways. If you know how to do it, please let me know. – dan-klasson Jun 27 '15 at 16:50
  • You have to change the transition manually. And also create your own gesture as the one that is used here, is of full width from left to right. Make your own small gesture and also change the minscore :) – kiok46 Jun 27 '15 at 16:50
  • If you change it to 0.3 then you won't be able to make other gestures with ease. let's say, maybe you want to create a gesture that goes diagonally and open some popup or something, then it might mistake it with left to right gesture.I would recommend that you make a new small gesture of your own from `../kivy/examples/gestures/` – kiok46 Jun 27 '15 at 16:54
  • True. I don't at the moment though. Just up and down and left to right. But good you mentioned it for other people. Please let me know if you know how to solve that other issue. – dan-klasson Jun 27 '15 at 16:55
  • Here the child page is not really a child of any other screen, they all are child of screen manager and all are treated equally. Here we are creating a virtual hierarchy that there is a child and a parent screen. – kiok46 Jun 27 '15 at 17:05
  • Yes but my original questions asked for a main, parent and child page tree structure. I.e 3 nodes. – dan-klasson Jun 27 '15 at 18:33
  • Why do you use super() in each method? Isn't just running in __init__ method enough? – TellMeWhy Feb 04 '18 at 18:58
3

I have another method that can be used. It is not through Carousel but it allows you to change screen through swiping. You can create a method that takes in the x point of your initial contact point of the screen and the x point of your final contact of the screen and subtract them. Here is the link to the video(https://www.youtube.com/watch?v=8pqtMAUEUyo&t=65s)

    def on_touch_move(self, touch):
        if touch.ox - touch.x > 50:  # check for swiping gestures
            # ScreenManger change screen

touch.ox is the initial contact point of the screen(x axis) and touch.x is the final. If the difference is then greater than a set value it changes screen.

Abacito
  • 141
  • 7
2

As you asked in your comment.

I have bunch of pages. Each page will have a bunch of buttons. Some pages have more buttons than can fit on the screen, so they need to be scrollable.

Now, Scrollable part, you already figured out.(You can do it in kivy file also), See here. And you can easily add that in the code below.

Clicking on a button should take you to the next child screen (with a scroll effect). On any child it should be possible to go back to it's parent by swiping back.

Here(Code below) you can both swipe or click on buttons to navigate.

Now,

Given I am going to have a lot pages, I probably need the Carousel to be loaded dynamically, using add_widget() and remove_widget().

These examples will help you. Kivy-Showcase and Container

In kivy-showcase have a look at load_screen method and also the build function

Here is an example to add_widgets on click of a button

Builder.load_string('''
[SideBar@BoxLayout]:
    content: content
    orientation: 'vertical'
    size_hint: .2,1
    BoxLayout:
        orientation: 'vertical'
        # just add a id that can be accessed later on
        id: content

<Root>:
    Button:
        center_x: root.center_x
        text: 'press to add_widgets'
        size_hint: .2, .2
        on_press:
            sb.content.clear_widgets()
            root.load_content(sb.content)
    SideBar:
        id: sb
''')

class Root(BoxLayout):

    def load_content(self, content):
        for but in range(20):
            content.add_widget(Button(text=str(but)))

class MyApp(App):
    def build(self):
        return Root()

if __name__ == '__main__':
    MyApp().run() 

Here is the example for screens.

Here is main.py file

from kivy.app import App
from kivy.uix.screenmanager import Screen
from kivy.uix.boxlayout import BoxLayout
from kivy.properties import ObjectProperty

class ShowTime(BoxLayout):
    carousel = ObjectProperty(None)

class Screen1(Screen):
    pass

class Screen2(Screen):
    pass

class MainApp(App):
    def build(self):
         return ShowTime()

if __name__ == '__main__':
    MainApp().run()

Here is the main.kv file

<Screen1>:
    name: "screen1"
    BoxLayout:
        orientation: 'vertical'
        padding: 50
        spacing: 50
        Button:
            text: "Next (2)"
            on_release: self.parent.parent.parent.parent.parent.ids.carousel.load_next()
        Button:
            text: "Go back (2)"
            on_release: self.parent.parent.parent.parent.parent.ids.carousel.load_previous()

<Screen2>:
    name: "screen2"
    BoxLayout:
        orientation: 'vertical'
        padding: 100
        spacing: 100
        Button:
            text: "go back (3)"
            on_release: self.parent.parent.parent.parent.parent.ids.carousel.load_previous()

<Showtime>:
    carousel: carousel
    Carousel:
        id: carousel
        loop: True
        BoxLayout:
            padding: 100
            spacing: 100
            Button:
                text: 'Tap me or Swipe (1)'
                on_release: carousel.load_next()
        Screen1:
        Screen2:

EDIT 1:

Q- How to use load_slide() method?

load_slide() method takes slides as its parameter def load_slide(self, slide):

Q- So now how to get slide?.

slide is a list property slides = ListProperty([]),

Print this where button has text "go back (3)"

on_release: print( self.parent.parent.parent.parent.parent.ids.carousel.slides) you will get a list of all slides under id(carousel).

This is how you use it. .....ids.carousel.load_slide(....ids.carousel..slides[2])

Community
  • 1
  • 1
kiok46
  • 1,704
  • 1
  • 12
  • 28
  • Thanks for that. But I've already managed to do something similar. Problem though is that it should not be possible to swipe from the main screen. And on a child page it should only be possible to swipe back. Probably not possible with Carousel. Maybe one would have to create your own Gestures. Not sure why the ScreenManager does not support this. – dan-klasson Jun 22 '15 at 11:17
  • If that's the case, maybe you have to make your own widget with methods like _start_animation, on_touch_move, using screenmanager :) – kiok46 Jun 22 '15 at 11:25
  • Yes, was thinking that too. Was looking the the Carousel source code. But I doubt I can pull it off. – dan-klasson Jun 22 '15 at 11:30