]> Repositories - hackapet/Adafruit_Blinka_Displayio.git/blobdiff - displayio/_displaycore.py
Start splitting display functions into display core
[hackapet/Adafruit_Blinka_Displayio.git] / displayio / _displaycore.py
diff --git a/displayio/_displaycore.py b/displayio/_displaycore.py
new file mode 100644 (file)
index 0000000..acedf6e
--- /dev/null
@@ -0,0 +1,457 @@
+# SPDX-FileCopyrightText: 2021 Melissa LeBlanc-Williams for Adafruit Industries
+# SPDX-FileCopyrightText: 2021 James Carr
+#
+# SPDX-License-Identifier: MIT
+
+"""
+`displayio._displaycore`
+================================================================================
+
+Super class of the display classes
+
+**Software and Dependencies:**
+
+* Adafruit Blinka:
+  https://github.com/adafruit/Adafruit_Blinka/releases
+
+* Author(s): James Carr, Melissa LeBlanc-Williams
+
+"""
+
+__version__ = "0.0.0-auto.0"
+__repo__ = "https://github.com/adafruit/Adafruit_Blinka_Displayio.git"
+
+
+from typing import Optional, Union
+import _typing
+from ._fourwire import FourWire
+from ._group import Group
+from ._i2cdisplay import I2CDisplay
+from ._structs import ColorspaceStruct, TransformStruct, RectangleStruct
+from ._area import Area
+
+from paralleldisplay import ParallelBus
+from ._constants import (
+    DISPLAY_COMMAND,
+    DISPLAY_DATA,
+    CHIP_SELECT_TOGGLE_EVERY_BYTE,
+    CHIP_SELECT_UNTOUCHED,
+)
+
+displays = []
+
+# Functions
+#* construct
+#  show
+#  set_dither
+#^ bus_reset
+#  set_region_to_update
+#* release
+#  fill_area
+#  clip_area (_clip)
+
+class _DisplayCore:
+    # pylint: disable=too-many-arguments, too-many-instance-attributes
+
+    def __init__(
+        self,
+        bus,
+        width: int,
+        height: int,
+        ram_width: int,
+        ram_height: int,
+        colstart: int,
+        rowstart: int,
+        rotation: int,
+        color_depth: int,
+        grayscale: bool,
+        pixels_in_byte_share_row: bool,
+        bytes_per_cell: int,
+        reverse_pixels_in_byte: bool,
+        reverse_bytes_in_word: bool
+    ):
+        self._colorspace = ColorspaceStruct(
+            depth = color_depth,
+            grayscale = grayscale,
+            grayscale_bit = 8 - color_depth,
+            pixels_in_byte_share_row = pixels_in_byte_share_row,
+            bytes_per_cell = bytes_per_cell,
+            reverse_pixels_in_byte = reverse_pixels_in_byte,
+            reverse_bytes_in_word = reverse_bytes_in_word,
+            dither = False
+        )
+        self._current_group = None
+        self._colstart = colstart
+        self._rowstart = rowstart
+        self._last_refresh = 0
+        self._refresh_in_progress = False
+
+        if bus:
+            if isinstance(bus, (FourWire, I2CDisplay, ParallelBus)):
+                self._bus_reset = bus.reset
+                self._begin_transaction = bus._begin_transaction
+                self._send = bus._send
+                self._end_transaction = bus._end_transaction
+            else:
+                raise ValueError("Unsupported display bus type")
+
+        self._bus = bus
+        self._area = Area(0, 0, width, height)
+
+        self._width = width
+        self._height = height
+        self._ram_width = ram_width
+        self._ram_height = ram_height
+        self._rotation = rotation
+        self._transform = TransformStruct()
+
+    def set_rotation(self, rotation: int) -> None:
+        """
+        Sets the rotation of the display as an int in degrees.
+        """
+        # pylint: disable=protected-access
+        transposed = self._rotation in (90, 270)
+        will_be_transposed = rotation in (90, 270)
+        if transposed != will_be_transposed:
+            self._width, self._height = self._height, self._width
+
+        height = self._height
+        width = self._width
+
+        rotation %= 360
+        self._rotation = rotation
+        self._transform.x = 0
+        self._transform.y = 0
+        self._transform.scale = 1
+        self._transform.mirror_x = False
+        self._transform.mirror_y = False
+        self._transform.transpose_xy = False
+
+        if rotation in (0, 180):
+            if rotation == 180:
+                self._transform.mirror_x = True
+                self._transform.mirror_y = True
+        else:
+            self._transform.transpose_xy = True
+            if rotation == 270:
+                self._transform.mirror_y = True
+            else:
+                self._transform.mirror_x = True
+
+        self._area.x1 = 0
+        self._area.y1 = 0
+        self._area.next = None
+
+        self._transform.dx = 1
+        self._transform.dy = 1
+        if self._transform.transpose_xy:
+            self._area.x2 = height
+            self._area.y2 = width
+            if self._transform.mirror_x:
+                self._transform.x = height
+                self._transform.dx = -1
+            if self._transform.mirror_y:
+                self._transform.y = width
+                self._transform.dy = -1
+        else:
+            self._area.x2 = width
+            self._area.y2 = height
+            if self._transform.mirror_x:
+                self._transform.x = width
+                self._transform.dx = -1
+            if self._transform.mirror_y:
+                self._transform.y = height
+                self._transform.dy = -1
+
+        if self._current_group is not None:
+            self._current_group._update_transform(self._transform)
+
+    def show(self, root_group: Group) -> bool:
+        # pylint: disable=protected-access
+
+        """
+        Switches to displaying the given group of layers. When group is `None`, the
+        default CircuitPython terminal will be shown.
+
+        :param Optional[displayio.Group] root_group: The group to show.
+        """
+
+        """
+        # TODO: Implement Supervisor
+        if root_group is None:
+            circuitpython_splash = _Supervisor().circuitpython_splash
+            if not circuitpython_splash._in_group:
+                root_group = circuitpython_splash
+            elif self._current_group == circuitpython_splash:
+                return True
+        """
+
+        if root_group == self._current_group:
+            return True
+
+        if root_group is not None and root_group._in_group:
+            return False
+
+        if self._current_group is not None:
+            self._current_group._in_group = False
+
+        if root_group is not None:
+            root_group._update_transform(self._transform)
+            root_group._in_group = True
+
+        self._current_group = root_group
+        self._full_refresh = True
+
+        return True
+
+    def set_region_to_update(
+        self,
+        column_command: int,
+        row_command: int,
+        set_current_column_command: Optional[int],
+        set_current_row_command: Optional[int],
+        data_as_commands: bool,
+        always_toggle_chip_select: bool,
+        area: Area,
+        SH1107_addressing: bool,
+    ) -> None:
+        # pylint: disable=invalid-name, too-many-arguments, too-many-locals, too-many-branches,
+        # pylint: disable=too-many-statements
+
+        big_endian = True  # default is True # TODO ????
+
+        x1 = area.x1 + self._colstart
+        x2 = area.x2 + self._colstart
+        y1 = area.y1 + self._rowstart
+        y2 = area.y2 + self._rowstart
+
+        # Collapse down the dimension where multiple pixels are in a byte.
+        if self._colorspace.depth < 8:
+            pixels_per_byte = 8 // self._colorspace.depth
+            if self._colorspace.pixels_in_byte_share_row:
+                x1 //= pixels_per_byte * self._colorspace.bytes_per_cell
+                x2 //= pixels_per_byte * self._colorspace.bytes_per_cell
+            else:
+                y1 //= pixels_per_byte * self._colorspace.bytes_per_cell
+                y2 //= pixels_per_byte * self._colorspace.bytes_per_cell
+
+        x2 -= 1
+        y2 -= 1
+
+        chip_select = CHIP_SELECT_UNTOUCHED
+        if always_toggle_chip_select or data_as_commands:
+            chip_select = CHIP_SELECT_TOGGLE_EVERY_BYTE
+
+        # Set column
+        self._begin_transaction()
+        data = bytearray(5)
+        data[0] = column_command
+        if not data_as_commands:
+            self._send(DISPLAY_COMMAND, CHIP_SELECT_UNTOUCHED, data, 1)
+            data_type = DISPLAY_DATA
+            data_length = 0
+        else:
+            data_type = DISPLAY_COMMAND
+            data_length = 1
+
+        if self._ram_width < 0x100:
+            data[data_length] = x1
+            data_length += 1
+            data[data_length] = x2
+            data_length += 1
+        else:
+            if big_endian:
+                data[data_length] = x1 >> 8
+                data_length += 1
+                data[data_length] = x1 & 0xFF
+                data_length += 1
+                data[data_length] = x2 >> 8
+                data_length += 1
+                data[data_length] = x2 & 0xFF
+                data_length += 1
+            else:
+                data[data_length] = x1 & 0xFF
+                data_length += 1
+                data[data_length] = x1 >> 8
+                data_length += 1
+                data[data_length] = x2 & 0xFF
+                data_length += 1
+                data[data_length] = x2 >> 8
+                data_length += 1
+
+        # Quirk for "SH1107_addressing"
+        #     Column lower command = 0x00, Column upper command = 0x10
+        if SH1107_addressing:
+            data[0] = ((x1 >> 4) & 0x0F) | 0x10  # 0x10 to 0x17
+            data[1] = x1 & 0x0F  # 0x00 to 0x0F
+            data_length = 2
+
+        self._send(data_type, chip_select, data, data_length)
+        self._end_transaction()
+
+        if set_current_column_command is not None:
+            command = bytearray(1)
+            command[0] = set_current_column_command
+            self._begin_transaction()
+            self._send(DISPLAY_COMMAND, chip_select, command, 1)
+            self._send(DISPLAY_DATA, chip_select, data, data_length // 2)
+            self._end_transaction()
+
+        # Set row
+        self._begin_transaction()
+        data[0] = row_command
+        data_length = 1
+        if not data_as_commands:
+            self._send(DISPLAY_COMMAND, CHIP_SELECT_UNTOUCHED, data, 1)
+            data_length = 0
+
+        if self._ram_height < 0x100:
+            data[data_length] = y1
+            data_length += 1
+            data[data_length] = y2
+            data_length += 1
+        else:
+            if big_endian:
+                data[data_length] = y1 >> 8
+                data_length += 1
+                data[data_length] = y1 & 0xFF
+                data_length += 1
+                data[data_length] = y2 >> 8
+                data_length += 1
+                data[data_length] = y2 & 0xFF
+                data_length += 1
+                # TODO Which is right? The core uses above
+            else:
+                data[data_length] = y1 & 0xFF
+                data_length += 1
+                data[data_length] = y1 >> 8
+                data_length += 1
+                data[data_length] = y2 & 0xFF
+                data_length += 1
+                data[data_length] = y2 >> 8
+                data_length += 1
+
+        # Quirk for "SH1107_addressing"
+        #  Page address command = 0xB0
+        if SH1107_addressing:
+            # Set the page to out y value
+            data[0] = 0xB0 | y1
+            data_length = 1
+
+        self._send(data_type, chip_select, data, data_length)
+        self._end_transaction()
+
+        if set_current_row_command is not None:
+            command = bytearray(1)
+            command[0] = set_current_row_command
+            self._begin_transaction()
+            self._send(DISPLAY_COMMAND, chip_select, command, 1)
+            self._send(DISPLAY_DATA, chip_select, data, data_length // 2)
+            self._end_transaction()
+
+    def start_refresh(self) -> bool:
+        # pylint: disable=protected-access
+
+        if self._refresh_in_progress:
+            return False
+
+        self._refresh_in_progress = True
+        #self._last_refresh = _Supervisor()._ticks_ms64()
+        return True
+
+    def finish_refresh(self) -> None:
+        # pylint: disable=protected-access
+
+        if self._current_group is not None:
+            self._current_group._finish_refresh()
+
+        self._full_refresh = False
+        self._refresh_in_progress = False
+        #self._last_refresh = _Supervisor()._ticks_ms64()
+
+    def get_refresh_areas(self):
+        subrectangles = []
+        if self._current_group is not None:
+            # Eventually calculate dirty rectangles here
+            subrectangles.append(RectangleStruct(0, 0, self._width, self._height))
+        return subrectangles
+
+    def release(self) -> None:
+        # pylint: disable=protected-access
+
+        if self._current_group is not None:
+            self._current_group._in_group = False
+
+    def fill_area(
+        self, area: Area, mask: _typing.WriteableBuffer, buffer: _typing.WriteableBuffer
+    ) -> bool:
+        # pylint: disable=protected-access
+
+        return self._current_group._fill_area(self._colorspace, area, mask, buffer)
+
+    def clip_area(self, area: Area, clipped: Area) -> bool:
+        # pylint: disable=protected-access
+
+        overlaps = self._area._compute_overlap(area, clipped)
+        if not overlaps:
+            return False
+
+        # Expand the area if we have multiple pixels per byte and we need to byte align the bounds
+        if self._colorspace.depth < 8:
+            pixels_per_byte = (
+                8 // self._colorspace.depth * self._colorspace.bytes_per_cell
+            )
+            if self._colorspace.pixels_in_byte_share_row:
+                if clipped.x1 % pixels_per_byte != 0:
+                    clipped.x1 -= clipped.x1 % pixels_per_byte
+                if clipped.x2 % pixels_per_byte != 0:
+                    clipped.x2 += pixels_per_byte - clipped.x2 % pixels_per_byte
+            else:
+                if clipped.y1 % pixels_per_byte != 0:
+                    clipped.y1 -= clipped.y1 % pixels_per_byte
+                if clipped.y2 % pixels_per_byte != 0:
+                    clipped.y2 += pixels_per_byte - clipped.y2 % pixels_per_byte
+
+        return True
+
+    def send(self, data_type: int, chip_select: int, data: _typing.ReadableBuffer) -> None:
+        """
+        Send the data to the current bus
+        """
+        self._send(data_type, chip_select, data)
+
+    def begin_transaction(self) -> None:
+        """
+        Begin Bus Transaction
+        """
+        self._begin_transaction()
+
+    def end_transaction(self) -> None:
+        """
+        End Bus Transaction
+        """
+        self._end_transaction()
+
+    def get_width(self) -> int:
+        """
+        Gets the width of the display in pixels.
+        """
+        return self._width
+
+    def get_height(self) -> int:
+        """
+        Gets the height of the display in pixels.
+        """
+        return self._height
+
+    def get_rotation(self) -> int:
+        """
+        Gets the rotation of the display as an int in degrees.
+        """
+        return self._rotation
+            
+    def get_bus(self) -> Union[FourWire,ParallelBus,I2CDisplay]:
+        """
+        The bus being used by the display. [readonly]
+        """
+        return self._bus
\ No newline at end of file