Skip to content

Divider lines for GirdLayout #48

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Sep 1, 2021
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
234 changes: 208 additions & 26 deletions adafruit_displayio_layout/layouts/grid_layout.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,6 @@


class GridLayout(displayio.Group):

"""
A layout that organizes children into a grid table structure.

Expand All @@ -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. Defaults to False.
:param tuple h_divider_line_rows: Row indexes to draw divider lines above.
Row indexes are 0 based.
:param tuple 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,
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
Expand All @@ -52,9 +68,13 @@ def __init__(self, x, y, width, height, grid_size, cell_padding):
self.grid_size = grid_size
self.cell_padding = cell_padding
self._cell_content_list = []
self._divider_lines_enabled = divider_lines
self._divider_lines = []
self.h_divider_line_rows = h_divider_line_rows
self.v_divider_line_cols = v_divider_line_cols

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]
Expand All @@ -66,44 +86,206 @@ def _layout_cells(self):
button_size_x = cell["cell_size"][0]
button_size_y = cell["cell_size"][1]

_measured_width = (
int(button_size_x * self._width / grid_size_x)
- 2 * self.cell_padding
)

_measured_height = (
int(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:
print(
"int({} * {} / {}) + {}".format(
grid_position_x, self._width, grid_size_x, self.cell_padding
)
)
print(
"int({} * {} / {}) + {}".format(
grid_position_y,
self._height,
grid_size_y,
self.cell_padding,
)
)

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,
)
print(cell["content"].anchored_position)
print("---")

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
)
_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
)
else:
_bottom_line_loc_y = (
cell["content"].anchored_position[1]
+ _measured_height
+ self.cell_padding
)
_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
)

_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"])

print("pos_y: {} - size_y: {}".format(grid_position_y, grid_size_y))
print(grid_position_y == grid_size_y)
if grid_position_y == grid_size_y - 1 and (
self.h_divider_line_rows is None
or grid_position_y + 1 in self.h_divider_line_rows
):
self._divider_lines.append(
{
"shape": _horizontal_divider_line,
"tilegrid": _bottom_divider_tilegrid,
}
)
if (
self.h_divider_line_rows is None
or grid_position_y in self.h_divider_line_rows
):
self._divider_lines.append(
{
"shape": _horizontal_divider_line,
"tilegrid": _top_divider_tilegrid,
}
)
if (
self.v_divider_line_cols is None
or 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 (
self.v_divider_line_cols is None
or 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.

Expand Down
9 changes: 9 additions & 0 deletions docs/examples.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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
------------------

Expand Down
87 changes: 87 additions & 0 deletions examples/displayio_layout_gridlayout_dividers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
# 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,
divider_lines=True,
h_divider_line_rows=(1, 2), # Lines before rows 1 and 2
v_divider_line_cols=(), # No vertical divider lines
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This appears to be required if you specify h_divider_lines_rows. Can you clarify the reasoning behind this? I'm wondering if it should default to none, instead of defaulting to having lines.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wanted to break the API down into two main use cases, the simplest being on/off for divider lines to get drawn at all. The user can enable them by passing divider_lines=True in the constructor. This will cause it to draw all available lines around every cell.

The second and slightly more advanced use case is the user wants to specify which cells and in which orientation to draw lines some of the lines. In this case with the current API the passes divider_lines=True and then passes the desired configurations with the h_divider_line_rows and v_divider_line_cols arguments. If either of those arguments is omitted then it reverts to the standard divider_lines=True behavior which is to draw all available lines. So currently passing the empty tuple is necessary in order to specify that it should not draw any lines for the given orientation.

Maybe it would be simpler if the user passes either h_divider_line_rows or v_divider_line_cols then they do not need to also pass divider_lines=True I think that would allow the logic to work opposite how it does currently. It could start from drawing no lines, and only draw the ones specified by h_divider_line_rows and v_divider_line_cols since divider_lines=True would not be passed it would not draw all available lines if either of those is omitted.

I can look into this further and work on those changes tonight.

It seems I may have unintentionally changed the positioning behavior with this PR as well. I noticed just now that running the example in the MacroPad repo results in the labels getting shifted further down than they should be and causing the bottom row to get drawn half way off of the screen. I will try to resolve this issue when I work on it as well.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I noticed the shift and forgot to mention it! I didn't even think about the fact that it was an inadvertent change. Indeed, I had to alter my y position by 7 pixels to get everything back in position.

)

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