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/.github/PULL_REQUEST_TEMPLATE/adafruit_circuitpython_pr.md b/.github/PULL_REQUEST_TEMPLATE/adafruit_circuitpython_pr.md index 71ef8f8..8de294e 100644 --- a/.github/PULL_REQUEST_TEMPLATE/adafruit_circuitpython_pr.md +++ b/.github/PULL_REQUEST_TEMPLATE/adafruit_circuitpython_pr.md @@ -4,7 +4,7 @@ Thank you for contributing! Before you submit a pull request, please read the following. -Make sure any changes you're submitting are in line with the CircuitPython Design Guide, available here: https://circuitpython.readthedocs.io/en/latest/docs/design_guide.html +Make sure any changes you're submitting are in line with the CircuitPython Design Guide, available here: https://docs.circuitpython.org/en/latest/docs/design_guide.html If your changes are to documentation, please verify that the documentation builds locally by following the steps found here: https://adafru.it/build-docs diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index aea7267..2470387 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -10,69 +10,8 @@ jobs: test: runs-on: ubuntu-latest steps: - - name: Dump GitHub context - env: - GITHUB_CONTEXT: ${{ toJson(github) }} - run: echo "$GITHUB_CONTEXT" - - name: Translate Repo Name For Build Tools filename_prefix - id: repo-name - run: | - echo ::set-output name=repo-name::$( - echo ${{ github.repository }} | - awk -F '\/' '{ print tolower($2) }' | - tr '_' '-' - ) - - name: Set up Python 3.7 - uses: actions/setup-python@v1 - with: - python-version: 3.7 - - name: Versions - run: | - python3 --version - - name: Checkout Current Repo - uses: actions/checkout@v1 - with: - submodules: true - - name: Checkout tools repo - uses: actions/checkout@v2 - with: - repository: adafruit/actions-ci-circuitpython-libs - path: actions-ci - - name: Install dependencies - # (e.g. - apt-get: gettext, etc; pip: circuitpython-build-tools, requirements.txt; etc.) - run: | - source actions-ci/install.sh - - name: Pip install Sphinx, pre-commit - run: | - pip install --force-reinstall Sphinx sphinx-rtd-theme pre-commit - name: Load graphviz run: | sudo apt install graphviz - - name: Library version - run: git describe --dirty --always --tags - - name: Pre-commit hooks - run: | - pre-commit run --all-files - - name: Build assets - run: circuitpython-build-bundles --filename_prefix ${{ steps.repo-name.outputs.repo-name }} --library_location . - - name: Archive bundles - uses: actions/upload-artifact@v2 - with: - name: bundles - path: ${{ github.workspace }}/bundles/ - - name: Build docs - working-directory: docs - run: sphinx-build -E -W -b html . _build/html - - name: Check For setup.py - id: need-pypi - run: | - echo ::set-output name=setup-py::$( find . -wholename './setup.py' ) - - name: Build Python package - if: contains(steps.need-pypi.outputs.setup-py, 'setup.py') - run: | - pip install --upgrade setuptools wheel twine readme_renderer testresources - python setup.py sdist - python setup.py bdist_wheel --universal - twine check dist/* - - name: Setup problem matchers - uses: adafruit/circuitpython-action-library-ci-problem-matchers@v1 + - name: Run Build CI workflow + uses: adafruit/workflows-circuitpython-libs/build@main diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml deleted file mode 100644 index 6d0015a..0000000 --- a/.github/workflows/release.yml +++ /dev/null @@ -1,85 +0,0 @@ -# SPDX-FileCopyrightText: 2017 Scott Shawcroft, written for Adafruit Industries -# -# SPDX-License-Identifier: MIT - -name: Release Actions - -on: - release: - types: [published] - -jobs: - upload-release-assets: - runs-on: ubuntu-latest - steps: - - name: Dump GitHub context - env: - GITHUB_CONTEXT: ${{ toJson(github) }} - run: echo "$GITHUB_CONTEXT" - - name: Translate Repo Name For Build Tools filename_prefix - id: repo-name - run: | - echo ::set-output name=repo-name::$( - echo ${{ github.repository }} | - awk -F '\/' '{ print tolower($2) }' | - tr '_' '-' - ) - - name: Set up Python 3.6 - uses: actions/setup-python@v1 - with: - python-version: 3.6 - - name: Versions - run: | - python3 --version - - name: Checkout Current Repo - uses: actions/checkout@v1 - with: - submodules: true - - name: Checkout tools repo - uses: actions/checkout@v2 - with: - repository: adafruit/actions-ci-circuitpython-libs - path: actions-ci - - name: Install deps - run: | - source actions-ci/install.sh - - name: Build assets - run: circuitpython-build-bundles --filename_prefix ${{ steps.repo-name.outputs.repo-name }} --library_location . - - name: Upload Release Assets - # the 'official' actions version does not yet support dynamically - # supplying asset names to upload. @csexton's version chosen based on - # discussion in the issue below, as its the simplest to implement and - # allows for selecting files with a pattern. - # https://github.com/actions/upload-release-asset/issues/4 - #uses: actions/upload-release-asset@v1.0.1 - uses: csexton/release-asset-action@master - with: - pattern: "bundles/*" - github-token: ${{ secrets.GITHUB_TOKEN }} - - upload-pypi: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v1 - - name: Check For setup.py - id: need-pypi - run: | - echo ::set-output name=setup-py::$( find . -wholename './setup.py' ) - - name: Set up Python - if: contains(steps.need-pypi.outputs.setup-py, 'setup.py') - uses: actions/setup-python@v1 - with: - python-version: '3.x' - - name: Install dependencies - if: contains(steps.need-pypi.outputs.setup-py, 'setup.py') - run: | - python -m pip install --upgrade pip - pip install setuptools wheel twine - - name: Build and publish - if: contains(steps.need-pypi.outputs.setup-py, 'setup.py') - env: - TWINE_USERNAME: ${{ secrets.pypi_username }} - TWINE_PASSWORD: ${{ secrets.pypi_password }} - run: | - python setup.py sdist - twine upload dist/* diff --git a/.github/workflows/release_gh.yml b/.github/workflows/release_gh.yml new file mode 100644 index 0000000..9acec60 --- /dev/null +++ b/.github/workflows/release_gh.yml @@ -0,0 +1,19 @@ +# SPDX-FileCopyrightText: 2017 Scott Shawcroft, written for Adafruit Industries +# +# SPDX-License-Identifier: MIT + +name: GitHub Release Actions + +on: + release: + types: [published] + +jobs: + upload-release-assets: + runs-on: ubuntu-latest + steps: + - name: Run GitHub Release CI workflow + uses: adafruit/workflows-circuitpython-libs/release-gh@main + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + upload-url: ${{ github.event.release.upload_url }} diff --git a/.github/workflows/release_pypi.yml b/.github/workflows/release_pypi.yml new file mode 100644 index 0000000..65775b7 --- /dev/null +++ b/.github/workflows/release_pypi.yml @@ -0,0 +1,19 @@ +# SPDX-FileCopyrightText: 2017 Scott Shawcroft, written for Adafruit Industries +# +# SPDX-License-Identifier: MIT + +name: PyPI Release Actions + +on: + release: + types: [published] + +jobs: + upload-release-assets: + runs-on: ubuntu-latest + steps: + - name: Run PyPI Release CI workflow + uses: adafruit/workflows-circuitpython-libs/release-pypi@main + with: + pypi-username: ${{ secrets.pypi_username }} + pypi-password: ${{ secrets.pypi_password }} diff --git a/.gitignore b/.gitignore index 2c6ddfd..db3d538 100644 --- a/.gitignore +++ b/.gitignore @@ -1,18 +1,48 @@ -# SPDX-FileCopyrightText: 2017 Scott Shawcroft, written for Adafruit Industries +# SPDX-FileCopyrightText: 2022 Kattni Rembor, written for Adafruit Industries # -# SPDX-License-Identifier: Unlicense +# SPDX-License-Identifier: MIT +# Do not include files and directories created by your personal work environment, such as the IDE +# you use, except for those already listed here. Pull requests including changes to this file will +# not be accepted. + +# This .gitignore file contains rules for files generated by working with CircuitPython libraries, +# including building Sphinx, testing with pip, and creating a virual environment, as well as the +# MacOS and IDE-specific files generated by using MacOS in general, or the PyCharm or VSCode IDEs. + +# If you find that there are files being generated on your machine that should not be included in +# your git commit, you should create a .gitignore_global file on your computer to include the +# files created by your personal setup. To do so, follow the two steps below. + +# First, create a file called .gitignore_global somewhere convenient for you, and add rules for +# the files you want to exclude from git commits. + +# Second, configure Git to use the exclude file for all Git repositories by running the +# following via commandline, replacing "path/to/your/" with the actual path to your newly created +# .gitignore_global file: +# git config --global core.excludesfile path/to/your/.gitignore_global + +# CircuitPython-specific files *.mpy -.idea + +# Python-specific files __pycache__ -_build *.pyc + +# Sphinx build-specific files +_build + +# This file results from running `pip -e install .` in a local repository +*.egg-info + +# Virtual environment-specific files .env -.python-version -build*/ -bundles +.venv + +# MacOS-specific files *.DS_Store -.eggs -dist -**/*.egg-info + +# IDE-specific files +.idea .vscode +*~ diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 43d1385..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: 20.8b1 + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v4.5.0 hooks: - - id: black -- repo: https://github.com/fsfe/reuse-tool - rev: v0.12.1 + - id: check-yaml + - id: end-of-file-fixer + - id: trailing-whitespace + - repo: https://github.com/astral-sh/ruff-pre-commit + rev: v0.3.4 hooks: - - id: reuse -- repo: https://github.com/pre-commit/pre-commit-hooks - rev: v2.3.0 + - id: ruff-format + - id: ruff + args: ["--fix"] + - repo: https://github.com/fsfe/reuse-tool + rev: v3.0.1 hooks: - - id: check-yaml - - id: end-of-file-fixer - - id: trailing-whitespace -- repo: https://github.com/pycqa/pylint - rev: v2.11.1 - hooks: - - id: pylint - name: pylint (library code) - types: [python] - args: - - --disable=consider-using-f-string,duplicate-code - 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: reuse diff --git a/.pylintrc b/.pylintrc deleted file mode 100644 index 08e12bf..0000000 --- a/.pylintrc +++ /dev/null @@ -1,436 +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= - -# 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,print-statement,parameter-unpacking,unpacking-in-except,old-raise-syntax,backtick,long-suffix,old-ne-operator,old-octal-literal,import-star-module-level,raw-checker-failed,bad-inline-option,locally-disabled,locally-enabled,file-ignored,suppressed-message,useless-suppression,deprecated-pragma,apply-builtin,basestring-builtin,buffer-builtin,cmp-builtin,coerce-builtin,execfile-builtin,file-builtin,long-builtin,raw_input-builtin,reduce-builtin,standarderror-builtin,unicode-builtin,xrange-builtin,coerce-method,delslice-method,getslice-method,setslice-method,no-absolute-import,old-division,dict-iter-method,dict-view-method,next-method-called,metaclass-assignment,indexing-exception,raising-string,reload-builtin,oct-method,hex-method,nonzero-method,cmp-method,input-builtin,round-builtin,intern-builtin,unichr-builtin,map-builtin-not-iterating,zip-builtin-not-iterating,range-builtin-not-iterating,filter-builtin-not-iterating,using-cmp-argument,eq-without-hash,div-method,idiv-method,rdiv-method,exception-message-attribute,invalid-str-codec,sys-max-int,bad-python3-import,deprecated-string-function,deprecated-str-translate-call -disable=print-statement,parameter-unpacking,unpacking-in-except,old-raise-syntax,backtick,long-suffix,old-ne-operator,old-octal-literal,import-star-module-level,raw-checker-failed,bad-inline-option,locally-disabled,locally-enabled,file-ignored,suppressed-message,useless-suppression,deprecated-pragma,apply-builtin,basestring-builtin,buffer-builtin,cmp-builtin,coerce-builtin,execfile-builtin,file-builtin,long-builtin,raw_input-builtin,reduce-builtin,standarderror-builtin,unicode-builtin,xrange-builtin,coerce-method,delslice-method,getslice-method,setslice-method,no-absolute-import,old-division,dict-iter-method,dict-view-method,next-method-called,metaclass-assignment,indexing-exception,raising-string,reload-builtin,oct-method,hex-method,nonzero-method,cmp-method,input-builtin,round-builtin,intern-builtin,unichr-builtin,map-builtin-not-iterating,zip-builtin-not-iterating,range-builtin-not-iterating,filter-builtin-not-iterating,using-cmp-argument,eq-without-hash,div-method,idiv-method,rdiv-method,exception-message-attribute,invalid-str-codec,sys-max-int,bad-python3-import,deprecated-string-function,deprecated-str-translate-call,import-error,bad-continuation,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 - -# List of optional constructs for which whitespace checking is disabled. `dict- -# separator` is used to allow tabulation in dicts, etc.: {1 : 1,\n222: 2}. -# `trailing-comma` allows a space between comma and closing bracket: (a, ). -# `empty-line` allows space-only lines. -no-space-check=trailing-comma,dict-separator - -# 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=4 - - -[BASIC] - -# Naming hint for argument names -argument-name-hint=(([a-z][a-z0-9_]{2,30})|(_[a-z0-9_]*))$ - -# Regular expression matching correct argument names -argument-rgx=(([a-z][a-z0-9_]{2,30})|(_[a-z0-9_]*))$ - -# Naming hint for attribute names -attr-name-hint=(([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 - -# Naming hint for class attribute names -class-attribute-name-hint=([A-Za-z_][A-Za-z0-9_]{2,30}|(__.*__))$ - -# Regular expression matching correct class attribute names -class-attribute-rgx=([A-Za-z_][A-Za-z0-9_]{2,30}|(__.*__))$ - -# Naming hint for class names -# class-name-hint=[A-Z_][a-zA-Z0-9]+$ -class-name-hint=[A-Z_][a-zA-Z0-9_]+$ - -# Regular expression matching correct class names -# class-rgx=[A-Z_][a-zA-Z0-9]+$ -class-rgx=[A-Z_][a-zA-Z0-9_]+$ - -# Naming hint for constant names -const-name-hint=(([A-Z_][A-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 - -# Naming hint for function names -function-name-hint=(([a-z][a-z0-9_]{2,30})|(_[a-z0-9_]*))$ - -# 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 - -# Naming hint for inline iteration names -inlinevar-name-hint=[A-Za-z_][A-Za-z0-9_]*$ - -# Regular expression matching correct inline iteration names -inlinevar-rgx=[A-Za-z_][A-Za-z0-9_]*$ - -# Naming hint for method names -method-name-hint=(([a-z][a-z0-9_]{2,30})|(_[a-z0-9_]*))$ - -# Regular expression matching correct method names -method-rgx=(([a-z][a-z0-9_]{2,30})|(_[a-z0-9_]*))$ - -# Naming hint for module names -module-name-hint=(([a-z_][a-z0-9_]*)|([A-Z][a-zA-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 - -# Naming hint for variable names -variable-name-hint=(([a-z][a-z0-9_]{2,30})|(_[a-z0-9_]*))$ - -# 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=Exception diff --git a/.readthedocs.yaml b/.readthedocs.yaml index 95ec218..3ed8fb5 100644 --- a/.readthedocs.yaml +++ b/.readthedocs.yaml @@ -8,8 +8,17 @@ # Required version: 2 +sphinx: + configuration: docs/conf.py + +build: + os: ubuntu-20.04 + tools: + python: "3" + apt_packages: + - graphviz + python: - version: "3.6" install: - requirements: docs/requirements.txt - requirements: requirements.txt diff --git a/README.rst b/README.rst index 68a3b76..b9628db 100644 --- a/README.rst +++ b/README.rst @@ -2,10 +2,10 @@ Introduction ============ .. image:: https://readthedocs.org/projects/adafruit-circuitpython-displayio-layout/badge/?version=latest - :target: https://circuitpython.readthedocs.io/projects/displayio-layout/en/latest/ + :target: https://docs.circuitpython.org/projects/displayio-layout/en/latest/ :alt: Documentation Status -.. image:: https://img.shields.io/discord/327254708534116352.svg +.. image:: https://raw.githubusercontent.com/adafruit/Adafruit_CircuitPython_Bundle/main/badges/adafruit_discord.svg :target: https://adafru.it/discord :alt: Discord @@ -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. @@ -51,8 +51,8 @@ To install in a virtual environment in your current project: .. code-block:: shell mkdir project-name && cd project-name - python3 -m venv .env - source .env/bin/activate + python3 -m venv .venv + source .venv/bin/activate pip3 install adafruit-circuitpython-displayio-layout Usage Example @@ -63,7 +63,9 @@ See scripts in the examples directory of this repository. Documentation ============= -API documentation for this library can be found on `Read the Docs `_. +API documentation for this library can be found on `Read the Docs `_. + +For information on building library documentation, please check out `this guide `_. Contributing ============ @@ -71,8 +73,3 @@ Contributing Contributions are welcome! Please read our `Code of Conduct `_ before contributing to help this project stay welcoming. - -Documentation -============= - -For information on building library documentation, please check out `this guide `_. 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 e63d6ac..b57bd16 100644 --- a/adafruit_displayio_layout/layouts/grid_layout.py +++ b/adafruit_displayio_layout/layouts/grid_layout.py @@ -22,10 +22,19 @@ https://github.com/adafruit/circuitpython/releases """ + +try: + # Used only for typing + from typing import Any, List, Optional, Tuple, Union +except ImportError: + pass + import math + import displayio +from vectorio import Rectangle -__version__ = "0.0.0-auto.0" +__version__ = "0.0.0+auto.0" __repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_DisplayIO_Layout.git" @@ -44,22 +53,27 @@ class GridLayout(displayio.Group): lines above. Row indexes are 0 based. :param Union[tuple, list] v_divider_line_cols: Column indexes to draw divider lines before. Column indexes are 0 based. + :param divider_line_color: The color of the divider lines (in hexadecimal) + :param tuple cell_anchor_point: Anchor point used within every cell. Needs to + be a tuple containing two floats between 0.0 and 1.0. Default is (0.0, 0.0) + which will anchor content to the top left of the cell. """ - # pylint: disable=too-many-arguments def __init__( self, - x, - y, - width, - height, - grid_size, - cell_padding=0, - divider_lines=False, - h_divider_line_rows=None, - v_divider_line_cols=None, - ): + x: int, + y: int, + width: int, + height: int, + grid_size: tuple[int, int], + cell_padding: int = 0, + divider_lines: bool = False, + h_divider_line_rows: Union[Tuple[int, ...], List[int], None] = None, + v_divider_line_cols: Union[Tuple[int, ...], List[int], None] = None, + divider_line_color: int = 0xFFFFFF, + cell_anchor_point: Tuple[float, float] = (0.0, 0.0), + ) -> None: super().__init__(x=x, y=y) self.x = x self.y = y @@ -67,11 +81,13 @@ def __init__( self._height = height self.grid_size = grid_size self.cell_padding = cell_padding - self._cell_content_list = [] + self._cell_content_list: List[dict[str, Any]] = [] + self._cell_anchor_point = cell_anchor_point - self._divider_lines = [] - self.h_divider_line_rows = h_divider_line_rows - self.v_divider_line_cols = v_divider_line_cols + self._divider_lines: List[dict[str, Any]] = [] + self._divider_color = divider_line_color + self.h_divider_line_rows = h_divider_line_rows or tuple() + self.v_divider_line_cols = v_divider_line_cols or tuple() self._divider_lines_enabled = ( (divider_lines is True) @@ -80,28 +96,26 @@ def __init__( ) if divider_lines: - if self.h_divider_line_rows is None: + if h_divider_line_rows is None: self.h_divider_line_rows = [] for _y in range(self.grid_size[1] + 1): self.h_divider_line_rows.append(_y) - if self.v_divider_line_cols is None: + if v_divider_line_cols is None: self.v_divider_line_cols = [] for _x in range(self.grid_size[0] + 1): self.v_divider_line_cols.append(_x) - else: - if not h_divider_line_rows: - self.h_divider_line_rows = tuple() - if not v_divider_line_cols: - self.v_divider_line_cols = tuple() # 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): - # pylint: disable=too-many-locals, too-many-branches, too-many-statements + def layout_cells(self): + """render the grid with all cell content and dividers""" + self._layout_cells() + + def _layout_cells(self) -> None: + for line_obj in self._divider_lines: + self.remove(line_obj["rect"]) for cell in self._cell_content_list: if cell["content"] not in self: grid_size_x = self.grid_size[0] @@ -110,18 +124,19 @@ def _layout_cells(self): grid_position_x = cell["grid_position"][0] grid_position_y = cell["grid_position"][1] - button_size_x = cell["cell_size"][0] - button_size_y = cell["cell_size"][1] + content_cell_size_x = cell["cell_size"][0] + content_cell_size_y = cell["cell_size"][1] _measured_width = ( - math.ceil(button_size_x * self._width / grid_size_x) + math.ceil(content_cell_size_x * self._width / grid_size_x) - 2 * self.cell_padding ) _measured_height = ( - math.ceil(button_size_y * self._height / grid_size_y) + math.ceil(content_cell_size_y * self._height / grid_size_y) - 2 * self.cell_padding ) + if hasattr(cell["content"], "resize"): # if it has resize function cell["content"].resize( @@ -142,152 +157,213 @@ def _layout_cells(self): pass if not hasattr(cell["content"], "anchor_point"): - cell["content"].x = ( int(grid_position_x * self._width / grid_size_x) + self.cell_padding + + int(cell["cell_anchor_point"][0] * _measured_width) + - int(cell["content"].width * cell["cell_anchor_point"][0]) ) cell["content"].y = ( int(grid_position_y * self._height / grid_size_y) + self.cell_padding + + int(cell["cell_anchor_point"][1] * _measured_height) + - int(cell["content"].height * cell["cell_anchor_point"][1]) ) else: - cell["content"].anchor_point = (0, 0) + cell["content"].anchor_point = cell["cell_anchor_point"] cell["content"].anchored_position = ( int(grid_position_x * self._width / grid_size_x) - + self.cell_padding, + + self.cell_padding + + int(cell["cell_anchor_point"][0] * _measured_width), int(grid_position_y * self._height / grid_size_y) - + self.cell_padding, + + self.cell_padding + + int(cell["cell_anchor_point"][1] * _measured_height), ) self.append(cell["content"]) if self._divider_lines_enabled: palette = displayio.Palette(2) - palette[0] = 0xFFFFFF - palette[1] = 0xFFFFFF + palette[0] = self._divider_color + palette[1] = self._divider_color if not hasattr(cell["content"], "anchor_point"): _bottom_line_loc_y = ( - cell["content"].y + _measured_height + self.cell_padding - ) - 1 - _bottom_line_loc_x = cell["content"].x - self.cell_padding + (cell["content"].y + _measured_height + self.cell_padding) + - 1 + - int(cell["cell_anchor_point"][1] * _measured_height) + + int(cell["content"].height * cell["cell_anchor_point"][1]) + ) + + _bottom_line_loc_x = ( + cell["content"].x + - self.cell_padding + - int(cell["cell_anchor_point"][0] * _measured_width) + + int(cell["content"].width * cell["cell_anchor_point"][0]) + ) + + _top_line_loc_y = ( + cell["content"].y + - self.cell_padding + - int(cell["cell_anchor_point"][1] * _measured_height) + + int(cell["content"].height * cell["cell_anchor_point"][1]) + ) - _top_line_loc_y = cell["content"].y - self.cell_padding - _top_line_loc_x = cell["content"].x - self.cell_padding + _top_line_loc_x = ( + cell["content"].x + - self.cell_padding + - int(cell["cell_anchor_point"][0] * _measured_width) + + int(cell["content"].width * cell["cell_anchor_point"][0]) + ) + + _right_line_loc_y = ( + cell["content"].y + - self.cell_padding + - int(cell["cell_anchor_point"][1] * _measured_height) + + int(cell["content"].height * cell["cell_anchor_point"][1]) + ) - _right_line_loc_y = cell["content"].y - self.cell_padding _right_line_loc_x = ( - cell["content"].x + _measured_width + self.cell_padding - ) - 1 + (cell["content"].x + _measured_width + self.cell_padding) + - 1 + - int(cell["cell_anchor_point"][0] * _measured_width) + + int(cell["content"].width * cell["cell_anchor_point"][0]) + ) else: _bottom_line_loc_y = ( cell["content"].anchored_position[1] + _measured_height + self.cell_padding + - int(cell["cell_anchor_point"][1] * _measured_height) ) - 1 _bottom_line_loc_x = ( - cell["content"].anchored_position[0] - self.cell_padding + cell["content"].anchored_position[0] + - self.cell_padding + - int(cell["cell_anchor_point"][0] * _measured_width) ) _top_line_loc_y = ( - cell["content"].anchored_position[1] - self.cell_padding + cell["content"].anchored_position[1] + - self.cell_padding + - int(cell["cell_anchor_point"][1] * _measured_height) ) _top_line_loc_x = ( - cell["content"].anchored_position[0] - self.cell_padding + cell["content"].anchored_position[0] + - self.cell_padding + - int(cell["cell_anchor_point"][0] * _measured_width) ) _right_line_loc_y = ( - cell["content"].anchored_position[1] - self.cell_padding + cell["content"].anchored_position[1] + - self.cell_padding + - int(cell["cell_anchor_point"][1] * _measured_height) ) _right_line_loc_x = ( - cell["content"].anchored_position[0] - + _measured_width - + self.cell_padding - ) - 1 - - _horizontal_divider_line = displayio.Shape( - _measured_width + (2 * self.cell_padding), - 1, - mirror_x=False, - mirror_y=False, - ) + ( + cell["content"].anchored_position[0] + + _measured_width + + self.cell_padding + ) + - 1 + - int(cell["cell_anchor_point"][0] * _measured_width) + ) - _bottom_divider_tilegrid = displayio.TileGrid( - _horizontal_divider_line, + _bottom_divider_rect = Rectangle( pixel_shader=palette, + width=_measured_width + (2 * self.cell_padding), + height=1, y=_bottom_line_loc_y, x=_bottom_line_loc_x, ) - _top_divider_tilegrid = displayio.TileGrid( - _horizontal_divider_line, + _top_divider_rect = Rectangle( + width=_measured_width + (2 * self.cell_padding), + height=1, pixel_shader=palette, y=_top_line_loc_y, x=_top_line_loc_x, ) - _vertical_divider_line = displayio.Shape( - 1, - _measured_height + (2 * self.cell_padding), - mirror_x=False, - mirror_y=False, - ) - - _left_divider_tilegrid = displayio.TileGrid( - _vertical_divider_line, + _left_divider_rect = Rectangle( pixel_shader=palette, + width=1, + height=_measured_height + (2 * self.cell_padding), y=_top_line_loc_y, x=_top_line_loc_x, ) - _right_divider_tilegrid = displayio.TileGrid( - _vertical_divider_line, + _right_divider_rect = Rectangle( pixel_shader=palette, + width=1, + height=_measured_height + (2 * self.cell_padding), y=_right_line_loc_y, x=_right_line_loc_x, ) - for line_obj in self._divider_lines: - self.remove(line_obj["tilegrid"]) - - if grid_position_y == grid_size_y - 1 and ( - grid_position_y + 1 in self.h_divider_line_rows + """ + Only use bottom divider lines on the bottom row. All + other rows rely on top divder lines of the row beneath them. + Add the content_cell_size to the grid_position to account for + areas larger than 1x1 cells. For 1x1 cells this will equal zero + and not change anything. + """ + if (grid_position_y + content_cell_size_y - 1) == grid_size_y - 1 and ( + (grid_position_y + content_cell_size_y - 1) + 1 in self.h_divider_line_rows ): self._divider_lines.append( { - "shape": _horizontal_divider_line, - "tilegrid": _bottom_divider_tilegrid, + "rect": _bottom_divider_rect, } ) + + """ + Every cell whose index is in h_divider_line_rows gets + a top divider line. + """ if grid_position_y in self.h_divider_line_rows: self._divider_lines.append( { - "shape": _horizontal_divider_line, - "tilegrid": _top_divider_tilegrid, + "rect": _top_divider_rect, } ) + + """ + Every cell whose index is in v_divider_line_cols gets + a left divider line. + """ if grid_position_x in self.v_divider_line_cols: self._divider_lines.append( { - "shape": _horizontal_divider_line, - "tilegrid": _left_divider_tilegrid, + "rect": _left_divider_rect, } ) - if grid_position_x == grid_size_x - 1 and ( - grid_position_x + 1 in self.v_divider_line_cols + """ + Only use right divider lines on the right-most column. All + other columns rely on left divider lines of the column to their + left. Add the content_cell_size to the grid_position to account for + areas larger than 1x1 cells. For 1x1 cells this will equal zero + and not change anything. + """ + if (grid_position_x + content_cell_size_x - 1) == grid_size_x - 1 and ( + (grid_position_x + content_cell_size_x - 1) + 1 in self.v_divider_line_cols ): self._divider_lines.append( { - "shape": _vertical_divider_line, - "tilegrid": _right_divider_tilegrid, + "rect": _right_divider_rect, } ) - for line_obj in self._divider_lines: - self.append(line_obj["tilegrid"]) + for line_obj in self._divider_lines: + self.append(line_obj["rect"]) - def add_content(self, cell_content, grid_position, cell_size): + def add_content( + self, + cell_content: displayio.Group, + grid_position: Tuple[int, int], + cell_size: Tuple[int, int], + cell_anchor_point: Optional[Tuple[float, ...]] = None, + layout_cells=True, + ) -> None: """Add a child to the grid. :param cell_content: the content to add to this cell e.g. label, button, etc... @@ -296,29 +372,136 @@ def add_content(self, cell_content, grid_position, cell_size): x,y coordinates in grid cells. e.g. (1,0) :param tuple cell_size: the size and shape that the new cell should occupy. Width and height in cells inside a tuple e.g. (1, 1) + :param tuple cell_anchor_point: a tuple of floats between 0.0 and 1.0. + If passed, this value will override the cell_anchor_point of the GridLayout + for the single cell having it's content added with this function call. If omitted + then the cell_anchor_point from the GridLayout will be used. :return: None""" + + 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: + _this_cell_anchor_point = self._cell_anchor_point + sub_view_obj = { + "cell_anchor_point": _this_cell_anchor_point, "content": cell_content, "grid_position": grid_position, "cell_size": cell_size, } self._cell_content_list.append(sub_view_obj) - self._layout_cells() + if layout_cells: + self._layout_cells() - def get_cell(self, cell_coordinates): + 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): - if cell["grid_position"] == cell_coordinates: + # exact location 1x1 cell + if cell["grid_position"] == grid_position: return self._cell_content_list[index]["content"] - raise KeyError( - "GridLayout does not contain cell at coordinates {}".format( - cell_coordinates - ) - ) + # 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] + ): + return self._cell_content_list[index]["content"] + + 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]: + """ + Get the size of a 1x1 cell in pixels. Can be useful for manually + re-positioning content within cells. + + :return Tuple[int, int]: A tuple containing the (x, y) size in + pixels of a 1x1 cell in the GridLayout + """ + return (self._width // self.grid_size[0], self._height // self.grid_size[1]) + + @property + def width(self) -> int: + """ + The width in pixels of the GridLayout. + """ + return self._width + + @property + def height(self) -> int: + """ + The height in pixels of the GridLayout. + """ + return self._height + + def which_cell_contains( + self, pixel_location: Union[Tuple[int, int], List[int]] + ) -> Optional[tuple]: + """ + Given a pixel x,y coordinate returns the location of the cell + that contains the coordinate. + + :param pixel_location: x,y pixel coordinate as a tuple or list + :returns: cell coordinates x,y tuple or None if the pixel coordinates are + outside the bounds of the GridLayout + """ + cell_size = self.cell_size_pixels + if ( + not self.x <= pixel_location[0] < self.x + self.width + or not self.y <= pixel_location[1] < self.y + self.height + ): + return None + + cell_x_coord = (pixel_location[0] - self.x) // cell_size[0] + cell_y_coord = (pixel_location[1] - self.y) // cell_size[1] + + return cell_x_coord, cell_y_coord diff --git a/adafruit_displayio_layout/layouts/linear_layout.py b/adafruit_displayio_layout/layouts/linear_layout.py new file mode 100644 index 0000000..9815300 --- /dev/null +++ b/adafruit_displayio_layout/layouts/linear_layout.py @@ -0,0 +1,144 @@ +# SPDX-FileCopyrightText: Copyright (c) 2021 Tim Cocks +# +# SPDX-License-Identifier: MIT +""" +`linear_layout` +================================================================================ + +A layout that organizes cells into a vertical or horizontal line. + + +* Author(s): Tim Cocks + +Implementation Notes +-------------------- + +**Hardware:** + +**Software and Dependencies:** + +* Adafruit CircuitPython firmware for the supported boards: + https://github.com/adafruit/circuitpython/releases + +""" + +from adafruit_displayio_layout.widgets.widget import Widget + + +class LinearLayout(Widget): + """ + LinearLayout holds multiple content elements and arranges + them in a line either horizontally or vertically. + """ + + VERTICAL_ORIENTATION = 1 + HORIZONTAL_ORIENTATION = 2 + + def __init__( + self, + x, + y, + orientation=VERTICAL_ORIENTATION, + padding=0, + ): + """ + :param int x: The horizontal position of the layout + :param int y: The vertical position of the layout + :param int orientation: The orientation of the layout. Must be VERTICAL_ORIENTATION + or HORIZONTAL_ORIENTATION + :param int padding: The padding between items in the layout + """ + + super().__init__(x=x, y=y, width=1, height=1) + + self.x = x + self.y = y + self.padding = padding + if orientation not in {self.VERTICAL_ORIENTATION, self.HORIZONTAL_ORIENTATION}: + raise ValueError( + "Orientation must be either LinearLayout.VERTICAL_ORIENTATION" + " or LinearLayout.HORIZONTAL_ORIENTATION" + ) + + self.orientation = orientation + self._content_list = [] + self._prev_content_end = 0 + + def add_content(self, content): + """Add a child to the linear layout. + + :param content: the content to add to the linear layout e.g. label, button, etc... + Group subclasses that have width and height properties can be used. + + :return: None""" + + self._content_list.append(content) + self.append(content) + self._layout() + + def _layout(self): + self._prev_content_end = 0 + + for _, content in enumerate(self._content_list): + if not hasattr(content, "anchor_point"): + if self.orientation == self.VERTICAL_ORIENTATION: + content.y = self._prev_content_end + try: + self._prev_content_end = ( + self._prev_content_end + content.height + self.padding + ) + except AttributeError as error: + print(error) + try: + self._prev_content_end = ( + self._prev_content_end + content._height + self.padding + ) + except AttributeError as inner_error: + print(inner_error) + + else: + content.x = self._prev_content_end + if not hasattr(content, "tile_width"): + 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) + ) + else: # use anchor point + content.anchor_point = ( + 0, + content.anchor_point[1] if content.anchor_point is not None else 0, + ) + if self.orientation == self.VERTICAL_ORIENTATION: + content.anchored_position = (0, self._prev_content_end) + # self._prev_content_end = content.y + content.height + if not hasattr(content, "bounding_box"): + self._prev_content_end = ( + self._prev_content_end + content.height + self.padding + ) + else: + self._prev_content_end = ( + self._prev_content_end + + (content.bounding_box[3] * content.scale) + + self.padding + ) + + else: + original_achored_pos_y = ( + content.anchored_position[1] if content.anchored_position is not None else 0 + ) + + content.anchored_position = ( + self._prev_content_end, + original_achored_pos_y, + ) + if not hasattr(content, "bounding_box"): + self._prev_content_end = ( + self._prev_content_end + content.width + self.padding + ) + else: + self._prev_content_end = ( + self._prev_content_end + + (content.bounding_box[2] * content.scale) + + self.padding + ) diff --git a/adafruit_displayio_layout/layouts/page_layout.py b/adafruit_displayio_layout/layouts/page_layout.py new file mode 100644 index 0000000..8e69c63 --- /dev/null +++ b/adafruit_displayio_layout/layouts/page_layout.py @@ -0,0 +1,218 @@ +# SPDX-FileCopyrightText: 2022 Tim Cocks +# +# SPDX-License-Identifier: MIT + +""" +`page_layout` +================================================================================ + +A layout that organizes pages which can be viewed one at a time. + + +* Author(s): Tim Cocks + +Implementation Notes +-------------------- + +**Hardware:** + +**Software and Dependencies:** + +* Adafruit CircuitPython firmware for the supported boards: + https://github.com/adafruit/circuitpython/releases + +""" + +try: + # Used only for typing + from typing import Tuple + +except ImportError: + pass + +import displayio + +__version__ = "0.0.0+auto.0" +__repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_DisplayIO_Layout.git" + + +class PageLayout(displayio.Group): + """ + A layout that organizes children into a grid table structure. + + :param int x: x location the layout should be placed. Pixel coordinates. + :param int y: y location the layout should be placed. Pixel coordinates. + """ + + def __init__( + self, + x, + y, + ): + super().__init__(x=x, y=y) + self.x = x + self.y = y + + self.page_content_list = [] + self._cur_showing_index = 0 + + def add_content(self, page_content, page_name=None): + """Add a child to the page layout. + + :param page_content: the content for the page typically a Group + :param page_name: the name of this page + + :return: None""" + + _page_group = displayio.Group() + _page_group.append(page_content) + + sub_view_obj = { + "content": _page_group, + "page_name": page_name, + } + + if len(self.page_content_list) > 0: + _page_group.hidden = True + + self.page_content_list.append(sub_view_obj) + self.append(_page_group) + + def _check_args(self, page_name, page_index): + """ + Ensure supplied arguments are valid + + :param string page_name: name of a page + :param int page_index: index of a page + :return: None + """ + if page_name is None and page_index is None: + 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") + + if page_index is not None: + if page_index >= len(self.page_content_list): + raise KeyError( + f"KeyError at index {page_index} in list length {len(self.page_content_list)}", + ) + + if page_name is not None: + _found = False + for page in self.page_content_list: + if not _found: + if page_name == page["page_name"]: + _found = True + + if not _found: + raise KeyError(f"Page with name {page_name} not found") + + def get_page(self, page_name=None, page_index=None): + """ + Return a page content based on the name or index. Raises + KeyError if the page was not found in the PageLayout. + + :param string page_name: the name of the page to lookup + :param int page_index: the index of the page to lookup + :return: the displayio content object at those coordinates + """ + + self._check_args(page_name, page_index) + + if page_index is not None: + return self.page_content_list[page_index] + + if page_name is not None: + for cell in self.page_content_list: + if cell["page_name"] == page_name: + return cell + + raise KeyError( + f"PageLayout does not contain page: {page_index if page_index else page_name}" + ) + + def show_page(self, page_name=None, page_index=None): + """ + Show the specified page, and hide all other pages. + + :param string page_name: The name of a page to show + :param int page_index: The index of a page to show + :return: None + """ + + self._check_args(page_name, page_index) + + for cur_index, page in enumerate(self.page_content_list): + if page_name is not None: + if page["page_name"] == page_name: + self._cur_showing_index = cur_index + page["content"].hidden = False + else: + page["content"].hidden = True + + if page_index is not None: + if cur_index == page_index: + self._cur_showing_index = cur_index + page["content"].hidden = False + else: + page["content"].hidden = True + + @property + def showing_page_index(self): + """ + Index of the currently showing page + :return int: showing_page_index + """ + return self._cur_showing_index + + @showing_page_index.setter + def showing_page_index(self, new_index): + self.show_page(page_index=new_index) + + @property + def showing_page_name(self): + """ + Name of the currently showing page + :return string: showing_page_name + """ + return self.page_content_list[self._cur_showing_index]["page_name"] + + @showing_page_name.setter + def showing_page_name(self, new_name): + self.show_page(page_name=new_name) + + @property + def showing_page_content(self): + """ + The content object for the currently showing page + :return Displayable: showing_page_content + """ + return self.page_content_list[self._cur_showing_index]["content"][0] + + def next_page(self, loop=True): + """ + Hide the current page and show the next one in the list by index + :param bool loop: whether to loop from the last page back to the first + :return: None + """ + + 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: + self.show_page(page_index=0) + + def previous_page(self, loop=True): + """ + Hide the current page and show the previous one in the list by index + :param bool loop: whether to loop from the first page to the last one + :return: None + """ + 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: + 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 new file mode 100644 index 0000000..84f70fb --- /dev/null +++ b/adafruit_displayio_layout/layouts/tab_layout.py @@ -0,0 +1,282 @@ +# SPDX-FileCopyrightText: 2022 Tim Cocks +# +# SPDX-License-Identifier: MIT + +""" +`tab_layout` +================================================================================ + +A layout that organizes pages into tabs. + + +* Author(s): Tim Cocks + +Implementation Notes +-------------------- + +**Hardware:** + +**Software and Dependencies:** + +* Adafruit CircuitPython firmware for the supported boards: + https://github.com/adafruit/circuitpython/releases + +""" + +try: + 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 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" +__repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_DisplayIO_Layout.git" + + +class TabLayout(displayio.Group): + """ + A layout that organizes children into a grid table structure. + + .. warning:: + Requires CircuitPython version 7.3.0-beta.2 or newer + + :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 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 + for the tab labels + :param str inactive_tab_spritesheet: Filepath of the spritesheet to show for inactive tabs. + :param str showing_tab_spritesheet: Filepath of the spritesheet to show for the active tab. + :param Optional[int, tuple[int, int, int]] showing_tab_text_color: Hex or tuple color to use + for the active tab label + :param Optional[int, tuple[int, int, int]] inactive_tab_text_color: Hex or tuple color to + use for inactive tab labels + :param Optional[Union[int, tuple[int, int]]] inactive_tab_transparent_indexes: single index + or tuple of multiple indexes to be made transparent in the inactive tab sprite palette. + :param Optional[Union[int, tuple[int, int]]] showing_tab_transparent_indexes: single index + or tuple of multiple indexes to be made transparent in the active tab sprite palette. + :param int tab_count: How many tabs to draw in the layout. Positive whole numbers are valid. + """ + + def __init__( + self, + x: int = 0, + y: int = 0, + display: Optional[AnyDisplay] = None, + tab_text_scale: int = 1, + custom_font: Optional[Union[BuiltinFont, BDF, PCF]] = terminalio.FONT, + inactive_tab_spritesheet: Optional[str] = None, + showing_tab_spritesheet: Optional[str] = None, + showing_tab_text_color: Optional[Union[int, Tuple[int, int, int]]] = 0x999999, + inactive_tab_text_color: Optional[Union[int, Tuple[int, int, int]]] = 0xFFFFF, + inactive_tab_transparent_indexes: Optional[Union[int, Tuple[int, int]]] = None, + showing_tab_transparent_indexes: Optional[Union[int, Tuple[int, int]]] = None, + tab_count: int = None, + ): + if display is None: + import board # noqa: PLC0415, non-top-level-import + + if hasattr(board, "DISPLAY"): + display = board.DISPLAY + if inactive_tab_spritesheet is None: + raise AttributeError("Must pass inactive_tab_spritesheet") + if showing_tab_spritesheet is None: + raise AttributeError("Must pass showing_tab_spritesheet") + if tab_count is None: + raise AttributeError("Must pass tab_count") + + super().__init__(x=x, y=y) + self.tab_count = tab_count + self._active_bmp, self._active_palette = adafruit_imageload.load(showing_tab_spritesheet) + self._inactive_bmp, self._inactive_palette = adafruit_imageload.load( + inactive_tab_spritesheet + ) + + if isinstance(showing_tab_transparent_indexes, int): + self._active_palette.make_transparent(showing_tab_transparent_indexes) + elif isinstance(showing_tab_transparent_indexes, tuple): + for index in showing_tab_transparent_indexes: + self._active_palette.make_transparent(index) + else: + raise AttributeError("active_tab_transparent_indexes must be int or tuple") + + if isinstance(inactive_tab_transparent_indexes, int): + self._inactive_palette.make_transparent(inactive_tab_transparent_indexes) + elif isinstance(inactive_tab_transparent_indexes, tuple): + 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") + + self.tab_height = self._active_bmp.height + self.display = display + self.active_tab_text_color = showing_tab_text_color + self.inactive_tab_text_color = inactive_tab_text_color + self.custom_font = custom_font + self.tab_text_scale = tab_text_scale + self.tab_group = displayio.Group() + self.tab_dict = {} + self.page_layout = PageLayout(x=x, y=y + self.tab_height) + + self.append(self.tab_group) + self.append(self.page_layout) + + def _draw_tabs(self): + for i, page_dict in enumerate(self.page_layout.page_content_list): + if i not in self.tab_dict: + print(f"creating tab {i}") + _new_tab_group = displayio.Group() + _tab_tilegrid = inflate_tilegrid( + bmp_obj=self._inactive_bmp, + bmp_palette=self._inactive_palette, + target_size=( + (self.display.width // self.tab_count) // (self._active_bmp.width // 3), + 3, + ), + ) + + _tab_tilegrid.x = (self.display.width // self.tab_count) * i + _new_tab_group.append(_tab_tilegrid) + + _tab_label = Label( + self.custom_font, + text=page_dict["page_name"], + color=self.inactive_tab_text_color, + scale=self.tab_text_scale, + ) + + _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.height * _tab_tilegrid.tile_height) // 2, + ) + _new_tab_group.append(_tab_label) + + if i == self.page_layout.showing_page_index: + try: + _tab_tilegrid.bitmap = self._active_bmp + except AttributeError as e: + print(e) + raise ( + AttributeError( + "TabLayout requires CircuitPython version 7.3.0-beta.2 or newer." + ) + ) from e + _tab_tilegrid.pixel_shader = self._active_palette + _tab_label.color = self.active_tab_text_color + self.tab_dict[i] = _new_tab_group + self.tab_group.append(_new_tab_group) + + def _update_active_tab(self): + for i in range(len(self.page_layout)): + if i == self.page_layout.showing_page_index: + self.tab_group[i][0].bitmap = self._active_bmp + self.tab_group[i][0].pixel_shader = self._active_palette + self.tab_group[i][1].color = self.active_tab_text_color + else: + self.tab_group[i][0].bitmap = self._inactive_bmp + self.tab_group[i][0].pixel_shader = self._inactive_palette + self.tab_group[i][1].color = self.inactive_tab_text_color + + def add_content(self, tab_content, tab_name): + """Add a child to the tab layout. + + :param tab_content: the content for the tab typically a Group + :param tab_name: the name of this tab, will be shown inside the tab + + :return: None""" + self.page_layout.add_content(tab_content, tab_name) + self._draw_tabs() + + def show_page(self, page_name=None, page_index=None): + """ + Show the specified page, and hide all other pages. + + :param string page_name: The name of a page to show + :param int page_index: The index of a page to show + :return: None + """ + + self.page_layout.show_page(page_name=page_name, page_index=page_index) + self._update_active_tab() + + @property + def showing_page_index(self): + """ + Index of the currently showing page + :return int: showing_page_index + """ + return self.page_layout.showing_page_index + + @showing_page_index.setter + def showing_page_index(self, new_index): + if self.showing_page_index != new_index: + self.show_page(page_index=new_index) + + @property + def showing_page_name(self): + """ + Name of the currently showing page + :return string: showing_page_name + """ + return self.page_layout.showing_page_name + + @showing_page_name.setter + def showing_page_name(self, new_name): + self.show_page(page_name=new_name) + + @property + def showing_page_content(self): + """ + The content object for the currently showing page + :return Displayable: showing_page_content + """ + return self.page_layout.showing_page_content + + def next_page(self, loop=True): + """ + Hide the current page and show the next one in the list by index + :param bool loop: whether to loop from the last page back to the first + :return: None + """ + + self.page_layout.next_page(loop=loop) + self._update_active_tab() + + def previous_page(self, loop=True): + """ + Hide the current page and show the previous one in the list by index + :param bool loop: whether to loop from the first page to the last one + :return: None + """ + self.page_layout.previous_page(loop=loop) + self._update_active_tab() + + def handle_touch_events(self, touch_event): + """ + Check if the touch event is on the tabs and if so change to the touched tab. + + :param tuple touch_event: tuple containing x and y coordinates of the + touch event in indexes 0 and 1. + :return: None + """ + + if touch_event: + if 0 <= touch_event[1] <= self.tab_height: + 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/__init__.py b/adafruit_displayio_layout/widgets/__init__.py index ed1bae9..d56d8b0 100644 --- a/adafruit_displayio_layout/widgets/__init__.py +++ b/adafruit_displayio_layout/widgets/__init__.py @@ -1,64 +1,3 @@ # SPDX-FileCopyrightText: 2021 Kevin Matocha, Tim C, Jose David M # # SPDX-License-Identifier: MIT - -""" -`adafruit_displayio_layout.widgets` -======================= -""" - -import vectorio - -try: - import bitmaptools -except NameError: - pass - - -# pylint: disable=invalid-name, too-many-arguments -def rectangle_helper( - x0: int, - y0: int, - height: int, - width: int, - bitmap, - color_index: int, - palette, - bitmaptool: bool = True, -) -> None: - """rectangle_helper function - Draws a rectangle to the bitmap given using ``bitmapstools.bitmap`` or - ``vectorio.rectangle`` functions - - :param int x0: rectangle lower corner x position - :param int y0: rectangle lower corner y position - - :param int width: rectangle upper corner x position - :param int height: rectangle upper corner y position - - :param int color_index: palette color index to be used - :param palette: palette object to be used to draw the rectangle - - :param bitmap: bitmap for the rectangle to be drawn - :param bool bitmaptool: uses :py:func:`~bitmaptools.draw_line` to draw the rectanlge. - when `False` uses :py:func:`~vectorio.Rectangle` - - :return: None - :rtype: None - - ┌───────────────────────┐ - │ │ - │ │ - (x0,y0) └───────────────────────┘ - - """ - if bitmaptool: - bitmaptools.fill_region(bitmap, x0, y0, x0 + width, y0 + height, color_index) - else: - rect = vectorio.Rectangle(width, height) - vectorio.VectorShape( - shape=rect, - pixel_shader=palette, - x=x0, - y=y0, - ) diff --git a/adafruit_displayio_layout/widgets/cartesian.py b/adafruit_displayio_layout/widgets/cartesian.py index fa678c6..50ca8a3 100644 --- a/adafruit_displayio_layout/widgets/cartesian.py +++ b/adafruit_displayio_layout/widgets/cartesian.py @@ -21,26 +21,28 @@ """ -# 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 -from adafruit_displayio_layout.widgets import rectangle_helper try: import bitmaptools -except NameError: +except ImportError: pass + try: - from typing import Tuple + from typing import Any, List, Optional, Tuple except ImportError: pass +__version__ = "0.0.0+auto.0" +__repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_DisplayIO_Layout.git" + + class Cartesian(Widget): """A cartesian widget. The origin is set using ``x`` and ``y``. @@ -73,6 +75,7 @@ class Cartesian(Widget): :param int nudge_y: movement in pixels in the y direction to move the origin. Defaults to 0 + :param bool verbose: print debugging information in some internal functions. Default to False **Quickstart: Importing and using Cartesian** @@ -93,7 +96,7 @@ class Cartesian(Widget): .. code-block:: python - display.show(my_plane) # add the group to the display + display.root_group = my_plane # add the group to the display If you want to have multiple display elements, you can create a group and then append the plane and the other elements to the group. Then, you can add the full @@ -109,7 +112,7 @@ class Cartesian(Widget): # Append other display elements to the group # - display.show(my_group) # add the group to the display + display.root_group = my_group # add the group to the display **Summary: Cartesian Features and input variables** @@ -132,7 +135,7 @@ class Cartesian(Widget): - **range**: ``xrange`` and ``yrange`` This is the range in absolute units. For example, when using (20-90), the X axis will start at 20 finishing at 90. - However the height of the graph is given by the height parameter. The scale + However, the height of the graph is given by the height parameter. The scale is handled internal to provide a 1:1 experience when you update the graph. @@ -173,18 +176,20 @@ def __init__( tick_color: int = 0xFFFFFF, major_tick_stroke: int = 1, major_tick_length: int = 5, - tick_label_font=terminalio.FONT, + tick_label_font: terminalio.FONT = terminalio.FONT, font_color: int = 0xFFFFFF, pointer_radius: int = 1, pointer_color: int = 0xFFFFFF, subticks: bool = False, nudge_x: int = 0, nudge_y: int = 0, - **kwargs, + verbose: bool = False, + **kwargs: Any, ) -> None: - super().__init__(**kwargs) + self._verbose = verbose + self._background_color = background_color self._axes_line_color = axes_color @@ -222,35 +227,27 @@ 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) self._axesy_bitmap.fill(0) - self._screen_bitmap = displayio.Bitmap(self.width, self.height, 5) - self._screen_bitmap.fill(5) + self._plot_bitmap = displayio.Bitmap(self.width, self.height, 5) + self.clear_plot_lines() self._screen_palette = displayio.Palette(6) self._screen_palette.make_transparent(0) self._screen_palette[1] = self._tick_color @@ -260,14 +257,14 @@ def __init__( self._screen_palette[5] = self._background_color self._corner_bitmap = displayio.Bitmap(10, 10, 5) - rectangle_helper( + + bitmaptools.fill_region( + self._corner_bitmap, 0, 0, self._axes_line_thickness, self._axes_line_thickness, - self._corner_bitmap, 2, - self._screen_palette, ) self._corner_tilegrid = displayio.TileGrid( @@ -292,7 +289,7 @@ def __init__( ) self._screen_tilegrid = displayio.TileGrid( - self._screen_bitmap, + self._plot_bitmap, pixel_shader=self._screen_palette, x=0, y=0, @@ -309,15 +306,12 @@ def __init__( self.append(self._screen_tilegrid) self.append(self._corner_tilegrid) - self._update_line = True - - self._pointer = None - self._circle_palette = None - self._pointer_vector_shape = None - self.plot_line_point = None + self._pointer: Optional[vectorio.Circle] = None + self._circle_palette: Optional[displayio.Palette] = None + self.plot_line_point: List[Tuple[int, int]] = [] @staticmethod - def _get_font_height(font, scale: int) -> Tuple[int, int]: + def _get_font_height(font: terminalio.FONT, scale: int) -> Tuple[int, int]: if hasattr(font, "get_bounding_box"): font_height = int(scale * font.get_bounding_box()[1]) font_width = int(scale * font.get_bounding_box()[0]) @@ -330,28 +324,22 @@ def _get_font_height(font, scale: int) -> Tuple[int, int]: return font_width, font_height def _draw_axes(self) -> None: - # Draw x axes line - rectangle_helper( + bitmaptools.fill_region( + self._axesx_bitmap, 0, 0, - self._axes_line_thickness, self.width, - self._axesx_bitmap, + self._axes_line_thickness, 2, - self._screen_palette, - True, ) - # Draw y axes line - rectangle_helper( + bitmaptools.fill_region( + self._axesy_bitmap, self._axesy_width - self._axes_line_thickness, 0, + self._axesy_width, self.height, - self._axes_line_thickness, - self._axesy_bitmap, 2, - self._screen_palette, - True, ) def _draw_ticks(self) -> None: @@ -376,28 +364,28 @@ def _draw_ticks(self) -> None: + 1, ) self.append(tick_text) - rectangle_helper( + + bitmaptools.fill_region( + self._axesx_bitmap, text_dist, self._axes_line_thickness, - self._tick_line_height, - self._tick_line_thickness, - self._axesx_bitmap, + text_dist + self._tick_line_thickness, + self._axes_line_thickness + self._tick_line_height, 1, - self._screen_palette, - True, ) if self._subticks: if i in subticks: - rectangle_helper( + # calc subtick_line_height; force min lineheigt to 1. + subtick_line_height = max(1, self._tick_line_height // 2) + + bitmaptools.fill_region( + self._axesx_bitmap, text_dist, self._axes_line_thickness, - self._tick_line_height // 2, + text_dist + 1, + self._axes_line_thickness + subtick_line_height, 1, - self._axesx_bitmap, - 1, - self._screen_palette, - True, ) # Y axes ticks @@ -410,56 +398,132 @@ 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) - rectangle_helper( - self._axesy_width - - self._axes_line_thickness - - self._tick_line_height - - 1, - text_dist, - self._tick_line_thickness, - self._tick_line_height, + + bitmaptools.fill_region( self._axesy_bitmap, + 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, 1, - self._screen_palette, - True, ) if self._subticks: if i in subticks: - rectangle_helper( + bitmaptools.fill_region( + self._axesy_bitmap, self._axesy_width - self._axes_line_thickness - self._tick_line_height // 2 - 1, text_dist, + self._axesy_width - self._axes_line_thickness - 1, + text_dist + 1, 1, - self._tick_line_height // 2, - self._axesy_bitmap, - 1, - self._screen_palette, - True, ) def _draw_pointers(self, x: int, y: int) -> None: - self._pointer = vectorio.Circle(self._pointer_radius) - self._circle_palette = displayio.Palette(2) - self._circle_palette.make_transparent(0) - self._circle_palette[1] = self._pointer_color - - self._pointer_vector_shape = vectorio.VectorShape( - shape=self._pointer, - pixel_shader=self._circle_palette, - x=x, - y=y, + self._circle_palette = displayio.Palette(1) + + self._circle_palette[0] = self._pointer_color + self._pointer = vectorio.Circle( + radius=self._pointer_radius, x=x, y=y, pixel_shader=self._circle_palette + ) + + 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 + # 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 + local_y = ( + int((self._yrange[0] - y) * self._factory * self._valuey) + + (self.height - 1) + + self._nudge_y ) - self.append(self._pointer_vector_shape) + return (local_x, local_y) + + def _check_local_x_in_range(self, local_x: int) -> bool: + return 0 <= local_x < self.width + + 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) + + def _check_x_in_range(self, x: int) -> bool: + return self._xrange[0] <= x <= self._xrange[1] + + def _check_y_in_range(self, y: int) -> bool: + return self._yrange[0] <= y <= self._yrange[1] + + def _check_xy_in_range(self, x: int, y: int) -> bool: + return self._check_x_in_range(x) and self._check_y_in_range(y) + + def _add_point(self, x: int, y: int) -> None: + """_add_point function + helper function to add a point to the graph in the plane + :param int x: ``x`` coordinate in the local plane + :param int y: ``y`` coordinate in the local plane + :return: None + rtype: None + """ + local_x, local_y = self._calc_local_xy(x, y) + if self._verbose: + print("") + print( + 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( + 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): + if self.plot_line_point is None: + self.plot_line_point = [] + self.plot_line_point.append((local_x, local_y)) + else: + # for better error messages we check in detail what failed... + # this should never happen: + # we already checked the range of the input values. + # but in case our calculation is wrong we handle this case to.. + if not self._check_local_x_in_range(local_x): + raise ValueError( + "local_x out of range: " + 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: " + 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: " + 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: " + f"y:{y: >4}; yrange({self._yrange[0]: >4}, {self._yrange[1]: >4})" + "" + ) def update_pointer(self, x: int, y: int) -> None: """updater_pointer function @@ -469,46 +533,51 @@ def update_pointer(self, x: int, y: int) -> None: :return: None rtype: None """ - local_x = int((x - self._xrange[0]) * self._factorx) + self._nudge_x - local_y = ( - int((self._yrange[0] - y) * self._factory) + self.height + self._nudge_y - ) - if local_x >= 0 or local_y <= 100: - if self._update_line: - self._draw_pointers(local_x, local_y) - self._update_line = False - else: - self._pointer_vector_shape.x = local_x - self._pointer_vector_shape.y = local_y + self._add_point(x, y) + if not self._pointer: + self._draw_pointers( + self.plot_line_point[-1][0], + self.plot_line_point[-1][1], + ) + else: + self._pointer.x = self.plot_line_point[-1][0] + self._pointer.y = self.plot_line_point[-1][1] - def _set_plotter_line(self) -> None: - self.plot_line_point = [] + def add_plot_line(self, x: int, y: int) -> None: + """add_plot_line function. + + add line to the plane. + multiple calls create a line-plot graph. - def update_line(self, x: int, y: int) -> None: - """updater_line function - helper function to update pointer in the plane :param int x: ``x`` coordinate in the local plane :param int y: ``y`` coordinate in the local plane :return: None + rtype: None """ - local_x = int((x - self._xrange[0]) * self._factorx) + self._nudge_x - local_y = ( - int((self._yrange[0] - y) * self._factory) + self.height + self._nudge_y - ) - if x < self._xrange[1] and y < self._yrange[1]: - if local_x > 0 or local_y < 100: - if self._update_line: - self._set_plotter_line() - self.plot_line_point.append((local_x, local_y)) - self._update_line = False - else: - bitmaptools.draw_line( - self._screen_bitmap, - self.plot_line_point[-1][0], - self.plot_line_point[-1][1], - local_x, - local_y, - 1, - ) + + self._add_point(x, y) + if len(self.plot_line_point) > 1: + bitmaptools.draw_line( + self._plot_bitmap, + self.plot_line_point[-2][0], + self.plot_line_point[-2][1], + self.plot_line_point[-1][0], + self.plot_line_point[-1][1], + 1, + ) + + def clear_plot_lines(self, palette_index: int = 5) -> None: + """clear_plot_lines function. + + clear all added lines + (clear line-plot graph) + + :param int palette_index: color palett index. Defaults to 5 + :return: None + + rtype: None + """ + self.plot_line_point = [] + self._plot_bitmap.fill(palette_index) diff --git a/adafruit_displayio_layout/widgets/control.py b/adafruit_displayio_layout/widgets/control.py index 4fbff5a..464804e 100644 --- a/adafruit_displayio_layout/widgets/control.py +++ b/adafruit_displayio_layout/widgets/control.py @@ -21,10 +21,14 @@ """ -__version__ = "0.0.0-auto.0" -__repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_DisplayIO_Layout.git" +try: + from typing import Optional, Tuple +except ImportError: + pass + -# pylint: disable=unsubscriptable-object, unnecessary-pass +__version__ = "0.0.0+auto.0" +__repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_DisplayIO_Layout.git" class Control: @@ -46,20 +50,24 @@ class Control: def __init__( self, - ): + ) -> None: self.touch_boundary = ( - None # `self.touch_boundary` should be updated by the subclass + 0, + 0, + 0, + 0, # `self.touch_boundary` should be updated by the subclass ) # Tuple of [x, y, width, height]: [int, int, int, int] all in pixel units # where x,y define the upper left corner # and width and height define the size of the `touch_boundary` - def contains(self, touch_point): + def contains(self, touch_point: Tuple[int, int, Optional[int]]) -> bool: """Checks if the Control was touched. Returns True if the touch_point is within the Control's touch_boundary. - :param touch_point: x,y location of the screen, converted to local coordinates. - :type touch_point: Tuple[x,y] + :param touch_point: x, y, p location of the screen, converted to local coordinates, plus + an optional pressure value for screens that support it. + :type touch_point: Tuple[int, int, Optional[int]] :return: Boolean """ @@ -82,11 +90,12 @@ def contains(self, touch_point): return False # place holder touch_handler response functions - def selected(self, touch_point): + def selected(self, touch_point: Tuple[int, int, Optional[int]]) -> None: """Response function when Control is selected. Should be overridden by subclass. - :param touch_point: x,y location of the screen, converted to local coordinates. - :type touch_point: Tuple[x,y] + :param touch_point: x, y, p location of the screen, converted to local coordinates, plus + an optional pressure value for screens that support it. + :type touch_point: Tuple[int, int, Optional[int]] :return: None """ diff --git a/adafruit_displayio_layout/widgets/easing.py b/adafruit_displayio_layout/widgets/easing.py index 0bfd8ef..db2d2d0 100644 --- a/adafruit_displayio_layout/widgets/easing.py +++ b/adafruit_displayio_layout/widgets/easing.py @@ -75,8 +75,12 @@ import math +__version__ = "0.0.0+auto.0" +__repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_DisplayIO_Layout.git" + + # Modeled after the line y = x -def linear_interpolation(pos): +def linear_interpolation(pos: float) -> float: """ Easing function for animations: Linear Interpolation. """ @@ -84,7 +88,7 @@ def linear_interpolation(pos): # Modeled after the parabola y = x^2 -def quadratic_easein(pos): +def quadratic_easein(pos: float) -> float: """ Easing function for animations: Quadratic Ease In """ @@ -92,7 +96,7 @@ def quadratic_easein(pos): # Modeled after the parabola y = -x^2 + 2x -def quadratic_easeout(pos): +def quadratic_easeout(pos: float) -> float: """ Easing function for animations: Quadratic Ease Out. """ @@ -102,7 +106,7 @@ def quadratic_easeout(pos): # Modeled after the piecewise quadratic # y = (1/2)((2x)^2) ; [0, 0.5) # y = -(1/2)((2x-1)*(2x-3) - 1) ; [0.5, 1] -def quadratic_easeinout(pos): +def quadratic_easeinout(pos: float) -> float: """ Easing function for animations: Quadratic Ease In & Out """ @@ -112,7 +116,7 @@ def quadratic_easeinout(pos): # Modeled after the cubic y = x^3 -def cubic_easein(pos): +def cubic_easein(pos: float) -> float: """ Easing function for animations: Cubic Ease In """ @@ -120,7 +124,7 @@ def cubic_easein(pos): # Modeled after the cubic y = (x - 1)^3 + 1 -def cubic_easeout(pos): +def cubic_easeout(pos: float) -> float: """ Easing function for animations: Cubic Ease Out """ @@ -131,7 +135,7 @@ def cubic_easeout(pos): # Modeled after the piecewise cubic # y = (1/2)((2x)^3) ; [0, 0.5) # y = (1/2)((2x-2)^3 + 2) ; [0.5, 1] -def cubic_easeinout(pos): +def cubic_easeinout(pos: float) -> float: """ Easing function for animations: Cubic Ease In & Out """ @@ -142,7 +146,7 @@ def cubic_easeinout(pos): # Modeled after the quartic x^4 -def quartic_easein(pos): +def quartic_easein(pos: float) -> float: """ Easing function for animations: Quartic Ease In """ @@ -150,7 +154,7 @@ def quartic_easein(pos): # Modeled after the quartic y = 1 - (x - 1)^4 -def quartic_easeout(pos): +def quartic_easeout(pos: float) -> float: """ Easing function for animations: Quartic Ease Out """ @@ -161,7 +165,7 @@ def quartic_easeout(pos): # Modeled after the piecewise quartic # y = (1/2)((2x)^4) ; [0, 0.5) # y = -(1/2)((2x-2)^4 - 2) ; [0.5, 1] -def quartic_easeinout(pos): +def quartic_easeinout(pos: float) -> float: """ Easing function for animations: Quartic Ease In & Out """ @@ -172,7 +176,7 @@ def quartic_easeinout(pos): # Modeled after the quintic y = x^5 -def quintic_easein(pos): +def quintic_easein(pos: float) -> float: """ Easing function for animations: Quintic Ease In """ @@ -180,7 +184,7 @@ def quintic_easein(pos): # Modeled after the quintic y = (x - 1)^5 + 1 -def quintic_easeout(pos): +def quintic_easeout(pos: float) -> float: """ Easing function for animations: Quintic Ease Out """ @@ -191,7 +195,7 @@ def quintic_easeout(pos): # Modeled after the piecewise quintic # y = (1/2)((2x)^5) ; [0, 0.5) # y = (1/2)((2x-2)^5 + 2) ; [0.5, 1] -def quintic_easeinout(pos): +def quintic_easeinout(pos: float) -> float: """ Easing function for animations: Quintic Ease In & Out """ @@ -202,7 +206,7 @@ def quintic_easeinout(pos): # Modeled after quarter-cycle of sine wave -def sine_easein(pos): +def sine_easein(pos: float) -> float: """ Easing function for animations: Sine Ease In """ @@ -210,7 +214,7 @@ def sine_easein(pos): # Modeled after quarter-cycle of sine wave (different phase) -def sine_easeout(pos): +def sine_easeout(pos: float) -> float: """ Easing function for animations: Sine Ease Out """ @@ -218,7 +222,7 @@ def sine_easeout(pos): # Modeled after half sine wave -def sine_easeinout(pos): +def sine_easeinout(pos: float) -> float: """ Easing function for animations: Sine Ease In & Out """ @@ -226,7 +230,7 @@ def sine_easeinout(pos): # Modeled after shifted quadrant IV of unit circle -def circular_easein(pos): +def circular_easein(pos: float) -> float: """ Easing function for animations: Circular Ease In """ @@ -234,7 +238,7 @@ def circular_easein(pos): # Modeled after shifted quadrant II of unit circle -def circular_easeout(pos): +def circular_easeout(pos: float) -> float: """ Easing function for animations: Circular Ease Out """ @@ -244,7 +248,7 @@ def circular_easeout(pos): # Modeled after the piecewise circular function # y = (1/2)(1 - sqrt(1 - 4x^2)) ; [0, 0.5) # y = (1/2)(sqrt(-(2x - 3)*(2x - 1)) + 1) ; [0.5, 1] -def circular_easeinout(pos): +def circular_easeinout(pos: float) -> float: """ Easing function for animations: Circular Ease In & Out """ @@ -254,7 +258,7 @@ def circular_easeinout(pos): # Modeled after the exponential function y = 2^(10(x - 1)) -def exponential_easein(pos): +def exponential_easein(pos: float) -> float: """ Easing function for animations: Exponential Ease In """ @@ -264,7 +268,7 @@ def exponential_easein(pos): # Modeled after the exponential function y = -2^(-10x) + 1 -def exponential_easeout(pos): +def exponential_easeout(pos: float) -> float: """ Easing function for animations: Exponential Ease Out """ @@ -276,11 +280,11 @@ def exponential_easeout(pos): # Modeled after the piecewise exponential # y = (1/2)2^(10(2x - 1)) ; [0,0.5) # y = -(1/2)*2^(-10(2x - 1))) + 1 ; [0.5,1] -def exponential_easeinout(pos): +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) @@ -288,7 +292,7 @@ def exponential_easeinout(pos): # Modeled after the damped sine wave y = sin(13pi/2*x)*pow(2, 10 * (x - 1)) -def elastic_easein(pos): +def elastic_easein(pos: float) -> float: """ Easing function for animations: Elastic Ease In """ @@ -296,7 +300,7 @@ def elastic_easein(pos): # Modeled after the damped sine wave y = sin(-13pi/2*(x + 1))*pow(2, -10x) + 1 -def elastic_easeout(pos): +def elastic_easeout(pos: float) -> float: """ Easing function for animations: Elastic Ease Out """ @@ -306,20 +310,19 @@ def elastic_easeout(pos): # Modeled after the piecewise exponentially-damped sine wave: # y = (1/2)*sin(13pi/2*(2*x))*pow(2, 10 * ((2*x) - 1)) ; [0,0.5) # y = (1/2)*(sin(-13pi/2*((2x-1)+1))*pow(2,-10(2*x-1)) + 2) ; [0.5, 1] -def elastic_easeinout(pos): +def elastic_easeinout(pos: float) -> float: """ Easing function for animations: Elastic Ease In & Out """ 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 ) # Modeled after the overshooting cubic y = x^3-x*sin(x*pi) -def back_easein(pos): +def back_easein(pos: float) -> float: """ Easing function for animations: Back Ease In """ @@ -327,7 +330,7 @@ def back_easein(pos): # Modeled after overshooting cubic y = 1-((1-x)^3-(1-x)*sin((1-x)*pi)) -def back_easeout(pos): +def back_easeout(pos: float) -> float: """ Easing function for animations: Back Ease Out """ @@ -338,7 +341,7 @@ def back_easeout(pos): # Modeled after the piecewise overshooting cubic function: # y = (1/2)*((2x)^3-(2x)*sin(2*x*pi)) ; [0, 0.5) # y = (1/2)*(1-((1-x)^3-(1-x)*sin((1-x)*pi))+1) ; [0.5, 1] -def back_easeinout(pos): +def back_easeinout(pos: float) -> float: """ Easing function for animations: Back Ease In & Out """ @@ -349,14 +352,14 @@ def back_easeinout(pos): return 0.5 * (1 - (fos * fos * fos - fos * math.sin(fos * math.pi))) + 0.5 -def bounce_easein(pos): +def bounce_easein(pos: float) -> float: """ Easing function for animations: Bounce Ease In """ return 1 - bounce_easeout(1 - pos) -def bounce_easeout(pos): +def bounce_easeout(pos: float) -> float: """ Easing function for animations: Bounce Ease Out """ @@ -369,7 +372,7 @@ def bounce_easeout(pos): return (54 / 5.0 * pos * pos) - (513 / 25.0 * pos) + 268 / 25.0 -def bounce_easeinout(pos): +def bounce_easeinout(pos: float) -> float: """ Easing function for animations: Bounce Ease In & Out """ diff --git a/adafruit_displayio_layout/widgets/flip_input.py b/adafruit_displayio_layout/widgets/flip_input.py index 2e2b8cb..83156dc 100644 --- a/adafruit_displayio_layout/widgets/flip_input.py +++ b/adafruit_displayio_layout/widgets/flip_input.py @@ -23,23 +23,29 @@ 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 -# pylint: disable=too-many-arguments, too-many-branches, too-many-statements -# pylint: disable=too-many-locals, too-many-instance-attributes + +__version__ = "0.0.0+auto.0" +__repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_DisplayIO_Layout.git" class FlipInput(Widget, Control): @@ -50,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``) @@ -83,25 +89,25 @@ class FlipInput(Widget, Control): def __init__( self, - display, + display: AnyDisplay, *, - value_list=None, - font=FONT, - font_scale=1, - color=0xFFFFFF, - value=0, # initial value, index into the value_list - arrow_touch_padding=0, # additional touch padding on the arrow sides of the Widget - arrow_color=0x333333, - arrow_outline=0x555555, - arrow_height=30, - arrow_width=30, - arrow_gap=5, - alt_touch_padding=0, # touch padding on the non-arrow sides of the Widget - horizontal=True, - animation_time=None, - cool_down=0.0, - **kwargs, - ): + value_list: List[str], + font: FONT = FONT, + font_scale: int = 1, + color: int = 0xFFFFFF, + value: int = 0, # initial value, index into the value_list + arrow_touch_padding: int = 0, # additional touch padding on the arrow sides of the Widget + arrow_color: int = 0x333333, + arrow_outline: int = 0x555555, + arrow_height: int = 30, + arrow_width: int = 30, + arrow_gap: int = 5, + alt_touch_padding: int = 0, # touch padding on the non-arrow sides of the Widget + horizontal: bool = True, + animation_time: Optional[float] = None, + cool_down: float = 0.0, + **kwargs: Any, + ) -> None: super().__init__(**kwargs) # Group elements for the FlipInput. # 0. The text @@ -111,7 +117,6 @@ def __init__( # initialize the Control superclass - # pylint: disable=bad-super-call super(Control, self).__init__() self.value_list = value_list @@ -147,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, @@ -178,6 +179,9 @@ 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 + self._bounding_box = [ 0, 0, @@ -207,28 +211,22 @@ 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.touch_boundary = ( + 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), self._bounding_box[3] + 2 * self._alt_touch_padding, - ] + ) else: # vertical orientation, add arrow padding to y-dimension and # alt_padding to x-dimension - self.touch_boundary = [ + 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), - ] + ) # create the Up/Down arrows self._update_position() # call Widget superclass function to reposition @@ -244,14 +242,8 @@ def __init__( # Add the two arrow triangles, if required 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( @@ -268,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] @@ -285,55 +273,43 @@ 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, animate=True): - + def _update_value(self, new_value: int, animate: bool = True) -> None: if ( (self._animation_time is not None) and (self._animation_time > 0) # If animation is required and (animate) ): - if ((new_value - self.value) == 1) or ( (self.value == (len(self.value_list) - 1)) and (new_value == 0) ): # wrap around @@ -357,9 +333,7 @@ def _update_value(self, new_value, animate=True): 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) @@ -371,20 +345,20 @@ def _update_value(self, new_value, animate=True): start_bitmap.blit(0, 0, self._label.bitmap) # get the bitmap1 position offsets - bitmap1_offset = [ + bitmap1_offset = ( -1 * self._left + self._label.tilegrid.x, -1 * self._top + self._label.tilegrid.y, - ] + ) # hide the label group self.pop(0) # update the value label and get the bitmap offsets self._label.text = str(self.value_list[new_value]) - bitmap2_offset = [ + bitmap2_offset = ( -1 * self._left + self._label.tilegrid.x, -1 * self._top + self._label.tilegrid.y, - ] + ) # animate between old and new bitmaps _animate_bitmap( @@ -419,7 +393,7 @@ def _update_value(self, new_value, animate=True): self._display.auto_refresh = True self._update_position() # call Widget superclass function to reposition - def _ok_to_change(self): # checks state variable and timers to determine + def _ok_to_change(self) -> bool: # checks state variable and timers to determine # if an update is allowed if self._cool_down < 0: # if cool_down is negative, require ``released`` # to be called before next change @@ -428,7 +402,9 @@ def _ok_to_change(self): # checks state variable and timers to determine return False # cool_down time has not transpired return True - def contains(self, touch_point): # overrides, then calls Control.contains(x,y) + def contains( + self, touch_point: Tuple[int, int, Optional[int]] + ) -> bool: # overrides, then calls Control.contains(x,y) """Returns True if the touch_point is within the widget's touch_boundary.""" ###### @@ -437,14 +413,12 @@ def contains(self, touch_point): # overrides, then calls Control.contains(x,y) # 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)) - def selected(self, touch_point): + def selected(self, touch_point: Tuple[int, int, Optional[int]]) -> None: """Response function when the Control is selected. Increases value when upper half is pressed and decreases value when lower half is pressed.""" @@ -460,38 +434,31 @@ def selected(self, touch_point): 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): + def released(self) -> None: """Response function when the Control is released. Resets the state variables for handling situation when ``cool_down`` is < 0 that requires `released()` before reacting another another `selected()`.""" self._pressed = False @property - def value(self): + def value(self) -> int: """The value index displayed on the widget. For the setter, the input can either be an `int` index into the ``value_list`` or can be a `str` that matches one of the items in the ``value_list``. If `int`, @@ -503,14 +470,14 @@ def value(self): return self._value @value.setter - def value(self, new_value): # Set the value based on the index or on the string. + def value( + self, new_value: int | str + ) -> int | None: # Set the value based on the index or on the string. if isinstance(new_value, str): # for an input string, search the value_list 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 @@ -523,15 +490,14 @@ def value(self, new_value): # Set the value based on the index or on the string # draw_position - Draws two bitmaps into the target bitmap with offsets. # Allows values < 0.0 and > 1.0 for "springy" easing functions def _draw_position( - target_bitmap, - bitmap1, - bitmap1_offset, - bitmap2, - bitmap2_offset, - position=0.0, - horizontal=True, -): - + target_bitmap: displayio.Bitmap, + bitmap1: displayio.Bitmap, + bitmap1_offset: Tuple[int, int], + bitmap2: displayio.Bitmap, + bitmap2_offset: Tuple[int, int], + position: float = 0.0, + horizontal: bool = True, +) -> None: x_offset1 = bitmap1_offset[0] y_offset1 = bitmap1_offset[1] x_offset2 = bitmap2_offset[0] @@ -574,10 +540,17 @@ def _draw_position( ) -# pylint: disable=invalid-name - # _blit_constrained: Copies bitmaps with constraints to the dimensions -def _blit_constrained(target, x, y, source, x1=None, y1=None, x2=None, y2=None): +def _blit_constrained( + target: displayio.Bitmap, + x: int, + y: int, + source: displayio.Bitmap, + x1: Optional[int] = None, + y1: Optional[int] = None, + x2: Optional[int] = None, + y2: Optional[int] = None, +) -> None: if x1 is None: x1 = 0 if y1 is None: @@ -607,12 +580,7 @@ def _blit_constrained(target, x, y, source, x1=None, y1=None, x2=None, y2=None): 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) @@ -620,18 +588,17 @@ def _blit_constrained(target, x, y, source, x1=None, y1=None, x2=None, y2=None): # _animate_bitmap - performs animation of scrolling between two bitmaps def _animate_bitmap( - display, - target_bitmap, - bitmap1, - bitmap1_offset, - bitmap2, - bitmap2_offset, - start_position, - end_position, - animation_time, - horizontal, -): - + display: AnyDisplay, + target_bitmap: displayio.Bitmap, + bitmap1: displayio.Bitmap, + bitmap1_offset: Tuple[int, int], + bitmap2: displayio.Bitmap, + bitmap2_offset: Tuple[int, int], + start_position: float, + end_position: float, + animation_time: float, + horizontal: bool, +) -> None: start_time = time.monotonic() if start_position > end_position: # direction is decreasing: "out" @@ -655,13 +622,10 @@ def _animate_bitmap( display.auto_refresh = True while True: - 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 @@ -678,7 +642,6 @@ def _animate_bitmap( ) display.auto_refresh = True else: - _draw_position( target_bitmap, bitmap1, diff --git a/adafruit_displayio_layout/widgets/icon_animated.py b/adafruit_displayio_layout/widgets/icon_animated.py index 1d14ebd..f63fd25 100644 --- a/adafruit_displayio_layout/widgets/icon_animated.py +++ b/adafruit_displayio_layout/widgets/icon_animated.py @@ -21,19 +21,32 @@ 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 circuitpython_typing.displayio import AnyDisplay +except ImportError: + pass -class IconAnimated(IconWidget): +__version__ = "0.0.0+auto.0" +__repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_DisplayIO_Layout.git" + + +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 @@ -66,17 +79,18 @@ 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 @classmethod def init_class( - cls, display=None, max_scale=1.5, max_icon_size=(80, 80), max_color_depth=256 - ): + cls, + display: Optional[AnyDisplay], + max_scale: float = 1.5, + max_icon_size: Tuple[int, int] = (80, 80), + max_color_depth: int = 256, + ) -> None: """ Initializes the IconAnimated Class variables, including preallocating memory buffers for the icon zoom bitmap and icon zoom palette. @@ -86,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 @@ -130,15 +144,14 @@ def init_class( def __init__( self, - label_text, - icon, - on_disk=False, - scale=None, - angle=4, - animation_time=0.15, - **kwargs, - ): - + label_text: str, + icon: str, + on_disk: bool = False, + scale: Optional[float] = None, + angle: float = 4, + animation_time: float = 0.15, + **kwargs: Any, + ) -> None: if self.__class__.display is None: raise ValueError( "Must initialize class using\n" @@ -155,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)) @@ -165,11 +177,11 @@ def __init__( self._angle = (angle / 360) * 2 * pi # in degrees, convert to radians self._zoomed = False # state variable for zoom status - def zoom_animation(self, touch_point): + def zoom_animation(self, touch_point: Tuple[int, int, Optional[int]]) -> None: """Performs zoom animation when icon is pressed. :param touch_point: x,y location of the screen. - :type touch_point: Tuple[x,y] + :type touch_point: Tuple[int, int, Optional[int]] :return: None """ @@ -192,10 +204,12 @@ def zoom_animation(self, touch_point): ) if self._animation_time > 0: - animation_bitmap = self.__class__.bitmap_buffer animation_palette = self.__class__.palette_buffer + # For mypy, if class is configured correctly this must be true + assert self.__class__.display is not None + # store the current display refresh setting refresh_status = self.__class__.display.auto_refresh @@ -218,9 +232,7 @@ def zoom_animation(self, touch_point): ) # 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 @@ -260,11 +272,11 @@ def zoom_animation(self, touch_point): self._zoomed = True - def zoom_out_animation(self, touch_point): + def zoom_out_animation(self, touch_point: Tuple[int, int, Optional[int]]) -> None: """Performs un-zoom animation when icon is released. :param touch_point: x,y location of the screen. - :type touch_point: Tuple[x,y] + :type touch_point: Tuple[int, int, Optional[int]] :return: None """ @@ -273,6 +285,9 @@ def zoom_out_animation(self, touch_point): animation_bitmap = self.__class__.bitmap_buffer animation_palette = self.__class__.palette_buffer + # For mypy, if class is configured correctly this must be true + assert self.__class__.display is not None + # store the current display refresh setting refresh_status = self.__class__.display.auto_refresh @@ -280,6 +295,7 @@ def zoom_out_animation(self, touch_point): # Animation: shrink down to the original size start_time = time.monotonic() + while True: elapsed_time = time.monotonic() - start_time position = max(0.0, easeout(1 - (elapsed_time / self._animation_time))) diff --git a/adafruit_displayio_layout/widgets/icon_widget.py b/adafruit_displayio_layout/widgets/icon_widget.py index 1b62dbd..2a262cc 100644 --- a/adafruit_displayio_layout/widgets/icon_widget.py +++ b/adafruit_displayio_layout/widgets/icon_widget.py @@ -22,17 +22,25 @@ """ - -import terminalio -from displayio import TileGrid, OnDiskBitmap, ColorConverter 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 +try: + from typing import Any, Optional, Tuple +except ImportError: + pass -class IconWidget(Widget, Control): +__version__ = "0.0.0+auto.0" +__repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_DisplayIO_Layout.git" + + +class IconWidget(Widget, Control): """ A touch enabled widget that holds an icon image loaded with adafruit_imageload and a text label centered beneath it. @@ -41,7 +49,10 @@ class IconWidget(Widget, Control): :param string icon: the filepath of the bmp image to be used as the icon. :param boolean on_disk: if True use OnDiskBitmap instead of imageload. This can be helpful to save memory. Defaults to False - + :param Optional[int] transparent_index: if not None this color index will get set to + transparent on the palette of the icon. + :param Optional[int] label_background: if not None this color will be used as a background + for the icon label. :param int x: x location the icon widget should be placed. Pixel coordinates. :param int y: y location the icon widget should be placed. Pixel coordinates. :param anchor_point: (X,Y) values from 0.0 to 1.0 to define the anchor point relative to the @@ -51,22 +62,28 @@ class IconWidget(Widget, Control): :type anchored_position: Tuple[int, int] """ - def __init__(self, label_text, icon, on_disk=False, **kwargs): + def __init__( + self, + label_text: str, + icon: str, + on_disk: bool = False, + transparent_index: Optional[int] = None, + label_background: Optional[int] = None, + **kwargs: Any, + ) -> None: super().__init__(**kwargs) self._icon = icon if on_disk: - with open(self._icon, "rb") as self._file: - image = OnDiskBitmap(self._file) - tile_grid = TileGrid( - image, - pixel_shader=getattr(image, "pixel_shader", ColorConverter()) - # TODO: Once CP6 is no longer supported replace the above line with below. - # tile_grid = TileGrid(image, pixel_shader=image.pixel_shader) - ) + image = OnDiskBitmap(self._icon) + if transparent_index is not None: + image.pixel_shader.make_transparent(transparent_index) + tile_grid = TileGrid(image, pixel_shader=image.pixel_shader) else: image, palette = adafruit_imageload.load(icon) + if transparent_index is not None: + palette.make_transparent(transparent_index) tile_grid = TileGrid(image, pixel_shader=palette) self.append(tile_grid) _label = bitmap_label.Label( @@ -76,27 +93,31 @@ def __init__(self, label_text, icon, on_disk=False, **kwargs): anchor_point=(0.5, 0), anchored_position=(image.width // 2, image.height), ) + + if label_background is not None: + _label.background_color = label_background + self.append(_label) - self.touch_boundary = ( + self.touch_boundary: Tuple[int, int, int, int] = ( 0, 0, image.width, image.height + _label.bounding_box[3], ) - def contains(self, touch_point): # overrides, then calls Control.contains(x,y) - + def contains( + self, touch_point: Tuple[int, int, Optional[int]] + ) -> bool: # overrides, then calls Control.contains(x,y) """Checks if the IconWidget was touched. Returns True if the touch_point is within the IconWidget's touch_boundary. - :param touch_point: x,y location of the screen, converted to local coordinates. - :type touch_point: Tuple[x,y] + :param touch_point: x, y, p location of the screen, converted to local coordinates, plus + an optional pressure value for screens that support it. + :type touch_point: Tuple[int, int, Optional[int]] :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 e955224..8ce0320 100644 --- a/adafruit_displayio_layout/widgets/switch_round.py +++ b/adafruit_displayio_layout/widgets/switch_round.py @@ -35,22 +35,28 @@ # 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 +except ImportError: + pass -__version__ = "0.0.0-auto.0" +__version__ = "0.0.0+auto.0" __repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_DisplayIO_Layout.git" class SwitchRound(Widget, Control): - """ .. note:: Jump directly to: @@ -143,7 +149,7 @@ class SwitchRound(Widget, Control): .. code-block:: python - display.show(my_switch) # add the group to the display + display.root_group = my_switch # add the group to the display If you want to have multiple display elements, you can create a group and then append the switch and the other elements to the group. Then, you can add the full @@ -159,7 +165,7 @@ class SwitchRound(Widget, Control): # Append other display elements to the group # - display.show(my_group) # add the group to the display + display.root_group = my_group # add the group to the display For a full example, including how to respond to screen touches, check out the following examples in the `Adafruit_CircuitPython_DisplayIO_Layout @@ -414,37 +420,44 @@ 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=0, - y=0, - width=None, # recommend to default to - height=40, - touch_padding=0, - horizontal=True, # horizontal orientation - flip=False, # flip the direction of the switch movement - anchor_point=None, - anchored_position=None, - fill_color_off=(66, 44, 66), - fill_color_on=(0, 100, 0), - outline_color_off=(30, 30, 30), - outline_color_on=(0, 60, 0), - background_color_off=(255, 255, 255), - background_color_on=(0, 60, 0), - background_outline_color_off=None, # default to background_color_off - background_outline_color_on=None, # default to background_color_on - switch_stroke=2, - text_stroke=None, # default to switch_stroke - display_button_text=True, - animation_time=0.2, # animation duration (in seconds) - value=False, # initial value - **kwargs, - ): + x: int = 0, + y: int = 0, + width: Optional[int] = None, # recommend to default to + height: int = 40, + touch_padding: int = 0, + horizontal: bool = True, # horizontal orientation + flip: bool = False, # flip the direction of the switch movement + anchor_point: Optional[Tuple[float, float]] = None, + anchored_position: Optional[Tuple[int, int]] = None, + fill_color_off: Union[Tuple[int, int, int], int] = (66, 44, 66), + fill_color_on: Union[Tuple[int, int, int], int] = (0, 100, 0), + outline_color_off: Union[Tuple[int, int, int], int] = (30, 30, 30), + outline_color_on: Union[Tuple[int, int, int], int] = (0, 60, 0), + background_color_off: Union[Tuple[int, int, int], int] = (255, 255, 255), + background_color_on: Union[Tuple[int, int, int], int] = (0, 60, 0), + background_outline_color_off: Union[ + Tuple[int, int, int], int, None + ] = None, # default to background_color_off + background_outline_color_on: Union[ + Tuple[int, int, int], int, None + ] = None, # default to background_color_on + switch_stroke: int = 2, + text_stroke: Optional[int] = None, # default to switch_stroke + display_button_text: bool = True, + animation_time: float = 0.2, # animation duration (in seconds) + value: bool = False, # initial value + **kwargs: Any, + ) -> None: + self._radius = height // 2 + + # If width is not provided, then use the preferred aspect ratio + if width is None: + width = 4 * self._radius # initialize the Widget superclass (x, y, scale) + # self._height and self._width are set in the super call super().__init__(x=x, y=y, height=height, width=width, **kwargs) # Group elements for SwitchRound: # 0. switch_roundrect: The switch background @@ -454,23 +467,11 @@ def __init__( # initialize the Control superclass - # pylint: disable=bad-super-call super(Control, self).__init__() self._horizontal = horizontal self._flip = flip - # height and width internal variables are treated before considering rotation - self._height = self.height - self._radius = self.height // 2 - - # If width is not provided, then use the preferred aspect ratio - if self._width is None: - self._width = 4 * self._radius - else: - self._width = self.width - print("width set!") - if background_outline_color_off is None: background_outline_color_off = background_color_off if background_outline_color_on is None: @@ -505,12 +506,15 @@ def __init__( self._create_switch() - def _create_switch(self): + def _create_switch(self) -> None: # The main function that creates the switch display elements switch_x = self._radius switch_y = self._radius + # These are Optional[int] values, let mypy know they should never be None here + assert self._height is not None and self._width is not None + # Define the motion "keyframes" that define the switch movement if self._horizontal: # horizontal switch orientation self._x_motion = self._width - 2 * self._radius - 1 @@ -614,12 +618,12 @@ def _create_switch(self): self._width, ] - self.touch_boundary = [ + self.touch_boundary = ( self._bounding_box[0] - self._touch_padding, self._bounding_box[1] - self._touch_padding, self._bounding_box[2] + 2 * self._touch_padding, self._bounding_box[3] + 2 * self._touch_padding, - ] + ) # Store initial positions of moving elements to be used in _draw_function self._switch_initial_x = self._switch_circle.x @@ -661,7 +665,7 @@ def _create_switch(self): # due to any changes that might have occurred in the bounding_box self._update_position() - def _get_offset_position(self, position): + def _get_offset_position(self, position: float) -> Tuple[int, int, float]: # Function to calculate the offset position (x, y, angle) of the moving # elements of an animated widget. Designed to be flexible depending upon # the widget's desired response. @@ -683,7 +687,7 @@ def _get_offset_position(self, position): return x_offset, y_offset, angle_offset - def _draw_position(self, position): + def _draw_position(self, position: float) -> None: # Draw the position of the slider. # The position parameter is a float between 0 and 1 (0= off, 1= on). @@ -691,9 +695,7 @@ def _draw_position(self, position): 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 @@ -704,9 +706,7 @@ def _draw_position(self, position): 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 ) @@ -733,7 +733,7 @@ def _draw_position(self, position): self._text_0.hidden = False self._text_1.hidden = True - def _animate_switch(self): + def _animate_switch(self) -> None: # The animation function for the switch. # 1. Move the switch # 2. Update the self._value to the opposite of its current value. @@ -750,73 +750,77 @@ def _animate_switch(self): start_time = time.monotonic() # set the starting time for animation while True: - # Determines the direction of movement, depending upon if the # switch is going from on->off or off->on - # constrain the elapsed time - elapsed_time = time.monotonic() - start_time - if elapsed_time > self._animation_time: - elapsed_time = self._animation_time - - if self._value: - position = ( - 1 - (elapsed_time) / self._animation_time - ) # fraction from 0 to 1 - else: - position = (elapsed_time) / self._animation_time # fraction from 0 to 1 - - # Update the moving elements based on the current position - # apply the "easing" function to the requested position to adjust motion - self._draw_position(easing(position)) # update the switch position + if self._animation_time == 0: + if not self._value: + position = 1.0 + self._draw_position(1) + else: + position = 0.0 + self._draw_position(0) + else: # animate over time + # constrain the elapsed time + elapsed_time = time.monotonic() - start_time + if elapsed_time > self._animation_time: + elapsed_time = self._animation_time + + if self._value: + position = 1 - (elapsed_time) / self._animation_time # fraction from 0 to 1 + else: + # fraction from 0 to 1 + position = (elapsed_time) / self._animation_time + + # Update the moving elements based on the current position + # apply the "easing" function to the requested position to adjust motion + self._draw_position(easing(position)) # update the switch position # update the switch value once the motion is complete 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 - def selected(self, touch_point): + def selected(self, touch_point: Tuple[int, int, Optional[int]]) -> None: """Response function when Switch is selected. When selected, the switch position and value is changed with an animation. - :param touch_point: x,y location of the screen, in absolute display coordinates. + :param touch_point: x, y, p location of the screen, converted to local coordinates, plus + an optional pressure value for screens that support it. :return: 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. # touch_point is adjusted for group's x,y position before sending to super() super().selected((touch_x, touch_y, 0)) - def contains(self, touch_point): # overrides, then calls Control.contains(x,y) + def contains( + self, touch_point: Tuple[int, int, Optional[int]] + ) -> bool: # overrides, then calls Control.contains(x,y) """Checks if the Widget was touched. Returns True if the touch_point is within the Control's touch_boundary. - :param touch_point: x,y location of the screen, in absolute display coordinates. + :param touch_point: x, y, p location of the screen, converted to local coordinates, plus + an optional pressure value for screens that support it. :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)) @property - def value(self): + def value(self) -> bool: """The current switch value (Boolean). :return: Boolean @@ -824,17 +828,19 @@ def value(self): return self._value @value.setter - def value(self, new_value): + def value(self, new_value: bool) -> None: if new_value != self._value: - fake_touch_point = [0, 0, 0] # send an arbitrary touch_point + fake_touch_point = (0, 0, 0) # send an arbitrary touch_point self.selected(fake_touch_point) @property - def width(self): + def width(self) -> int: + # Type is Optional[int], let mypy know that it can't be None here + assert self._width is not None return self._width @width.setter - def width(self, new_width): + def width(self, new_width: int) -> None: if self._width is None: self._width = 4 * self._radius else: @@ -842,16 +848,18 @@ def width(self, new_width): self._create_switch() @property - def height(self): + def height(self) -> int: + # Type is Optional[int], let mypy know that it can't be None here + assert self._height is not None return self._height @height.setter - def height(self, new_height): + def height(self, new_height: int) -> None: self._height = new_height self._radius = new_height // 2 self._create_switch() - def resize(self, new_width, new_height): + def resize(self, new_width: int, new_height: int) -> None: """Resize the switch to a new requested width and height. :param int new_width: requested maximum width @@ -884,7 +892,7 @@ def resize(self, new_width, new_height): ###### color support functions ###### -def _color_to_tuple(value): +def _color_to_tuple(value: Union[Tuple[int, int, int], int]) -> Tuple[int, int, int]: """Converts a color from a 24-bit integer to a tuple. :param value: RGB LED desired value - can be a RGB tuple or a 24-bit integer. """ @@ -896,12 +904,16 @@ def _color_to_tuple(value): r = value >> 16 g = (value >> 8) & 0xFF b = value & 0xFF - return [r, g, b] + return (r, g, b) raise ValueError("Color must be a tuple or 24-bit integer value.") -def _color_fade(start_color, end_color, fraction): +def _color_fade( + start_color: Union[Tuple[int, int, int], int], + end_color: Union[Tuple[int, int, int], int], + fraction: float, +) -> Tuple[int, ...]: """Linear extrapolation of a color between two RGB colors (tuple or 24-bit integer). :param start_color: starting color :param end_color: ending color @@ -918,7 +930,5 @@ def _color_fade(start_color, end_color, fraction): faded_color = [0, 0, 0] for i in range(3): - faded_color[i] = start_color[i] - int( - (start_color[i] - end_color[i]) * fraction - ) - return faded_color + 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 d1cd226..2e6bd8d 100644 --- a/adafruit_displayio_layout/widgets/widget.py +++ b/adafruit_displayio_layout/widgets/widget.py @@ -24,10 +24,14 @@ import displayio -__version__ = "0.0.0-auto.0" -__repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_DisplayIO_Layout.git" +try: + from typing import Optional, Tuple +except ImportError: + pass + -# pylint: disable=too-many-arguments +__version__ = "0.0.0+auto.0" +__repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_DisplayIO_Layout.git" class Widget(displayio.Group): @@ -116,7 +120,7 @@ class Widget(displayio.Group): The Widget class has several options for setting the widget position on the screen. In the simplest case, you can define the widget's *.x* and *.y* properties to set the position. (**Reminder**: If your widget is directly shown by the display using - *display.show(my_widget)*), then the *.x* and *.y* positions will be in the display's + *display.root_group=my_widget*), then the *.x* and *.y* positions will be in the display's coordinate system. But if your widget is held inside of another Group, then its coordinates will be in that Group's coordinate system.) @@ -166,15 +170,14 @@ class Widget(displayio.Group): def __init__( self, - x=0, - y=0, - scale=1, - width=None, - height=None, - anchor_point=None, - anchored_position=None, - ): - + x: int = 0, + y: int = 0, + scale: int = 1, + width: Optional[int] = None, + height: Optional[int] = None, + anchor_point: Optional[Tuple[float, float]] = None, + anchored_position: Optional[Tuple[int, int]] = None, + ) -> None: super().__init__(x=x, y=y, scale=scale) # send x,y and scale to Group # @@ -195,7 +198,7 @@ def __init__( self._update_position() - def resize(self, new_width, new_height): + def resize(self, new_width: int, new_height: int) -> None: """Resizes the widget dimensions (for use with automated layout functions). **IMPORTANT:** The `resize` function should be overridden by the subclass definition. @@ -218,7 +221,7 @@ def resize(self, new_width, new_height): self._bounding_box[2] = new_width self._bounding_box[3] = new_height - def _update_position(self): + def _update_position(self) -> None: """ Widget class function for updating the widget's *x* and *y* position based upon the `anchor_point` and `anchored_position` values. The subclass should @@ -228,43 +231,43 @@ def _update_position(self): """ if (self._anchor_point is not None) and (self._anchored_position is not None): - self.x = ( + self.x = int( self._anchored_position[0] - int(self._anchor_point[0] * self._bounding_box[2]) - self._bounding_box[0] ) - self.y = ( + self.y = int( self._anchored_position[1] - int(self._anchor_point[1] * self._bounding_box[3]) - self._bounding_box[1] ) @property - def width(self): + def width(self) -> int: """The widget width, in pixels. (getter only) :return: int """ - return self._width + return self._width or 0 @property - def height(self): + def height(self) -> int: """The widget height, in pixels. (getter only) :return: int """ - return self._height + return self._height or 0 @property - def bounding_box(self): + def bounding_box(self) -> Tuple[int, ...]: """The boundary of the widget. [x, y, width, height] in Widget's local coordinates (in pixels). (getter only) :return: Tuple[int, int, int, int]""" - return self._bounding_box + return tuple(self._bounding_box) @property - def anchor_point(self): + def anchor_point(self) -> Optional[Tuple[float, float]]: """The anchor point for positioning the widget, works in concert with `anchored_position` The relative (X,Y) position of the widget where the anchored_position is placed. For example (0.0, 0.0) is the Widget's upper left corner, @@ -275,12 +278,12 @@ def anchor_point(self): return self._anchor_point @anchor_point.setter - def anchor_point(self, new_anchor_point): + def anchor_point(self, new_anchor_point: Tuple[float, float]) -> None: self._anchor_point = new_anchor_point self._update_position() @property - def anchored_position(self): + def anchored_position(self) -> Optional[Tuple[int, int]]: """The anchored position (in pixels) for positioning the widget, works in concert with `anchor_point`. The `anchored_position` is the x,y pixel position for the placement of the Widget's `anchor_point`. @@ -292,6 +295,6 @@ def anchored_position(self): return self._anchored_position @anchored_position.setter - def anchored_position(self, new_anchored_position): + def anchored_position(self, new_anchored_position: Tuple[int, int]) -> None: self._anchored_position = new_anchored_position self._update_position() diff --git a/docs/api.rst b/docs/api.rst index b517167..0cddc93 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -4,8 +4,23 @@ .. 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: + :member-order: bysource + +.. automodule:: adafruit_displayio_layout.layouts.page_layout + :members: + :private-members: + :member-order: bysource + +.. automodule:: adafruit_displayio_layout.layouts.tab_layout + :members: + :private-members: + :member-order: bysource .. automodule:: adafruit_displayio_layout.widgets.widget :members: diff --git a/docs/conf.py b/docs/conf.py index 516748e..a0e4431 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -1,9 +1,8 @@ -# -*- coding: utf-8 -*- - # SPDX-FileCopyrightText: 2017 Scott Shawcroft, written for Adafruit Industries # # SPDX-License-Identifier: MIT +import datetime import os import sys @@ -16,6 +15,7 @@ # ones. extensions = [ "sphinx.ext.autodoc", + "sphinxcontrib.jquery", "sphinx.ext.intersphinx", "sphinx.ext.napoleon", "sphinx.ext.todo", @@ -30,13 +30,14 @@ # autodoc module docs will fail to generate with a warning. autodoc_mock_imports = [ "vectorio", + "terminalio", "bitmaptools", ] intersphinx_mapping = { - "python": ("https://docs.python.org/3.4", None), - "CircuitPython": ("https://circuitpython.readthedocs.io/en/latest/", None), + "python": ("https://docs.python.org/3", None), + "CircuitPython": ("https://docs.circuitpython.org/en/latest/", None), } # Add any paths that contain templates here, relative to this directory. @@ -49,7 +50,12 @@ # General information about the project. project = "Adafruit DisplayIO_Layout Library" -copyright = "2021 Tim Cocks" +creation_year = "2021" +current_year = str(datetime.datetime.now().year) +year_duration = ( + current_year if current_year == creation_year else creation_year + " - " + current_year +) +copyright = year_duration + " Tim Cocks" author = "Tim Cocks" # The version info for the project you're documenting, acts as replacement for @@ -66,7 +72,7 @@ # # This is also used if you do content translation via gettext catalogs. # Usually you set "language" from the command line for these cases. -language = None +language = "en" # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. @@ -105,19 +111,9 @@ # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. # -on_rtd = os.environ.get("READTHEDOCS", None) == "True" - -if not on_rtd: # only import and set the theme if we're building docs locally - try: - import sphinx_rtd_theme - - html_theme = "sphinx_rtd_theme" - html_theme_path = [sphinx_rtd_theme.get_html_theme_path(), "."] - except: - html_theme = "default" - html_theme_path = ["."] -else: - html_theme_path = ["."] +import sphinx_rtd_theme + +html_theme = "sphinx_rtd_theme" # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, diff --git a/docs/examples.rst b/docs/examples.rst index 398ee82..34d812a 100644 --- a/docs/examples.rst +++ b/docs/examples.rst @@ -7,6 +7,42 @@ Ensure your device works with this simple test. :caption: examples/displayio_layout_simpletest.py :linenos: +Cartesian plane simple test +--------------------------- + +Create a simple plot plane. + +.. literalinclude:: ../examples/displayio_layout_cartesian_simpletest.py + :caption: examples/displayio_layout_cartesian_simpletest.py + :linenos: + +Cartesian lineplot +--------------------- + +Create a lineplot. + +.. literalinclude:: ../examples/displayio_layout_cartesian_lineplot.py + :caption: examples/displayio_layout_cartesian_lineplot.py + :linenos: + +Cartesian Advanced +--------------------- + +Create three different cartesian planes in the display + +.. literalinclude:: ../examples/displayio_layout_cartesian_advanced_test.py + :caption: examples/displayio_layout_cartesian_advanced_test.py + :linenos: + +GridLayout simple text +------------------------ + +Make green and purple rectangles and a "Hello World" label + +.. literalinclude:: ../examples/displayio_layout_gridlayout_simpletest.py + :caption: examples/displayio_layout_gridlayout_simpletest.py + :linenos: + GridLayout divider lines example -------------------------------- @@ -16,6 +52,15 @@ Create GridLayouts with divider lines. :caption: examples/displayio_layout_gridlayout_dividers.py :linenos: +GridLayout Get Cell +------------------------- + +Make green and purple rectangles and then update the color and text values of the labels using the get_cell() function. + +.. literalinclude:: ../examples/displayio_layout_grid_layout_get_cell_test.py + :caption: examples/displayio_layout_grid_layout_get_cell_test.py + :linenos: + Pygame simple test ------------------ @@ -25,6 +70,42 @@ Display Hello World using Blinka_Displayio_PyGameDisplay. :caption: examples/displayio_layout_gridlayout_pygame_display_simpletest.py :linenos: +Icon Animated simple test +------------------------- + +Creates two animated icons with touch response: zoom and shrink animations. + +.. literalinclude:: ../examples/displayio_layout_icon_animated_simpletest.py + :caption: examples/displayio_layout_icon_animated_simpletest.py + :linenos: + +Page Layout simple test +------------------------- + +Make a PageLayout with two pages and change between them. + +.. literalinclude:: ../examples/displayio_layout_page_layout_simpletest.py + :caption: examples/displayio_layout_page_layout_simpletest.py + :linenos: + +Page Layout advanced test +------------------------- + +Make a PageLayout and illustrate all of it's features + +.. literalinclude:: ../examples/displayio_layout_page_layout_advancedtest.py + :caption: examples/displayio_layout_page_layout_advancedtest.py + :linenos: + +Pygame Switch example +------------------------- + +Make a GridLayout with some Labels in its cells. Displayed with Blinka_Displayio_PyGameDisplay + +.. literalinclude:: ../examples/displayio_layout_page_layout_advancedtest.py + :caption: examples/displayio_layout_page_layout_advancedtest.py + :linenos: + Switch simple test ------------------ @@ -52,11 +133,20 @@ Create three FlipInput selectors. :caption: examples/displayio_layout_flip_input_simpletest.py :linenos: -Cartesian plane simple test ---------------------------- +Tab Layout simple test +----------------------- -Create a simple plot plane. +Make a TabLayout and illustrate the most basic features and usage. -.. literalinclude:: ../examples/displayio_layout_cartesian_simpletest.py - :caption: examples/displayio_layout_cartesian_simpletest.py +.. literalinclude:: ../examples/displayio_layout_tab_layout_simpletest.py + :caption: examples/displayio_layout_tab_layout_simpletest.py + :linenos: + +Tab Layout touch test +--------------------- + +Make a TabLayout change tabs with the touchscreen + +.. literalinclude:: ../examples/displayio_layout_tab_layout_touchtest.py + :caption: examples/ddisplayio_layout_tab_layout_touchtest.py :linenos: diff --git a/docs/index.rst b/docs/index.rst index 5908e6f..9790c2f 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -33,8 +33,9 @@ Table of Contents .. toctree:: :caption: Other Links - Download - CircuitPython Reference Documentation + Download from GitHub + Download Library Bundle + CircuitPython Reference Documentation CircuitPython Support Forum Discord Chat Adafruit Learning System diff --git a/docs/requirements.txt b/docs/requirements.txt index 88e6733..979f568 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -2,4 +2,6 @@ # # SPDX-License-Identifier: Unlicense -sphinx>=4.0.0 +sphinx +sphinxcontrib-jquery +sphinx-rtd-theme diff --git a/examples/bmps/active_tab_sprite.bmp b/examples/bmps/active_tab_sprite.bmp new file mode 100644 index 0000000..d97d1db Binary files /dev/null and b/examples/bmps/active_tab_sprite.bmp differ diff --git a/examples/bmps/active_tab_sprite.bmp.license b/examples/bmps/active_tab_sprite.bmp.license new file mode 100644 index 0000000..8f7990c --- /dev/null +++ b/examples/bmps/active_tab_sprite.bmp.license @@ -0,0 +1,2 @@ +# SPDX-FileCopyrightText: 2022 Tim Cocks for Adafruit Industries +# SPDX-License-Identifier: MIT diff --git a/examples/bmps/inactive_tab_sprite.bmp b/examples/bmps/inactive_tab_sprite.bmp new file mode 100644 index 0000000..ab41509 Binary files /dev/null and b/examples/bmps/inactive_tab_sprite.bmp differ diff --git a/examples/bmps/inactive_tab_sprite.bmp.license b/examples/bmps/inactive_tab_sprite.bmp.license new file mode 100644 index 0000000..8f7990c --- /dev/null +++ b/examples/bmps/inactive_tab_sprite.bmp.license @@ -0,0 +1,2 @@ +# SPDX-FileCopyrightText: 2022 Tim Cocks for Adafruit Industries +# SPDX-License-Identifier: MIT diff --git a/examples/displayio_layout_cartesian_advanced_test.py b/examples/displayio_layout_cartesian_advanced_test.py index 9d3ccbc..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 @@ -69,7 +70,7 @@ ) my_group.append(car5) -display.show(my_group) +display.root_group = my_group while True: pass diff --git a/examples/displayio_layout_cartesian_lineplot.py b/examples/displayio_layout_cartesian_lineplot.py new file mode 100644 index 0000000..54ac442 --- /dev/null +++ b/examples/displayio_layout_cartesian_lineplot.py @@ -0,0 +1,65 @@ +# SPDX-FileCopyrightText: 2021 Stefan Krüger +# +# SPDX-License-Identifier: MIT +############################# +""" +This is a basic demonstration of a Cartesian widget for line-ploting +""" + +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) +display = board.DISPLAY +# otherwise change this to setup the display +# for display chip driver and pinout you have (e.g. ILI9341) + +# pybadge display: 160x128 +# Create a Cartesian widget +# https://circuitpython.readthedocs.io/projects/displayio-layout/en/latest/api.html#module-adafruit_displayio_layout.widgets.cartesian +my_plane = Cartesian( + x=15, # x position for the plane + y=2, # y plane position + width=140, # display width + height=105, # display height + xrange=(0, 10), # x range + yrange=(0, 10), # y range +) + +my_group = displayio.Group() +my_group.append(my_plane) +display.root_group = my_group # add high level Group to the display + +data = [ + # (0, 0), # we do this point manually - so we have no wait... + (1, 1), + (2, 1), + (2, 2), + (3, 3), + (4, 3), + (4, 4), + (5, 5), + (6, 5), + (6, 6), + (7, 7), + (8, 7), + (8, 8), + (9, 9), + (10, 9), + (10, 10), +] + +print("examples/displayio_layout_cartesian_lineplot.py") + +# first point without a wait. +my_plane.add_plot_line(0, 0) +for x, y in data: + my_plane.add_plot_line(x, y) + time.sleep(0.5) + +while True: + pass diff --git a/examples/displayio_layout_cartesian_simpletest.py b/examples/displayio_layout_cartesian_simpletest.py index 3eb604a..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 @@ -37,7 +39,7 @@ my_group = displayio.Group() my_group.append(my_plane) -display.show(my_group) # add high level Group to the display +display.root_group = my_group # add high level Group to the display posx = 0 posy = 0 diff --git a/examples/displayio_layout_flip_input_simpletest.py b/examples/displayio_layout_flip_input_simpletest.py index 2a64645..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 @@ -97,11 +97,10 @@ my_group.append(my_flip2) my_group.append(my_flip3) -display.show(my_group) # add high level Group to the display +display.root_group = my_group # add high level Group to the display display.auto_refresh = True while True: - p = ts.touch_point # print("touch_point p: {}".format(p)) # print the touch point diff --git a/examples/displayio_layout_grid_layout_get_cell_test.py b/examples/displayio_layout_grid_layout_get_cell_test.py index a9b812d..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.) @@ -18,7 +20,7 @@ # Make the display context main_group = displayio.Group() -display.show(main_group) +display.root_group = main_group layout = GridLayout( x=10, @@ -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 f649562..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.) @@ -17,7 +19,7 @@ # Make the display context main_group = displayio.Group() -display.show(main_group) +display.root_group = main_group layout = GridLayout( x=10, @@ -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 447d75a..f0a5623 100644 --- a/examples/displayio_layout_gridlayout_pygame_display_simpletest.py +++ b/examples/displayio_layout_gridlayout_pygame_display_simpletest.py @@ -2,21 +2,23 @@ # # SPDX-License-Identifier: MIT """ -Make green and purple rectangles and a -"Hello World" label. Displayed with Blinka_Displayio_PyGameDisplay +Make a GridLayout with some Labels in it's cells. +Displayed with Blinka_Displayio_PyGameDisplay + +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 display = PyGameDisplay(width=320, height=240) main_group = displayio.Group() -display.show(main_group) +display.root_group = main_group layout = GridLayout( x=10, @@ -29,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 98e9578..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.) @@ -18,7 +20,7 @@ # Make the display context main_group = displayio.Group() -display.show(main_group) +display.root_group = main_group layout = GridLayout( x=10, @@ -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 32b6aaa..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", @@ -52,7 +53,7 @@ main_group.append(icon_zoom) main_group.append(icon_shrink) -display.show(main_group) +display.root_group = main_group COOLDOWN_TIME = 0.25 diff --git a/examples/displayio_layout_linearlayout_simpletest.py b/examples/displayio_layout_linearlayout_simpletest.py new file mode 100644 index 0000000..1925330 --- /dev/null +++ b/examples/displayio_layout_linearlayout_simpletest.py @@ -0,0 +1,37 @@ +# SPDX-FileCopyrightText: 2024 Tim C, written for Adafruit Industries +# +# SPDX-License-Identifier: MIT +""" +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.) +# see guide for setting up external displays (TFT / OLED breakouts, RGB matrices, etc.) +# https://learn.adafruit.com/circuitpython-display-support-using-displayio/display-and-display-bus +display = board.DISPLAY + +# Make the display context +main_group = displayio.Group() +display.root_group = main_group + +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") + +icon, icon_palette = adafruit_imageload.load("icons/Play_48x48_small.bmp") +icon_tile_grid = displayio.TileGrid(icon, pixel_shader=icon_palette) +layout.add_content(icon_tile_grid) +layout.add_content(lbl) + +main_group.append(layout) +while True: + pass diff --git a/examples/displayio_layout_page_layout_advancedtest.py b/examples/displayio_layout_page_layout_advancedtest.py new file mode 100644 index 0000000..7dbdca7 --- /dev/null +++ b/examples/displayio_layout_page_layout_advancedtest.py @@ -0,0 +1,115 @@ +# SPDX-FileCopyrightText: 2022 Tim C +# +# SPDX-License-Identifier: MIT +""" +Make a PageLayout and illustrate all of it's features +""" + +import time + +import board +import displayio +import terminalio +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 +display = board.DISPLAY + +# create and show main_group +main_group = displayio.Group() +display.root_group = main_group + +# create the page layout +test_page_layout = PageLayout(x=0, y=0) + +# make 3 pages of content +page_1_group = displayio.Group() +page_2_group = displayio.Group() +page_3_group = displayio.Group() + +# labels +page_1_lbl = Label( + font=terminalio.FONT, + scale=2, + text="This is the first page!", + anchor_point=(0, 0), + anchored_position=(10, 10), +) +page_2_lbl = Label( + font=terminalio.FONT, + scale=2, + text="This page is the second page!", + anchor_point=(0, 0), + anchored_position=(10, 10), +) +page_3_lbl = Label( + font=terminalio.FONT, + scale=2, + text="The third page is fun!", + anchor_point=(0, 0), + anchored_position=(10, 10), +) + +# shapes +square = Rect(x=20, y=70, width=40, height=40, fill=0x00DD00) +circle = Circle(50, 100, r=30, fill=0xDD00DD) +triangle = Triangle(50, 0, 100, 50, 0, 50, fill=0xDDDD00) +triangle.x = 80 +triangle.y = 70 + +# add everything to their page groups +page_1_group.append(square) +page_1_group.append(page_1_lbl) +page_2_group.append(page_2_lbl) +page_2_group.append(circle) +page_3_group.append(page_3_lbl) +page_3_group.append(triangle) + +# add the pages to the layout, supply your own page names +test_page_layout.add_content(page_1_group, "page_1") +test_page_layout.add_content(page_2_group, "page_2") +test_page_layout.add_content(page_3_group, "page_3") + +# add it to the group that is showing on the display +main_group.append(test_page_layout) + +# change page with function by name +test_page_layout.show_page(page_name="page_3") +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(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(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(f"showing page name: {test_page_layout.showing_page_name}") +time.sleep(5) + +another_text = Label( + terminalio.FONT, + text="And another thing!", + scale=2, + color=0x00FF00, + anchor_point=(0, 0), + anchored_position=(100, 100), +) +test_page_layout.showing_page_content.append(another_text) + +print("starting loop") +while True: + time.sleep(1) + # change page by next page function. It will loop by default + test_page_layout.next_page() diff --git a/examples/displayio_layout_page_layout_simpletest.py b/examples/displayio_layout_page_layout_simpletest.py new file mode 100644 index 0000000..74e3120 --- /dev/null +++ b/examples/displayio_layout_page_layout_simpletest.py @@ -0,0 +1,62 @@ +# SPDX-FileCopyrightText: 2022 Tim C +# +# SPDX-License-Identifier: MIT +""" +Make a PageLayout with two pages and change between them. +""" + +import time + +import board +import displayio +import terminalio +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 +display = board.DISPLAY + +# create and show main_group +main_group = displayio.Group() +display.root_group = main_group + +# create the page layout +test_page_layout = PageLayout(x=0, y=0) + +page_1_lbl = Label( + font=terminalio.FONT, + scale=2, + text="This is the first page!", + anchor_point=(0, 0), + anchored_position=(10, 10), +) +page_2_lbl = Label( + font=terminalio.FONT, + scale=2, + text="This page is the second page!", + anchor_point=(0, 0), + anchored_position=(10, 10), +) + +page_1_group = displayio.Group() +page_2_group = displayio.Group() + +square = Rect(x=20, y=70, width=40, height=40, fill=0x00DD00) +circle = Circle(50, 100, r=30, fill=0xDD00DD) + +page_1_group.append(square) +page_1_group.append(page_1_lbl) + +page_2_group.append(page_2_lbl) +page_2_group.append(circle) + +test_page_layout.add_content(page_1_group, "page_1") +test_page_layout.add_content(page_2_group, "page_2") + +main_group.append(test_page_layout) +while True: + time.sleep(1) + test_page_layout.next_page() diff --git a/examples/displayio_layout_pygame_display_switch_round.py b/examples/displayio_layout_pygame_display_switch_round.py new file mode 100644 index 0000000..c85f241 --- /dev/null +++ b/examples/displayio_layout_pygame_display_switch_round.py @@ -0,0 +1,83 @@ +# SPDX-FileCopyrightText: 2021 Tim C +# +# SPDX-License-Identifier: MIT +""" +Make a GridLayout with some Labels in it's cells. +Displayed with Blinka_Displayio_PyGameDisplay + +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 + +# Make the display context. Change size if you want +display = PyGameDisplay(width=320, height=240) + +# Make the display context +main_group = displayio.Group() +display.root_group = main_group + +switch_x = 30 +switch_y = 30 +switch_radius = 20 + +switch_fill_color_off = (200, 44, 200) +switch_fill_color_on = (0, 100, 0) + +switch_outline_color_off = (30, 30, 30) +switch_outline_color_on = (0, 60, 0) + +background_color_off = (255, 255, 255) +background_color_on = (90, 255, 90) + +background_outline_color_off = background_color_off +background_outline_color_on = background_color_on + +switch_width = 4 * switch_radius # This is a good aspect ratio to start with + +switch_stroke = 2 # Width of the outlines (in pixels) +text_stroke = switch_stroke # width of text lines +touch_padding = 0 # Additional boundary around widget that will accept touch input + +animation_time = 0.2 # time for switch to display change (in seconds). +# animation_time=0.15 is a good starting point +display_text = True # show the text (0/1) + +# initialize state variables +switch_value = False +switch_value = True + +my_switch = Switch( + x=switch_x, + y=switch_y, + height=switch_radius * 2, + fill_color_off=switch_fill_color_off, + fill_color_on=switch_fill_color_on, + outline_color_off=switch_outline_color_off, + outline_color_on=switch_outline_color_on, + background_color_off=background_color_off, + background_color_on=background_color_on, + background_outline_color_off=background_outline_color_off, + background_outline_color_on=background_outline_color_on, + switch_stroke=switch_stroke, + display_button_text=display_text, + touch_padding=10, + animation_time=animation_time, + value=False, +) + + +main_group.append(my_switch) +while display.running: + # get mouse up events + ev = pygame.event.get(eventtype=pygame.MOUSEBUTTONUP) + # proceed events + for event in ev: + pos = pygame.mouse.get_pos() + print(pos) + if my_switch.contains(pos): + my_switch.selected(pos) diff --git a/examples/displayio_layout_simpletest.py b/examples/displayio_layout_simpletest.py index 818bcb8..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.) @@ -20,7 +22,7 @@ # Make the display context main_group = displayio.Group() -display.show(main_group) +display.root_group = main_group layout = GridLayout( x=10, @@ -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 95faff8..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 @@ -102,12 +104,11 @@ my_group.append(my_switch8) # Add my_group to the display -display.show(my_group) +display.root_group = my_group # Start the main loop while True: - p = ts.touch_point # get any touches on the screen if p: # Check each switch if the touch point is within the switch touch area diff --git a/examples/displayio_layout_switch_simpletest.py b/examples/displayio_layout_switch_simpletest.py index b890d84..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 @@ -30,11 +32,10 @@ my_group.append(my_switch) # Add my_group to the display -display.show(my_group) +display.root_group = my_group # Start the main loop while True: - p = ts.touch_point # get any touches on the screen if p: # Check each switch if the touch point is within the switch touch area diff --git a/examples/displayio_layout_tab_layout_simpletest.py b/examples/displayio_layout_tab_layout_simpletest.py new file mode 100644 index 0000000..c8c73b4 --- /dev/null +++ b/examples/displayio_layout_tab_layout_simpletest.py @@ -0,0 +1,151 @@ +# SPDX-FileCopyrightText: 2022 Tim C +# +# SPDX-License-Identifier: MIT +""" +Make a TabLayout and illustrate the most basic features and usage. +""" + +import time + +import board +import displayio +import terminalio +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 + +# built-in display +display = board.DISPLAY + +# create and show main_group +main_group = displayio.Group() +display.root_group = main_group + +font = terminalio.FONT + +# create the page layout +test_page_layout = TabLayout( + x=0, + y=0, + display=board.DISPLAY, + tab_text_scale=2, + custom_font=font, + inactive_tab_spritesheet="bmps/inactive_tab_sprite.bmp", + showing_tab_spritesheet="bmps/active_tab_sprite.bmp", + showing_tab_text_color=0x00AA59, + inactive_tab_text_color=0xEEEEEE, + inactive_tab_transparent_indexes=(0, 1), + showing_tab_transparent_indexes=(0, 1), + tab_count=4, +) + +# make page content Groups +page_1_group = displayio.Group() +page_2_group = displayio.Group() +page_3_group = displayio.Group() +page_4_group = displayio.Group() + +# labels +page_1_lbl = Label( + font=terminalio.FONT, + scale=2, + text="This is the first page!", + anchor_point=(0, 0), + anchored_position=(10, 10), +) +page_2_lbl = Label( + font=terminalio.FONT, + scale=2, + text="This page is the\nsecond page!", + anchor_point=(0, 0), + anchored_position=(10, 10), +) +page_3_lbl = Label( + font=terminalio.FONT, + scale=2, + text="The third page is fun!", + anchor_point=(0, 0), + anchored_position=(10, 10), +) + +page_4_lbl = Label( + font=terminalio.FONT, + scale=2, + text="The fourth page\nis where it's at", + anchor_point=(0, 0), + anchored_position=(10, 10), +) + +# shapes +square = Rect(x=20, y=70, width=40, height=40, fill=0x00DD00) +circle = Circle(50, 120, r=30, fill=0xDD00DD) +triangle = Triangle(50, 0, 100, 50, 0, 50, fill=0xDDDD00) +rectangle = Rect(x=80, y=80, width=100, height=50, fill=0x0000DD) + +triangle.x = 80 +triangle.y = 70 + +# add everything to their page groups +page_1_group.append(square) +page_1_group.append(page_1_lbl) +page_2_group.append(page_2_lbl) +page_2_group.append(circle) +page_3_group.append(page_3_lbl) +page_3_group.append(triangle) +page_4_group.append(page_4_lbl) +page_4_group.append(rectangle) + +# add the pages to the layout, supply your own page names +test_page_layout.add_content(page_1_group, "One") +test_page_layout.add_content(page_2_group, "Two") +test_page_layout.add_content(page_3_group, "Thr") +test_page_layout.add_content(page_4_group, "For") + +# add it to the group that is showing on the display +main_group.append(test_page_layout) + +# change page with function by name +test_page_layout.show_page(page_name="Thr") +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(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(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(f"showing page name: {test_page_layout.showing_page_name}") +time.sleep(5) + +another_text = Label( + terminalio.FONT, + text="And another thing!", + scale=2, + color=0x00FF00, + anchor_point=(0, 0), + anchored_position=(100, 100), +) +test_page_layout.showing_page_content.append(another_text) + +print("starting loop") + +prev_change_time = time.monotonic() + +while True: + now = time.monotonic() + if prev_change_time + CHANGE_DELAY <= now: + prev_change_time = now + # change page by next page function. It will loop by default + test_page_layout.next_page() diff --git a/examples/displayio_layout_tab_layout_touchtest.py b/examples/displayio_layout_tab_layout_touchtest.py new file mode 100644 index 0000000..a58db70 --- /dev/null +++ b/examples/displayio_layout_tab_layout_touchtest.py @@ -0,0 +1,139 @@ +# SPDX-FileCopyrightText: 2022 Tim C +# +# SPDX-License-Identifier: MIT +""" +Make a TabLayout change tabs with the touchscreen +""" + +import adafruit_touchscreen +import board +import displayio +import terminalio +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 +display = board.DISPLAY + +# ------------ Touchscreen setup --------------- # +# See: https://learn.adafruit.com/making-a-pyportal-user-interface-displayio/display +display = board.DISPLAY # create the display object + +screen_width = display.width +screen_height = display.height +ts = adafruit_touchscreen.Touchscreen( + board.TOUCH_XL, + board.TOUCH_XR, + board.TOUCH_YD, + board.TOUCH_YU, + calibration=((5200, 59000), (5800, 57000)), + size=(screen_width, screen_height), +) + +# create and show main_group +main_group = displayio.Group() +display.root_group = main_group + +font = terminalio.FONT + +# create the page layout +test_page_layout = TabLayout( + x=0, + y=0, + display=board.DISPLAY, + tab_text_scale=2, + custom_font=font, + inactive_tab_spritesheet="bmps/inactive_tab_sprite.bmp", + showing_tab_spritesheet="bmps/active_tab_sprite.bmp", + showing_tab_text_color=0x00AA59, + inactive_tab_text_color=0xEEEEEE, + inactive_tab_transparent_indexes=(0, 1), + showing_tab_transparent_indexes=(0, 1), + tab_count=4, +) + +# make page content Groups +page_1_group = displayio.Group() +page_2_group = displayio.Group() +page_3_group = displayio.Group() +page_4_group = displayio.Group() + +# labels +page_1_lbl = Label( + font=terminalio.FONT, + scale=2, + text="This is the first page!", + anchor_point=(0, 0), + anchored_position=(10, 10), +) +page_2_lbl = Label( + font=terminalio.FONT, + scale=2, + text="This page is the\nsecond page!", + anchor_point=(0, 0), + anchored_position=(10, 10), +) +page_3_lbl = Label( + font=terminalio.FONT, + scale=2, + text="The third page is fun!", + anchor_point=(0, 0), + anchored_position=(10, 10), +) + +page_4_lbl = Label( + font=terminalio.FONT, + scale=2, + text="The fourth page\nis where it's at", + anchor_point=(0, 0), + anchored_position=(10, 10), +) + +# shapes +square = Rect(x=20, y=70, width=40, height=40, fill=0x00DD00) +circle = Circle(50, 120, r=30, fill=0xDD00DD) +triangle = Triangle(50, 0, 100, 50, 0, 50, fill=0xDDDD00) +rectangle = Rect(x=80, y=80, width=100, height=50, fill=0x0000DD) + +triangle.x = 80 +triangle.y = 70 + +# add everything to their page groups +page_1_group.append(square) +page_1_group.append(page_1_lbl) +page_2_group.append(page_2_lbl) +page_2_group.append(circle) +page_3_group.append(page_3_lbl) +page_3_group.append(triangle) +page_4_group.append(page_4_lbl) +page_4_group.append(rectangle) + +# add the pages to the layout, supply your own page names +test_page_layout.add_content(page_1_group, "One") +test_page_layout.add_content(page_2_group, "Two") +test_page_layout.add_content(page_3_group, "Thr") +test_page_layout.add_content(page_4_group, "For") + +# add it to the group that is showing on the display +main_group.append(test_page_layout) + + +# add something new after the TabLayout was already created +another_text = Label( + terminalio.FONT, + text="And another thing!", + scale=2, + color=0x00FF00, + anchor_point=(0, 0), + anchored_position=(100, 100), +) +test_page_layout.showing_page_content.append(another_text) + +while True: + touch = ts.touch_point + if touch: + test_page_layout.handle_touch_events(touch) diff --git a/examples/fonts/Arial-16.bdf b/examples/fonts/Arial-16.bdf new file mode 100644 index 0000000..087da7d --- /dev/null +++ b/examples/fonts/Arial-16.bdf @@ -0,0 +1,7366 @@ +STARTFONT 2.1 +COMMENT +COMMENT Converted from OpenType font "arial.ttf" by "otf2bdf 3.0". +COMMENT +FONT -FreeType-Arial-Medium-R-Normal--22-160-100-100-P-109-ISO10646-1 +SIZE 16 100 100 +FONTBOUNDINGBOX 43 29 -11 -7 +STARTPROPERTIES 19 +FOUNDRY "FreeType" +FAMILY_NAME "Arial" +WEIGHT_NAME "Medium" +SLANT "R" +SETWIDTH_NAME "Normal" +ADD_STYLE_NAME "" +PIXEL_SIZE 22 +POINT_SIZE 160 +RESOLUTION_X 100 +RESOLUTION_Y 100 +SPACING "P" +AVERAGE_WIDTH 109 +CHARSET_REGISTRY "ISO10646" +CHARSET_ENCODING "1" +FONT_ASCENT 19 +FONT_DESCENT 4 +COPYRIGHT " 2017 The Monotype Corporation. All Rights Reserved. Hebrew OpenType Layout logic copyright 2003 & 2007, Ralph Hancock & John Hudson. This layout logic for Biblical Hebrew is open source software under the MIT License; see embedded license description for details." +_OTF_FONTFILE "arial.ttf" +_OTF_PSNAME "ArialMT" +ENDPROPERTIES +CHARS 3361 +STARTCHAR 0020 +ENCODING 32 +SWIDTH 270 0 +DWIDTH 6 0 +BBX 0 0 0 0 +BITMAP +ENDCHAR +STARTCHAR 0021 +ENCODING 33 +SWIDTH 270 0 +DWIDTH 6 0 +BBX 2 16 2 0 +BITMAP +C0 +C0 +C0 +C0 +C0 +C0 +C0 +C0 +C0 +C0 +C0 +C0 +00 +00 +C0 +C0 +ENDCHAR +STARTCHAR 0022 +ENCODING 34 +SWIDTH 360 0 +DWIDTH 8 0 +BBX 6 6 1 10 +BITMAP +CC +CC +CC +CC +CC +CC +ENDCHAR +STARTCHAR 0023 +ENCODING 35 +SWIDTH 540 0 +DWIDTH 12 0 +BBX 12 16 0 0 +BITMAP +0C60 +0C60 +0C60 +18C0 +FFF0 +FFF0 +18C0 +18C0 +3180 +3180 +FFF0 +FFF0 +3180 +6300 +6300 +6300 +ENDCHAR +STARTCHAR 0024 +ENCODING 36 +SWIDTH 540 0 +DWIDTH 12 0 +BBX 10 19 1 -2 +BITMAP +0800 +3E00 +7F80 +E9C0 +C8C0 +C800 +E800 +7800 +3F00 +0F80 +09C0 +08C0 +C8C0 +C8C0 +6980 +7F80 +3E00 +0800 +0800 +ENDCHAR +STARTCHAR 0025 +ENCODING 37 +SWIDTH 900 0 +DWIDTH 20 0 +BBX 18 16 1 0 +BITMAP +380600 +6C0C00 +C60C00 +C61800 +C63000 +C63000 +C66000 +6C6700 +38CD80 +0198C0 +0198C0 +0318C0 +0318C0 +0618C0 +0C0D80 +0C0700 +ENDCHAR +STARTCHAR 0026 +ENCODING 38 +SWIDTH 675 0 +DWIDTH 15 0 +BBX 13 16 1 0 +BITMAP +0E00 +1F00 +3180 +3180 +3180 +1B00 +1F00 +1C00 +7600 +6330 +C1B0 +C1E0 +C0E0 +61A0 +7F98 +1E10 +ENDCHAR +STARTCHAR 0027 +ENCODING 39 +SWIDTH 180 0 +DWIDTH 4 0 +BBX 2 6 1 10 +BITMAP +C0 +C0 +C0 +C0 +C0 +C0 +ENDCHAR +STARTCHAR 0028 +ENCODING 40 +SWIDTH 315 0 +DWIDTH 7 0 +BBX 5 20 1 -4 +BITMAP +08 +10 +30 +20 +60 +60 +C0 +C0 +C0 +C0 +C0 +C0 +C0 +C0 +60 +60 +20 +30 +10 +08 +ENDCHAR +STARTCHAR 0029 +ENCODING 41 +SWIDTH 315 0 +DWIDTH 7 0 +BBX 5 20 1 -4 +BITMAP +80 +40 +60 +20 +30 +30 +18 +18 +18 +18 +18 +18 +18 +18 +30 +30 +20 +60 +40 +80 +ENDCHAR +STARTCHAR 002A +ENCODING 42 +SWIDTH 405 0 +DWIDTH 9 0 +BBX 8 7 1 9 +BITMAP +18 +18 +FF +3C +3C +66 +24 +ENDCHAR +STARTCHAR 002B +ENCODING 43 +SWIDTH 585 0 +DWIDTH 13 0 +BBX 10 10 1 3 +BITMAP +0C00 +0C00 +0C00 +0C00 +FFC0 +FFC0 +0C00 +0C00 +0C00 +0C00 +ENDCHAR +STARTCHAR 002C +ENCODING 44 +SWIDTH 270 0 +DWIDTH 6 0 +BBX 2 5 2 -3 +BITMAP +C0 +C0 +40 +40 +80 +ENDCHAR +STARTCHAR 002D +ENCODING 45 +SWIDTH 315 0 +DWIDTH 7 0 +BBX 6 2 0 5 +BITMAP +FC +FC +ENDCHAR +STARTCHAR 002E +ENCODING 46 +SWIDTH 270 0 +DWIDTH 6 0 +BBX 2 2 2 0 +BITMAP +C0 +C0 +ENDCHAR +STARTCHAR 002F +ENCODING 47 +SWIDTH 270 0 +DWIDTH 6 0 +BBX 7 16 0 0 +BITMAP +06 +06 +0C +0C +0C +18 +18 +18 +30 +30 +30 +60 +60 +60 +E0 +C0 +ENDCHAR +STARTCHAR 0030 +ENCODING 48 +SWIDTH 540 0 +DWIDTH 12 0 +BBX 10 16 1 0 +BITMAP +1E00 +3F80 +6180 +6180 +C0C0 +C0C0 +C0C0 +C0C0 +C0C0 +C0C0 +C0C0 +C0C0 +6180 +6180 +3F00 +1E00 +ENDCHAR +STARTCHAR 0031 +ENCODING 49 +SWIDTH 540 0 +DWIDTH 12 0 +BBX 6 16 2 0 +BITMAP +0C +0C +1C +7C +EC +8C +0C +0C +0C +0C +0C +0C +0C +0C +0C +0C +ENDCHAR +STARTCHAR 0032 +ENCODING 50 +SWIDTH 540 0 +DWIDTH 12 0 +BBX 10 16 1 0 +BITMAP +3F00 +7F80 +E1C0 +C0C0 +00C0 +00C0 +00C0 +0180 +0300 +0600 +0C00 +1800 +3000 +6000 +FFC0 +FFC0 +ENDCHAR +STARTCHAR 0033 +ENCODING 51 +SWIDTH 540 0 +DWIDTH 12 0 +BBX 10 16 1 0 +BITMAP +3E00 +7F00 +E380 +C180 +0180 +0300 +0E00 +0F00 +0180 +00C0 +00C0 +00C0 +C0C0 +E180 +7F80 +3E00 +ENDCHAR +STARTCHAR 0034 +ENCODING 52 +SWIDTH 540 0 +DWIDTH 12 0 +BBX 10 16 1 0 +BITMAP +0300 +0700 +0F00 +0F00 +1B00 +1B00 +3300 +7300 +6300 +C300 +FFC0 +FFC0 +0300 +0300 +0300 +0300 +ENDCHAR +STARTCHAR 0035 +ENCODING 53 +SWIDTH 540 0 +DWIDTH 12 0 +BBX 10 16 1 0 +BITMAP +3F80 +3F80 +6000 +6000 +6000 +7E00 +FF80 +C180 +00C0 +00C0 +00C0 +00C0 +C0C0 +E180 +7F80 +3E00 +ENDCHAR +STARTCHAR 0036 +ENCODING 54 +SWIDTH 540 0 +DWIDTH 12 0 +BBX 10 16 1 0 +BITMAP +1F00 +3F80 +61C0 +60C0 +C000 +C000 +CF00 +FF80 +E1C0 +C0C0 +C0C0 +C0C0 +C0C0 +6180 +7F80 +1E00 +ENDCHAR +STARTCHAR 0037 +ENCODING 55 +SWIDTH 540 0 +DWIDTH 12 0 +BBX 10 16 1 0 +BITMAP +FFC0 +FFC0 +0080 +0180 +0300 +0600 +0600 +0C00 +0C00 +1800 +1800 +1800 +1000 +3000 +3000 +3000 +ENDCHAR +STARTCHAR 0038 +ENCODING 56 +SWIDTH 540 0 +DWIDTH 12 0 +BBX 10 16 1 0 +BITMAP +1E00 +3F00 +6180 +6180 +6180 +6180 +3F00 +3F00 +6180 +C0C0 +C0C0 +C0C0 +C0C0 +6180 +7F80 +1E00 +ENDCHAR +STARTCHAR 0039 +ENCODING 57 +SWIDTH 540 0 +DWIDTH 12 0 +BBX 10 16 1 0 +BITMAP +1E00 +7F00 +6180 +C080 +C0C0 +C0C0 +C0C0 +E1C0 +7FC0 +3CC0 +00C0 +00C0 +C180 +E380 +7F00 +3E00 +ENDCHAR +STARTCHAR 003A +ENCODING 58 +SWIDTH 270 0 +DWIDTH 6 0 +BBX 2 12 2 0 +BITMAP +C0 +C0 +00 +00 +00 +00 +00 +00 +00 +00 +C0 +C0 +ENDCHAR +STARTCHAR 003B +ENCODING 59 +SWIDTH 270 0 +DWIDTH 6 0 +BBX 2 15 2 -3 +BITMAP +C0 +C0 +00 +00 +00 +00 +00 +00 +00 +00 +C0 +C0 +40 +40 +80 +ENDCHAR +STARTCHAR 003C +ENCODING 60 +SWIDTH 585 0 +DWIDTH 13 0 +BBX 11 11 1 2 +BITMAP +0020 +00E0 +07C0 +1E00 +7800 +C000 +7800 +1E00 +07C0 +00E0 +0020 +ENDCHAR +STARTCHAR 003D +ENCODING 61 +SWIDTH 585 0 +DWIDTH 13 0 +BBX 11 7 1 4 +BITMAP +FFE0 +FFE0 +0000 +0000 +0000 +FFE0 +FFE0 +ENDCHAR +STARTCHAR 003E +ENCODING 62 +SWIDTH 585 0 +DWIDTH 13 0 +BBX 11 11 1 2 +BITMAP +8000 +E000 +7C00 +0F00 +03C0 +0060 +03C0 +0F00 +7C00 +E000 +8000 +ENDCHAR +STARTCHAR 003F +ENCODING 63 +SWIDTH 540 0 +DWIDTH 12 0 +BBX 10 16 1 0 +BITMAP +3F00 +7F80 +E1C0 +C0C0 +00C0 +01C0 +0380 +0700 +0600 +0C00 +0C00 +0C00 +0000 +0000 +0C00 +0C00 +ENDCHAR +STARTCHAR 0040 +ENCODING 64 +SWIDTH 990 0 +DWIDTH 22 0 +BBX 20 21 1 -5 +BITMAP +01F800 +07FE00 +1E0780 +180180 +31CCC0 +77ECE0 +6E3860 +6C1860 +DC1860 +D81860 +D81860 +D830C0 +D831C0 +DC7380 +EFFF00 +671C00 +700030 +3800E0 +1E03C0 +0FFF80 +01FC00 +ENDCHAR +STARTCHAR 0041 +ENCODING 65 +SWIDTH 675 0 +DWIDTH 15 0 +BBX 15 16 0 0 +BITMAP +0380 +0380 +06C0 +06C0 +0EE0 +0C60 +0C60 +1830 +1830 +3FF8 +3FF8 +3018 +600C +600C +C006 +C006 +ENDCHAR +STARTCHAR 0042 +ENCODING 66 +SWIDTH 675 0 +DWIDTH 15 0 +BBX 12 16 2 0 +BITMAP +FF80 +FFC0 +C0E0 +C060 +C060 +C060 +C0C0 +FFC0 +FFC0 +C060 +C030 +C030 +C030 +C070 +FFE0 +FF80 +ENDCHAR +STARTCHAR 0043 +ENCODING 67 +SWIDTH 720 0 +DWIDTH 16 0 +BBX 14 16 1 0 +BITMAP +07C0 +1FF0 +3838 +6018 +600C +C000 +C000 +C000 +C000 +C000 +C000 +600C +6018 +3838 +1FF0 +0FC0 +ENDCHAR +STARTCHAR 0044 +ENCODING 68 +SWIDTH 720 0 +DWIDTH 16 0 +BBX 13 16 2 0 +BITMAP +FF80 +FFE0 +C070 +C030 +C038 +C018 +C018 +C018 +C018 +C018 +C018 +C030 +C030 +C0E0 +FFE0 +FF80 +ENDCHAR +STARTCHAR 0045 +ENCODING 69 +SWIDTH 675 0 +DWIDTH 15 0 +BBX 12 16 2 0 +BITMAP +FFF0 +FFF0 +C000 +C000 +C000 +C000 +C000 +FFE0 +FFE0 +C000 +C000 +C000 +C000 +C000 +FFF0 +FFF0 +ENDCHAR +STARTCHAR 0046 +ENCODING 70 +SWIDTH 585 0 +DWIDTH 13 0 +BBX 10 16 2 0 +BITMAP +FFC0 +FFC0 +C000 +C000 +C000 +C000 +C000 +FF80 +FF80 +C000 +C000 +C000 +C000 +C000 +C000 +C000 +ENDCHAR +STARTCHAR 0047 +ENCODING 71 +SWIDTH 765 0 +DWIDTH 17 0 +BBX 15 16 1 0 +BITMAP +07E0 +1FF8 +381C +700C +6006 +C000 +C000 +C000 +C0FE +C0FE +C006 +6006 +7006 +381E +1FFC +07E0 +ENDCHAR +STARTCHAR 0048 +ENCODING 72 +SWIDTH 720 0 +DWIDTH 16 0 +BBX 12 16 2 0 +BITMAP +C030 +C030 +C030 +C030 +C030 +C030 +C030 +FFF0 +FFF0 +C030 +C030 +C030 +C030 +C030 +C030 +C030 +ENDCHAR +STARTCHAR 0049 +ENCODING 73 +SWIDTH 270 0 +DWIDTH 6 0 +BBX 2 16 2 0 +BITMAP +C0 +C0 +C0 +C0 +C0 +C0 +C0 +C0 +C0 +C0 +C0 +C0 +C0 +C0 +C0 +C0 +ENDCHAR +STARTCHAR 004A +ENCODING 74 +SWIDTH 495 0 +DWIDTH 11 0 +BBX 8 16 1 0 +BITMAP +03 +03 +03 +03 +03 +03 +03 +03 +03 +03 +03 +C3 +C3 +E7 +7E +3C +ENDCHAR +STARTCHAR 004B +ENCODING 75 +SWIDTH 675 0 +DWIDTH 15 0 +BBX 13 16 2 0 +BITMAP +C070 +C0E0 +C1C0 +C380 +C700 +CE00 +DC00 +FC00 +FE00 +E700 +C380 +C1C0 +C0C0 +C0E0 +C070 +C038 +ENDCHAR +STARTCHAR 004C +ENCODING 76 +SWIDTH 540 0 +DWIDTH 12 0 +BBX 9 16 2 0 +BITMAP +C000 +C000 +C000 +C000 +C000 +C000 +C000 +C000 +C000 +C000 +C000 +C000 +C000 +C000 +FF80 +FF80 +ENDCHAR +STARTCHAR 004D +ENCODING 77 +SWIDTH 855 0 +DWIDTH 19 0 +BBX 15 16 2 0 +BITMAP +E00E +F01E +F01E +F01E +D836 +D836 +D836 +CC66 +CC66 +CC66 +C6C6 +C6C6 +C6C6 +C386 +C386 +C386 +ENDCHAR +STARTCHAR 004E +ENCODING 78 +SWIDTH 720 0 +DWIDTH 16 0 +BBX 12 16 2 0 +BITMAP +C030 +E030 +F030 +F030 +D830 +D830 +CC30 +C630 +C630 +C330 +C1B0 +C1B0 +C0F0 +C0F0 +C070 +C030 +ENDCHAR +STARTCHAR 004F +ENCODING 79 +SWIDTH 765 0 +DWIDTH 17 0 +BBX 15 16 1 0 +BITMAP +07C0 +1FF0 +3838 +701C +600C +C006 +C006 +C006 +C006 +C006 +C006 +600C +701C +3838 +1FF0 +07C0 +ENDCHAR +STARTCHAR 0050 +ENCODING 80 +SWIDTH 675 0 +DWIDTH 15 0 +BBX 12 16 2 0 +BITMAP +FF80 +FFE0 +C060 +C030 +C030 +C030 +C030 +C060 +FFE0 +FF80 +C000 +C000 +C000 +C000 +C000 +C000 +ENDCHAR +STARTCHAR 0051 +ENCODING 81 +SWIDTH 765 0 +DWIDTH 17 0 +BBX 15 17 1 -1 +BITMAP +07C0 +1FF0 +3838 +701C +600C +C006 +C006 +C006 +C006 +C006 +C006 +600C +70EC +3838 +1FF8 +07EE +0002 +ENDCHAR +STARTCHAR 0052 +ENCODING 82 +SWIDTH 720 0 +DWIDTH 16 0 +BBX 12 16 2 0 +BITMAP +FFC0 +FFE0 +C070 +C030 +C030 +C030 +C070 +FFE0 +FFC0 +C300 +C180 +C1C0 +C0E0 +C060 +C070 +C030 +ENDCHAR +STARTCHAR 0053 +ENCODING 83 +SWIDTH 675 0 +DWIDTH 15 0 +BBX 13 16 1 0 +BITMAP +0FC0 +3FE0 +7070 +6018 +6018 +7000 +3C00 +1FC0 +03F0 +0038 +C018 +C018 +6018 +7070 +3FF0 +0FC0 +ENDCHAR +STARTCHAR 0054 +ENCODING 84 +SWIDTH 630 0 +DWIDTH 14 0 +BBX 12 16 1 0 +BITMAP +FFF0 +FFF0 +0600 +0600 +0600 +0600 +0600 +0600 +0600 +0600 +0600 +0600 +0600 +0600 +0600 +0600 +ENDCHAR +STARTCHAR 0055 +ENCODING 85 +SWIDTH 720 0 +DWIDTH 16 0 +BBX 12 16 2 0 +BITMAP +C030 +C030 +C030 +C030 +C030 +C030 +C030 +C030 +C030 +C030 +C030 +C030 +E070 +70E0 +3FC0 +1F80 +ENDCHAR +STARTCHAR 0056 +ENCODING 86 +SWIDTH 675 0 +DWIDTH 15 0 +BBX 15 16 0 0 +BITMAP +C006 +E00E +600C +600C +3018 +3018 +3838 +1830 +1830 +0C60 +0C60 +0C60 +06C0 +06C0 +07C0 +0380 +ENDCHAR +STARTCHAR 0057 +ENCODING 87 +SWIDTH 945 0 +DWIDTH 21 0 +BBX 21 16 0 0 +BITMAP +C07018 +C07018 +60D830 +60D830 +60D830 +60D830 +318C60 +318C60 +318C60 +318C60 +1B06C0 +1B06C0 +1B06C0 +1B06C0 +0E0380 +0E0380 +ENDCHAR +STARTCHAR 0058 +ENCODING 88 +SWIDTH 675 0 +DWIDTH 15 0 +BBX 15 16 0 0 +BITMAP +701C +3838 +1830 +0C60 +0EE0 +06C0 +0380 +0380 +0380 +06C0 +0C60 +1C70 +1830 +3018 +701C +E00E +ENDCHAR +STARTCHAR 0059 +ENCODING 89 +SWIDTH 630 0 +DWIDTH 14 0 +BBX 14 16 0 0 +BITMAP +E01C +6018 +3030 +3870 +1860 +0CC0 +0FC0 +0780 +0300 +0300 +0300 +0300 +0300 +0300 +0300 +0300 +ENDCHAR +STARTCHAR 005A +ENCODING 90 +SWIDTH 585 0 +DWIDTH 13 0 +BBX 12 16 0 0 +BITMAP +7FF0 +7FF0 +0060 +00C0 +00C0 +0180 +0300 +0600 +0600 +0C00 +1800 +3000 +3000 +6000 +FFF0 +FFF0 +ENDCHAR +STARTCHAR 005B +ENCODING 91 +SWIDTH 270 0 +DWIDTH 6 0 +BBX 4 20 1 -4 +BITMAP +F0 +F0 +C0 +C0 +C0 +C0 +C0 +C0 +C0 +C0 +C0 +C0 +C0 +C0 +C0 +C0 +C0 +C0 +F0 +F0 +ENDCHAR +STARTCHAR 005C +ENCODING 92 +SWIDTH 270 0 +DWIDTH 6 0 +BBX 7 16 0 0 +BITMAP +C0 +C0 +60 +60 +60 +30 +30 +30 +18 +18 +18 +0C +0C +0C +0E +06 +ENDCHAR +STARTCHAR 005D +ENCODING 93 +SWIDTH 270 0 +DWIDTH 6 0 +BBX 4 20 1 -4 +BITMAP +F0 +F0 +30 +30 +30 +30 +30 +30 +30 +30 +30 +30 +30 +30 +30 +30 +30 +30 +F0 +F0 +ENDCHAR +STARTCHAR 005E +ENCODING 94 +SWIDTH 495 0 +DWIDTH 11 0 +BBX 10 9 0 7 +BITMAP +0C00 +1E00 +1E00 +3300 +3300 +3300 +6180 +6180 +C0C0 +ENDCHAR +STARTCHAR 005F +ENCODING 95 +SWIDTH 540 0 +DWIDTH 12 0 +BBX 12 2 0 -4 +BITMAP +FFF0 +FFF0 +ENDCHAR +STARTCHAR 0060 +ENCODING 96 +SWIDTH 315 0 +DWIDTH 7 0 +BBX 3 3 1 13 +BITMAP +C0 +60 +20 +ENDCHAR +STARTCHAR 0061 +ENCODING 97 +SWIDTH 540 0 +DWIDTH 12 0 +BBX 10 12 1 0 +BITMAP +3E00 +7F00 +C180 +C180 +0780 +7F80 +7980 +C180 +C180 +C380 +7F80 +7CC0 +ENDCHAR +STARTCHAR 0062 +ENCODING 98 +SWIDTH 540 0 +DWIDTH 12 0 +BBX 10 16 1 0 +BITMAP +C000 +C000 +C000 +C000 +DE00 +FF80 +E180 +C0C0 +C0C0 +C0C0 +C0C0 +C0C0 +C0C0 +E180 +FF80 +DE00 +ENDCHAR +STARTCHAR 0063 +ENCODING 99 +SWIDTH 495 0 +DWIDTH 11 0 +BBX 9 12 1 0 +BITMAP +1E00 +7F00 +6380 +C180 +C000 +C000 +C000 +C000 +C180 +6380 +7F00 +1E00 +ENDCHAR +STARTCHAR 0064 +ENCODING 100 +SWIDTH 540 0 +DWIDTH 12 0 +BBX 10 16 1 0 +BITMAP +00C0 +00C0 +00C0 +00C0 +1EC0 +7FC0 +61C0 +C0C0 +C0C0 +C0C0 +C0C0 +C0C0 +C0C0 +E1C0 +7FC0 +1EC0 +ENDCHAR +STARTCHAR 0065 +ENCODING 101 +SWIDTH 540 0 +DWIDTH 12 0 +BBX 10 12 1 0 +BITMAP +1E00 +7F80 +6180 +C0C0 +FFC0 +FFC0 +C000 +C000 +E0C0 +7180 +3F80 +1E00 +ENDCHAR +STARTCHAR 0066 +ENCODING 102 +SWIDTH 315 0 +DWIDTH 7 0 +BBX 7 16 0 0 +BITMAP +1E +3E +30 +30 +FC +FC +30 +30 +30 +30 +30 +30 +30 +30 +30 +30 +ENDCHAR +STARTCHAR 0067 +ENCODING 103 +SWIDTH 540 0 +DWIDTH 12 0 +BBX 10 16 1 -4 +BITMAP +1EC0 +7FC0 +61C0 +E0C0 +C0C0 +C0C0 +C0C0 +C0C0 +C1C0 +61C0 +7FC0 +1EC0 +00C0 +C180 +FF80 +3E00 +ENDCHAR +STARTCHAR 0068 +ENCODING 104 +SWIDTH 495 0 +DWIDTH 11 0 +BBX 9 16 1 0 +BITMAP +C000 +C000 +C000 +C000 +DE00 +FF00 +E380 +C180 +C180 +C180 +C180 +C180 +C180 +C180 +C180 +C180 +ENDCHAR +STARTCHAR 0069 +ENCODING 105 +SWIDTH 225 0 +DWIDTH 5 0 +BBX 2 16 1 0 +BITMAP +C0 +C0 +00 +00 +C0 +C0 +C0 +C0 +C0 +C0 +C0 +C0 +C0 +C0 +C0 +C0 +ENDCHAR +STARTCHAR 006A +ENCODING 106 +SWIDTH 180 0 +DWIDTH 4 0 +BBX 4 20 -1 -4 +BITMAP +30 +30 +00 +00 +30 +30 +30 +30 +30 +30 +30 +30 +30 +30 +30 +30 +30 +30 +F0 +E0 +ENDCHAR +STARTCHAR 006B +ENCODING 107 +SWIDTH 450 0 +DWIDTH 10 0 +BBX 9 16 1 0 +BITMAP +C000 +C000 +C000 +C000 +C380 +C700 +CE00 +DC00 +F800 +FC00 +EC00 +CE00 +C600 +C700 +C300 +C380 +ENDCHAR +STARTCHAR 006C +ENCODING 108 +SWIDTH 180 0 +DWIDTH 4 0 +BBX 2 16 1 0 +BITMAP +C0 +C0 +C0 +C0 +C0 +C0 +C0 +C0 +C0 +C0 +C0 +C0 +C0 +C0 +C0 +C0 +ENDCHAR +STARTCHAR 006D +ENCODING 109 +SWIDTH 810 0 +DWIDTH 18 0 +BBX 16 12 1 0 +BITMAP +DE3C +FF7E +E3C7 +C183 +C183 +C183 +C183 +C183 +C183 +C183 +C183 +C183 +ENDCHAR +STARTCHAR 006E +ENCODING 110 +SWIDTH 495 0 +DWIDTH 11 0 +BBX 9 12 1 0 +BITMAP +DE00 +FF00 +E380 +C180 +C180 +C180 +C180 +C180 +C180 +C180 +C180 +C180 +ENDCHAR +STARTCHAR 006F +ENCODING 111 +SWIDTH 585 0 +DWIDTH 13 0 +BBX 11 12 1 0 +BITMAP +1F00 +3F80 +71C0 +E0E0 +C060 +C060 +C060 +C060 +E0E0 +71C0 +3F80 +1F00 +ENDCHAR +STARTCHAR 0070 +ENCODING 112 +SWIDTH 540 0 +DWIDTH 12 0 +BBX 10 16 1 -4 +BITMAP +DE00 +FF80 +E180 +C0C0 +C0C0 +C0C0 +C0C0 +C0C0 +C0C0 +E180 +FF00 +DE00 +C000 +C000 +C000 +C000 +ENDCHAR +STARTCHAR 0071 +ENCODING 113 +SWIDTH 540 0 +DWIDTH 12 0 +BBX 10 16 1 -4 +BITMAP +1EC0 +7FC0 +61C0 +C0C0 +C0C0 +C0C0 +C0C0 +C0C0 +C0C0 +61C0 +7FC0 +1EC0 +00C0 +00C0 +00C0 +00C0 +ENDCHAR +STARTCHAR 0072 +ENCODING 114 +SWIDTH 315 0 +DWIDTH 7 0 +BBX 6 12 1 0 +BITMAP +DC +FC +E0 +C0 +C0 +C0 +C0 +C0 +C0 +C0 +C0 +C0 +ENDCHAR +STARTCHAR 0073 +ENCODING 115 +SWIDTH 495 0 +DWIDTH 11 0 +BBX 9 12 1 0 +BITMAP +3E00 +7F00 +C180 +C000 +F000 +7E00 +1F80 +0380 +0180 +C380 +7F00 +3E00 +ENDCHAR +STARTCHAR 0074 +ENCODING 116 +SWIDTH 270 0 +DWIDTH 6 0 +BBX 5 16 0 0 +BITMAP +20 +60 +60 +60 +F8 +F8 +60 +60 +60 +60 +60 +60 +60 +60 +78 +38 +ENDCHAR +STARTCHAR 0075 +ENCODING 117 +SWIDTH 495 0 +DWIDTH 11 0 +BBX 9 12 1 0 +BITMAP +C180 +C180 +C180 +C180 +C180 +C180 +C180 +C180 +C180 +E380 +7F80 +3D80 +ENDCHAR +STARTCHAR 0076 +ENCODING 118 +SWIDTH 495 0 +DWIDTH 11 0 +BBX 11 12 0 0 +BITMAP +C060 +C060 +60C0 +60C0 +3180 +3180 +3180 +1B00 +1B00 +0E00 +0E00 +0400 +ENDCHAR +STARTCHAR 0077 +ENCODING 119 +SWIDTH 675 0 +DWIDTH 15 0 +BBX 15 12 0 0 +BITMAP +C106 +C386 +C386 +628C +66CC +26C8 +36D8 +36D8 +1450 +1C70 +1C70 +0C60 +ENDCHAR +STARTCHAR 0078 +ENCODING 120 +SWIDTH 450 0 +DWIDTH 10 0 +BBX 10 12 0 0 +BITMAP +E1C0 +6180 +3300 +3300 +1E00 +0C00 +0C00 +1E00 +3300 +3300 +6180 +E1C0 +ENDCHAR +STARTCHAR 0079 +ENCODING 121 +SWIDTH 495 0 +DWIDTH 11 0 +BBX 11 16 0 -4 +BITMAP +C060 +C060 +60C0 +60C0 +30C0 +3180 +1980 +1980 +0F00 +0F00 +0700 +0600 +0E00 +0C00 +7C00 +7800 +ENDCHAR +STARTCHAR 007A +ENCODING 122 +SWIDTH 495 0 +DWIDTH 11 0 +BBX 10 12 0 0 +BITMAP +7FC0 +7FC0 +01C0 +0380 +0700 +0E00 +1C00 +3800 +7000 +E000 +FFC0 +FFC0 +ENDCHAR +STARTCHAR 007B +ENCODING 123 +SWIDTH 315 0 +DWIDTH 7 0 +BBX 6 20 0 -4 +BITMAP +1C +3C +30 +30 +30 +30 +30 +30 +60 +E0 +E0 +60 +30 +30 +30 +30 +30 +30 +3C +1C +ENDCHAR +STARTCHAR 007C +ENCODING 124 +SWIDTH 270 0 +DWIDTH 6 0 +BBX 2 21 2 -5 +BITMAP +C0 +C0 +C0 +C0 +C0 +C0 +C0 +C0 +C0 +C0 +C0 +C0 +C0 +C0 +C0 +C0 +C0 +C0 +C0 +C0 +C0 +ENDCHAR +STARTCHAR 007D +ENCODING 125 +SWIDTH 315 0 +DWIDTH 7 0 +BBX 6 20 1 -4 +BITMAP +E0 +F0 +30 +30 +30 +30 +30 +30 +38 +1C +1C +38 +30 +30 +30 +30 +30 +30 +F0 +E0 +ENDCHAR +STARTCHAR 007E +ENCODING 126 +SWIDTH 585 0 +DWIDTH 13 0 +BBX 11 4 1 6 +BITMAP +7800 +FE20 +8FE0 +03C0 +ENDCHAR +STARTCHAR 00A0 +ENCODING 160 +SWIDTH 270 0 +DWIDTH 6 0 +BBX 0 0 0 0 +BITMAP +ENDCHAR +STARTCHAR 00A1 +ENCODING 161 +SWIDTH 270 0 +DWIDTH 6 0 +BBX 2 16 2 -4 +BITMAP +C0 +C0 +00 +00 +C0 +C0 +C0 +C0 +C0 +C0 +C0 +C0 +C0 +C0 +C0 +C0 +ENDCHAR +STARTCHAR 00A2 +ENCODING 162 +SWIDTH 540 0 +DWIDTH 12 0 +BBX 10 20 1 -4 +BITMAP +0100 +0100 +0100 +0100 +1E00 +3F80 +6380 +E4C0 +C400 +C400 +C800 +C800 +E8C0 +7980 +3F80 +1E00 +1000 +2000 +2000 +2000 +ENDCHAR +STARTCHAR 00A3 +ENCODING 163 +SWIDTH 540 0 +DWIDTH 12 0 +BBX 11 16 0 0 +BITMAP +1F00 +3F80 +71C0 +60C0 +6000 +6000 +6000 +FE00 +FE00 +3000 +3000 +3000 +6000 +7C40 +FFE0 +83C0 +ENDCHAR +STARTCHAR 00A4 +ENCODING 164 +SWIDTH 540 0 +DWIDTH 12 0 +BBX 11 11 1 2 +BITMAP +4040 +EEE0 +7FC0 +3180 +60C0 +60C0 +60C0 +3180 +7FC0 +EEE0 +4040 +ENDCHAR +STARTCHAR 00A5 +ENCODING 165 +SWIDTH 540 0 +DWIDTH 12 0 +BBX 12 16 0 0 +BITMAP +C030 +6060 +6060 +30C0 +30C0 +1980 +1F80 +0F00 +7FE0 +7FE0 +0600 +0600 +7FE0 +7FE0 +0600 +0600 +ENDCHAR +STARTCHAR 00A6 +ENCODING 166 +SWIDTH 270 0 +DWIDTH 6 0 +BBX 2 21 2 -5 +BITMAP +C0 +C0 +C0 +C0 +C0 +C0 +C0 +C0 +00 +00 +00 +00 +C0 +C0 +C0 +C0 +C0 +C0 +C0 +C0 +C0 +ENDCHAR +STARTCHAR 00A7 +ENCODING 167 +SWIDTH 540 0 +DWIDTH 12 0 +BBX 10 20 1 -4 +BITMAP +1E00 +3F00 +7380 +6180 +6000 +3800 +7C00 +CE00 +C380 +C1C0 +E0C0 +70C0 +1D80 +0F00 +0700 +0180 +6180 +7180 +3F00 +1E00 +ENDCHAR +STARTCHAR 00A8 +ENCODING 168 +SWIDTH 315 0 +DWIDTH 7 0 +BBX 6 2 1 14 +BITMAP +CC +CC +ENDCHAR +STARTCHAR 00A9 +ENCODING 169 +SWIDTH 720 0 +DWIDTH 16 0 +BBX 16 16 0 0 +BITMAP +07E0 +1818 +2004 +43C2 +4422 +8811 +8801 +8801 +8801 +8801 +8811 +4422 +43C2 +2004 +1818 +07E0 +ENDCHAR +STARTCHAR 00AA +ENCODING 170 +SWIDTH 360 0 +DWIDTH 8 0 +BBX 7 8 0 8 +BITMAP +7C +C6 +06 +3E +F6 +C6 +CE +76 +ENDCHAR +STARTCHAR 00AB +ENCODING 171 +SWIDTH 540 0 +DWIDTH 12 0 +BBX 9 10 1 1 +BITMAP +1980 +3300 +7700 +6600 +CC00 +CC00 +6600 +7700 +3300 +1980 +ENDCHAR +STARTCHAR 00AC +ENCODING 172 +SWIDTH 585 0 +DWIDTH 13 0 +BBX 11 6 1 5 +BITMAP +FFE0 +FFE0 +0060 +0060 +0060 +0060 +ENDCHAR +STARTCHAR 00AD +ENCODING 173 +SWIDTH 315 0 +DWIDTH 7 0 +BBX 6 2 0 5 +BITMAP +FC +FC +ENDCHAR +STARTCHAR 00AE +ENCODING 174 +SWIDTH 720 0 +DWIDTH 16 0 +BBX 16 16 0 0 +BITMAP +07E0 +1818 +2004 +47C2 +4422 +8421 +8421 +87C1 +8481 +8441 +8421 +4422 +4412 +2004 +1818 +07E0 +ENDCHAR +STARTCHAR 00AF +ENCODING 175 +SWIDTH 540 0 +DWIDTH 12 0 +BBX 13 2 -1 17 +BITMAP +FFF8 +FFF8 +ENDCHAR +STARTCHAR 00B0 +ENCODING 176 +SWIDTH 405 0 +DWIDTH 9 0 +BBX 6 6 1 10 +BITMAP +78 +CC +84 +84 +CC +78 +ENDCHAR +STARTCHAR 00B1 +ENCODING 177 +SWIDTH 540 0 +DWIDTH 12 0 +BBX 10 13 1 0 +BITMAP +0C00 +0C00 +0C00 +0C00 +FFC0 +FFC0 +0C00 +0C00 +0C00 +0C00 +0000 +FFC0 +FFC0 +ENDCHAR +STARTCHAR 00B2 +ENCODING 178 +SWIDTH 315 0 +DWIDTH 7 0 +BBX 7 8 0 8 +BITMAP +7C +C6 +06 +0E +1C +38 +60 +FE +ENDCHAR +STARTCHAR 00B3 +ENCODING 179 +SWIDTH 315 0 +DWIDTH 7 0 +BBX 7 8 0 8 +BITMAP +7C +C6 +06 +18 +06 +06 +C6 +7C +ENDCHAR +STARTCHAR 00B4 +ENCODING 180 +SWIDTH 315 0 +DWIDTH 7 0 +BBX 3 3 2 13 +BITMAP +60 +C0 +80 +ENDCHAR +STARTCHAR 00B5 +ENCODING 181 +SWIDTH 540 0 +DWIDTH 12 0 +BBX 10 16 1 -4 +BITMAP +C0C0 +C0C0 +C0C0 +C0C0 +C0C0 +C0C0 +C0C0 +C0C0 +C0C0 +E1C0 +FFC0 +DEC0 +C000 +C000 +C000 +C000 +ENDCHAR +STARTCHAR 00B6 +ENCODING 182 +SWIDTH 540 0 +DWIDTH 12 0 +BBX 12 20 0 -4 +BITMAP +3FF0 +7FF0 +FC60 +FC60 +FC60 +FC60 +FC60 +7C60 +3C60 +0C60 +0C60 +0C60 +0C60 +0C60 +0C60 +0C60 +0C60 +0C60 +0C60 +0C60 +ENDCHAR +STARTCHAR 00B7 +ENCODING 183 +SWIDTH 315 0 +DWIDTH 7 0 +BBX 2 2 3 7 +BITMAP +C0 +C0 +ENDCHAR +STARTCHAR 00B8 +ENCODING 184 +SWIDTH 315 0 +DWIDTH 7 0 +BBX 5 4 1 -4 +BITMAP +20 +30 +18 +F0 +ENDCHAR +STARTCHAR 00B9 +ENCODING 185 +SWIDTH 315 0 +DWIDTH 7 0 +BBX 4 8 1 8 +BITMAP +30 +70 +F0 +B0 +30 +30 +30 +30 +ENDCHAR +STARTCHAR 00BA +ENCODING 186 +SWIDTH 360 0 +DWIDTH 8 0 +BBX 8 8 0 8 +BITMAP +3C +66 +C3 +C3 +C3 +C3 +66 +3C +ENDCHAR +STARTCHAR 00BB +ENCODING 187 +SWIDTH 540 0 +DWIDTH 12 0 +BBX 9 10 2 1 +BITMAP +CC00 +6600 +7700 +3300 +1980 +1980 +3300 +7700 +6600 +CC00 +ENDCHAR +STARTCHAR 00BC +ENCODING 188 +SWIDTH 810 0 +DWIDTH 18 0 +BBX 16 16 2 0 +BITMAP +300C +7018 +F030 +B060 +3060 +30C0 +3180 +3300 +0306 +060E +0C1E +0C36 +1866 +307F +6006 +6006 +ENDCHAR +STARTCHAR 00BD +ENCODING 189 +SWIDTH 810 0 +DWIDTH 18 0 +BBX 16 16 2 0 +BITMAP +300C +7018 +F030 +B060 +3060 +30C0 +3180 +3180 +031E +0633 +0C03 +0C07 +1806 +300C +7018 +603F +ENDCHAR +STARTCHAR 00BE +ENCODING 190 +SWIDTH 810 0 +DWIDTH 18 0 +BBX 17 16 0 0 +BITMAP +7C0300 +C60600 +060C00 +180C00 +061800 +063000 +C63000 +7C6000 +00C300 +018700 +018F00 +031B00 +063300 +0C3F80 +0C0300 +180300 +ENDCHAR +STARTCHAR 00BF +ENCODING 191 +SWIDTH 585 0 +DWIDTH 13 0 +BBX 9 16 2 -4 +BITMAP +0C00 +0C00 +0000 +0000 +0C00 +0C00 +0C00 +1800 +3800 +7000 +E000 +C000 +C180 +E380 +7F00 +3E00 +ENDCHAR +STARTCHAR 00C0 +ENCODING 192 +SWIDTH 675 0 +DWIDTH 15 0 +BBX 15 20 0 0 +BITMAP +0300 +0180 +0080 +0000 +0380 +0380 +06C0 +06C0 +0EE0 +0C60 +0C60 +1830 +1830 +3FF8 +3FF8 +3018 +600C +600C +C006 +C006 +ENDCHAR +STARTCHAR 00C1 +ENCODING 193 +SWIDTH 675 0 +DWIDTH 15 0 +BBX 15 20 0 0 +BITMAP +0180 +0300 +0200 +0000 +0380 +0380 +06C0 +06C0 +0EE0 +0C60 +0C60 +1830 +1830 +3FF8 +3FF8 +3018 +600C +600C +C006 +C006 +ENDCHAR +STARTCHAR 00C2 +ENCODING 194 +SWIDTH 675 0 +DWIDTH 15 0 +BBX 15 20 0 0 +BITMAP +0180 +03C0 +0660 +0000 +0380 +0380 +06C0 +06C0 +0EE0 +0C60 +0C60 +1830 +1830 +3FF8 +3FF8 +3018 +600C +600C +C006 +C006 +ENDCHAR +STARTCHAR 00C3 +ENCODING 195 +SWIDTH 675 0 +DWIDTH 15 0 +BBX 15 20 0 0 +BITMAP +0760 +0FE0 +0DC0 +0000 +0380 +0380 +06C0 +06C0 +0EE0 +0C60 +0C60 +1830 +1830 +3FF8 +3FF8 +3018 +600C +600C +C006 +C006 +ENDCHAR +STARTCHAR 00C4 +ENCODING 196 +SWIDTH 675 0 +DWIDTH 15 0 +BBX 15 19 0 0 +BITMAP +0660 +0660 +0000 +0380 +0380 +06C0 +06C0 +0EE0 +0C60 +0C60 +1830 +1830 +3FF8 +3FF8 +3018 +600C +600C +C006 +C006 +ENDCHAR +STARTCHAR 00C5 +ENCODING 197 +SWIDTH 675 0 +DWIDTH 15 0 +BBX 15 20 0 0 +BITMAP +0380 +0440 +0440 +0440 +0380 +0380 +06C0 +06C0 +0EE0 +0C60 +0C60 +1830 +1830 +3FF8 +3FF8 +3018 +600C +600C +C006 +C006 +ENDCHAR +STARTCHAR 00C6 +ENCODING 198 +SWIDTH 990 0 +DWIDTH 22 0 +BBX 21 16 0 0 +BITMAP +00FFF8 +01FFF8 +019800 +031800 +061800 +061800 +0C1800 +0C1FF0 +181FF0 +1FF800 +3FF800 +301800 +601800 +601800 +C01FF8 +C01FF8 +ENDCHAR +STARTCHAR 00C7 +ENCODING 199 +SWIDTH 720 0 +DWIDTH 16 0 +BBX 14 20 1 -4 +BITMAP +07C0 +1FF0 +3838 +6018 +600C +C000 +C000 +C000 +C000 +C000 +C000 +600C +6018 +3838 +1FF0 +0FC0 +0200 +0300 +0180 +0F00 +ENDCHAR +STARTCHAR 00C8 +ENCODING 200 +SWIDTH 675 0 +DWIDTH 15 0 +BBX 12 20 2 0 +BITMAP +0C00 +0600 +0200 +0000 +FFF0 +FFF0 +C000 +C000 +C000 +C000 +C000 +FFE0 +FFE0 +C000 +C000 +C000 +C000 +C000 +FFF0 +FFF0 +ENDCHAR +STARTCHAR 00C9 +ENCODING 201 +SWIDTH 675 0 +DWIDTH 15 0 +BBX 12 20 2 0 +BITMAP +0300 +0600 +0400 +0000 +FFF0 +FFF0 +C000 +C000 +C000 +C000 +C000 +FFE0 +FFE0 +C000 +C000 +C000 +C000 +C000 +FFF0 +FFF0 +ENDCHAR +STARTCHAR 00CA +ENCODING 202 +SWIDTH 675 0 +DWIDTH 15 0 +BBX 12 20 2 0 +BITMAP +0600 +0F00 +1980 +0000 +FFF0 +FFF0 +C000 +C000 +C000 +C000 +C000 +FFE0 +FFE0 +C000 +C000 +C000 +C000 +C000 +FFF0 +FFF0 +ENDCHAR +STARTCHAR 00CB +ENCODING 203 +SWIDTH 675 0 +DWIDTH 15 0 +BBX 12 19 2 0 +BITMAP +1980 +1980 +0000 +FFF0 +FFF0 +C000 +C000 +C000 +C000 +C000 +FFE0 +FFE0 +C000 +C000 +C000 +C000 +C000 +FFF0 +FFF0 +ENDCHAR +STARTCHAR 00CC +ENCODING 204 +SWIDTH 270 0 +DWIDTH 6 0 +BBX 3 20 1 0 +BITMAP +C0 +60 +20 +00 +60 +60 +60 +60 +60 +60 +60 +60 +60 +60 +60 +60 +60 +60 +60 +60 +ENDCHAR +STARTCHAR 00CD +ENCODING 205 +SWIDTH 270 0 +DWIDTH 6 0 +BBX 3 20 2 0 +BITMAP +60 +C0 +80 +00 +C0 +C0 +C0 +C0 +C0 +C0 +C0 +C0 +C0 +C0 +C0 +C0 +C0 +C0 +C0 +C0 +ENDCHAR +STARTCHAR 00CE +ENCODING 206 +SWIDTH 270 0 +DWIDTH 6 0 +BBX 6 20 0 0 +BITMAP +30 +78 +CC +00 +30 +30 +30 +30 +30 +30 +30 +30 +30 +30 +30 +30 +30 +30 +30 +30 +ENDCHAR +STARTCHAR 00CF +ENCODING 207 +SWIDTH 270 0 +DWIDTH 6 0 +BBX 6 19 0 0 +BITMAP +CC +CC +00 +30 +30 +30 +30 +30 +30 +30 +30 +30 +30 +30 +30 +30 +30 +30 +30 +ENDCHAR +STARTCHAR 00D0 +ENCODING 208 +SWIDTH 720 0 +DWIDTH 16 0 +BBX 15 16 0 0 +BITMAP +3FE0 +3FF8 +301C +300C +300E +3006 +3006 +FF06 +FF06 +3006 +3006 +300C +300C +303C +3FF8 +3FE0 +ENDCHAR +STARTCHAR 00D1 +ENCODING 209 +SWIDTH 720 0 +DWIDTH 16 0 +BBX 12 20 2 0 +BITMAP +0EC0 +1FC0 +1B80 +0000 +C030 +E030 +F030 +F030 +D830 +D830 +CC30 +C630 +C630 +C330 +C1B0 +C1B0 +C0F0 +C0F0 +C070 +C030 +ENDCHAR +STARTCHAR 00D2 +ENCODING 210 +SWIDTH 765 0 +DWIDTH 17 0 +BBX 15 20 1 0 +BITMAP +0300 +0180 +0080 +0000 +07C0 +1FF0 +3838 +701C +600C +C006 +C006 +C006 +C006 +C006 +C006 +600C +701C +3838 +1FF0 +07C0 +ENDCHAR +STARTCHAR 00D3 +ENCODING 211 +SWIDTH 765 0 +DWIDTH 17 0 +BBX 15 20 1 0 +BITMAP +0180 +0300 +0200 +0000 +07C0 +1FF0 +3838 +701C +600C +C006 +C006 +C006 +C006 +C006 +C006 +600C +701C +3838 +1FF0 +07C0 +ENDCHAR +STARTCHAR 00D4 +ENCODING 212 +SWIDTH 765 0 +DWIDTH 17 0 +BBX 15 20 1 0 +BITMAP +0180 +03C0 +0660 +0000 +07C0 +1FF0 +3838 +701C +600C +C006 +C006 +C006 +C006 +C006 +C006 +600C +701C +3838 +1FF0 +07C0 +ENDCHAR +STARTCHAR 00D5 +ENCODING 213 +SWIDTH 765 0 +DWIDTH 17 0 +BBX 15 20 1 0 +BITMAP +0760 +0FE0 +0DC0 +0000 +07C0 +1FF0 +3838 +701C +600C +C006 +C006 +C006 +C006 +C006 +C006 +600C +701C +3838 +1FF0 +07C0 +ENDCHAR +STARTCHAR 00D6 +ENCODING 214 +SWIDTH 765 0 +DWIDTH 17 0 +BBX 15 19 1 0 +BITMAP +0660 +0660 +0000 +07C0 +1FF0 +3838 +701C +600C +C006 +C006 +C006 +C006 +C006 +C006 +600C +701C +3838 +1FF0 +07C0 +ENDCHAR +STARTCHAR 00D7 +ENCODING 215 +SWIDTH 585 0 +DWIDTH 13 0 +BBX 9 9 2 3 +BITMAP +8100 +E380 +7700 +3E00 +1C00 +3E00 +7700 +E380 +8100 +ENDCHAR +STARTCHAR 00D8 +ENCODING 216 +SWIDTH 765 0 +DWIDTH 17 0 +BBX 15 16 1 -1 +BITMAP +07C6 +1FFC +383C +701C +603C +C066 +C0C6 +C186 +C306 +C606 +C606 +6C0C +781C +3838 +7FF0 +CFC0 +ENDCHAR +STARTCHAR 00D9 +ENCODING 217 +SWIDTH 720 0 +DWIDTH 16 0 +BBX 12 20 2 0 +BITMAP +0C00 +0600 +0200 +0000 +C030 +C030 +C030 +C030 +C030 +C030 +C030 +C030 +C030 +C030 +C030 +C030 +E070 +70E0 +3FC0 +1F80 +ENDCHAR +STARTCHAR 00DA +ENCODING 218 +SWIDTH 720 0 +DWIDTH 16 0 +BBX 12 20 2 0 +BITMAP +0300 +0600 +0400 +0000 +C030 +C030 +C030 +C030 +C030 +C030 +C030 +C030 +C030 +C030 +C030 +C030 +E070 +70E0 +3FC0 +1F80 +ENDCHAR +STARTCHAR 00DB +ENCODING 219 +SWIDTH 720 0 +DWIDTH 16 0 +BBX 12 20 2 0 +BITMAP +0600 +0F00 +1980 +0000 +C030 +C030 +C030 +C030 +C030 +C030 +C030 +C030 +C030 +C030 +C030 +C030 +E070 +70E0 +3FC0 +1F80 +ENDCHAR +STARTCHAR 00DC +ENCODING 220 +SWIDTH 720 0 +DWIDTH 16 0 +BBX 12 19 2 0 +BITMAP +1980 +1980 +0000 +C030 +C030 +C030 +C030 +C030 +C030 +C030 +C030 +C030 +C030 +C030 +C030 +E070 +70E0 +3FC0 +1F80 +ENDCHAR +STARTCHAR 00DD +ENCODING 221 +SWIDTH 630 0 +DWIDTH 14 0 +BBX 14 20 0 0 +BITMAP +0180 +0300 +0200 +0000 +E01C +6018 +3030 +3870 +1860 +0CC0 +0FC0 +0780 +0300 +0300 +0300 +0300 +0300 +0300 +0300 +0300 +ENDCHAR +STARTCHAR 00DE +ENCODING 222 +SWIDTH 675 0 +DWIDTH 15 0 +BBX 12 16 2 0 +BITMAP +C000 +C000 +C000 +FF80 +FFE0 +C060 +C030 +C030 +C030 +C030 +C060 +FFE0 +FF80 +C000 +C000 +C000 +ENDCHAR +STARTCHAR 00DF +ENCODING 223 +SWIDTH 585 0 +DWIDTH 13 0 +BBX 11 16 2 0 +BITMAP +3E00 +7F00 +E380 +C180 +C180 +C300 +C700 +C600 +C700 +C380 +C0C0 +C060 +C060 +DCE0 +CFC0 +C780 +ENDCHAR +STARTCHAR 00E0 +ENCODING 224 +SWIDTH 540 0 +DWIDTH 12 0 +BBX 10 16 1 0 +BITMAP +1800 +0C00 +0400 +0000 +3E00 +7F00 +C180 +C180 +0780 +7F80 +7980 +C180 +C180 +C380 +7F80 +7CC0 +ENDCHAR +STARTCHAR 00E1 +ENCODING 225 +SWIDTH 540 0 +DWIDTH 12 0 +BBX 10 16 1 0 +BITMAP +0600 +0C00 +0800 +0000 +3E00 +7F00 +C180 +C180 +0780 +7F80 +7980 +C180 +C180 +C380 +7F80 +7CC0 +ENDCHAR +STARTCHAR 00E2 +ENCODING 226 +SWIDTH 540 0 +DWIDTH 12 0 +BBX 10 16 1 0 +BITMAP +0C00 +1E00 +3300 +0000 +3E00 +7F00 +C180 +C180 +0780 +7F80 +7980 +C180 +C180 +C380 +7F80 +7CC0 +ENDCHAR +STARTCHAR 00E3 +ENCODING 227 +SWIDTH 540 0 +DWIDTH 12 0 +BBX 10 16 1 0 +BITMAP +3B00 +7F00 +6E00 +0000 +3E00 +7F00 +C180 +C180 +0780 +7F80 +7980 +C180 +C180 +C380 +7F80 +7CC0 +ENDCHAR +STARTCHAR 00E4 +ENCODING 228 +SWIDTH 540 0 +DWIDTH 12 0 +BBX 10 15 1 0 +BITMAP +3300 +3300 +0000 +3E00 +7F00 +C180 +C180 +0780 +7F80 +7980 +C180 +C180 +C380 +7F80 +7CC0 +ENDCHAR +STARTCHAR 00E5 +ENCODING 229 +SWIDTH 540 0 +DWIDTH 12 0 +BBX 10 18 1 0 +BITMAP +1C00 +2200 +2200 +2200 +1C00 +0000 +3E00 +7F00 +C180 +C180 +0780 +7F80 +7980 +C180 +C180 +C380 +7F80 +7CC0 +ENDCHAR +STARTCHAR 00E6 +ENCODING 230 +SWIDTH 900 0 +DWIDTH 20 0 +BBX 18 12 1 0 +BITMAP +3F3E00 +7FFF80 +E0E180 +C0C0C0 +03FFC0 +3FFFC0 +7CC000 +C0C000 +C0C0C0 +E1E180 +7F7F80 +3C1E00 +ENDCHAR +STARTCHAR 00E7 +ENCODING 231 +SWIDTH 495 0 +DWIDTH 11 0 +BBX 9 16 1 -4 +BITMAP +1E00 +7F00 +6380 +C180 +C000 +C000 +C000 +C000 +C180 +6380 +7F00 +1E00 +0800 +0C00 +0600 +3C00 +ENDCHAR +STARTCHAR 00E8 +ENCODING 232 +SWIDTH 540 0 +DWIDTH 12 0 +BBX 10 16 1 0 +BITMAP +1800 +0C00 +0400 +0000 +1E00 +7F80 +6180 +C0C0 +FFC0 +FFC0 +C000 +C000 +E0C0 +7180 +3F80 +1E00 +ENDCHAR +STARTCHAR 00E9 +ENCODING 233 +SWIDTH 540 0 +DWIDTH 12 0 +BBX 10 16 1 0 +BITMAP +0600 +0C00 +0800 +0000 +1E00 +7F80 +6180 +C0C0 +FFC0 +FFC0 +C000 +C000 +E0C0 +7180 +3F80 +1E00 +ENDCHAR +STARTCHAR 00EA +ENCODING 234 +SWIDTH 540 0 +DWIDTH 12 0 +BBX 10 16 1 0 +BITMAP +0C00 +1E00 +3300 +0000 +1E00 +7F80 +6180 +C0C0 +FFC0 +FFC0 +C000 +C000 +E0C0 +7180 +3F80 +1E00 +ENDCHAR +STARTCHAR 00EB +ENCODING 235 +SWIDTH 540 0 +DWIDTH 12 0 +BBX 10 15 1 0 +BITMAP +3300 +3300 +0000 +1E00 +7F80 +6180 +C0C0 +FFC0 +FFC0 +C000 +C000 +E0C0 +7180 +3F80 +1E00 +ENDCHAR +STARTCHAR 00EC +ENCODING 236 +SWIDTH 270 0 +DWIDTH 6 0 +BBX 3 16 1 0 +BITMAP +C0 +60 +20 +00 +60 +60 +60 +60 +60 +60 +60 +60 +60 +60 +60 +60 +ENDCHAR +STARTCHAR 00ED +ENCODING 237 +SWIDTH 270 0 +DWIDTH 6 0 +BBX 3 16 2 0 +BITMAP +60 +C0 +80 +00 +C0 +C0 +C0 +C0 +C0 +C0 +C0 +C0 +C0 +C0 +C0 +C0 +ENDCHAR +STARTCHAR 00EE +ENCODING 238 +SWIDTH 270 0 +DWIDTH 6 0 +BBX 6 16 0 0 +BITMAP +30 +78 +CC +00 +30 +30 +30 +30 +30 +30 +30 +30 +30 +30 +30 +30 +ENDCHAR +STARTCHAR 00EF +ENCODING 239 +SWIDTH 270 0 +DWIDTH 6 0 +BBX 6 15 0 0 +BITMAP +CC +CC +00 +30 +30 +30 +30 +30 +30 +30 +30 +30 +30 +30 +30 +ENDCHAR +STARTCHAR 00F0 +ENCODING 240 +SWIDTH 540 0 +DWIDTH 12 0 +BBX 10 16 1 0 +BITMAP +1900 +0F00 +1E00 +3300 +1D00 +7F80 +6180 +E1C0 +C0C0 +C0C0 +C0C0 +C0C0 +C1C0 +6180 +7F80 +1E00 +ENDCHAR +STARTCHAR 00F1 +ENCODING 241 +SWIDTH 495 0 +DWIDTH 11 0 +BBX 9 16 1 0 +BITMAP +3B00 +7F00 +6E00 +0000 +DE00 +FF00 +E380 +C180 +C180 +C180 +C180 +C180 +C180 +C180 +C180 +C180 +ENDCHAR +STARTCHAR 00F2 +ENCODING 242 +SWIDTH 585 0 +DWIDTH 13 0 +BBX 11 16 1 0 +BITMAP +0C00 +0600 +0200 +0000 +1F00 +3F80 +71C0 +E0E0 +C060 +C060 +C060 +C060 +E0E0 +71C0 +3F80 +1F00 +ENDCHAR +STARTCHAR 00F3 +ENCODING 243 +SWIDTH 585 0 +DWIDTH 13 0 +BBX 11 16 1 0 +BITMAP +0300 +0600 +0400 +0000 +1F00 +3F80 +71C0 +E0E0 +C060 +C060 +C060 +C060 +E0E0 +71C0 +3F80 +1F00 +ENDCHAR +STARTCHAR 00F4 +ENCODING 244 +SWIDTH 585 0 +DWIDTH 13 0 +BBX 11 16 1 0 +BITMAP +0600 +0F00 +1980 +0000 +1F00 +3F80 +71C0 +E0E0 +C060 +C060 +C060 +C060 +E0E0 +71C0 +3F80 +1F00 +ENDCHAR +STARTCHAR 00F5 +ENCODING 245 +SWIDTH 585 0 +DWIDTH 13 0 +BBX 11 16 1 0 +BITMAP +1D80 +3F80 +3700 +0000 +1F00 +3F80 +71C0 +E0E0 +C060 +C060 +C060 +C060 +E0E0 +71C0 +3F80 +1F00 +ENDCHAR +STARTCHAR 00F6 +ENCODING 246 +SWIDTH 585 0 +DWIDTH 13 0 +BBX 11 15 1 0 +BITMAP +1980 +1980 +0000 +1F00 +3F80 +71C0 +E0E0 +C060 +C060 +C060 +C060 +E0E0 +71C0 +3F80 +1F00 +ENDCHAR +STARTCHAR 00F7 +ENCODING 247 +SWIDTH 540 0 +DWIDTH 12 0 +BBX 10 8 1 4 +BITMAP +0C00 +0C00 +0000 +FFC0 +FFC0 +0000 +0C00 +0C00 +ENDCHAR +STARTCHAR 00F8 +ENCODING 248 +SWIDTH 630 0 +DWIDTH 14 0 +BBX 11 14 1 -1 +BITMAP +0060 +1F60 +7FC0 +71C0 +E3E0 +C660 +C660 +CC60 +D860 +F8E0 +71C0 +7F80 +DF00 +4000 +ENDCHAR +STARTCHAR 00F9 +ENCODING 249 +SWIDTH 495 0 +DWIDTH 11 0 +BBX 9 16 1 0 +BITMAP +3000 +1800 +0800 +0000 +C180 +C180 +C180 +C180 +C180 +C180 +C180 +C180 +C180 +E380 +7F80 +3D80 +ENDCHAR +STARTCHAR 00FA +ENCODING 250 +SWIDTH 495 0 +DWIDTH 11 0 +BBX 9 16 1 0 +BITMAP +0600 +0C00 +0800 +0000 +C180 +C180 +C180 +C180 +C180 +C180 +C180 +C180 +C180 +E380 +7F80 +3D80 +ENDCHAR +STARTCHAR 00FB +ENCODING 251 +SWIDTH 495 0 +DWIDTH 11 0 +BBX 9 16 1 0 +BITMAP +0C00 +1E00 +3300 +0000 +C180 +C180 +C180 +C180 +C180 +C180 +C180 +C180 +C180 +E380 +7F80 +3D80 +ENDCHAR +STARTCHAR 00FC +ENCODING 252 +SWIDTH 495 0 +DWIDTH 11 0 +BBX 9 15 1 0 +BITMAP +6600 +6600 +0000 +C180 +C180 +C180 +C180 +C180 +C180 +C180 +C180 +C180 +E380 +7F80 +3D80 +ENDCHAR +STARTCHAR 00FD +ENCODING 253 +SWIDTH 495 0 +DWIDTH 11 0 +BBX 11 20 0 -4 +BITMAP +0300 +0600 +0400 +0000 +C060 +C060 +60C0 +60C0 +30C0 +3180 +1980 +1980 +0F00 +0F00 +0700 +0600 +0E00 +0C00 +7C00 +7800 +ENDCHAR +STARTCHAR 00FE +ENCODING 254 +SWIDTH 540 0 +DWIDTH 12 0 +BBX 10 20 1 -4 +BITMAP +C000 +C000 +C000 +C000 +DE00 +FF80 +E180 +C1C0 +C0C0 +C0C0 +C0C0 +C0C0 +C1C0 +E180 +FF00 +DE00 +C000 +C000 +C000 +C000 +ENDCHAR +STARTCHAR 00FF +ENCODING 255 +SWIDTH 495 0 +DWIDTH 11 0 +BBX 11 19 0 -4 +BITMAP +1980 +1980 +0000 +C060 +C060 +60C0 +60C0 +30C0 +3180 +1980 +1980 +0F00 +0F00 +0700 +0600 +0E00 +0C00 +7C00 +7800 +ENDCHAR +STARTCHAR 0100 +ENCODING 256 +SWIDTH 675 0 +DWIDTH 15 0 +BBX 15 19 0 0 +BITMAP +07E0 +07E0 +0000 +0380 +0380 +06C0 +06C0 +0EE0 +0C60 +0C60 +1830 +1830 +3FF8 +3FF8 +3018 +600C +600C +C006 +C006 +ENDCHAR +STARTCHAR 0101 +ENCODING 257 +SWIDTH 540 0 +DWIDTH 12 0 +BBX 10 15 1 0 +BITMAP +3F00 +3F00 +0000 +3E00 +7F00 +C180 +C180 +0780 +7F80 +7980 +C180 +C180 +C380 +7F80 +7CC0 +ENDCHAR +STARTCHAR 0102 +ENCODING 258 +SWIDTH 675 0 +DWIDTH 15 0 +BBX 15 20 0 0 +BITMAP +0820 +0C60 +07C0 +0000 +0380 +0380 +06C0 +06C0 +0EE0 +0C60 +0C60 +1830 +1830 +3FF8 +3FF8 +3018 +600C +600C +C006 +C006 +ENDCHAR +STARTCHAR 0103 +ENCODING 259 +SWIDTH 540 0 +DWIDTH 12 0 +BBX 10 16 1 0 +BITMAP +4100 +6300 +3E00 +0000 +3E00 +7F00 +C180 +C180 +0780 +7F80 +7980 +C180 +C180 +C380 +7F80 +7CC0 +ENDCHAR +STARTCHAR 0104 +ENCODING 260 +SWIDTH 675 0 +DWIDTH 15 0 +BBX 15 20 0 -4 +BITMAP +0380 +0380 +06C0 +06C0 +0EE0 +0C60 +0C60 +1830 +1830 +3FF8 +3FF8 +3018 +600C +600C +C006 +C006 +0004 +0008 +0008 +000E +ENDCHAR +STARTCHAR 0105 +ENCODING 261 +SWIDTH 540 0 +DWIDTH 12 0 +BBX 10 16 1 -4 +BITMAP +3E00 +7F00 +C180 +C180 +0780 +7F80 +7980 +C180 +C180 +C380 +7F80 +7CC0 +0080 +0100 +0100 +01C0 +ENDCHAR +STARTCHAR 0106 +ENCODING 262 +SWIDTH 720 0 +DWIDTH 16 0 +BBX 14 20 1 0 +BITMAP +0180 +0300 +0200 +0000 +07C0 +1FF0 +3838 +6018 +600C +C000 +C000 +C000 +C000 +C000 +C000 +600C +6018 +3838 +1FF0 +0FC0 +ENDCHAR +STARTCHAR 0107 +ENCODING 263 +SWIDTH 495 0 +DWIDTH 11 0 +BBX 9 16 1 0 +BITMAP +0600 +0C00 +0800 +0000 +1E00 +7F00 +6380 +C180 +C000 +C000 +C000 +C000 +C180 +6380 +7F00 +1E00 +ENDCHAR +STARTCHAR 0108 +ENCODING 264 +SWIDTH 720 0 +DWIDTH 16 0 +BBX 14 20 1 0 +BITMAP +0300 +0780 +0CC0 +0000 +07C0 +1FF0 +3838 +6018 +600C +C000 +C000 +C000 +C000 +C000 +C000 +600C +6018 +3838 +1FF0 +0FC0 +ENDCHAR +STARTCHAR 0109 +ENCODING 265 +SWIDTH 495 0 +DWIDTH 11 0 +BBX 9 16 1 0 +BITMAP +0C00 +1E00 +3300 +0000 +1E00 +7F00 +6380 +C180 +C000 +C000 +C000 +C000 +C180 +6380 +7F00 +1E00 +ENDCHAR +STARTCHAR 010A +ENCODING 266 +SWIDTH 720 0 +DWIDTH 16 0 +BBX 14 19 1 0 +BITMAP +0180 +0180 +0000 +07C0 +1FF0 +3838 +6018 +600C +C000 +C000 +C000 +C000 +C000 +C000 +600C +6018 +3838 +1FF0 +0FC0 +ENDCHAR +STARTCHAR 010B +ENCODING 267 +SWIDTH 495 0 +DWIDTH 11 0 +BBX 9 15 1 0 +BITMAP +0C00 +0C00 +0000 +1E00 +7F00 +6380 +C180 +C000 +C000 +C000 +C000 +C180 +6380 +7F00 +1E00 +ENDCHAR +STARTCHAR 010C +ENCODING 268 +SWIDTH 720 0 +DWIDTH 16 0 +BBX 14 20 1 0 +BITMAP +0CC0 +0780 +0300 +0000 +07C0 +1FF0 +3838 +6018 +600C +C000 +C000 +C000 +C000 +C000 +C000 +600C +6018 +3838 +1FF0 +0FC0 +ENDCHAR +STARTCHAR 010D +ENCODING 269 +SWIDTH 495 0 +DWIDTH 11 0 +BBX 9 16 1 0 +BITMAP +3300 +1E00 +0C00 +0000 +1E00 +7F00 +6380 +C180 +C000 +C000 +C000 +C000 +C180 +6380 +7F00 +1E00 +ENDCHAR +STARTCHAR 010E +ENCODING 270 +SWIDTH 720 0 +DWIDTH 16 0 +BBX 13 20 2 0 +BITMAP +3300 +1E00 +0C00 +0000 +FF80 +FFE0 +C070 +C030 +C038 +C018 +C018 +C018 +C018 +C018 +C018 +C030 +C030 +C0E0 +FFE0 +FF80 +ENDCHAR +STARTCHAR 010F +ENCODING 271 +SWIDTH 630 0 +DWIDTH 14 0 +BBX 13 16 1 0 +BITMAP +00D8 +00D8 +00C8 +00C8 +1ED0 +7FC0 +61C0 +C1C0 +C0C0 +C0C0 +C0C0 +C0C0 +E0C0 +61C0 +3FC0 +1EC0 +ENDCHAR +STARTCHAR 0110 +ENCODING 272 +SWIDTH 720 0 +DWIDTH 16 0 +BBX 15 16 0 0 +BITMAP +3FE0 +3FF8 +301C +300C +300E +3006 +3006 +FF06 +FF06 +3006 +3006 +300C +300C +303C +3FF8 +3FE0 +ENDCHAR +STARTCHAR 0111 +ENCODING 273 +SWIDTH 540 0 +DWIDTH 12 0 +BBX 11 16 1 0 +BITMAP +00C0 +0FE0 +0FE0 +00C0 +1EC0 +7FC0 +61C0 +C1C0 +C0C0 +C0C0 +C0C0 +C0C0 +E0C0 +61C0 +3FC0 +1EC0 +ENDCHAR +STARTCHAR 0112 +ENCODING 274 +SWIDTH 675 0 +DWIDTH 15 0 +BBX 12 19 2 0 +BITMAP +1F80 +1F80 +0000 +FFF0 +FFF0 +C000 +C000 +C000 +C000 +C000 +FFE0 +FFE0 +C000 +C000 +C000 +C000 +C000 +FFF0 +FFF0 +ENDCHAR +STARTCHAR 0113 +ENCODING 275 +SWIDTH 540 0 +DWIDTH 12 0 +BBX 10 15 1 0 +BITMAP +3F00 +3F00 +0000 +1E00 +7F80 +6180 +C0C0 +FFC0 +FFC0 +C000 +C000 +E0C0 +7180 +3F80 +1E00 +ENDCHAR +STARTCHAR 0114 +ENCODING 276 +SWIDTH 675 0 +DWIDTH 15 0 +BBX 12 20 2 0 +BITMAP +1040 +18C0 +0F80 +0000 +FFF0 +FFF0 +C000 +C000 +C000 +C000 +C000 +FFE0 +FFE0 +C000 +C000 +C000 +C000 +C000 +FFF0 +FFF0 +ENDCHAR +STARTCHAR 0115 +ENCODING 277 +SWIDTH 540 0 +DWIDTH 12 0 +BBX 10 16 1 0 +BITMAP +2080 +3180 +1F00 +0000 +1E00 +7F80 +6180 +C0C0 +FFC0 +FFC0 +C000 +C000 +E0C0 +7180 +3F80 +1E00 +ENDCHAR +STARTCHAR 0116 +ENCODING 278 +SWIDTH 675 0 +DWIDTH 15 0 +BBX 12 19 2 0 +BITMAP +0600 +0600 +0000 +FFF0 +FFF0 +C000 +C000 +C000 +C000 +C000 +FFE0 +FFE0 +C000 +C000 +C000 +C000 +C000 +FFF0 +FFF0 +ENDCHAR +STARTCHAR 0117 +ENCODING 279 +SWIDTH 540 0 +DWIDTH 12 0 +BBX 10 15 1 0 +BITMAP +0C00 +0C00 +0000 +1E00 +7F80 +6180 +C0C0 +FFC0 +FFC0 +C000 +C000 +E0C0 +7180 +3F80 +1E00 +ENDCHAR +STARTCHAR 0118 +ENCODING 280 +SWIDTH 675 0 +DWIDTH 15 0 +BBX 12 20 2 -4 +BITMAP +FFF0 +FFF0 +C000 +C000 +C000 +C000 +C000 +FFE0 +FFE0 +C000 +C000 +C000 +C000 +C000 +FFF0 +FFF0 +0020 +0040 +0040 +0070 +ENDCHAR +STARTCHAR 0119 +ENCODING 281 +SWIDTH 540 0 +DWIDTH 12 0 +BBX 10 16 1 -4 +BITMAP +1E00 +7F80 +6180 +C0C0 +FFC0 +FFC0 +C000 +C000 +E0C0 +7180 +3F80 +1E00 +0200 +0400 +0400 +0700 +ENDCHAR +STARTCHAR 011A +ENCODING 282 +SWIDTH 675 0 +DWIDTH 15 0 +BBX 12 20 2 0 +BITMAP +1980 +0F00 +0600 +0000 +FFF0 +FFF0 +C000 +C000 +C000 +C000 +C000 +FFE0 +FFE0 +C000 +C000 +C000 +C000 +C000 +FFF0 +FFF0 +ENDCHAR +STARTCHAR 011B +ENCODING 283 +SWIDTH 540 0 +DWIDTH 12 0 +BBX 10 16 1 0 +BITMAP +3300 +1E00 +0C00 +0000 +1E00 +7F80 +6180 +C0C0 +FFC0 +FFC0 +C000 +C000 +E0C0 +7180 +3F80 +1E00 +ENDCHAR +STARTCHAR 011C +ENCODING 284 +SWIDTH 765 0 +DWIDTH 17 0 +BBX 15 20 1 0 +BITMAP +0180 +03C0 +0660 +0000 +07E0 +1FF8 +381C +700C +6006 +C000 +C000 +C000 +C0FE +C0FE +C006 +6006 +7006 +381E +1FFC +07E0 +ENDCHAR +STARTCHAR 011D +ENCODING 285 +SWIDTH 540 0 +DWIDTH 12 0 +BBX 10 20 1 -4 +BITMAP +0C00 +1E00 +3300 +0000 +1EC0 +7FC0 +61C0 +E0C0 +C0C0 +C0C0 +C0C0 +C0C0 +C1C0 +61C0 +7FC0 +1EC0 +00C0 +C180 +FF80 +3E00 +ENDCHAR +STARTCHAR 011E +ENCODING 286 +SWIDTH 765 0 +DWIDTH 17 0 +BBX 15 20 1 0 +BITMAP +0410 +0630 +03E0 +0000 +07E0 +1FF8 +381C +700C +6006 +C000 +C000 +C000 +C0FE +C0FE +C006 +6006 +7006 +381E +1FFC +07E0 +ENDCHAR +STARTCHAR 011F +ENCODING 287 +SWIDTH 540 0 +DWIDTH 12 0 +BBX 10 20 1 -4 +BITMAP +2080 +3180 +1F00 +0000 +1EC0 +7FC0 +61C0 +E0C0 +C0C0 +C0C0 +C0C0 +C0C0 +C1C0 +61C0 +7FC0 +1EC0 +00C0 +C180 +FF80 +3E00 +ENDCHAR +STARTCHAR 0120 +ENCODING 288 +SWIDTH 765 0 +DWIDTH 17 0 +BBX 15 19 1 0 +BITMAP +0180 +0180 +0000 +07E0 +1FF8 +381C +700C +6006 +C000 +C000 +C000 +C0FE +C0FE +C006 +6006 +7006 +381E +1FFC +07E0 +ENDCHAR +STARTCHAR 0121 +ENCODING 289 +SWIDTH 540 0 +DWIDTH 12 0 +BBX 10 19 1 -4 +BITMAP +0C00 +0C00 +0000 +1EC0 +7FC0 +61C0 +E0C0 +C0C0 +C0C0 +C0C0 +C0C0 +C1C0 +61C0 +7FC0 +1EC0 +00C0 +C180 +FF80 +3E00 +ENDCHAR +STARTCHAR 0122 +ENCODING 290 +SWIDTH 765 0 +DWIDTH 17 0 +BBX 15 23 1 -7 +BITMAP +07E0 +1FF8 +381C +700C +6006 +C000 +C000 +C000 +C0FE +C0FE +C006 +6006 +7006 +381E +1FFC +07E0 +0000 +0000 +00C0 +00C0 +0040 +00C0 +0080 +ENDCHAR +STARTCHAR 0123 +ENCODING 291 +SWIDTH 540 0 +DWIDTH 12 0 +BBX 10 22 1 -4 +BITMAP +0400 +0800 +0800 +0C00 +0C00 +0000 +1EC0 +7FC0 +61C0 +E1C0 +C0C0 +C0C0 +C0C0 +C0C0 +C1C0 +61C0 +7FC0 +1EC0 +00C0 +C180 +FF80 +3E00 +ENDCHAR +STARTCHAR 0124 +ENCODING 292 +SWIDTH 720 0 +DWIDTH 16 0 +BBX 12 20 2 0 +BITMAP +0600 +0F00 +1980 +0000 +C030 +C030 +C030 +C030 +C030 +C030 +C030 +FFF0 +FFF0 +C030 +C030 +C030 +C030 +C030 +C030 +C030 +ENDCHAR +STARTCHAR 0125 +ENCODING 293 +SWIDTH 495 0 +DWIDTH 11 0 +BBX 9 20 1 0 +BITMAP +0C00 +1E00 +3300 +0000 +C000 +C000 +C000 +C000 +DE00 +FF00 +E380 +C180 +C180 +C180 +C180 +C180 +C180 +C180 +C180 +C180 +ENDCHAR +STARTCHAR 0126 +ENCODING 294 +SWIDTH 720 0 +DWIDTH 16 0 +BBX 14 16 1 0 +BITMAP +6018 +6018 +FFFC +FFFC +6018 +6018 +7FF8 +7FF8 +6018 +6018 +6018 +6018 +6018 +6018 +6018 +6018 +ENDCHAR +STARTCHAR 0127 +ENCODING 295 +SWIDTH 495 0 +DWIDTH 11 0 +BBX 10 16 0 0 +BITMAP +6000 +FE00 +FE00 +6000 +6F00 +7F80 +70C0 +60C0 +60C0 +60C0 +60C0 +60C0 +60C0 +60C0 +60C0 +60C0 +ENDCHAR +STARTCHAR 0128 +ENCODING 296 +SWIDTH 270 0 +DWIDTH 6 0 +BBX 7 20 0 0 +BITMAP +76 +FE +DC +00 +30 +30 +30 +30 +30 +30 +30 +30 +30 +30 +30 +30 +30 +30 +30 +30 +ENDCHAR +STARTCHAR 0129 +ENCODING 297 +SWIDTH 270 0 +DWIDTH 6 0 +BBX 7 16 0 0 +BITMAP +76 +FE +DC +00 +30 +30 +30 +30 +30 +30 +30 +30 +30 +30 +30 +30 +ENDCHAR +STARTCHAR 012A +ENCODING 298 +SWIDTH 270 0 +DWIDTH 6 0 +BBX 6 19 0 0 +BITMAP +FC +FC +00 +30 +30 +30 +30 +30 +30 +30 +30 +30 +30 +30 +30 +30 +30 +30 +30 +ENDCHAR +STARTCHAR 012B +ENCODING 299 +SWIDTH 270 0 +DWIDTH 6 0 +BBX 6 15 0 0 +BITMAP +FC +FC +00 +30 +30 +30 +30 +30 +30 +30 +30 +30 +30 +30 +30 +ENDCHAR +STARTCHAR 012C +ENCODING 300 +SWIDTH 270 0 +DWIDTH 6 0 +BBX 7 20 0 0 +BITMAP +82 +C6 +7C +00 +30 +30 +30 +30 +30 +30 +30 +30 +30 +30 +30 +30 +30 +30 +30 +30 +ENDCHAR +STARTCHAR 012D +ENCODING 301 +SWIDTH 270 0 +DWIDTH 6 0 +BBX 7 16 0 0 +BITMAP +82 +C6 +7C +00 +30 +30 +30 +30 +30 +30 +30 +30 +30 +30 +30 +30 +ENDCHAR +STARTCHAR 012E +ENCODING 302 +SWIDTH 270 0 +DWIDTH 6 0 +BBX 3 20 1 -4 +BITMAP +60 +60 +60 +60 +60 +60 +60 +60 +60 +60 +60 +60 +60 +60 +60 +60 +40 +80 +80 +E0 +ENDCHAR +STARTCHAR 012F +ENCODING 303 +SWIDTH 225 0 +DWIDTH 5 0 +BBX 3 20 0 -4 +BITMAP +60 +60 +00 +00 +60 +60 +60 +60 +60 +60 +60 +60 +60 +60 +60 +60 +40 +80 +80 +E0 +ENDCHAR +STARTCHAR 0130 +ENCODING 304 +SWIDTH 270 0 +DWIDTH 6 0 +BBX 2 19 2 0 +BITMAP +C0 +C0 +00 +C0 +C0 +C0 +C0 +C0 +C0 +C0 +C0 +C0 +C0 +C0 +C0 +C0 +C0 +C0 +C0 +ENDCHAR +STARTCHAR 0131 +ENCODING 305 +SWIDTH 270 0 +DWIDTH 6 0 +BBX 2 12 2 0 +BITMAP +C0 +C0 +C0 +C0 +C0 +C0 +C0 +C0 +C0 +C0 +C0 +C0 +ENDCHAR +STARTCHAR 0132 +ENCODING 306 +SWIDTH 720 0 +DWIDTH 16 0 +BBX 12 16 2 0 +BITMAP +C030 +C030 +C030 +C030 +C030 +C030 +C030 +C030 +C030 +C030 +C030 +CC30 +CC30 +CE70 +C7E0 +C3C0 +ENDCHAR +STARTCHAR 0133 +ENCODING 307 +SWIDTH 450 0 +DWIDTH 10 0 +BBX 7 20 1 -4 +BITMAP +C6 +C6 +00 +00 +C6 +C6 +C6 +C6 +C6 +C6 +C6 +C6 +C6 +C6 +C6 +C6 +06 +06 +1E +1C +ENDCHAR +STARTCHAR 0134 +ENCODING 308 +SWIDTH 495 0 +DWIDTH 11 0 +BBX 10 20 1 0 +BITMAP +0300 +0780 +0CC0 +0000 +0300 +0300 +0300 +0300 +0300 +0300 +0300 +0300 +0300 +0300 +0300 +C300 +C300 +E700 +7E00 +3C00 +ENDCHAR +STARTCHAR 0135 +ENCODING 309 +SWIDTH 180 0 +DWIDTH 4 0 +BBX 6 20 -1 -4 +BITMAP +30 +78 +CC +00 +30 +30 +30 +30 +30 +30 +30 +30 +30 +30 +30 +30 +30 +30 +F0 +E0 +ENDCHAR +STARTCHAR 0136 +ENCODING 310 +SWIDTH 675 0 +DWIDTH 15 0 +BBX 13 23 2 -7 +BITMAP +C070 +C0E0 +C1C0 +C380 +C700 +CE00 +DC00 +FC00 +FE00 +E700 +C380 +C1C0 +C0C0 +C0E0 +C070 +C038 +0000 +0000 +0600 +0600 +0200 +0600 +0400 +ENDCHAR +STARTCHAR 0137 +ENCODING 311 +SWIDTH 450 0 +DWIDTH 10 0 +BBX 9 23 1 -7 +BITMAP +C000 +C000 +C000 +C000 +C380 +C700 +CE00 +DC00 +F800 +FC00 +EC00 +CE00 +C600 +C700 +C300 +C380 +0000 +0000 +0C00 +0C00 +0400 +0C00 +0800 +ENDCHAR +STARTCHAR 0138 +ENCODING 312 +SWIDTH 495 0 +DWIDTH 11 0 +BBX 9 12 1 0 +BITMAP +C180 +C300 +C600 +CC00 +D800 +F800 +CC00 +C600 +C600 +C300 +C180 +C180 +ENDCHAR +STARTCHAR 0139 +ENCODING 313 +SWIDTH 540 0 +DWIDTH 12 0 +BBX 9 20 2 0 +BITMAP +1800 +3000 +2000 +0000 +C000 +C000 +C000 +C000 +C000 +C000 +C000 +C000 +C000 +C000 +C000 +C000 +C000 +C000 +FF80 +FF80 +ENDCHAR +STARTCHAR 013A +ENCODING 314 +SWIDTH 180 0 +DWIDTH 4 0 +BBX 3 20 1 0 +BITMAP +60 +C0 +80 +00 +C0 +C0 +C0 +C0 +C0 +C0 +C0 +C0 +C0 +C0 +C0 +C0 +C0 +C0 +C0 +C0 +ENDCHAR +STARTCHAR 013B +ENCODING 315 +SWIDTH 540 0 +DWIDTH 12 0 +BBX 9 23 2 -7 +BITMAP +C000 +C000 +C000 +C000 +C000 +C000 +C000 +C000 +C000 +C000 +C000 +C000 +C000 +C000 +FF80 +FF80 +0000 +0000 +0C00 +0C00 +0400 +0C00 +0800 +ENDCHAR +STARTCHAR 013C +ENCODING 316 +SWIDTH 180 0 +DWIDTH 4 0 +BBX 2 23 1 -7 +BITMAP +C0 +C0 +C0 +C0 +C0 +C0 +C0 +C0 +C0 +C0 +C0 +C0 +C0 +C0 +C0 +C0 +00 +00 +C0 +C0 +40 +C0 +80 +ENDCHAR +STARTCHAR 013D +ENCODING 317 +SWIDTH 540 0 +DWIDTH 12 0 +BBX 9 16 2 0 +BITMAP +C300 +C300 +C100 +C100 +C200 +C000 +C000 +C000 +C000 +C000 +C000 +C000 +C000 +C000 +FF80 +FF80 +ENDCHAR +STARTCHAR 013E +ENCODING 318 +SWIDTH 315 0 +DWIDTH 7 0 +BBX 5 16 2 0 +BITMAP +D8 +D8 +C8 +C8 +D0 +C0 +C0 +C0 +C0 +C0 +C0 +C0 +C0 +C0 +C0 +C0 +ENDCHAR +STARTCHAR 013F +ENCODING 319 +SWIDTH 540 0 +DWIDTH 12 0 +BBX 9 16 2 0 +BITMAP +C000 +C000 +C000 +C000 +C000 +C000 +C000 +C600 +C600 +C000 +C000 +C000 +C000 +C000 +FF80 +FF80 +ENDCHAR +STARTCHAR 0140 +ENCODING 320 +SWIDTH 315 0 +DWIDTH 7 0 +BBX 6 16 1 0 +BITMAP +C0 +C0 +C0 +C0 +C0 +C0 +C0 +CC +CC +C0 +C0 +C0 +C0 +C0 +C0 +C0 +ENDCHAR +STARTCHAR 0141 +ENCODING 321 +SWIDTH 540 0 +DWIDTH 12 0 +BBX 11 16 0 0 +BITMAP +3000 +3000 +3000 +3100 +3300 +3600 +3C00 +3800 +7000 +F000 +B000 +3000 +3000 +3000 +3FE0 +3FE0 +ENDCHAR +STARTCHAR 0142 +ENCODING 322 +SWIDTH 180 0 +DWIDTH 4 0 +BBX 4 16 0 0 +BITMAP +60 +60 +60 +60 +60 +70 +70 +60 +E0 +E0 +60 +60 +60 +60 +60 +60 +ENDCHAR +STARTCHAR 0143 +ENCODING 323 +SWIDTH 720 0 +DWIDTH 16 0 +BBX 12 20 2 0 +BITMAP +0300 +0600 +0400 +0000 +C030 +E030 +F030 +F030 +D830 +D830 +CC30 +C630 +C630 +C330 +C1B0 +C1B0 +C0F0 +C0F0 +C070 +C030 +ENDCHAR +STARTCHAR 0144 +ENCODING 324 +SWIDTH 495 0 +DWIDTH 11 0 +BBX 9 16 1 0 +BITMAP +0600 +0C00 +0800 +0000 +DE00 +FF00 +E380 +C180 +C180 +C180 +C180 +C180 +C180 +C180 +C180 +C180 +ENDCHAR +STARTCHAR 0145 +ENCODING 325 +SWIDTH 720 0 +DWIDTH 16 0 +BBX 12 23 2 -7 +BITMAP +C030 +E030 +F030 +F030 +D830 +D830 +CC30 +C630 +C630 +C330 +C1B0 +C1B0 +C0F0 +C0F0 +C070 +C030 +0000 +0000 +0600 +0600 +0200 +0600 +0400 +ENDCHAR +STARTCHAR 0146 +ENCODING 326 +SWIDTH 495 0 +DWIDTH 11 0 +BBX 9 19 1 -7 +BITMAP +DE00 +FF00 +E380 +C180 +C180 +C180 +C180 +C180 +C180 +C180 +C180 +C180 +0000 +0000 +0C00 +0C00 +0400 +0C00 +0800 +ENDCHAR +STARTCHAR 0147 +ENCODING 327 +SWIDTH 720 0 +DWIDTH 16 0 +BBX 12 20 2 0 +BITMAP +1980 +0F00 +0600 +0000 +C030 +E030 +F030 +F030 +D830 +D830 +CC30 +C630 +C630 +C330 +C1B0 +C1B0 +C0F0 +C0F0 +C070 +C030 +ENDCHAR +STARTCHAR 0148 +ENCODING 328 +SWIDTH 495 0 +DWIDTH 11 0 +BBX 9 16 1 0 +BITMAP +3300 +1E00 +0C00 +0000 +DE00 +FF00 +E380 +C180 +C180 +C180 +C180 +C180 +C180 +C180 +C180 +C180 +ENDCHAR +STARTCHAR 0149 +ENCODING 329 +SWIDTH 585 0 +DWIDTH 13 0 +BBX 12 16 0 0 +BITMAP +C000 +C000 +4000 +4000 +9BC0 +1FE0 +1C70 +1830 +1830 +1830 +1830 +1830 +1830 +1830 +1830 +1830 +ENDCHAR +STARTCHAR 014A +ENCODING 330 +SWIDTH 720 0 +DWIDTH 16 0 +BBX 13 16 2 0 +BITMAP +CFC0 +DFE0 +F070 +E030 +C038 +C018 +C018 +C018 +C018 +C018 +C018 +C018 +C030 +C070 +C7E0 +C3C0 +ENDCHAR +STARTCHAR 014B +ENCODING 331 +SWIDTH 540 0 +DWIDTH 12 0 +BBX 10 16 1 -4 +BITMAP +DF00 +FF80 +E1C0 +C0C0 +C0C0 +C0C0 +C0C0 +C0C0 +C0C0 +C0C0 +C0C0 +C0C0 +00C0 +00C0 +03C0 +0380 +ENDCHAR +STARTCHAR 014C +ENCODING 332 +SWIDTH 765 0 +DWIDTH 17 0 +BBX 15 19 1 0 +BITMAP +07E0 +07E0 +0000 +07C0 +1FF0 +3838 +701C +600C +C006 +C006 +C006 +C006 +C006 +C006 +600C +701C +3838 +1FF0 +07C0 +ENDCHAR +STARTCHAR 014D +ENCODING 333 +SWIDTH 585 0 +DWIDTH 13 0 +BBX 11 15 1 0 +BITMAP +3F00 +3F00 +0000 +1F00 +3F80 +71C0 +E0E0 +C060 +C060 +C060 +C060 +E0E0 +71C0 +3F80 +1F00 +ENDCHAR +STARTCHAR 014E +ENCODING 334 +SWIDTH 765 0 +DWIDTH 17 0 +BBX 15 20 1 0 +BITMAP +0820 +0C60 +07C0 +0000 +07C0 +1FF0 +3838 +701C +600C +C006 +C006 +C006 +C006 +C006 +C006 +600C +701C +3838 +1FF0 +07C0 +ENDCHAR +STARTCHAR 014F +ENCODING 335 +SWIDTH 585 0 +DWIDTH 13 0 +BBX 11 16 1 0 +BITMAP +2080 +3180 +1F00 +0000 +1F00 +3F80 +71C0 +E0E0 +C060 +C060 +C060 +C060 +E0E0 +71C0 +3F80 +1F00 +ENDCHAR +STARTCHAR 0150 +ENCODING 336 +SWIDTH 765 0 +DWIDTH 17 0 +BBX 15 20 1 0 +BITMAP +0660 +0CC0 +0CC0 +0000 +07C0 +1FF0 +3838 +701C +600C +C006 +C006 +C006 +C006 +C006 +C006 +600C +701C +3838 +1FF0 +07C0 +ENDCHAR +STARTCHAR 0151 +ENCODING 337 +SWIDTH 585 0 +DWIDTH 13 0 +BBX 11 16 1 0 +BITMAP +1980 +3300 +3300 +0000 +1F00 +3F80 +71C0 +E0E0 +C060 +C060 +C060 +C060 +E0E0 +71C0 +3F80 +1F00 +ENDCHAR +STARTCHAR 0152 +ENCODING 338 +SWIDTH 990 0 +DWIDTH 22 0 +BBX 20 16 1 0 +BITMAP +0F9FF0 +3FDFF0 +787800 +603800 +603800 +C01800 +C01800 +C01FE0 +C01FE0 +C01800 +C01800 +603800 +603800 +707800 +3FDFF0 +0F9FF0 +ENDCHAR +STARTCHAR 0153 +ENCODING 339 +SWIDTH 945 0 +DWIDTH 21 0 +BBX 19 12 1 0 +BITMAP +1F0F00 +3F9F80 +71F0C0 +E0E060 +C06060 +C07FE0 +C07FE0 +C06000 +E0E060 +71F0C0 +3F9FC0 +1F0F80 +ENDCHAR +STARTCHAR 0154 +ENCODING 340 +SWIDTH 720 0 +DWIDTH 16 0 +BBX 12 20 2 0 +BITMAP +0600 +0C00 +0800 +0000 +FFC0 +FFE0 +C070 +C030 +C030 +C030 +C070 +FFE0 +FFC0 +C300 +C180 +C1C0 +C0E0 +C060 +C070 +C030 +ENDCHAR +STARTCHAR 0155 +ENCODING 341 +SWIDTH 315 0 +DWIDTH 7 0 +BBX 6 16 1 0 +BITMAP +0C +18 +10 +00 +DC +FC +E0 +C0 +C0 +C0 +C0 +C0 +C0 +C0 +C0 +C0 +ENDCHAR +STARTCHAR 0156 +ENCODING 342 +SWIDTH 720 0 +DWIDTH 16 0 +BBX 12 23 2 -7 +BITMAP +FFC0 +FFE0 +C070 +C030 +C030 +C030 +C070 +FFE0 +FFC0 +C300 +C180 +C1C0 +C0E0 +C060 +C070 +C030 +0000 +0000 +0600 +0600 +0200 +0600 +0400 +ENDCHAR +STARTCHAR 0157 +ENCODING 343 +SWIDTH 315 0 +DWIDTH 7 0 +BBX 6 19 1 -7 +BITMAP +DC +FC +E0 +C0 +C0 +C0 +C0 +C0 +C0 +C0 +C0 +C0 +00 +00 +C0 +C0 +40 +C0 +80 +ENDCHAR +STARTCHAR 0158 +ENCODING 344 +SWIDTH 720 0 +DWIDTH 16 0 +BBX 12 20 2 0 +BITMAP +3300 +1E00 +0C00 +0000 +FFC0 +FFE0 +C070 +C030 +C030 +C030 +C070 +FFE0 +FFC0 +C300 +C180 +C1C0 +C0E0 +C060 +C070 +C030 +ENDCHAR +STARTCHAR 0159 +ENCODING 345 +SWIDTH 315 0 +DWIDTH 7 0 +BBX 6 16 1 0 +BITMAP +CC +78 +30 +00 +DC +FC +E0 +C0 +C0 +C0 +C0 +C0 +C0 +C0 +C0 +C0 +ENDCHAR +STARTCHAR 015A +ENCODING 346 +SWIDTH 675 0 +DWIDTH 15 0 +BBX 13 20 1 0 +BITMAP +0180 +0300 +0200 +0000 +0FC0 +3FE0 +7070 +6018 +6018 +7000 +3C00 +1FC0 +03F0 +0038 +C018 +C018 +6018 +7070 +3FF0 +0FC0 +ENDCHAR +STARTCHAR 015B +ENCODING 347 +SWIDTH 495 0 +DWIDTH 11 0 +BBX 9 16 1 0 +BITMAP +0600 +0C00 +0800 +0000 +3E00 +7F00 +C180 +C000 +F000 +7E00 +1F80 +0380 +0180 +C380 +7F00 +3E00 +ENDCHAR +STARTCHAR 015C +ENCODING 348 +SWIDTH 675 0 +DWIDTH 15 0 +BBX 13 20 1 0 +BITMAP +0300 +0780 +0CC0 +0000 +0FC0 +3FE0 +7070 +6018 +6018 +7000 +3C00 +1FC0 +03F0 +0038 +C018 +C018 +6018 +7070 +3FF0 +0FC0 +ENDCHAR +STARTCHAR 015D +ENCODING 349 +SWIDTH 495 0 +DWIDTH 11 0 +BBX 9 16 1 0 +BITMAP +0C00 +1E00 +3300 +0000 +3E00 +7F00 +C180 +C000 +F000 +7E00 +1F80 +0380 +0180 +C380 +7F00 +3E00 +ENDCHAR +STARTCHAR 015E +ENCODING 350 +SWIDTH 675 0 +DWIDTH 15 0 +BBX 13 20 1 -4 +BITMAP +0FC0 +3FE0 +7070 +6018 +6018 +7000 +3C00 +1FC0 +03F0 +0038 +C018 +C018 +6018 +7070 +3FF0 +0FC0 +0200 +0300 +0180 +0F00 +ENDCHAR +STARTCHAR 015F +ENCODING 351 +SWIDTH 495 0 +DWIDTH 11 0 +BBX 9 16 1 -4 +BITMAP +3E00 +7F00 +C180 +C000 +F000 +7E00 +1F80 +0380 +0180 +C380 +7F00 +3E00 +0800 +0C00 +0600 +3C00 +ENDCHAR +STARTCHAR 0160 +ENCODING 352 +SWIDTH 675 0 +DWIDTH 15 0 +BBX 13 20 1 0 +BITMAP +0CC0 +0780 +0300 +0000 +0FC0 +3FE0 +7070 +6018 +6018 +7000 +3C00 +1FC0 +03F0 +0038 +C018 +C018 +6018 +7070 +3FF0 +0FC0 +ENDCHAR +STARTCHAR 0161 +ENCODING 353 +SWIDTH 495 0 +DWIDTH 11 0 +BBX 9 16 1 0 +BITMAP +3300 +1E00 +0C00 +0000 +3E00 +7F00 +C180 +C000 +F000 +7E00 +1F80 +0380 +0180 +C380 +7F00 +3E00 +ENDCHAR +STARTCHAR 0162 +ENCODING 354 +SWIDTH 630 0 +DWIDTH 14 0 +BBX 12 20 1 -4 +BITMAP +FFF0 +FFF0 +0600 +0600 +0600 +0600 +0600 +0600 +0600 +0600 +0600 +0600 +0600 +0600 +0600 +0600 +0200 +0300 +0180 +0F00 +ENDCHAR +STARTCHAR 0163 +ENCODING 355 +SWIDTH 270 0 +DWIDTH 6 0 +BBX 6 20 0 -4 +BITMAP +20 +60 +60 +60 +F8 +F8 +60 +60 +60 +60 +60 +60 +60 +60 +78 +38 +10 +18 +0C +78 +ENDCHAR +STARTCHAR 0164 +ENCODING 356 +SWIDTH 630 0 +DWIDTH 14 0 +BBX 12 20 1 0 +BITMAP +1980 +0F00 +0600 +0000 +FFF0 +FFF0 +0600 +0600 +0600 +0600 +0600 +0600 +0600 +0600 +0600 +0600 +0600 +0600 +0600 +0600 +ENDCHAR +STARTCHAR 0165 +ENCODING 357 +SWIDTH 360 0 +DWIDTH 8 0 +BBX 7 16 1 0 +BITMAP +26 +66 +62 +62 +FC +F8 +60 +60 +60 +60 +60 +60 +60 +60 +78 +38 +ENDCHAR +STARTCHAR 0166 +ENCODING 358 +SWIDTH 585 0 +DWIDTH 13 0 +BBX 12 16 1 0 +BITMAP +FFF0 +FFF0 +0600 +0600 +0600 +0600 +0600 +0600 +3FC0 +3FC0 +0600 +0600 +0600 +0600 +0600 +0600 +ENDCHAR +STARTCHAR 0167 +ENCODING 359 +SWIDTH 270 0 +DWIDTH 6 0 +BBX 5 15 0 0 +BITMAP +60 +60 +60 +F8 +F8 +60 +60 +60 +F8 +F8 +60 +60 +60 +78 +38 +ENDCHAR +STARTCHAR 0168 +ENCODING 360 +SWIDTH 720 0 +DWIDTH 16 0 +BBX 12 20 2 0 +BITMAP +0EC0 +1FC0 +1B80 +0000 +C030 +C030 +C030 +C030 +C030 +C030 +C030 +C030 +C030 +C030 +C030 +C030 +E070 +70E0 +3FC0 +1F80 +ENDCHAR +STARTCHAR 0169 +ENCODING 361 +SWIDTH 495 0 +DWIDTH 11 0 +BBX 9 16 1 0 +BITMAP +3B00 +7F00 +6E00 +0000 +C180 +C180 +C180 +C180 +C180 +C180 +C180 +C180 +C180 +E380 +7F80 +3D80 +ENDCHAR +STARTCHAR 016A +ENCODING 362 +SWIDTH 720 0 +DWIDTH 16 0 +BBX 12 19 2 0 +BITMAP +1F80 +1F80 +0000 +C030 +C030 +C030 +C030 +C030 +C030 +C030 +C030 +C030 +C030 +C030 +C030 +E070 +70E0 +3FC0 +1F80 +ENDCHAR +STARTCHAR 016B +ENCODING 363 +SWIDTH 495 0 +DWIDTH 11 0 +BBX 9 15 1 0 +BITMAP +3F00 +3F00 +0000 +C180 +C180 +C180 +C180 +C180 +C180 +C180 +C180 +C180 +E380 +7F80 +3D80 +ENDCHAR +STARTCHAR 016C +ENCODING 364 +SWIDTH 720 0 +DWIDTH 16 0 +BBX 12 20 2 0 +BITMAP +1040 +18C0 +0F80 +0000 +C030 +C030 +C030 +C030 +C030 +C030 +C030 +C030 +C030 +C030 +C030 +C030 +E070 +70E0 +3FC0 +1F80 +ENDCHAR +STARTCHAR 016D +ENCODING 365 +SWIDTH 495 0 +DWIDTH 11 0 +BBX 9 16 1 0 +BITMAP +2080 +3180 +1F00 +0000 +C180 +C180 +C180 +C180 +C180 +C180 +C180 +C180 +C180 +E380 +7F80 +3D80 +ENDCHAR +STARTCHAR 016E +ENCODING 366 +SWIDTH 720 0 +DWIDTH 16 0 +BBX 12 20 2 0 +BITMAP +0700 +0880 +0880 +0880 +C730 +C030 +C030 +C030 +C030 +C030 +C030 +C030 +C030 +C030 +C030 +C030 +E070 +70E0 +3FC0 +1F80 +ENDCHAR +STARTCHAR 016F +ENCODING 367 +SWIDTH 495 0 +DWIDTH 11 0 +BBX 9 18 1 0 +BITMAP +1C00 +2200 +2200 +2200 +1C00 +0000 +C180 +C180 +C180 +C180 +C180 +C180 +C180 +C180 +C180 +E380 +7F80 +3D80 +ENDCHAR +STARTCHAR 0170 +ENCODING 368 +SWIDTH 720 0 +DWIDTH 16 0 +BBX 12 20 2 0 +BITMAP +0CC0 +1980 +1980 +0000 +C030 +C030 +C030 +C030 +C030 +C030 +C030 +C030 +C030 +C030 +C030 +C030 +E070 +70E0 +3FC0 +1F80 +ENDCHAR +STARTCHAR 0171 +ENCODING 369 +SWIDTH 495 0 +DWIDTH 11 0 +BBX 9 16 1 0 +BITMAP +3300 +6600 +6600 +0000 +C180 +C180 +C180 +C180 +C180 +C180 +C180 +C180 +C180 +E380 +7F80 +3D80 +ENDCHAR +STARTCHAR 0172 +ENCODING 370 +SWIDTH 720 0 +DWIDTH 16 0 +BBX 12 20 2 -4 +BITMAP +C030 +C030 +C030 +C030 +C030 +C030 +C030 +C030 +C030 +C030 +C030 +C030 +E070 +70E0 +3FC0 +1F80 +0100 +0200 +0200 +0380 +ENDCHAR +STARTCHAR 0173 +ENCODING 371 +SWIDTH 495 0 +DWIDTH 11 0 +BBX 9 16 1 -4 +BITMAP +C180 +C180 +C180 +C180 +C180 +C180 +C180 +C180 +C180 +E380 +7F80 +3D80 +0100 +0200 +0200 +0380 +ENDCHAR +STARTCHAR 0174 +ENCODING 372 +SWIDTH 945 0 +DWIDTH 21 0 +BBX 21 20 0 0 +BITMAP +003000 +007800 +00CC00 +000000 +C07018 +C07018 +60D830 +60D830 +60D830 +60D830 +318C60 +318C60 +318C60 +318C60 +1B06C0 +1B06C0 +1B06C0 +1B06C0 +0E0380 +0E0380 +ENDCHAR +STARTCHAR 0175 +ENCODING 373 +SWIDTH 675 0 +DWIDTH 15 0 +BBX 15 16 0 0 +BITMAP +0180 +03C0 +0660 +0000 +C106 +C386 +C386 +628C +66CC +26C8 +36D8 +36D8 +1450 +1C70 +1C70 +0C60 +ENDCHAR +STARTCHAR 0176 +ENCODING 374 +SWIDTH 630 0 +DWIDTH 14 0 +BBX 14 20 0 0 +BITMAP +0300 +0780 +0CC0 +0000 +E01C +6018 +3030 +3870 +1860 +0CC0 +0FC0 +0780 +0300 +0300 +0300 +0300 +0300 +0300 +0300 +0300 +ENDCHAR +STARTCHAR 0177 +ENCODING 375 +SWIDTH 495 0 +DWIDTH 11 0 +BBX 11 20 0 -4 +BITMAP +0600 +0F00 +1980 +0000 +C060 +C060 +60C0 +60C0 +30C0 +3180 +1980 +1980 +0F00 +0F00 +0700 +0600 +0E00 +0C00 +7C00 +7800 +ENDCHAR +STARTCHAR 0178 +ENCODING 376 +SWIDTH 630 0 +DWIDTH 14 0 +BBX 14 19 0 0 +BITMAP +0CC0 +0CC0 +0000 +E01C +6018 +3030 +3870 +1860 +0CC0 +0FC0 +0780 +0300 +0300 +0300 +0300 +0300 +0300 +0300 +0300 +ENDCHAR +STARTCHAR 0179 +ENCODING 377 +SWIDTH 585 0 +DWIDTH 13 0 +BBX 12 20 0 0 +BITMAP +0180 +0300 +0200 +0000 +7FF0 +7FF0 +0060 +00C0 +00C0 +0180 +0300 +0600 +0600 +0C00 +1800 +3000 +3000 +6000 +FFF0 +FFF0 +ENDCHAR +STARTCHAR 017A +ENCODING 378 +SWIDTH 495 0 +DWIDTH 11 0 +BBX 10 16 0 0 +BITMAP +0300 +0600 +0400 +0000 +7FC0 +7FC0 +01C0 +0380 +0700 +0E00 +1C00 +3800 +7000 +E000 +FFC0 +FFC0 +ENDCHAR +STARTCHAR 017B +ENCODING 379 +SWIDTH 585 0 +DWIDTH 13 0 +BBX 12 19 0 0 +BITMAP +0300 +0300 +0000 +7FF0 +7FF0 +0060 +00C0 +00C0 +0180 +0300 +0600 +0600 +0C00 +1800 +3000 +3000 +6000 +FFF0 +FFF0 +ENDCHAR +STARTCHAR 017C +ENCODING 380 +SWIDTH 495 0 +DWIDTH 11 0 +BBX 10 15 0 0 +BITMAP +0600 +0600 +0000 +7FC0 +7FC0 +01C0 +0380 +0700 +0E00 +1C00 +3800 +7000 +E000 +FFC0 +FFC0 +ENDCHAR +STARTCHAR 017D +ENCODING 381 +SWIDTH 585 0 +DWIDTH 13 0 +BBX 12 20 0 0 +BITMAP +0CC0 +0780 +0300 +0000 +7FF0 +7FF0 +0060 +00C0 +00C0 +0180 +0300 +0600 +0600 +0C00 +1800 +3000 +3000 +6000 +FFF0 +FFF0 +ENDCHAR +STARTCHAR 017E +ENCODING 382 +SWIDTH 495 0 +DWIDTH 11 0 +BBX 10 16 0 0 +BITMAP +1980 +0F00 +0600 +0000 +7FC0 +7FC0 +01C0 +0380 +0700 +0E00 +1C00 +3800 +7000 +E000 +FFC0 +FFC0 +ENDCHAR +ENDFONT diff --git a/examples/fonts/Arial-16.bdf.license b/examples/fonts/Arial-16.bdf.license new file mode 100644 index 0000000..db3e0e3 --- /dev/null +++ b/examples/fonts/Arial-16.bdf.license @@ -0,0 +1,3 @@ +# SPDX-FileCopyrightText: 2017 The Monotype Corporation. All Rights Reserved. Hebrew OpenType Layout logic copyright © 2003 & 2007, Ralph Hancock & John Hudson. + +# SPDX-License-Identifier: MIT diff --git a/examples/hotplug_sensor_examples/displayio_layout_hotplug_rtc.py b/examples/hotplug_sensor_examples/displayio_layout_hotplug_rtc.py new file mode 100644 index 0000000..1268b53 --- /dev/null +++ b/examples/hotplug_sensor_examples/displayio_layout_hotplug_rtc.py @@ -0,0 +1,639 @@ +# SPDX-FileCopyrightText: 2022 PaulskPt +# +# SPDX-License-Identifier: MIT +""" +Make a PageLayout and illustrate all of it's features +""" + +import time + +import adafruit_tmp117 +import board +import displayio +import terminalio +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 adafruit_displayio_layout.layouts.tab_layout import TabLayout + +# +-------------------------------------------------------+ +# | Definition for variables in the past defined as global| +# +-------------------------------------------------------+ +# The gVars class is created +# to elminate the need for global variables. + + +class gVars: + def __init__(self): + self.gVarsDict = { + 0: "my_debug", + 1: "rtc", + 2: "temp_sensor", + 3: "lStart", + 4: "o_secs", + 5: "c_secs", + 6: "dt_refresh", + 7: "sDT_old", + 8: "t0", + 9: "t1", + 10: "t2", + 11: "default_dt", + 12: "pge3_lbl_dflt", + 13: "pge4_lbl_dflt", + 14: "online_time_present", + 15: "temp_in_REPL", + 16: "old_temp", + 17: "use_ntp", + 18: "use_txt_in_month", + 19: "use_usa_notation", + 20: "content_sensor_idx", + 21: "temp_in_fahrenheit", + } + + self.gVars_rDict = { + "my_debug": 0, + "rtc": 1, + "temp_sensor": 2, + "lStart": 3, + "o_secs": 4, + "c_secs": 5, + "dt_refresh": 6, + "sDT_old": 7, + "t0": 8, + "t1": 9, + "t2": 10, + "default_dt": 11, + "pge3_lbl_dflt": 12, + "pge4_lbl_dflt": 13, + "online_time_present": 14, + "temp_in_REPL": 15, + "old_temp": 16, + "use_ntp": 17, + "use_txt_in_month": 18, + "use_usa_notation": 19, + "content_sensor_idx": 20, + "temp_in_fahrenheit": 21, + } + + self.g_vars = {} + + # self.clean() + + def write(self, s, value): + if isinstance(s, str): + if s in self.gVars_rDict: + n = self.gVars_rDict[s] + # print("myVars.write() \'{:" ">20s}\'found in self.gVars_rDict, + # key: {}".format(s, n)) + self.g_vars[n] = value + else: + raise KeyError("variable '{:" ">20s}' not found in self.gVars_rDict".format(s)) + else: + raise TypeError(f"myVars.write(): param s expected str, {type(s)} received") + + def read(self, s): + RetVal = None + if isinstance(s, str): + if s in self.gVars_rDict: + n = self.gVars_rDict[s] + if n in self.g_vars: + RetVal = self.g_vars[n] + return RetVal + + def clean(self): + self.g_vars = { + 0: None, + 1: None, + 2: None, + 3: None, + 4: None, + 5: None, + 6: None, + 7: None, + 8: None, + 9: None, + 10: None, + 11: None, + 12: None, + 13: None, + 14: None, + 15: None, + 16: None, + 17: None, + 18: None, + 19: None, + 20: None, + 21: None, + } + + def list(self): + for i in range(0, len(self.g_vars) - 1): + print( + "self.g_vars['{:" ">20s}'] = {}".format( + self.gVarsDict[i], self.g_vars[i] if i in self.g_vars else "None" + ) + ) + + +# ---------- End of class gVars ------------------------ + +myVars = gVars() # create an instance of the gVars class + +myVars.write("my_debug", False) + +# Adjust here the date and time that you want the RTC to be set at start: +myVars.write("default_dt", time.struct_time((2022, 5, 14, 19, 42, 0, 5, -1, -1))) + +months = { + 0: "Dum", + 1: "Jan", + 2: "Feb", + 3: "Mar", + 4: "Apr", + 5: "May", + 6: "Jun", + 7: "Jul", + 8: "Aug", + 9: "Sep", + 10: "Oct", + 11: "Nov", + 12: "Dec", +} + +i2c = board.I2C() + +if myVars.read("my_debug"): + while not i2c.try_lock(): + pass + try: + while True: + print( + "I2C addresses found:", + [hex(device_address) for device_address in i2c.scan()], + ) + time.sleep(2) + break + finally: # unlock the i2c bus when ctrl-c'ing out of the loop + i2c.unlock() + +# -------------- Setting myVars elements ---------------------------------- +myVars.write("rtc", None) +myVars.write("temp_sensor", None) +myVars.write("lStart", True) +myVars.write("o_secs", 0) # old seconds +myVars.write("c_secs", 0) # current seconds +# dt_refresh is used to flag when more or less static elements +# in datetime stamp have to be refreshed +myVars.write("dt_refresh", True) +myVars.write("sDT_old", "") +myVars.write("t0", None) +myVars.write("t1", None) +myVars.write("t2", None) +# default_dt already set above +myVars.write("pge3_lbl_dflt", "The third page is fun!") +myVars.write("pge4_lbl_dflt", "The fourth page is where it's at") +myVars.write("online_time_present", False) +myVars.write("temp_in_REPL", False) +myVars.write("old_temp", 0.00) +myVars.write("use_txt_in_month", True) +myVars.write("use_usa_notation", True) +myVars.write("use_ntp", False) +myVars.write("content_sensor_idx", None) +myVars.write("temp_in_fahrenheit", False) +# ------------------------------------------------------------------------- +if myVars.read("my_debug"): + # print list of all variables in myVars + myVars.list() + +# degs_sign = chr(186) # I preferred the real degrees sign which is: chr(176) +# ----------------------------------- + +# built-in display +display = board.DISPLAY +# display.rotation = 90 +display.rotation = 0 + +# create and show main_group +main_group = displayio.Group() +display.root_group = main_group + +# fon.gvars bitmap_font.load_font("fonts/Helvetica-Bold-16.bdf") +font_arial = bitmap_font.load_font("/fonts/Arial-16.bdf") +font_term = terminalio.FONT + +# create the page layout +test_page_layout = TabLayout( + x=0, + y=0, + display=board.DISPLAY, + tab_text_scale=2, + custom_font=font_term, + inactive_tab_spritesheet="bmps/inactive_tab_sprite.bmp", + showing_tab_spritesheet="bmps/active_tab_sprite.bmp", + showing_tab_text_color=0x00AA59, + inactive_tab_text_color=0xEEEEEE, + inactive_tab_transparent_indexes=(0, 1), + showing_tab_transparent_indexes=(0, 1), + tab_count=4, +) + +# make 3 pages of content +pge1_group = displayio.Group() +pge2_group = displayio.Group() +pge3_group = displayio.Group() +pge4_group = displayio.Group() + +# labels +pge1_lbl = Label( + font=font_term, + scale=2, + text="This is the first page!", + anchor_point=(0, 0), + anchored_position=(10, 10), +) +pge1_lbl2 = Label( + font=font_term, + scale=2, + text="Please wait...", + anchor_point=(0, 0), + anchored_position=(10, 150), +) +pge2_lbl = Label( + font=font_term, + scale=2, + text="This page is the second page!", + anchor_point=(0, 0), + anchored_position=(10, 10), +) +pge3_lbl = Label( + font=font_term, + scale=2, + text=myVars.read("pge3_lbl_dflt"), # Will be "Date/time:" + anchor_point=(0, 0), + anchored_position=(10, 10), +) +pge3_lbl2 = Label( + font=font_term, + scale=2, + text="", # pge3_lbl2_dflt, # Will be DD-MO-YYYY or Month-DD-YYYY + anchor_point=(0, 0), + anchored_position=(10, 40), +) +pge3_lbl3 = Label( + font=font_term, + scale=2, + text="", # pge3_lbl3_dflt, # Will be HH:MM:SS + anchor_point=(0, 0), + anchored_position=(10, 70), +) +pge4_lbl = Label( + font=font_term, + scale=2, + text=myVars.read("pge4_lbl_dflt"), + anchor_point=(0, 0), + anchored_position=(10, 10), +) +pge4_lbl2 = Label( + font=font_term, + scale=2, + text="", # Will be "Temperature" + anchor_point=(0, 0), + anchored_position=(10, 130), +) +pge4_lbl3 = Label( + font=font_arial, + scale=2, + text="", # Will be "xx.yy C" + anchor_point=(0, 0), + anchored_position=(10, 160), +) + +# shapes +square = Rect(x=20, y=70, width=40, height=40, fill=0x00DD00) +circle = Circle(50, 100, r=30, fill=0xDD00DD) +triangle = Triangle(50, 0, 100, 50, 0, 50, fill=0xDDDD00) +rectangle = Rect(x=80, y=60, width=100, height=50, fill=0x0000DD) + +triangle.x = 80 +triangle.y = 70 + +# add everything to their page groups +pge1_group.append(square) +pge1_group.append(pge1_lbl) +pge2_group.append(pge2_lbl) +pge2_group.append(circle) +pge3_group.append(pge3_lbl) +pge3_group.append(pge3_lbl2) +pge3_group.append(pge3_lbl3) +pge3_group.append(triangle) +pge4_group.append(pge4_lbl) +pge4_group.append(pge4_lbl2) +pge4_group.append(pge4_lbl3) +pge4_group.append(rectangle) + +if board.board_id == "pyportal_titano": + pages = {0: "Dum", 1: "One", 2: "Two", 3: "Three", 4: "Four"} +else: + pages = {0: "Dum", 1: "One", 2: "Two", 3: "Thr", 4: "For"} + +# add the pages to the layout, supply your own page names +test_page_layout.add_content(pge1_group, pages[1]) +test_page_layout.add_content(pge2_group, pages[2]) +test_page_layout.add_content(pge3_group, pages[3]) +test_page_layout.add_content(pge4_group, pages[4]) + +# test_page_layout.add_content(displayio.Group(), "page_5") + +# add it to the group that is showing on the display +main_group.append(test_page_layout) + +# test_page_layout.tab_tilegrids_group[3].x += 50 + +# change page with function by name +test_page_layout.show_page(page_name=pages[2]) +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(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(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(f"showing page name: {test_page_layout.showing_page_name}") +time.sleep(5) + +""" +another_text = Label(terminalio.FONT, text="And another thing!", \ + scale=2, color=0x00ff00, anchor_point=(0, 0), \ + anchored_position=(100, 100)) +test_page_layout.showing_page_content.append(another_text) +""" + + +""" + If the temperature sensor has been disconnected, + this function will try to reconnect (test if the sensor is present by now) + If reconnected this function sets the global variable t_sensor_present + If failed to reconnect the function clears t_sensor_present +""" + + +def connect_temp_sensor(): + t = "temperature sensor found" + + # myVars.write("temp_sensor",None) + + try: + myVars.write("temp_sensor", adafruit_tmp117.TMP117(i2c)) + except ValueError: # ValueError occurs if the temperature sensor is not connected + pass + + print( + "connect_temp_sensor(): type(temp_sensor) object = ", + type(myVars.read("temp_sensor")), + ) + if myVars.read("temp_sensor") is not None: + print(t) + print("temperature sensor connected") + myVars.write("t0", "Temperature") + if myVars.read("temp_in_fahrenheit"): + myVars.write("t1", chr(186) + "F") + else: + myVars.write("t1", chr(186) + "C") + + myVars.write("t2", 27 * "_") + else: + print("no " + t) + print("failed to connect temperature sensor") + myVars.write("t0", None) + myVars.write("t1", None) + myVars.write("t2", None) + + +""" + If the external rtc has been disconnected, + this function will try to reconnect (test if the external rtc is present by now) +""" + + +def connect_rtc(): + t = "RTC found" + + # myVars.write("rtc",None) + + try: + myVars.write("rtc", DS3231(i2c)) # i2c addres 0x68 + # myVars.write("rtc",rtc) + except ValueError: + pass + + print("connect_rtc() type rtc object = ", type(myVars.read("rtc"))) + if myVars.read("rtc") is not None: + print(t) + print("RTC connected") + if myVars.read("lStart"): + myVars.write("lStart", False) + myVars.read("rtc").datetime = myVars.read("default_dt") + else: + print("no " + t) + print("Failed to connect RTC") + + +""" + Function gets a value from the external temperature sensor + It only updates if the value has changed compared to the previous value + A fixed text is set in pge4_lbl2.text. The variable temperature value is set in pge4_lbl3.text + If no value obtained (for instance if the sensor is disconnected), + the function sets the pge4_lbl to a default text and makes empty + pge4_lbl2.text and pge4_lbl3.text +""" + + +def get_temp(): + showing_page_idx = test_page_layout.showing_page_index + RetVal = False + if myVars.read("temp_sensor") is not None: + try: + temp = myVars.read("temp_sensor").temperature + if myVars.read("temp_in_fahrenheit"): + temp = (temp * 1.8) + 32 + 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 + if temp is not None: + if temp != myVars.read( + "old_temp" + ): # Only update if there is a change in temperature + myVars.write("old_temp", temp) + t = f"{temp:5.2f} " + myVars.read("t1") + pge4_lbl.text = "" + pge4_lbl2.text = myVars.read("t0") + pge4_lbl3.text = t + # if not my_debug: + # print("pge4_lbl.tex.gvars {}".format(pge4_lbl.text)) + # time.sleep(2) + RetVal = True + else: + t = "" + pge4_lbl.text = myVars.read("pge4_lbl_dflt") + except OSError: + 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_lbl2.text = "" + pge4_lbl3.text = "" + + return RetVal + + +yy = 0 +mo = 1 +dd = 2 +hh = 3 +mm = 4 +ss = 5 + + +def handle_dt(dt): + RetVal = False + s = "Date/time: " + sYY = str(dt[yy]) + sMO = ( + months[dt[mo]] + if myVars.read("use_txt_in_month") + else "0" + str(dt[mo]) + if dt[mo] < 10 + else str(dt[mo]) + ) + + dt_dict = {} + + for _ in range(dd, ss + 1): + dt_dict[_] = "0" + str(dt[_]) if dt[_] < 10 else str(dt[_]) + + if myVars.read("my_debug"): + print("dt_dict = ", dt_dict) + + myVars.write("c_secs", dt_dict[ss]) + sDT = ( + sMO + "-" + dt_dict[dd] + "-" + sYY + if myVars.read("use_usa_notation") + else sYY + "-" + sMO + "-" + dt_dict[dd] + ) + if myVars.read("my_debug"): + print("handle_dt(): sDT_old = {}, sDT = {}".format(myVars.read("sDT_old"), sDT)) + if myVars.read("sDT_old") != sDT: + myVars.write("sDT_old", sDT) + myVars.write("dt_refresh", True) # The date has changed, set the refresh flag + sDT2 = dt_dict[hh] + ":" + dt_dict[mm] + ":" + dt_dict[ss] + + if myVars.read("dt_refresh"): # only refresh when needed + myVars.write("dt_refresh", False) + pge3_lbl.text = s + pge3_lbl2.text = sDT + + if myVars.read("c_secs") != myVars.read("o_secs"): + myVars.write("o_secs", myVars.read("c_secs")) + sDT3 = s + f"{sDT} {sDT2}" + print(sDT3) + + pge3_lbl3.text = sDT2 + if myVars.read("my_debug"): + 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. + # It is only to say to the loop() that we did't update the datetime + return RetVal + + +""" + Function gets the date and time: + a) if an rtc is present from the rtc; + b) if using online NTP pool server then get the date and time from the function time.localtime + This time.localtime has before been set with data from the NTP server. + In both cases the date and time will be set to the pge3_lbl, pge3_lbl12 and pge3_lbl3 + If no (valid) date and time has been received then a default text will be shown on the pge3_lbl +""" + + +def get_dt(): + dt = None + RetVal = False + + if myVars.read("rtc") is not None: + try: + dt = myVars.read("rtc").datetime + except OSError as exc: + if myVars.read("my_debug"): + print("Error number: ", exc.args[0]) + if exc.args[0] == 5: # Input/output error + print("get_dt(): OSError occurred. RTC probably is disconnected") + pge3_lbl.text = myVars.read("pge3_lbl_dflt") + myVars.write("sDT_old", "") + pge3_lbl2.text = "" + pge3_lbl3.text = "" + return RetVal + raise # Handle other errors + + elif myVars.read("online_time_present") or myVars.read("use_ntp"): + dt = time.localtime() + + if myVars.read("my_debug"): + print("get_dt(): dt = ", dt) + if dt is not None: + RetVal = handle_dt(dt) + else: + pge3_lbl.text = myVars.read("pge3_lbl_dflt") + pge3_lbl2.text = "" + pge3_lbl3.text = "" + return RetVal + + +print("starting loop") + + +def main(): + cnt = 0 + while True: + try: + print(f"Loop nr: {cnt:03d}") + # print("main(): type(rtc) object = ", type(myVars.read("rtc"))) + if myVars.read("rtc") is not None: + get_dt() + else: + connect_rtc() + # print("main(): type(temp_sensor) object = ", type(myVars.read("temp_sensor"))) + if myVars.read("temp_sensor") is not None: + get_temp() + else: + connect_temp_sensor() + cnt += 1 + if cnt > 999: + cnt = 0 + # change page by next page function. It will loop by default + time.sleep(2) + test_page_layout.next_page() + except KeyboardInterrupt as exc: + raise KeyboardInterrupt("Keyboard interrupt...exiting...") from exc + # raise SystemExit + + +if __name__ == "__main__": + main() diff --git a/examples/hotplug_sensor_examples/displayio_layout_hotplug_temp_sensor.py b/examples/hotplug_sensor_examples/displayio_layout_hotplug_temp_sensor.py new file mode 100644 index 0000000..7c2a0dd --- /dev/null +++ b/examples/hotplug_sensor_examples/displayio_layout_hotplug_temp_sensor.py @@ -0,0 +1,969 @@ +# SPDX-FileCopyrightText: 2022 PaulskPt +# +# SPDX-License-Identifier: MIT +""" +Notes by @PaulskPt +Script tested on an Adafruit PyPortal Titano +(Product ID 4444. See: https://www.adafruit.com/product/4444) +This script can make use of an I2C Realtime Clock type DS3231 +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 +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 adafruit_esp32spi import adafruit_esp32spi +from adafruit_ntp import NTP +from adafruit_pyportal import PyPortal +from digitalio import DigitalInOut + +from adafruit_displayio_layout.layouts.tab_layout import TabLayout + +# +-------------------------------------------------------+ +# | Definition for variables in the past defined as global| +# +-------------------------------------------------------+ +# The gVars class is created +# to elminate the need for global variables. + + +class gVars: + def __init__(self): + self.gVarsDict = { + 0: "my_debug", + 1: "rtc", + 2: "temp_sensor", + 3: "lStart", + 4: "o_secs", + 5: "c_secs", + 6: "dt_refresh", + 7: "sDT_old", + 8: "t0", + 9: "t1", + 10: "t2", + 11: "default_dt", + 12: "pge3_lbl_dflt", + 13: "pge4_lbl_dflt", + 14: "online_time_present", + 15: "temp_in_REPL", + 16: "old_temp", + 17: "use_ntp", + 18: "use_txt_in_month", + 19: "use_usa_notation", + 20: "content_sensor_idx", + 21: "ntp_refresh", + 22: "nHH_old", + 23: "next_NTP_sync", + 24: "s_cnt", + 25: "five_min_cnt", + 26: "next_NTP_sync_t1", + 27: "next_NTP_sync_t3", + 28: "temp_in_fahrenheit", + } + + self.gVars_rDict = { + "my_debug": 0, + "rtc": 1, + "temp_sensor": 2, + "lStart": 3, + "o_secs": 4, + "c_secs": 5, + "dt_refresh": 6, + "sDT_old": 7, + "t0": 8, + "t1": 9, + "t2": 10, + "default_dt": 11, + "pge3_lbl_dflt": 12, + "pge4_lbl_dflt": 13, + "online_time_present": 14, + "temp_in_REPL": 15, + "old_temp": 16, + "use_ntp": 17, + "use_txt_in_month": 18, + "use_usa_notation": 19, + "content_sensor_idx": 20, + "ntp_refresh": 21, + "nHH_old": 22, + "next_NTP_sync": 23, + "s_cnt": 24, + "five_min_cnt": 25, + "next_NTP_sync_t1": 26, + "next_NTP_sync_t3": 27, + "temp_in_fahrenheit": 28, + } + + self.g_vars = {} + + # self.clean() + + def write(self, s, value): + if isinstance(s, str): + if s in self.gVars_rDict: + n = self.gVars_rDict[s] + # print("myVars.write() \'{:" ">20s}\'found in self.gVars_rDict, + # key: {}".format(s, n)) + self.g_vars[n] = value + else: + raise KeyError("variable '{:" ">20s}' not found in self.gVars_rDict".format(s)) + else: + raise TypeError(f"myVars.write(): param s expected str, {type(s)} received") + + def read(self, s): + RetVal = None + if isinstance(s, str): + if s in self.gVars_rDict: + n = self.gVars_rDict[s] + if n in self.g_vars: + RetVal = self.g_vars[n] + return RetVal + + def clean(self): + self.g_vars = { + 0: None, + 1: None, + 2: None, + 3: None, + 4: None, + 5: None, + 6: None, + 7: None, + 8: None, + 9: None, + 10: None, + 11: None, + 12: None, + 13: None, + 14: None, + 15: None, + 16: None, + 17: None, + 18: None, + 19: None, + 20: None, + 21: None, + 22: None, + 23: None, + 24: None, + 25: None, + 26: None, + 27: None, + 28: None, + } + + def list(self): + for i in range(0, len(self.g_vars) - 1): + print( + "self.g_vars['{:" ">20s}'] = {}".format( + self.gVarsDict[i], self.g_vars[i] if i in self.g_vars else "None" + ) + ) + + +# ---------- End of class gVars ------------------------ + +myVars = gVars() # create an instance of the gVars class + +myVars.write("my_debug", False) + +# Adjust here the date and time that you want the RTC to be set at start: +myVars.write("default_dt", time.struct_time((2022, 5, 14, 11, 42, 0, 5, -1, -1))) + +# start_time = time.monotonic() + +# -------------- Setting myVars elements ---------------------------------- +myVars.write("rtc", None) +myVars.write("temp_sensor", None) +myVars.write("lStart", True) +myVars.write("o_secs", 0) # old seconds +myVars.write("c_secs", 0) # current seconds +# dt_refresh is used to flag when more or less static elements +# in datetime stamp have to be refreshed +myVars.write("dt_refresh", True) +myVars.write("sDT_old", "") +myVars.write("t0", None) +myVars.write("t1", None) +myVars.write("t2", None) +# default_dt already set above +myVars.write("pge3_lbl_dflt", "The third page is fun!") +myVars.write("pge4_lbl_dflt", "The fourth page is where it's at") +myVars.write("online_time_present", False) +myVars.write("temp_in_REPL", False) +myVars.write("old_temp", 0.00) +myVars.write("use_txt_in_month", True) +myVars.write("use_usa_notation", True) +myVars.write("use_ntp", True) +myVars.write("content_sensor_idx", None) +myVars.write("ntp_refresh", True) +myVars.write("next_NTP_sync", 0) +myVars.write("s_cnt", 0) +myVars.write("five_min_cnt", 0) +myVars.write("next_NTP_sync_t1", "Next NTP sync in ") +myVars.write("next_NTP_sync_t3", " (mm:ss)") +myVars.write("temp_in_fahrenheit", True) +# nHH_old is used to check if the hour has changed. +# If so we have to re-sync from NTP server +# (if not using an external RTC) +myVars.write("nHH_old", -1) + +if myVars.read("my_debug"): + # print list of all variables in myVars + myVars.list() +# ------------------------------------------------------------------------- +# degs_sign = chr(186) # I preferred the real degrees sign which is: chr(176) + +# If you are using a board with pre-defined ESP32 Pins: +esp32_cs = DigitalInOut(board.ESP_CS) +esp32_ready = DigitalInOut(board.ESP_BUSY) +esp32_reset = DigitalInOut(board.ESP_RESET) + +spi = busio.SPI(board.SCK, board.MOSI, board.MISO) +esp = adafruit_esp32spi.ESP_SPIcontrol(spi, esp32_cs, esp32_ready, esp32_reset) + +# ------------- Screen Setup ------------- # +pyportal = None +timeout_cnt = 0 +while pyportal is None: + try: + pyportal = PyPortal( + esp=esp, external_spi=spi, debug=True + ) # esp=esp, external_spi=spi) # create a PyPortal object + if pyportal is not None: + break + except ValueError: # Occurred the error: "SCK in use". + # Also occurred the error "SPEAKER_ENABLE in use" + time.sleep(0.5) + timeout_cnt += 1 + if timeout_cnt > 10: + print("Timeout occurred while trying to create a PyPortal object") + raise + +months = { + 0: "Dum", + 1: "Jan", + 2: "Feb", + 3: "Mar", + 4: "Apr", + 5: "May", + 6: "Jun", + 7: "Jul", + 8: "Aug", + 9: "Sep", + 10: "Oct", + 11: "Nov", + 12: "Dec", +} + +i2c = board.I2C() + +if myVars.read("use_ntp"): + print( + "\ntest_page_layout.showing_page_index test with I2C Temperature sensor and NTP \ +synchronized local time" + ) +else: + print("\nTabLayout test with I2C Temperature sensor and I2C Realtime clock") +print("Add your WiFi SSID, WiFi password and Timezone in file: settings.toml\n") + +if myVars.read("my_debug"): + while not i2c.try_lock(): + pass + + try: + while True: + print( + "I2C addresses found:", + [hex(device_address) for device_address in i2c.scan()], + ) + time.sleep(2) + break + + finally: # unlock the i2c bus when ctrl-c'ing out of the loop + i2c.unlock() + +# -------- Setting up SDCard --------------------- +# Is not needed to be done here: the SPI module is taking care of initializing the SD Card. +# See: https://andyfelong.com/2019/07/pyportal-access-the-micro-sd-card/#:~:text= \ +# It%20also%20has%20support%20for%20a%20micro%2DSD%20Card.&text=Software%20support%20 \ +# for%20PyPortal%20is, \ +# %2Din%20serial%2Dport%20terminal.77 +# +# NOTE: there is also the board.SD_CARD_DETECT pin (33)(but I don't know yet how to interface it) +#### + +# 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: + print("ESP32 found and in idle mode") + print("Firmware vers.", esp.firmware_version) + print("MAC addr:", [hex(i) for i in esp.MAC_address]) + + for ap in esp.scan_networks(): + print("\t%s\t\tRSSI: %d" % (str(ap["ssid"], "utf-8"), ap["rssi"])) + +# Get our desired timezone +location = getenv("timezone", None) + +print("\nConnecting to AP...") +while not esp.is_connected: + try: + esp.connect_AP(ssid, password) + except RuntimeError as e: + print("could not connect to AP, retrying: ", e) + continue +print("Connected to", str(esp.ssid, "utf-8"), "\tRSSI:", esp.rssi) +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("Ping google.com: %d ms" % esp.ping("google.com")) + + +def refresh_from_NTP(): + # Fetch and set the microcontroller's current UTC time + # keep retrying until a valid time is returned + timeout_cnt2 = 0 + while not ntp.valid_time: + ntp.set_time(tz_offset) + if myVars.read("my_debug"): + print("Failed to obtain time, retrying in 5 seconds...") + timeout_cnt2 += 1 + time.sleep(5) + if timeout_cnt2 > 10: + print("Timeout while trying to get ntp datetime to set the internal rtc") + break + + if myVars.read("my_debug"): + print("Value ntp.valid_time = ", ntp.valid_time) + + if ntp.valid_time: + 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 settings.toml) + ntp_current_time = time.time() + if myVars.read("my_debug"): + 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)) + if not myVars.read("my_debug"): + print( + "Internal clock synchronized from NTP pool, now =", + myVars.read("default_dt"), + ) + + +if myVars.read("use_ntp"): + # Initialize the NTP object + ntp = NTP(esp) + + location = getenv("timezone", location) + if myVars.read("my_debug"): + print(f"location (from settings.toml) = {location}") + if location == "Europe/Lisbon": + if myVars.read("my_debug"): + print("Using timezone Europe/Lisbon") + tz_offset = 3600 + else: + tz_offset = 0 + + refresh_from_NTP() + +pixel = neopixel.NeoPixel(board.NEOPIXEL, 1, brightness=1) +WHITE = 0xFFFFFF +RED = 0xFF0000 +YELLOW = 0xFFFF00 +GREEN = 0x00FF00 +BLUE = 0x0000FF +PURPLE = 0xFF00FF +BLACK = 0x000000 + +# ---------- Sound Effects ------------- # +soundDemo = "/sounds/sound.wav" +soundBeep = "/sounds/beep.wav" +soundTab = "/sounds/tab.wav" + +# ------------ Touchscreen setup --------------- # +# See: https://learn.adafruit.com/making-a-pyportal-user-interface-displayio/display +display = board.DISPLAY # create the display object +display.rotation = 0 +# screen_width = 320 +# screen_height = 240 +screen_width = display.width +screen_height = display.height +# -------Rotate 0: +# Note @PaulskPt dd 2022-05-13 +# After using a touchscreen calibration script, the values are as follows: +# (XL, YU, XR, YD) are: (6935, 10496, 60127, 57631) +ts = adafruit_touchscreen.Touchscreen( + board.TOUCH_XL, + board.TOUCH_XR, + board.TOUCH_YD, + board.TOUCH_YU, # #calibration=((5200, 59000), (5800, 57000)), + calibration=((6815, 60095), (10520, 58007)), + size=(screen_width, screen_height), +) # was: screen_width, screen_height +""" +# If Rotate is 90: +# -------Rotate 90: +ts = adafruit_touchscreen.Touchscreen(board.TOUCH_YU, board.TOUCH_YD, + board.TOUCH_XL, board.TOUCH_XR, + calibration=((5200, 59000), (5800, 57000)), + size=(screen_height, screen_width)) +# If Rotate 180: +ts = adafruit_touchscreen.Touchscreen(board.TOUCH_XR, board.TOUCH_XL, + board.TOUCH_YU, board.TOUCH_YD, + calibration=((5200, 59000), (5800, 57000)), + size=(screen_width, screen_height)) + +# If Rotate 270: +ts = adafruit_touchscreen.Touchscreen(board.TOUCH_XL, board.TOUCH_XR, + board.TOUCH_YD, board.TOUCH_YU, + calibration=((5200, 59000), (5800, 57000)), + size=(screen_height, screen_width)) +""" +# ----------------------------------- + +# create and show main_group +main_group = displayio.Group() # The Main Display Group + +display.root_group = main_group + +# font = bitmap_font.load_font("fonts/Helvetica-Bold-16.bdf") +font_arial = bitmap_font.load_font("/fonts/Arial-16.bdf") +font_term = terminalio.FONT + +# create the page layout +test_page_layout = TabLayout( + x=0, + y=0, + display=board.DISPLAY, + tab_text_scale=2, + custom_font=font_term, + inactive_tab_spritesheet="lib/adafruit_displayio_layout/examples/bmps/inactive_tab_sprite.bmp", + showing_tab_spritesheet="lib/adafruit_displayio_layout/examples/bmps/active_tab_sprite.bmp", + showing_tab_text_color=0x00AA59, + inactive_tab_text_color=0xEEEEEE, + inactive_tab_transparent_indexes=(0, 1), + showing_tab_transparent_indexes=(0, 1), + tab_count=4, +) +# make 4 pages of content +pge1_group = displayio.Group() +pge2_group = displayio.Group() +pge3_group = displayio.Group() +pge4_group = displayio.Group() +# make 1 background group +bg_group = displayio.Group() + +""" + From: https://learn.adafruit.com/making-a-pyportal-user-interface-displayio/the-full-code +""" + + +# This will handle switching Images and Icons +def set_image(group, filename): + """Set the image file for a given goup for display. + This is most useful for Icons or image slideshows. + :param group: The chosen group + :param filename: The filename of the chosen image + """ + print("Set image to ", filename) + image = None + image_sprite = None + if group: + group.pop() + if not filename: + return # we're done, no icon desired + # CircuitPython 6 & 7 compatible + try: + image = displayio.OnDiskBitmap(filename) + except OSError as exc: + if exc.args[0] == 2: # No such file/directory + return + if image is not None: + image_sprite = displayio.TileGrid( + image, + pixel_shader=getattr(image, "pixel_shader", displayio.ColorConverter()), + ) + if image_sprite is not None: + main_group.append(image_sprite) + + +# ------------- Setup for Images ------------- # + +bg_group = displayio.Group() +set_image(bg_group, "/images/BGimage4.bmp") +print( + "Please wait...building-up things..." +) # 2022-05-08 13h19 (utc+1) It takes 24 seconds from here to start of main() loop +main_group.append(bg_group) + +icon_group = displayio.Group() +icon_group.x = 180 +icon_group.y = 120 +icon_group.scale = 1 +pge2_group.append(icon_group) + +# labels +pge1_lbl = Label( + font=font_term, + scale=2, + text="This is the first page!", + anchor_point=(0, 0), + anchored_position=(10, 10), +) +pge1_lbl2 = Label( + font=font_term, + scale=2, + text="Please wait...", + anchor_point=(0, 0), + anchored_position=(10, 150), +) +pge2_lbl = Label( + font=font_term, + scale=2, + text="This page is the second page!", + anchor_point=(0, 0), + anchored_position=(10, 10), +) +pge3_lbl = Label( + font=font_term, + scale=2, + text=myVars.read("pge3_lbl_dflt"), # Will be "Date/time:" + anchor_point=(0, 0), + anchored_position=(10, 10), +) +pge3_lbl2 = Label( + font=font_term, + scale=2, + text="", # pge3_lbl2_dflt, # Will be DD-MO-YYYY or Month-DD-YYYY + anchor_point=(0, 0), + anchored_position=(10, 40), +) +pge3_lbl3 = Label( + font=font_term, + scale=2, + text="", # pge3_lbl3_dflt, # Will be HH:MM:SS + anchor_point=(0, 0), + anchored_position=(10, 70), +) +pge3_lbl4 = Label( + font=font_term, + scale=2, + text="", # pge3_lbl3_dflt, # Will be time until next NTP sync in MM:SS + anchor_point=(0, 0), + anchored_position=(10, 200), +) +pge4_lbl = Label( + font=font_term, + scale=2, + text=myVars.read("pge4_lbl_dflt"), + anchor_point=(0, 0), + anchored_position=(10, 10), +) +pge4_lbl2 = Label( + font=font_term, + scale=2, + text="", # Will be "Temperature" + anchor_point=(0, 0), + anchored_position=(10, 130), +) +pge4_lbl3 = Label( + font=font_arial, # bitmap_font.load_font("/fonts/Arial-16.bdf"), + scale=2, + text="", # Will be "xx.yy ºC" + anchor_point=(0, 0), + anchored_position=(10, 160), +) + +# shapes +square = Rect(x=20, y=70, width=40, height=40, fill=0x00DD00) +circle = Circle(50, 100, r=30, fill=0xDD00DD) +triangle = Triangle(50, 0, 100, 50, 0, 50, fill=0xDDDD00) +rectangle = Rect(x=80, y=60, width=100, height=50, fill=0x0000DD) + +triangle.x = 80 +triangle.y = 70 + +# add everything to their page groups +pge1_group.append(square) +pge1_group.append(pge1_lbl) +pge1_group.append(pge1_lbl2) +pge2_group.append(pge2_lbl) +pge2_group.append(circle) +pge3_group.append(pge3_lbl) +pge3_group.append(pge3_lbl2) +pge3_group.append(pge3_lbl3) +pge3_group.append(pge3_lbl4) +pge3_group.append(triangle) +pge4_group.append(pge4_lbl) +pge4_group.append(pge4_lbl2) +pge4_group.append(pge4_lbl3) +pge4_group.append(rectangle) + +if board.board_id == "pyportal_titano": + pages = {0: "Dum", 1: "One", 2: "Two", 3: "Three", 4: "Four"} +else: + pages = {0: "Dum", 1: "One", 2: "Two", 3: "Thr", 4: "For"} + +# add the pages to the layout, supply your own page names +test_page_layout.add_content(pge1_group, pages[1]) +test_page_layout.add_content(pge2_group, pages[2]) +test_page_layout.add_content(pge3_group, pages[3]) +test_page_layout.add_content(pge4_group, pages[4]) +# test_page_layout.add_content(displayio.Group(), "page_5") +# add it to the group that is showing on the display +main_group.append(test_page_layout) +# test_page_layout.tab_tilegrids_group[3].x += 50 +# ---------- Text Boxes ------------- # +# Set the font and preload letters +# font = bitmap_font.load_font("/fonts/Arial-16.bdf") # was: Helvetica-Bold-16.bdf") +# font.load_glyphs(b"abcdefghjiklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890- ()") +glyphs = b' "(),-.0123456789:ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz' +font_arial.load_glyphs(glyphs) +font_arial.load_glyphs(("°",)) # a non-ascii character we need +# font=font_term.collect() # ADDED by @PaulskPt -- +# to prevent MemoryError - memory allocation failed, +# allocating 6444 bytes + +pge2_group = 1 + + +"""If the temperature sensor has been disconnected, + this function will try to reconnect (test if the sensor is present by now) + If reconnected this function creates the temp_sensor object""" + + +def connect_temp_sensor(): + t = "temperature sensor found" + + # myVars.write("temp_sensor",None) + + try: + myVars.write("temp_sensor", adafruit_tmp117.TMP117(i2c)) + except ValueError: # ValueError occurs if the temperature sensor is not connected + pass + + print( + "connect_temp_sensor(): type(temp_sensor) object = ", + type(myVars.read("temp_sensor")), + ) + if myVars.read("temp_sensor") is not None: + print(t) + print("temperature sensor connected") + myVars.write("t0", "Temperature") + if myVars.read("temp_in_fahrenheit"): + myVars.write("t1", chr(186) + "F") + else: + myVars.write("t1", chr(186) + "C") + myVars.write("t2", 27 * "_") + else: + print("no " + t) + print("failed to connect temperature sensor") + myVars.write("t0", None) + myVars.write("t1", None) + myVars.write("t2", None) + + +""" If the external rtc has been disconnected, + this function will try to reconnect (test if the external rtc is present by now)""" + + +def connect_rtc(): + t = "RTC found" + + # myVars.write("rtc",None) + + try: + myVars.write("rtc", DS3231(i2c)) # i2c addres 0x68 + # myVars.write("rtc",rtc) + except ValueError: + pass + + print("connect_rtc() type rtc object = ", type(myVars.read("rtc"))) + if myVars.read("rtc") is not None: + print(t) + print("RTC connected") + if myVars.read("lStart"): + myVars.write("lStart", False) + myVars.read("rtc").datetime = myVars.read("default_dt") + else: + print("no " + t) + print("Failed to connect RTC") + + +"""Function gets a value from the external temperature sensor + It only updates if the value has changed compared to the previous value + A fixed text is set in pge4_lbl2.text. The variable temperature value is set in pge4_lbl3.text + If no value obtained (for instance if the sensor is disconnected), + the function sets the pge4_lbl to a default text and makes empty + pge4_lbl2.text and pge4_lbl3.text""" + + +def get_temp(): + my_debug = myVars.read("my_debug") + showing_page_idx = test_page_layout.showing_page_index + RetVal = False + if myVars.read("temp_sensor") is not None: + try: + 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 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 + if temp is not None: + if temp != myVars.read( + "old_temp" + ): # Only update if there is a change in temperature + myVars.write("old_temp", temp) + t = "{:5.2f}{} ".format(temp, myVars.read("t1")) + pge4_lbl.text = "" + pge4_lbl2.text = myVars.read("t0") + pge4_lbl3.text = t + # if not my_debug: + # print("pge4_lbl.tex.gvars {}".format(pge4_lbl.text)) + # time.sleep(2) + RetVal = True + else: + t = "" + pge4_lbl.text = myVars.read("pge4_lbl_dflt") + except OSError: + 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_lbl2.text = "Sensor disconnected." + pge4_lbl3.text = "Check wiring." + return RetVal + + +dt_ridxs = {"yy": 0, "mo": 1, "dd": 2, "hh": 3, "mm": 4, "ss": 5} + +# print("dict dt_ridxs =", dt_ridxs.keys()) + + +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]) + dd = dt_ridxs["dd"] + hh = dt_ridxs["hh"] + mm = dt_ridxs["mm"] + ss = dt_ridxs["ss"] + if "mo" in dt_ridxs: + sMO = ( + months[dt[dt_ridxs["mo"]]] # was: months[dt[mo]] + if myVars.read("use_txt_in_month") + else "0" + str(dt[dt_ridxs["mo"]]) + if dt[dt_ridxs["mo"]] < 10 + else str(dt[dt_ridxs["mo"]]) + ) + else: + raise KeyError("key {} not in dt_ridxs dict".format("mo")) + + dt_dict = {} + + for _ in range(dd, ss + 1): + dt_dict[_] = "0" + str(dt[_]) if dt[_] < 10 else str(dt[_]) + + if my_debug: + print("dt_dict = ", dt_dict) + + myVars.write("c_secs", dt_dict[ss]) + sDT = ( + sMO + "-" + dt_dict[dd] + "-" + sYY + if myVars.read("use_usa_notation") + else sYY + "-" + sMO + "-" + dt_dict[dd] + ) + if my_debug: + print("handle_dt(): sDT_old = {}, sDT = {}".format(myVars.read("sDT_old"), sDT)) + if myVars.read("sDT_old") != sDT: + myVars.write("sDT_old", sDT) + myVars.write("dt_refresh", True) # The date has changed, set the refresh flag + sDT2 = dt_dict[hh] + ":" + dt_dict[mm] + ":" + dt_dict[ss] + + if myVars.read("dt_refresh"): # only refresh when needed + myVars.write("dt_refresh", False) + pge3_lbl.text = s + pge3_lbl2.text = sDT + + if myVars.read("c_secs") != myVars.read("o_secs"): + myVars.write("o_secs", myVars.read("c_secs")) + sDT3 = s + f"{sDT} {sDT2}" + print(sDT3) + + pge3_lbl3.text = sDT2 + if my_debug: + 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. + # It is only to say to the loop() that we did't update the datetime + return RetVal + + +"""Function gets the date and time: + a) if an rtc is present from the rtc; + b) if using online NTP pool server then get the date and time from the function time.localtime + This time.localtime has before been set with data from the NTP server. + In both cases the date and time will be set to the pge3_lbl, pge3_lbl12 and pge3_lbl3 + If no (valid) date and time received then a default text will be shown on the pge3_lbl""" + + +def get_dt(): + dt = None + RetVal = False + + if myVars.read("rtc") is not None: + try: + dt = myVars.read("rtc").datetime + except OSError as exc: + if myVars.read("my_debug"): + print("Error number: ", exc.args[0]) + if exc.args[0] == 5: # Input/output error + print("get_dt(): OSError occurred. RTC probably is disconnected") + pge3_lbl.text = myVars.read("pge3_lbl_dflt") + myVars.write("sDT_old", "") + pge3_lbl2.text = "" + pge3_lbl3.text = "" + return RetVal + raise # Handle other errors + + elif myVars.read("online_time_present") or myVars.read("use_ntp"): + dt = time.localtime() + + if myVars.read("my_debug"): + print("get_dt(): dt = ", dt) + if dt is not None: + RetVal = handle_dt(dt) + else: + pge3_lbl.text = myVars.read("pge3_lbl_dflt") + pge3_lbl2.text = "" + pge3_lbl3.text = "" + return RetVal + + +""" hms_to_cnt() + function returns a integer value representing + the conversion from the current hours, minutes and seconds + into seconds""" + + +def hms_to_cnt(): + dt = time.localtime() # get the local time as a time_struct + return (dt.tm_hour * 3600) + (dt.tm_min * 60) + dt.tm_sec + + +def ck_next_NTP_sync(): + s_cnt = myVars.read("s_cnt") + c_cnt = hms_to_cnt() # set current count (seconds) + c_elapsed = c_cnt - s_cnt + if c_elapsed < 10: # continue only when c_elapsed >= 10 + return + TAG = "ck_next_NTP_sync(): " + my_debug = myVars.read("my_debug") + t1 = myVars.read("next_NTP_sync_t1") + t3 = myVars.read("next_NTP_sync_t3") + five_min = myVars.read("five_min_cnt") + myVars.write("s_cnt", hms_to_cnt()) + # --- five minutes count down calculations #1 --- + if my_debug: + 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 --- + myVars.write("s_cnt", c_cnt) # remember c_cnt + five_min -= 10 + myVars.write("five_min_cnt", five_min) # remember count + mm2 = five_min // 60 + ss2 = five_min - (mm2 * 60) + t2 = f"{mm2:02d}:{ss2:02d}" + t0 = t1 + t2 + t3 + print(t0) + pge3_lbl4.text = t0 + if five_min == 0: # five minutes passed + pge3_lbl4.text = "" + myVars.write("five_min_cnt", 300) # reset count + myVars.write("ntp_refresh", True) + + +def inc_cnt(cnt): + cnt += 1 + if cnt > 999: + cnt = 0 + return cnt + + +def main(): + cnt = 1 + wipe_pge1_lbl2_text = False + print("Starting loop") + pge1_lbl2.text = "Ready..." + myVars.write("five_min_cnt", 300) # 5 minutes + myVars.write("s_cnt", hms_to_cnt()) # set start count (seconds) + use_ntp = myVars.read("use_ntp") + rtc = myVars.read("rtc") + otp = myVars.read("online_time_present") + # print("Starting loop") + while True: + touch = ts.touch_point + try: + if use_ntp: + ck_next_NTP_sync() + ntp_refresh = myVars.read("ntp_refresh") + # ------------- Handle Tab touch ------------- # + # print("main() value touch: ", touch) + if touch: # Only do this if the screen is touched + if not wipe_pge1_lbl2_text: + pge1_lbl2.text = "" # Clear the label + wipe_pge1_lbl2_text = True + test_page_layout.handle_touch_events(touch) + if rtc is not None or otp: + if otp and ntp_refresh: + refresh_from_NTP() # first re-synchronize internal clock from NTP server + if get_dt(): + print(f"Loop nr: {cnt:03d}") + else: + connect_rtc() + if myVars.read("temp_sensor") is not None: + get_temp() + else: + connect_temp_sensor() + touch = ( + ts.touch_point + ) # Just to try - it looks like after re-connecting the sensor, + # the touch data has lost + if myVars.read("temp_in_REPL"): + myVars.write("temp_in_REPL", False) + cnt = inc_cnt(cnt) + except KeyboardInterrupt as exc: + print("Keyboard interrupt...exiting...") + raise KeyboardInterrupt from exc + + +if __name__ == "__main__": + main() diff --git a/examples/hotplug_sensor_examples/images/BGimage4.bmp b/examples/hotplug_sensor_examples/images/BGimage4.bmp new file mode 100644 index 0000000..02d4c25 Binary files /dev/null and b/examples/hotplug_sensor_examples/images/BGimage4.bmp differ diff --git a/examples/hotplug_sensor_examples/images/BGimage4.bmp.license b/examples/hotplug_sensor_examples/images/BGimage4.bmp.license new file mode 100644 index 0000000..82d4370 --- /dev/null +++ b/examples/hotplug_sensor_examples/images/BGimage4.bmp.license @@ -0,0 +1,2 @@ +# SPDX-FileCopyrightText: 2022 PaulskPt +# SPDX-License-Identifier: MIT diff --git a/optional_requirements.txt b/optional_requirements.txt new file mode 100644 index 0000000..d4e27c4 --- /dev/null +++ b/optional_requirements.txt @@ -0,0 +1,3 @@ +# SPDX-FileCopyrightText: 2022 Alec Delaney, for Adafruit Industries +# +# SPDX-License-Identifier: Unlicense diff --git a/pyproject.toml b/pyproject.toml index f3c35ae..c036644 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,48 @@ -# SPDX-FileCopyrightText: 2020 Diego Elio Pettenò +# SPDX-FileCopyrightText: 2022 Alec Delaney for Adafruit Industries # -# SPDX-License-Identifier: Unlicense +# SPDX-License-Identifier: MIT -[tool.black] -target-version = ['py35'] +[build-system] +requires = [ + "setuptools", + "wheel", + "setuptools-scm", +] + +[project] +name = "adafruit-circuitpython-displayio-layout" +description = "CircuitPython helper library for displayio layouts and widgets." +version = "0.0.0+auto.0" +readme = "README.rst" +authors = [ + {name = "Adafruit Industries", email = "circuitpython@adafruit.com"} +] +urls = {Homepage = "https://github.com/adafruit/Adafruit_CircuitPython_DisplayIO_Layout"} +keywords = [ + "adafruit", + "blinka", + "circuitpython", + "micropython", + "displayio_layout", + "displayio", + "gui", + "layout", + "widget", +] +license = {text = "MIT"} +classifiers = [ + "Intended Audience :: Developers", + "Topic :: Software Development :: Libraries", + "Topic :: Software Development :: Embedded Systems", + "Topic :: System :: Hardware", + "License :: OSI Approved :: MIT License", + "Programming Language :: Python :: 3", +] +dynamic = ["dependencies", "optional-dependencies"] + +[tool.setuptools] +packages = ["adafruit_displayio_layout"] + +[tool.setuptools.dynamic] +dependencies = {file = ["requirements.txt"]} +optional-dependencies = {optional = {file = ["optional_requirements.txt"]}} diff --git a/requirements.txt b/requirements.txt index bdd5ac6..433415f 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,10 +1,11 @@ -# SPDX-FileCopyrightText: 2017 Scott Shawcroft, written for Adafruit Industries -# SPDX-FileCopyrightText: Copyright (c) 2021 Tim Cocks for Adafruit Industries +# SPDX-FileCopyrightText: 2022 Alec Delaney, for Adafruit Industries # -# SPDX-License-Identifier: MIT +# SPDX-License-Identifier: Unlicense +Adafruit-Blinka-displayio Adafruit-Blinka -adafruit-blinka-displayio -adafruit-circuitpython-display-shapes -adafruit-circuitpython-imageload +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" diff --git a/setup.py b/setup.py deleted file mode 100644 index f17d31d..0000000 --- a/setup.py +++ /dev/null @@ -1,69 +0,0 @@ -# SPDX-FileCopyrightText: 2017 Scott Shawcroft, written for Adafruit Industries -# SPDX-FileCopyrightText: Copyright (c) 2021 Tim Cocks for Adafruit Industries -# -# SPDX-License-Identifier: MIT - -"""A setuptools based setup module. - -See: -https://packaging.python.org/en/latest/distributing.html -https://github.com/pypa/sampleproject -""" - -from setuptools import setup, find_packages - -# To use a consistent encoding -from codecs import open -from os import path - -here = path.abspath(path.dirname(__file__)) - -# Get the long description from the README file -with open(path.join(here, "README.rst"), encoding="utf-8") as f: - long_description = f.read() - -setup( - name="adafruit-circuitpython-displayio-layout", - use_scm_version=True, - setup_requires=["setuptools_scm"], - description="CircuitPython helper library for displayio layouts and widgets.", - long_description=long_description, - long_description_content_type="text/x-rst", - # The project's main homepage. - url="https://github.com/adafruit/Adafruit_CircuitPython_DisplayIO_Layout", - # Author details - author="Adafruit Industries", - author_email="circuitpython@adafruit.com", - install_requires=[ - "Adafruit-Blinka", - "adafruit-blinka-displayio", - "adafruit-circuitpython-display-shapes", - "adafruit-circuitpython-imageload", - "adafruit-circuitpython-display-text", - ], - # Choose your license - license="MIT", - # See https://pypi.python.org/pypi?%3Aaction=list_classifiers - classifiers=[ - "Development Status :: 3 - Alpha", - "Intended Audience :: Developers", - "Topic :: Software Development :: Libraries", - "Topic :: System :: Hardware", - "License :: OSI Approved :: MIT License", - "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.4", - "Programming Language :: Python :: 3.5", - ], - # What does your project relate to? - keywords="adafruit blinka circuitpython micropython displayio_layout displayio gui layout " - "widget", - # You can just specify the packages manually here if your project is - # simple. Or you can use find_packages(). - # TODO: IF LIBRARY FILES ARE A PACKAGE FOLDER, - # CHANGE `py_modules=['...']` TO `packages=['...']` - packages=[ - "adafruit_displayio_layout", - "adafruit_displayio_layout.layouts", - "adafruit_displayio_layout.widgets", - ], -) 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