2222 https://github.com/adafruit/circuitpython/releases
2323
2424"""
25+ try :
26+ # Used only for typing
27+ from typing import Tuple
28+ except ImportError :
29+ pass
30+
2531import math
2632import displayio
2733
@@ -45,6 +51,9 @@ class GridLayout(displayio.Group):
4551 :param Union[tuple, list] v_divider_line_cols: Column indexes to draw divider
4652 lines before. Column indexes are 0 based.
4753 :param divider_line_color: The color of the divider lines (in hexadecimal)
54+ :param tuple cell_anchor_point: Anchor point used within every cell. Needs to
55+ be a tuple containing two floats between 0.0 and 1.0. Default is (0.0, 0.0)
56+ which will anchor content to the top left of the cell.
4857
4958 """
5059
@@ -62,6 +71,7 @@ def __init__(
6271 h_divider_line_rows = None ,
6372 v_divider_line_cols = None ,
6473 divider_line_color = 0xFFFFFF ,
74+ cell_anchor_point = (0.0 , 0.0 ),
6575 ):
6676 super ().__init__ (x = x , y = y )
6777 self .x = x
@@ -71,6 +81,7 @@ def __init__(
7181 self .grid_size = grid_size
7282 self .cell_padding = cell_padding
7383 self ._cell_content_list = []
84+ self ._cell_anchor_point = cell_anchor_point
7485
7586 self ._divider_lines = []
7687 self ._divider_color = divider_line_color
@@ -114,18 +125,19 @@ def _layout_cells(self):
114125 grid_position_x = cell ["grid_position" ][0 ]
115126 grid_position_y = cell ["grid_position" ][1 ]
116127
117- button_size_x = cell ["cell_size" ][0 ]
118- button_size_y = cell ["cell_size" ][1 ]
128+ content_cell_size_x = cell ["cell_size" ][0 ]
129+ content_cell_size_y = cell ["cell_size" ][1 ]
119130
120131 _measured_width = (
121- math .ceil (button_size_x * self ._width / grid_size_x )
132+ math .ceil (content_cell_size_x * self ._width / grid_size_x )
122133 - 2 * self .cell_padding
123134 )
124135
125136 _measured_height = (
126- math .ceil (button_size_y * self ._height / grid_size_y )
137+ math .ceil (content_cell_size_y * self ._height / grid_size_y )
127138 - 2 * self .cell_padding
128139 )
140+
129141 if hasattr (cell ["content" ], "resize" ):
130142 # if it has resize function
131143 cell ["content" ].resize (
@@ -150,18 +162,24 @@ def _layout_cells(self):
150162 cell ["content" ].x = (
151163 int (grid_position_x * self ._width / grid_size_x )
152164 + self .cell_padding
165+ + int (cell ["cell_anchor_point" ][0 ] * _measured_width )
166+ - int (cell ["content" ].width * cell ["cell_anchor_point" ][0 ])
153167 )
154168 cell ["content" ].y = (
155169 int (grid_position_y * self ._height / grid_size_y )
156170 + self .cell_padding
171+ + int (cell ["cell_anchor_point" ][1 ] * _measured_height )
172+ - int (cell ["content" ].height * cell ["cell_anchor_point" ][1 ])
157173 )
158174 else :
159- cell ["content" ].anchor_point = ( 0 , 0 )
175+ cell ["content" ].anchor_point = cell [ "cell_anchor_point" ]
160176 cell ["content" ].anchored_position = (
161177 int (grid_position_x * self ._width / grid_size_x )
162- + self .cell_padding ,
178+ + self .cell_padding
179+ + (cell ["cell_anchor_point" ][0 ] * _measured_width ),
163180 int (grid_position_y * self ._height / grid_size_y )
164- + self .cell_padding ,
181+ + self .cell_padding
182+ + (cell ["cell_anchor_point" ][1 ] * _measured_height ),
165183 )
166184
167185 self .append (cell ["content" ])
@@ -173,42 +191,84 @@ def _layout_cells(self):
173191
174192 if not hasattr (cell ["content" ], "anchor_point" ):
175193 _bottom_line_loc_y = (
176- cell ["content" ].y + _measured_height + self .cell_padding
177- ) - 1
178- _bottom_line_loc_x = cell ["content" ].x - self .cell_padding
194+ (cell ["content" ].y + _measured_height + self .cell_padding )
195+ - 1
196+ - int (cell ["cell_anchor_point" ][1 ] * _measured_height )
197+ + int (cell ["content" ].height * cell ["cell_anchor_point" ][1 ])
198+ )
179199
180- _top_line_loc_y = cell ["content" ].y - self .cell_padding
181- _top_line_loc_x = cell ["content" ].x - self .cell_padding
200+ _bottom_line_loc_x = (
201+ cell ["content" ].x
202+ - self .cell_padding
203+ - int (cell ["cell_anchor_point" ][0 ] * _measured_width )
204+ + int (cell ["content" ].width * cell ["cell_anchor_point" ][0 ])
205+ )
206+
207+ _top_line_loc_y = (
208+ cell ["content" ].y
209+ - self .cell_padding
210+ - int (cell ["cell_anchor_point" ][1 ] * _measured_height )
211+ + int (cell ["content" ].height * cell ["cell_anchor_point" ][1 ])
212+ )
213+
214+ _top_line_loc_x = (
215+ cell ["content" ].x
216+ - self .cell_padding
217+ - int (cell ["cell_anchor_point" ][0 ] * _measured_width )
218+ + int (cell ["content" ].width * cell ["cell_anchor_point" ][0 ])
219+ )
220+
221+ _right_line_loc_y = (
222+ cell ["content" ].y
223+ - self .cell_padding
224+ - int (cell ["cell_anchor_point" ][1 ] * _measured_height )
225+ + int (cell ["content" ].height * cell ["cell_anchor_point" ][1 ])
226+ )
182227
183- _right_line_loc_y = cell ["content" ].y - self .cell_padding
184228 _right_line_loc_x = (
185- cell ["content" ].x + _measured_width + self .cell_padding
186- ) - 1
229+ (cell ["content" ].x + _measured_width + self .cell_padding )
230+ - 1
231+ - int (cell ["cell_anchor_point" ][0 ] * _measured_width )
232+ + int (cell ["content" ].width * cell ["cell_anchor_point" ][0 ])
233+ )
187234 else :
188235 _bottom_line_loc_y = (
189236 cell ["content" ].anchored_position [1 ]
190237 + _measured_height
191238 + self .cell_padding
239+ - (cell ["cell_anchor_point" ][1 ] * _measured_height )
192240 ) - 1
193241 _bottom_line_loc_x = (
194- cell ["content" ].anchored_position [0 ] - self .cell_padding
242+ cell ["content" ].anchored_position [0 ]
243+ - self .cell_padding
244+ - (cell ["cell_anchor_point" ][0 ] * _measured_width )
195245 )
196246
197247 _top_line_loc_y = (
198- cell ["content" ].anchored_position [1 ] - self .cell_padding
248+ cell ["content" ].anchored_position [1 ]
249+ - self .cell_padding
250+ - (cell ["cell_anchor_point" ][1 ] * _measured_height )
199251 )
200252 _top_line_loc_x = (
201- cell ["content" ].anchored_position [0 ] - self .cell_padding
253+ cell ["content" ].anchored_position [0 ]
254+ - self .cell_padding
255+ - (cell ["cell_anchor_point" ][0 ] * _measured_width )
202256 )
203257
204258 _right_line_loc_y = (
205- cell ["content" ].anchored_position [1 ] - self .cell_padding
259+ cell ["content" ].anchored_position [1 ]
260+ - self .cell_padding
261+ - (cell ["cell_anchor_point" ][1 ] * _measured_height )
206262 )
207263 _right_line_loc_x = (
208- cell ["content" ].anchored_position [0 ]
209- + _measured_width
210- + self .cell_padding
211- ) - 1
264+ (
265+ cell ["content" ].anchored_position [0 ]
266+ + _measured_width
267+ + self .cell_padding
268+ )
269+ - 1
270+ - (cell ["cell_anchor_point" ][0 ] * _measured_width )
271+ )
212272
213273 _horizontal_divider_line = displayio .Shape (
214274 _measured_width + (2 * self .cell_padding ),
@@ -255,31 +315,61 @@ def _layout_cells(self):
255315 for line_obj in self ._divider_lines :
256316 self .remove (line_obj ["tilegrid" ])
257317
258- if grid_position_y == grid_size_y - 1 and (
259- grid_position_y + 1 in self .h_divider_line_rows
318+ """
319+ Only use bottom divider lines on the bottom row. All
320+ other rows rely on top divder lines of the row beneath them.
321+ Add the content_cell_size to the grid_position to account for
322+ areas larger than 1x1 cells. For 1x1 cells this will equal zero
323+ and not change anything.
324+ """
325+ if (
326+ grid_position_y + content_cell_size_y - 1
327+ ) == grid_size_y - 1 and (
328+ (grid_position_y + content_cell_size_y - 1 ) + 1
329+ in self .h_divider_line_rows
260330 ):
261331 self ._divider_lines .append (
262332 {
263333 "shape" : _horizontal_divider_line ,
264334 "tilegrid" : _bottom_divider_tilegrid ,
265335 }
266336 )
337+
338+ """
339+ Every cell whose index is in h_divider_line_rows gets
340+ a top divider line.
341+ """
267342 if grid_position_y in self .h_divider_line_rows :
268343 self ._divider_lines .append (
269344 {
270345 "shape" : _horizontal_divider_line ,
271346 "tilegrid" : _top_divider_tilegrid ,
272347 }
273348 )
349+
350+ """
351+ Every cell whose index is in v_divider_line_cols gets
352+ a left divider line.
353+ """
274354 if grid_position_x in self .v_divider_line_cols :
275355 self ._divider_lines .append (
276356 {
277357 "shape" : _horizontal_divider_line ,
278358 "tilegrid" : _left_divider_tilegrid ,
279359 }
280360 )
281- if grid_position_x == grid_size_x - 1 and (
282- grid_position_x + 1 in self .v_divider_line_cols
361+ """
362+ Only use right divider lines on the right-most column. All
363+ other columns rely on left divider lines of the column to their
364+ left. Add the content_cell_size to the grid_position to account for
365+ areas larger than 1x1 cells. For 1x1 cells this will equal zero
366+ and not change anything.
367+ """
368+ if (
369+ grid_position_x + content_cell_size_x - 1
370+ ) == grid_size_x - 1 and (
371+ (grid_position_x + content_cell_size_x - 1 ) + 1
372+ in self .v_divider_line_cols
283373 ):
284374 self ._divider_lines .append (
285375 {
@@ -291,7 +381,9 @@ def _layout_cells(self):
291381 for line_obj in self ._divider_lines :
292382 self .append (line_obj ["tilegrid" ])
293383
294- def add_content (self , cell_content , grid_position , cell_size ):
384+ def add_content (
385+ self , cell_content , grid_position , cell_size , cell_anchor_point = None
386+ ):
295387 """Add a child to the grid.
296388
297389 :param cell_content: the content to add to this cell e.g. label, button, etc...
@@ -300,8 +392,19 @@ def add_content(self, cell_content, grid_position, cell_size):
300392 x,y coordinates in grid cells. e.g. (1,0)
301393 :param tuple cell_size: the size and shape that the new cell should
302394 occupy. Width and height in cells inside a tuple e.g. (1, 1)
395+ :param tuple cell_anchor_point: a tuple of floats between 0.0 and 1.0.
396+ If passed, this value will override the cell_anchor_point of the GridLayout
397+ for the single cell having it's content added with this function call. If omitted
398+ then the cell_anchor_point from the GridLayout will be used.
303399 :return: None"""
400+
401+ if cell_anchor_point :
402+ _this_cell_anchor_point = cell_anchor_point
403+ else :
404+ _this_cell_anchor_point = self ._cell_anchor_point
405+
304406 sub_view_obj = {
407+ "cell_anchor_point" : _this_cell_anchor_point ,
305408 "content" : cell_content ,
306409 "grid_position" : grid_position ,
307410 "cell_size" : cell_size ,
@@ -326,3 +429,14 @@ def get_cell(self, cell_coordinates):
326429 cell_coordinates
327430 )
328431 )
432+
433+ @property
434+ def cell_size_pixels (self ) -> Tuple [int , int ]:
435+ """
436+ Get the size of a 1x1 cell in pixels. Can be useful for manually
437+ re-positioning content within cells.
438+
439+ :return Tuple[int, int]: A tuple containing the (x, y) size in
440+ pixels of a 1x1 cell in the GridLayout
441+ """
442+ return (self ._width // self .grid_size [0 ], self ._height // self .grid_size [1 ])
0 commit comments