Fixed position of any Scatter child widget in Kivy
Contents
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 Scatterclass 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, 100Button: 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.