How do I change the color of my widget in Kivy at run time?

Andy picture Andy · Oct 21, 2012 · Viewed 16.4k times · Source

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!

Answer

qua-non picture qua-non · Oct 22, 2012

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.