Related
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 ...
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])
What's the preferred way to combine an image/icon and text within a button? For example, how would you create a button with text = 'my button', and a graphical icon to the left of that text?
Regarding to question #2.
The way Kivy works is embedding Widget instances. Since Image and Button are subclasses of Widget, then all you have to do is embed an Image inside a the Button. Notice that the positioning inside a widget is fixed. You have to give explicit coordinates.
That said, you can always embed a Layout to organize the stuff you are putting inside the Button.
Here is the simple ex
from kivy.app import App
from kivy.uix.boxlayout import BoxLayout
from kivy.lang import Builder
Builder.load_string("""
<ButtonsApp>:
orientation: "vertical"
Button:
text: "B1"
Image:
source: 'kivy.png'
y: self.parent.y + self.parent.height - 200
x: self.parent.x
Label:
text: "A label"
""")
class ButtonsApp(App, BoxLayout):
def build(self):
return self
if __name__ == "__main__":
ButtonsApp().run()
EDIT: An example of how a relative layout can be embedded inside a button
In this case I am using a StackLayout to organize an Image and a Label inside. As I said, Button is a Widget and Kivy works embedding widgets inside widgets. It doesn't matter if they are labels, buttons or layouts.
from kivy.app import App
from kivy.uix.boxlayout import BoxLayout
from kivy.lang import Builder
Builder.load_string("""
<ButtonsApp>:
orientation: "vertical"
Button:
StackLayout:
pos: self.parent.pos
size: self.parent.size
orientation: 'lr-tb'
Image:
source: 'kivy.png'
size_hint_x: None
width: 74
Label:
size_hint_x: None
width: 100
text: "The text"
Label:
text: "A label"
""")
class ButtonsApp(App, BoxLayout):
def build(self):
return self
if __name__ == "__main__":
ButtonsApp().run()
Meanwhile, there is another way.
You can use icon fonts such as Font Awesome and combine them with text.
Either import the font directly and wrap the text in their font tag, or simply use some of the libraries that take care of that.
#: import icon ...
Button:
markup: True
text: "%s Comment" % icon('comment', 32)
size_hint_x: None
width: 100
Kivy-iconfonts converts css/tff combinations that are distributed for web sites into a json format that it loads during runtime using the import statement as shown in the example above.
I extended this in my fork to fetch Font Awesome icons during runtime and put them into the working directory of your application. That gives you the advantage of not having to distribute the fonts with the application.
Define a new Button class or modify the one in Kivy.uix.button
class MyButton(Button):
#add these three properties in the class
icon = ObjectProperty(None)
icon_size = (0,0)
icon_padding = NumericProperty(0) #Enter any default value like 50 if you will
#always use an icon, or specify this field
#while creating the button
def __init__(self, **kwargs):
#no changes here, just for reference
return super(MyButton, self).__init__(**kwargs)
KV file:
<MyButton>:
state_image: self.background_normal if self.state == 'normal' else self.background_down
disabled_image: self.background_disabled_normal if self.state == 'normal' else self.background_disabled_down
canvas:
Color:
rgba: self.background_color
BorderImage:
border: self.border
pos: self.pos
size: self.size
source: self.disabled_image if self.disabled else self.state_image
Color:
rgba: (1, 1, 1, 1) if root.icon != None else (1,1,1,0)
Rectangle:
source: root.icon
size: (self.texture_size[1],self.texture_size[1]) if self.icon_size == (0,0) else self.icon_size
pos: int(self.center_x - self.texture_size[0] / 2.)-dp(root.icon_padding), int(self.center_y - self.texture_size[1] / 2.)
Color:
rgba: 1, 1, 1, 1
Rectangle:
texture: self.texture
size: self.texture_size
pos: int(self.center_x - self.texture_size[0] / 2.)+dp(root.icon_padding), int(self.center_y - self.texture_size[1] / 2.)
Now just create a button widget
MyButton(text = 'Hello',icon = 'icon.png', icon_padding = 50)