diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..21c125c --- /dev/null +++ b/.gitattributes @@ -0,0 +1,11 @@ +# SPDX-FileCopyrightText: 2024 Justin Myers for Adafruit Industries +# +# SPDX-License-Identifier: Unlicense + +.py text eol=lf +.rst text eol=lf +.txt text eol=lf +.yaml text eol=lf +.toml text eol=lf +.license text eol=lf +.md text eol=lf diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 70ade69..ff19dde 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,42 +1,21 @@ -# SPDX-FileCopyrightText: 2020 Diego Elio Pettenò +# SPDX-FileCopyrightText: 2024 Justin Myers for Adafruit Industries # # SPDX-License-Identifier: Unlicense repos: - - repo: https://github.com/python/black - rev: 23.3.0 - hooks: - - id: black - - repo: https://github.com/fsfe/reuse-tool - rev: v1.1.2 - hooks: - - id: reuse - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v4.4.0 + rev: v4.5.0 hooks: - id: check-yaml - id: end-of-file-fixer - id: trailing-whitespace - - repo: https://github.com/pycqa/pylint - rev: v2.17.4 + - repo: https://github.com/astral-sh/ruff-pre-commit + rev: v0.3.4 hooks: - - id: pylint - name: pylint (library code) - types: [python] - args: - - --disable=consider-using-f-string - exclude: "^(docs/|examples/|tests/|setup.py$)" - - id: pylint - name: pylint (example code) - description: Run pylint rules on "examples/*.py" files - types: [python] - files: "^examples/" - args: - - --disable=missing-docstring,invalid-name,consider-using-f-string,duplicate-code - - id: pylint - name: pylint (test code) - description: Run pylint rules on "tests/*.py" files - types: [python] - files: "^tests/" - args: - - --disable=missing-docstring,consider-using-f-string,duplicate-code + - id: ruff-format + - id: ruff + args: ["--fix"] + - repo: https://github.com/fsfe/reuse-tool + rev: v3.0.1 + hooks: + - id: reuse diff --git a/.pylintrc b/.pylintrc deleted file mode 100644 index f945e92..0000000 --- a/.pylintrc +++ /dev/null @@ -1,399 +0,0 @@ -# SPDX-FileCopyrightText: 2017 Scott Shawcroft, written for Adafruit Industries -# -# SPDX-License-Identifier: Unlicense - -[MASTER] - -# A comma-separated list of package or module names from where C extensions may -# be loaded. Extensions are loading into the active Python interpreter and may -# run arbitrary code -extension-pkg-whitelist= - -# Add files or directories to the ignore-list. They should be base names, not -# paths. -ignore=CVS - -# Add files or directories matching the regex patterns to the ignore-list. The -# regex matches against base names, not paths. -ignore-patterns= - -# Python code to execute, usually for sys.path manipulation such as -# pygtk.require(). -#init-hook= - -# Use multiple processes to speed up Pylint. -jobs=1 - -# List of plugins (as comma separated values of python modules names) to load, -# usually to register additional checkers. -load-plugins=pylint.extensions.no_self_use - -# Pickle collected data for later comparisons. -persistent=yes - -# Specify a configuration file. -#rcfile= - -# Allow loading of arbitrary C extensions. Extensions are imported into the -# active Python interpreter and may run arbitrary code. -unsafe-load-any-extension=no - - -[MESSAGES CONTROL] - -# Only show warnings with the listed confidence levels. Leave empty to show -# all. Valid levels: HIGH, INFERENCE, INFERENCE_FAILURE, UNDEFINED -confidence= - -# Disable the message, report, category or checker with the given id(s). You -# can either give multiple identifiers separated by comma (,) or put this -# option multiple times (only on the command line, not in the configuration -# file where it should appear only once).You can also use "--disable=all" to -# disable everything first and then reenable specific checks. For example, if -# you want to run only the similarities checker, you can use "--disable=all -# --enable=similarities". If you want to run only the classes checker, but have -# no Warning level messages displayed, use"--disable=all --enable=classes -# --disable=W" -# disable=import-error,raw-checker-failed,bad-inline-option,locally-disabled,file-ignored,suppressed-message,useless-suppression,deprecated-pragma,deprecated-str-translate-call -disable=raw-checker-failed,bad-inline-option,locally-disabled,file-ignored,suppressed-message,useless-suppression,deprecated-pragma,import-error,pointless-string-statement,unspecified-encoding - -# Enable the message, report, category or checker with the given id(s). You can -# either give multiple identifier separated by comma (,) or put this option -# multiple time (only on the command line, not in the configuration file where -# it should appear only once). See also the "--disable" option for examples. -enable= - - -[REPORTS] - -# Python expression which should return a note less than 10 (10 is the highest -# note). You have access to the variables errors warning, statement which -# respectively contain the number of errors / warnings messages and the total -# number of statements analyzed. This is used by the global evaluation report -# (RP0004). -evaluation=10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10) - -# Template used to display messages. This is a python new-style format string -# used to format the message information. See doc for all details -#msg-template= - -# Set the output format. Available formats are text, parseable, colorized, json -# and msvs (visual studio).You can also give a reporter class, eg -# mypackage.mymodule.MyReporterClass. -output-format=text - -# Tells whether to display a full report or only the messages -reports=no - -# Activate the evaluation score. -score=yes - - -[REFACTORING] - -# Maximum number of nested blocks for function / method body -max-nested-blocks=5 - - -[LOGGING] - -# Logging modules to check that the string format arguments are in logging -# function parameter format -logging-modules=logging - - -[SPELLING] - -# Spelling dictionary name. Available dictionaries: none. To make it working -# install python-enchant package. -spelling-dict= - -# List of comma separated words that should not be checked. -spelling-ignore-words= - -# A path to a file that contains private dictionary; one word per line. -spelling-private-dict-file= - -# Tells whether to store unknown words to indicated private dictionary in -# --spelling-private-dict-file option instead of raising a message. -spelling-store-unknown-words=no - - -[MISCELLANEOUS] - -# List of note tags to take in consideration, separated by a comma. -# notes=FIXME,XXX,TODO -notes=FIXME,XXX - - -[TYPECHECK] - -# List of decorators that produce context managers, such as -# contextlib.contextmanager. Add to this list to register other decorators that -# produce valid context managers. -contextmanager-decorators=contextlib.contextmanager - -# List of members which are set dynamically and missed by pylint inference -# system, and so shouldn't trigger E1101 when accessed. Python regular -# expressions are accepted. -generated-members= - -# Tells whether missing members accessed in mixin class should be ignored. A -# mixin class is detected if its name ends with "mixin" (case insensitive). -ignore-mixin-members=yes - -# This flag controls whether pylint should warn about no-member and similar -# checks whenever an opaque object is returned when inferring. The inference -# can return multiple potential results while evaluating a Python object, but -# some branches might not be evaluated, which results in partial inference. In -# that case, it might be useful to still emit no-member and other checks for -# the rest of the inferred objects. -ignore-on-opaque-inference=yes - -# List of class names for which member attributes should not be checked (useful -# for classes with dynamically set attributes). This supports the use of -# qualified names. -ignored-classes=optparse.Values,thread._local,_thread._local - -# List of module names for which member attributes should not be checked -# (useful for modules/projects where namespaces are manipulated during runtime -# and thus existing member attributes cannot be deduced by static analysis. It -# supports qualified module names, as well as Unix pattern matching. -ignored-modules=board - -# Show a hint with possible names when a member name was not found. The aspect -# of finding the hint is based on edit distance. -missing-member-hint=yes - -# The minimum edit distance a name should have in order to be considered a -# similar match for a missing member name. -missing-member-hint-distance=1 - -# The total number of similar names that should be taken in consideration when -# showing a hint for a missing member. -missing-member-max-choices=1 - - -[VARIABLES] - -# List of additional names supposed to be defined in builtins. Remember that -# you should avoid to define new builtins when possible. -additional-builtins= - -# Tells whether unused global variables should be treated as a violation. -allow-global-unused-variables=yes - -# List of strings which can identify a callback function by name. A callback -# name must start or end with one of those strings. -callbacks=cb_,_cb - -# A regular expression matching the name of dummy variables (i.e. expectedly -# not used). -dummy-variables-rgx=_+$|(_[a-zA-Z0-9_]*[a-zA-Z0-9]+?$)|dummy|^ignored_|^unused_ - -# Argument names that match this expression will be ignored. Default to name -# with leading underscore -ignored-argument-names=_.*|^ignored_|^unused_ - -# Tells whether we should check for unused import in __init__ files. -init-import=no - -# List of qualified module names which can have objects that can redefine -# builtins. -redefining-builtins-modules=six.moves,future.builtins - - -[FORMAT] - -# Expected format of line ending, e.g. empty (any line ending), LF or CRLF. -# expected-line-ending-format= -expected-line-ending-format=LF - -# Regexp for a line that is allowed to be longer than the limit. -ignore-long-lines=^\s*(# )??$ - -# Number of spaces of indent required inside a hanging or continued line. -indent-after-paren=4 - -# String used as indentation unit. This is usually " " (4 spaces) or "\t" (1 -# tab). -indent-string=' ' - -# Maximum number of characters on a single line. -max-line-length=100 - -# Maximum number of lines in a module -max-module-lines=1000 - -# Allow the body of a class to be on the same line as the declaration if body -# contains single statement. -single-line-class-stmt=no - -# Allow the body of an if to be on the same line as the test if there is no -# else. -single-line-if-stmt=no - - -[SIMILARITIES] - -# Ignore comments when computing similarities. -ignore-comments=yes - -# Ignore docstrings when computing similarities. -ignore-docstrings=yes - -# Ignore imports when computing similarities. -ignore-imports=yes - -# Minimum lines number of a similarity. -min-similarity-lines=12 - - -[BASIC] - -# Regular expression matching correct argument names -argument-rgx=(([a-z][a-z0-9_]{2,30})|(_[a-z0-9_]*))$ - -# Regular expression matching correct attribute names -attr-rgx=(([a-z][a-z0-9_]{2,30})|(_[a-z0-9_]*))$ - -# Bad variable names which should always be refused, separated by a comma -bad-names=foo,bar,baz,toto,tutu,tata - -# Regular expression matching correct class attribute names -class-attribute-rgx=([A-Za-z_][A-Za-z0-9_]{2,30}|(__.*__))$ - -# Regular expression matching correct class names -# class-rgx=[A-Z_][a-zA-Z0-9]+$ -class-rgx=[A-Z_][a-zA-Z0-9_]+$ - -# Regular expression matching correct constant names -const-rgx=(([A-Z_][A-Z0-9_]*)|(__.*__))$ - -# Minimum line length for functions/classes that require docstrings, shorter -# ones are exempt. -docstring-min-length=-1 - -# Regular expression matching correct function names -function-rgx=(([a-z][a-z0-9_]{2,30})|(_[a-z0-9_]*))$ - -# Good variable names which should always be accepted, separated by a comma -# good-names=i,j,k,ex,Run,_ -good-names=r,g,b,w,i,j,k,n,x,y,z,ex,ok,Run,_ - -# Include a hint for the correct naming format with invalid-name -include-naming-hint=no - -# Regular expression matching correct inline iteration names -inlinevar-rgx=[A-Za-z_][A-Za-z0-9_]*$ - -# Regular expression matching correct method names -method-rgx=(([a-z][a-z0-9_]{2,30})|(_[a-z0-9_]*))$ - -# Regular expression matching correct module names -module-rgx=(([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9]+))$ - -# Colon-delimited sets of names that determine each other's naming style when -# the name regexes allow several styles. -name-group= - -# Regular expression which should only match function or class names that do -# not require a docstring. -no-docstring-rgx=^_ - -# List of decorators that produce properties, such as abc.abstractproperty. Add -# to this list to register other decorators that produce valid properties. -property-classes=abc.abstractproperty - -# Regular expression matching correct variable names -variable-rgx=(([a-z][a-z0-9_]{2,30})|(_[a-z0-9_]*))$ - - -[IMPORTS] - -# Allow wildcard imports from modules that define __all__. -allow-wildcard-with-all=no - -# Analyse import fallback blocks. This can be used to support both Python 2 and -# 3 compatible code, which means that the block might have code that exists -# only in one or another interpreter, leading to false positives when analysed. -analyse-fallback-blocks=no - -# Deprecated modules which should not be used, separated by a comma -deprecated-modules=optparse,tkinter.tix - -# Create a graph of external dependencies in the given file (report RP0402 must -# not be disabled) -ext-import-graph= - -# Create a graph of every (i.e. internal and external) dependencies in the -# given file (report RP0402 must not be disabled) -import-graph= - -# Create a graph of internal dependencies in the given file (report RP0402 must -# not be disabled) -int-import-graph= - -# Force import order to recognize a module as part of the standard -# compatibility libraries. -known-standard-library= - -# Force import order to recognize a module as part of a third party library. -known-third-party=enchant - - -[CLASSES] - -# List of method names used to declare (i.e. assign) instance attributes. -defining-attr-methods=__init__,__new__,setUp - -# List of member names, which should be excluded from the protected access -# warning. -exclude-protected=_asdict,_fields,_replace,_source,_make - -# List of valid names for the first argument in a class method. -valid-classmethod-first-arg=cls - -# List of valid names for the first argument in a metaclass class method. -valid-metaclass-classmethod-first-arg=mcs - - -[DESIGN] - -# Maximum number of arguments for function / method -max-args=5 - -# Maximum number of attributes for a class (see R0902). -# max-attributes=7 -max-attributes=11 - -# Maximum number of boolean expressions in a if statement -max-bool-expr=5 - -# Maximum number of branch for function / method body -max-branches=12 - -# Maximum number of locals for function / method body -max-locals=15 - -# Maximum number of parents for a class (see R0901). -max-parents=7 - -# Maximum number of public methods for a class (see R0904). -max-public-methods=20 - -# Maximum number of return / yield for function / method body -max-returns=6 - -# Maximum number of statements in function / method body -max-statements=50 - -# Minimum number of public methods for a class (see R0903). -min-public-methods=1 - - -[EXCEPTIONS] - -# Exceptions that will emit a warning when being caught. Defaults to -# "Exception" -overgeneral-exceptions=builtins.Exception diff --git a/README.rst b/README.rst index a797fcd..b9628db 100644 --- a/README.rst +++ b/README.rst @@ -13,9 +13,9 @@ Introduction :target: https://github.com/adafruit/Adafruit_CircuitPython_DisplayIO_Layout/actions :alt: Build Status -.. image:: https://img.shields.io/badge/code%20style-black-000000.svg - :target: https://github.com/psf/black - :alt: Code Style: Black +.. image:: https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json + :target: https://github.com/astral-sh/ruff + :alt: Code Style: Ruff CircuitPython helper library for displayio layouts and widgets. 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), + ) diff --git a/adafruit_displayio_layout/layouts/grid_layout.py b/adafruit_displayio_layout/layouts/grid_layout.py index fd06895..b57bd16 100644 --- a/adafruit_displayio_layout/layouts/grid_layout.py +++ b/adafruit_displayio_layout/layouts/grid_layout.py @@ -22,6 +22,7 @@ https://github.com/adafruit/circuitpython/releases """ + try: # Used only for typing from typing import Any, List, Optional, Tuple, Union @@ -29,6 +30,7 @@ pass import math + import displayio from vectorio import Rectangle @@ -58,8 +60,6 @@ class GridLayout(displayio.Group): """ - # pylint: disable=too-many-arguments - # pylint: disable=too-many-instance-attributes def __init__( self, x: int, @@ -106,9 +106,7 @@ def __init__( self.v_divider_line_cols.append(_x) # 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 - ): + 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): @@ -116,7 +114,6 @@ def layout_cells(self): self._layout_cells() def _layout_cells(self) -> None: - # pylint: disable=too-many-locals, too-many-branches, too-many-statements for line_obj in self._divider_lines: self.remove(line_obj["rect"]) for cell in self._cell_content_list: @@ -310,11 +307,8 @@ def _layout_cells(self) -> None: 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 + 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( { @@ -350,11 +344,8 @@ def _layout_cells(self) -> None: 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 + 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( { @@ -387,6 +378,13 @@ def add_content( then the cell_anchor_point from the GridLayout will be used. :return: None""" + 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}" + ) + if cell_anchor_point: _this_cell_anchor_point = cell_anchor_point else: @@ -402,35 +400,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( - "GridLayout does not contain cell at coordinates {}".format( - 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]: diff --git a/adafruit_displayio_layout/layouts/linear_layout.py b/adafruit_displayio_layout/layouts/linear_layout.py index 3bcd6cb..9815300 100644 --- a/adafruit_displayio_layout/layouts/linear_layout.py +++ b/adafruit_displayio_layout/layouts/linear_layout.py @@ -54,7 +54,7 @@ def __init__( self.x = x self.y = y self.padding = padding - if orientation not in [self.VERTICAL_ORIENTATION, self.HORIZONTAL_ORIENTATION]: + if orientation not in {self.VERTICAL_ORIENTATION, self.HORIZONTAL_ORIENTATION}: raise ValueError( "Orientation must be either LinearLayout.VERTICAL_ORIENTATION" " or LinearLayout.HORIZONTAL_ORIENTATION" @@ -77,7 +77,6 @@ def add_content(self, content): self._layout() def _layout(self): - # pylint: disable=too-many-branches, protected-access self._prev_content_end = 0 for _, content in enumerate(self._content_list): @@ -100,14 +99,10 @@ def _layout(self): else: content.x = self._prev_content_end if not hasattr(content, "tile_width"): - self._prev_content_end = ( - content.x + content.width + (self.padding * 2) - ) + self._prev_content_end = content.x + content.width + (self.padding * 2) else: self._prev_content_end = ( - content.x - + (content.width * content.tile_width) - + (self.padding * 2) + content.x + (content.width * content.tile_width) + (self.padding * 2) ) else: # use anchor point content.anchor_point = ( @@ -130,9 +125,7 @@ def _layout(self): else: original_achored_pos_y = ( - content.anchored_position[1] - if content.anchored_position is not None - else 0 + content.anchored_position[1] if content.anchored_position is not None else 0 ) content.anchored_position = ( diff --git a/adafruit_displayio_layout/layouts/page_layout.py b/adafruit_displayio_layout/layouts/page_layout.py index 040ccbd..8e69c63 100644 --- a/adafruit_displayio_layout/layouts/page_layout.py +++ b/adafruit_displayio_layout/layouts/page_layout.py @@ -22,9 +22,9 @@ https://github.com/adafruit/circuitpython/releases """ + try: # Used only for typing - # pylint: disable=unused-import from typing import Tuple except ImportError: @@ -90,16 +90,12 @@ def _check_args(self, page_name, page_index): raise AttributeError("Must pass either page_name or page_index") if page_index is not None and page_name is not None: - raise AttributeError( - "Must pass either page_name or page_index only one or the other" - ) + raise AttributeError("Must pass either page_name or page_index only one or the other") if page_index is not None: if page_index >= len(self.page_content_list): raise KeyError( - "KeyError at index {} in list length {}".format( - page_index, len(self.page_content_list) - ), + f"KeyError at index {page_index} in list length {len(self.page_content_list)}", ) if page_name is not None: @@ -110,7 +106,7 @@ def _check_args(self, page_name, page_index): _found = True if not _found: - raise KeyError("Page with name {} not found".format(page_name)) + raise KeyError(f"Page with name {page_name} not found") def get_page(self, page_name=None, page_index=None): """ @@ -133,9 +129,7 @@ def get_page(self, page_name=None, page_index=None): return cell raise KeyError( - "PageLayout does not contain page: {}".format( - page_index if page_index else page_name - ) + f"PageLayout does not contain page: {page_index if page_index else page_name}" ) def show_page(self, page_name=None, page_index=None): @@ -205,11 +199,10 @@ def next_page(self, loop=True): if self._cur_showing_index + 1 < len(self.page_content_list): self.show_page(page_index=self._cur_showing_index + 1) + elif not loop: + print("No more pages") else: - if not loop: - print("No more pages") - else: - self.show_page(page_index=0) + self.show_page(page_index=0) def previous_page(self, loop=True): """ @@ -219,8 +212,7 @@ def previous_page(self, loop=True): """ if self._cur_showing_index - 1 >= 0: self.show_page(page_index=self._cur_showing_index - 1) + elif not loop: + print("No more pages") else: - if not loop: - print("No more pages") - else: - self.show_page(page_index=len(self.page_content_list) - 1) + self.show_page(page_index=len(self.page_content_list) - 1) diff --git a/adafruit_displayio_layout/layouts/tab_layout.py b/adafruit_displayio_layout/layouts/tab_layout.py index e63f021..84f70fb 100644 --- a/adafruit_displayio_layout/layouts/tab_layout.py +++ b/adafruit_displayio_layout/layouts/tab_layout.py @@ -22,19 +22,23 @@ https://github.com/adafruit/circuitpython/releases """ + try: - from typing import Optional, Union, Tuple - from fontio import BuiltinFont + from typing import Optional, Tuple, Union + 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 -import terminalio -import displayio import adafruit_imageload +import displayio +import terminalio from adafruit_display_text.bitmap_label import Label from adafruit_imageload.tilegrid_inflator import inflate_tilegrid + from adafruit_displayio_layout.layouts.page_layout import PageLayout __version__ = "0.0.0+auto.0" @@ -50,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 @@ -68,13 +72,11 @@ class TabLayout(displayio.Group): :param int tab_count: How many tabs to draw in the layout. Positive whole numbers are valid. """ - # pylint: disable=too-many-instance-attributes, too-many-arguments, invalid-name, too-many-branches - 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, @@ -86,8 +88,7 @@ def __init__( tab_count: int = None, ): if display is None: - # pylint: disable=import-outside-toplevel - import board + import board # noqa: PLC0415, non-top-level-import if hasattr(board, "DISPLAY"): display = board.DISPLAY @@ -100,9 +101,7 @@ def __init__( super().__init__(x=x, y=y) self.tab_count = tab_count - self._active_bmp, self._active_palette = adafruit_imageload.load( - showing_tab_spritesheet - ) + self._active_bmp, self._active_palette = adafruit_imageload.load(showing_tab_spritesheet) self._inactive_bmp, self._inactive_palette = adafruit_imageload.load( inactive_tab_spritesheet ) @@ -121,9 +120,7 @@ def __init__( for index in inactive_tab_transparent_indexes: self._inactive_palette.make_transparent(index) else: - raise AttributeError( - "inactive_tab_transparent_indexes must be int or tuple" - ) + raise AttributeError("inactive_tab_transparent_indexes must be int or tuple") self.tab_height = self._active_bmp.height self.display = display @@ -147,8 +144,7 @@ def _draw_tabs(self): bmp_obj=self._inactive_bmp, bmp_palette=self._inactive_palette, target_size=( - (self.display.width // self.tab_count) - // (self._active_bmp.width // 3), + (self.display.width // self.tab_count) // (self._active_bmp.width // 3), 3, ), ) @@ -165,8 +161,7 @@ def _draw_tabs(self): _tab_label.anchor_point = (0.5, 0.5) _tab_label.anchored_position = ( - _tab_tilegrid.x - + ((_tab_tilegrid.width * _tab_tilegrid.tile_width) // 2), + _tab_tilegrid.x + ((_tab_tilegrid.width * _tab_tilegrid.tile_width) // 2), (_tab_tilegrid.height * _tab_tilegrid.tile_height) // 2, ) _new_tab_group.append(_tab_label) @@ -282,8 +277,6 @@ def handle_touch_events(self, touch_event): if touch_event: if 0 <= touch_event[1] <= self.tab_height: - touched_tab_index = touch_event[0] // ( - self.display.width // self.tab_count - ) + touched_tab_index = touch_event[0] // (self.display.width // self.tab_count) print(f"{touch_event[0]} - {touched_tab_index}") self.showing_page_index = touched_tab_index diff --git a/adafruit_displayio_layout/widgets/cartesian.py b/adafruit_displayio_layout/widgets/cartesian.py index bcb11dc..50ca8a3 100644 --- a/adafruit_displayio_layout/widgets/cartesian.py +++ b/adafruit_displayio_layout/widgets/cartesian.py @@ -21,13 +21,11 @@ """ -# pylint: disable=too-many-lines, too-many-instance-attributes, too-many-arguments -# pylint: disable=too-many-locals, too-many-statements - import displayio import terminalio -from adafruit_display_text import bitmap_label import vectorio +from adafruit_display_text import bitmap_label + from adafruit_displayio_layout.widgets.widget import Widget try: @@ -229,28 +227,20 @@ def __init__( self._valuey = self.height / 100 self._factory = 100 / (self._yrange[1] - self._yrange[0]) - self._tick_bitmap = displayio.Bitmap( - self._tick_line_thickness, self._tick_line_height, 3 - ) + self._tick_bitmap = displayio.Bitmap(self._tick_line_thickness, self._tick_line_height, 3) self._tick_bitmap.fill(1) self._subticks = subticks axesx_height = ( - 2 - + self._axes_line_thickness - + self._font_height - + self._tick_line_height // 2 + 2 + self._axes_line_thickness + self._font_height + self._tick_line_height // 2 ) self._axesx_bitmap = displayio.Bitmap(self.width, axesx_height, 4) self._axesx_bitmap.fill(0) self._axesy_width = ( - 2 - + self._axes_line_thickness - + self._font_width - + self._tick_line_height // 2 + 2 + self._axes_line_thickness + self._font_width + self._tick_line_height // 2 ) self._axesy_bitmap = displayio.Bitmap(self._axesy_width, self.height, 4) @@ -408,20 +398,14 @@ def _draw_ticks(self) -> None: self._font, color=self._font_color, text=text_tick, - x=-shift_label_x - - self._axes_line_thickness - - self._tick_line_height - - 2, + x=-shift_label_x - self._axes_line_thickness - self._tick_line_height - 2, y=0 + self.height - text_dist, ) self.append(tick_text) bitmaptools.fill_region( self._axesy_bitmap, - self._axesy_width - - self._axes_line_thickness - - self._tick_line_height - - 1, + self._axesy_width - self._axes_line_thickness - self._tick_line_height - 1, text_dist, self._axesy_width - self._axes_line_thickness - 1, text_dist + self._tick_line_thickness, @@ -453,9 +437,7 @@ def _draw_pointers(self, x: int, y: int) -> None: self.append(self._pointer) def _calc_local_xy(self, x: int, y: int) -> Tuple[int, int]: - local_x = ( - int((x - self._xrange[0]) * self._factorx * self._valuex) + self._nudge_x - ) + local_x = int((x - self._xrange[0]) * self._factorx * self._valuex) + self._nudge_x # details on `+ (self.height - 1)` : # the bitmap is set to self.width & self.height # but we are only allowed to draw to pixels 0..height-1 and 0..width-1 @@ -473,9 +455,7 @@ def _check_local_y_in_range(self, local_y: int) -> bool: return 0 <= local_y < self.height def _check_local_xy_in_range(self, local_x: int, local_y: int) -> bool: - return self._check_local_x_in_range(local_x) and self._check_local_y_in_range( - local_y - ) + return self._check_local_x_in_range(local_x) and self._check_local_y_in_range(local_y) def _check_x_in_range(self, x: int) -> bool: return self._xrange[0] <= x <= self._xrange[1] @@ -498,30 +478,15 @@ def _add_point(self, x: int, y: int) -> None: if self._verbose: print("") print( - "xy: ({: >4}, {: >4}) " - "_xrange: ({: >4}, {: >4}) " - "_yrange: ({: >4}, {: >4}) " - "".format( - x, - y, - self._xrange[0], - self._xrange[1], - self._yrange[0], - self._yrange[1], - ) + f"xy: ({x: >4}, {y: >4}) " + + f"_xrange: ({self._xrange[0]: >4}, {self._xrange[1]: >4}) " + + f"_yrange: ({self._yrange[0]: >4}, {self._yrange[1]: >4}) " + "" ) print( - "local_*: ({: >4}, {: >4}) " - " width: ({: >4}, {: >4}) " - " height: ({: >4}, {: >4}) " - "".format( - local_x, - local_y, - 0, - self.width, - 0, - self.height, - ) + f"local_*: ({local_x: >4}, {local_y: >4}) " + + f" width: ({0: >4}, {self.width: >4}) " + + f" height: ({0: >4}, {self.height: >4}) " ) if self._check_xy_in_range(x, y): if self._check_local_xy_in_range(local_x, local_y): @@ -536,44 +501,28 @@ def _add_point(self, x: int, y: int) -> None: if not self._check_local_x_in_range(local_x): raise ValueError( "local_x out of range: " - "local_x:{: >4}; _xrange({: >4}, {: >4})" - "".format( - local_x, - 0, - self.width, - ) + f"local_x:{local_x: >4}; _xrange({0: >4}, {self.width: >4})" + "" ) if not self._check_local_y_in_range(local_y): raise ValueError( "local_y out of range: " - "local_y:{: >4}; _yrange({: >4}, {: >4})" - "".format( - local_y, - 0, - self.height, - ) + f"local_y:{local_y: >4}; _yrange({0: >4}, {self.height: >4})" + "" ) else: # for better error messages we check in detail what failed... if not self._check_x_in_range(x): raise ValueError( "x out of range: " - "x:{: >4}; xrange({: >4}, {: >4})" - "".format( - x, - self._xrange[0], - self._xrange[1], - ) + f"x:{x: >4}; xrange({self._xrange[0]: >4}, {self._xrange[1]: >4})" + "" ) if not self._check_y_in_range(y): raise ValueError( "y out of range: " - "y:{: >4}; yrange({: >4}, {: >4})" - "".format( - y, - self._yrange[0], - self._yrange[1], - ) + f"y:{y: >4}; yrange({self._yrange[0]: >4}, {self._yrange[1]: >4})" + "" ) def update_pointer(self, x: int, y: int) -> None: diff --git a/adafruit_displayio_layout/widgets/control.py b/adafruit_displayio_layout/widgets/control.py index f9e5a2b..464804e 100644 --- a/adafruit_displayio_layout/widgets/control.py +++ b/adafruit_displayio_layout/widgets/control.py @@ -30,8 +30,6 @@ __version__ = "0.0.0+auto.0" __repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_DisplayIO_Layout.git" -# pylint: disable=unsubscriptable-object, unnecessary-pass - class Control: """A Control class for responsive elements, including touch response functions for displays. diff --git a/adafruit_displayio_layout/widgets/easing.py b/adafruit_displayio_layout/widgets/easing.py index 9a1e9a9..db2d2d0 100644 --- a/adafruit_displayio_layout/widgets/easing.py +++ b/adafruit_displayio_layout/widgets/easing.py @@ -75,7 +75,6 @@ import math - __version__ = "0.0.0+auto.0" __repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_DisplayIO_Layout.git" @@ -285,7 +284,7 @@ def exponential_easeinout(pos: float) -> float: """ Easing function for animations: Exponential Ease In & Out """ - if pos in (0.0, 1.0): + if pos in {0.0, 1.0}: return pos if pos < 0.5: return 0.5 * math.pow(2, (20 * pos) - 10) @@ -318,8 +317,7 @@ def elastic_easeinout(pos: float) -> float: if pos < 0.5: return 0.5 * math.sin(13 * math.pi * pos) * math.pow(2, 10 * ((2 * pos) - 1)) return 0.5 * ( - math.sin(-13 * math.pi / 2 * ((2 * pos - 1) + 1)) * pow(2, -10 * (2 * pos - 1)) - + 2 + math.sin(-13 * math.pi / 2 * ((2 * pos - 1) + 1)) * pow(2, -10 * (2 * pos - 1)) + 2 ) diff --git a/adafruit_displayio_layout/widgets/flip_input.py b/adafruit_displayio_layout/widgets/flip_input.py index 1474225..83156dc 100644 --- a/adafruit_displayio_layout/widgets/flip_input.py +++ b/adafruit_displayio_layout/widgets/flip_input.py @@ -23,23 +23,23 @@ import gc import time -import displayio -from terminalio import FONT +import displayio from adafruit_display_shapes.triangle import Triangle - from adafruit_display_text import bitmap_label -from adafruit_displayio_layout.widgets.widget import Widget -from adafruit_displayio_layout.widgets.control import Control +from terminalio import FONT -# pylint: disable=reimported +from adafruit_displayio_layout.widgets.control import Control # select the two "easing" functions to use for animations from adafruit_displayio_layout.widgets.easing import back_easeinout as easein from adafruit_displayio_layout.widgets.easing import back_easeinout as easeout +from adafruit_displayio_layout.widgets.widget import Widget try: from typing import Any, List, Optional, Tuple + + from circuitpython_typing.displayio import AnyDisplay except ImportError: pass @@ -48,10 +48,6 @@ __repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_DisplayIO_Layout.git" -# pylint: disable=too-many-arguments, too-many-branches, too-many-statements -# pylint: disable=too-many-locals, too-many-instance-attributes - - class FlipInput(Widget, Control): """A flip style input selector. The value changes based on touch inputs on the two halves of the indicator with optional arrows added. @@ -60,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``) @@ -93,7 +89,7 @@ class FlipInput(Widget, Control): def __init__( self, - display: displayio.Display, + display: AnyDisplay, *, value_list: List[str], font: FONT = FONT, @@ -121,7 +117,6 @@ def __init__( # initialize the Control superclass - # pylint: disable=bad-super-call super(Control, self).__init__() self.value_list = value_list @@ -157,18 +152,14 @@ def __init__( for i, character in enumerate(this_value): glyph = self._font.get_glyph(ord(character)) - if ( - i == 0 - ): # if it's the first character in the string, check the left value + if i == 0: # if it's the first character in the string, check the left value if left is None: left = glyph.dx else: left = min(left, glyph.dx) if right is None: - right = max( - xposition + glyph.dx + glyph.width, xposition + glyph.shift_x - ) + right = max(xposition + glyph.dx + glyph.width, xposition + glyph.shift_x) else: right = max( right, @@ -189,12 +180,7 @@ def __init__( xposition = xposition + glyph.shift_x # Something is wrong if left, right, top, or bottom are still None here - assert ( - right is not None - and left is not None - and top is not None - and bottom is not None - ) + assert right is not None and left is not None and top is not None and bottom is not None self._bounding_box = [ 0, @@ -226,10 +212,7 @@ def __init__( if horizontal: # horizontal orientation, add arrow padding to x-dimension and # alt_padding to y-dimension self.touch_boundary = ( - self._bounding_box[0] - - self._arrow_gap - - arrow_height - - self._arrow_touch_padding, + self._bounding_box[0] - self._arrow_gap - arrow_height - self._arrow_touch_padding, self._bounding_box[1] - self._alt_touch_padding, self._bounding_box[2] + 2 * (self._arrow_gap + arrow_height + self._arrow_touch_padding), @@ -239,10 +222,7 @@ def __init__( # alt_padding to x-dimension self.touch_boundary = ( self._bounding_box[0] - self._alt_touch_padding, - self._bounding_box[1] - - self._arrow_gap - - arrow_height - - self._arrow_touch_padding, + self._bounding_box[1] - self._arrow_gap - arrow_height - self._arrow_touch_padding, self._bounding_box[2] + 2 * self._alt_touch_padding, self._bounding_box[3] + 2 * (self._arrow_gap + arrow_height + self._arrow_touch_padding), @@ -263,11 +243,7 @@ def __init__( if (arrow_color is not None) or (arrow_outline is not None): if horizontal: # horizontal orientation, add left and right arrows - if ( - (arrow_width is not None) - and (arrow_height is not None) - and (arrow_width > 0) - ): + if (arrow_width is not None) and (arrow_height is not None) and (arrow_width > 0): mid_point_y = self._bounding_box[1] + self._bounding_box[3] // 2 self.append( Triangle( @@ -284,13 +260,9 @@ def __init__( self.append( Triangle( - self._bounding_box[0] - + self._bounding_box[2] - + self._arrow_gap, + self._bounding_box[0] + self._bounding_box[2] + self._arrow_gap, mid_point_y - arrow_height // 2, - self._bounding_box[0] - + self._bounding_box[2] - + self._arrow_gap, + self._bounding_box[0] + self._bounding_box[2] + self._arrow_gap, mid_point_y + arrow_height // 2, self._bounding_box[0] + self._bounding_box[2] @@ -301,44 +273,35 @@ def __init__( outline=arrow_outline, ) ) - else: # vertical orientation, add upper and lower arrows - if ( - (arrow_height is not None) - and (arrow_width is not None) - and (arrow_height > 0) - ): - mid_point_x = self._bounding_box[0] + self._bounding_box[2] // 2 - self.append( - Triangle( - mid_point_x - arrow_width // 2, - self._bounding_box[1] - self._arrow_gap, - mid_point_x + arrow_width // 2, - self._bounding_box[1] - self._arrow_gap, - mid_point_x, - self._bounding_box[1] - self._arrow_gap - arrow_height, - fill=arrow_color, - outline=arrow_outline, - ) + elif (arrow_height is not None) and (arrow_width is not None) and (arrow_height > 0): + mid_point_x = self._bounding_box[0] + self._bounding_box[2] // 2 + self.append( + Triangle( + mid_point_x - arrow_width // 2, + self._bounding_box[1] - self._arrow_gap, + mid_point_x + arrow_width // 2, + self._bounding_box[1] - self._arrow_gap, + mid_point_x, + self._bounding_box[1] - self._arrow_gap - arrow_height, + fill=arrow_color, + outline=arrow_outline, ) - self.append( - Triangle( - mid_point_x - arrow_width // 2, - self._bounding_box[1] - + self._bounding_box[3] - + self._arrow_gap, - mid_point_x + arrow_width // 2, - self._bounding_box[1] - + self._bounding_box[3] - + self._arrow_gap, - mid_point_x, - self._bounding_box[1] - + self._bounding_box[3] - + self._arrow_gap - + arrow_height, - fill=arrow_color, - outline=arrow_outline, - ) + ) + self.append( + Triangle( + mid_point_x - arrow_width // 2, + self._bounding_box[1] + self._bounding_box[3] + self._arrow_gap, + mid_point_x + arrow_width // 2, + self._bounding_box[1] + self._bounding_box[3] + self._arrow_gap, + mid_point_x, + self._bounding_box[1] + + self._bounding_box[3] + + self._arrow_gap + + arrow_height, + fill=arrow_color, + outline=arrow_outline, ) + ) # Draw function to update the current value def _update_value(self, new_value: int, animate: bool = True) -> None: @@ -370,9 +333,7 @@ def _update_value(self, new_value: int, animate: bool = True) -> None: palette = displayio.Palette(2) palette.make_transparent(0) palette[1] = self._color - animation_tilegrid = displayio.TileGrid( - animation_bitmap, pixel_shader=palette - ) + animation_tilegrid = displayio.TileGrid(animation_bitmap, pixel_shader=palette) # add bitmap to the animation_group self._animation_group.append(animation_tilegrid) @@ -452,9 +413,7 @@ def contains( # offsetting for self.x and self.y before calling the Control superclass function # ###### - touch_x = ( - touch_point[0] - self.x - ) # adjust touch position for the local position + touch_x = touch_point[0] - self.x # adjust touch position for the local position touch_y = touch_point[1] - self.y return super().contains((touch_x, touch_y, 0)) @@ -475,29 +434,22 @@ def selected(self, touch_point: Tuple[int, int, Optional[int]]) -> None: self.value = self.value - 1 elif ( - (t_b[0] + t_b[2] // 2) - <= (touch_point[0] - self.x) - <= (t_b[0] + t_b[2]) + (t_b[0] + t_b[2] // 2) <= (touch_point[0] - self.x) <= (t_b[0] + t_b[2]) ): # in right half of touch_boundary self.value = self.value + 1 - else: - if ( - t_b[1] <= (touch_point[1] - self.y) < (t_b[1] + t_b[3] // 2) - ): # in upper half of touch_boundary - self.value = self.value + 1 + elif ( + t_b[1] <= (touch_point[1] - self.y) < (t_b[1] + t_b[3] // 2) + ): # in upper half of touch_boundary + self.value = self.value + 1 - elif ( - (t_b[1] + t_b[3] // 2) - <= (touch_point[1] - self.y) - <= (t_b[1] + t_b[3]) - ): # in lower half of touch_boundary - self.value = self.value - 1 + elif ( + (t_b[1] + t_b[3] // 2) <= (touch_point[1] - self.y) <= (t_b[1] + t_b[3]) + ): # in lower half of touch_boundary + self.value = self.value - 1 self._pressed = True # update the state variable - self._last_pressed = ( - time.monotonic() - ) # value changed, so update cool_down timer + self._last_pressed = time.monotonic() # value changed, so update cool_down timer def released(self) -> None: """Response function when the Control is released. Resets the state variables @@ -525,9 +477,7 @@ def value( try: new_value = self.value_list.index(new_value) except ValueError: - print( - 'ValueError: Value "{}" not found in value_list.'.format(new_value) - ) + print(f'ValueError: Value "{new_value}" not found in value_list.') return None new_value = new_value % len(self.value_list) # Update the value @@ -590,9 +540,6 @@ def _draw_position( ) -# pylint: disable=invalid-name - - # _blit_constrained: Copies bitmaps with constraints to the dimensions def _blit_constrained( target: displayio.Bitmap, @@ -633,12 +580,7 @@ def _blit_constrained( if y2 > source.height: y2 = source.height - if ( - (x > target.width) - or (y > target.height) - or (x1 > source.width) - or (y1 > source.height) - ): + if (x > target.width) or (y > target.height) or (x1 > source.width) or (y1 > source.height): return target.blit(x, y, source, x1=x1, y1=y1, x2=x2, y2=y2) @@ -646,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], @@ -683,9 +625,7 @@ def _animate_bitmap( this_time = time.monotonic() target_position = ( start_position - + (end_position - start_position) - * (this_time - start_time) - / animation_time + + (end_position - start_position) * (this_time - start_time) / animation_time ) display.auto_refresh = False diff --git a/adafruit_displayio_layout/widgets/icon_animated.py b/adafruit_displayio_layout/widgets/icon_animated.py index 2e5db2f..f63fd25 100644 --- a/adafruit_displayio_layout/widgets/icon_animated.py +++ b/adafruit_displayio_layout/widgets/icon_animated.py @@ -21,19 +21,23 @@ https://github.com/adafruit/circuitpython/releases """ + import gc import time from math import pi -import bitmaptools -from displayio import TileGrid, Bitmap, Palette + import adafruit_imageload -from adafruit_displayio_layout.widgets.icon_widget import IconWidget -from adafruit_displayio_layout.widgets.easing import quadratic_easeout as easein +import bitmaptools +from displayio import Bitmap, Palette, TileGrid + from adafruit_displayio_layout.widgets.easing import quadratic_easein as easeout +from adafruit_displayio_layout.widgets.easing import quadratic_easeout as easein +from adafruit_displayio_layout.widgets.icon_widget import IconWidget try: from typing import Any, Optional, Tuple - from displayio import Display # pylint: disable=ungrouped-imports + + from circuitpython_typing.displayio import AnyDisplay except ImportError: pass @@ -43,7 +47,6 @@ class IconAnimated(IconWidget): - """ An animated touch enabled widget that holds an icon image loaded with OnDiskBitmap and a text label centered beneath it. Includes optional @@ -76,9 +79,6 @@ class IconAnimated(IconWidget): :type anchored_position: Tuple[int, int] """ - # pylint: disable=bad-super-call, too-many-instance-attributes, too-many-locals - # pylint: disable=too-many-arguments, unused-argument - display = None # The other Class variables are created in Class method `init_class`: # max_scale, bitmap_buffer, palette_buffer @@ -86,7 +86,7 @@ class IconAnimated(IconWidget): @classmethod def init_class( cls, - display: Optional[Display], + 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 @@ -168,9 +168,8 @@ def __init__( if scale > self.__class__.max_scale: print( "Warning - IconAnimated: max_scale is constrained by value of " - "IconAnimated.max_scale set by IconAnimated.init_class(): {}".format( - self.__class__.max_scale - ) + "IconAnimated.max_scale set by " + f"IconAnimated.init_class(): {self.__class__.max_scale}" ) self._scale = max(0, min(scale, self.__class__.max_scale)) @@ -233,9 +232,7 @@ def zoom_animation(self, touch_point: Tuple[int, int, Optional[int]]) -> None: ) # blit the image into the center of the zoom_bitmap # place zoom_bitmap at same location as image - animation_tilegrid = TileGrid( - animation_bitmap, pixel_shader=animation_palette - ) + animation_tilegrid = TileGrid(animation_bitmap, pixel_shader=animation_palette) animation_tilegrid.x = -(animation_bitmap.width - _image.width) // 2 animation_tilegrid.y = -(animation_bitmap.height - _image.height) // 2 diff --git a/adafruit_displayio_layout/widgets/icon_widget.py b/adafruit_displayio_layout/widgets/icon_widget.py index 318d1c6..2a262cc 100644 --- a/adafruit_displayio_layout/widgets/icon_widget.py +++ b/adafruit_displayio_layout/widgets/icon_widget.py @@ -22,11 +22,11 @@ """ - -import terminalio -from displayio import TileGrid, OnDiskBitmap import adafruit_imageload +import terminalio from adafruit_display_text import bitmap_label +from displayio import OnDiskBitmap, TileGrid + from adafruit_displayio_layout.widgets.control import Control from adafruit_displayio_layout.widgets.widget import Widget @@ -41,7 +41,6 @@ class IconWidget(Widget, Control): - """ A touch enabled widget that holds an icon image loaded with adafruit_imageload and a text label centered beneath it. @@ -63,8 +62,6 @@ class IconWidget(Widget, Control): :type anchored_position: Tuple[int, int] """ - # pylint: disable=too-many-arguments - def __init__( self, label_text: str, @@ -72,7 +69,7 @@ def __init__( on_disk: bool = False, transparent_index: Optional[int] = None, label_background: Optional[int] = None, - **kwargs: Any + **kwargs: Any, ) -> None: super().__init__(**kwargs) @@ -120,9 +117,7 @@ def contains( :return: Boolean """ - touch_x = ( - touch_point[0] - self.x - ) # adjust touch position for the local position + touch_x = touch_point[0] - self.x # adjust touch position for the local position touch_y = touch_point[1] - self.y return super().contains((touch_x, touch_y, 0)) diff --git a/adafruit_displayio_layout/widgets/switch_round.py b/adafruit_displayio_layout/widgets/switch_round.py index e583b18..8ce0320 100644 --- a/adafruit_displayio_layout/widgets/switch_round.py +++ b/adafruit_displayio_layout/widgets/switch_round.py @@ -35,14 +35,16 @@ # import time + from adafruit_display_shapes.circle import Circle -from adafruit_display_shapes.roundrect import RoundRect from adafruit_display_shapes.rect import Rect -from adafruit_displayio_layout.widgets.widget import Widget +from adafruit_display_shapes.roundrect import RoundRect + from adafruit_displayio_layout.widgets.control import Control # modify the "easing" function that is imported to change the switch animation behaviour from adafruit_displayio_layout.widgets.easing import back_easeinout as easing +from adafruit_displayio_layout.widgets.widget import Widget try: from typing import Any, Optional, Tuple, Union @@ -55,7 +57,6 @@ class SwitchRound(Widget, Control): - """ .. note:: Jump directly to: @@ -419,9 +420,6 @@ class functions. The `Widget` class handles the overall sizing and positioning """ - # pylint: disable=too-many-instance-attributes, too-many-arguments, too-many-locals - # pylint: disable=too-many-branches, too-many-statements - def __init__( self, x: int = 0, @@ -469,7 +467,6 @@ def __init__( # initialize the Control superclass - # pylint: disable=bad-super-call super(Control, self).__init__() self._horizontal = horizontal @@ -698,9 +695,7 @@ def _draw_position(self, position: float) -> None: position = easing(position) # Get the position offset from the motion function - x_offset, y_offset, _ = self._get_offset_position( - position - ) # ignore angle_offset + x_offset, y_offset, _ = self._get_offset_position(position) # ignore angle_offset # Update the switch and text x- and y-positions self._switch_circle.x = self._switch_initial_x + x_offset @@ -711,9 +706,7 @@ def _draw_position(self, position: float) -> None: self._text_1.y = self._text_1_initial_y + y_offset # Set the color to the correct fade - self._switch_circle.fill = _color_fade( - self._fill_color_off, self._fill_color_on, position - ) + self._switch_circle.fill = _color_fade(self._fill_color_off, self._fill_color_on, position) self._switch_circle.outline = _color_fade( self._outline_color_off, self._outline_color_on, position ) @@ -774,9 +767,7 @@ def _animate_switch(self) -> None: elapsed_time = self._animation_time if self._value: - position = ( - 1 - (elapsed_time) / self._animation_time - ) # fraction from 0 to 1 + position = 1 - (elapsed_time) / self._animation_time # fraction from 0 to 1 else: # fraction from 0 to 1 position = (elapsed_time) / self._animation_time @@ -789,9 +780,7 @@ def _animate_switch(self) -> None: if (position >= 1) and not self._value: self._value = True break - if ( - position <= 0 - ) and self._value: # ensures that the final position is drawn + if (position <= 0) and self._value: # ensures that the final position is drawn self._value = False break @@ -807,9 +796,7 @@ def selected(self, touch_point: Tuple[int, int, Optional[int]]) -> None: self._animate_switch() # show the animation and switch the self._value - touch_x = ( - touch_point[0] - self.x - ) # adjust touch position for the local position + touch_x = touch_point[0] - self.x # adjust touch position for the local position touch_y = touch_point[1] - self.y # Call the parent's .selected function in case there is any work up there. @@ -827,9 +814,7 @@ def contains( :return: Boolean """ - touch_x = ( - touch_point[0] - self.x - ) # adjust touch position for the local position + touch_x = touch_point[0] - self.x # adjust touch position for the local position touch_y = touch_point[1] - self.y return super().contains((touch_x, touch_y, 0)) @@ -945,7 +930,5 @@ def _color_fade( faded_color = [0, 0, 0] for i in range(3): - faded_color[i] = start_color[i] - int( - (start_color[i] - end_color[i]) * fraction - ) + faded_color[i] = start_color[i] - int((start_color[i] - end_color[i]) * fraction) return tuple(faded_color) diff --git a/adafruit_displayio_layout/widgets/widget.py b/adafruit_displayio_layout/widgets/widget.py index 7a376f1..2e6bd8d 100644 --- a/adafruit_displayio_layout/widgets/widget.py +++ b/adafruit_displayio_layout/widgets/widget.py @@ -33,8 +33,6 @@ __version__ = "0.0.0+auto.0" __repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_DisplayIO_Layout.git" -# pylint: disable=too-many-arguments - class Widget(displayio.Group): """ diff --git a/docs/api.rst b/docs/api.rst index a707e30..0cddc93 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -4,6 +4,9 @@ .. If your library file(s) are nested in a directory (e.g. /adafruit_foo/foo.py) .. use this format as the module name: "adafruit_foo.foo" +API Reference +############# + .. automodule:: adafruit_displayio_layout.layouts.grid_layout :members: :private-members: diff --git a/docs/conf.py b/docs/conf.py index a314186..a0e4431 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -1,12 +1,10 @@ -# -*- coding: utf-8 -*- - # SPDX-FileCopyrightText: 2017 Scott Shawcroft, written for Adafruit Industries # # SPDX-License-Identifier: MIT +import datetime import os import sys -import datetime sys.path.insert(0, os.path.abspath("..")) @@ -55,9 +53,7 @@ creation_year = "2021" current_year = str(datetime.datetime.now().year) year_duration = ( - current_year - if current_year == creation_year - else creation_year + " - " + current_year + current_year if current_year == creation_year else creation_year + " - " + current_year ) copyright = year_duration + " Tim Cocks" author = "Tim Cocks" diff --git a/examples/displayio_layout_cartesian_advanced_test.py b/examples/displayio_layout_cartesian_advanced_test.py index e10a765..3acda2e 100644 --- a/examples/displayio_layout_cartesian_advanced_test.py +++ b/examples/displayio_layout_cartesian_advanced_test.py @@ -10,6 +10,7 @@ import board import displayio import terminalio + from adafruit_displayio_layout.widgets.cartesian import Cartesian # Fonts used for the Dial tick labels diff --git a/examples/displayio_layout_cartesian_lineplot.py b/examples/displayio_layout_cartesian_lineplot.py index 14683a0..54ac442 100644 --- a/examples/displayio_layout_cartesian_lineplot.py +++ b/examples/displayio_layout_cartesian_lineplot.py @@ -7,8 +7,10 @@ """ import time + import board import displayio + from adafruit_displayio_layout.widgets.cartesian import Cartesian # create the display on the PyPortal or Clue or PyBadge(for example) diff --git a/examples/displayio_layout_cartesian_simpletest.py b/examples/displayio_layout_cartesian_simpletest.py index e859380..ba1353c 100644 --- a/examples/displayio_layout_cartesian_simpletest.py +++ b/examples/displayio_layout_cartesian_simpletest.py @@ -7,9 +7,11 @@ """ import time + import board import displayio import terminalio + from adafruit_displayio_layout.widgets.cartesian import Cartesian # Fonts used for the Dial tick labels diff --git a/examples/displayio_layout_flip_input_simpletest.py b/examples/displayio_layout_flip_input_simpletest.py index c51c00d..c6c3ce0 100755 --- a/examples/displayio_layout_flip_input_simpletest.py +++ b/examples/displayio_layout_flip_input_simpletest.py @@ -6,13 +6,13 @@ This is a basic demonstration of a FlipInput widget. """ -# pylint: disable=invalid-name - import time + +import adafruit_touchscreen import board import displayio -import adafruit_touchscreen from adafruit_bitmap_font import bitmap_font + from adafruit_displayio_layout.widgets.flip_input import FlipInput display = board.DISPLAY # create the display on the PyPortal, @@ -62,7 +62,7 @@ anchor_point=[0.0, 0.0], anchored_position=[220, 40], color=0xFF2222, # reddish orange color - value_list=["{0:02d}".format(x) for x in range(1, 31 + 1)], + value_list=[f"{x:02d}" for x in range(1, 31 + 1)], # use a list of strings from 01 through 31 # use the {0:02d} format string to always use two digits (e.g. '03') font_scale=5, @@ -75,7 +75,7 @@ anchor_point=[0.5, 1.0], anchored_position=[320 // 2, 240 - 10], color=0xFF2222, # reddish orange color - value_list=["{}".format(x) for x in range(1985, 2022, 1)], + value_list=[f"{x}" for x in range(1985, 2022, 1)], # use a list with values of stringsfrom 1985 to 2022 font=my_font, horizontal=True, # use horizontal arrows diff --git a/examples/displayio_layout_grid_layout_get_cell_test.py b/examples/displayio_layout_grid_layout_get_cell_test.py index 9e6d674..06ccfd0 100644 --- a/examples/displayio_layout_grid_layout_get_cell_test.py +++ b/examples/displayio_layout_grid_layout_get_cell_test.py @@ -5,10 +5,12 @@ Make green and purple rectangles and then update the color and text values of the labels using the get_cell() function. """ + 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.) @@ -31,15 +33,11 @@ _labels = [] _labels.append( - label.Label( - terminalio.FONT, scale=2, x=0, y=0, text="Hello", background_color=0x770077 - ) + 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 - ) + 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")) diff --git a/examples/displayio_layout_gridlayout_dividers.py b/examples/displayio_layout_gridlayout_dividers.py index d7cefab..34e47b9 100644 --- a/examples/displayio_layout_gridlayout_dividers.py +++ b/examples/displayio_layout_gridlayout_dividers.py @@ -4,10 +4,12 @@ """ 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.) @@ -31,15 +33,11 @@ _labels = [] _labels.append( - label.Label( - terminalio.FONT, scale=2, x=0, y=0, text="Hello", background_color=0x770077 - ) + 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 - ) + 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")) diff --git a/examples/displayio_layout_gridlayout_pygame_display_simpletest.py b/examples/displayio_layout_gridlayout_pygame_display_simpletest.py index 15fa6d9..f0a5623 100644 --- a/examples/displayio_layout_gridlayout_pygame_display_simpletest.py +++ b/examples/displayio_layout_gridlayout_pygame_display_simpletest.py @@ -7,12 +7,12 @@ Requires: https://github.com/FoamyGuy/Blinka_Displayio_PyGameDisplay """ + import displayio import terminalio from adafruit_display_text import label from blinka_displayio_pygamedisplay import PyGameDisplay - # Make the display context. Change size if you want from adafruit_displayio_layout.layouts.grid_layout import GridLayout @@ -31,15 +31,11 @@ _labels = [] _labels.append( - label.Label( - terminalio.FONT, scale=2, x=0, y=0, text="Hello", background_color=0x770077 - ) + 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 - ) + 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")) diff --git a/examples/displayio_layout_gridlayout_simpletest.py b/examples/displayio_layout_gridlayout_simpletest.py index 0816dd7..2d0b3ad 100644 --- a/examples/displayio_layout_gridlayout_simpletest.py +++ b/examples/displayio_layout_gridlayout_simpletest.py @@ -5,10 +5,12 @@ Make green and purple rectangles and a "Hello World" label. """ + 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.) @@ -31,15 +33,11 @@ _labels = [] _labels.append( - label.Label( - terminalio.FONT, scale=2, x=0, y=0, text="Hello", background_color=0x770077 - ) + 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 - ) + 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")) diff --git a/examples/displayio_layout_icon_animated_simpletest.py b/examples/displayio_layout_icon_animated_simpletest.py index c8cb37a..3ec9851 100644 --- a/examples/displayio_layout_icon_animated_simpletest.py +++ b/examples/displayio_layout_icon_animated_simpletest.py @@ -4,10 +4,13 @@ """ Creates two animated icons with touch response: zoom and shrink animations. """ + import time + +import adafruit_touchscreen import board import displayio -import adafruit_touchscreen + from adafruit_displayio_layout.widgets.icon_animated import IconAnimated display = board.DISPLAY @@ -22,9 +25,7 @@ ) -IconAnimated.init_class( - display, max_scale=1.5, max_icon_size=(48, 48), max_color_depth=255 -) +IconAnimated.init_class(display, max_scale=1.5, max_icon_size=(48, 48), max_color_depth=255) icon_zoom = IconAnimated( "Zoom", diff --git a/examples/displayio_layout_linearlayout_simpletest.py b/examples/displayio_layout_linearlayout_simpletest.py index 4090561..1925330 100644 --- a/examples/displayio_layout_linearlayout_simpletest.py +++ b/examples/displayio_layout_linearlayout_simpletest.py @@ -5,11 +5,13 @@ Illustrates usage of LinearLayout to display a text label to the right of an icon. """ + import adafruit_imageload import board import displayio import terminalio from adafruit_display_text import label + from adafruit_displayio_layout.layouts.linear_layout import LinearLayout # use built in display (PyPortal, PyGamer, PyBadge, CLUE, etc.) @@ -21,9 +23,7 @@ main_group = displayio.Group() display.root_group = main_group -layout = LinearLayout( - x=10, y=10, padding=4, orientation=LinearLayout.HORIZONTAL_ORIENTATION -) +layout = LinearLayout(x=10, y=10, padding=4, orientation=LinearLayout.HORIZONTAL_ORIENTATION) lbl = label.Label(terminalio.FONT, scale=4, x=0, y=0, text="Hello") diff --git a/examples/displayio_layout_page_layout_advancedtest.py b/examples/displayio_layout_page_layout_advancedtest.py index 9a6bde8..7dbdca7 100644 --- a/examples/displayio_layout_page_layout_advancedtest.py +++ b/examples/displayio_layout_page_layout_advancedtest.py @@ -4,14 +4,17 @@ """ Make a PageLayout and illustrate all of it's features """ + import time -import displayio + import board +import displayio import terminalio -from adafruit_display_text.bitmap_label import Label -from adafruit_display_shapes.rect import Rect from adafruit_display_shapes.circle import Circle +from adafruit_display_shapes.rect import Rect from adafruit_display_shapes.triangle import Triangle +from adafruit_display_text.bitmap_label import Label + from adafruit_displayio_layout.layouts.page_layout import PageLayout # built-in display @@ -77,22 +80,22 @@ # change page with function by name test_page_layout.show_page(page_name="page_3") -print("showing page index:{}".format(test_page_layout.showing_page_index)) +print(f"showing page index:{test_page_layout.showing_page_index}") time.sleep(1) # change page with function by index test_page_layout.show_page(page_index=0) -print("showing page name: {}".format(test_page_layout.showing_page_name)) +print(f"showing page name: {test_page_layout.showing_page_name}") time.sleep(1) # change page by updating the page name property test_page_layout.showing_page_name = "page_3" -print("showing page index: {}".format(test_page_layout.showing_page_index)) +print(f"showing page index: {test_page_layout.showing_page_index}") time.sleep(1) # change page by updating the page index property test_page_layout.showing_page_index = 1 -print("showing page name: {}".format(test_page_layout.showing_page_name)) +print(f"showing page name: {test_page_layout.showing_page_name}") time.sleep(5) another_text = Label( diff --git a/examples/displayio_layout_page_layout_simpletest.py b/examples/displayio_layout_page_layout_simpletest.py index b11c02c..74e3120 100644 --- a/examples/displayio_layout_page_layout_simpletest.py +++ b/examples/displayio_layout_page_layout_simpletest.py @@ -4,13 +4,16 @@ """ Make a PageLayout with two pages and change between them. """ + import time -import displayio + import board +import displayio import terminalio -from adafruit_display_text.bitmap_label import Label -from adafruit_display_shapes.rect import Rect from adafruit_display_shapes.circle import Circle +from adafruit_display_shapes.rect import Rect +from adafruit_display_text.bitmap_label import Label + from adafruit_displayio_layout.layouts.page_layout import PageLayout # built-in display diff --git a/examples/displayio_layout_pygame_display_switch_round.py b/examples/displayio_layout_pygame_display_switch_round.py index c21111b..c85f241 100644 --- a/examples/displayio_layout_pygame_display_switch_round.py +++ b/examples/displayio_layout_pygame_display_switch_round.py @@ -7,11 +7,12 @@ Requires: https://github.com/FoamyGuy/Blinka_Displayio_PyGameDisplay """ + import displayio import pygame from blinka_displayio_pygamedisplay import PyGameDisplay -from adafruit_displayio_layout.widgets.switch_round import SwitchRound as Switch +from adafruit_displayio_layout.widgets.switch_round import SwitchRound as Switch # Make the display context. Change size if you want display = PyGameDisplay(width=320, height=240) diff --git a/examples/displayio_layout_simpletest.py b/examples/displayio_layout_simpletest.py index 16b3bb4..2b79736 100644 --- a/examples/displayio_layout_simpletest.py +++ b/examples/displayio_layout_simpletest.py @@ -7,10 +7,12 @@ Make green and purple rectangles and a "Hello World" label. """ + 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.) @@ -33,15 +35,11 @@ _labels = [] _labels.append( - label.Label( - terminalio.FONT, scale=2, x=0, y=0, text="Hello", background_color=0x770077 - ) + 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 - ) + 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")) diff --git a/examples/displayio_layout_switch_multiple.py b/examples/displayio_layout_switch_multiple.py index 6cb1bab..5f8a2ce 100755 --- a/examples/displayio_layout_switch_multiple.py +++ b/examples/displayio_layout_switch_multiple.py @@ -6,9 +6,11 @@ """ import time + +import adafruit_touchscreen import board import displayio -import adafruit_touchscreen + from adafruit_displayio_layout.widgets.switch_round import SwitchRound as Switch display = board.DISPLAY diff --git a/examples/displayio_layout_switch_simpletest.py b/examples/displayio_layout_switch_simpletest.py index a9cfbc8..432c1e0 100644 --- a/examples/displayio_layout_switch_simpletest.py +++ b/examples/displayio_layout_switch_simpletest.py @@ -6,9 +6,11 @@ """ import time + +import adafruit_touchscreen import board import displayio -import adafruit_touchscreen + from adafruit_displayio_layout.widgets.switch_round import SwitchRound as Switch display = board.DISPLAY diff --git a/examples/displayio_layout_tab_layout_simpletest.py b/examples/displayio_layout_tab_layout_simpletest.py index 188cd75..c8c73b4 100644 --- a/examples/displayio_layout_tab_layout_simpletest.py +++ b/examples/displayio_layout_tab_layout_simpletest.py @@ -4,14 +4,17 @@ """ Make a TabLayout and illustrate the most basic features and usage. """ + import time -import displayio + import board +import displayio import terminalio -from adafruit_display_text.bitmap_label import Label -from adafruit_display_shapes.rect import Rect from adafruit_display_shapes.circle import Circle +from adafruit_display_shapes.rect import Rect from adafruit_display_shapes.triangle import Triangle +from adafruit_display_text.bitmap_label import Label + from adafruit_displayio_layout.layouts.tab_layout import TabLayout CHANGE_DELAY = 1.0 # Seconds to wait before auto-advancing to the next tab @@ -108,22 +111,22 @@ # change page with function by name test_page_layout.show_page(page_name="Thr") -print("showing page index:{}".format(test_page_layout.showing_page_index)) +print(f"showing page index:{test_page_layout.showing_page_index}") time.sleep(1) # change page with function by index test_page_layout.show_page(page_index=0) -print("showing page name: {}".format(test_page_layout.showing_page_name)) +print(f"showing page name: {test_page_layout.showing_page_name}") time.sleep(1) # change page by updating the page name property test_page_layout.showing_page_name = "Thr" -print("showing page index: {}".format(test_page_layout.showing_page_index)) +print(f"showing page index: {test_page_layout.showing_page_index}") time.sleep(1) # change page by updating the page index property test_page_layout.showing_page_index = 1 -print("showing page name: {}".format(test_page_layout.showing_page_name)) +print(f"showing page name: {test_page_layout.showing_page_name}") time.sleep(5) another_text = Label( diff --git a/examples/displayio_layout_tab_layout_touchtest.py b/examples/displayio_layout_tab_layout_touchtest.py index e8fe6ee..a58db70 100644 --- a/examples/displayio_layout_tab_layout_touchtest.py +++ b/examples/displayio_layout_tab_layout_touchtest.py @@ -4,14 +4,16 @@ """ Make a TabLayout change tabs with the touchscreen """ -import displayio + +import adafruit_touchscreen import board +import displayio import terminalio -import adafruit_touchscreen -from adafruit_display_text.bitmap_label import Label -from adafruit_display_shapes.rect import Rect from adafruit_display_shapes.circle import Circle +from adafruit_display_shapes.rect import Rect from adafruit_display_shapes.triangle import Triangle +from adafruit_display_text.bitmap_label import Label + from adafruit_displayio_layout.layouts.tab_layout import TabLayout # built-in display diff --git a/examples/hotplug_sensor_examples/displayio_layout_hotplug_rtc.py b/examples/hotplug_sensor_examples/displayio_layout_hotplug_rtc.py index 041bbcf..1268b53 100644 --- a/examples/hotplug_sensor_examples/displayio_layout_hotplug_rtc.py +++ b/examples/hotplug_sensor_examples/displayio_layout_hotplug_rtc.py @@ -6,16 +6,18 @@ """ import time -import displayio + +import adafruit_tmp117 import board +import displayio import terminalio -import adafruit_tmp117 -from adafruit_ds3231 import DS3231 -from adafruit_display_text.bitmap_label import Label -from adafruit_display_shapes.rect import Rect +from adafruit_bitmap_font import bitmap_font from adafruit_display_shapes.circle import Circle +from adafruit_display_shapes.rect import Rect from adafruit_display_shapes.triangle import Triangle -from adafruit_bitmap_font import bitmap_font +from adafruit_display_text.bitmap_label import Label +from adafruit_ds3231 import DS3231 + from adafruit_displayio_layout.layouts.tab_layout import TabLayout # +-------------------------------------------------------+ @@ -89,13 +91,9 @@ def write(self, s, value): # key: {}".format(s, n)) self.g_vars[n] = value else: - raise KeyError( - "variable '{:" ">20s}' not found in self.gVars_rDict".format(s) - ) + raise KeyError("variable '{:" ">20s}' not found in self.gVars_rDict".format(s)) else: - raise TypeError( - "myVars.write(): param s expected str, {} received".format(type(s)) - ) + raise TypeError(f"myVars.write(): param s expected str, {type(s)} received") def read(self, s): RetVal = None @@ -135,8 +133,7 @@ def clean(self): def list(self): for i in range(0, len(self.g_vars) - 1): print( - "self.g_vars['{:" - ">20s}'] = {}".format( + "self.g_vars['{:" ">20s}'] = {}".format( self.gVarsDict[i], self.g_vars[i] if i in self.g_vars else "None" ) ) @@ -358,22 +355,22 @@ def list(self): # change page with function by name test_page_layout.show_page(page_name=pages[2]) -print("showing page index:{}".format(test_page_layout.showing_page_index)) +print(f"showing page index:{test_page_layout.showing_page_index}") time.sleep(1) # change page with function by index test_page_layout.show_page(page_index=0) -print("showing page name: {}".format(test_page_layout.showing_page_name)) +print(f"showing page name: {test_page_layout.showing_page_name}") time.sleep(1) # change page by updating the page name property test_page_layout.showing_page_name = pages[2] -print("showing page index: {}".format(test_page_layout.showing_page_index)) +print(f"showing page index: {test_page_layout.showing_page_index}") time.sleep(1) # change page by updating the page index property test_page_layout.showing_page_index = 1 -print("showing page name: {}".format(test_page_layout.showing_page_name)) +print(f"showing page name: {test_page_layout.showing_page_name}") time.sleep(5) """ @@ -471,12 +468,8 @@ def get_temp(): temp = myVars.read("temp_sensor").temperature if myVars.read("temp_in_fahrenheit"): temp = (temp * 1.8) + 32 - t = "{:5.2f} ".format(temp) + myVars.read("t1") - if ( - myVars.read("my_debug") - and temp is not None - and not myVars.read("temp_in_REPL") - ): + t = f"{temp:5.2f} " + myVars.read("t1") + if myVars.read("my_debug") and temp is not None and not myVars.read("temp_in_REPL"): myVars.write("temp_in_REPL", True) print("get_temp(): {} {}".format(myVars.read("t0"), t)) if showing_page_idx == 3: # show temperature on most right Tab page @@ -485,7 +478,7 @@ def get_temp(): "old_temp" ): # Only update if there is a change in temperature myVars.write("old_temp", temp) - t = "{:5.2f} ".format(temp) + myVars.read("t1") + t = f"{temp:5.2f} " + myVars.read("t1") pge4_lbl.text = "" pge4_lbl2.text = myVars.read("t0") pge4_lbl3.text = t @@ -500,20 +493,13 @@ def get_temp(): print("Temperature sensor has disconnected") t = "" myVars.write("temp_sensor", None) - pge4_lbl.text = myVars.read( - "pge4_lbl_dflt" - ) # clean the line (eventually: t2) + pge4_lbl.text = myVars.read("pge4_lbl_dflt") # clean the line (eventually: t2) pge4_lbl2.text = "" pge4_lbl3.text = "" return RetVal -""" - Function called by get_dt() - Created to repair pylint error R0912: Too many branches (13/12) -""" - yy = 0 mo = 1 dd = 2 @@ -562,14 +548,14 @@ def handle_dt(dt): if myVars.read("c_secs") != myVars.read("o_secs"): myVars.write("o_secs", myVars.read("c_secs")) - sDT3 = s + "{} {}".format(sDT, sDT2) + sDT3 = s + f"{sDT} {sDT2}" print(sDT3) pge3_lbl3.text = sDT2 if myVars.read("my_debug"): - print("pge3_lbl.text = {}".format(pge3_lbl.text)) - print("pge3_lbl2.text = {}".format(pge3_lbl2.text)) - print("pge3_lbl3.text = {}".format(pge3_lbl3.text)) + print(f"pge3_lbl.text = {pge3_lbl.text}") + print(f"pge3_lbl2.text = {pge3_lbl2.text}") + print(f"pge3_lbl3.text = {pge3_lbl3.text}") RetVal = True # Return from here with a False but don't set the pge3_lbl to default. @@ -627,7 +613,7 @@ def main(): cnt = 0 while True: try: - print("Loop nr: {:03d}".format(cnt)) + print(f"Loop nr: {cnt:03d}") # print("main(): type(rtc) object = ", type(myVars.read("rtc"))) if myVars.read("rtc") is not None: get_dt() 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 6ec1cb1..7c2a0dd 100644 --- a/examples/hotplug_sensor_examples/displayio_layout_hotplug_temp_sensor.py +++ b/examples/hotplug_sensor_examples/displayio_layout_hotplug_temp_sensor.py @@ -9,26 +9,29 @@ However, when the flag 'use_ntp' is set, the DS3231 will not be used instead the NTP class from adafruit_ntp.py will be used. """ + import time +from os import getenv + +import adafruit_tmp117 +import adafruit_touchscreen import board import busio import displayio +import neopixel import terminalio -import adafruit_tmp117 +from adafruit_bitmap_font import bitmap_font +from adafruit_display_shapes.circle import Circle +from adafruit_display_shapes.rect import Rect +from adafruit_display_shapes.triangle import Triangle +from adafruit_display_text.bitmap_label import Label from adafruit_ds3231 import DS3231 -from digitalio import DigitalInOut -import neopixel -import adafruit_touchscreen from adafruit_esp32spi import adafruit_esp32spi from adafruit_ntp import NTP from adafruit_pyportal import PyPortal -from adafruit_display_text.bitmap_label import Label -from adafruit_display_shapes.rect import Rect -from adafruit_display_shapes.circle import Circle -from adafruit_display_shapes.triangle import Triangle -from adafruit_bitmap_font import bitmap_font -from adafruit_displayio_layout.layouts.tab_layout import TabLayout +from digitalio import DigitalInOut +from adafruit_displayio_layout.layouts.tab_layout import TabLayout # +-------------------------------------------------------+ # | Definition for variables in the past defined as global| @@ -115,13 +118,9 @@ def write(self, s, value): # key: {}".format(s, n)) self.g_vars[n] = value else: - raise KeyError( - "variable '{:" ">20s}' not found in self.gVars_rDict".format(s) - ) + raise KeyError("variable '{:" ">20s}' not found in self.gVars_rDict".format(s)) else: - raise TypeError( - "myVars.write(): param s expected str, {} received".format(type(s)) - ) + raise TypeError(f"myVars.write(): param s expected str, {type(s)} received") def read(self, s): RetVal = None @@ -168,8 +167,7 @@ def clean(self): def list(self): for i in range(0, len(self.g_vars) - 1): print( - "self.g_vars['{:" - ">20s}'] = {}".format( + "self.g_vars['{:" ">20s}'] = {}".format( self.gVarsDict[i], self.g_vars[i] if i in self.g_vars else "None" ) ) @@ -278,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(): @@ -306,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: @@ -323,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 @@ -337,10 +331,7 @@ def list(self): print("Please wait...") if myVars.read("my_debug"): print("My IP address is", esp.pretty_ip(esp.ip_address)) - print( - "IP lookup adafruit.com: %s" - % esp.pretty_ip(esp.get_host_by_name("adafruit.com")) - ) + print("IP lookup adafruit.com: %s" % esp.pretty_ip(esp.get_host_by_name("adafruit.com"))) print("Ping google.com: %d ms" % esp.ping("google.com")) @@ -365,10 +356,10 @@ 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("Seconds since Jan 1, 1970: {} seconds".format(ntp_current_time)) + print(f"Seconds since Jan 1, 1970: {ntp_current_time} seconds") # Convert the current time in seconds since Jan 1, 1970 to a struct_time myVars.write("default_dt", time.localtime(ntp_current_time)) @@ -383,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") @@ -761,33 +752,22 @@ def get_temp(): print("Temperature sensor has disconnected") t = "" myVars.write("temp_sensor", None) - pge4_lbl.text = myVars.read( - "pge4_lbl_dflt" - ) # clean the line (eventually: t2) + pge4_lbl.text = myVars.read("pge4_lbl_dflt") # clean the line (eventually: t2) pge4_lbl2.text = "Sensor disconnected." pge4_lbl3.text = "Check wiring." return RetVal -# Moved these six definitions outside handle_dt() -# to correct pylint error 'too many variables' dt_ridxs = {"yy": 0, "mo": 1, "dd": 2, "hh": 3, "mm": 4, "ss": 5} # print("dict dt_ridxs =", dt_ridxs.keys()) -""" Function called by get_dt() - Created to repair pylint error R0912: Too many branches (13/12)""" - - def handle_dt(dt): my_debug = myVars.read("my_debug") RetVal = False s = "Date/time: " sYY = str(dt[dt_ridxs["yy"]]) # was: str(dt[yy]) - # print("dt_ridxs["mo"] = ", dt_ridxs["mo"]) - # modified mo because plynt error R0914 'Too many local variables' - # mo = dt_ridxs["mo"] dd = dt_ridxs["dd"] hh = dt_ridxs["hh"] mm = dt_ridxs["mm"] @@ -831,14 +811,14 @@ def handle_dt(dt): if myVars.read("c_secs") != myVars.read("o_secs"): myVars.write("o_secs", myVars.read("c_secs")) - sDT3 = s + "{} {}".format(sDT, sDT2) + sDT3 = s + f"{sDT} {sDT2}" print(sDT3) pge3_lbl3.text = sDT2 if my_debug: - print("pge3_lbl.text = {}".format(pge3_lbl.text)) - print("pge3_lbl2.text = {}".format(pge3_lbl2.text)) - print("pge3_lbl3.text = {}".format(pge3_lbl3.text)) + print(f"pge3_lbl.text = {pge3_lbl.text}") + print(f"pge3_lbl2.text = {pge3_lbl2.text}") + print(f"pge3_lbl3.text = {pge3_lbl3.text}") RetVal = True # Return from here with a False but don't set the pge3_lbl to default. @@ -898,11 +878,6 @@ def hms_to_cnt(): return (dt.tm_hour * 3600) + (dt.tm_min * 60) + dt.tm_sec -""" Created this function to correct pylint errors: - 'Too many branches' R0912 and - 'Too many statements' R0915""" - - def ck_next_NTP_sync(): s_cnt = myVars.read("s_cnt") c_cnt = hms_to_cnt() # set current count (seconds) @@ -917,9 +892,7 @@ def ck_next_NTP_sync(): myVars.write("s_cnt", hms_to_cnt()) # --- five minutes count down calculations #1 --- if my_debug: - print( - TAG + "five_min = {}, s_cnt = {}, c_cnt = {}".format(five_min, s_cnt, c_cnt) - ) + print(TAG + f"five_min = {five_min}, s_cnt = {s_cnt}, c_cnt = {c_cnt}") print(TAG + "c_elapsed = ", c_elapsed) # --- five minutes count down calculations #2 --- @@ -928,7 +901,7 @@ def ck_next_NTP_sync(): myVars.write("five_min_cnt", five_min) # remember count mm2 = five_min // 60 ss2 = five_min - (mm2 * 60) - t2 = "{:02d}:{:02d}".format(mm2, ss2) + t2 = f"{mm2:02d}:{ss2:02d}" t0 = t1 + t2 + t3 print(t0) pge3_lbl4.text = t0 @@ -973,7 +946,7 @@ def main(): if otp and ntp_refresh: refresh_from_NTP() # first re-synchronize internal clock from NTP server if get_dt(): - print("Loop nr: {:03d}".format(cnt)) + print(f"Loop nr: {cnt:03d}") else: connect_rtc() if myVars.read("temp_sensor") is not None: 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 diff --git a/ruff.toml b/ruff.toml new file mode 100644 index 0000000..5a5dbf0 --- /dev/null +++ b/ruff.toml @@ -0,0 +1,101 @@ +# SPDX-FileCopyrightText: 2024 Tim Cocks for Adafruit Industries +# +# SPDX-License-Identifier: MIT + +target-version = "py38" +line-length = 100 + +[lint] +preview = true +select = ["I", "PL", "UP"] + +extend-select = [ + "D419", # empty-docstring + "E501", # line-too-long + "W291", # trailing-whitespace + "PLC0414", # useless-import-alias + "PLC2401", # non-ascii-name + "PLC2801", # unnecessary-dunder-call + "PLC3002", # unnecessary-direct-lambda-call + "E999", # syntax-error + "PLE0101", # return-in-init + "F706", # return-outside-function + "F704", # yield-outside-function + "PLE0116", # continue-in-finally + "PLE0117", # nonlocal-without-binding + "PLE0241", # duplicate-bases + "PLE0302", # unexpected-special-method-signature + "PLE0604", # invalid-all-object + "PLE0605", # invalid-all-format + "PLE0643", # potential-index-error + "PLE0704", # misplaced-bare-raise + "PLE1141", # dict-iter-missing-items + "PLE1142", # await-outside-async + "PLE1205", # logging-too-many-args + "PLE1206", # logging-too-few-args + "PLE1307", # bad-string-format-type + "PLE1310", # bad-str-strip-call + "PLE1507", # invalid-envvar-value + "PLE2502", # bidirectional-unicode + "PLE2510", # invalid-character-backspace + "PLE2512", # invalid-character-sub + "PLE2513", # invalid-character-esc + "PLE2514", # invalid-character-nul + "PLE2515", # invalid-character-zero-width-space + "PLR0124", # comparison-with-itself + "PLR0202", # no-classmethod-decorator + "PLR0203", # no-staticmethod-decorator + "UP004", # useless-object-inheritance + "PLR0206", # property-with-parameters + "PLR0904", # too-many-public-methods + "PLR0911", # too-many-return-statements + "PLR0916", # too-many-boolean-expressions + "PLR1702", # too-many-nested-blocks + "PLR1704", # redefined-argument-from-local + "PLR1711", # useless-return + "C416", # unnecessary-comprehension + "PLR1733", # unnecessary-dict-index-lookup + "PLR1736", # unnecessary-list-index-lookup + + # ruff reports this rule is unstable + #"PLR6301", # no-self-use + + "PLW0108", # unnecessary-lambda + "PLW0120", # useless-else-on-loop + "PLW0127", # self-assigning-variable + "PLW0129", # assert-on-string-literal + "B033", # duplicate-value + "PLW0131", # named-expr-without-context + "PLW0245", # super-without-brackets + "PLW0406", # import-self + "PLW0602", # global-variable-not-assigned + "PLW0603", # global-statement + "PLW0604", # global-at-module-level + + # fails on the try: import typing used by libraries + #"F401", # unused-import + + "F841", # unused-variable + "E722", # bare-except + "PLW0711", # binary-op-exception + "PLW1501", # bad-open-mode + "PLW1508", # invalid-envvar-default + "PLW1509", # subprocess-popen-preexec-fn + "PLW2101", # useless-with-lock + "PLW3301", # nested-min-max +] + +ignore = [ + "PLR2004", # magic-value-comparison + "UP030", # format literals + "PLW1514", # unspecified-encoding + "PLR0912", # too-many-branches + "PLR0913", # too-many-arguments + "PLR0914", # too-many-locals + "PLR0915", # too-many-statements + "PLR0917", # too-many-positional-arguments + +] + +[format] +line-ending = "lf" pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy