diff --git a/adafruit_displayio_layout/layouts/grid_layout.py b/adafruit_displayio_layout/layouts/grid_layout.py index 807774c..6c8bb8b 100644 --- a/adafruit_displayio_layout/layouts/grid_layout.py +++ b/adafruit_displayio_layout/layouts/grid_layout.py @@ -22,7 +22,7 @@ https://github.com/adafruit/circuitpython/releases """ - +import math import displayio __version__ = "0.0.0-auto.0" @@ -30,7 +30,6 @@ class GridLayout(displayio.Group): - """ A layout that organizes children into a grid table structure. @@ -40,10 +39,27 @@ class GridLayout(displayio.Group): :param int height: Height of the layout in pixels. :param tuple grid_size: Size in cells as two ints in a tuple e.g. (2, 2) :param int cell_padding: Extra padding space inside each cell. In pixels. + :param bool divider_lines: Whether or not to draw lines between the cells. + :param Union[tuple, list] h_divider_line_rows: Row indexes to draw divider + lines above. Row indexes are 0 based. + :param Union[tuple, list] v_divider_line_cols: Column indexes to draw divider + lines before. Column indexes are 0 based. + """ # pylint: disable=too-many-arguments - def __init__(self, x, y, width, height, grid_size, cell_padding): + def __init__( + self, + x, + y, + width, + height, + grid_size, + cell_padding=0, + divider_lines=False, + h_divider_line_rows=None, + v_divider_line_cols=None, + ): super().__init__(x=x, y=y) self.x = x self.y = y @@ -53,8 +69,39 @@ def __init__(self, x, y, width, height, grid_size, cell_padding): self.cell_padding = cell_padding self._cell_content_list = [] - def _layout_cells(self): + self._divider_lines = [] + self.h_divider_line_rows = h_divider_line_rows + self.v_divider_line_cols = v_divider_line_cols + + self._divider_lines_enabled = ( + (divider_lines is True) + or (h_divider_line_rows is not None) + or (v_divider_line_cols is not None) + ) + + if divider_lines: + if self.h_divider_line_rows is None: + self.h_divider_line_rows = [] + for _y in range(self.grid_size[1] + 1): + self.h_divider_line_rows.append(_y) + if self.v_divider_line_cols is None: + self.v_divider_line_cols = [] + for _x in range(self.grid_size[0] + 1): + self.v_divider_line_cols.append(_x) + else: + if not h_divider_line_rows: + self.h_divider_line_rows = tuple() + if not v_divider_line_cols: + self.v_divider_line_cols = tuple() + + # use at least 1 padding so that content is inside the divider lines + if cell_padding == 0 and ( + divider_lines or h_divider_line_rows or v_divider_line_cols + ): + self.cell_padding = 1 + def _layout_cells(self): + # pylint: disable=too-many-locals, too-many-branches, too-many-statements for cell in self._cell_content_list: if cell["content"] not in self: grid_size_x = self.grid_size[0] @@ -66,44 +113,180 @@ def _layout_cells(self): button_size_x = cell["cell_size"][0] button_size_y = cell["cell_size"][1] + _measured_width = ( + math.ceil(button_size_x * self._width / grid_size_x) + - 2 * self.cell_padding + ) + + _measured_height = ( + math.ceil(button_size_y * self._height / grid_size_y) + - 2 * self.cell_padding + ) if hasattr(cell["content"], "resize"): # if it has resize function cell["content"].resize( - ( - int(button_size_x * self._width / grid_size_x) - - 2 * self.cell_padding - ), - ( - int(button_size_y * self._height / grid_size_y) - - 2 * self.cell_padding - ), + _measured_width, + _measured_height, ) else: try: # try width and height properties. - cell["content"].width = ( - int(button_size_x * self._width / grid_size_x) - - 2 * self.cell_padding - ) - cell["content"].height = ( - int(button_size_y * self._height / grid_size_y) - - 2 * self.cell_padding - ) + cell["content"].width = _measured_width + cell["content"].height = _measured_height except AttributeError: # This element does not allow setting width and height. # No problem, we'll use whatever size it already is. + # _measured_width = cell["content"].width + # _measured_height = cell["content"].height + pass - cell["content"].x = ( - int(grid_position_x * self._width / grid_size_x) + self.cell_padding - ) - cell["content"].y = ( - int(grid_position_y * self._height / grid_size_y) - + self.cell_padding - ) + if not hasattr(cell["content"], "anchor_point"): + + cell["content"].x = ( + int(grid_position_x * self._width / grid_size_x) + + self.cell_padding + ) + cell["content"].y = ( + int(grid_position_y * self._height / grid_size_y) + + self.cell_padding + ) + else: + cell["content"].anchor_point = (0, 0) + cell["content"].anchored_position = ( + int(grid_position_x * self._width / grid_size_x) + + self.cell_padding, + int(grid_position_y * self._height / grid_size_y) + + self.cell_padding, + ) self.append(cell["content"]) + if self._divider_lines_enabled: + palette = displayio.Palette(2) + palette[0] = 0xFFFFFF + palette[1] = 0xFFFFFF + + if not hasattr(cell["content"], "anchor_point"): + _bottom_line_loc_y = ( + cell["content"].y + _measured_height + self.cell_padding + ) - 1 + _bottom_line_loc_x = cell["content"].x - self.cell_padding + + _top_line_loc_y = cell["content"].y - self.cell_padding + _top_line_loc_x = cell["content"].x - self.cell_padding + + _right_line_loc_y = cell["content"].y - self.cell_padding + _right_line_loc_x = ( + cell["content"].x + _measured_width + self.cell_padding + ) - 1 + else: + _bottom_line_loc_y = ( + cell["content"].anchored_position[1] + + _measured_height + + self.cell_padding + ) - 1 + _bottom_line_loc_x = ( + cell["content"].anchored_position[0] - self.cell_padding + ) + + _top_line_loc_y = ( + cell["content"].anchored_position[1] - self.cell_padding + ) + _top_line_loc_x = ( + cell["content"].anchored_position[0] - self.cell_padding + ) + + _right_line_loc_y = ( + cell["content"].anchored_position[1] - self.cell_padding + ) + _right_line_loc_x = ( + cell["content"].anchored_position[0] + + _measured_width + + self.cell_padding + ) - 1 + + _horizontal_divider_line = displayio.Shape( + _measured_width + (2 * self.cell_padding), + 1, + mirror_x=False, + mirror_y=False, + ) + + _bottom_divider_tilegrid = displayio.TileGrid( + _horizontal_divider_line, + pixel_shader=palette, + y=_bottom_line_loc_y, + x=_bottom_line_loc_x, + ) + + _top_divider_tilegrid = displayio.TileGrid( + _horizontal_divider_line, + pixel_shader=palette, + y=_top_line_loc_y, + x=_top_line_loc_x, + ) + + _vertical_divider_line = displayio.Shape( + 1, + _measured_height + (2 * self.cell_padding), + mirror_x=False, + mirror_y=False, + ) + + _left_divider_tilegrid = displayio.TileGrid( + _vertical_divider_line, + pixel_shader=palette, + y=_top_line_loc_y, + x=_top_line_loc_x, + ) + + _right_divider_tilegrid = displayio.TileGrid( + _vertical_divider_line, + pixel_shader=palette, + y=_right_line_loc_y, + x=_right_line_loc_x, + ) + + for line_obj in self._divider_lines: + self.remove(line_obj["tilegrid"]) + + if grid_position_y == grid_size_y - 1 and ( + grid_position_y + 1 in self.h_divider_line_rows + ): + self._divider_lines.append( + { + "shape": _horizontal_divider_line, + "tilegrid": _bottom_divider_tilegrid, + } + ) + if grid_position_y in self.h_divider_line_rows: + self._divider_lines.append( + { + "shape": _horizontal_divider_line, + "tilegrid": _top_divider_tilegrid, + } + ) + if grid_position_x in self.v_divider_line_cols: + self._divider_lines.append( + { + "shape": _horizontal_divider_line, + "tilegrid": _left_divider_tilegrid, + } + ) + if grid_position_x == grid_size_x - 1 and ( + grid_position_x + 1 in self.v_divider_line_cols + ): + self._divider_lines.append( + { + "shape": _vertical_divider_line, + "tilegrid": _right_divider_tilegrid, + } + ) + + for line_obj in self._divider_lines: + self.append(line_obj["tilegrid"]) + def add_content(self, cell_content, grid_position, cell_size): """Add a child to the grid. diff --git a/docs/examples.rst b/docs/examples.rst index 2c46c7f..b8d6bbc 100644 --- a/docs/examples.rst +++ b/docs/examples.rst @@ -7,6 +7,15 @@ Ensure your device works with this simple test. :caption: examples/displayio_layout_simpletest.py :linenos: +GridLayout divider lines example +-------------------------------- + +Create GridLayouts with divider lines. + +.. literalinclude:: ../examples/displayio_layout_gridlayout_dividers.py + :caption: examples/displayio_layout_gridlayout_dividers.py + :linenos: + Pygame simple test ------------------ diff --git a/examples/displayio_layout_gridlayout_dividers.py b/examples/displayio_layout_gridlayout_dividers.py new file mode 100644 index 0000000..f649562 --- /dev/null +++ b/examples/displayio_layout_gridlayout_dividers.py @@ -0,0 +1,85 @@ +# SPDX-FileCopyrightText: 2021 Tim C, written for Adafruit Industries +# +# SPDX-License-Identifier: MIT +""" +Illustrate how to use divider lines with GridLayout +""" +import board +import displayio +import terminalio +from adafruit_display_text import label +from adafruit_displayio_layout.layouts.grid_layout import GridLayout + +# use built in display (PyPortal, PyGamer, PyBadge, CLUE, etc.) +# see guide for setting up external displays (TFT / OLED breakouts, RGB matrices, etc.) +# https://learn.adafruit.com/circuitpython-display-support-using-displayio/display-and-display-bus +display = board.DISPLAY + +# Make the display context +main_group = displayio.Group() +display.show(main_group) + +layout = GridLayout( + x=10, + y=10, + width=200, + height=100, + grid_size=(2, 2), + cell_padding=8, + divider_lines=True, # divider lines around every cell +) +_labels = [] + +_labels.append( + label.Label( + terminalio.FONT, scale=2, x=0, y=0, text="Hello", background_color=0x770077 + ) +) +layout.add_content(_labels[0], grid_position=(0, 0), cell_size=(1, 1)) +_labels.append( + label.Label( + terminalio.FONT, scale=2, x=0, y=0, text="World", background_color=0x007700 + ) +) +layout.add_content(_labels[1], grid_position=(1, 0), cell_size=(1, 1)) +_labels.append(label.Label(terminalio.FONT, scale=2, x=0, y=0, text="Hello")) +layout.add_content(_labels[2], grid_position=(0, 1), cell_size=(1, 1)) +_labels.append(label.Label(terminalio.FONT, scale=2, x=0, y=0, text="Grid")) +layout.add_content(_labels[3], grid_position=(1, 1), cell_size=(1, 1)) + +main_group.append(layout) + +other_layout = GridLayout( + x=10, + y=120, + width=140, + height=80, + grid_size=(2, 3), + cell_padding=4, + h_divider_line_rows=(1, 2), # Lines before rows 1 and 2 +) + +other_layout.add_content( + label.Label(terminalio.FONT, text="0x0"), grid_position=(0, 0), cell_size=(1, 1) +) +other_layout.add_content( + label.Label(terminalio.FONT, text="0x1"), grid_position=(0, 1), cell_size=(1, 1) +) +other_layout.add_content( + label.Label(terminalio.FONT, text="0x2"), grid_position=(0, 2), cell_size=(1, 1) +) + +other_layout.add_content( + label.Label(terminalio.FONT, text="1x0"), grid_position=(1, 0), cell_size=(1, 1) +) +other_layout.add_content( + label.Label(terminalio.FONT, text="1x1"), grid_position=(1, 1), cell_size=(1, 1) +) +other_layout.add_content( + label.Label(terminalio.FONT, text="1x2"), grid_position=(1, 2), cell_size=(1, 1) +) + +main_group.append(other_layout) + +while True: + pass