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",
- ],
-)