]> Repositories - hackapet/Adafruit_Blinka_Displayio.git/commitdiff
Merge pull request #75 from makermelissa/add-typing 0.8.0
authorMelissa LeBlanc-Williams <melissa@adafruit.com>
Sat, 18 Dec 2021 21:29:43 +0000 (13:29 -0800)
committerGitHub <noreply@github.com>
Sat, 18 Dec 2021 21:29:43 +0000 (13:29 -0800)
Added typing and missing CP7 functions

20 files changed:
.pre-commit-config.yaml
_typing.py [new file with mode: 0644]
displayio/__init__.py
displayio/_bitmap.py [moved from displayio/bitmap.py with 78% similarity]
displayio/_colorconverter.py [moved from displayio/colorconverter.py with 75% similarity]
displayio/_colorspace.py [new file with mode: 0644]
displayio/_constants.py [new file with mode: 0644]
displayio/_display.py [moved from displayio/display.py with 76% similarity]
displayio/_displaybus.py [new file with mode: 0644]
displayio/_epaperdisplay.py [moved from displayio/epaperdisplay.py with 74% similarity]
displayio/_fourwire.py [moved from displayio/fourwire.py with 69% similarity]
displayio/_group.py [moved from displayio/group.py with 80% similarity]
displayio/_i2cdisplay.py [moved from displayio/i2cdisplay.py with 83% similarity]
displayio/_ondiskbitmap.py [moved from displayio/ondiskbitmap.py with 77% similarity]
displayio/_palette.py [moved from displayio/palette.py with 84% similarity]
displayio/_shape.py [moved from displayio/shape.py with 84% similarity]
displayio/_tilegrid.py [moved from displayio/tilegrid.py with 90% similarity]
docs/api.rst
paralleldisplay.py [moved from displayio/parallelbus.py with 77% similarity]
setup.py

index 7e0211762b9e1e096e5d02f92884b499aaacbfa6..68b37c856c946521c83c52955856351782c749bc 100644 (file)
@@ -18,7 +18,7 @@ repos:
     -   id: end-of-file-fixer
     -   id: trailing-whitespace
 -   repo: https://github.com/pycqa/pylint
-    rev: pylint-2.7.1
+    rev: v2.11.1
     hooks:
     -   id: pylint
         name: pylint (library code)
diff --git a/_typing.py b/_typing.py
new file mode 100644 (file)
index 0000000..85cb393
--- /dev/null
@@ -0,0 +1,37 @@
+# SPDX-FileCopyrightText: 2021 Melissa LeBlanc-Williams
+#
+# SPDX-License-Identifier: MIT
+
+"""
+`_typing`
+================================================================================
+
+Type aliases for Blinka
+
+**Software and Dependencies:**
+
+* Adafruit Blinka:
+  https://github.com/adafruit/Adafruit_Blinka/releases
+
+* Author(s): Melissa LeBlanc-Williams
+
+"""
+
+from typing import Union
+
+__version__ = "0.0.0-auto.0"
+__repo__ = "https://github.com/adafruit/Adafruit_Blinka_Displayio.git"
+
+
+WriteableBuffer = Union[
+    bytearray,
+    memoryview,
+    # array.array,
+    # ulab.numpy.ndarray,
+    # rgbmatrix.RGBMatrix
+]
+
+ReadableBuffer = Union[
+    bytes,
+    WriteableBuffer,
+]
index 343ffa1c8fa8c8ea719e96180014d936e9eaf8aa..d0707ac13cb8e96dad4c07eefcbc2f547d253cca 100644 (file)
@@ -17,25 +17,27 @@ displayio for Blinka
 
 """
 
-from displayio.bitmap import Bitmap
-from displayio.colorconverter import ColorConverter
-from displayio.display import Display
-from displayio.epaperdisplay import EPaperDisplay
-from displayio.fourwire import FourWire
-from displayio.group import Group
-from displayio.i2cdisplay import I2CDisplay
-from displayio.ondiskbitmap import OnDiskBitmap
-from displayio.palette import Palette
-from displayio.parallelbus import ParallelBus
-from displayio.shape import Shape
-from displayio.tilegrid import TileGrid
-from displayio.display import displays
+from typing import Union
+from ._fourwire import FourWire
+from ._i2cdisplay import I2CDisplay
+from ._bitmap import Bitmap
+from ._colorspace import Colorspace
+from ._colorconverter import ColorConverter
+from ._display import Display
+from ._epaperdisplay import EPaperDisplay
+from ._group import Group
+from ._ondiskbitmap import OnDiskBitmap
+from ._palette import Palette
+from ._shape import Shape
+from ._tilegrid import TileGrid
+from ._display import displays
+from ._displaybus import _DisplayBus
 
 __version__ = "0.0.0-auto.0"
 __repo__ = "https://github.com/adafruit/Adafruit_Blinka_displayio.git"
 
 
-def release_displays():
+def release_displays() -> None:
     """Releases any actively used displays so their busses and pins can be used again.
 
     Use this once in your code.py if you initialize a display. Place it right before the
similarity index 78%
rename from displayio/bitmap.py
rename to displayio/_bitmap.py
index 2f67937074cf3df11c37ea8dac3dff23cf8c4488..f28e72a15961201994042f92deaaf028b13f71eb 100644 (file)
@@ -17,6 +17,8 @@ displayio for Blinka
 
 """
 
+from __future__ import annotations
+from typing import Union, Tuple
 from recordclass import recordclass
 from PIL import Image
 
@@ -29,7 +31,7 @@ Rectangle = recordclass("Rectangle", "x1 y1 x2 y2")
 class Bitmap:
     """Stores values of a certain size in a 2D array"""
 
-    def __init__(self, width, height, value_count):
+    def __init__(self, width: int, height: int, value_count: int):
         """Create a Bitmap object with the given fixed size. Each pixel stores a value that is
         used to index into a corresponding palette. This enables differently colored sprites to
         share the underlying Bitmap. value_count is used to minimize the memory used to store
@@ -61,7 +63,7 @@ class Bitmap:
         self._image = Image.new("P", (width, height), 0)
         self._dirty_area = Rectangle(0, 0, width, height)
 
-    def __getitem__(self, index):
+    def __getitem__(self, index: Union[Tuple[int, int], int]) -> int:
         """
         Returns the value at the given index. The index can either be
         an x,y tuple or an int equal to `y * width + x`.
@@ -75,10 +77,10 @@ class Bitmap:
             raise TypeError("Index is not an int, list, or tuple")
 
         if x > self._image.width or y > self._image.height:
-            raise ValueError("Index {} is out of range".format(index))
+            raise ValueError(f"Index {index} is out of range")
         return self._image.getpixel((x, y))
 
-    def __setitem__(self, index, value):
+    def __setitem__(self, index: Union[Tuple[int, int], int], value: int) -> None:
         """
         Sets the value at the given index. The index can either be
         an x,y tuple or an int equal to `y * width + x`.
@@ -112,17 +114,38 @@ class Bitmap:
         self._dirty_area.x1 = 0
         self._dirty_area.x2 = 0
 
-    def fill(self, value):
+    def fill(self, value: int) -> None:
         """Fills the bitmap with the supplied palette index value."""
         self._image = Image.new("P", (self._bmp_width, self._bmp_height), value)
         self._dirty_area = Rectangle(0, 0, self._bmp_width, self._bmp_height)
 
