]> Repositories - hackapet/Adafruit_Blinka_Displayio.git/blobdiff - displayio/_display.py
Merge pull request #129 from makermelissa/main
[hackapet/Adafruit_Blinka_Displayio.git] / displayio / _display.py
index 7bfe532b4d0f2c6b55ab26b40f1baeca937da762..9e720541fb9787093b56bf0a024e6655ccec7bb1 100644 (file)
@@ -18,7 +18,6 @@ displayio for Blinka
 """
 
 import time
 """
 
 import time
-from array import array
 from typing import Optional
 import digitalio
 import microcontroller
 from typing import Optional
 import digitalio
 import microcontroller
@@ -82,7 +81,7 @@ class Display:
         backlight_on_high: bool = True,
         SH1107_addressing: bool = False,
     ):
         backlight_on_high: bool = True,
         SH1107_addressing: bool = False,
     ):
-        # pylint: disable=too-many-locals,invalid-name
+        # pylint: disable=too-many-locals,invalid-name, too-many-branches
         """Create a Display object on the given display bus (`displayio.FourWire` or
         `paralleldisplay.ParallelBus`).
 
         """Create a Display object on the given display bus (`displayio.FourWire` or
         `paralleldisplay.ParallelBus`).
 
@@ -112,6 +111,13 @@ class Display:
         The initialization sequence should always leave the display memory access inline with
         the scan of the display to minimize tearing artifacts.
         """
         The initialization sequence should always leave the display memory access inline with
         the scan of the display to minimize tearing artifacts.
         """
+
+        if rotation % 90 != 0:
+            raise ValueError("Display rotation must be in 90 degree increments")
+
+        if SH1107_addressing and color_depth != 1:
+            raise ValueError("color_depth must be 1 when SH1107_addressing is True")
+
         # Turn off auto-refresh as we init
         self._auto_refresh = False
         ram_width = 0x100
         # Turn off auto-refresh as we init
         self._auto_refresh = False
         ram_width = 0x100
@@ -206,7 +212,7 @@ class Display:
                 # 100Hz looks decent and doesn't keep the CPU too busy
                 self._backlight = PWMOut(backlight_pin, frequency=100, duty_cycle=0)
                 self._backlight_type = BACKLIGHT_PWM
                 # 100Hz looks decent and doesn't keep the CPU too busy
                 self._backlight = PWMOut(backlight_pin, frequency=100, duty_cycle=0)
                 self._backlight_type = BACKLIGHT_PWM
-            except ImportError:
+            except (ImportError, NotImplementedError):
                 # PWMOut not implemented on this platform
                 pass
             if self._backlight_type is None:
                 # PWMOut not implemented on this platform
                 pass
             if self._backlight_type is None:
@@ -237,8 +243,14 @@ class Display:
         self._core.send(DISPLAY_DATA, CHIP_SELECT_UNTOUCHED, pixels)
 
     def show(self, group: Group) -> None:
         self._core.send(DISPLAY_DATA, CHIP_SELECT_UNTOUCHED, pixels)
 
     def show(self, group: Group) -> None:
-        """Switches to displaying the given group of layers. When group is None, the
+        """
+        .. note:: `show()` is deprecated and will be removed when CircuitPython 9.0.0
+          is released. Use ``.root_group = group`` instead.
+
+        Switches to displaying the given group of layers. When group is None, the
         default CircuitPython terminal will be shown.
         default CircuitPython terminal will be shown.
+
+        :param Group group: The group to show.
         """
         if group is None:
             group = circuitpython_splash
         """
         if group is None:
             group = circuitpython_splash
@@ -320,7 +332,7 @@ class Display:
             )
         return areas
 
             )
         return areas
 
