I would like to implement text input, that is able to autocomplete
Wikipedia addresses.
For example, what you start typing dog, you have do so far
and it will suggest:
https://en.wikipedia.org/wiki/Do
https://en.wikipedia.org/wiki/Donald_Trump
https://en.wikipedia.org/wiki/Dog
https://en.wikipedia.org/wiki/Dominican_Republic
...
as in Wikipedia search engine
And would be fine, if it could solve somehow disambiguation pages as well, but it's not necessary
The structure of your application has to be broken into two parts, the first is you GUI and the second is the logic that checks for wikipedia pages.
The GUI part consists of a TextInput, a binding that will call the function that checks for wikipedia pages and finally a way to display these results. Since you haven't specified how to display these results, I will just assume that they are to be displayed in a Label.
The GUI might look something like this:
# main.py
from kivy.app import App
from kivy.lang import Builder
from kivy.uix.textinput import TextInput
from kivy.properties import ListProperty
from wiki_recommendations import WikiSearcher
class SearchBar(TextInput):
articles = ListProperty()
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.bind(text=self.on_text)
self.bind(articles=self.on_articles)
def on_text(self, *args):
WikiSearcher().get_search_results(self.text, self)
def on_articles(self, *args):
label = self.parent.ids['results']
label.text = '' # Reset the text
for article in self.articles:
label.text = label.text + '\n' + article
kv = """
BoxLayout:
orientation: 'vertical'
padding: self.width * 0.1
spacing: self.height * 0.1
SearchBar
size_hint: 1, 0.2
multiline: False
font_size: self.height*0.8
Label:
id: results
size_hint: 1, 0.8
"""
class SearchApp(App):
def build(self):
return Builder.load_string(kv)
if __name__ == '__main__':
SearchApp().run()
Our SearchBar object has two bindings. The first calls the on_text callback when the user inputs text. This initialises a search, passing the text the user submitted as an argument. The second binding is to a ListProperty that fires the on_articles function when the articles property receives data from WikiSearcher(). This function updates the text property of the Label.
The logic that checks the wikipedia pages should look something like this:
# wiki_recommendations.py
from bs4 import BeautifulSoup
import requests
import threading
def thread(function):
def wrap(*args, **kwargs):
t = threading.Thread(target=function, args=args, kwargs=kwargs, daemon=True)
t.start()
return t
return wrap
class WikiSearcher:
url = 'https://en.wikipedia.org/wiki/Special:Search'
#thread
def get_search_results(self, text: str, root):
"""
This function uses the BeautifulSoup library and the requests library to get the top Wikipedia articls
:param text: The text to be searched.
:param root: The object that calls this function - useful for returning a result.
:return:
"""
# Web scraping happens here
top_articles = [] # The results of your web scraping
root.articles = top_articles
I won't code the web scraping in this answer, as to do so would take a load of work! I have however, noticed that there exists a special wikipedia search page (see url property). When searching "hello" into the bar the web address returned is:
https://en.wikipedia.org/w/index.php?search=hello&title=Special%3ASearch&profile=advanced&fulltext=1&advancedSearch-current=%7B%7D&ns0=1
The key here is the search=hello bit of the URL. Perhaps you can manipulate the URL so that this snippet changes in accordance with the text argument passed to the get_search_results() function. Something like:
url = f"https://en.wikipedia.org/w/index.php?search={text}&title=Special%3ASearch&profile=advanced&fulltext=1&advancedSearch-current=%7B%7D&ns0=1"
but I'll leave that with you to figure out, as this is beyond the remit of Kivy.
You absolutely should use a thread when calling this function, otherwise it will cause the GUI to stutter, resulting in a really poor looking user interface.
Finally, when this function has done its thing, the last thing it should do is update the SearchBar.articles attribute. This is where the root argument comes in, which provides a convenient way to access this class across files. Updating root.articles in-turn calls on_articles which displays these articles in a Label.
Okay, here are some of the improvements to the solution that I have already posted, but since I have made quite a few changes I thought I would post another answer. Here is the final code:
from kivy.app import App
from kivy.lang import Builder
from kivy.uix.textinput import TextInput
from kivy.properties import ListProperty
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.behaviors import ButtonBehavior
from kivy.uix.label import Label
import webbrowser
from wiki_recommendations import WikiSearcher
class SearchBar(TextInput):
articles = ListProperty()
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.bind(text=self.on_text)
self.bind(articles=self.on_articles)
def on_text(self, *args):
WikiSearcher().get_search_results(self.text, self)
def on_articles(self, *args):
self.parent.ids['recommendations'].update_recommendations(self.articles)
class SearchItem(ButtonBehavior, Label):
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.url = ''
def on_release(self):
webbrowser.open(self.url)
class Recommendations(BoxLayout):
def update_recommendations(self, recommendations: list):
for (search_item, recommendation) in zip(self.children, recommendations):
search_item.text = recommendation
search_item.url = 'https://en.wikipedia.org/wiki/' + recommendation
kv = """
<SearchItem>:
canvas.before:
Color:
rgba: [0.8, 0.8, 0.8, 1] if self.state == 'normal' else [30/255, 139/255, 195/255, 1]
Rectangle:
size: self.size
pos: self.pos
Color:
rgba: 0, 0, 0, 1
Line:
rectangle: self.x, self.y, self.width, self.height
color: 0, 0, 0, 1
BoxLayout:
canvas.before:
Color:
rgba: 1, 1, 1, 1
Rectangle:
size: self.size
pos: self.pos
orientation: 'vertical'
padding: self.width * 0.1
spacing: self.height * 0.1
SearchBar:
size_hint: 1, 0.2
multiline: False
font_size: self.height*0.8
Recommendations:
id: recommendations
orientation: 'vertical'
SearchItem
SearchItem
SearchItem
SearchItem
SearchItem
"""
class SearchApp(App):
def build(self):
return Builder.load_string(kv)
if __name__ == '__main__':
SearchApp().run()
I have created a custom SearchItem object which is essentially a minimalist button, but on pressing it you are redirected to the appropriate wikipedia page. To be honest you could have just used a Button object, but from my personal experience I prefer the freedoms and flexibility of creating a Label endowed with the ButtonBehavior behaviour.
Alongside this is a wiki_reccomendation.py file:
import wikipedia
import threading
def thread(function):
def wrap(*args, **kwargs):
t = threading.Thread(target=function, args=args, kwargs=kwargs, daemon=True)
t.start()
return t
return wrap
class WikiSearcher:
url = 'https://en.wikipedia.org/wiki/Special:Search'
#thread
def get_search_results(self, text: str, root):
"""
Gets the top Wikipedia articles
:param text: The text to be searched.
:param root: The object that calls this function - useful for returning a result.
:return:
"""
root.articles = wikipedia.search(text)
This remains mostly unchanged. Here is what the app looks like now:
As the user types in the search bar the WikiSearcher object is instantiated and the get_search_results() is called. This updates the articles property of the SearchBar which in turn updates the children of Recommendations (a BoxLayout). These children are essentially just Buttons which direct the user to the recommended page when they are pressed.
I will leave you with making it look pretty - if that is important to you - but I think that is it. Note this only displays the top 5 recommended articles. You can add and remove recommendations (to have more or less than 5) using the clear_widgets() and add_widgets() methods, but simply updating the ones on the screen is much faster.
Related
I am trying to animate a graphic in kivy. Since all my inputs will be coming from the keyboard, I need to have object references in python, however I still want to set up the widgets in the kv file. To do this I found the only way to trigger anything from python was using IDs. However when trying to start an animation via ID the object doesn't move. Printing the x coordinate shows a change, although I don't see it move.
Here is my main.py:
import threading
import time
import keyboard
from kivy.app import App, ObjectProperty
from kivy.uix.screenmanager import Screen, ScreenManager, NoTransition
from kivy.uix.floatlayout import FloatLayout
from kivy.animation import Animation, AnimationTransition
class MainScreen(Screen):
pass
class OtherScreen(Screen):
pass
def left_animate(object):
anim = Animation(x=object.x - 40, transition='in_back')
anim.start(object)
class RootWidget(FloatLayout):
def __init__(self, **kwargs):
super(RootWidget, self).__init__(**kwargs)
self.screenman = ScreenManager()
self.screenman.add_widget(MainScreen(name="main"))
self.screenman.add_widget(OtherScreen(name="other"))
self.add_widget(self.screenman)
x = threading.Thread(target=self.keyboard_thread)
x.start()
def keyboard_thread(self):
print("Thread started")
while True:
if keyboard.is_pressed('h'):
self.key_event('h')
elif keyboard.is_pressed('j'):
self.key_event('j')
elif keyboard.is_pressed('left'):
self.left_pressed()
def left_pressed(self):
if self.screenman.current == 'main':
anim = Animation(x = 40)
print(self.ids.main.ids.img_ok.x)
anim.start(self.ids.main.ids.img_ok)
def key_event(self, key):
if key == 'h':
self.screenman.current = "main"
else:
self.screenman.current = "other"
class TestApp(App):
def build(self):
return RootWidget()
if __name__ == '__main__':
TestApp().run()
and this is the .kv file:
<RootWidget>:
ScreenManager:
MainScreen:
id: main
OtherScreen:
id: other
<MainScreen>:
canvas.before:
Color:
rgba: 0, 0, 0, 1
canvas:
Rectangle:
pos: root.pos
size: root.size
Image:
id: img_ok
source: '../graphics/icons8-abstimmung-80.png'
pos: 200, 200
<OtherScreen>:
Button:
text: 'Go to main screen'
on_press: root.manager.current = 'main'
The first problem is that you are building two ScreenManagers. One is built in the __init__() method of RootWidget. The other is being built by kv when it evaluates the <RootWidget>: rule in your .kv file. Both of those ScreenManagers fill the RootWidget and one obscures the other. I believe that your Animation is moving the Image widget, but it is underneath and not visible.
I suggest that you start fixing this by eliminating:
self.screenman = ScreenManager()
self.screenman.add_widget(MainScreen(name="main"))
self.screenman.add_widget(OtherScreen(name="other"))
self.add_widget(self.screenman)
from the __init__() method. That will force additional changes to your code.
I have created a custom toggle button layout that I would like to reuse multiple times. I would like to reuse it as it contains extensive formatting. I am using generated uuid's to assign the groups so that the multiple instances don't interfere with each other.
Here my test.kv:
#:kivy 2.0.0
#:import uuid uuid
<ExampleToggle#BoxLayout>:
uuid: uuid.uuid4()
orientation: 'horizontal'
ToggleButton:
id: run
text: 'RUN'
group: root.uuid
on_release: root.on_run()
ToggleButton:
id: stop
text: 'STOP'
group: root.uuid
on_release: root.on_stop()
<TestDisplay>:
BoxLayout:
orientation: 'vertical'
ExampleToggle:
on_run: print('A')
on_stop: print('B')
ExampleToggle:
on_run: print('C')
on_stop: print('D')
Here is my test.py:
import kivy
kivy.require('2.0.0')
from kivy.app import App
from kivy.properties import ObjectProperty
from kivy.uix.widget import Widget
class ExampleToggle():
on_run = ObjectProperty(None)
on_stop = ObjectProperty(None)
def on_run(self, *args):
# Dummy function
pass
def on_stop(self, *args):
# Dummy function
pass
class TestDisplay(Widget):
pass
class TestApp(App):
def __init__(self, **kwargs):
super().__init__(**kwargs)
def build(self):
return TestDisplay()
if __name__ == '__main__':
TestApp().run()
I would like to be able to create multiple copies of the ExampleToggle widget and assign different functions to the buttons. I have no issue assigning a function to the on_release event of the individual buttons, however if I do that then it is the same event for every instance of this widget. I would like to be able to assign different functions every time I reuse the widget.
I feel I am either missing something ridiculously simple, or I am going down the wrong path. I have tried multiple different methods, and spent a ton of time reading and researching... Any help would be greatly appreciated.
You just need to provide a way for your functions called to be set at runtime.
For instance, you could have f1 = ObjectProperty() and f2 = ObjectProperty() in your ExampleToggle Python code, then on_release: root.f1() etc. in your kv code. To set the functions, use f1: do_something_a in kv, or your_exampletoggle.f1 = do_something_a in python, where do_something_a is a function.
After a long break I came back to this and worked out the answer. Here is my working solution for others who are curious, or for myself in the future. I had to build the toggle layout in python and then use it in the .kv file. The key was using the 'self.dispatch()' method. I found this from reading through the kivy source code for buttons.
test.kv:
#:kivy 2.0.0
#:import uuid uuid
#:import ExampleToggle test
<TestDisplay>:
BoxLayout:
orientation: 'vertical'
ExampleToggle:
on_run: print('A')
on_stop: print('B')
ExampleToggle:
on_run: print('C')
on_stop: print('D')
test.py:
from uuid import uuid4
import kivy
kivy.require('2.0.0')
from kivy.app import App
from kivy.uix.widget import Widget
from kivy.uix.togglebutton import ToggleButton
from kivy.uix.boxlayout import BoxLayout
class ExampleToggle(BoxLayout):
def __init__(self, **kwargs):
self.register_event_type('on_run')
self.register_event_type('on_stop')
super(ExampleToggle, self).__init__(**kwargs)
self.groupid = uuid4()
self.orientation = 'horizontal'
self.size_hint = (None, .1)
self.width: self.height * 5
self.state = None
rbtn = ToggleButton(text='RUN',
group=self.groupid,
allow_no_selection=False)
sbtn = ToggleButton(text='STOP',
group=self.groupid,
allow_no_selection=False)
self.add_widget(rbtn)
self.add_widget(sbtn)
rbtn.bind(on_release=self.run)
sbtn.bind(on_release=self.stop)
def run(self, *args):
if self.state != 'RUN':
self.dispatch('on_run')
self.state = 'RUN'
def stop(self, *args):
if self.state != 'STOP':
self.dispatch('on_stop')
self.state = 'STOP'
def on_run(self):
pass
def on_stop(self):
pass
class TestDisplay(Widget):
pass
class TestApp(App):
def __init__(self, **kwargs):
super().__init__(**kwargs)
def build(self):
return TestDisplay()
if __name__ == '__main__':
TestApp().run()
Apologies if this question has an obvious answer but I have been unable to find a solution for some time now. A widget in my app has a 'graph' that is defined in terms of the widget's dimensions. I can dynamically update the 'graph' from kv because I have access to the widget's dimensions there. However I would like to define a default 'graph', also in terms of the widget's size, that appears at startup. I do not know how to pass the widget's dimensions to the __init__ function. Here is my boiled down example:
from kivy.app import App
from kivy.uix.boxlayout import BoxLayout
from kivy.lang import Builder
from kivy.properties import ListProperty
Builder.load_string('''
#:kivy 1.9.2
<MainWidget>:
BoxLayout:
size_hint_x: 20
orientation: 'vertical'
ToggleButton:
text: 'WF1'
state: 'down'
allow_no_selection: False
on_press:
root.line_points = [waveform.x, waveform.top, waveform.right, waveform.y]
root.event_handler()
group: 'lhs_buttons'
ToggleButton:
text: 'WF2'
allow_no_selection: False
on_press:
root.line_points = [waveform.x, waveform.y, waveform.right, waveform.top]
root.event_handler()
group: 'lhs_buttons'
BoxLayout:
size_hint_x: 80
Button:
id: waveform
canvas:
Line:
points: root.line_points
''')
class MainWidget(BoxLayout):
line_points = ListProperty()
def __init__(self, **kwargs):
super(MainWidget, self).__init__(**kwargs)
#self.line_points = [waveform.x, waveform.top, waveform.right, waveform.y]
def event_handler(self):
print "event"
class MyApp(App):
def build(self):
return MainWidget()
if __name__ == '__main__':
MyApp().run()
I suppose a partial solution would be to trigger the on_press event in __init__ for one of the buttons , but I have been unable to figure out how to do that. I am new to Python and to Kivy.
One approach will be to bind line_points like this:
<MainWidget>:
line_points: self.calc_line_points(waveform.x, waveform.y, waveform.right, waveform.top)
...
And calc_line_points will be defined such as :
def calc_line_points(self, x, y, right, top):
return [ x, top, right, y] #put more logic here ...
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
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
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.
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])
main:
# -*- coding: utf-8 -*-
import kivy
kivy.require('1.8.0')
'''
Check aida.kv for the ui design
'''
from kivy.app import App
from kivy.uix.boxlayout import BoxLayout
from kivy.properties import (ObjectProperty, ListProperty, StringProperty, NumericProperty)
class Controls(BoxLayout):
timer_value = NumericProperty()
def __init__(self, **kwargs):
super(Controls, self).__init__(**kwargs)
self.roulette_time.bind(rolling_value=self.time_changed)
def time_changed(self, instance, value):
self.timer_value = value
class WeatherRoot(BoxLayout):
pass
class AidaApp(App):
pass
if __name__ == '__main__':
AidaApp().run()
kv:
# -*- coding: cp1252 -*-
#:kivy 1.8.0
#:import CyclicRoulette kivy.garden.roulette.CyclicRoulette
WeatherRoot:
<WeatherRoot#BoxLayout>:
carousel: carousel
controls: controls
BoxLayout:
orientation: "vertical"
Carousel:
id: carousel
Controls:
id: controls
<Controls>:
canvas.before:
Color:
rgba: 0.686, 0.635, 0.541, 0.5
Rectangle:
pos: self.pos
size: self.size
roulette_time: rlt_time
BoxLayout:
CyclicRoulette:
cycle: 60
density: 15
zero_indexed: True
selected_value: 5
width: 50
background_color: [0.686, 0.635, 0.541, 1]
id: rlt_time
Label:
size_hint: (1, .8)
text: format(root.timer_value)
font_size: 50
I get an error 'Controls'object has no attribute 'roulette_time
When I set an attribute I get sort of different errors about binding, etc
only works when in kv Controls set to root, but I must have a different class as a root
Please help me to solve this problem, I am stucked
You get the error Controls'object has no attribute 'roulette_time' because nowhere in your Controls class, or anywhere for that matter, do you define a roulette_time variable. Try setting roulette_time = NumericProperty(0) in the Controls class.
class Controls(BoxLayout):
roulette_time = NumericProperty(0)
Not sure why that's not working, because it should. An ObjectProperty should automatically be created to hold the reference to the CyclicRoulette instance.
But - there's an easier way to do this! You're just updating a property on your Controls class with that value anyway, so you can directly bind them. Replace this line:
roulette_time: rlt_time
with:
timer_value: rlt_time.rolling_value
and whenever the CyclicRoulettes rolling_value property changes, your timer_value on Controls will automatically be updated. You don't need to create the time_changed method or bind it to the CyclicRoulette.