Please refer to the detail explanations and example.
Overload method __init__
def __init__(self, **kwargs):
super(Paint, self).__init__(**kwargs)
We overload the method __init__() so as to add widgets and to define
their behavior. One should not forget to call super in order to
implement the functionality of the original class being overloaded.
Also note that it is good practice not to omit the **kwargs while
calling super, as they are sometimes used internally.
Paint Class
1. Added Kivy Properties
Since you will be accessing palette and contents, we declared the following:
from kivy.properties import ObjectProperty, ListProperty
class Paint(Widget):
palette = ObjectProperty(None)
contents = ListProperty([])
2. on_touch_down Event
By default, touch events are dispatched to all currently displayed widgets. This means that widgets receive the touch event whether it occurs within their physical area or not. Kivy dispatches the touch event to all widgets and lets them decide how to react to them. If you only want to respond to touch events inside the widget/button, you have to check it using self.collide_point method. When it collides, you should get only one widget/button.
In the following snippet, we override the on_touch_down() method of the Widget class. Here, we check for collision of the touch with our widget.
If the touch falls inside our widget, we create a square on the canvas and return True, indicating that we have consumed the touch and don't want it to propagate any further.
Finally, if the touch falls outside our widget, we call the original event using super(Paint, self).on_touch_down(touch) and return the result. This allows the touch event propagation to continue as it would
normally have occurred.
def on_touch_down(self, touch):
if self.collide_point(*touch.pos):
color = self.palette.act_col
with self.canvas:
Color(color[0], color[1], color[2])
sqr = Rectangle(pos=(touch.x, touch.y), size=(20, 40))
self.contents.append(sqr)
return True
return super(Paint, self).on_touch_down(touch)
Palette Class
3. act_col - ListProperty
Since act_col will be accessed in class Paint, we declared it as a Kivy ListProperty.
act_col = ListProperty([1, 0, 0])
4. Nested BoxLayout - Removed
Since Palette class is a BoxLayout, we don't need a nested BoxLayout. Therefore it is removed.
5. on_press Event
The only argument passed to the callback is the object i.e. button, which we have bound to.
def SetColor(self, btn):
self.act_col = self.licol[int(btn.id)]
def ClearContents(self, btn):
MyPaintApp Class
6. build method
Instead of putting everything in the build method of class MyPaintApp, we have moved them into class Painting. We have also declared ObjectProperty to hook up to class palette and class paint.
class Painting(BoxLayout):
palette = ObjectProperty(None)
paint = ObjectProperty(None)
def __init__(self, **kwargs):
super(Painting, self).__init__(**kwargs)
self.orientation = "vertical"
self.palette = Palette()
self.paint = Paint(palette=self.palette)
self.add_widget(self.paint)
self.add_widget(self.palette)
Example
main.py
from kivy.app import App
from kivy.uix.widget import Widget
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.button import Button
from kivy.graphics import Color, Rectangle
from kivy.properties import ObjectProperty, ListProperty
class Paint(Widget):
palette = ObjectProperty(None)
contents = ListProperty([])
def __init__(self, palette, **kwargs):
# make sure we aren't overriding any important functionality
super(Paint, self).__init__(**kwargs)
self.palette = palette
def on_touch_down(self, touch):
"""
We override the on_touch_down() method of the Widget class. Here, we check for
collision of the touch with our widget.
If the touch falls inside our widget, we create a square on the canvas and return True,
indicating that we have consumed the touch and don't want it to propagate any further.
Finally, if the touch falls outside our widget, we call the original event using super(...)
and return the result. This allows the touch event propagation to continue as it would
normally have occurred.
:param touch:
:return:
"""
if self.collide_point(*touch.pos):
color = self.palette.act_col
with self.canvas:
Color(color[0], color[1], color[2])
sqr = Rectangle(pos=(touch.x, touch.y), size=(20, 40))
self.contents.append(sqr)
return True
return super(Paint, self).on_touch_down(touch)
class Palette(BoxLayout):
act_col = ListProperty([1, 0, 0])
def __init__(self, **kwargs):
super(Palette, self).__init__(**kwargs)
self.licol = []
self.licol.append((1, 0, 0))
self.licol.append((0, 1, 0))
self.size_hint = (1, None)
self.height = 50
for i in range(0, len(self.licol)):
but = Button(id=str(i))
col = self.licol[i]
but.background_normal = ''
but.background_color = (col[0], col[1], col[2], 1)
but.bind(on_press=self.SetColor)
self.add_widget(but)
but = Button(text="<--", on_press=self.ClearContents)
self.add_widget(but)
# The only argument passed to the callback is the
# object (i.e. button) which we have bound to
def SetColor(self, btn):
self.act_col = self.licol[int(btn.id)]
# The only argument passed to the callback is the
# object (i.e. button) which we have bound to
def ClearContents(self, btn):
if len(self.parent.paint.contents) != 0:
# list.pop() - removes and returns the last item in the list
self.parent.paint.canvas.remove(self.parent.paint.contents.pop())
class Painting(BoxLayout):
palette = ObjectProperty(None)
paint = ObjectProperty(None)
def __init__(self, **kwargs):
super(Painting, self).__init__(**kwargs)
self.orientation = "vertical"
self.palette = Palette()
self.paint = Paint(palette=self.palette)
self.add_widget(self.paint)
self.add_widget(self.palette)
class MyPaintApp(App):
def build(self):
return Painting()
if __name__ == '__main__':
MyPaintApp().run()
Output
