- A Kivy
canvas
is not the space in which you paint.
The major problems I had at the beginning with the canvas
were due to its name. Particularly considering all the buzz about HTML5 canvas
. I initially visualize the canvas
as the cloth that painters paint. Since all the widgets has one canvas
, I started to get confused and asked why did the Widget
seem to share the same canvas?
The answer needs, first, to clarify what is the canvas
? (2nd bullet point) and where is it that the widgets share? (3rd bullet point)
- A Kivy
canvas
is a set of instructions!
A canvas
is basically a container of instructions. More like a recipe that tells you how to draw, rather than a place to draw. In other words the bitmap is not saved (like .bmp), but the vectors (like .gif). The canvas
of the Widget
basically keep the instructions to draw that performs the drawing.
- The widgets share the same coordinate space, not the same
canvas
.
The final answer to my previous question is that all the widgets have a different canvas
but all the canvas
draw in exactly the same drawing space, i.e. the coordinate space. Moreover, the drawing space is not limited to the position and size of the widget. The 0,0 of the drawing space is always (except for the RelativeLayout and the ScatterLayout ) the bottom-left corner. If we want to draw a Rectangle that covers the whole area of the Widget
we should do something like this:
...
Widget:
canvas:
Rectangle:
pos: self.pos
size: self.size
Notice that we need to specify explicitly that the size and pos of the Rectangle is the same of the Widget. There lies...
- ... the little secret of RelativeLayout.
The previous code doesn't work with RelativeLayout
. Instead we actually use the coordinate (0,0) to achieve the same result:
...
Widget:
canvas:
Rectangle:
pos: 0,0
size: self.size
The RelativeLayout
includes a Translate instruction:
Translate:
xy: self.pos
That instruction is going to translate the canvas
to the self.pos
position. In other words, the (0,0) is now self.pos
.
PushMatrix
and PopMatrix
to control the effects of sharing the same drawing space.
Sharing the same drawing space has its consequences. The problem is that the instructions of one Widget
canvas
may affect the next one. If I rotate the drawing space, the drawing space will be rotated until some other instructions rotate it back again. It is up to us to guarantee that the context of the drawing space remains the same when the canvas
stop drawings. Kivy provides two instructions PushMatrix
and PopMatrix
. RelativeLayout
uses a PushMatrix
before the instructions of the canvas
and a PopMatrix
after it.
PushMatrix:
...
Translate:
xy: self.pos
...
PopMatrix:
The PushMatrix
saves the current context state (rotations, translations, zoomings) and the PopMatrix
recovers it and let thing as they were before.
- The color is an exception.
That said, PushMatrix
and PopMatrix
doesn't have an effect over the Color
instruction.
PushMatrix:
Color:
rgb: 1,0,0
Rectangle:
...
PopMatrix:
Line:
...
It doesn't matter which was the previous Color
before PushMatrix
, the Rectangle
and and the Line
would be red (rgb: 1,0,0).
- The order in which
canvas
instructions are executed.
The execution order follow a traversal order. There is recursivity involved but, let's stick with what you have to consider. When you are using a Widget
canvas
, the instructions of all the canvas
of the parent and grandparents were executed. Also the instructions of the canvas
of the first added siblings. The instructions of the canvas
children hasn't been executed yet. Neither the ones of the siblings added after. When you are coding you actually perceive a normal top-down execution and thinking that way is ok for most of the cases. When you start using dynamic interfaces, adding and removing children, strange things seem to happen and you will need to remember the order of execution. Perhaps, more important is to know how to do to manipulate this order.
- Drawing after the children with
canvas.after
What happens if we want to execute instructions after the children were added. In that case we have another canvas
(called canvas.after
) that is called after traverse all the children.
Widget:
canvas:
...
canvas.after:
...
Button:
canvas.before:
...
canvas:
...
canvas.after:
...
The canvas
execution order is as follows:
(1) The Widget
canvas
(2) All the Button
canvas
(canvas.before
, canvas
, canvas.after
)
(3) The Widget
canvas.after
Notice that all the canvas
of the Button
are executed in between canvas
and canvas.after
.
- What about
canvas.before
?
You probably notice that there is a third canvas
(canvas.before
). It executes just before the canvas
. I found the canvas.before
particularly useful when
(1) I need to manipulate the Color
of an instance and, (2) I need to manipulate inheritance. Let's say I have MyWidget
that draws a Rectangle
inside.
<MyWidget@Widget>:
canvas:
Rectangle:
...
So I can modify the Color
of a particular instance doing something like this:
...
mywidget = MyWidget
mywidget.canvas.before:
Color(rgba=(1,0,0,0))
If I use canvas
instead of canvas.before
in RedWidget
, the instruction is going to be executed after the Rectangle, so it won't apply to the Rectangle.
(2) Similarly, when I need to manipulate inheritance. Let's say I have MyWidget that draws a Rectangle inside. I can do the following to change the color of the base class Rectangle:
<RedWidget@MyWidget>:
canvas.before:
Color:
rgb: 1,0,0
The RedWidget
inherits from MyWidget
and I would have the same problem if I just use the canvas
. Of course, similar applications goes for the canvas.after
.
- Drawing on top of a
Button
Widget
with canvas.after
.
This works for any other Widget
and not just for the Label
but it is probably the most common case. If you want to draw over the Button
background you can use the canvas.after
. Otherwise the background will cover your drawings:
Button:
text:
canvas.after:
Rectangle:
pos: self.pos
size: self.size
The rectangle of the previous code will actually cover its text.
This post turned out to be longer than I was expected. I hope I didn't make any mistake in my explanation. If so, please let me know.