I'm having trouble changing the color of a simple widget in Kivy. I can set the color when I create the widget, but I can't change it afterwards.
Here is the simple layout definition file circletest.kv
. It defines a circle where the color (actually just the r, from rgba), position and size are all linked to variables in the widget class.
#:kivy 1.4.1
<CircleWidget>:
canvas:
Color:
rgba: self.r,1,1,1
Ellipse:
pos: self.pos
size: self.size
Here's the application circletest.py
. It creates and displays the simple widget. The color and position are successfully set when the object is created. When the widget is clicked the widget can change it's own position, but when I try to change the color nothing happens.
import kivy
kivy.require('1.4.1')
from kivy.app import App
from kivy.lang import Builder
from kivy.uix.widget import Widget
Builder.load_file('circletest.kv')
class CircleWidget(Widget):
def __init__(s, **kwargs):
s.size= [50,50]
s.pos = [100,50]
s.r = 0
super(CircleWidget, s).__init__(**kwargs)
def on_touch_down(s, touch):
if s.collide_point(touch.x,touch.y):
s.pos = [s.pos[1],s.pos[0]] # This works
s.r = 1.0 # <---- This does nothing!
class TestApp(App):
def build(s):
parent = Widget()
w = CircleWidget()
parent.add_widget(w)
return parent
if __name__ == '__main__':
TestApp().run()
Can anyone see the problem?
UPDATE
Still not sure what the answer to this question is, but I do have a work around:
In the .kv file I pointed the color to a variable in my object. Works for extracting the initial color:
Color:
rgba: self.col
When I want to change the color from the .py file I loop through all the instructions in the canvas and modify the first one of type "Color". Obviously this is a hack, and won't work on widgets with more than one Color:
property:
for i in s.canvas.get_group(None):
if type(i) is Color:
i.r, i.g, i.b, i.a = v
break
I wrapped that all up in a property so it's neat to use:
class CircleWidget(Widget):
def get_col(s):
return s._col
def set_col(s,v):
for i in s.canvas.get_group(None):
if type(i) is Color:
i.r, i.g, i.b, i.a = v
break
s._col = v
col = property(get_col, set_col)
def __init__(s, **kwargs):
s.size= [50,50]
s.pos = [100,50]
s._col = (1,1,0,1)
super(CircleWidget, s).__init__(**kwargs)
def on_touch_down(s, touch):
if s.collide_point(touch.x,touch.y):
s.col = (s.col[::-1]) # Set to some other color
Seems to work for now. Please let me know if you know a better way of doing this. I'm sure there must be a simpler way, and that I'm missing something obvious!
The answer by tshirtman is correct, here is the explanation of what's going on.
In your kv file when you set
<CircleWidget>:
canvas:
Color:
rgba: self.r, 1, 1, 1
Ellipse:
pos: self.pos
size: self.size
The line rgba: self.r, 1, 1, 1
tries to update the value of rgba
whenever there is a change to the value of r
. This is done implicitly in kv language by binding, which can be done on a kivy Property as it implements a Observer Pattern.
The variable r
in your code was updated but it's just a variable that doesn't provide any indication that it's value has changed and can not be bound to. If you notice your changes to pos
work because pos
is a ReferenceListProperty.
General rule for programming in Kivy, if you want to change code depending on a property of a Widget/Object use a Kivy Property. It provides you the option to Observe Property changes and adjust your code accordingly either explicitly through bind/on_property_name events or implicitly through kv language as mentioned above.