Is it possible to fully customize the Kivy slider? Specifically, how do you change the slider background color, add a slider "fill" color, and customize the knob image/color?
I've been struggling with the all week since there is not much on Kivy style customization online, so I decided to post this question/answer here for others. It turns out you need to essentially rewrite the Kivy style class for Sliders.
Using the default style sheet from Kivy's GitHub repo, I found the default style Slider class. I then created Color and BorderImage children of the canvas childer under the Slider class, copying the existing childen to start with. Remember that Color applies to the next children, so you would then need a total of 5 children under canvas if you are completely modifying the Slider. Also, I had to modify the size attribute of the BorderImage so that it dynamically changes as you move the knob.
If you are just interested in adding a "fill" color, here is my code:
from kivy.lang import Builder
Builder.load_string('''
<Slider>:
canvas:
Color:
rgb: 0, 0, 0
BorderImage:
border: (0, 18, 0, 18) if self.orientation == 'horizontal' else (18,
0, 18, 0)
pos: (self.x + self.padding, self.center_y - sp(18)) if self.orienta
tion == 'horizontal' else (self.center_x - 18, self.y + self.padding)
size: (max(self.value_pos[0] - self.padding * 2 - sp(16), 0), sp(36)
) if self.orientation == 'horizontal' else (sp(36), max(self.value_pos[1] - self
.padding * 2 - sp(16), 0))
source: 'atlas://data/images/defaulttheme/slider{}_background{}'.format(self.orientation[0], '_disabled' if self.disabled else '')
''')
Of course, you could just as easily move the string to a .kv file. Let me know if you need samples for the other two customizations, but they can be done similarly.
Related
This is a part of a python-script running kivy:
class someclass(Widget):
# code
# code
Clock.schedule_interval(self.timeandlog, 0.1)
self.x = 20
def timeandlog(self,dt):
if condition == True: #
self.ids.pofchild.add_widget(Label(text=logmsg, pos = (10, self.x)))
self.x = self.x + 10 ### just playing with position
condition = False
kv file:
<someclass>
#somelabels and buttons:
ScrollView:
do_scroll_x: False
do_scroll_y: True
pos: root.width*0.3, root.height*0.7
size: root.width*0.8, root.height*0.7
Widget:
cols: 1
spacing: 10
id: pofchild
Now I know the ScrollView accepts one Widget so I added just one with an id: pofchild then I added labels inside it with self.ids.pofchild.add_widget(Label() and changing every new label's pos with pos=(20, self.x) but the labels are not scrollable and only fill the widget height then stop appearing. What are right attributions so they will be scrollable?
In general, when you want a Widget to contain other Widgets, you should use a Layout Widget. A simple Widget does not honor size_hint or pos_hint, so the children of a simple Widget often end up with the default size of (100,100) and the default position of (0,0).
So, a good start is to change:
class someclass(Widget):
to something like:
class Someclass(FloatLayout):
Note that the class name starts with a capital letter. Although it does not cause any difficulties in your example, it can produce errors when you use kv and your classname starts with lower case.
Similarly, the child of the ScrollView is also normally a Layout. A possibility is GridLayout, like this:
GridLayout:
size_hint_y: None
height: self.minimum_height
cols: 1
spacing: 10
id: pofchild
Keys attributes here are the size_hint_y: None and height: self.minimum_height. They allow the GridLayout to grow as more children are added, and its height will be calculated as the minimum height needed to contain the children.
Then, you can add children like this:
self.ids.pofchild.add_widget(Label(text=logmsg, pos=(10, self.x), size_hint_y=None, height=50))
Since we are expecting the GridLayout to calculate its minimum height, we must provide an explicit height for its children, thus the size_hint_y=None, height=50.
As a beginner python learner, I am trying to create this simple app using kivy to change the thickness of a rectangle through various inputs. Firstly, I had tried to do it using buttons, and with some help from this community managed to make it work.
Now that this problem got solved, I thought of taking it to the next level by using the on_touch_move function to slide on the screen to change thickness. But have again stumbled across a new problem.
When I run this code, there is no error, also the boundary_thickness_x and boundary_thickness_y are getting updated (tested using print). But the size (thickness) of the widgets is not updating in the window.
I wonder what mistake I might be doing?
**main.py**
from kivy.app import App
from kivy.uix.widget import Widget
from kivy.properties import NumericProperty, ObjectProperty
class Boundary(Widget):
boundary_thickness_x = NumericProperty(10)
boundary_thickness_y = NumericProperty(10)
def on_touch_move(self, touch):
x = touch.x/self.width * 100
y = touch.y/self.height * 100
boundary_thickness_x = x
boundary_thickness_y = y
#print(boundary_thickness_x, boundary_thickness_y)
class BounceApp(App):
def build(self):
return Boundary()
BounceApp().run()
**bounce.kv**
<Boundary>
canvas:
Rectangle:
pos : 0, 0
size: self.boundary_thickness_x, root.height
Rectangle:
pos : 0, 0
size: root.width, self.boundary_thickness_y
Rectangle:
pos : root.width - self.boundary_thickness_x, 0
size: self.boundary_thickness_x, root.height
Rectangle:
pos : 0, root.height - self.boundary_thickness_y
size: root.width, self.boundary_thickness_y
Your on_touch_move() method is not adjusting the correct properties. It is only adjusting a couple local variables. Just change that method to:
def on_touch_move(self, touch):
x = touch.x / self.width * 100
y = touch.y / self.height * 100
self.boundary_thickness_x = x
self.boundary_thickness_y = y
You must use self. to reference the properties.
I need help in changing the background color of the treeview on kivy.
I am working on the kivy framework in python that will list some labels.
But what happens while executing the application is, my apps background color is white and the tree-view gets the background-color from the application background.
Below is the sample-screenshot
Sample Code: To create tree view.
list_label=TreeView(root_options=dict(text='My root label'),hide_root=False)
list_label.add_node(TreeViewLabel(text='My first item'))
Add the following to your .py:
Builder.load_string('''
<TreeView>:
canvas.before:
Color:
rgba: 1, 0, 0, 1
Rectangle:
pos: self.pos
size: self.size
''')
This changes the background to red. You can replace 1, 0, 0, 1 with any color you prefer.
You can do this entirely in Python, but you will need to manually create bindings that kv creates for you automatically:
list_label=TreeView(root_options=dict(text='My root label'),hide_root=False)
with list_label.canvas.before:
Color(1, 0, 0, 1)
self.background_rect = Rectangle()
list_label.bind(pos=self.adjust_rect_pos)
list_label.bind(size=self.adjust_rect_size)
def adjust_rect_size(self, treeview, new_size):
self.background_rect.size = new_size
def adjust_rect_pos(self, treeview, new_pos):
self.background_rect.pos = new_pos
EDIT: I kind of answered the wrong question, my solution below changes the color of the even/odd nodes which overlay the background. I'll leave it here in case it's helpful.
Original Answer:
There are a couple ways to skin this cat. The easiest is to use the even_color and odd_color properties of the TreeViewNode widget. Here's how you would use that in your case:
list_label=TreeView(root_options=dict(text='My root label'),hide_root=False)
list_label.add_node(TreeViewLabel(text='My first item', \
even_color=[0.5,0.1,0.1,1],odd_color=[0.1,0.1,0.5,1]))
It would definitely be more DRY to create your own custom widget which is equally easy:
from kivy.uix.treeview import TreeViewLabel
from kivy.uix.button import Button
class MyTreeViewLabel(Button, TreeViewLabel):
def __init__(self, **kwargs):
super(MyTreeViewLabel, self).__init__(**kwargs)
self.even_color = [0.5,0.1,0.1,1]
self.odd_color = [0.1,0.1,0.5,1]
Then, your code would just be:
list_label=TreeView(root_options=dict(text='My root label'),hide_root=False)
list_label.add_node(MyTreeViewLabel(text='My first item'))
I want to draw a border around a ScrollView in my Kivy app. The problem is that the content of the ScrollView overlap that border since I'm drawing it inside the widget.
So I'm wondering if one of those is a possible solution:
How can I draw outside the widget's boundaries?
When I tried to move a part of a canvas element outside the widget, it simply cut off that part, which is no surprise. Perhaps I could make another widget outside this one and draw on it?
How can I limit the content of the ScrollView? So, can I change the scroll boundaries? What I mean is that I don't want the children to go beyond a certain point in the widget to make them not touch the border
Here is some test code to demonstrate the issue. It's a slightly modified official example. The buttons overlap the green border when scrolling, which is what I don't want:
from kivy.app import App
from kivy.lang import Builder
from kivy.uix.button import Button
from kivy.uix.scrollview import ScrollView
from kivy.uix.gridlayout import GridLayout
Builder.load_string('''
<ScrollView>:
canvas:
Color:
rgba: 1, 1, 1, 1
Rectangle:
pos: self.pos
size: self.size
Color:
rgba: 0, 1, 0, 1
Line:
points: self.x, self.y + self.height,\
self.x + self.width, self.y + self.height,\
self.x + self.width, self.y, self.x, self.y,\
self.x, self.y + self.height
width: 1.2
''')
class TestApp(App):
def build(self):
layout = GridLayout(cols=1, padding=10, spacing=10,
size_hint=(None, None), width=500)
layout.bind(minimum_height=layout.setter('height'))
for i in range(30):
btn = Button(text=str(i), size=(480, 40),
size_hint=(None, None))
layout.add_widget(btn)
root = ScrollView(size_hint=(None, None), size=(500, 320),
pos_hint={'center_x': .5, 'center_y': .5}, do_scroll_x=False)
root.add_widget(layout)
return root
TestApp().run()
How can I draw outside the widget's boundaries?
When I tried to move a part of a canvas element outside the widget, it simply cut off that part, which is no surprise. Perhaps I could make another widget outside this one and draw on it?
Don't really have a clue what you mean by it. canvas has no boundaries, except if you use Stencil. When you try to draw outside of ScrollView, content out of the widget's space will be hidden, because originally it uses Stencil so that the unnecessary content is hidden and you could scroll through it as you can see here.
Now about that overlaping. canvas.after before the second Color makes it draw after the widgets are drawn. Similar to canvas.before, which is used for backgrounds.
canvas:
Color:
rgba: 1, 1, 1, 1
Rectangle:
pos: self.pos
size: self.size
canvas.after:
...
Basic canvas is drawn in the "same" time as the widgets are, therefore the item that is drawn later will be on the top no matter what you do. after will draw after all is drawn again with that order and before does the same thing before the widgets are even drawn at all.
How can I limit the content of the ScrollView? So, can I change the scroll boundaries? What I mean is that I don't want the children to go beyond a certain point in the widget to make them not touch the border
Use layout with fixed size that handles it's children's pos e.g. FloatLayout with pos_hint or BoxLayout depends on your need, therefore it will push the widgets inside to imaginary rectangle, which is basically [0, 0] to [width, height] i.e. size of your layout(if you use it right).
How do I dynamically resize the a label or button, in particular, the text_size and height, depending on the amount of text, at run-time?
I am aware that this question has already been answered in one way with this question:
Dynamically resizing a Label within a Scrollview?
And I reflect that example in part of my code.
The problem is dynamically resizing the labels and buttons at run-time. Using, for example:
btn = Button(text_size=(self.width, self.height), text='blah blah')
...and so on, only makes the program think (and logically so) that the "self" is referring to the class which is containing the button.
So, how do I dynamically resize these attributes in the python language, not kivy?
My example code:
import kivy
kivy.require('1.7.2') # replace with your current kivy version !
from kivy.app import App
from kivy.uix.screenmanager import ScreenManager, Screen
from kivy.properties import ObjectProperty
from kivy.uix.button import Button
from kivy.uix.gridlayout import GridLayout
i = range(20)
long_text = 'sometimes the search result could be rather long \
sometimes the search result could be rather long \
sometimes the search result could be rather long '
class ButtonILike(Button):
def get_text(self):
return long_text
class HomeScreen(Screen):
scroll_view = ObjectProperty(None)
def __init__(self, *args, **kwargs):
super(HomeScreen, self).__init__(*args, **kwargs)
layout1 = GridLayout(cols=1, spacing=0, size_hint=(1, None), \
row_force_default=False, row_default_height=40)
layout1.bind(minimum_height=layout1.setter('height'),
minimum_width=layout1.setter('width'))
layout1.add_widget(ButtonILike())
for result in i:
btn1 = Button(font_name="data/fonts/DejaVuSans.ttf", \
size_hint=(1, None), valign='middle',)#, \
#height=self.texture_size[1], text_size=(self.width-10, None))
btn1.height = btn1.texture_size[1]
btn1.text_size = (btn1.width-20, layout1.row_default_height)
btn1.text = long_text
btn2 = Button(font_name="data/fonts/DejaVuSans.ttf", \
size_hint=(1, None), valign='middle')
btn2.bind(text_size=(btn2.width-20, None))
btn2.text = 'or short'
layout1.add_widget(btn1)
layout1.add_widget(btn2)
scrollview1 = self.scroll_view
scrollview1.clear_widgets()
scrollview1.add_widget(layout1)
class mybuttonsApp(App):
def build(self):
return HomeScreen()
if __name__ == '__main__':
mybuttonsApp().run()
And the kv file:
#:kivy 1.7.2
<ButtonILike>:
text_size: self.width-10, None
size_hint: (1, None)
height: self.texture_size[1]
text: root.get_text()
#on_release: root.RunSearchButton_pressed()
<HomeScreen>:
scroll_view: scrollviewID
AnchorLayout:
size_hint: 1, .1
pos_hint: {'x': 0, 'y': .9}
anchor_x: 'center'
anchor_y: 'center'
Label:
text: 'Button Tester'
ScrollView:
id: scrollviewID
orientation: 'vertical'
pos_hint: {'x': 0, 'y': 0}
size_hint: 1, .9
bar_width: '8dp'
You can see that I added the button from the kv file which displays all the behavior that I want at the top of the list. Resize your window while running it, and you can see the magic. And, of course, changing the text_size also makes it possible for me to align text.
I simply have not been able to achieve the same behavior on the python side. My app requires that the buttons be created at run-time. I think the answer might lie with "bind()", though admittedly, I'm not sure I used it correctly in my attempts or that I understand it fully. You can see that I tried with "btn2", which I thought would've thrown the text to the left (since halign defaults to left), but didn't seem to do anything.
I appreciate the help.
I think the best way is to set Label's/Button's size to texture_size:
Label:
text: "test"
size_hint: None, None
size: self.texture_size
canvas.before: # for testing purposes
Color:
rgb: 0, 1, 0
Rectangle:
pos: self.pos
size: self.size
My answer is slightly different from #martin's - I only want to modify the height.
def my_height_callback(obj, texture: Texture):
if texture:
obj.height = max(texture.size[1], 100)
class MyButton(Button):
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.size_hint = (1, None)
self.bind(texture=my_height_callback)
When the text is rendered the texture property of the button gets set. That texture's height is then pushed to the button's height via the callback. Calling max() allows for a minimum height to be set. This works fine with labels as well.
btn2.bind(text_size=(btn2.width-20, None))
As with your other question, the problem is that you have the syntax of bind wrong. You must pass a function, but you just wrote a tuple, and bind can't do anything useful with that - it certainly doesn't know you happened to write btn2.width there.
Also, the syntax is that bind calls the function when the given property changes. That's the opposite of what you want - you need to change the text_size when btn2.width changes, not call a function when text_size changes
I think something like the following would work. instance and value are the default arguments we ignored in the other question.
def setting_function(instance, value):
btn2.text_size = (value-20, None)
btn1.bind(width=setting_function)
I was looking to resize both the text_size width and height, the latter specifically with regard to the documented behaviour of kivy.Label that vertical alignment of text in a label cannot be achieved without doing this first. Further, I needed to do it in python, not .kv.
class WrappedVAlignedLabel(Label):
def __init__(self,**kwargs):
super().__init__(**kwargs)
self.bind(height=lambda *x:self.setter('text_size')(self, (self.width, self.height)))
strangely, binding on width instead of height would only set text_size[0], I guess due to some order of rendering self.height wasn't yet computed, so the setting of text_size[1] wasn't happening. Whereas binding on height gets them both.