Skip to content

Cell anchor point #58

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 3 commits into from
Dec 6, 2021
Merged
Changes from all 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
170 changes: 142 additions & 28 deletions adafruit_displayio_layout/layouts/grid_layout.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,12 @@
https://github.com/adafruit/circuitpython/releases

"""
try:
# Used only for typing
from typing import Tuple
except ImportError:
pass

import math
import displayio

Expand All @@ -45,6 +51,9 @@ class GridLayout(displayio.Group):
:param Union[tuple, list] v_divider_line_cols: Column indexes to draw divider
lines before. Column indexes are 0 based.
:param divider_line_color: The color of the divider lines (in hexadecimal)
:param tuple cell_anchor_point: Anchor point used within every cell. Needs to
be a tuple containing two floats between 0.0 and 1.0. Default is (0.0, 0.0)
which will anchor content to the top left of the cell.

"""

Expand All @@ -62,6 +71,7 @@ def __init__(
h_divider_line_rows=None,
v_divider_line_cols=None,
divider_line_color=0xFFFFFF,
cell_anchor_point=(0.0, 0.0),
):
super().__init__(x=x, y=y)
self.x = x
Expand All @@ -71,6 +81,7 @@ def __init__(
self.grid_size = grid_size
self.cell_padding = cell_padding
self._cell_content_list = []
self._cell_anchor_point = cell_anchor_point

self._divider_lines = []
self._divider_color = divider_line_color
Expand Down Expand Up @@ -114,18 +125,19 @@ def _layout_cells(self):
grid_position_x = cell["grid_position"][0]
grid_position_y = cell["grid_position"][1]

button_size_x = cell["cell_size"][0]
button_size_y = cell["cell_size"][1]
content_cell_size_x = cell["cell_size"][0]
content_cell_size_y = cell["cell_size"][1]

_measured_width = (
math.ceil(button_size_x * self._width / grid_size_x)
math.ceil(content_cell_size_x * self._width / grid_size_x)
- 2 * self.cell_padding
)

_measured_height = (
math.ceil(button_size_y * self._height / grid_size_y)
math.ceil(content_cell_size_y * self._height / grid_size_y)
- 2 * self.cell_padding
)

if hasattr(cell["content"], "resize"):
# if it has resize function
cell["content"].resize(
Expand All @@ -150,18 +162,24 @@ def _layout_cells(self):
cell["content"].x = (
int(grid_position_x * self._width / grid_size_x)
+ self.cell_padding
+ int(cell["cell_anchor_point"][0] * _measured_width)
- int(cell["content"].width * cell["cell_anchor_point"][0])
)
cell["content"].y = (
int(grid_position_y * self._height / grid_size_y)
+ self.cell_padding
+ int(cell["cell_anchor_point"][1] * _measured_height)
- int(cell["content"].height * cell["cell_anchor_point"][1])
)
else:
cell["content"].anchor_point = (0, 0)
cell["content"].anchor_point = cell["cell_anchor_point"]
cell["content"].anchored_position = (
int(grid_position_x * self._width / grid_size_x)
+ self.cell_padding,
+ self.cell_padding
+ (cell["cell_anchor_point"][0] * _measured_width),
int(grid_position_y * self._height / grid_size_y)
+ self.cell_padding,
+ self.cell_padding
+ (cell["cell_anchor_point"][1] * _measured_height),
)

self.append(cell["content"])
Expand All @@ -173,42 +191,84 @@ def _layout_cells(self):

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
(cell["content"].y + _measured_height + self.cell_padding)
- 1
- int(cell["cell_anchor_point"][1] * _measured_height)
+ int(cell["content"].height * cell["cell_anchor_point"][1])
)

_top_line_loc_y = cell["content"].y - self.cell_padding
_top_line_loc_x = cell["content"].x - self.cell_padding
_bottom_line_loc_x = (
cell["content"].x
- self.cell_padding
- int(cell["cell_anchor_point"][0] * _measured_width)
+ int(cell["content"].width * cell["cell_anchor_point"][0])
)

_top_line_loc_y = (
cell["content"].y
- self.cell_padding
- int(cell["cell_anchor_point"][1] * _measured_height)
+ int(cell["content"].height * cell["cell_anchor_point"][1])
)

_top_line_loc_x = (
cell["content"].x
- self.cell_padding
- int(cell["cell_anchor_point"][0] * _measured_width)
+ int(cell["content"].width * cell["cell_anchor_point"][0])
)

_right_line_loc_y = (
cell["content"].y
- self.cell_padding
- int(cell["cell_anchor_point"][1] * _measured_height)
+ int(cell["content"].height * cell["cell_anchor_point"][1])
)

_right_line_loc_y = cell["content"].y - self.cell_padding
_right_line_loc_x = (
cell["content"].x + _measured_width + self.cell_padding
) - 1
(cell["content"].x + _measured_width + self.cell_padding)
- 1
- int(cell["cell_anchor_point"][0] * _measured_width)
+ int(cell["content"].width * cell["cell_anchor_point"][0])
)
else:
_bottom_line_loc_y = (
cell["content"].anchored_position[1]
+ _measured_height
+ self.cell_padding
- (cell["cell_anchor_point"][1] * _measured_height)
) - 1
_bottom_line_loc_x = (
cell["content"].anchored_position[0] - self.cell_padding
cell["content"].anchored_position[0]
- self.cell_padding
- (cell["cell_anchor_point"][0] * _measured_width)
)