+    def blit(
+        self,
+        x: int,
+        y: int,
+        source_bitmap: Bitmap,
+        *,
+        x1: int,
+        y1: int,
+        x2: int,
+        y2: int,
+        skip_index: int,
+    ) -> None:
+        # pylint: disable=unnecessary-pass, invalid-name
+        """Inserts the source_bitmap region defined by rectangular boundaries"""
+        pass
+
+    def dirty(self, x1: int = 0, y1: int = 0, x2: int = -1, y2: int = -1) -> None:
+        # pylint: disable=unnecessary-pass, invalid-name
+        """Inform displayio of bitmap updates done via the buffer protocol."""
+        pass
+
     @property
-    def width(self):
+    def width(self) -> int:
         """Width of the bitmap. (read only)"""
         return self._bmp_width
 
     @property
-    def height(self):
+    def height(self) -> int:
         """Height of the bitmap. (read only)"""
         return self._bmp_height
similarity index 75%
rename from displayio/colorconverter.py
rename to displayio/_colorconverter.py
index 8dc0e7ab4e0dbe18fb925a93e781b4c80ec32f71..dc6fc164b48dc7f198449cc92ab8ec91d4dc637a 100644 (file)
@@ -20,39 +20,46 @@ displayio for Blinka
 __version__ = "0.0.0-auto.0"
 __repo__ = "https://github.com/adafruit/Adafruit_Blinka_displayio.git"
 
+from ._colorspace import Colorspace
+
 
 class ColorConverter:
     """Converts one color format to another. Color converter based on original displayio
     code for consistency.
     """
 
-    def __init__(self, *, dither=False):
+    def __init__(
+        self, *, input_colorspace: Colorspace = Colorspace.RGB888, dither: bool = False
+    ):
         """Create a ColorConverter object to convert color formats.
         Only supports rgb888 to RGB565 currently.
         :param bool dither: Adds random noise to dither the output image
         """
         self._dither = dither
         self._depth = 16
+        self._transparent_color = None
         self._rgba = False
+        self._input_colorspace = input_colorspace
 
-    # pylint: disable=no-self-use
-    def _compute_rgb565(self, color):
+    def _compute_rgb565(self, color: int):
         self._depth = 16
         return (color[0] & 0xF8) << 8 | (color[1] & 0xFC) << 3 | color[2] >> 3
 
-    def _compute_luma(self, color):
+    @staticmethod
+    def _compute_luma(color: int):
         red = color >> 16
         green = (color >> 8) & 0xFF
         blue = color & 0xFF
         return (red * 19) / 255 + (green * 182) / 255 + (blue + 54) / 255
 
-    def _compute_chroma(self, color):
+    @staticmethod
+    def _compute_chroma(color: int):
         red = color >> 16
         green = (color >> 8) & 0xFF
         blue = color & 0xFF
         return max(red, green, blue) - min(red, green, blue)
 
-    def _compute_hue(self, color):
+    def _compute_hue(self, color: int):
         red = color >> 16
         green = (color >> 8) & 0xFF
         blue = color & 0xFF
@@ -72,7 +79,8 @@ class ColorConverter:
 
         return hue
 
-    def _dither_noise_1(self, noise):
+    @staticmethod
+    def _dither_noise_1(noise):
         noise = (noise >> 13) ^ noise
         more_noise = (
             noise * (noise * noise * 60493 + 19990303) + 1376312589
@@ -85,7 +93,7 @@ class ColorConverter:
     def _compute_tricolor(self):
         pass
 
-    def convert(self, color):
+    def convert(self, color: int) -> int:
         "Converts the given rgb888 color to RGB565"
         if isinstance(color, int):
             color = ((color >> 16) & 0xFF, (color >> 8) & 0xFF, color & 0xFF, 255)
@@ -103,17 +111,26 @@ class ColorConverter:
             return color
         return self._compute_rgb565(color)
 
-    # pylint: enable=no-self-use
+    def make_transparent(self, color: int) -> None:
+        """Set the transparent color or index for the ColorConverter. This will
+        raise an Exception if there is already a selected transparent index.
+        """
+        self._transparent_color = color
+
+    def make_opaque(self, color: int) -> None:
+        # pylint: disable=unused-argument
+        """Make the ColorConverter be opaque and have no transparent pixels."""
+        self._transparent_color = None
 
     @property
-    def dither(self):
+    def dither(self) -> bool:
         """When true the color converter dithers the output by adding
         random noise when truncating to display bitdepth
         """
         return self._dither
 
     @dither.setter
-    def dither(self, value):
+    def dither(self, value: bool):
         if not isinstance(value, bool):
             raise ValueError("Value should be boolean")
         self._dither = value
diff --git a/displayio/_colorspace.py b/displayio/_colorspace.py
new file mode 100644 (file)
index 0000000..5aacb24
--- /dev/null
@@ -0,0 +1,42 @@
+# SPDX-FileCopyrightText: 2020 Melissa LeBlanc-Williams for Adafruit Industries
+#
+# SPDX-License-Identifier: MIT
+
+
+"""
+`displayio.colorspace`
+================================================================================
+
+displayio for Blinka
+
+**Software and Dependencies:**
+
+* Adafruit Blinka:
+  https://github.com/adafruit/Adafruit_Blinka/releases
+
+* Author(s): Melissa LeBlanc-Williams
+
+"""
+
+__version__ = "0.0.0-auto.0"
+__repo__ = "https://github.com/adafruit/Adafruit_Blinka_displayio.git"
+
+
+class Colorspace:
+    """The colorspace for a ColorConverter to operate in."""
+
+    # pylint: disable=too-few-public-methods
+    def __init__(self, colorspace):
+        self._type = colorspace
+
+
+Colorspace.RGB888 = Colorspace("RGB888")
+Colorspace.RGB565 = Colorspace("RGB565")
+Colorspace.RGB565_SWAPPED = Colorspace("RGB565_SWAPPED")
+Colorspace.RGB555 = Colorspace("RGB555")
+Colorspace.RGB555_SWAPPED = Colorspace("RGB555_SWAPPED")
+Colorspace.BGR565 = Colorspace("BGR565")
+Colorspace.BGR565_SWAPPED = Colorspace("BGR565_SWAPPED")
+Colorspace.BGR555 = Colorspace("BGR555")
+Colorspace.BGR555_SWAPPED = Colorspace("BGR555_SWAPPED")
+Colorspace.L8 = Colorspace("L8")
diff --git a/displayio/_constants.py b/displayio/_constants.py
new file mode 100644 (file)
index 0000000..d0a00b7
--- /dev/null
@@ -0,0 +1,17 @@
+# SPDX-FileCopyrightText: 2021 James Carr
+#
+# SPDX-License-Identifier: MIT
+
+
+"""Constants"""
+
+
+__version__ = "0.0.0-auto.0"
+__repo__ = "https://github.com/adafruit/Adafruit_Blinka_Displayio.git"
+
+
+DISPLAY_COMMAND = 0
+DISPLAY_DATA = 1
+
+CHIP_SELECT_UNTOUCHED = 0
+CHIP_SELECT_TOGGLE_EVERY_BYTE = 1
similarity index 76%
rename from displayio/display.py
rename to displayio/_display.py
index 4d5a8bab1180961ddae10274e54adae15233cdbe..eacb6544775f45a8195236788a3e2e3f71216f00 100644 (file)
@@ -20,11 +20,22 @@ displayio for Blinka
 import time
 import struct
 import threading
+from typing import Optional
 import digitalio
 from PIL import Image
 import numpy
+import microcontroller
 from recordclass import recordclass
-from displayio.colorconverter import ColorConverter
+import _typing
+from ._displaybus import _DisplayBus
+from ._colorconverter import ColorConverter
+from ._group import Group
+from ._constants import (
+    CHIP_SELECT_TOGGLE_EVERY_BYTE,
+    CHIP_SELECT_UNTOUCHED,
+    DISPLAY_COMMAND,
+    DISPLAY_DATA,
+)
 
 __version__ = "0.0.0-auto.0"
 __repo__ = "https://github.com/adafruit/Adafruit_Blinka_displayio.git"
@@ -48,35 +59,37 @@ class Display:
 
     def __init__(
         self,
-        display_bus,
-        init_sequence,
+        display_bus: _DisplayBus,
+        init_sequence: _typing.ReadableBuffer,
         *,
-        width,
-        height,
-        colstart=0,
-        rowstart=0,
-        rotation=0,
-        color_depth=16,
-        grayscale=False,
-        pixels_in_byte_share_row=True,
-        bytes_per_cell=1,
-        reverse_pixels_in_byte=False,
-        set_column_command=0x2A,
-        set_row_command=0x2B,
-        write_ram_command=0x2C,
-        set_vertical_scroll=0,
-        backlight_pin=None,
-        brightness_command=None,
-        brightness=1.0,
-        auto_brightness=False,
-        single_byte_bounds=False,
-        data_as_commands=False,
-        auto_refresh=True,
-        native_frames_per_second=60
+        width: int,
+        height: int,
+        colstart: int = 0,
+        rowstart: int = 0,
+        rotation: int = 0,
+        color_depth: int = 16,
+        grayscale: bool = False,
+        pixels_in_byte_share_row: bool = True,
+        bytes_per_cell: int = 1,
+        reverse_pixels_in_byte: bool = False,
+        set_column_command: int = 0x2A,
+        set_row_command: int = 0x2B,
+        write_ram_command: int = 0x2C,
+        backlight_pin: Optional[microcontroller.Pin] = None,
+        brightness_command: Optional[int] = None,
+        brightness: float = 1.0,
+        auto_brightness: bool = False,
+        single_byte_bounds: bool = False,
+        data_as_commands: bool = False,
+        auto_refresh: bool = True,
+        native_frames_per_second: int = 60,
+        backlight_on_high: bool = True,
+        SH1107_addressing: bool = False,
+        set_vertical_scroll: int = 0,
     ):
-        # pylint: disable=unused-argument,too-many-locals
+        # pylint: disable=unused-argument,too-many-locals,invalid-name
         """Create a Display object on the given display bus (`displayio.FourWire` or
-        `displayio.ParallelBus`).
+        `paralleldisplay.ParallelBus`).
 
         The ``init_sequence`` is bitpacked to minimize the ram impact. Every command begins
         with a command byte followed by a byte to determine the parameter count and if a
@@ -133,7 +146,7 @@ class Display:
         self._backlight_type = None
         if backlight_pin is not None:
             try:
-                from pulseio import PWMOut  # pylint: disable=import-outside-toplevel
+                from pwmio import PWMOut  # pylint: disable=import-outside-toplevel
 
                 # 100Hz looks decent and doesn't keep the CPU too busy
                 self._backlight = PWMOut(backlight_pin, frequency=100, duty_cycle=0)
@@ -155,7 +168,7 @@ class Display:
             delay = (data_size & 0x80) > 0
             data_size &= ~0x80
 
-            self._write(command, init_sequence[i + 2 : i + 2 + data_size])
+            self._send(command, init_sequence[i + 2 : i + 2 + data_size])
             delay_time_ms = 10
             if delay:
                 data_size += 1
@@ -165,28 +178,46 @@ class Display:
             time.sleep(delay_time_ms / 1000)
             i += 2 + data_size
 
-    def _write(self, command, data):
-        self._bus.begin_transaction()
+    def _send(self, command, data):
+        # pylint: disable=protected-access
+        self._bus._begin_transaction()
         if self._data_as_commands:
-            if command is not None:
-                self._bus.send(True, bytes([command]), toggle_every_byte=True)
-            self._bus.send(command is not None, data)
+            self._bus._send(
+                DISPLAY_COMMAND, CHIP_SELECT_TOGGLE_EVERY_BYTE, bytes([command] + data)
+            )
         else:
-            self._bus.send(True, bytes([command]), toggle_every_byte=True)
-            self._bus.send(False, data)
-        self._bus.end_transaction()
+            self._bus._send(
+                DISPLAY_COMMAND, CHIP_SELECT_TOGGLE_EVERY_BYTE, bytes([command])
+            )
+            self._bus._send(DISPLAY_DATA, CHIP_SELECT_UNTOUCHED, data)
+        self._bus._end_transaction()
+
+    def _send_pixels(self, data):
+        # pylint: disable=protected-access
+        if not self._data_as_commands:
+            self._bus._send(
+                DISPLAY_COMMAND,
+                CHIP_SELECT_TOGGLE_EVERY_BYTE,
+                bytes([self._write_ram_command]),
+            )
+        self._bus._send(DISPLAY_DATA, CHIP_SELECT_UNTOUCHED, data)
 
     def _release(self):
         self._bus._release()  # pylint: disable=protected-access
         self._bus = None
 
-    def show(self, group):
+    def show(self, group: Group) -> None:
         """Switches to displaying the given group of layers. When group is None, the
         default CircuitPython terminal will be shown.
         """
         self._current_group = group
 
-    def refresh(self, *, target_frames_per_second=60, minimum_frames_per_second=1):
+    def refresh(
+        self,
+        *,
+        target_frames_per_second: Optional[int] = None,
+        minimum_frames_per_second: int = 0,
+    ) -> bool:
         # pylint: disable=unused-argument
         """When auto refresh is off, waits for the target frame rate and then refreshes the
         display, returning True. If the call has taken too long since the last refresh call
@@ -216,6 +247,8 @@ class Display:
         for area in self._subrectangles:
             self._refresh_display_area(area)
 
+        return True
+
     def _refresh_loop(self):
         while self._auto_refresh:
             self.refresh()
@@ -240,14 +273,14 @@ class Display:
             numpy.dstack(((color >> 8) & 0xFF, color & 0xFF)).flatten().tolist()
         )
 
