Joshua Harwood
Joshua Harwood

Reputation: 357

Moving Canvas Context From Window to Widget in Kivy's RelativeLayout in Python, Alone (No .kv Files)

I've got the following code snippet that will draw a board. I've read about RelativeLayout and seen tutorials for manipulating the .kv file, but no explanation on how to relativize the coordinates to the widget with just Python code.

from kivy.uix.widget import Widget
from kivy import graphics
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.button import Button
from kivy.app import App

class Chessboard(Widget):
    def __init__(self, **kwargs):
        super(Chessboard, self).__init__(**kwargs)
        self.bind(pos=self.drawBoardandPieces)
        self.bind(size=self.drawBoardandPieces)
        self.drawBoardandPieces()

    def drawBoardandPieces(self, *args):
        with self.canvas:
            # Reset the canvas in case of redraws.
            self.canvas.clear()
            # Define the lengths of the edges of the squares.
            edge_len = min(self.height, self.width) // 8
            for column in range(0, 8):
                for row in range(0, 8):
                    if ((row + column) % 2) == 0:
                        graphics.Color(0, 0, 1)
                        self.dark_rect = graphics.Rectangle(pos=(column*edge_len, row*edge_len), size=(edge_len, edge_len))
                    else:
                        graphics.Color(1, 1, 1)
                        self.light_rect = graphics.Rectangle(pos=(column*edge_len, row*edge_len), size=(edge_len, edge_len))

    def on_touch_down(self, touch):
        print(touch.pos)


class SCApp(App):
    def build(self):
        app_layout = BoxLayout(orientation="vertical")
        app_layout.add_widget(widget=Chessboard())
        play_again_btn = Button(text="Play Again", size_hint=(1, 1))
        app_layout.add_widget(widget=play_again_btn)
        return app_layout

SCApp().run()

Instead of what this code renders, I need the board drawn on the canvas to appear above the button in its own BoxLayout container.

Also, will click event need to be contained in the RelativeLayout container, as well, to work only in the context of the board?

Upvotes: 0

Views: 165

Answers (2)

Joshua Harwood
Joshua Harwood

Reputation: 357

I solved it. Here's the corrected code in the app. All you need to do is to place the widget into its own RelativeLayout. Then, the RelativeLayout widget can, itself, be placed inside of a box in BoxLayout, a grid position in GridLayout, etc.

from kivy.uix.relativelayout import RelativeLayout

# All of that other code.

class SCApp(App):
    def build(self):
        app_layout = BoxLayout(orientation="vertical")
        chessboard_layout = RelativeLayout()
        chessboard_layout.add_widget(widget=Chessboard())
        app_layout.add_widget(widget=chessboard_layout))
        play_again_btn = Button(text="Play Again", size_hint=(1, 1))
        app_layout.add_widget(widget=play_again_btn)
        return app_layout

Upvotes: 0

John Anderson
John Anderson

Reputation: 38937

Note that the documentation specifies:

Kivy drawing instructions are not automatically relative to the widgets position or size.

So you must also handle the position of your chessboard. You can handle the position using something like:

def drawBoardandPieces(self, *args):
    with self.canvas:
        # Reset the canvas in case of redraws.
        self.canvas.clear()
        # Define the lengths of the edges of the squares.
        edge_len = min(self.height, self.width) // 8
        for column in range(0, 8):
            for row in range(0, 8):
                if ((row + column) % 2) == 0:
                    graphics.Color(0, 0, 1)
                    self.dark_rect = graphics.Rectangle(pos=(self.width/2 - 4*edge_len + column*edge_len, self.y + row*edge_len), size=(edge_len, edge_len))
                else:
                    graphics.Color(1, 1, 1)
                    self.light_rect = graphics.Rectangle(pos=(self.width/2 - 4*edge_len + column*edge_len, self.y + row*edge_len), size=(edge_len, edge_len))

This uses self.y as the bottom of the chess board and calculates the x based on the width of the Widget and the edge_len. Probably should also add self.x to the x position, but that is zero in your case.

In general, your on_touch_down() method should check if the touch is on your Widget as stated in the documentation. If you determine that no other Widgets need to know about that touch, your on_touch_down() method can return True, otherwise it should return False.

Upvotes: 1

Related Questions