_top_line_loc_y = (
cell["content"].anchored_position[1] - self.cell_padding
cell["content"].anchored_position[1]
- self.cell_padding
- (cell["cell_anchor_point"][1] * _measured_height)
)
_top_line_loc_x = (
cell["content"].anchored_position[0] - self.cell_padding
cell["content"].anchored_position[0]
- self.cell_padding
- (cell["cell_anchor_point"][0] * _measured_width)
)

_right_line_loc_y = (
cell["content"].anchored_position[1] - self.cell_padding
cell["content"].anchored_position[1]
- self.cell_padding
- (cell["cell_anchor_point"][1] * _measured_height)
)
_right_line_loc_x = (
cell["content"].anchored_position[0]
+ _measured_width
+ self.cell_padding
) - 1
(
cell["content"].anchored_position[0]
+ _measured_width
+ self.cell_padding
)
- 1
- (cell["cell_anchor_point"][0] * _measured_width)
)

_horizontal_divider_line = displayio.Shape(
_measured_width + (2 * self.cell_padding),
Expand Down Expand Up @@ -255,31 +315,61 @@ def _layout_cells(self):
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
"""
Only use bottom divider lines on the bottom row. All
other rows rely on top divder lines of the row beneath them.
Add the content_cell_size to the grid_position to account for
areas larger than 1x1 cells. For 1x1 cells this will equal zero
and not change anything.
"""
if (
grid_position_y + content_cell_size_y - 1
) == grid_size_y - 1 and (
(grid_position_y + content_cell_size_y - 1) + 1
in self.h_divider_line_rows
):
self._divider_lines.append(
{
"shape": _horizontal_divider_line,
"tilegrid": _bottom_divider_tilegrid,
}
)

"""
Every cell whose index is in h_divider_line_rows gets
a top divider line.
"""
if grid_position_y in self.h_divider_line_rows:
self._divider_lines.append(
{
"shape": _horizontal_divider_line,
"tilegrid": _top_divider_tilegrid,
}
)

"""
Every cell whose index is in v_divider_line_cols gets
a left divider line.
"""
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
"""
Only use right divider lines on the right-most column. All
other columns rely on left divider lines of the column to their
left. Add the content_cell_size to the grid_position to account for
areas larger than 1x1 cells. For 1x1 cells this will equal zero
and not change anything.
"""
if (
grid_position_x + content_cell_size_x - 1
) == grid_size_x - 1 and (
(grid_position_x + content_cell_size_x - 1) + 1
in self.v_divider_line_cols
):
self._divider_lines.append(
{
Expand All @@ -291,7 +381,9 @@ def _layout_cells(self):
for line_obj in self._divider_lines:
self.append(line_obj["tilegrid"])

def add_content(self, cell_content, grid_position, cell_size):
def add_content(
self, cell_content, grid_position, cell_size, cell_anchor_point=None
):
"""Add a child to the grid.

:param cell_content: the content to add to this cell e.g. label, button, etc...
Expand All @@ -300,8 +392,19 @@ def add_content(self, cell_content, grid_position, cell_size):
x,y coordinates in grid cells. e.g. (1,0)
:param tuple cell_size: the size and shape that the new cell should
occupy. Width and height in cells inside a tuple e.g. (1, 1)
:param tuple cell_anchor_point: a tuple of floats between 0.0 and 1.0.
If passed, this value will override the cell_anchor_point of the GridLayout
for the single cell having it's content added with this function call. If omitted
then the cell_anchor_point from the GridLayout will be used.
:return: None"""

if cell_anchor_point:
_this_cell_anchor_point = cell_anchor_point
else:
_this_cell_anchor_point = self._cell_anchor_point

sub_view_obj = {
"cell_anchor_point": _this_cell_anchor_point,
"content": cell_content,
"grid_position": grid_position,
"cell_size": cell_size,
Expand All @@ -326,3 +429,14 @@ def get_cell(self, cell_coordinates):
cell_coordinates
)
)

@property
def cell_size_pixels(self) -> Tuple[int, int]:
"""
Get the size of a 1x1 cell in pixels. Can be useful for manually
re-positioning content within cells.

:return Tuple[int, int]: A tuple containing the (x, y) size in
pixels of a 1x1 cell in the GridLayout
"""
return (self._width // self.grid_size[0], self._height // self.grid_size[1])