Skip to content
Advertisement

How to make Canvas vertex instructions relative to a widget in Kivy

I just started learning Kivy and I was trying to understand how the Canvas instructions are affected on resizing of the window of the kivyApp.

In the Kivy documentation it is mentioned that –

Note

Kivy drawing instructions are not automatically relative to the position or size of the widget. You, therefore, need to consider these factors when drawing. In order to make your drawing instructions relative to the widget, the instructions need either to be declared in the KvLang or bound to pos and size changes.

The example which follows, shows how to bind the size and position of a Rectangle instruction to the size and position of the widget in which it is being drawn. Therefore the size and the position changes proportionally when the window is resized.

But, how can I do the same for somthing like a Bezier instruction, which uses points.

I have a custom widget HangManFig1 which extend the Widget class, which is defined in KVlang like this:

<HangManFig1>:
    canvas:
        Line:
            points: (150, 100, 150, 700)
            width: 10
        Line:
            points: (150, 700, 600, 700)
            width: 10
        Line:
            points: (150, 600, 250, 700)
            width: 10
        Line:
            points: (600, 700, 600, 600)
            width: 3
        Ellipse:
            pos: (550, 500)
        Line:
            bezier: (610, 510, 630, 400, 570, 350)
            width: 10
        Line:
            bezier: (570, 350, 510, 370, 450, 270)
            width: 10
        Line:
            bezier: (570, 350, 600, 300, 550, 200)
            width: 10
        Line:
            bezier: (610, 480, 530, 430, 500, 430)
            width: 10
        Line:
            bezier: (610, 480, 630, 500, 680, 390)
            width: 10

I use this widget in a Screen, in the following manner:

#:import HangManFig1 figures.hangmanfig1

<MainScreen>:
    name: '_main_screen_'
    BoxLayout:
        RelativeLayout:
            size_hint_x: 0.7
            HangManFig1:

        RelativeLayout:
            size_hint_x: 0.3
            Button:
                text: 'Play'
                pos_hint: {'x': 0.1, 'y': 0.80}
                size_hint: (0.8, 0.1)
                on_release:
                    root.manager.transition.direction = 'left'
                    root.manager.current = '_game_screen_'

            Button:
                text: 'Practice'
                pos_hint: {'x': 0.1, 'y': 0.60}
                size_hint: (0.8, 0.1)
                on_release:
                    root.manager.transition.direction = 'left'
                    root.manager.current = '_game_screen_'

            Button:
                text: 'Share'
                pos_hint: {'x': 0.1, 'y': 0.40}
                size_hint: (0.8, 0.1)
                on_release:
                    root.manager.transition.direction = 'left'
                    root.manager.current = '_share_screen_'

            Button:
                text: 'Credits'
                pos_hint: {'x': 0.1, 'y': 0.20}
                size_hint: (0.8, 0.1)
                on_release:
                    root.manager.transition.direction = 'left'
                    root.manager.current = '_credits_screen_'

KivyApp in full screen

When I am resizing the window, I see that although the Buttons are being positioned correctly, but not HangManFig1.

Enter image description here

Is there a way, in which I can bind the size of this widget to that of the Parent Widget so that it is positioned correctly even when the Window size changes?

Advertisement

Answer

While you used RelativeLayout to make the coordinates of your instructions relative to the position of your widget, it doesn’t do anything regarding its size.

As you hardcoded all the positions by numeric values, you’ll need a way to scale these values relative to the size of your widget, and you have to consider what you want to happen regarding the width of your lines in this situation, should it grow relative to the size of the widget as well? linearly? Something else? Depending on what you want various possibility exist.

The easiest starting point, IMHO, would be to use a Scale instruction, to set all the canvas instructions relative to the size of your widget, over the size you used to design your current hangman.

<HangManFig1>:
    h: 600
    w: 800 # just guessing the size you used to design it, adjust as needed
    canvas:
        PushMatrix:
        Scale:
           xy: self.width / self.w, self.height / self.h

        Line:
            points: (150, 100, 150, 700)
            width: 10

        Ellipse:
            pos: (550, 500)
        ... # etc, all the other instructions remain unchanged

        PopMatrix: # restore the initial canvas so it doesn't affect other instructions after your drawing

If that’s not enough for you, because you want to keep the width of the line constant for example, you could either not do it with a Scale instruction, but instead have a function that takes the size of your widget and a set of coordinates as input, and returns the value relative to that size:

def rscale(size, *args):
    w, h = size
    ws = w / 800  # adjust accordingly, as in the first example
    hs = h / 600
    return (x / (ws if i % 2 else hs) for i, x in enumerate(args))

This function could be used like this.

        Line:
           points: rscale(self.size, 150, 100, 150, 700)

And if you want something more sophisticated, like preserving the aspect ratio of your hangman, while staying in the boundaries of your size, you could adjust accordingly to something like:

def rscale(size, *args):
    w, h = size
    scale = min(w / 800, h / 600) # pick the smallest scale of the two
    return (x / s for x in args)
Advertisement