Kivy: Change the background of ActionButton in ActionOverFlow - kivy

I want to change the background of the ActionButtons which are inside the ActionOverflow in an ActionBar, however using the usual approach for Buttons (background_down, background_normal) seems not to work.
[ActionButton with black background] (https://raw.githubusercontent.com/Thomas-Adams/kivy_mananger/master/color_mananger/kivy-actionbutton.png)
Strangely enough when i click on that specific ActionButton (for instance 'List') the background changes as desired. However the 'normal' not pressed state has still a black background. I also tried tampering with background-image property, but to no avail. What do I miss? Does anybody has a clue or hint about this?
#:import get_color_from_hex kivy.utils.get_color_from_hex
<MainView>:
...
ActionButton:
canvas.before:
Color:
rgba: get_color_from_hex('#d500f9')
Rectangle:
size: self.size
pos: self.pos
background_normal: 'electric-violet.png'
background_down: 'electric-violet-lighten.png'
background_disabled: ''
background_disabled_down: ''
background_color: get_color_from_hex('#d500f9')
text: 'Create'
Here's the link to the complete kv file

Another possibility is to create your own class based on ActionButton:
class MyActionButton(ActionButton):
real_background_normal = StringProperty('')
And modify its style in your kv file:
<MyActionButton>:
-background_normal: self.real_background_normal
Then elsewhere in your kv file, when you use MyActionButton, just use real_background_normal instead of background_normal:
MyActionButton:
real_background_normal: 'electric-violet.png'

I figured it out by myself, in case anybody else is stuck here, instead of using the ActionButton widget i wrote my own widget which is based on ActionItem and Button :
class ColorActionButton(ActionItem, Button, EventDispatcher):
color_name = StringProperty()
base_dir = IMAGE_BASE_DIR
b_normal = StringProperty()
b_down = StringProperty()
b_disabled_down = StringProperty()
b_disabled = StringProperty()
def __init__(self, **kwargs):
super().__init__(**kwargs)
def on_color_name(self, instance, value):
self.b_normal = self.base_dir + value + SUFFIX
self.b_down = self.base_dir + value + LIGHTEN_SUFFIX
self.b_disabled_down = self.base_dir + value + DARKEN_SUFFIX
Using this now works like a charm.

Related

Kivy text input autocompleting Wikipedia addresses

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.

kivy: How to change Switch default from ON/OFF to OPEN/CLOSE or to YES/NO?

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 ;)

Highlight a Kivy ToggleButton with mouse hovering

I am currently coding a GUI with Kivy. I need to modify a ToggleButton behaviour so that it is highlighted when hovered by the mouse. Here is my code so far:
class FilterToggle(ToggleButton):
def __init__(self, **kwargs):
Window.bind(mouse_pos=self.on_mouse_pos)
super(FilterToggle, self).__init__(**kwargs)
def on_mouse_pos(self, *args):
pos = args[1]
if self.collide_point(*pos):
print("I am on the good path!)
Here is my .kv file:
<FilterToggle>:
text_size: self.width - 20, None
valign: 'middle'
halign: 'left'
markup: True
.
.
.
FilterToggle:
text: "This is just to illustrate"
The on_mouse_pos() function never prints anything, as self.collide_point(*pos) always returns "False". I found that self.pos gives me the coordinates of all my FilterToggle widgets, so there is obviously an issue with my code.
Thanks a lot for helping a Python beginner!

remove_widget in kivy doesn't remove widget

I'm having trouble removing a widget using remove_widget in kivy
I either get TypeError: 'Label' object has no attribute 'getitem' error
or it just doesn't remove the label that was created.
I think I'm not properly referring to the widget made but not sure how to...
Builder.load_string("""
<MenuScreen>:
FloatLayout:
#cols: 2
#rows: 2
size: 800,480
Button:
id: resetmash
text: "Reset"
font_size: 30
size_hint: None, None
background_color: 1,0.88,0.882,1
size: 100,100
pos: 1450,800
on_press: root.resetmash()
Button:
id: btn_0
text: "+"
size_hint: None, None
size: 100,100
pos: 550,700
on_press: root.listmashsteps()
""")
class MenuScreen(Screen):
def resetmash(self):
self.remove_widget(Label())
def listmashsteps(self, *largs):
self.add_widget(Label(text="Step"))
def __init__(self, **kwargs):
super(MenuScreen, self).__init__(**kwargs)
sm = ScreenManager()
menu_screen = MenuScreen(name='menu')
sm.add_widget(menu_screen)
class TestApp(App):
def build(self):
return sm
if __name__ == '__main__':
TestApp().run()
self.remove_widget(Label())
This doesn't make sense - you just instantiated that Label, so it isn't already added to the widget, so you can't remove it.
Instead store a reference to the Label when you add it, something like:
self.label = Label(text='Step')
self.add_widget(self.label)
Then later:
self.remove_widget(self.label)

Combining image and text within a button in kivy

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)

Resources