Repository: adafruit/Adafruit_CircuitPython_ProgressBar (language: python)

Issue:
Add a vertical progress bar
It would be nice to have a vertical progress bar. Some examples of use:
* Gauges
* Meters
* Goal "Thermometer" displays


Discussion / hints:
This can be assigned to me since I've already started doing some work on this control as mentioned in today's (2021/02/01) weekly CircuitPython weekly meeting.

Fix patch:
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index 59baa53..fd99fee 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -50,10 +50,6 @@ jobs:
     - name: Pre-commit hooks
       run: |
         pre-commit run --all-files
-    - name: PyLint
-      run: |
-        pylint $( find . -path './adafruit*.py' )
-        ([[ ! -d "examples" ]] || pylint --disable=missing-docstring,invalid-name,bad-whitespace $( find . -path "./examples/*.py" ))
     - name: Build assets
       run: circuitpython-build-bundles --filename_prefix ${{ steps.repo-name.outputs.repo-name }} --library_location .
     - name: Archive bundles
@@ -61,7 +57,12 @@ jobs:
       with:
         name: bundles
         path: ${{ github.workspace }}/bundles/
+    - name: Check For docs folder
+      id: need-docs
+      run: |
+        echo ::set-output name=docs::$( find . -wholename './docs' )
     - name: Build docs
+      if: contains(steps.need-docs.outputs.docs, 'docs')
       working-directory: docs
       run: sphinx-build -E -W -b html . _build/html
     - name: Check For setup.py
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..eb39186
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,341 @@
+# SPDX-FileCopyrightText: 2017 Scott Shawcroft, written for Adafruit Industries
+#
+# SPDX-License-Identifier: MIT
+
+# Created by https://www.toptal.com/developers/gitignore/api/macos
+# Edit at https://www.toptal.com/developers/gitignore?templates=macos
+
+### macOS ###
+# General
+.DS_Store
+.AppleDouble
+.LSOverride
+
+# Icon must end with two \r
+Icon
+
+# Thumbnails
+._*
+
+# Files that might appear in the root of a volume
+.DocumentRevisions-V100
+.fseventsd
+.Spotlight-V100
+.TemporaryItems
+.Trashes
+.VolumeIcon.icns
+.com.apple.timemachine.donotpresent
+
+# Directories potentially created on remote AFP share
+.AppleDB
+.AppleDesktop
+Network Trash Folder
+Temporary Items
+.apdisk
+
+# End of https://www.toptal.com/developers/gitignore/api/macos
+
+# Created by https://www.toptal.com/developers/gitignore/api/windows
+# Edit at https://www.toptal.com/developers/gitignore?templates=windows
+
+### Windows ###
+# Windows thumbnail cache files
+Thumbs.db
+Thumbs.db:encryptable
+ehthumbs.db
+ehthumbs_vista.db
+
+# Dump file
+*.stackdump
+
+# Folder config file
+[Dd]esktop.ini
+
+# Recycle Bin used on file shares
+$RECYCLE.BIN/
+
+# Windows Installer files
+*.cab
+*.msi
+*.msix
+*.msm
+*.msp
+
+# Windows shortcuts
+*.lnk
+
+# End of https://www.toptal.com/developers/gitignore/api/windows
+
+# Created by https://www.toptal.com/developers/gitignore/api/linux
+# Edit at https://www.toptal.com/developers/gitignore?templates=linux
+
+### Linux ###
+*~
+
+# temporary files which can be created if a process still has a handle open of a deleted file
+.fuse_hidden*
+
+# KDE directory preferences
+.directory
+
+# Linux trash folder which might appear on any partition or disk
+.Trash-*
+
+# .nfs files are created when an open file is removed but is still being accessed
+.nfs*
+
+# End of https://www.toptal.com/developers/gitignore/api/linux
+
+# Created by https://www.toptal.com/developers/gitignore/api/jetbrains
+# Edit at https://www.toptal.com/developers/gitignore?templates=jetbrains
+
+### JetBrains ###
+# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider
+# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
+
+# User-specific stuff
+.idea/**/workspace.xml
+.idea/**/tasks.xml
+.idea/**/usage.statistics.xml
+.idea/**/dictionaries
+.idea/**/shelf
+
+# Generated files
+.idea/**/contentModel.xml
+
+# Sensitive or high-churn files
+.idea/**/dataSources/
+.idea/**/dataSources.ids
+.idea/**/dataSources.local.xml
+.idea/**/sqlDataSources.xml
+.idea/**/dynamic.xml
+.idea/**/uiDesigner.xml
+.idea/**/dbnavigator.xml
+
+# Gradle
+.idea/**/gradle.xml
+.idea/**/libraries
+
+# Gradle and Maven with auto-import
+# When using Gradle or Maven with auto-import, you should exclude module files,
+# since they will be recreated, and may cause churn.  Uncomment if using
+# auto-import.
+# .idea/artifacts
+# .idea/compiler.xml
+# .idea/jarRepositories.xml
+# .idea/modules.xml
+# .idea/*.iml
+# .idea/modules
+# *.iml
+# *.ipr
+
+# CMake
+cmake-build-*/
+
+# Mongo Explorer plugin
+.idea/**/mongoSettings.xml
+
+# File-based project format
+*.iws
+
+# IntelliJ
+out/
+
+# mpeltonen/sbt-idea plugin
+.idea_modules/
+
+# JIRA plugin
+atlassian-ide-plugin.xml
+
+# Cursive Clojure plugin
+.idea/replstate.xml
+
+# Crashlytics plugin (for Android Studio and IntelliJ)
+com_crashlytics_export_strings.xml
+crashlytics.properties
+crashlytics-build.properties
+fabric.properties
+
+# Editor-based Rest Client
+.idea/httpRequests
+
+# Android studio 3.1+ serialized cache file
+.idea/caches/build_file_checksums.ser
+
+### JetBrains Patch ###
+# Comment Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-215987721
+
+# *.iml
+# modules.xml
+# .idea/misc.xml
+# *.ipr
+
+# Sonarlint plugin
+# https://plugins.jetbrains.com/plugin/7973-sonarlint
+.idea/**/sonarlint/
+
+# SonarQube Plugin
+# https://plugins.jetbrains.com/plugin/7238-sonarqube-community-plugin
+.idea/**/sonarIssues.xml
+
+# Markdown Navigator plugin
+# https://plugins.jetbrains.com/plugin/7896-markdown-navigator-enhanced
+.idea/**/markdown-navigator.xml
+.idea/**/markdown-navigator-enh.xml
+.idea/**/markdown-navigator/
+
+# Cache file creation bug
+# See https://youtrack.jetbrains.com/issue/JBR-2257
+.idea/$CACHE_FILE$
+
+# CodeStream plugin
+# https://plugins.jetbrains.com/plugin/12206-codestream
+.idea/codestream.xml
+
+# End of https://www.toptal.com/developers/gitignore/api/jetbrains
+
+# Created by https://www.toptal.com/developers/gitignore/api/python
+# Edit at https://www.toptal.com/developers/gitignore?templates=python
+
+### Python ###
+# Byte-compiled / optimized / DLL files
+__pycache__/
+*.py[cod]
+*$py.class
+
+# C extensions
+*.so
+
+# Distribution / packaging
+.Python
+build/
+develop-eggs/
+dist/
+downloads/
+eggs/
+.eggs/
+lib/
+lib64/
+parts/
+sdist/
+var/
+wheels/
+pip-wheel-metadata/
+share/python-wheels/
+*.egg-info/
+.installed.cfg
+*.egg
+MANIFEST
+
+# PyInstaller
+#  Usually these files are written by a python script from a template
+#  before PyInstaller builds the exe, so as to inject date/other infos into it.
+*.manifest
+*.spec
+
+# Installer logs
+pip-log.txt
+pip-delete-this-directory.txt
+
+# Unit test / coverage reports
+htmlcov/
+.tox/
+.nox/
+.coverage
+.coverage.*
+.cache
+nosetests.xml
+coverage.xml
+*.cover
+*.py,cover
+.hypothesis/
+.pytest_cache/
+pytestdebug.log
+
+# Translations
+*.mo
+*.pot
+
+# Django stuff:
+*.log
+local_settings.py
+db.sqlite3
+db.sqlite3-journal
+
+# Flask stuff:
+instance/
+.webassets-cache
+
+# Scrapy stuff:
+.scrapy
+
+# Sphinx documentation
+docs/_build/
+doc/_build/
+
+# PyBuilder
+target/
+
+# Jupyter Notebook
+.ipynb_checkpoints
+
+# IPython
+profile_default/
+ipython_config.py
+
+# pyenv
+.python-version
+
+# pipenv
+#   According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
+#   However, in case of collaboration, if having platform-specific dependencies or dependencies
+#   having no cross-platform support, pipenv may install dependencies that don't work, or not
+#   install all needed dependencies.
+#Pipfile.lock
+
+# PEP 582; used by e.g. github.com/David-OConnor/pyflow
+__pypackages__/
+
+# Celery stuff
+celerybeat-schedule
+celerybeat.pid
+
+# SageMath parsed files
+*.sage.py
+
+# Environments
+.env
+.venv
+env/
+venv/
+ENV/
+env.bak/
+venv.bak/
+pythonenv*
+
+# Spyder project settings
+.spyderproject
+.spyproject
+
+# Rope project settings
+.ropeproject
+
+# mkdocs documentation
+/site
+
+# mypy
+.mypy_cache/
+.dmypy.json
+dmypy.json
+
+# Pyre type checker
+.pyre/
+
+# pytype static type analyzer
+.pytype/
+
+# profiling data
+.prof
+
+# End of https://www.toptal.com/developers/gitignore/api/python
+.idea/
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index aab5f1c..354c761 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -4,11 +4,11 @@
 
 repos:
 -   repo: https://github.com/python/black
-    rev: stable
+    rev: 20.8b1
     hooks:
     - id: black
 -   repo: https://github.com/fsfe/reuse-tool
-    rev: latest
+    rev: v0.12.1
     hooks:
     - id: reuse
 -   repo: https://github.com/pre-commit/pre-commit-hooks
@@ -17,3 +17,18 @@ repos:
     -   id: check-yaml
     -   id: end-of-file-fixer
     -   id: trailing-whitespace
+-   repo: https://github.com/pycqa/pylint
+    rev: pylint-2.7.1
+    hooks:
+    -   id: pylint
+        name: pylint (library code)
+        types: [python]
+        exclude: "^(docs/|examples/|setup.py$)"
+-   repo: local
+    hooks:
+    -   id: pylint_examples
+        name: pylint (examples code)
+        description: Run pylint rules on "examples/*.py" files
+        entry: /usr/bin/env bash -c
+        args: ['([[ ! -d "examples" ]] || for example in $(find . -path "./examples/*.py"); do pylint --disable=missing-docstring,invalid-name $example; done)']
+        language: system
diff --git a/adafruit_progressbar.py b/adafruit_progressbar.py
deleted file mode 100755
index 6a1d5ad..0000000
--- a/adafruit_progressbar.py
+++ /dev/null
@@ -1,149 +0,0 @@
-# SPDX-FileCopyrightText: 2020 Brent Rubell for Adafruit Industries
-#
-# SPDX-License-Identifier: MIT
-
-"""
-`adafruit_progressbar`
-================================================================================
-
-Dynamic progress bar widget for CircuitPython displays
-
-
-* Author(s): Brent Rubell
-
-Implementation Notes
---------------------
-
-**Software and Dependencies:**
-
-* Adafruit CircuitPython firmware for the supported boards:
-  https://github.com/adafruit/circuitpython/releases
-
-"""
-
-# imports
-import displayio
-
-__version__ = "0.0.0-auto.0"
-__repo__ = "https://github.com/brentru/Adafruit_CircuitPython_ProgressBar.git"
-
-# pylint: disable=too-many-arguments, too-few-public-methods
-class ProgressBar(displayio.TileGrid):
-    """A dynamic progress bar widget.
-
-    :param int x: The x-position of the top left corner.
-    :param int y: The y-position of the top left corner.
-    :param int width: The width of the progress bar.
-    :param int height: The height of the progress bar.
-    :param float progress: The percentage of the progress bar.
-    :param bar_color: The color of the progress bar. Can be a hex
-                                value for color.
-    :param int outline_color: The outline of the progress bar. Can be a hex
-                            value for color.
-    :param int stroke: Used for the outline_color
-
-    """
-
-    # pylint: disable=invalid-name
-    def __init__(
-        self,
-        x,
-        y,
-        width,
-        height,
-        progress=0.0,
-        bar_color=0x00FF00,
-        outline_color=0xFFFFFF,
-        stroke=1,
-    ):
-        assert isinstance(progress, float), "Progress must be a floating point value."
-        self._bitmap = displayio.Bitmap(width, height, 3)
-        self._palette = displayio.Palette(3)
-        self._palette[0] = 0x0
-        self._palette[1] = outline_color
-        self._palette[2] = bar_color
-
-        # _width and _height are already in use for blinka TileGrid
-        self._bar_width = width
-        self._bar_height = height
-
-        self._progress_val = 0.0
-        self.progress = self._progress_val
-        self.progress = progress
-
-        # draw outline rectangle
-        for _w in range(width):
-            for line in range(stroke):
-                self._bitmap[_w, line] = 1
-                self._bitmap[_w, height - 1 - line] = 1
-        for _h in range(height):
-            for line in range(stroke):
-                self._bitmap[line, _h] = 1
-                self._bitmap[width - 1 - line, _h] = 1
-        super().__init__(self._bitmap, pixel_shader=self._palette, x=x, y=y)
-
-    @property
-    def progress(self):
-        """The percentage of the progress bar expressed as a
-        floating point number.
-
-        """
-        return self._progress_val
-
-    @progress.setter
-    def progress(self, value):
-        """Draws the progress bar
-
-        :param float value: Progress bar value.
-        """
-        assert value <= 1.0, "Progress value may not be > 100%"
-        assert isinstance(
-            value, float
-        ), "Progress value must be a floating point value."
-        if self._progress_val > value:
-            # uncolorize range from width*value+margin to width-margin
-            # from right to left
-            _prev_pixel = max(2, int(self.width * self._progress_val - 2))
-            _new_pixel = max(int(self.width * value - 2), 2)
-            for _w in range(_prev_pixel, _new_pixel - 1, -1):
-                for _h in range(2, self.height - 2):
-                    self._bitmap[_w, _h] = 0
-        else:
-            # fill from the previous x pixel to the new x pixel
-            _prev_pixel = max(2, int(self.width * self._progress_val - 3))
-            _new_pixel = min(int(self.width * value - 2), int(self.width * 1.0 - 3))
-            for _w in range(_prev_pixel, _new_pixel + 1):
-                for _h in range(2, self.height - 2):
-                    self._bitmap[_w, _h] = 2
-        self._progress_val = value
-
-    @property
-    def fill(self):
-        """The fill of the progress bar. Can be a hex value for a color or ``None`` for
-        transparent.
-
-        """
-        return self._palette[0]
-
-    @property
-    def width(self):
-        """The width of the progress bar. In pixels, includes the border."""
-        return self._bar_width
-
-    @property
-    def height(self):
-        """The height of the progress bar. In pixels, includes the border."""
-        return self._bar_height
-
-    @fill.setter
-    def fill(self, color):
-        """Sets the fill of the progress bar. Can be a hex value for a color or ``None`` for
-        transparent.
-
-        """
-        if color is None:
-            self._palette[2] = 0
-            self._palette.make_transparent(0)
-        else:
-            self._palette[2] = color
-            self._palette.make_opaque(0)
diff --git a/adafruit_progressbar/__init__.py b/adafruit_progressbar/__init__.py
new file mode 100755
index 0000000..e833f27
--- /dev/null
+++ b/adafruit_progressbar/__init__.py
@@ -0,0 +1,593 @@
+# SPDX-FileCopyrightText: 2020 Brent Rubell for Adafruit Industries
+#
+# SPDX-License-Identifier: MIT
+
+"""
+`progressbar_base`
+================================================================================
+
+Dynamic progress bar widget for CircuitPython displays
+
+
+* Author(s): Brent Rubell and Hugo Dahl
+
+Implementation Notes
+--------------------
+
+**Software and Dependencies:**
+
+* Adafruit CircuitPython firmware for the supported boards:
+  https://github.com/adafruit/circuitpython/releases
+
+"""
+
+# imports
+try:
+    from typing import Tuple, Union, List
+except ImportError:
+    pass  # No harm if the module isn't located
+import displayio
+
+
+class ProgressBarBase(displayio.TileGrid):
+    """The base class for dynamic progress bar widgets.
+
+    :param position: The coordinates (x, y) of the top left corner
+    :type position: Tuple[int, int]
+    :param size: The size (width, height) of the progress bar
+    :type size: Tuple[int, int]
+    :param bar_color: The color of the bar representing the value. This can
+                            be a hexadecimal value for color (0x224466).
+                            Default: 0x00FF00 (Solid green)
+    :type bar_color: int
+    :param border_color: The color of the border around the progress bar. This
+                            can be a hexadecimal value for color (0x4488BB).
+                            Default: 0xFFFFFF (White)
+    :type border_color: int
+    :param fill_color: The colour of the bar representing the remainder of the
+                            value. i.e. if the current value is 42%, the 42 value
+                            is represented by the bar_color parameter. The remainder,
+                            58%, will be displayed in this color. This can also
+                            be a hexadecimal value for color (0xEE7755).
+                            Default: 0x000000 (Black)
+    :type fill_color: int
+    :param margin_size: Specify whether a margin between the border of the widget and the bar
+                              representing the value should be visible or not.
+                              Default: True
+    :type margin_size: bool
+    :param value_range: Specify the range of allowed values for which the progress
+                        should be displayed. When setting the "value" property,
+                        this range is the one against which its progression will be determined.
+                        Default: (0.0, 1.0)
+    :type value_range: Tuple[int, int] or Tuple[float, float]
+    """
+
+    # pylint: disable=too-many-arguments, too-many-instance-attributes
+    def __init__(
+        self,
+        position: Tuple[int, int],
+        size: Tuple[int, int],
+        value: Union[int, float] = 0,
+        bar_color=0x00FF00,
+        border_color=0xFFFFFF,
+        fill_color=0x000000,
+        border_thickness: int = 1,
+        margin_size: int = 1,
+        value_range: Union[Tuple[int, int], Tuple[float, float]] = (0, 100),
+    ) -> None:
+
+        assert (
+            value_range[0] < value_range[1]
+        ), "The minimum value must be less than the maximum value"
+
+        assert (
+            size[0] > 0 and size[1] > 0
+        ), "The width and the height must be greater than zero"
+
+        assert (
+            value_range[0] <= value <= value_range[1]
+        ), "The starting value must be within the range of minimum to maximum"
+
+        _edge_size = 2 * margin_size + 2 * border_thickness
+
+        assert _edge_size < size[0], (
+            "The size of the borders and margins combined must be "
+            "less than the width of the widget"
+        )
+
+        assert _edge_size < size[1], (
+            "The size of the borders and margins combined must be "
+            "less than the height of the widget"
+        )
+
+        self._progress = 0.0
+        self._widget_size = size
+        self._position = position
+
+        self._bitmap = displayio.Bitmap(size[0], size[1], 3)
+        self._palette = displayio.Palette(3)
+        self._border_thickness = border_thickness
+        self._margin_size = margin_size
+        self._range = value_range
+        self._progress = 0.0
+
+        self._old_value = self.minimum
+        self._value = self.minimum
+
+        self.fill = fill_color
+        self.bar_color = bar_color
+        self.border_color = border_color
+
+        # Setup value and old_value to handle the change to the new
+        # initial value later.
+        self._value = self.minimum
+        self._old_value = self.minimum
+
+        super().__init__(
+            self._bitmap,
+            pixel_shader=self._palette,
+            x=self._position[0],
+            y=self._position[1],
+        )
+
+        self._draw_outline()
+        self.value = value
+
+    #     _bitmap: displayio.Bitmap  # The bitmap used for the bar/value
+    #     _position: (int, int)  # The (x,y) coordinates of the top-left corner
+    #     _widget_size: (int, int)  # The dimensions of the progress bar
+    #     _palette: displayio.Palette(3)  # The palette to be used
+    #     _progress: float  # The value to represent, between 0.0 and 100.0
+    #     _border_thickness: int  # The thickness of the border around the control, in pixels
+    #     _margin_size: bool  # Whether we should display a margin between
+    #       the border and the value/bar
+    #     # The minimum and maximum values we can represent
+    #     _range: (int, int) or (float, float)
+
+    #     Color palette index to property mapping:
+    #       0:  Bar fill color
+    #       1:  Border color
+    #       2:  Background fill color
+
+    @property
+    def widget_size(self) -> int:
+        """The size at the outer edge of the control, returned as a tuple (width, height)
+
+        :rtype: int
+        """
+        return self._widget_size
+
+    @property
+    def widget_width(self) -> Tuple[int, int]:
+        """The total width of the widget, in pixels. Includes the border and margin.
+
+        :rtype: Tuple[int, int]
+        """
+        return self.widget_size[0]
+
+    @property
+    def border_thickness(self) -> int:
+        """Gets the currently configured thickness of the border (in pixels)
+
+        :rtype: int
+        """
+        return self._border_thickness
+
+    @property
+    def widget_height(self) -> int:
+        """The total height of the widget, in pixels. Includes the border and margin.
+
+        :rtype: int
+        """
+        return self.widget_size[1]
+
+    @property
+    def border_color(self) -> int:
+        """Returns the currently configured value for the color of the
+        outline (border) of the widget.
+
+        :rtype: int
+        """
+        return self._border_color
+
+    @border_color.setter
+    def border_color(self, color: Union[int, Tuple[int, int, int]]) -> None:
+        """Sets the color of the border of the widget. Set it to 'None'
+        if a border should still be part of the widget but not displayed.
+
+        :param color: The color to be used for the border
+        :type int/None/Tuple[int, int, int]:
+
+        :rtype: None
+        """
+
+        assert (
+            isinstance(color, int) or color is None
+        ), "A color must be represented by a integer value"
+
+        self._border_color = color
+
+        if color is None:
+            self._palette[1] = 0x00
+            self._palette.make_transparent(1)
+        else:
+            self._palette[1] = color
+            self._palette.make_opaque(1)
+
+    @property
+    def fill(self) -> int:
+        """The fill of the progress bar. Can be a hex value for a color or ``None`` for
+        transparent.
+
+        :rtype: int
+        """
+        return self._fill_color
+
+    @fill.setter
+    def fill(self, color: Union[int, Tuple[int, int, int]]) -> None:
+        """Sets the fill of the progress bar. Can be a hex value for a color or ``None`` for
+        transparent.
+
+        :param color: The color to use for the widget's background
+        :type color: int/None/Tuple[int, int, int]
+        """
+        self._fill_color = color
+        if color is None:
+            self._palette[0] = 0x00
+            self._palette.make_transparent(0)
+        else:
+            self._palette[0] = color
+            self._palette.make_opaque(0)
+
+    @property
+    def bar_color(self) -> int:
+        """The color of the bar's fill
+
+        :rtype: int/None
+        """
+
+        return self._bar_color
+
+    @bar_color.setter
+    def bar_color(self, color: Union[int, Tuple[int, int, int]]) -> None:
+        """Sets the color of the bar
+
+        :param color: The color to use for the bar
+        :type color: int/None/Tuple[int, int, int]
+
+        :rtype: None
+        """
+
+        self._bar_color = color
+
+        if color is None:
+            self._palette[2] = 0x00
+            self._palette.make_transparent(2)
+        else:
+            self._palette[2] = color
+            self._palette.make_opaque(2)
+
+    @property
+    def value(self) -> Union[int, float]:
+        """
+        The current value of the control, used to determine its progress/ratio
+        :rtype: int/float
+        """
+        return self._value
+
+    @value.setter
+    def value(self, value: Union[int, float]) -> None:
+        """Sets the current value of the progress within the min-max range
+
+        :param value: The new value for the progress status
+        :type value: int/float
+
+        :rtype: None
+        """
+
+        assert isinstance(
+            value, (int, float)
+        ), "The value to set must be either an integer or a float"
+
+        assert (
+            self.minimum <= value <= self.maximum
+        ), f"The value must be between minimum ({self.minimum}) and maximum ({self.maximum})"
+
+        # Save off the previous value, so we can pass it in the
+        # call to "Render"
+        self._old_value = self._value
+        self._value = value
+        # Convert value to float since we may be dealing with
+        # integer types, and we can't work with integer division
+        # to get a ratio (position) of "value" within range.
+        self._set_progress(self.get_value_ratio(value))
+
+    @property
+    def progress(self) -> float:
+        """Gets the current displayed value of the widget.
+
+        :return: The current progress ratio
+        :rtype: float
+        """
+        return self._progress
+
+    @progress.setter
+    def progress(self, value: float) -> None:
+        """Sets the current displayed value of the widget. This will update the
+        `value` property to an approximation based on the allowed range. The calculation
+        used to determine the approximate value is
+        `((self.minimum + (self.maximum - self.minimum)) * progress)`.
+        For the most accurate representation of a given value, it is recommended to set the
+        property "value" to the desired value.
+
+        Example: If the range for the widget is 0-10, setting a progress value of "35"
+        will result in `value` being "3.5", since 3.5 is the 35% value of the range between
+        0 and 10. The value determined from this method makes no assumptions or checks based on
+        the type of the "value" field.
+
+        :param value: The new value which should be displayed by the progress
+                            bar. Must be between 0.0-100.0
+        :type value: float
+
+        :rtype: None
+        """
+
+        assert [isinstance(value, (float, int)), "'progress' must be an int or a float"]
+
+        assert 0.0 <= value <= 100.0, "'progress' must be between 0 and 100"
+
+        self.value = (self.minimum + (self.maximum - self.minimum)) * (value * 0.01)
+
+    # Bit of a hack to be able to work around the shim "ProgressBar" class
+    # to be able to handle values as it used to.
+    def _set_progress(self, value: float) -> None:
+        """Sets the value for the underlying variable _progress, then
+        calls self.render() with the appropriate values.
+
+        :param value: The value to which self.progress should be set
+        :type value: float
+        :rtype: None
+        """
+
+        self._progress = round(value, 4)
+        self._render(self._old_value, self._value, value)
+
+    @property
+    def range(self) -> Tuple[Union[int, float], Union[int, float]]:
+        """The range which can be handled as a Tuple(min,max)
+
+        :rtype: Tuple(int/float, int/float)
+        """
+        return self._range
+
+    @property
+    def minimum(self) -> Union[int, float]:
+        """The minimum (lowest) value which can be displayed
+
+        :rtype: int/float
+        """
+        return self.range[0]
+
+    @property
+    def maximum(self) -> Union[int, float]:
+        """The maximum (highest) value which can be displayed
+
+        :rtype: int/float
+        """
+        return self.range[1]
+
+    def _draw_outline(self) -> None:
+        """Draws the outline (border) of the progressbar, with a thickness value
+        from self.border_thickness.
+
+        :rtype None:
+        """
+        stroke = self.border_thickness
+
+        # draw outline rectangle
+        for _w in range(self.widget_width):
+            for line in range(stroke):
+                self._bitmap[_w, line] = 1
+                self._bitmap[_w, self.widget_height - 1 - line] = 1
+        for _h in range(self.widget_height):
+            for line in range(stroke):
+                self._bitmap[line, _h] = 1
+                self._bitmap[self.widget_width - 1 - line, _h] = 1
+
+    def fill_width(self) -> int:
+        """Returns the amount of horizontal space within the widget
+        which can be used for value display. This is typically the
+        width of the widget as defined, minus any visually reserved space.
+
+        :rtype: int
+        """
+
+        return self.widget_width - self._get_fill_border_size()
+
+    def fill_height(self) -> int:
+        """Returns the amount of vertical space within the widget
+        which can be used for value display. This is typically the
+        width of the widget as defined, minus any visually reserved
+        space.
+
+        :rtype: int
+        """
+
+        return self.widget_height - self._get_fill_border_size()
+
+    def _get_fill_border_size(self) -> int:
+        """Determines any visual space reserved for the widget
+        based on the defined border thickness, and whether a margin
+        should be placed between the border and the bar.
+        The value is calculated as (2 x border_thickness) minus
+        (2 x margin_size). The value for margin_size is either 0 (zero)
+        or 1 (one) depending on the value of margin_size when the
+        widget was created.
+
+        :rtype: int
+        """
+
+        return (2 * self.border_thickness) + (2 * self.margin_size)
+
+    @property
+    def margin_size(self) -> int:
+        """Returns the size of the margin on a single side of the display
+
+        :return int:
+        """
+        return self._margin_size
+
+    @margin_size.setter
+    def margin_size(self, value: int) -> None:
+        """Sets the new size of the margin to be used between the border
+        (if displayed) and the value bar.
+
+        :param value: The new size of the margin between the border
+        and value bar on all sides of the widget.
+        :type value: int
+
+        :rtype: None
+        """
+
+        assert isinstance(value, int), "The margin size must be an integer"
+
+        margin_spacing = (2 * value) + (2 * self._border_thickness)
+
+        assert margin_spacing < self.widget_width, (
+            "The size of the borders and margins combined can total the same or more"
+            "than the widget's width."
+        )
+
+        assert margin_spacing < self.widget_height, (
+            "The size of the borders and margins combined can total the same or more"
+            "than the widget's height."
+        )
+
+        self._margin_size = value
+        self._set_progress(self._progress)  # For a render pass
+
+    def get_value_ratio(self, value: Union[int, float]) -> float:
+        """Gets the ratio (percentage) of a given value within the
+        range of self.minimum and self.maximum.
+
+        :param value: The value for which the ration should be calculated
+        :type value: int/float
+
+        :return: The ratio of value:range
+        :rtype: float
+        """
+
+        if self.maximum == self.minimum:
+            return 0.0
+
+        return (float(value) - self.minimum) / (self.maximum - self.minimum)
+
+    @classmethod
+    def _get_value_sizes(cls, _old_ratio: float, _new_ratio: float) -> Tuple[int, int]:
+        return 0, 0
+
+    @classmethod
+    def _get_max_fill_size(cls) -> int:
+        return 0
+
+    def _get_ratios(
+        self, _old_value: Union[int, float], _new_value: Union[int, float]
+    ) -> Tuple[float, float]:
+        return self.get_value_ratio(_old_value), self.get_value_ratio(_new_value)
+
+    def _adjust_size_for_range_limits(
+        self, _new_value_size: int, _new_value: Union[int, float]
+    ) -> int:
+        # If we have *ANY* value other than "zero" (minimum), we should
+        #   have at least one element showing
+        if _new_value_size == 0 and _new_value > self.minimum:
+            _new_value_size = 1
+
+        # Conversely, if we have *ANY* value other than 100% (maximum),
+        #   we should NOT show a full bar.
+        if _new_value_size == self._get_max_fill_size() and _new_value < self.maximum:
+            _new_value_size -= 1
+
+        return _new_value_size
+
+    def _get_sizes_min_max(self) -> Tuple[int, int]:
+        return 0, min(self.fill_width(), self.fill_height())
+
+    @classmethod
+    def _invert_fill_direction(cls) -> bool:
+        return False
+
+    def _get_horizontal_fill(
+        self, _start: int, _end: int, _incr: int
+    ) -> Tuple[int, int, int]:
+        return 0, self.fill_width(), 1  # Subclass must return values
+
+    def _get_vertical_fill(
+        self, _start: int, _end: int, _incr: int
+    ) -> Tuple[int, int, int]:
+        return 0, self.fill_height(), 1  # Subclass must return values
+
+    # pylint: disable=too-many-locals
+    def _render(
+        self,
+        _old_value: Union[int, float],
+        _new_value: Union[int, float],
+        _progress_value: float,
+    ) -> None:
+        """
+        Does the work of actually creating the graphical representation of
+            the value (percentage, aka "progress") to be displayed.
+
+        :param _old_value: The previously displayed value
+        :type _old_value: int/float
+        :param _new_value: The new value to display
+        :type _new_value: int/float
+        :param _progress_value: The value to display, as a percentage, represented
+            by a float from 0.0 to 1.0 (0% to 100%)
+        :type _progress_value: float
+        :rtype: None
+        """
+
+        _prev_ratio, _new_ratio = self._get_ratios(_old_value, _new_value)
+        _old_value_size, _new_value_size = self._get_value_sizes(
+            _prev_ratio, _new_ratio
+        )
+
+        # Adjusts for edge cases, such as 0-width non-zero value, or 100% width
+        # non-maximum values
+        _new_value_size = self._adjust_size_for_range_limits(
+            _new_value_size, _new_value
+        )
+
+        # Default values for increasing value
+        _color = 2
+        _incr = 1
+        _start = max(_old_value_size, 0)
+        _end = max(_new_value_size, 0)
+
+        if _old_value_size >= _new_value_size:
+            # Override defaults to be decreasing
+            _color = 0  # Clear
+            _incr = -1  # Iterate range downward
+            _start = max(_old_value_size, 0) - 1
+            _end = max(_new_value_size, 0) - 1
+            # If we're setting to minimum, make sure we're clearing by
+            #  starting one "bar" further
+            if _new_value == self.minimum:
+                _start += 1
+
+        _render_offset = self.margin_size + self.border_thickness
+
+        vert_start, vert_end, vert_incr = self._get_vertical_fill(_start, _end, _incr)
+        horiz_start, horiz_end, horiz_incr = self._get_horizontal_fill(
+            _start, _end, _incr
+        )
+
+        vert_start += _render_offset
+        vert_end += _render_offset
+        horiz_start += _render_offset
+        horiz_end += _render_offset
+
+        for vertical_position in range(vert_start, vert_end, vert_incr):
+            for horizontal_position in range(horiz_start, horiz_end, horiz_incr):
+                self._bitmap[horizontal_position, vertical_position] = _color
diff --git a/adafruit_progressbar/horizontalprogressbar.py b/adafruit_progressbar/horizontalprogressbar.py
new file mode 100755
index 0000000..a82abdb
--- /dev/null
+++ b/adafruit_progressbar/horizontalprogressbar.py
@@ -0,0 +1,169 @@
+# SPDX-FileCopyrightText: 2020 Brent Rubell for Adafruit Industries
+#
+# SPDX-License-Identifier: MIT
+
+"""
+`horizontalprogressbar`
+================================================================================
+
+Dynamic progress bar widget for CircuitPython displays
+
+
+* Author(s): Brent Rubell, Hugo Dahl
+
+Implementation Notes
+--------------------
+
+**Software and Dependencies:**
+
+* Adafruit CircuitPython firmware for the supported boards:
+  https://github.com/adafruit/circuitpython/releases
+
+"""
+
+try:
+    from typing import Tuple, Union
+except ImportError:
+    pass  # Not needed for execution
+from . import ProgressBarBase
+
+
+# pylint: disable=too-few-public-methods
+class HorizontalFillDirection:
+    """This enum is used to specify the direction in which the progress
+    bar's display bar should fill as the value represented increases."""
+
+    # pylint: disable=pointless-string-statement
+    """Fills from the left-hand side toward the right"""
+    LEFT_TO_RIGHT = 0
+    # pylint: disable=pointless-string-statement
+    """Specifies the default fill direction (LEFT_TO_RIGHT)"""
+    DEFAULT = LEFT_TO_RIGHT
+    # pylint: disable=pointless-string-statement
+    """Fill from the right-hand side toward the left"""
+    RIGHT_TO_LEFT = 1
+
+
+class HorizontalProgressBar(ProgressBarBase):
+    """A dynamic progress bar widget.
+
+    The anchor position is the position where the control would start if it
+    were being read visually or on paper, where the (0, 0) position is
+    the lower-left corner for ascending progress bars (fills from the bottom to
+    to the top in vertical bars, or from the left to the right in horizontal
+    progress bars), upper-left corner for descending progress bars (fills from
+    the top to the bottom).
+
+    Using the diagrams below, the bar will fill in the following directions::
+
+        --------------------------------
+        | Left-to-right   |  1-3 to 2-4 |
+        --------------------------------
+        | Right-to-left  |  2-4 to 1-3 |
+        --------------------------------
+
+        Horizontal
+
+        1-----------------------2
+        |                       |
+        |                       |
+        3-----------------------4
+
+
+    :param position: The coordinates of the top-left corner of progress bar.
+    :type position: Tuple[int, int]
+    :param size: The size in (width, height) of the progress bar, in pixels
+    :type size: Tuple[int, int]
+    :param min_value: The lowest value which can be displayed by the progress bar.
+        When the "value" property is set to the same value, no bar is displayed.
+    :type min_value: int, float
+    :param max_value:  This highest value which can be displayed by the progress bar.
+        When the "value" property is set to the same value, the bar shows as full.
+    :type max_value: int, float
+    :param value: The starting value to be displayed. Must be between the values of
+        min_value and max_value, inclusively.
+    :type value: int, float
+    :param bar_color: The color of the value portion of the progress bar.
+        Can be a hex value for color (i.e. 0x225588).
+    :type bar_color: int, Tuple[byte, byte, byte]
+    :param outline_color: The colour for the outline of the progress bar.
+        Can be a hex value for color (i.e. 0x225588).
+    :type outline_color: int, Tuple[byte, byte, byte]
+    :param fill_color: The colour for the background within the progress bar.
+        Can be a hex value for color (i.e. 0x225588).
+    :type fill_color: int, Tuple[byte, byte, byte]
+    :param border_thickness: The thickness of the outer border of the widget. If it is
+        1 or larger, will be displayed with the color of the "outline_color" parameter.
+    :type border_thickness: int
+    :param margin_size: The thickness (in pixels) of the margin between the border and
+        the bar. If it is 1 or larger, will be filled in by the color of the
+        "fill_color" parameter.
+    :type margin_size: int
+    :param direction: The direction of the fill
+    :type direction: HorizontalFillDirection
+
+    """
+
+    # pylint: disable=too-many-arguments
+    def __init__(
+        self,
+        position: Tuple[int, int],
+        size: Tuple[int, int],
+        min_value: Union[int, float] = 0,
+        max_value: Union[int, float] = 100,
+        value: Union[int, float] = 0,
+        bar_color: Union[int, Tuple[int, int, int]] = 0x00FF00,
+        outline_color: Union[int, Tuple[int, int, int]] = 0xFFFFFF,
+        fill_color: Union[int, Tuple[int, int, int]] = 0x444444,
+        border_thickness: int = 1,
+        margin_size: int = 1,
+        direction: HorizontalFillDirection = HorizontalFillDirection.DEFAULT,
+    ) -> None:
+
+        # Store the "direction" value locally. While they may appear to
+        # "relate" with the values of the vertical bar, their handling
+        # is too different to be stored in the same underlying property,
+        # which could lead to confusion
+        self._direction = direction
+
+        super().__init__(
+            position,
+            size,
+            value,
+            bar_color,
+            outline_color,
+            fill_color,
+            border_thickness,
+            margin_size,
+            (min_value, max_value),
+        )
+
+    def _get_sizes_min_max(self) -> Tuple[int, int]:
+        return 0, self.fill_width()
+
+    def _get_value_sizes(self, _old_ratio: float, _new_ratio: float) -> Tuple[int, int]:
+        return int(_old_ratio * self.fill_width()), int(_new_ratio * self.fill_width())
+
+    def _get_horizontal_fill(
+        self, _start: int, _end: int, _incr: int
+    ) -> Tuple[int, int, int]:
+        if not self._invert_fill_direction():
+            return _start, _end, _incr
+
+        base_offset = self.fill_width() - 1
+
+        return base_offset - _start, base_offset - _end, _incr * -1
+
+    # pylint: disable=protected-access
+    def _get_vertical_fill(
+        self, _start: int, _end: int, _incr: int
+    ) -> Tuple[int, int, int]:
+        return ProgressBarBase._get_vertical_fill(self, _start, _end, _incr)
+
+    # pylint: enable=protected-access
+
+    def _invert_fill_direction(self) -> bool:
+        return self._direction == HorizontalFillDirection.RIGHT_TO_LEFT
+
+    def _get_max_fill_size(self):
+        return self.fill_width()
diff --git a/adafruit_progressbar/progressbar.py b/adafruit_progressbar/progressbar.py
new file mode 100755
index 0000000..5f1eeb3
--- /dev/null
+++ b/adafruit_progressbar/progressbar.py
@@ -0,0 +1,105 @@
+# SPDX-FileCopyrightText: 2020 Brent Rubell for Adafruit Industries
+#
+# SPDX-License-Identifier: MIT
+
+"""
+`progressbar`
+================================================================================
+
+Dynamic progress bar widget for CircuitPython displays
+
+
+* Author(s): Brent Rubell
+
+Implementation Notes
+--------------------
+
+**Software and Dependencies:**
+
+* Adafruit CircuitPython firmware for the supported boards:
+  https://github.com/adafruit/circuitpython/releases
+
+"""
+
+# imports
+from adafruit_progressbar.horizontalprogressbar import HorizontalProgressBar
+
+
+# pylint: disable=too-many-arguments, too-few-public-methods
+class ProgressBar(HorizontalProgressBar):
+    """A dynamic progress bar widget.
+
+    NOTE: This class is made available for backward compatibility with v1.x of
+    the adafruit_progressbar library. New uses should not use this class, but
+    instead, use its successor, HorizontalProgressBar.
+
+    :param x: The x-position of the top left corner.
+    :type x: int
+    :param y: The y-position of the top left corner.
+    :type y: int
+    :param width: The width of the progress bar.
+    :type width: int
+    :param height: The height of the progress bar.
+    :type height: int
+    :param bar_color: The color of the progress bar. Can be a hex
+        value for color.
+    :param outline_color: The outline of the progress bar. Can be a hex
+        value for color.
+    :type outline_color: int
+    :param stroke: Used for the outline_color
+    :type stroke: int
+    """
+
+    # pylint: disable=invalid-name
+    def __init__(
+        self,
+        x: int,
+        y: int,
+        width: int,
+        height: int,
+        progress: float = 0.0,
+        bar_color=0x00FF00,
+        outline_color=0xFFFFFF,
+        stroke: int = 1,
+    ) -> None:
+
+        # This needs to remain for backward compatibility, the default ProgressBar class
+        # should only be able to handle values of type "float"
+        assert isinstance(progress, float), "Progress must be a floating point value."
+
+        super().__init__(
+            (x, y),
+            (width, height),
+            0.0,
+            1.0,
+            progress,
+            bar_color,
+            outline_color,
+            0x000000,
+            border_thickness=stroke,
+        )
+
+    # Override the base "progress" property to correctly handle values
+    # in the v1 range of 0.0-1.0
+    @property
+    def progress(self) -> float:
+        """Gets the progress value displayed
+
+        :rtype float:
+        """
+        return self._progress
+
+    @progress.setter
+    def progress(self, value: float) -> None:
+        """Sets the progress value for display
+
+        :param value: The progress value to be set, between 0.0 and 1.0
+        :type value: float
+
+        :rtype: None
+        """
+
+        # Disable pylint since the property "value" is defined in the
+        # base class "ProgressBarBase"
+        # pylint: disable=access-member-before-definition
+        self.value = value
diff --git a/adafruit_progressbar/verticalprogressbar.py b/adafruit_progressbar/verticalprogressbar.py
new file mode 100755
index 0000000..31c5927
--- /dev/null
+++ b/adafruit_progressbar/verticalprogressbar.py
@@ -0,0 +1,135 @@
+# SPDX-FileCopyrightText: Copyright (c) 2020-2021 Brent Rubell for Adafruit Industries, Hugo Dahl
+#
+# SPDX-License-Identifier: MIT
+
+"""
+`verticalprogressbar`
+================================================================================
+
+Dynamic progress bar widget for CircuitPython displays
+
+
+* Author(s): Brent Rubell, Hugo Dahl
+
+Implementation Notes
+--------------------
+
+**Software and Dependencies:**
+
+* Adafruit CircuitPython firmware for the supported boards:
+  https://github.com/adafruit/circuitpython/releases
+
+"""
+
+try:
+    from typing import Tuple
+except ImportError:
+    pass  # Not needed for execution
+from . import ProgressBarBase
+from .horizontalprogressbar import HorizontalProgressBar
+
+
+# pylint: disable=too-few-public-methods
+class VerticalFillDirection:
+    """This enum is used to specify the direction in which the progress
+    bar's display bar should fill as the value represented increases."""
+
+    # pylint: disable=pointless-string-statement
+    """Fills from the bottom up toward the top"""
+    BOTTOM_TO_TOP = 0
+    # pylint: disable=pointless-string-statement
+    """Default fill direction (BOTTOM_TO_TOP)"""
+    DEFAULT = BOTTOM_TO_TOP
+    # pylint: disable=pointless-string-statement
+    """Fills from the top down toward the bottom"""
+    TOP_TO_BOTTOM = 1
+
+
+# pylint: disable=too-many-arguments, too-few-public-methods, too-many-instance-attributes
+class VerticalProgressBar(HorizontalProgressBar):
+    """A dynamic progress bar widget.
+
+    The anchor position is the position where the control would start if it
+    were being read visually or on paper, where the (0, 0) position is
+    the lower-left corner for ascending progress bars (fills from the bottom to
+    to the top in vertical bars, or from the left to the right in horizontal
+    progress bars), upper-left corner for descending progress bars (fills from
+    the top to the bottom).
+
+    Using the diagrams below, the bar will fill in the following directions::
+
+        --------------------------------
+        | Bottom-to-top  |  3-4 to 1-2 |
+        --------------------------------
+        | Top-to-bottom  |  1-2 to 3-4 |
+        --------------------------------
+
+        1--2
+        |  |
+        |  |
+        |  |
+        |  |
+        3--4
+
+    :param position: The coordinates of the top-left corner of progress bar.
+    :type position: Tuple[int, int]
+    :param size: The size in (width, height) of the progress bar, in pixels
+    :type size: Tuple[int, int]
+    :param min_value: The lowest value which can be displayed by the progress bar.
+        When the "value" property is set to the same value, no bar is displayed.
+    :type min_value: int, float
+    :param max_value:  This highest value which can be displayed by the progress bar.
+        When the "value" property is set to the same value, the bar shows as full.
+    :type max_value: int, float
+    :param value: The starting value to be displayed. Must be between the values of
+        min_value and max_value, inclusively.
+    :type value: int, float
+    :param bar_color: The color of the value portion of the progress bar.
+        Can be a hex value for color (i.e. 0x225588).
+    :type bar_color: int, Tuple[byte, byte, byte]
+    :param outline_color: The colour for the outline of the progress bar.
+        Can be a hex value for color (i.e. 0x225588).
+    :type outline_color: int, Tuple[byte, byte, byte]
+    :param fill_color: The colour for the background within the progress bar.
+        Can be a hex value for color (i.e. 0x225588).
+    :type fill_color: int, Tuple[byte, byte, byte]
+    :param border_thickness: The thickness of the outer border of the widget. If it is
+        1 or larger, will be displayed with the color of the "outline_color" parameter.
+    :type border_thickness: int
+    :param margin_size: The thickness (in pixels) of the margin between the border and
+        the bar. If it is 1 or larger, will be filled in by the color of the
+        "fill_color" parameter.
+    :type margin_size: int
+    :param direction: The direction of the fill
+    :type direction: VerticalFillDirection
+
+    """
+
+    def _get_sizes_min_max(self) -> Tuple[int, int]:
+        return 0, self.fill_height()
+
+    def _get_value_sizes(self, _old_ratio: float, _new_ratio: float) -> Tuple[int, int]:
+        return int(_old_ratio * self.fill_height()), int(
+            _new_ratio * self.fill_height()
+        )
+
+    # pylint: disable=protected-access
+    def _get_horizontal_fill(
+        self, _start: int, _end: int, _incr: int
+    ) -> Tuple[int, int, int]:
+        return ProgressBarBase._get_horizontal_fill(self, _start, _end, _incr)
+
+    # pylint: enable=protected-access
+
+    def _get_vertical_fill(
+        self, _start: int, _end: int, _incr: int
+    ) -> Tuple[int, int, int]:
+        if not self._invert_fill_direction():
+            return _start, _end, _incr
+
+        base_offset = self.fill_height() - 1
+
+        return base_offset - _start, base_offset - _end, _incr * -1
+
+    def _invert_fill_direction(self) -> bool:
+        return self._direction == VerticalFillDirection.BOTTOM_TO_TOP
diff --git a/docs/api.rst b/docs/api.rst
index 7a3376a..c25e47d 100644
--- a/docs/api.rst
+++ b/docs/api.rst
@@ -1,8 +1,22 @@
+API Definition
+---------------------
 
 .. If you created a package, create one automodule per module in the package.
 
 .. 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"
 
-.. automodule:: adafruit_progressbar
+.. automodule:: adafruit_progressbar.horizontalprogressbar
    :members:
+   :show-inheritance:
+   :inherited-members:
+
+.. automodule:: adafruit_progressbar.progressbar
+   :members:
+   :show-inheritance:
+   :inherited-members:
+
+.. automodule:: adafruit_progressbar.verticalprogressbar
+   :members:
+   :show-inheritance:
+   :inherited-members:
diff --git a/examples/progressbar_combined.py b/examples/progressbar_combined.py
new file mode 100644
index 0000000..ce3c2a1
--- /dev/null
+++ b/examples/progressbar_combined.py
@@ -0,0 +1,53 @@
+#!/usr/bin/env python3
+
+# SPDX-FileCopyrightText: 2021 Hugo Dahl for Adafruit Industries
+# SPDX-License-Identifier: MIT
+
+# Before you can run this example, you will need to install the
+# required libraries identifies in the `requirements.txt` file.
+# You can do so automatically by using the "pip" utility.
+
+import time
+import board
+import displayio
+from adafruit_progressbar.horizontalprogressbar import HorizontalProgressBar
+from adafruit_progressbar.verticalprogressbar import VerticalProgressBar
+
+# Make the display context
+splash = displayio.Group(max_size=10)
+board.DISPLAY.show(splash)
+
+# set horizontal progress bar width and height relative to board's display
+h_width = board.DISPLAY.width - 40
+h_height = 30
+
+v_width = 30
+v_height = 140
+
+h_x = 20
+h_y = 20
+
+v_x = 60
+v_y = 70
+
+# Create a new progress_bar objects at their x, y locations
+progress_bar = HorizontalProgressBar((h_x, h_y), (h_width, h_height), 0, 100)
+vert_progress_bar = VerticalProgressBar((v_x, v_y), (v_width, v_height), 0, 200)
+
+# Append progress_bars to the splash group
+splash.append(progress_bar)
+splash.append(vert_progress_bar)
+
+current_progress = 0.0
+while True:
+    # range end is exclusive so we need to use 1 bigger than max number that we want
+    for current_progress in range(0, 101, 1):
+        print("Progress: {}%".format(current_progress))
+        progress_bar.value = current_progress
+        vert_progress_bar.value = current_progress * 2
+        time.sleep(0.01)
+    time.sleep(0.3)
+    # reset to empty
+    progress_bar.value = 0
+    vert_progress_bar.value = 0
+    time.sleep(0.3)
diff --git a/examples/progressbar_displayio_blinka.py b/examples/progressbar_displayio_blinka.py
new file mode 100644
index 0000000..a4e5739
--- /dev/null
+++ b/examples/progressbar_displayio_blinka.py
@@ -0,0 +1,122 @@
+#!/usr/bin/env python3
+
+# SPDX-FileCopyrightText: 2021 Hugo Dahl for Adafruit Industries
+# SPDX-License-Identifier: MIT
+
+# Before you can run this example, you will need to install the
+# required libraries identifies in the `requirements.txt` file.
+# You can do so automatically by using the "pip" utility.
+
+import time
+import sys
+import displayio
+from blinka_displayio_pygamedisplay import PyGameDisplay
+from adafruit_progressbar.progressbar import ProgressBar
+from adafruit_progressbar.horizontalprogressbar import (
+    HorizontalProgressBar,
+    HorizontalFillDirection,
+)
+from adafruit_progressbar.verticalprogressbar import (
+    VerticalProgressBar,
+    VerticalFillDirection,
+)
+
+display = PyGameDisplay(width=320, height=240, auto_refresh=False)
+splash = displayio.Group(max_size=10)
+display.show(splash)
+
+color_bitmap = displayio.Bitmap(display.width, display.height, 1)
+color_palette = displayio.Palette(1)
+color_palette[0] = 0x2266AA  # Teal-ish-kinda
+
+bg_sprite = displayio.TileGrid(color_bitmap, pixel_shader=color_palette, x=0, y=0)
+splash.append(bg_sprite)
+
+progress_bar = ProgressBar(
+    width=180,
+    height=40,
+    x=10,
+    y=20,
+    progress=0.0,
+    bar_color=0x1100FF,
+    outline_color=0x777777,
+)
+splash.append(progress_bar)
+
+horizontal_bar = HorizontalProgressBar(
+    (10, 80),
+    (180, 40),
+    fill_color=0x778822,
+    outline_color=0x0000FF,
+    bar_color=0x00FF00,
+    direction=HorizontalFillDirection.LEFT_TO_RIGHT,
+)
+splash.append(horizontal_bar)
+
+horizontal_thermometer = HorizontalProgressBar(
+    (10, 140),
+    (180, 40),
+    value=-10,
+    min_value=(-40),
+    max_value=130,
+    fill_color=0x00FF00,
+    outline_color=0x0000FF,
+    bar_color=0x22BB88,
+    direction=HorizontalFillDirection.RIGHT_TO_LEFT,
+)
+splash.append(horizontal_thermometer)
+
+vertical_bar = VerticalProgressBar(
+    (200, 30),
+    (32, 180),
+    direction=VerticalFillDirection.TOP_TO_BOTTOM,
+)
+splash.append(vertical_bar)
+
+vertical_thermometer = VerticalProgressBar(
+    (260, 30),
+    (32, 180),
+    min_value=-40,
+    max_value=130,
+    direction=VerticalFillDirection.BOTTOM_TO_TOP,
+)
+splash.append(vertical_thermometer)
+
+test_value_range_1 = [99, 100, 99, 1, 0, 1]
+test_value_range_2 = [120, 130, 129, -20, -39, -40, -28]
+delay = 2
+_incr = 1
+
+# Must check display.running in the main loop!
+while display.running:
+
+    print("\nDemonstration of legacy functionality and syntax, increment by 0.01")
+    for val in range(0, 101):
+        if not display.running:
+            sys.exit(0)
+        _use_value = round((val * 0.01), 2)
+        if (val % 10) == 0:
+            print(f"Value has reached {_use_value:2}")
+        progress_bar.progress = round(_use_value, 2)
+        display.refresh()
+        time.sleep(0.05)
+
+    print("\nDemonstration of values between 0 and 100 - Horizontal and vertical")
+    for val in test_value_range_1:
+        if not display.running:
+            sys.exit(0)
+        print(f"Setting value to {val}")
+        vertical_bar.value = val
+        horizontal_bar.value = val
+        display.refresh()
+        time.sleep(delay)
+
+    print("\nDemonstration of Fahrenheit range -40 and 130 - Horizontal and vertical")
+    for val in test_value_range_2:
+        if not display.running:
+            sys.exit(0)
+        print(f"Setting value to {val}")
+        vertical_thermometer.value = val
+        horizontal_thermometer.value = val
+        display.refresh()
+        time.sleep(delay)
diff --git a/examples/progressbar_matrixportal.py b/examples/progressbar_matrixportal.py
new file mode 100644
index 0000000..ab69eac
--- /dev/null
+++ b/examples/progressbar_matrixportal.py
@@ -0,0 +1,185 @@
+# SPDX-FileCopyrightText: 2021 ladyada for Adafruit Industries
+# SPDX-License-Identifier: MIT
+
+# Source: https://github.com/ajs256/matrixportal-weather-display
+
+# ############## IMPORTS ###############
+
+# HARDWARE
+import time
+import board
+
+# DISPLAY
+import displayio  # Main display library
+import framebufferio  # For showing things on the display
+import rgbmatrix  # For talking to matrices specifically
+
+# CONTROLS
+
+import digitalio
+
+from adafruit_progressbar.horizontalprogressbar import (
+    HorizontalProgressBar,
+    HorizontalFillDirection,
+)
+from adafruit_progressbar.verticalprogressbar import (
+    VerticalProgressBar,
+    VerticalFillDirection,
+)
+
+# ############## DISPLAY SETUP ###############
+
+# If there was a display before (protomatter, LCD, or E-paper), release it so
+# we can create ours
+displayio.release_displays()
+
+print("Setting up RGB matrix")
+
+# This next call creates the RGB Matrix object itself. It has the given width
+# and height.
+#
+# These lines are for the Matrix Portal. If you're using a different board,
+# check the guide to find the pins and wiring diagrams for your board.
+# If you have a matrix with a different width or height, change that too.
+matrix = rgbmatrix.RGBMatrix(
+    width=64,
+    height=32,
+    bit_depth=3,
+    rgb_pins=[
+        board.MTX_R1,
+        board.MTX_G1,
+        board.MTX_B1,
+        board.MTX_R2,
+        board.MTX_G2,
+        board.MTX_B2,
+    ],
+    addr_pins=[board.MTX_ADDRA, board.MTX_ADDRB, board.MTX_ADDRC, board.MTX_ADDRD],
+    clock_pin=board.MTX_CLK,
+    latch_pin=board.MTX_LAT,
+    output_enable_pin=board.MTX_OE,
+)
+
+# Associate the RGB matrix with a Display so that we can use displayio features
+display = framebufferio.FramebufferDisplay(matrix)
+
+print("Adding display group")
+group = displayio.Group(max_size=5)  # Create a group to hold all our labels
+display.show(group)
+
+print("Creating progress bars and adding to group")
+
+# A horizontal percentage progress bar, valued at 60%
+progress_bar = HorizontalProgressBar((2, 8), (40, 8), value=60)
+group.insert(0, progress_bar)
+
+# Another progress bar, with explicit range and fill from the right
+ranged_bar = HorizontalProgressBar(
+    (2, 20),
+    (40, 8),
+    value=40,
+    min_value=0,
+    max_value=100,
+    direction=HorizontalFillDirection.RIGHT_TO_LEFT,
+)
+group.insert(1, ranged_bar)
+
+# Sample thermometer from -40C to 50C, with a value of +15C
+vertical_bar = VerticalProgressBar(
+    (44, 4),
+    (8, 24),
+    min_value=-40,
+    max_value=50,
+    value=15,
+    bar_color=0x1111FF,
+    fill_color=None,
+    margin_size=0,
+    outline_color=0x2222AA,
+)
+group.insert(2, vertical_bar)
+
+vertical_bar2 = VerticalProgressBar(
+    (54, 4),
+    (8, 24),
+    min_value=-40,
+    max_value=50,
+    value=15,
+    bar_color=0x1111FF,
+    fill_color=None,
+    margin_size=0,
+    outline_color=0x2222AA,
+    direction=VerticalFillDirection.TOP_TO_BOTTOM,
+)
+group.insert(3, vertical_bar2)
+
+# Countdown to the start of the bars demo
+countdown_bar = HorizontalProgressBar(
+    (2, 2),
+    (20, 5),
+    0,
+    5,
+    value=5,
+    bar_color=0x11FF11,
+    fill_color=0x333333,
+    border_thickness=0,
+    margin_size=0,
+)
+
+countdown_end_color = 0xFF1111
+
+group.insert(4, countdown_bar)
+# group.insert(0, countdown_bar)
+
+print("Progress bars added. Starting demo...")
+
+print("Using countdown bar")
+
+for timer in range(countdown_bar.maximum, countdown_bar.minimum, -1):
+    bar_color_to_set = (0x20 * (6 - timer) + 20, (0x20 * (timer - 1)) + 20, 0x10)
+    countdown_bar.bar_color = bar_color_to_set
+    countdown_bar.value = timer
+    time.sleep(1)
+
+print("Removing countdown bar")
+
+countdown_bar.value = 0
+group.remove(countdown_bar)
+
+progress_bar_value = 0.0
+progress_bar_incr = 3.0
+
+button1 = digitalio.DigitalInOut(board.BUTTON_UP)
+button1.switch_to_input(digitalio.Pull.UP)
+button2 = digitalio.DigitalInOut(board.BUTTON_DOWN)
+button2.switch_to_input(digitalio.Pull.UP)
+
+
+print("Start forever loop")
+while True:
+
+    print("Setting progress bar value to", progress_bar_value)
+
+    progress_bar.value = progress_bar_value
+    ranged_bar.value = progress_bar_value
+    progress_bar_value += progress_bar_incr
+
+    if not (button1.value and button2.value):
+
+        if not button1.value:  # "UP" button pushed
+            print("UP button pressed. Increasing vertical bars by 3")
+            vertical_bar.value = min(vertical_bar.maximum, vertical_bar.value + 3)
+            vertical_bar2.value = min(vertical_bar2.maximum, vertical_bar2.value + 3)
+
+        if not button2.value:  # "DOWN" button pushed
+            print("DOWN button pressed. Decreasing vertical bars by 3")
+            vertical_bar.value = max(vertical_bar.minimum, vertical_bar.value - 3)
+            vertical_bar2.value = max(vertical_bar2.minimum, vertical_bar2.value - 3)
+
+    if progress_bar_value > progress_bar.maximum:
+        progress_bar_value = progress_bar.maximum
+        progress_bar_incr *= -1
+
+    if progress_bar_value < progress_bar.minimum:
+        progress_bar_value = progress_bar.minimum
+        progress_bar_incr *= -1
+
+    time.sleep(0.5)
diff --git a/examples/requirements.txt b/examples/requirements.txt
new file mode 100644
index 0000000..ad621e7
--- /dev/null
+++ b/examples/requirements.txt
@@ -0,0 +1,11 @@
+# SPDX-FileCopyrightText: 2021 ladyada for Adafruit Industries
+#
+# SPDX-License-Identifier: Unlicense
+
+# We're using a separate "requirements.txt" file for the
+# examples, to prevent the circup tool from attempting to
+# copy the library to the device.
+
+Adafruit-Blinka
+adafruit-blinka-displayio
+blinka-displayio-pygamedisplay
diff --git a/requirements.txt b/requirements.txt
index 17a850d..9e78ada 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -3,3 +3,8 @@
 # SPDX-License-Identifier: Unlicense
 
 Adafruit-Blinka
+adafruit-blinka-displayio
+
+# This library is only needed for use with Blinka. Should not
+# be retrieved or used for boards through Circup
+# blinka-displayio-pygamedisplay
diff --git a/setup.py b/setup.py
index 4a7b577..f564714 100644
--- a/setup.py
+++ b/setup.py
@@ -54,5 +54,5 @@
     # simple. Or you can use find_packages().
     # TODO: IF LIBRARY FILES ARE A PACKAGE FOLDER,
     #       CHANGE `py_modules=['...']` TO `packages=['...']`
-    py_modules=["adafruit_progressbar"],
+    packages=["adafruit_progressbar"],
 )


Test patch:
diff --git a/examples/progressbar_magtag_simpletest.py b/examples/progressbar_magtag_simpletest.py
index 4930902..f002b79 100644
--- a/examples/progressbar_magtag_simpletest.py
+++ b/examples/progressbar_magtag_simpletest.py
@@ -9,7 +9,7 @@
 import board
 import displayio
 import digitalio
-from adafruit_progressbar import ProgressBar
+from adafruit_progressbar.progressbar import HorizontalProgressBar
 
 # use built in display (PyPortal, PyGamer, PyBadge, CLUE, etc.)
 # see guide for setting up external displays (TFT / OLED breakouts, RGB matrices, etc.)
@@ -17,10 +17,15 @@
 display = board.DISPLAY
 time.sleep(display.time_to_refresh)
 
-# a button will be used to advance the progress
-a_btn = digitalio.DigitalInOut(board.BUTTON_A)
-a_btn.direction = digitalio.Direction.INPUT
-a_btn.pull = digitalio.Pull.UP
+# B/up button will be used to increase the progress
+up_btn = digitalio.DigitalInOut(board.BUTTON_B)
+up_btn.direction = digitalio.Direction.INPUT
+up_btn.pull = digitalio.Pull.UP
+
+# C/down button will be used to increase the progress
+down_btn = digitalio.DigitalInOut(board.BUTTON_C)
+down_btn.direction = digitalio.Direction.INPUT
+down_btn.pull = digitalio.Pull.UP
 
 # Make the display context
 splash = displayio.Group(max_size=10)
@@ -34,33 +39,57 @@
 y = display.height // 3
 
 # Create a new progress_bar object at (x, y)
-progress_bar = ProgressBar(
-    x, y, BAR_WIDTH, BAR_HEIGHT, 1.0, bar_color=0x666666, outline_color=0xFFFFFF
+progress_bar = HorizontalProgressBar(
+    (x, y),
+    (BAR_WIDTH, BAR_HEIGHT),
+    bar_color=0xFFFFFF,
+    outline_color=0xAAAAAA,
+    fill_color=0x777777,
 )
 
 # Append progress_bar to the splash group
 splash.append(progress_bar)
 
-current_progress = (time.monotonic() % 101) / 100.0
+# Get a random starting value within our min/max range
+current_progress = time.monotonic() % 101
 print(current_progress)
-progress_bar.progress = current_progress
+progress_bar.value = current_progress
 
 # refresh the display
 display.refresh()
 
-prev_a = a_btn.value
+value_incrementor = 3
+
+prev_up = up_btn.value
+prev_down = down_btn.value
 while True:
-    cur_a = a_btn.value
-    # if a_btn was just pressed down
-    if not cur_a and prev_a:
-        current_progress += 0.20
-        if current_progress > 1.0:
-            current_progress = 0.0
+    cur_up = up_btn.value
+    cur_down = down_btn.value
+    do_refresh = False
+    # if up_btn was just pressed down
+    if not cur_up and prev_up:
+        current_progress += value_incrementor
+        # Wrap if we get over the maximum value
+        if current_progress > progress_bar.maximum:
+            current_progress = progress_bar.minimum
+
+        do_refresh = True
+
+    if not cur_down and prev_down:
+        current_progress -= value_incrementor
+        # Wrap if we get below the minimum value
+        if current_progress < progress_bar.minimum:
+            current_progress = progress_bar.maximum
+
+        do_refresh = True
+
+    if do_refresh:
         print(current_progress)
-        progress_bar.progress = current_progress
+        progress_bar.value = current_progress
 
         time.sleep(display.time_to_refresh)
         display.refresh()
         time.sleep(display.time_to_refresh)
 
-    prev_a = cur_a
+    prev_up = cur_up
+    prev_down = cur_down
diff --git a/examples/progressbar_simpletest.py b/examples/progressbar_simpletest.py
index ae0c817..de3cb90 100644
--- a/examples/progressbar_simpletest.py
+++ b/examples/progressbar_simpletest.py
@@ -4,7 +4,10 @@
 import time
 import board
 import displayio
-from adafruit_progressbar import ProgressBar
+from adafruit_progressbar.horizontalprogressbar import (
+    HorizontalProgressBar,
+    HorizontalFillDirection,
+)
 
 # Make the display context
 splash = displayio.Group(max_size=10)
@@ -18,18 +21,20 @@
 y = board.DISPLAY.height // 3
 
 # Create a new progress_bar object at (x, y)
-progress_bar = ProgressBar(x, y, width, height, 1.0)
+progress_bar = HorizontalProgressBar(
+    (x, y), (width, height), direction=HorizontalFillDirection.LEFT_TO_RIGHT
+)
 
 # Append progress_bar to the splash group
 splash.append(progress_bar)
 
-current_progress = 0.0
+current_value = progress_bar.minimum
 while True:
     # range end is exclusive so we need to use 1 bigger than max number that we want
-    for current_progress in range(0, 101, 1):
-        print("Progress: {}%".format(current_progress))
-        progress_bar.progress = current_progress / 100  # convert to decimal
+    for current_value in range(progress_bar.minimum, progress_bar.maximum + 1, 1):
+        print("Progress: {}%".format(current_value))
+        progress_bar.value = current_value
         time.sleep(0.01)
     time.sleep(0.3)
-    progress_bar.progress = 0.0
+    progress_bar.value = progress_bar.minimum
     time.sleep(0.3)
diff --git a/examples/progressbar_vertical_simpletest.py b/examples/progressbar_vertical_simpletest.py
new file mode 100644
index 0000000..8018ed0
--- /dev/null
+++ b/examples/progressbar_vertical_simpletest.py
@@ -0,0 +1,52 @@
+# SPDX-FileCopyrightText: 2021 ladyada for Adafruit Industries
+# SPDX-License-Identifier: MIT
+
+import time
+import board
+import displayio
+from adafruit_progressbar.verticalprogressbar import (
+    VerticalProgressBar,
+    VerticalFillDirection,
+)
+
+# Make the display context
+splash = displayio.Group(max_size=10)
+board.DISPLAY.show(splash)
+
+# set progress bar width and height relative to board's display
+width = 10
+height = board.DISPLAY.height - 40
+
+x = width * 2
+y = 10
+
+# Create a new VerticalProgressBar object at (x, y)
+vertical_progress_bar = VerticalProgressBar(
+    (x, y), (width, height), direction=VerticalFillDirection.TOP_TO_BOTTOM
+)
+
+# Append progress_bar to the splash group
+splash.append(vertical_progress_bar)
+
+x = x * 2
+# Create a second VerticalProgressBar object at (x+20, y)
+vertical_progress_bar2 = VerticalProgressBar(
+    (x, y), (width, height), direction=VerticalFillDirection.BOTTOM_TO_TOP
+)
+
+# Append progress_bar to the splash group
+splash.append(vertical_progress_bar2)
+
+
+current_progress = 0.0
+while True:
+    # range end is exclusive so we need to use 1 bigger than max number that we want
+    for current_progress in range(0, 101, 1):
+        print("Progress: {}%".format(current_progress))
+        vertical_progress_bar.value = current_progress
+        vertical_progress_bar2.value = current_progress
+        time.sleep(0.05)
+    time.sleep(0.3)
+    vertical_progress_bar.value = vertical_progress_bar.minimum
+    vertical_progress_bar2.value = vertical_progress_bar.minimum
+    time.sleep(0.3)