I already posted before about the how to use transformations with Kivy. There was still some gaps in the explanation. After a while of being working with Kivy I have a much better understanding of what it is happening so I came with a list of 10 things that you should know about the Kivy
- A Kivy
canvasis not the space in which you paint.
The major problems I had at the beginning with the
canvaswere due to its name. Particularly considering all the buzz about HTML5
canvas. I initially visualize the
canvasas the cloth that painters paint. Since all the widgets has one
canvas, I started to get confused and asked why did the
Widgetseem to share the same canvas?
- A Kivy
canvasis a set of instructions!
canvasis 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
Widgetbasically keep the instructions to draw that performs the drawing.
- The widgets share the same coordinate space, not the same
The final answer to my previous question is that all the widgets have a different
canvasbut all the
canvasdraw 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
Widgetwe 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
RelativeLayoutincludes a Translate instruction:
Translate: xy: self.pos
That instruction is going to translate the
self.posposition. In other words, the (0,0) is now
PopMatrixto 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
canvasmay 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
canvasstop drawings. Kivy provides two instructions
PushMatrixbefore the instructions of the
PushMatrix: ... Translate: xy: self.pos ... PopMatrix:
PushMatrixsaves the current context state (rotations, translations, zoomings) and the
PopMatrixrecovers it and let thing as they were before.
- The color is an exception.
PopMatrixdoesn't have an effect over the
PushMatrix: Color: rgb: 1,0,0 Rectangle: ... PopMatrix: Line: ...
It doesn't matter which was the previous
Rectangleand and the
Linewould be red (rgb: 1,0,0).
- The order in which
canvasinstructions 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
canvas, the instructions of all the
canvasof the parent and grandparents were executed. Also the instructions of the
canvasof the first added siblings. The instructions of the
canvaschildren 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
What happens if we want to execute instructions after the children were added. In that case we have another
canvas.after) that is called after traverse all the children.
Widget: canvas: ... canvas.after: ... Button: canvas.before: ... canvas: ... canvas.after: ...
canvasexecution order is as follows:
(2) All the
Notice that all the
Buttonare executed in between
- What about
You probably notice that there is a third
canvas.before). It executes just before the
canvas. I found the
canvas.beforeparticularly useful when
(1) I need to manipulate the
Colorof an instance and, (2) I need to manipulate inheritance. Let's say I have
MyWidgetthat draws a
<MyWidget@Widget>: canvas: Rectangle: ...
So I can modify the
Colorof a particular instance doing something like this:
... mywidget = MyWidget mywidget.canvas.before: Color(rgba=(1,0,0,0))
If I use
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
MyWidgetand I would have the same problem if I just use the
canvas. Of course, similar applications goes for the
- Drawing on top of a
This works for any other
Widgetand not just for the
Labelbut it is probably the most common case. If you want to draw over the
Buttonbackground 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.
The answer needs, first, to clarify what is the
canvas? (2nd bullet point) and where is it that the widgets share? (3rd bullet point)
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.