Fixed position of any Scatter child widget in Kivy

Just in case you were wondering how one could go about having any child widget of a Kivy Scatter widget that would stick in a single window-relative position whilst the scatter itself was being translated and rotated, I thought I’d post this solution.

With a scatter, one can very easily implement a canvas-like object that can be rotated, translated and zoomed, whilst all of the widgets it contains are transformed along with it. This is usually great, unless you’d like, for some or other reason, to display a widget at a fixed position, for example any kind of overlay, such as a label.

The short answer is that this can be done by using a MatrixInstruction that is executed before that widget is drawn (in the canvas.before context) and that effectively reverses the parent Scatter’s transformation.

The slightly more involved answer is the minimal working example below. First we have the Python part of the ScatterOverlayApp. This only defines the MyScatter class which we’ll flesh out in the KV file, and the main app class which instantiates the top-level widget, in our case MyScatter:

from kivy.app import App
from kivy.uix.scatter import Scatter

class MyScatter(Scatter):
    pass

class ScatterOverlayApp(App):
    def build(self):
        return MyScatter()

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

In the KV Language part of this example, the <MyScatter> rule defines two buttons, and then the Label which we shall configure to be the fixed position overlay. The Label stores the current matrix, and then applies the inverse transformation matrix of its parent, in other words that of the MyScatter.

This will result in the widget drawing in the space of MyScatter‘s parent, in our case the window. So when we specify the label to be in the middle of the root widget, it will actually be drawn right in the middle of the window. Even when we rotate and scale MyScatter (see screenshot below), the label will remain exactly where we put it.

<MyScatter>:
    Button:
        text: "Click me"
        pos: 100, 100

    Button:
        text: "Don't click me"
        pos: 150, 200

    Label:
        pos: root.width / 2.0, root.height / 2.0
        text: "I am an overlay label!"
        font_size: 32

        canvas.before:
            # store current matrix
            PushMatrix
            MatrixInstruction:
                # reverse MyScatter's transformation matrix
                matrix: self.parent.transform_inv

        canvas.after:
            # restore matrix so that other widgets draw normally
            PopMatrix

Here’s a screenshot showing the situation after the containing MyScatter widget has been rotated, translated and zoomed. Both the buttons it contains have also been transformed, but the label is exactly where we placed it initially.

scatteroverlay.jpg

One thought on “Fixed position of any Scatter child widget in Kivy”

  1. Hi
    Sorry for my poor english
    I work on my iMac with your example : Fixed position of any Scatter child widget in Kivy

    I don’t understand how to use the mouse (left click) to generate red dot and to remove this red dot
    Can you help me
    Regards

Leave a Reply

Your email address will not be published. Required fields are marked *