self._x_shift += 1
power_of_two = power_of_two << 1
- self._x_mask = (1 << self._x_shift) - 1 # UUsed as a modulus on the x value
+ self._x_mask = (1 << self._x_shift) - 1 # Used as a modulus on the x value
self._bitmask = (1 << bits_per_value) - 1
self._dirty_area = Area(0, 0, width, height)
from ._displaycore import _DisplayCore
from ._displaybus import _DisplayBus
from ._colorconverter import ColorConverter
-from ._group import Group
+from ._group import Group, circuitpython_splash
from ._area import Area
from ._constants import (
CHIP_SELECT_TOGGLE_EVERY_BYTE,
self._auto_refresh = auto_refresh
self._initialize(init_sequence)
+
self._current_group = None
self._last_refresh_call = 0
self._refresh_thread = None
- if self._auto_refresh:
- self.auto_refresh = True
self._colorconverter = ColorConverter()
self._backlight_type = None
self._backlight_type = BACKLIGHT_IN_OUT
self._backlight = digitalio.DigitalInOut(backlight_pin)
self._backlight.switch_to_output()
- self.brightness = brightness
+ self.brightness = brightness
+ if not circuitpython_splash._in_group:
+ self._set_root_group(circuitpython_splash)
+ self.auto_refresh = auto_refresh
def __new__(cls, *args, **kwargs):
from . import ( # pylint: disable=import-outside-toplevel, cyclic-import
"""Switches to displaying the given group of layers. When group is None, the
default CircuitPython terminal will be shown.
"""
- self._core.show(group)
+ if group is None:
+ group = circuitpython_splash
+ self._core.set_root_group(group)
+
+ def _set_root_group(self, root_group: Group) -> None:
+ ok = self._core.set_root_group(root_group)
+ if not ok:
+ raise ValueError("Group already used")
def refresh(
self,
buffer_size = 128
clipped = Area()
+ # Clip the area to the display by overlapping the areas.
+ # If there is no overlap then we're done.
if not self._core.clip_area(area, clipped):
return True
pixels_per_buffer = clipped.size()
subrectangles = 1
-
+ # for SH1107 and other boundary constrained controllers
+ # write one single row at a time
if self._core.sh1107_addressing:
subrectangles = rows_per_buffer // 8
rows_per_buffer = 8
rows_per_buffer = buffer_size * pixels_per_word // clipped.width()
if rows_per_buffer == 0:
rows_per_buffer = 1
+ # If pixels are packed by column then ensure rows_per_buffer is on a byte boundary
if (
self._core.colorspace.depth < 8
and self._core.colorspace.pixels_in_byte_share_row
if pixels_per_buffer % pixels_per_word:
buffer_size += 1
+ # TODO: Optimize with memoryview
buffer = bytearray([0] * (buffer_size * struct.calcsize("I")))
mask_length = (pixels_per_buffer // 32) + 1
mask = array("L", [0] * mask_length)
)
if remaining_rows < rows_per_buffer:
subrectangle.y2 = subrectangle.y1 + remaining_rows
+ remaining_rows -= rows_per_buffer
self._core.set_region_to_update(subrectangle)
if self._core.colorspace.depth >= 8:
subrectangle_size_bytes = subrectangle.size() * (
)
self._core.fill_area(subrectangle, mask, buffer)
-
self._core.begin_transaction()
self._send_pixels(buffer[:subrectangle_size_bytes])
self._core.end_transaction()
def reset(self) -> None:
"""Reset the display"""
self.auto_refresh = True
+ circuitpython_splash.x = 0
+ circuitpython_splash.y = 0
+ if not circuitpython_splash._in_group: # pylint: disable=protected-access
+ self._set_root_group(circuitpython_splash)
@property
def auto_refresh(self) -> bool:
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
-
+ def set_root_group(self, root_group: Group) -> bool:
"""
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
- """
+ # pylint: disable=protected-access
if root_group == self.current_group:
return True
"""
Send the data to the current bus
"""
- # print(data_type, chip_select, data)
self._send(data_type, chip_select, data)
def begin_transaction(self) -> None:
class Group:
+ # pylint: disable=too-many-instance-attributes
"""
Manage a group of sprites and groups and how they are inter-related.
self._group_x = x
self._group_y = y
self._hidden_group = False
+ self._hidden_by_parent = False
self._layers = []
self._supported_types = (TileGrid, Group)
self._in_group = False
) -> bool:
if self._hidden_group:
return False
-
for layer in self._layers:
if isinstance(layer, (Group, TileGrid)):
if layer._fill_area( # pylint: disable=protected-access
layer._finish_refresh() # pylint: disable=protected-access
def _get_refresh_areas(self, areas: list[Area]) -> None:
+ # pylint: disable=protected-access
+ for layer in self._layers:
+ if isinstance(layer, Group):
+ layer._get_refresh_areas(areas)
+ elif isinstance(layer, TileGrid):
+ if not layer._get_rendered_hidden():
+ layer._get_refresh_areas(areas)
+
+ def _set_hidden(self, hidden: bool) -> None:
+ if self._hidden_group == hidden:
+ return
+ self._hidden_group = hidden
+ if self._hidden_by_parent:
+ return
for layer in self._layers:
if isinstance(layer, (Group, TileGrid)):
- if not layer.hidden:
- layer._get_refresh_areas(areas) # pylint: disable=protected-access
+ layer._set_hidden_by_parent(hidden) # pylint: disable=protected-access
+
+ def _set_hidden_by_parent(self, hidden: bool) -> None:
+ if self._hidden_by_parent == hidden:
+ return
+ self._hidden_by_parent = hidden
+ if self._hidden_group:
+ return
+ for layer in self._layers:
+ if isinstance(layer, (Group, TileGrid)):
+ layer._set_hidden_by_parent(hidden) # pylint: disable=protected-access
@property
def hidden(self) -> bool:
return self._hidden_group
@hidden.setter
- def hidden(self, value: bool):
+ def hidden(self, value: bool) -> None:
if not isinstance(value, (bool, int)):
raise ValueError("Expecting a boolean or integer value")
- self._hidden_group = bool(value)
+ value = bool(value)
+ self._set_hidden(value)
@property
def scale(self) -> int:
self._absolute_transform.y += dy_value * (value - self._group_y)
self._group_y = value
self._update_child_transforms()
+
+
+circuitpython_splash = Group(scale=2, x=0, y=0)
class Palette:
- """Map a pixel palette_index to a full color. Colors are transformed to the display’s
+ """Map a pixel palette_index to a full color. Colors are transformed to the display's
format internally to save memory.
"""
- def __init__(self, color_count: int, dither: bool = False):
- """Create a Palette object to store a set number of colors."""
+ def __init__(self, color_count: int, *, dither: bool = False):
+ """Create a Palette object to store a set number of colors.
+
+ :param int color_count: The number of colors in the Palette
+ :param bool dither: When true, dither the RGB color before converting to the
+ display's color space
+ """
self._needs_refresh = False
self._dither = dither
for _ in range(color_count):
self._colors.append(self._make_color(0))
- def _make_color(self, value, transparent=False):
+ @staticmethod
+ def _make_color(value, transparent=False):
color = ColorStruct(transparent=transparent)
if isinstance(value, (tuple, list, bytes, bytearray)):
else:
raise TypeError("Color buffer must be a buffer, tuple, list, or int")
color.rgb888 = value
- self._needs_refresh = True
return color
(to represent an RGB value). Value can be an int, bytes (3 bytes (RGB) or
4 bytes (RGB + pad byte)), bytearray, or a tuple or list of 3 integers.
"""
- if self._colors[index].rgb888 != value:
- self._colors[index] = self._make_color(value)
+ if self._colors[index].rgb888 == value:
+ return
+ self._colors[index] = self._make_color(value)
+ self._colors[index].cached_colorspace = None
+ self._needs_refresh = True
def __getitem__(self, index: int) -> Optional[int]:
if not 0 <= index < len(self._colors):
def make_transparent(self, palette_index: int) -> None:
"""Set the palette index to be a transparent color"""
self._colors[palette_index].transparent = True
+ self._needs_refresh = True
def make_opaque(self, palette_index: int) -> None:
"""Set the palette index to be an opaque color"""
self._colors[palette_index].transparent = False
+ self._needs_refresh = True
def _get_palette(self):
"""Generate a palette for use with PIL"""
return
rgb888_pixel = input_pixel
+ rgb888_pixel.pixel = self._colors[palette_index].rgb888
ColorConverter._convert_color( # pylint: disable=protected-access
colorspace, self._dither, rgb888_pixel, output_color
)
return self._colors[palette_index].transparent
def _finish_refresh(self):
- pass
+ self._needs_refresh = False
@property
def dither(self) -> bool:
struct.pack_into(
"H",
buffer,
- offset,
+ offset * 2,
output_pixel.pixel,
)
elif colorspace.depth == 32:
struct.pack_into(
"I",
buffer,
- offset,
+ offset * 4,
output_pixel.pixel,
)
elif colorspace.depth == 8:
)
areas.append(self._dirty_area)
+ def _set_hidden(self, hidden: bool) -> None:
+ self._hidden_tilegrid = hidden
+ self._rendered_hidden = False
+ if not hidden:
+ self._full_change = True
+
+ def _set_hidden_by_parent(self, hidden: bool) -> None:
+ self._hidden_by_parent = hidden
+ self._rendered_hidden = False
+ if not hidden:
+ self._full_change = True
+
+ def _get_rendered_hidden(self) -> bool:
+ return self._rendered_hidden
+
@property
def hidden(self) -> bool:
"""True when the TileGrid is hidden. This may be False even
def hidden(self, value: bool):
if not isinstance(value, (bool, int)):
raise ValueError("Expecting a boolean or integer value")
- self._hidden_tilegrid = bool(value)
+ value = bool(value)
+ self._set_hidden(value)
@property
def x(self) -> int: