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.
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 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.
I am trying to create a GUI using Kivy. However, I cannot resolve some formatting issues.
Here is a slimmed down version of my KV file:
BoxLayout:
MainCanvas:
size_hint: 1,1
size: (root.width,root.height*.9)
DoubleEllipseWidget:
ActionBar:
id: _action
size_hint: 1,0.1
size: (root.width,root.height*.1)
pos_hint: {'bottom':1}
ActionView:
use_separator: True
ActionPrevious:
title: 'Test App:'
with_previous: False
ActionOverflow:
disabled: True
ActionButton:
important: True
text: 'Button 1'
#on_release: some_function
ActionButton:
text: 'Button 2'
#on_release: some_function
ActionButton:
text: 'Button 3'
#on_release: some_function
<DoubleEllipseWidget>
size: [200, 200]
canvas:
Color:
rgba: 0, 0, 0, 1
Ellipse
size: [198, 198]
pos: [600-200-100, 800-200-100]
Color:
rgba: 1, 1, 1, 1
Ellipse
size: [200, 200]
pos: [600-200-100, 800-200-100]
TextInput:
on_parent:self.focus = True
text: 'center of circle'
background_color: (0,0,0,0)
foreground_color: (0,0,0,1)
What I am trying to get to is very easily explained.
Essentially, there should be a menu bar running horizontally along the screen window (10% of total height and 100% of width). I believe I have done this.
The remaining 95% height should be the main canvas - I believe I have also done this.
The final part is getting a particular widget to be placed into the center of the main canvas. This is where I am stuck and would appreciate some help.
The widget I need to center is made up of two circles (one centered on top of the other, with one being slightly smaller than the other). Then, on top of the top-most circle should be a TextInput.
After taking the advice from this community, I stripped all of the logic back until I was left with just the basics (class definitions and layout), and was still having issues.
However, I have now learnt that what I assumed was a layout issue was likely not. A Kivy bug report last commented on in Nov 2018 (TextInput focus problem #3863), suggests there are unexplained instances where textinputs can lose focus. Below is the suggested workaround which also worked for me. Thanks for those that tried to help me.
from kivy.app import App
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.popup import Popup
from kivy.uix.textinput import TextInput
from kivy.lang import Builder
Builder.load_string('''
<Base>:
Button:
text: 'Press for Popup'
on_press: root.edit_text()
<TextInputX>
id: texter
keyboard_mode: 'managed'
is_focusable: True
focus: True
keyboard_on_key_down:
texter.show_keyboard()
''')
class Base(BoxLayout):
def __init__(self, **kwargs):
super(Base, self).__init__(**kwargs)
def edit_text(self, *args):
self.clear_widgets()
content = TextInputX()
# content.focus = True
popup = Popup(content=content)
popup.open()
class TextInputX(TextInput):
def __init__(self, **kwargs):
super(TextInputX, self).__init__(**kwargs)
# self.focus = True
class SampleApp(App):
def build(self):
return Base()
if __name__ == '__main__':
SampleApp().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 ...
To be more clearly, I want the text of the Switch changed from On/Off to Open/Close or Yes/No. I did not find out how to do it. Thanks.
You can hack this by changing the background image of the right rectangle in the Switch.
You can make an icon that is 83x32 pixels like the widget.
I Made a very ugly example on sumo an online photoeditor:
Then I saved it as images/icon.jpg
If you want to change the slider too, its 43x32 pixels.
from kivy.app import App
from kivy.uix.widget import Widget
from kivy.uix.switch import Switch
class MyGui(Widget):
def __init__(self,**kwargs):
super(MyGui,self).__init__(**kwargs)
self.switch = Switch()
self.switch.canvas.children[2].source = "images/icon.jpg" # The slider is the last element, so in that case -> children[-1].source
self.add_widget(self.switch)
class MyApp(App):
def build(self):
return MyGui()
if __name__ == '__main__':
MyApp().run()
If you want to do this in kv language, you could do like this:
from kivy.lang import Builder
Builder.load_string("""
<MyGui>:
Switch:
canvas:
Color:
rgb: 1,1,1
Rectangle:
source: 'switch.jpg' # make or download your background jpg
size: sp(83), sp(32)
pos: int(self.center_x - sp(41)), int(self.center_y - sp(16))
Rectangle:
source: 'switch_slider.jpg' # make or download your slider jpg
size: sp(43), sp(32)
pos: int(self.center_x - sp(41) + self.active_norm_pos * sp(41)), int(self.center_y - sp(16))
""")
class MyGui(Widget):
pass
The switch text is sadly not a text but an image. You can find the kv file here. You should be able to provide a different theme (see here) or you can override the class and implement your own rendering (which then displays OPEN/CLOSE) instead of the ON/OFF.
If you don't intend to change its whole appearance, you can patch the style.kv and use a little bit of canvas instructions to create a widget of your taste. ^.^
(and it's a lot more flexible than creating image for each different switch, if you don't want to change its color)
from kivy.lang import Builder
from kivy.base import runTouchApp
from kivy.uix.boxlayout import BoxLayout
Builder.load_string('''
<Custom#Switch>:
values: ['OFF', 'ON']
canvas:
Color:
rgb: 0.2, 0.709, 0.898, 1
Rectangle:
size: [sp(41.5), sp(20)]
pos: [self.center_x - sp(41.5), self.center_y - sp(10)]
Color:
rgb: 0.4, 0.4, 0.4, 1
Rectangle:
size: [sp(41.5), sp(20)]
pos: [self.center_x, self.center_y - sp(10)]
Label:
text: '[b]{}[/b]'.format(root.values[0])
markup: True
font_size: 13
pos: [root.center_x - sp(70), root.center_y - sp(50)]
Label:
color: 0.75, 0.75, 0.75, 1
text: '[b]{}[/b]'.format(root.values[1])
markup: True
font_size: 13
pos: [root.center_x - sp(30), root.center_y - sp(50)]
<Test>:
Custom:
Switch:
Custom:
values: ['Yes', 'No']
''')
class Test(BoxLayout): pass
runTouchApp(Test())
If you're using #el3in's solution make sure to clarify canvas.after instead of canvas otherwise the original switch image stays on top ;)