-        self._write(
+        self._send(
             self._set_column_command,
             self._encode_pos(
                 display_rectangle.x1 + self._colstart,
                 display_rectangle.x2 + self._colstart - 1,
             ),
         )
-        self._write(
+        self._send(
             self._set_row_command,
             self._encode_pos(
                 display_rectangle.y1 + self._rowstart,
@@ -255,10 +288,9 @@ class Display:
             ),
         )
 
-        if self._data_as_commands:
-            self._write(None, pixels)
-        else:
-            self._write(self._write_ram_command, pixels)
+        self._bus._begin_transaction()  # pylint: disable=protected-access
+        self._send_pixels(pixels)
+        self._bus._end_transaction()  # pylint: disable=protected-access
 
     def _clip(self, rectangle):
         if self._rotation in (90, 270):
@@ -266,14 +298,10 @@ class Display:
         else:
             width, height = self._width, self._height
 
-        if rectangle.x1 < 0:
-            rectangle.x1 = 0
-        if rectangle.y1 < 0:
-            rectangle.y1 = 0
-        if rectangle.x2 > width:
-            rectangle.x2 = width
-        if rectangle.y2 > height:
-            rectangle.y2 = height
+        rectangle.x1 = max(rectangle.x1, 0)
+        rectangle.y1 = max(rectangle.y1, 0)
+        rectangle.x2 = min(rectangle.x2, width)
+        rectangle.y2 = min(rectangle.y2, height)
 
         return rectangle
 
@@ -304,9 +332,11 @@ class Display:
 
     def _encode_pos(self, x, y):
         """Encode a postion into bytes."""
-        return struct.pack(self._bounds_encoding, x, y)
+        return struct.pack(self._bounds_encoding, x, y)  # pylint: disable=no-member
 
-    def fill_row(self, y, buffer):
+    def fill_row(
+        self, y: int, buffer: _typing.WriteableBuffer
+    ) -> _typing.WriteableBuffer:
         """Extract the pixels from a single row"""
         for x in range(0, self._width):
             _rgb_565 = self._colorconverter.convert(self._buffer.getpixel((x, y)))
@@ -315,12 +345,12 @@ class Display:
         return buffer
 
     @property
-    def auto_refresh(self):
+    def auto_refresh(self) -> bool:
         """True when the display is refreshed automatically."""
         return self._auto_refresh
 
     @auto_refresh.setter
-    def auto_refresh(self, value):
+    def auto_refresh(self, value: bool):
         self._auto_refresh = value
         if self._refresh_thread is None:
             self._refresh_thread = threading.Thread(
@@ -334,7 +364,7 @@ class Display:
             self._refresh_thread.join()
 
     @property
-    def brightness(self):
+    def brightness(self) -> float:
         """The brightness of the display as a float. 0.0 is off and 1.0 is full `brightness`.
         When `auto_brightness` is True, the value of `brightness` will change automatically.
         If `brightness` is set, `auto_brightness` will be disabled and will be set to False.
@@ -342,7 +372,7 @@ class Display:
         return self._brightness
 
     @brightness.setter
-    def brightness(self, value):
+    def brightness(self, value: float):
         if 0 <= float(value) <= 1.0:
             self._brightness = value
             if self._backlight_type == BACKLIGHT_IN_OUT:
@@ -350,12 +380,12 @@ class Display:
             elif self._backlight_type == BACKLIGHT_PWM:
                 self._backlight.duty_cycle = self._brightness * 65535
             elif self._brightness_command is not None:
-                self._write(self._brightness_command, round(value * 255))
+                self._send(self._brightness_command, round(value * 255))
         else:
             raise ValueError("Brightness must be between 0.0 and 1.0")
 
     @property
-    def auto_brightness(self):
+    def auto_brightness(self) -> bool:
         """True when the display brightness is adjusted automatically, based on an ambient
         light sensor or other method. Note that some displays may have this set to True by
         default, but not actually implement automatic brightness adjustment.
@@ -364,31 +394,31 @@ class Display:
         return self._auto_brightness
 
     @auto_brightness.setter
-    def auto_brightness(self, value):
+    def auto_brightness(self, value: bool):
         self._auto_brightness = value
 
     @property
-    def width(self):
+    def width(self) -> int:
         """Display Width"""
         return self._width
 
     @property
-    def height(self):
+    def height(self) -> int:
         """Display Height"""
         return self._height
 
     @property
-    def rotation(self):
+    def rotation(self) -> int:
         """The rotation of the display as an int in degrees."""
         return self._rotation
 
     @rotation.setter
-    def rotation(self, value):
+    def rotation(self, value: int):
         if value not in (0, 90, 180, 270):
             raise ValueError("Rotation must be 0/90/180/270")
         self._rotation = value
 
     @property
-    def bus(self):
+    def bus(self) -> _DisplayBus:
         """Current Display Bus"""
         return self._bus
diff --git a/displayio/_displaybus.py b/displayio/_displaybus.py
new file mode 100644 (file)
index 0000000..976e203
--- /dev/null
@@ -0,0 +1,28 @@
+# SPDX-FileCopyrightText: 2021 Melissa LeBlanc-Williams
+#
+# SPDX-License-Identifier: MIT
+
+"""
+`displayio._displaybus`
+================================================================================
+
+Type aliases for Blinka
+
+**Software and Dependencies:**
+
+* Adafruit Blinka:
+  https://github.com/adafruit/Adafruit_Blinka/releases
+
+* Author(s): Melissa LeBlanc-Williams
+
+"""
+
+from typing import Union
+import paralleldisplay
+from ._fourwire import FourWire
+from ._i2cdisplay import I2CDisplay
+
+__version__ = "0.0.0-auto.0"
+__repo__ = "https://github.com/adafruit/Adafruit_Blinka_Displayio.git"
+
+_DisplayBus = Union[FourWire, I2CDisplay, paralleldisplay.ParallelBus]
similarity index 74%
rename from displayio/epaperdisplay.py
rename to displayio/_epaperdisplay.py
index 65af4be7f357a335874830e24d1b4c55305d3ed3..29f8a54d840e9f1d7d08dfb991abe82be3616905 100644 (file)
@@ -17,6 +17,11 @@ displayio for Blinka
 
 """
 
+from typing import Optional
+import microcontroller
+import _typing
+from ._group import Group
+from ._displaybus import _DisplayBus
 
 __version__ = "0.0.0-auto.0"
 __repo__ = "https://github.com/adafruit/Adafruit_Blinka_displayio.git"
@@ -36,38 +41,38 @@ class EPaperDisplay:
 
     def __init__(
         self,
-        display_bus,
-        start_sequence,
-        stop_sequence,
+        display_bus: _DisplayBus,
+        start_sequence: _typing.ReadableBuffer,
+        stop_sequence: _typing.ReadableBuffer,
         *,
-        width,
-        height,
-        ram_width,
-        ram_height,
-        colstart=0,
-        rowstart=0,
-        rotation=0,
-        set_column_window_command=None,
-        set_row_window_command=None,
-        set_current_column_command=None,
-        set_current_row_command=None,
-        write_black_ram_command,
-        black_bits_inverted=False,
-        write_color_ram_command=None,
-        color_bits_inverted=False,
-        highlight_color=0x000000,
-        refresh_display_command,
-        refresh_time=40,
-        busy_pin=None,
-        busy_state=True,
-        seconds_per_frame=180,
-        always_toggle_chip_select=False,
-        grayscale=False,
+        width: int,
+        height: int,
+        ram_width: int,
+        ram_height: int,
+        colstart: int = 0,
+        rowstart: int = 0,
+        rotation: int = 0,
+        set_column_window_command: Optional[int] = None,
+        set_row_window_command: Optional[int] = None,
+        set_current_column_command: Optional[int] = None,
+        set_current_row_command: Optional[int] = None,
+        write_black_ram_command: int,
+        black_bits_inverted: bool = False,
+        write_color_ram_command: Optional[int] = None,
+        color_bits_inverted: bool = False,
+        highlight_color: int = 0x000000,
+        refresh_display_command: int,
+        refresh_time: float = 40.0,
+        busy_pin: Optional[microcontroller.Pin] = None,
+        busy_state: bool = True,
+        seconds_per_frame: float = 180.0,
+        always_toggle_chip_select: bool = False,
+        grayscale: bool = False,
     ):
-        # pylint: disable=too-many-locals,unnecessary-pass
+        # pylint: disable=too-many-locals, unused-argument
         """
         Create a EPaperDisplay object on the given display bus (displayio.FourWire or
-        displayio.ParallelBus).
+        paralleldisplay.ParallelBus).
 
         The start_sequence and stop_sequence are bitpacked to minimize the ram impact. Every
         command begins with a command byte followed by a byte to determine the parameter
@@ -112,16 +117,18 @@ class EPaperDisplay:
         :param bool always_toggle_chip_select: When True, chip select is toggled every byte
         :param bool grayscale: When true, the color ram is the low bit of 2-bit grayscale
         """
-        pass
+        self._bus = display_bus
+        self._width = width
+        self._height = height
 
-    def show(self, group):
+    def show(self, group: Group) -> None:
         # pylint: disable=unnecessary-pass
         """Switches to displaying the given group of layers. When group is None, the default
         CircuitPython terminal will be shown (eventually).
         """
         pass
 
-    def refresh(self):
+    def refresh(self) -> None:
         # pylint: disable=unnecessary-pass
         """Refreshes the display immediately or raises an exception if too soon. Use
         ``time.sleep(display.time_to_refresh)`` to sleep until a refresh can occur.
@@ -129,24 +136,21 @@ class EPaperDisplay:
         pass
 
     @property
-    def time_to_refresh(self):
+    def time_to_refresh(self) -> float:
         """Time, in fractional seconds, until the ePaper display can be refreshed."""
-        return 0
+        return 0.0
 
     @property
-    def width(self):
-        # pylint: disable=unnecessary-pass
+    def width(self) -> int:
         """Display Width"""
-        pass
+        return self._width
 
     @property
-    def height(self):
-        # pylint: disable=unnecessary-pass
+    def height(self) -> int:
         """Display Height"""
-        pass
+        return self._height
 
     @property
-    def bus(self):
-        # pylint: disable=unnecessary-pass
+    def bus(self) -> _DisplayBus:
         """Current Display Bus"""
-        pass
+        return self._bus
similarity index 69%
rename from displayio/fourwire.py
rename to displayio/_fourwire.py
index 47299e03056f2fb3632899ac3ef56ea743aafde5..0aae5dad212a0239c5f26ae68414a05f13463b33 100644 (file)
@@ -18,7 +18,17 @@ displayio for Blinka
 """
 
 import time
+from typing import Optional
 import digitalio
+import busio
+import microcontroller
+import _typing
+from ._constants import (
+    CHIP_SELECT_TOGGLE_EVERY_BYTE,
+    CHIP_SELECT_UNTOUCHED,
+    DISPLAY_COMMAND,
+    DISPLAY_DATA,
+)
 
 __version__ = "0.0.0-auto.0"
 __repo__ = "https://github.com/adafruit/Adafruit_Blinka_displayio.git"
@@ -31,14 +41,14 @@ class FourWire:
 
     def __init__(
         self,
-        spi_bus,
+        spi_bus: busio.SPI,
         *,
-        command,
-        chip_select,
-        reset=None,
-        baudrate=24000000,
-        polarity=0,
-        phase=0
+        command: microcontroller.Pin,
+        chip_select: microcontroller.Pin,
+        reset: Optional[microcontroller.Pin] = None,
+        baudrate: int = 24000000,
+        polarity: int = 0,
+        phase: int = 0,
     ):
         """Create a FourWire object associated with the given pins.
 
@@ -72,7 +82,7 @@ class FourWire:
         if self._reset is not None:
             self._reset.deinit()
 
-    def reset(self):
+    def reset(self) -> None:
         """Performs a hardware reset via the reset pin.
         Raises an exception if called when no reset pin is available.
         """
@@ -82,13 +92,29 @@ class FourWire:
             self._reset.value = True
             time.sleep(0.001)
 
-    def send(self, is_command, data, *, toggle_every_byte=False):
-        """Sends the given command value followed by the full set of data. Display state,
+    def send(
+        self, command, data: _typing.ReadableBuffer, *, toggle_every_byte: bool = False
+    ) -> None:
+        """
+        Sends the given command value followed by the full set of data. Display state,
         such as vertical scroll, set via ``send`` may or may not be reset once the code is
         done.
         """
-        self._dc.value = not is_command
-        if toggle_every_byte:
+        if not 0 <= command <= 255:
+            raise ValueError("Command must be an int between 0 and 255")
+        chip_select = (
+            CHIP_SELECT_TOGGLE_EVERY_BYTE
+            if toggle_every_byte
+            else CHIP_SELECT_UNTOUCHED
+        )
+        self._begin_transaction()
+        self._send(DISPLAY_COMMAND, chip_select, command)
+        self._send(DISPLAY_DATA, chip_select, data)
+        self._end_transaction()
+
+    def _send(self, data_type: int, chip_select: int, data: _typing.ReadableBuffer):
+        self._dc.value = data_type == DISPLAY_DATA
+        if chip_select == CHIP_SELECT_TOGGLE_EVERY_BYTE:
             for byte in data:
                 self._spi.write(bytes([byte]))
                 self._chip_select.value = True
@@ -97,7 +123,7 @@ class FourWire:
         else:
             self._spi.write(data)
 
-    def begin_transaction(self):
+    def _begin_transaction(self):
         """Begin the SPI transaction by locking, configuring, and setting Chip Select"""
         while not self._spi.try_lock():
             pass
@@ -106,7 +132,7 @@ class FourWire:
         )
         self._chip_select.value = False
 
-    def end_transaction(self):
+    def _end_transaction(self):
         """End the SPI transaction by unlocking and setting Chip Select"""
         self._chip_select.value = True
         self._spi.unlock()
similarity index 80%
rename from displayio/group.py
rename to displayio/_group.py
index e84a487e62711085353643a549b841ddd18aab4a..6be7c6fb0974602d98e54a0becb6e1d1cd83f018 100644 (file)
@@ -17,8 +17,10 @@ displayio for Blinka
 
 """
 
+from __future__ import annotations
+from typing import Union, Callable
 from recordclass import recordclass
-from displayio.tilegrid import TileGrid
+from ._tilegrid import TileGrid
 
 __version__ = "0.0.0-auto.0"
 __repo__ = "https://github.com/adafruit/Adafruit_Blinka_displayio.git"
@@ -35,21 +37,13 @@ class Group:
     leads to a layer's pixel being 2x2 pixels when in the group.
     """
 
-    def __init__(self, *, max_size=None, scale=1, x=0, y=0):
+    def __init__(self, *, scale: int = 1, x: int = 0, y: int = 0):
         """
-        :param Optional(int) max_size: *DEPRECATED* This has been removed in CircuitPython 7 and
-            will be removed in a future version of ``Adafruit_Blinka_Displayio``
         :param int scale: Scale of layer pixels in one dimension.
         :param int x: Initial x position within the parent.
         :param int y: Initial y position within the parent.
         """
 
-        if max_size is not None:
-            print(
-                "The max_size parameter displayio.Group() has been deprecated. "
-                "Please remove max_size from your code."
-            )
-
         if not isinstance(scale, int) or scale < 1:
             raise ValueError("Scale must be >= 1")
         self._scale = 1  # Use the setter below to actually set the scale
@@ -62,7 +56,7 @@ class Group:
         self._absolute_transform = Transform(0, 0, 1, 1, 1, False, False, False)
         self._set_scale(scale)  # Set the scale via the setter
 
-    def update_transform(self, parent_transform):
+    def _update_transform(self, parent_transform):
         """Update the parent transform and child transforms"""
         self.in_group = parent_transform is not None
         if self.in_group:
@@ -81,25 +75,28 @@ class Group:
         self._update_child_transforms()
 
     def _update_child_transforms(self):
+        # pylint: disable=protected-access
         if self.in_group:
             for layer in self._layers:
-                layer.update_transform(self._absolute_transform)
+                layer._update_transform(self._absolute_transform)
 
     def _removal_cleanup(self, index):
+        # pylint: disable=protected-access
         layer = self._layers[index]
-        layer.update_transform(None)
+        layer._update_transform(None)
 
     def _layer_update(self, index):
+        # pylint: disable=protected-access
         layer = self._layers[index]
-        layer.update_transform(self._absolute_transform)
+        layer._update_transform(self._absolute_transform)
 
-    def append(self, layer):
+    def append(self, layer: Union[Group, TileGrid]) -> None:
         """Append a layer to the group. It will be drawn
         above other layers.
         """
         self.insert(len(self._layers), layer)
 
-    def insert(self, index, layer):
+    def insert(self, index: int, layer: Union[Group, TileGrid]) -> None:
         """Insert a layer into the group."""
         if not isinstance(layer, self._supported_types):
             raise ValueError("Invalid Group Member")
@@ -108,38 +105,42 @@ class Group:
         self._layers.insert(index, layer)
         self._layer_update(index)
 
-    def index(self, layer):
+    def index(self, layer: Union[Group, TileGrid]) -> int:
         """Returns the index of the first copy of layer.
         Raises ValueError if not found.
         """
         return self._layers.index(layer)
 
-    def pop(self, index=-1):
+    def pop(self, index: int = -1) -> Union[Group, TileGrid]:
         """Remove the ith item and return it."""
         self._removal_cleanup(index)
         return self._layers.pop(index)
 
-    def remove(self, layer):
+    def remove(self, layer: Union[Group, TileGrid]) -> None:
         """Remove the first copy of layer. Raises ValueError
         if it is not present."""
         index = self.index(layer)
         self._layers.pop(index)
 
-    def __len__(self):
+    def __bool__(self) -> bool:
+        """Returns if there are any layers"""
+        return len(self._layers) > 0
+
+    def __len__(self) -> int:
         """Returns the number of layers in a Group"""
         return len(self._layers)
 
-    def __getitem__(self, index):
+    def __getitem__(self, index: int) -> Union[Group, TileGrid]:
         """Returns the value at the given index."""
         return self._layers[index]
 
-    def __setitem__(self, index, value):
+    def __setitem__(self, index: int, value: Union[Group, TileGrid]) -> None:
         """Sets the value at the given index."""
         self._removal_cleanup(index)
         self._layers[index] = value
         self._layer_update(index)
 
-    def __delitem__(self, index):
+    def __delitem__(self, index: int) -> None:
         """Deletes the value at the given index."""
         del self._layers[index]
 
@@ -151,31 +152,35 @@ class Group:
             if isinstance(layer, (Group, TileGrid)):
                 layer._fill_area(buffer)  # pylint: disable=protected-access
 
+    def sort(self, key: Callable, reverse: bool) -> None:
+        """Sort the members of the group."""
+        self._layers.sort(key=key, reverse=reverse)
+
     @property
-    def hidden(self):
-        """True when the Group and all of its layers are not visible. When False, the
-        Group’s layers are visible if they havent been hidden.
+    def hidden(self) -> bool:
+        """True when the Group and all of it's layers are not visible. When False, the
+        Group’s layers are visible if they haven't been hidden.
         """
         return self._hidden_group
 
     @hidden.setter
-    def hidden(self, value):
+    def hidden(self, value: bool):
         if not isinstance(value, (bool, int)):
             raise ValueError("Expecting a boolean or integer value")
         self._hidden_group = bool(value)
 
     @property
-    def scale(self):
+    def scale(self) -> int:
         """Scales each pixel within the Group in both directions. For example, when
         scale=2 each pixel will be represented by 2x2 pixels.
         """
         return self._scale
 
     @scale.setter
-    def scale(self, value):
+    def scale(self, value: int):
         self._set_scale(value)
 
-    def _set_scale(self, value):
+    def _set_scale(self, value: int):
         # This is method allows the scale to be set by this class even when
         # the scale property is over-ridden by a subclass.
         if not isinstance(value, int) or value < 1:
@@ -194,12 +199,12 @@ class Group:
             self._update_child_transforms()
 
     @property
-    def x(self):
+    def x(self) -> int:
         """X position of the Group in the parent."""
         return self._group_x
 
     @x.setter
-    def x(self, value):
+    def x(self, value: int):
         if not isinstance(value, int):
             raise ValueError("x must be an integer")
         if self._group_x != value:
@@ -213,12 +218,12 @@ class Group:
             self._update_child_transforms()
 
     @property
-    def y(self):
+    def y(self) -> int:
         """Y position of the Group in the parent."""
         return self._group_y
 
     @y.setter
-    def y(self, value):
+    def y(self, value: int):
         if not isinstance(value, int):
             raise ValueError("y must be an integer")
         if self._group_y != value:
similarity index 83%
rename from displayio/i2cdisplay.py
rename to displayio/_i2cdisplay.py
index b2d38dc808e1c4340522630b889b00b7df56a38c..421fd105c7d3024df692cd688b6d4deddc05e886 100644 (file)
@@ -20,12 +20,14 @@ displayio for Blinka
 
 """
 
-__version__ = "0.0.0-auto.0"
-__repo__ = "https://github.com/adafruit/Adafruit_Blinka_displayio.git"
-
 import time
 import busio
 import digitalio
+import _typing
+from ._constants import CHIP_SELECT_UNTOUCHED, DISPLAY_COMMAND
+
+__version__ = "0.0.0-auto.0"
+__repo__ = "https://github.com/adafruit/Adafruit_Blinka_displayio.git"
 
 
 class I2CDisplay:
@@ -69,24 +71,24 @@ class I2CDisplay:
         time.sleep(0.0001)
         self._reset.value = True
 
-    def begin_transaction(self) -> None:
-        """
-        Lock the bus before sending data.
-        """
+    def _begin_transaction(self) -> None:
+        """Lock the bus before sending data."""
         while not self._i2c.try_lock():
             pass
 
-    def send(self, command: bool, data, *, toggle_every_byte=False) -> None:
-        # pylint: disable=unused-argument
+    def send(self, command: int, data: _typing.ReadableBuffer) -> None:
         """
         Sends the given command value followed by the full set of data. Display state,
         such as vertical scroll, set via ``send`` may or may not be reset once the code is
         done.
         """
-        # NOTE: we have to have a toggle_every_byte parameter, which we ignore,
-        # because Display._write() sets it regardless of bus type.
+        self._begin_transaction()
+        self._send(DISPLAY_COMMAND, CHIP_SELECT_UNTOUCHED, bytes([command] + data))
+        self._end_transaction()
 
-        if command:
+    def _send(self, data_type: int, chip_select: int, data: _typing.ReadableBuffer):
+        # pylint: disable=unused-argument
+        if data_type == DISPLAY_COMMAND:
             n = len(data)
             if n > 0:
                 command_bytes = bytearray(n * 2)
@@ -95,15 +97,12 @@ class I2CDisplay:
                     command_bytes[2 * i + 1] = data[i]
 
                 self._i2c.writeto(self._dev_addr, buffer=command_bytes, stop=True)
-
         else:
             data_bytes = bytearray(len(data) + 1)
             data_bytes[0] = 0x40
             data_bytes[1:] = data
             self._i2c.writeto(self._dev_addr, buffer=data_bytes, stop=True)
 
-    def end_transaction(self) -> None:
-        """
-        Release the bus after sending data.
-        """
+    def _end_transaction(self) -> None:
+        """Release the bus after sending data."""
         self._i2c.unlock()
similarity index 77%
rename from displayio/ondiskbitmap.py
rename to displayio/_ondiskbitmap.py
index bb9efaa97731b203c5b7899f813ca6f792074e7f..2c4804360f9ec0d83928382f182808dc60129f11 100644 (file)
@@ -17,7 +17,10 @@ displayio for Blinka
 
 """
 
+from typing import Union, BinaryIO
 from PIL import Image
+from ._colorconverter import ColorConverter
+from ._palette import Palette
 
 __version__ = "0.0.0-auto.0"
 __repo__ = "https://github.com/adafruit/Adafruit_Blinka_displayio.git"
@@ -29,20 +32,25 @@ class OnDiskBitmap:
     pixel load times. These load times may result in frame tearing where only part of the
     image is visible."""
 
-    def __init__(self, file):
+    def __init__(self, file: Union[str, BinaryIO]):
         self._image = Image.open(file).convert("RGBA")
 
     @property
-    def width(self):
+    def width(self) -> int:
         """Width of the bitmap. (read only)"""
         return self._image.width
 
     @property
-    def height(self):
+    def height(self) -> int:
         """Height of the bitmap. (read only)"""
         return self._image.height
 
-    def __getitem__(self, index):
+    @property
+    def pixel_shader(self) -> Union[ColorConverter, Palette]:
+        """Height of the bitmap. (read only)"""
+        return self._image.height
+
+    def __getitem__(self, index: Union[tuple, list, int]) -> int:
         """
         Returns the value at the given index. The index can either be
         an x,y tuple or an int equal to `y * width + x`.
similarity index 84%
rename from displayio/palette.py
rename to displayio/_palette.py
index cda3b705200378ca24fe1f542f8204514a20eb67..8579bcb14cc182c8feabf061d75b1601da047b53 100644 (file)
@@ -17,6 +17,9 @@ displayio for Blinka
 
 """
 
+from typing import Optional, Union, Tuple
+import _typing
+
 __version__ = "0.0.0-auto.0"
 __repo__ = "https://github.com/adafruit/Adafruit_Blinka_displayio.git"
 
@@ -26,7 +29,7 @@ class Palette:
     format internally to save memory.
     """
 
-    def __init__(self, color_count):
+    def __init__(self, color_count: int):
         """Create a Palette object to store a set number of colors."""
         self._needs_refresh = False
 
@@ -63,11 +66,15 @@ class Palette:
 
         return color
 
-    def __len__(self):
+    def __len__(self) -> int:
         """Returns the number of colors in a Palette"""
         return len(self._colors)
 
-    def __setitem__(self, index, value):
+    def __setitem__(
+        self,
+        index: int,
+        value: Union[int, _typing.ReadableBuffer, Tuple[int, int, int]],
+    ) -> None:
         """Sets the pixel color at the given index. The index should be
         an integer in the range 0 to color_count-1.
 
@@ -79,17 +86,17 @@ class Palette:
             self._colors[index] = self._make_color(value)
             self._update_rgba(index)
 
-    def __getitem__(self, index):
+    def __getitem__(self, index: int) -> Optional[int]:
         if not 0 <= index < len(self._colors):
             raise ValueError("Palette index out of range")
         return self._colors[index]["rgb888"]
 
-    def make_transparent(self, palette_index):
+    def make_transparent(self, palette_index: int) -> None:
         """Set the palette index to be a transparent color"""
         self._colors[palette_index]["transparent"] = True
         self._update_rgba(palette_index)
 
-    def make_opaque(self, palette_index):
+    def make_opaque(self, palette_index: int) -> None:
         """Set the palette index to be an opaque color"""
         self._colors[palette_index]["transparent"] = False
         self._update_rgba(palette_index)
@@ -109,3 +116,7 @@ class Palette:
             for _ in range(3):
                 palette += [0 if color["transparent"] else 255]
         return palette
+
+    def is_transparent(self, palette_index: int) -> bool:
+        """Returns True if the palette index is transparent. Returns False if opaque."""
+        return self._colors[palette_index]["transparent"]
similarity index 84%
rename from displayio/shape.py
rename to displayio/_shape.py
index fec29d012ea7ec213259a82b1421db591b922b37..9fe565dda0553e8874af368f02503f639542f8aa 100644 (file)
@@ -18,7 +18,7 @@ displayio for Blinka
 
 """
 
-from displayio.bitmap import Bitmap
+from ._bitmap import Bitmap
 
 __version__ = "0.0.0-auto.0"
 __repo__ = "https://github.com/adafruit/Adafruit_Blinka_displayio.git"
@@ -30,7 +30,9 @@ class Shape(Bitmap):
     full row.
     """
 
-    def __init__(self, width, height, *, mirror_x=False, mirror_y=False):
+    def __init__(
+        self, width: int, height: int, *, mirror_x: bool = False, mirror_y: bool = False
+    ):
         # pylint: disable=unused-argument
         """Create a Shape object with the given fixed size. Each pixel is one bit and is
         stored by the column boundaries of the shape on each row. Each row’s boundary
@@ -38,7 +40,7 @@ class Shape(Bitmap):
         """
         super().__init__(width, height, 2)
 
-    def set_boundary(self, y, start_x, end_x):
+    def set_boundary(self, y: int, start_x: int, end_x: int) -> None:
         # pylint: disable=unnecessary-pass
         """Loads pre-packed data into the given row."""
         pass
similarity index 90%
rename from displayio/tilegrid.py
rename to displayio/_tilegrid.py
index e2a4653b81cf7ccb4aa7b813b20b35c995fb8368..f2ccde23ab827dcc0525fe596f157e6e049535bb 100644 (file)
@@ -17,13 +17,14 @@ displayio for Blinka
 
 """
 
+from typing import Union, Optional, Tuple
 from recordclass import recordclass
 from PIL import Image
-from displayio.bitmap import Bitmap
-from displayio.colorconverter import ColorConverter
-from displayio.ondiskbitmap import OnDiskBitmap
-from displayio.shape import Shape
-from displayio.palette import Palette
+from ._bitmap import Bitmap
+from ._colorconverter import ColorConverter
+from ._ondiskbitmap import OnDiskBitmap
+from ._shape import Shape
+from ._palette import Palette
 
 __version__ = "0.0.0-auto.0"
 __repo__ = "https://github.com/adafruit/Adafruit_Blinka_displayio.git"
@@ -42,16 +43,16 @@ class TileGrid:
 
     def __init__(
         self,
-        bitmap,
+        bitmap: Union[Bitmap, OnDiskBitmap, Shape],
         *,
-        pixel_shader,
-        width=1,
-        height=1,
-        tile_width=None,
-        tile_height=None,
-        default_tile=0,
-        x=0,
-        y=0
+        pixel_shader: Union[ColorConverter, Palette],
+        width: int = 1,
+        height: int = 1,
+        tile_width: Optional[int] = None,
+        tile_height: Optional[int] = None,
+        default_tile: int = 0,
+        x: int = 0,
+        y: int = 0,
     ):
         """Create a TileGrid object. The bitmap is source for 2d pixels. The pixel_shader is
         used to convert the value and its location to a display native pixel color. This may
@@ -86,10 +87,8 @@ class TileGrid:
             tile_width = bitmap_width
         if tile_height is None or tile_width == 0:
             tile_height = bitmap_height
-        if tile_width < 1:
-            tile_width = 1
-        if tile_height < 1:
-            tile_height = 1
+        if tile_width < 1 or tile_height < 1:
+            raise ValueError("Tile width and height must be greater than 0")
         if bitmap_width % tile_width != 0:
             raise ValueError("Tile width must exactly divide bitmap width")
         self._tile_width = tile_width
@@ -106,7 +105,7 @@ class TileGrid:
         self._current_area = Rectangle(0, 0, self._pixel_width, self._pixel_height)
         self._moved = False
 
-    def update_transform(self, absolute_transform):
+    def _update_transform(self, absolute_transform):
         """Update the parent transform and child transforms"""
         self._absolute_transform = absolute_transform
         if self._absolute_transform is not None:
@@ -298,24 +297,24 @@ class TileGrid:
             buffer.alpha_composite(image, (x, y), source=(source_x, source_y))
 
     @property
-    def hidden(self):
+    def hidden(self) -> bool:
         """True when the TileGrid is hidden. This may be False even
         when a part of a hidden Group."""
         return self._hidden_tilegrid
 
     @hidden.setter
-    def hidden(self, value):
+    def hidden(self, value: bool):
         if not isinstance(value, (bool, int)):
             raise ValueError("Expecting a boolean or integer value")
         self._hidden_tilegrid = bool(value)
 
     @property
-    def x(self):
+    def x(self) -> int:
         """X position of the left edge in the parent."""
         return self._x
 
     @x.setter
-    def x(self, value):
+    def x(self, value: int):
         if not isinstance(value, int):
             raise TypeError("X should be a integer type")
         if self._x != value:
@@ -323,12 +322,12 @@ class TileGrid:
             self._update_current_x()
 
     @property
-    def y(self):
+    def y(self) -> int:
         """Y position of the top edge in the parent."""
         return self._y
 
     @y.setter
-    def y(self, value):
+    def y(self, value: int):
         if not isinstance(value, int):
             raise TypeError("Y should be a integer type")
         if self._y != value:
@@ -336,38 +335,38 @@ class TileGrid:
             self._update_current_y()
 
     @property
-    def flip_x(self):
+    def flip_x(self) -> bool:
         """If true, the left edge rendered will be the right edge of the right-most tile."""
         return self._flip_x
 
     @flip_x.setter
-    def flip_x(self, value):
+    def flip_x(self, value: bool):
         if not isinstance(value, bool):
             raise TypeError("Flip X should be a boolean type")
         if self._flip_x != value:
             self._flip_x = value
 
     @property
-    def flip_y(self):
+    def flip_y(self) -> bool:
         """If true, the top edge rendered will be the bottom edge of the bottom-most tile."""
         return self._flip_y
 
     @flip_y.setter
-    def flip_y(self, value):
+    def flip_y(self, value: bool):
         if not isinstance(value, bool):
             raise TypeError("Flip Y should be a boolean type")
         if self._flip_y != value:
             self._flip_y = value
 
     @property
-    def transpose_xy(self):
-        """If true, the TileGrids axis will be swapped. When combined with mirroring, any 90
+    def transpose_xy(self) -> bool:
+        """If true, the TileGrid's axis will be swapped. When combined with mirroring, any 90
         degree rotation can be achieved along with the corresponding mirrored version.
         """
         return self._transpose_xy
 
     @transpose_xy.setter
-    def transpose_xy(self, value):
+    def transpose_xy(self, value: bool):
         if not isinstance(value, bool):
             raise TypeError("Transpose XY should be a boolean type")
         if self._transpose_xy != value:
@@ -376,7 +375,7 @@ class TileGrid:
             self._update_current_y()
 
     @property
-    def pixel_shader(self):
+    def pixel_shader(self) -> Union[ColorConverter, Palette]:
         """The pixel shader of the tilegrid."""
         return self._pixel_shader
 
@@ -392,14 +391,14 @@ class TileGrid:
             raise ValueError("Tile index out of bounds")
         return index
 
-    def __getitem__(self, index):
+    def __getitem__(self, index: Union[Tuple[int, int], int]) -> int:
         """Returns the tile index at the given index. The index can either be
         an x,y tuple or an int equal to ``y * width + x``'.
         """
         index = self._extract_and_check_index(index)
         return self._tiles[index]
 
-    def __setitem__(self, index, value):
+    def __setitem__(self, index: Union[Tuple[int, int], int], value: int) -> None:
         """Sets the tile index at the given index. The index can either be
         an x,y tuple or an int equal to ``y * width + x``.
         """
index 0bfc8e6076020647e02f698aafa8385cd3bbdb06..d99d69db0468fdd13100d8b0dd1e2ce1c0eb2439 100644 (file)
@@ -7,10 +7,13 @@
 
 .. automodule:: displayio
     :members: Bitmap, ColorConverter, Display, EPaperDisplay, FourWire, Group, I2CDisplay,
-        OnDiskBitmap, Palette, ParallelBus, Shape, TileGrid
+        OnDiskBitmap, Palette, Shape, TileGrid
 
 .. automodule:: fontio
     :members:
 
 .. automodule:: terminalio
     :members:
+
+.. automodule:: paralleldisplay
+    :members:
similarity index 77%
rename from displayio/parallelbus.py
rename to paralleldisplay.py
index 7b4ea43491914d9b196cf846ecf4bf932ab92be6..b666121126aa977e57ae0e35b0c188077c48c668 100644 (file)
@@ -3,10 +3,10 @@
 # SPDX-License-Identifier: MIT
 
 """
-`displayio.parallelbus`
+`paralleldisplay`
 ================================================================================
 
-displayio for Blinka
+paralleldisplay for Blinka
 
 **Software and Dependencies:**
 
@@ -17,6 +17,9 @@ displayio for Blinka
 
 """
 
+import microcontroller
+import _typing
+
 __version__ = "0.0.0-auto.0"
 __repo__ = "https://github.com/adafruit/Adafruit_Blinka_displayio.git"
 
@@ -24,10 +27,20 @@ __repo__ = "https://github.com/adafruit/Adafruit_Blinka_displayio.git"
 class ParallelBus:
     """Manage updating a display over 8-bit parallel bus in the background while Python code
     runs. This protocol may be refered to as 8080-I Series Parallel Interface in datasheets.
-    It doesnt handle display initialization.
+    It doesn't handle display initialization.
     """
 
-    def __init__(self, i2c_bus, *, device_address, reset=None):
+    def __init__(
+        self,
+        *,
+        data0: microcontroller.Pin,
+        command: microcontroller.Pin,
+        chip_select: microcontroller.Pin,
+        write: microcontroller.Pin,
+        read: microcontroller.Pin,
+        reset: microcontroller.Pin,
+        frequency: int = 30000000,
+    ):
         # pylint: disable=unnecessary-pass
         """Create a ParallelBus object associated with the given pins. The
         bus is inferred from data0 by implying the next 7 additional pins on a given GPIO
@@ -42,13 +55,13 @@ class ParallelBus:
         """
         pass
 
-    def reset(self):
+    def reset(self) -> None:
         """Performs a hardware reset via the reset pin. Raises an exception if called when
         no reset pin is available.
         """
         raise NotImplementedError("ParallelBus reset has not been implemented yet")
 
-    def send(self, command, data):
+    def send(self, command: int, data: _typing.ReadableBuffer) -> None:
         """Sends the given command value followed by the full set of data. Display state,
         such as vertical scroll, set via ``send`` may or may not be reset once the code is
         done.
index 5e2a025318fc13a5b64fa950d403d303b43a704d..fa87d91387c7c40d728f7363693939a55b11362b 100644 (file)
--- a/setup.py
+++ b/setup.py
@@ -55,6 +55,6 @@ setup(
     keywords="adafruit blinka circuitpython micropython displayio lcd tft display pitft",
     # You can just specify the packages manually here if your project is
     # simple. Or you can use find_packages().
-    py_modules=["fontio", "terminalio"],
+    py_modules=["fontio", "terminalio", "paralleldisplay", "_typing"],
     packages=["displayio"],
 )