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_'
When I am resizing the window, I see that although the Buttons
are being positioned correctly, but not HangManFig1
.
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)