Petar Luketina
Petar Luketina

Reputation: 449

How to assign a canvas to multiple widgets in Kivy

I would like to give all of the widgets on the screen a white canvas. I know how to create the intended canvas in KV but in this case I must use Python code.

In my code, I tried self.cavas.add(...) and in the code below, I use with self.canvas:. Both attempts resulted in a canvas being drawn in the corner of the screen, rather than inside of the widgets.

How do I put a canvas to be inside of every widget using Python code?

Code:

from kivy.app import App
from kivy.core.window import Window
from kivy.graphics.context_instructions import Color
from kivy.graphics.vertex_instructions import Rectangle
from kivy.lang import Builder
from random import random
from kivy.uix.button import Button
from kivy.animation import Animation
from kivy.clock import Clock

class LittleButtons(Button):

    dur = 2

    def reup(self, *args):

        Animation.cancel_all(self)
        Animation(pos_hint = {'center_x': random(), 'center_y': random()}, duration = self.dur).start(self)

    def __init__(self, **kwargs):

        super(LittleButtons, self).__init__(**kwargs)
        self.pos_hint = {'center_x': random(), 'center_y': random()}
        self.size_hint = None, None
        self.width = random() * (Window.width / 20)
        self.height = self.width
        self.background_color = [0,0,0,.05]
        with self.canvas:
            Color(rgba = [1,1,1,.2])
            Rectangle(pos = self.pos, size = self.size)

        Animation(pos_hint = {'center_x': random(), 'center_y': random()}, duration = self.dur).start(self)
        Clock.schedule_interval(self.reup, self.dur)

KV = Builder.load_string("""
#:import Factory kivy.factory.Factory

Screen:
    FloatLayout:
        on_parent:
            (lambda ltext: [self.add_widget(Factory.LittleButtons(text=ltext)) for i in range (150)])('hi!')
        Button:
            background_color: 0,0,0,0
            canvas:
                Color:
                    rgba: 0,1,1,1
                Rectangle:
                    pos: self.pos
                    size:self.size
""")

class MyApp(App):
    def build(self):
        return KV

if __name__ == '__main__':
    MyApp().run()

Upvotes: 2

Views: 1120

Answers (1)

John Anderson
John Anderson

Reputation: 38822

The problem is that in the LittleButtons __init__() method, its position is still the default of (0,0), so the position of the Rectangle is set to (0,0). When you use KV, it cleverly binds the Rectangle position to the LittleButtons position when you reference self.pos. Unfortunately, in a .py file, you must provide that binding yourself. So, here is a modification of your LittleButtons that should handle the position changes:

class LittleButtons(Button):

    dur = 2

    def reup(self, *args):

        Animation.cancel_all(self)
        Animation(pos_hint = {'center_x': random(), 'center_y': random()}, duration = self.dur).start(self)

    def __init__(self, **kwargs):
        self.rect = None
        super(LittleButtons, self).__init__(**kwargs)
        self.pos_hint = {'center_x': random(), 'center_y': random()}
        self.size_hint = None, None
        self.width = random() * (Window.width / 20)
        self.height = self.width
        self.background_color = [0,0,0,.05]
        with self.canvas:
            Color(rgba = [1,1,1,.2])
            self.rect = Rectangle(pos = self.pos, size = self.size)

        Animation(pos_hint = {'center_x': random(), 'center_y': random()}, duration = self.dur).start(self)
        Clock.schedule_interval(self.reup, self.dur)


    def on_pos(self, instance, pos):
        if self.rect is not None:
            self.rect.pos = pos

The changes are the addition of a self.rect attribute and a on_pos method.

You can add something similar if you need to handle changing size.

Upvotes: 2

Related Questions