From 2f031fc9e3eb2e56f7d38d969c76e689ddd97fb1 Mon Sep 17 00:00:00 2001 From: Justin Myers Date: Thu, 27 Feb 2025 15:38:32 -0800 Subject: [PATCH 1/7] Remove secrets usage --- .../displayio_layout_hotplug_temp_sensor.py | 25 ++++++++----------- 1 file changed, 11 insertions(+), 14 deletions(-) diff --git a/examples/hotplug_sensor_examples/displayio_layout_hotplug_temp_sensor.py b/examples/hotplug_sensor_examples/displayio_layout_hotplug_temp_sensor.py index 81cd23d..7c2a0dd 100644 --- a/examples/hotplug_sensor_examples/displayio_layout_hotplug_temp_sensor.py +++ b/examples/hotplug_sensor_examples/displayio_layout_hotplug_temp_sensor.py @@ -11,6 +11,7 @@ """ import time +from os import getenv import adafruit_tmp117 import adafruit_touchscreen @@ -275,7 +276,7 @@ def list(self): ) else: print("\nTabLayout test with I2C Temperature sensor and I2C Realtime clock") -print("Add your WiFi SSID, WiFi password and Timezone in file: secrets.h\n") +print("Add your WiFi SSID, WiFi password and Timezone in file: settings.toml\n") if myVars.read("my_debug"): while not i2c.try_lock(): @@ -303,13 +304,9 @@ def list(self): # NOTE: there is also the board.SD_CARD_DETECT pin (33)(but I don't know yet how to interface it) #### -# you'll need to pass in an io username and key -# Get wifi details and more from a secrets.py file -try: - from secrets import secrets -except ImportError: - print("WiFi secrets are kept in secrets.py, please add them there!") - raise +# Get WiFi details, ensure these are setup in settings.toml +ssid = getenv("CIRCUITPY_WIFI_SSID") +password = getenv("CIRCUITPY_WIFI_PASSWORD") if myVars.read("my_debug"): if esp.status == adafruit_esp32spi.WL_IDLE_STATUS: @@ -320,13 +317,13 @@ def list(self): for ap in esp.scan_networks(): print("\t%s\t\tRSSI: %d" % (str(ap["ssid"], "utf-8"), ap["rssi"])) -# Get our username, key and desired timezone -location = secrets.get("timezone", None) +# Get our desired timezone +location = getenv("timezone", None) print("\nConnecting to AP...") while not esp.is_connected: try: - esp.connect_AP(secrets["ssid"], secrets["password"]) + esp.connect_AP(ssid, password) except RuntimeError as e: print("could not connect to AP, retrying: ", e) continue @@ -359,7 +356,7 @@ def refresh_from_NTP(): myVars.write("online_time_present", True) myVars.write("ntp_refresh", False) # Get the current time in seconds since Jan 1, 1970 and correct it for local timezone - # (defined in secrets.h) + # (defined in settings.toml) ntp_current_time = time.time() if myVars.read("my_debug"): print(f"Seconds since Jan 1, 1970: {ntp_current_time} seconds") @@ -377,9 +374,9 @@ def refresh_from_NTP(): # Initialize the NTP object ntp = NTP(esp) - location = secrets.get("timezone", location) + location = getenv("timezone", location) if myVars.read("my_debug"): - print("location (from secrets.h) = ", location) + print(f"location (from settings.toml) = {location}") if location == "Europe/Lisbon": if myVars.read("my_debug"): print("Using timezone Europe/Lisbon") From 746b1886b58dd4ef10ac369a7f59c4db7ad886ec Mon Sep 17 00:00:00 2001 From: foamyguy Date: Wed, 2 Apr 2025 10:14:02 -0500 Subject: [PATCH 2/7] add pop_content, rename get_cell to get_content --- .../layouts/grid_layout.py | 46 ++++++++++++++++--- 1 file changed, 39 insertions(+), 7 deletions(-) diff --git a/adafruit_displayio_layout/layouts/grid_layout.py b/adafruit_displayio_layout/layouts/grid_layout.py index 5d2be9b..957cb91 100644 --- a/adafruit_displayio_layout/layouts/grid_layout.py +++ b/adafruit_displayio_layout/layouts/grid_layout.py @@ -393,31 +393,63 @@ def add_content( if layout_cells: self._layout_cells() - def get_cell(self, cell_coordinates: Tuple[int, int]) -> displayio.Group: + def get_content(self, grid_position: Tuple[int, int]) -> displayio.Group: """ - Return a cells content based on the cell_coordinates. Raises + Return a cells content based on the grid_position. Raises KeyError if coordinates were not found in the GridLayout. - :param tuple cell_coordinates: the coordinates to lookup in the grid + :param tuple grid_position: the coordinates to lookup in the grid :return: the displayio content object at those coordinates """ for index, cell in enumerate(self._cell_content_list): # exact location 1x1 cell - if cell["grid_position"] == cell_coordinates: + if cell["grid_position"] == grid_position: return self._cell_content_list[index]["content"] # multi-spanning cell, any size bigger than 1x1 if ( cell["grid_position"][0] - <= cell_coordinates[0] + <= grid_position[0] < cell["grid_position"][0] + cell["cell_size"][0] and cell["grid_position"][1] - <= cell_coordinates[1] + <= grid_position[1] < cell["grid_position"][1] + cell["cell_size"][1] ): return self._cell_content_list[index]["content"] - raise KeyError(f"GridLayout does not contain cell at coordinates {cell_coordinates}") + raise KeyError(f"GridLayout does not contain content at coordinates {grid_position}") + + def pop_content(self, grid_position: Tuple[int, int]) -> None: + """ + Remove and return a cells content based on the grid_position. Raises + KeyError if coordinates were not found in the GridLayout. + + :param tuple grid_position: the coordinates to lookup in the grid + :return: the displayio content object at those coordinates + """ + for index, cell in enumerate(self._cell_content_list): + # exact location 1x1 cell + if cell["grid_position"] == grid_position: + _found = self._cell_content_list.pop(index) + self._layout_cells() + self.remove(_found["content"]) + return _found["content"] + + # multi-spanning cell, any size bigger than 1x1 + if ( + cell["grid_position"][0] + <= grid_position[0] + < cell["grid_position"][0] + cell["cell_size"][0] + and cell["grid_position"][1] + <= grid_position[1] + < cell["grid_position"][1] + cell["cell_size"][1] + ): + _found = self._cell_content_list.pop(index) + self._layout_cells() + self.remove(_found["content"]) + return _found["content"] + + raise KeyError(f"GridLayout does not contain content at coordinates {grid_position}") @property def cell_size_pixels(self) -> Tuple[int, int]: From e655978e468482b2f431630ee6dbeaddee3a60fb Mon Sep 17 00:00:00 2001 From: foamyguy Date: Fri, 30 May 2025 11:41:02 -0500 Subject: [PATCH 3/7] displayio api updates --- adafruit_displayio_layout/layouts/tab_layout.py | 5 +++-- adafruit_displayio_layout/widgets/flip_input.py | 8 +++++--- adafruit_displayio_layout/widgets/icon_animated.py | 6 +++--- requirements.txt | 1 + 4 files changed, 12 insertions(+), 8 deletions(-) diff --git a/adafruit_displayio_layout/layouts/tab_layout.py b/adafruit_displayio_layout/layouts/tab_layout.py index ac5875d..84f70fb 100644 --- a/adafruit_displayio_layout/layouts/tab_layout.py +++ b/adafruit_displayio_layout/layouts/tab_layout.py @@ -28,6 +28,7 @@ from adafruit_bitmap_font.bdf import BDF from adafruit_bitmap_font.pcf import PCF + from circuitpython_typing.displayio import AnyDisplay from fontio import BuiltinFont except ImportError: pass @@ -53,7 +54,7 @@ class TabLayout(displayio.Group): :param int x: x location the layout should be placed. Pixel coordinates. :param int y: y location the layout should be placed. Pixel coordinates. - :param displayio.Display display: The Display object to show the tab layout on. + :param AnyDisplay display: The Display object to show the tab layout on. :param int tab_text_scale: Size of the text shown in the tabs. Whole numbers 1 and greater are valid :param Optional[Union[BuiltinFont, BDF, PCF]] custom_font: A pre-loaded font object to use @@ -75,7 +76,7 @@ def __init__( self, x: int = 0, y: int = 0, - display: Optional[displayio.Display] = None, + display: Optional[AnyDisplay] = None, tab_text_scale: int = 1, custom_font: Optional[Union[BuiltinFont, BDF, PCF]] = terminalio.FONT, inactive_tab_spritesheet: Optional[str] = None, diff --git a/adafruit_displayio_layout/widgets/flip_input.py b/adafruit_displayio_layout/widgets/flip_input.py index cbdae52..83156dc 100644 --- a/adafruit_displayio_layout/widgets/flip_input.py +++ b/adafruit_displayio_layout/widgets/flip_input.py @@ -38,6 +38,8 @@ try: from typing import Any, List, Optional, Tuple + + from circuitpython_typing.displayio import AnyDisplay except ImportError: pass @@ -54,7 +56,7 @@ class FlipInput(Widget, Control): :param int x: pixel position :param int y: pixel position - :param displayio.Display display: the display where the widget will be displayed + :param AnyDisplay display: the display where the widget will be displayed :param value_list: the list of strings that will be displayed :type value_list: List[str] :param Font font: the font used for the text (defaults to ``terminalio.FONT``) @@ -87,7 +89,7 @@ class FlipInput(Widget, Control): def __init__( self, - display: displayio.Display, + display: AnyDisplay, *, value_list: List[str], font: FONT = FONT, @@ -586,7 +588,7 @@ def _blit_constrained( # _animate_bitmap - performs animation of scrolling between two bitmaps def _animate_bitmap( - display: displayio.Display, + display: AnyDisplay, target_bitmap: displayio.Bitmap, bitmap1: displayio.Bitmap, bitmap1_offset: Tuple[int, int], diff --git a/adafruit_displayio_layout/widgets/icon_animated.py b/adafruit_displayio_layout/widgets/icon_animated.py index 2e43821..f63fd25 100644 --- a/adafruit_displayio_layout/widgets/icon_animated.py +++ b/adafruit_displayio_layout/widgets/icon_animated.py @@ -37,7 +37,7 @@ try: from typing import Any, Optional, Tuple - from busdisplay import BusDisplay + from circuitpython_typing.displayio import AnyDisplay except ImportError: pass @@ -86,7 +86,7 @@ class IconAnimated(IconWidget): @classmethod def init_class( cls, - display: Optional[BusDisplay], + display: Optional[AnyDisplay], max_scale: float = 1.5, max_icon_size: Tuple[int, int] = (80, 80), max_color_depth: int = 256, @@ -100,7 +100,7 @@ def init_class( ``IconAnimated.init_class(display=board.DISPLAY, max_scale=1.5, max_icon_size=(80,80), max_color_depth=256)`` - :param displayio.Display display: The display where the icons will be displayed. + :param AnyDisplay display: The display where the icons will be displayed. :param float max_scale: The maximum zoom of the any of the icons, should be >= 1.0, (default: 1.5) :param max_icon_size: The maximum (x,y) pixel dimensions of any `IconAnimated` bitmap size diff --git a/requirements.txt b/requirements.txt index 1cfb615..433415f 100644 --- a/requirements.txt +++ b/requirements.txt @@ -8,3 +8,4 @@ adafruit-circuitpython-bitmap-font adafruit-circuitpython-display-text adafruit-circuitpython-imageload adafruit-circuitpython-display-shapes +adafruit-circuitpython-typing From 5d089bc557813a5624e6d3e6bac6c9e9777bd0ff Mon Sep 17 00:00:00 2001 From: Shubham Patel <165564832+shubham0x13@users.noreply.github.com> Date: Thu, 5 Jun 2025 10:49:05 +0530 Subject: [PATCH 4/7] Add anchor method --- adafruit_displayio_layout/layouts/__init__.py | 61 +++++++++++++++++++ 1 file changed, 61 insertions(+) diff --git a/adafruit_displayio_layout/layouts/__init__.py b/adafruit_displayio_layout/layouts/__init__.py index e69de29..7d9f684 100644 --- a/adafruit_displayio_layout/layouts/__init__.py +++ b/adafruit_displayio_layout/layouts/__init__.py @@ -0,0 +1,61 @@ +# SPDX-FileCopyrightText: 2025 Shubham Patel +# +# SPDX-License-Identifier: MIT + +from micropython import const + +try: + from typing import Tuple +except ImportError: + pass + +# Anchor point constants for anchor method. +ANCHOR_TOP_LEFT = const((0.0, 0.0)) +ANCHOR_TOP_CENTER = const((0.5, 0.0)) +ANCHOR_TOP_RIGHT = const((1.0, 0.0)) +ANCHOR_CENTER_LEFT = const((0.0, 0.5)) +ANCHOR_CENTER = const((0.5, 0.5)) +ANCHOR_CENTER_RIGHT = const((1.0, 0.5)) +ANCHOR_BOTTOM_LEFT = const((0.0, 1.0)) +ANCHOR_BOTTOM_CENTER = const((0.5, 1.0)) +ANCHOR_BOTTOM_RIGHT = const((1.0, 1.0)) + + +def anchor(obj, anchor: Tuple[float, float], width: int, height: int) -> None: + """Helper to position a display object on screen using a defined anchor point. + + Sets `anchor_point` and `anchored_position` for display elements such as `Label`, + `Widget`, `AnchoredTileGrid`, or `AnchoredGroup`. + + :param obj: The object to be positioned. Must support `anchor_point` and `anchored_position`. + :param anchor: One of the predefined anchor constants (e.g. ANCHOR_TOP_LEFT, ANCHOR_CENTER) + :param width: Width of the display in pixels + :param height: Height of the display in pixels + """ + if not hasattr(obj, "anchor_point") or not hasattr(obj, "anchored_position"): + raise AttributeError( + "Object must have both `anchor_point` and `anchored_position` attributes." + ) + + if anchor not in { + ANCHOR_TOP_LEFT, + ANCHOR_TOP_CENTER, + ANCHOR_TOP_RIGHT, + ANCHOR_CENTER_LEFT, + ANCHOR_CENTER, + ANCHOR_CENTER_RIGHT, + ANCHOR_BOTTOM_LEFT, + ANCHOR_BOTTOM_CENTER, + ANCHOR_BOTTOM_RIGHT, + }: + raise ValueError( + "Anchor must be one of: ANCHOR_TOP_LEFT, ANCHOR_TOP_CENTER, ANCHOR_TOP_RIGHT,\n" + "ANCHOR_CENTER_LEFT, ANCHOR_CENTER, ANCHOR_CENTER_RIGHT,\n" + "ANCHOR_BOTTOM_LEFT, ANCHOR_BOTTOM_CENTER, ANCHOR_BOTTOM_RIGHT." + ) + + obj.anchor_point = anchor + obj.anchored_position = ( + int(anchor[0] * width), + int(anchor[1] * height), + ) From cfb3f13e9fffb3e2c28a04eb633f2f53608750f3 Mon Sep 17 00:00:00 2001 From: Shubham Patel <165564832+shubham0x13@users.noreply.github.com> Date: Thu, 5 Jun 2025 11:16:35 +0530 Subject: [PATCH 5/7] Add bounds check in add_content --- adafruit_displayio_layout/layouts/grid_layout.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/adafruit_displayio_layout/layouts/grid_layout.py b/adafruit_displayio_layout/layouts/grid_layout.py index 957cb91..15cfd09 100644 --- a/adafruit_displayio_layout/layouts/grid_layout.py +++ b/adafruit_displayio_layout/layouts/grid_layout.py @@ -378,6 +378,15 @@ def add_content( then the cell_anchor_point from the GridLayout will be used. :return: None""" + grid_x = grid_position[0] + grid_y = grid_position[1] + max_grid_x = self.grid_size[0] + max_grid_y = self.grid_size[1] + if grid_x >= max_grid_x or grid_y >= max_grid_y: + raise ValueError( + f"Grid position {grid_position} is out of bounds for grid size {self.grid_size}" + ) + if cell_anchor_point: _this_cell_anchor_point = cell_anchor_point else: From 74411706db2f863ced498925409b4060a99492c2 Mon Sep 17 00:00:00 2001 From: Shubham Patel <165564832+shubham0x13@users.noreply.github.com> Date: Thu, 5 Jun 2025 11:23:46 +0530 Subject: [PATCH 6/7] Simplify grid bounds unpacking --- adafruit_displayio_layout/layouts/grid_layout.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/adafruit_displayio_layout/layouts/grid_layout.py b/adafruit_displayio_layout/layouts/grid_layout.py index 15cfd09..b57bd16 100644 --- a/adafruit_displayio_layout/layouts/grid_layout.py +++ b/adafruit_displayio_layout/layouts/grid_layout.py @@ -378,10 +378,8 @@ def add_content( then the cell_anchor_point from the GridLayout will be used. :return: None""" - grid_x = grid_position[0] - grid_y = grid_position[1] - max_grid_x = self.grid_size[0] - max_grid_y = self.grid_size[1] + grid_x, grid_y = grid_position + max_grid_x, max_grid_y = self.grid_size if grid_x >= max_grid_x or grid_y >= max_grid_y: raise ValueError( f"Grid position {grid_position} is out of bounds for grid size {self.grid_size}" From 7eac6b8038cae2a7252b1e78539f6b0a674773d4 Mon Sep 17 00:00:00 2001 From: Shubham Patel <165564832+shubham0x13@users.noreply.github.com> Date: Tue, 2 Sep 2025 13:53:57 +0530 Subject: [PATCH 7/7] Update example --- .../displayio_layout_grid_layout_get_cell_test.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/examples/displayio_layout_grid_layout_get_cell_test.py b/examples/displayio_layout_grid_layout_get_cell_test.py index 06ccfd0..66d856a 100644 --- a/examples/displayio_layout_grid_layout_get_cell_test.py +++ b/examples/displayio_layout_grid_layout_get_cell_test.py @@ -3,7 +3,7 @@ # SPDX-License-Identifier: MIT """ Make green and purple rectangles and then update the color -and text values of the labels using the get_cell() function. +and text values of the labels using the get_content() function. """ import board @@ -47,14 +47,14 @@ main_group.append(layout) -layout.get_cell((0, 0)).text = "Happy" -layout.get_cell((1, 0)).text = "Circuit" +layout.get_content((0, 0)).text = "Happy" +layout.get_content((1, 0)).text = "Circuit" -layout.get_cell((0, 1)).text = "Python" -layout.get_cell((1, 1)).text = "Day" +layout.get_content((0, 1)).text = "Python" +layout.get_content((1, 1)).text = "Day" -layout.get_cell((0, 1)).background_color = 0x007700 -layout.get_cell((1, 1)).background_color = 0x770077 +layout.get_content((0, 1)).background_color = 0x007700 +layout.get_content((1, 1)).background_color = 0x770077 while True: pass