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/grid_layout.py b/adafruit_displayio_layout/layouts/grid_layout.py index e63d6ac..957cb91 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,129 @@ def add_content(self, cell_content, grid_position, cell_size): x,y coordinates in grid cells. e.g. (1,0) :param tuple cell_size: the size and shape that the new cell should occupy. Width and height in cells inside a tuple e.g. (1, 1) + :param tuple cell_anchor_point: a tuple of floats between 0.0 and 1.0. + If passed, this value will override the cell_anchor_point of the GridLayout + for the single cell having it's content added with this function call. If omitted + then the cell_anchor_point from the GridLayout will be used. :return: None""" + + if cell_anchor_point: + _this_cell_anchor_point = cell_anchor_point + else: + _this_cell_anchor_point = self._cell_anchor_point + sub_view_obj = { + "cell_anchor_point": _this_cell_anchor_point, "content": cell_content, "grid_position": grid_position, "cell_size": cell_size, } 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..ac5875d --- /dev/null +++ b/adafruit_displayio_layout/layouts/tab_layout.py @@ -0,0 +1,281 @@ +# 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 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 displayio.Display 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[displayio.Display] = 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..cbdae52 100644 --- a/adafruit_displayio_layout/widgets/flip_input.py +++ b/adafruit_displayio_layout/widgets/flip_input.py @@ -23,23 +23,27 @@ 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 +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): @@ -83,25 +87,25 @@ class FlipInput(Widget, Control): def __init__( self, - display, + display: displayio.Display, *, - 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 +115,6 @@ def __init__( # initialize the Control superclass - # pylint: disable=bad-super-call super(Control, self).__init__() self.value_list = value_list @@ -147,18 +150,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 +177,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 +209,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 +240,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 +258,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 +271,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 +331,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 +343,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 +391,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 +400,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 +411,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 +432,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 +468,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 +488,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 +538,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 +578,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 +586,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: displayio.Display, + 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 +620,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 +640,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..2e43821 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 busdisplay import BusDisplay +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[BusDisplay], + 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. @@ -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..1cfb615 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,10 +1,10 @@ -# 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 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", - ], -)