-    def background(self):
+    def _background(self):
         """Run background refresh tasks. Do not call directly"""
         if (
             self._auto_refresh
         """Run background refresh tasks. Do not call directly"""
         if (
             self._auto_refresh
@@ -331,7 +343,7 @@ class Display:
 
     def _refresh_area(self, area) -> bool:
         """Loop through dirty areas and redraw that area."""
 
     def _refresh_area(self, area) -> bool:
         """Loop through dirty areas and redraw that area."""
-        # pylint: disable=too-many-locals
+        # pylint: disable=too-many-locals, too-many-branches
 
         clipped = Area()
         # Clip the area to the display by overlapping the areas.
 
         clipped = Area()
         # Clip the area to the display by overlapping the areas.
@@ -371,15 +383,15 @@ class Display:
             buffer_size = pixels_per_buffer // pixels_per_word
             if pixels_per_buffer % pixels_per_word:
                 buffer_size += 1
             buffer_size = pixels_per_buffer // pixels_per_word
             if pixels_per_buffer % pixels_per_word:
                 buffer_size += 1
-        mask_length = (pixels_per_buffer // 8) + 1  # 1 bit per pixel + 1
+        mask_length = (pixels_per_buffer // 32) + 1  # 1 bit per pixel + 1
         remaining_rows = clipped.height()
 
         for subrect_index in range(subrectangles):
             subrectangle = Area(
         remaining_rows = clipped.height()
 
         for subrect_index in range(subrectangles):
             subrectangle = Area(
-                clipped.x1,
-                clipped.y1 + rows_per_buffer * subrect_index,
-                clipped.x2,
-                clipped.y1 + rows_per_buffer * (subrect_index + 1),
+                x1=clipped.x1,
+                y1=clipped.y1 + rows_per_buffer * subrect_index,
+                x2=clipped.x2,
+                y2=clipped.y1 + rows_per_buffer * (subrect_index + 1),
             )
             if remaining_rows < rows_per_buffer:
                 subrectangle.y2 = subrectangle.y1 + remaining_rows
             )
             if remaining_rows < rows_per_buffer:
                 subrectangle.y2 = subrectangle.y1 + remaining_rows
@@ -394,11 +406,16 @@ class Display:
                     8 // self._core.colorspace.depth
                 )
 
                     8 // self._core.colorspace.depth
                 )
 
-            buffer = memoryview(bytearray([0] * (buffer_size * 4)))
-            mask = memoryview(bytearray([0] * mask_length))
+            buffer = memoryview(bytearray([0] * (buffer_size * 4))).cast("I")
+            mask = memoryview(bytearray([0] * (mask_length * 4))).cast("I")
             self._core.fill_area(subrectangle, mask, buffer)
             self._core.fill_area(subrectangle, mask, buffer)
+
+            # Can't acquire display bus; skip the rest of the data.
+            if not self._core.bus_free():
+                return False
+
             self._core.begin_transaction()
             self._core.begin_transaction()
-            self._send_pixels(buffer[:subrectangle_size_bytes])
+            self._send_pixels(buffer.tobytes()[:subrectangle_size_bytes])
             self._core.end_transaction()
         return True
 
             self._core.end_transaction()
         return True
 
@@ -414,18 +431,18 @@ class Display:
         if pixels_per_buffer % pixels_per_word:
             buffer_size += 1
 
         if pixels_per_buffer % pixels_per_word:
             buffer_size += 1
 
-        buffer = bytearray([0] * (buffer_size * 4))
+        buffer = memoryview(bytearray([0] * (buffer_size * 4))).cast("I")
         mask_length = (pixels_per_buffer // 32) + 1
         mask_length = (pixels_per_buffer // 32) + 1
-        mask = array("L", [0x00000000] * mask_length)
+        mask = memoryview(bytearray([0] * (mask_length * 4))).cast("I")
         self._core.fill_area(area, mask, buffer)
         return buffer
 
         self._core.fill_area(area, mask, buffer)
         return buffer
 
-    def release(self) -> None:
+    def _release(self) -> None:
         """Release the display and free its resources"""
         self.auto_refresh = False
         self._core.release_display_core()
 
         """Release the display and free its resources"""
         self.auto_refresh = False
         self._core.release_display_core()
 
-    def reset(self) -> None:
+    def _reset(self) -> None:
         """Reset the display"""
         self.auto_refresh = True
         circuitpython_splash.x = 0
         """Reset the display"""
         self.auto_refresh = True
         circuitpython_splash.x = 0
@@ -498,9 +515,33 @@ class Display:
 
     @rotation.setter
     def rotation(self, value: int):
 
     @rotation.setter
     def rotation(self, value: int):
+        if value % 90 != 0:
+            raise ValueError("Display rotation must be in 90 degree increments")
+        transposed = self._core.rotation in (90, 270)
+        will_transposed = value in (90, 270)
+        if transposed != will_transposed:
+            self._core.width, self._core.height = self._core.height, self._core.width
         self._core.set_rotation(value)
         self._core.set_rotation(value)
+        if self._core.current_group is not None:
+            self._core.current_group._update_transform(  # pylint: disable=protected-access
+                self._core.transform
+            )
 
     @property
     def bus(self) -> _DisplayBus:
         """Current Display Bus"""
         return self._core.get_bus()
 
     @property
     def bus(self) -> _DisplayBus:
         """Current Display Bus"""
         return self._core.get_bus()
+
+    @property
+    def root_group(self) -> Group:
+        """
+        The root group on the display.
+        If the root group is set to `displayio.CIRCUITPYTHON_TERMINAL`, the default
+        CircuitPython terminal will be shown.
+        If the root group is set to ``None``, no output will be shown.
+        """
+        return self._core.current_group
+
+    @root_group.setter
+    def root_group(self, new_group: Group) -> None:
+        self._set_root_group(new_group)