Search code examples
pythonandroidkivyscalingbuildozer

Kivy GridLayout scaling issues - Android vs PC - 16:9 and 19:5:9 vs 4:3(1920x1080 and 1440x3120 vs 800x600)


I've been making an Android Snake game using kivy and buildozer and after many iterations of using different values, layouts and approaches(and many searches on Google, StackOverflow and kivy forums/documentation) I've reached a point where I am seeking help here myself.

The issue is so;
the grid looks great when the kivy window is 800x600, with the goal of the rectangles being perfect, even sided squares. The moment the window maximizes and the window turns into 1920x1080(16:9), the squares become wide rectangles and thus uneven. Unfortunately, trying the game on my OnePlus 7 Pro, with the aspect ratio of 19:5:9 and resolution of 1440x3120 the undesired result is even more extreme.

currently I am using a GridLayout populated by Rectangle to achieve the level's grid. I've tried making formulas based on Window.size, Window.width, I've tried using size_hint, size, width, cols_minimum and kivy.metrics so far with no success.

As a side note, if anything of what I am trying to achieve or in the code below can be done in a better way, please let me know. Any alternatives would be appreciated, I am very inexperienced and new to both kivy and Python.

My code:

from kivy.core.window import Window
from kivy.lang import Builder
from kivy.properties import ListProperty
from kivy.uix.gridlayout import GridLayout
from kivy.uix.widget import Widget


class GridCell(Widget):
    color = ListProperty([1, 1, 1, 1])
    size = ListProperty([0, 0])
    pos = ListProperty([0, 0])

    def __init__(self, size, x, y, **kwargs):
        super().__init__(**kwargs)
        self.color = (0.0, 0.0, 0.0, 1.0)
        self.size = (size, size)
        self.pos = (x, y)


class Level(GridLayout):
    def __init__(self, **kwargs):
        super(Level, self).__init__(**kwargs)
        self.cols = 30
        self.cell_size = 28
        self.spacing = 2
        self.positions = [(row, column) for row in range(self.cols) for column
                          in range(self.cols)]
        self.grid_cells = {}
        self.create_grid()

    def create_grid(self):
        for position in self.positions:
            self.grid_cells[position] = GridCell(self.cell_size, *position)
            self.add_widget(self.grid_cells[position])


if __name__ == '__main__':
    from kivy.app import App
    Builder.load_file('grid_cell.kv')
    Window.clearcolor = (1.0, 1.0, 1.0, 1.0)


    class LevelApp(App):
        def build(self):
            self.title = 'Level'
            level = Level()
            return level


    level_app = LevelApp()
    level_app.run()

grid_cell.kv:

<GridCell>:
    canvas:
        Color:
            rgba: self.color
        Rectangle:
            pos: self.pos
            size: self.size

Solution

  • Note that you cannot fill a non-square window with a square grid of square cells. But, to keep the cells square and base their size on the GridLayout, try changing the GridCell class to:

    class GridCell(Widget):
        color = ListProperty([1, 1, 1, 1])
        def __init__(self, x, y, **kwargs):
            super().__init__(**kwargs)
            self.color = (0.0, 0.0, 0.0, 1.0)
            self.pos = (x, y)
    

    Note that you don't need to define pos and size properties since Widget already has them, and I have removed size from the __init__() method.

    And change Level to:

    class Level(GridLayout):
        def __init__(self, **kwargs):
            super(Level, self).__init__(**kwargs)
            self.cols = 30
            self.rows = 30
            self.spacing = 2
            self.positions = [(row, column) for row in range(self.rows) for column
                              in range(self.cols)]
            self.grid_cells = {}
            self.create_grid()
    
        def create_grid(self):
            for position in self.positions:
                self.grid_cells[position] = GridCell(*position)
                self.add_widget(self.grid_cells[position])
    

    Then in your kv:

    <GridCell>:
        grid_cols: self.parent.cols if self.parent and self.parent.cols else 1
        grid_rows: self.parent.rows if self.parent and self.parent.rows else 1
        tmp_size: min(self.parent.width/self.grid_cols - self.parent.spacing[0], self.parent.height/self.grid_rows - self.parent.spacing[1]) if self.parent else 0
        size_hint: None, None
        size: self.tmp_size, self.tmp_size
        canvas:
            Color:
                rgba: self.color
            Rectangle:
                pos: self.pos
                size: self.size
    

    This calculates a tmp_size based on the GridLayout size, rows, and cols, and assigns that size to the GridCell. The if statements in the kv are necessary because the <GridCell> rule is applied before the GridCell gets its parent assigned.