1 # The MIT License (MIT)
 
   3 # Copyright (c) 2020 Melissa LeBlanc-Williams for Adafruit Industries
 
   5 # Permission is hereby granted, free of charge, to any person obtaining a copy
 
   6 # of this software and associated documentation files (the "Software"), to deal
 
   7 # in the Software without restriction, including without limitation the rights
 
   8 # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 
   9 # copies of the Software, and to permit persons to whom the Software is
 
  10 # furnished to do so, subject to the following conditions:
 
  12 # The above copyright notice and this permission notice shall be included in
 
  13 # all copies or substantial portions of the Software.
 
  15 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 
  16 # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 
  17 # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 
  18 # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 
  19 # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 
  20 # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 
  25 ================================================================================
 
  29 **Software and Dependencies:**
 
  32   https://github.com/adafruit/Adafruit_Blinka/releases
 
  34 * Author(s): Melissa LeBlanc-Williams
 
  38 from recordclass import recordclass
 
  39 from displayio.tilegrid import TileGrid
 
  41 __version__ = "0.0.0-auto.0"
 
  42 __repo__ = "https://github.com/adafruit/Adafruit_Blinka_displayio.git"
 
  45 Transform = recordclass("Transform", "x y dx dy scale transpose_xy mirror_x mirror_y")
 
  49     """Manage a group of sprites and groups and how they are inter-related."""
 
  51     def __init__(self, *, max_size=4, scale=1, x=0, y=0):
 
  52         """Create a Group of a given size and scale. Scale is in
 
  53         one dimension. For example, scale=2 leads to a layer’s
 
  54         pixel being 2x2 pixels when in the group.
 
  56         if not isinstance(max_size, int) or max_size < 1:
 
  57             raise ValueError("Max Size must be >= 1")
 
  58         self._max_size = max_size
 
  59         if not isinstance(scale, int) or scale < 1:
 
  60             raise ValueError("Scale must be >= 1")
 
  66         self._supported_types = (TileGrid, Group)
 
  67         self._absolute_transform = None
 
  69         self._absolute_transform = Transform(0, 0, 1, 1, 1, False, False, False)
 
  71     def update_transform(self, parent_transform):
 
  72         """Update the parent transform and child transforms"""
 
  73         self.in_group = parent_transform is not None
 
  77             if parent_transform.transpose_xy:
 
  79             self._absolute_transform.x = parent_transform.x + parent_transform.dx * x
 
  80             self._absolute_transform.y = parent_transform.y + parent_transform.dy * y
 
  81             self._absolute_transform.dx = parent_transform.dx * self._scale
 
  82             self._absolute_transform.dy = parent_transform.dy * self._scale
 
  83             self._absolute_transform.transpose_xy = parent_transform.transpose_xy
 
  84             self._absolute_transform.mirror_x = parent_transform.mirror_x
 
  85             self._absolute_transform.mirror_y = parent_transform.mirror_y
 
  86             self._absolute_transform.scale = parent_transform.scale * self._scale
 
  87         self._update_child_transforms()
 
  89     def _update_child_transforms(self):
 
  91             for layer in self._layers:
 
  92                 layer.update_transform(self._absolute_transform)
 
  94     def _removal_cleanup(self, index):
 
  95         layer = self._layers[index]
 
  96         layer.update_transform(None)
 
  98     def _layer_update(self, index):
 
  99         layer = self._layers[index]
 
 100         layer.update_transform(self._absolute_transform)
 
 102     def append(self, layer):
 
 103         """Append a layer to the group. It will be drawn
 
 106         self.insert(len(self._layers), layer)
 
 108     def insert(self, index, layer):
 
 109         """Insert a layer into the group."""
 
 110         if not isinstance(layer, self._supported_types):
 
 111             raise ValueError("Invalid Group Member")
 
 113             raise ValueError("Layer already in a group.")
 
 114         if len(self._layers) == self._max_size:
 
 115             raise RuntimeError("Group full")
 
 116         self._layers.insert(index, layer)
 
 117         self._layer_update(index)
 
 119     def index(self, layer):
 
 120         """Returns the index of the first copy of layer.
 
 121         Raises ValueError if not found.
 
 123         return self._layers.index(layer)
 
 125     def pop(self, index=-1):
 
 126         """Remove the ith item and return it."""
 
 127         self._removal_cleanup(index)
 
 128         return self._layers.pop(index)
 
 130     def remove(self, layer):
 
 131         """Remove the first copy of layer. Raises ValueError
 
 132         if it is not present."""
 
 133         index = self.index(layer)
 
 134         self._layers.pop(index)
 
 137         """Returns the number of layers in a Group"""
 
 138         return len(self._layers)
 
 140     def __getitem__(self, index):
 
 141         """Returns the value at the given index."""
 
 142         return self._layers[index]
 
 144     def __setitem__(self, index, value):
 
 145         """Sets the value at the given index."""
 
 146         self._removal_cleanup(index)
 
 147         self._layers[index] = value
 
 148         self._layer_update(index)
 
 150     def __delitem__(self, index):
 
 151         """Deletes the value at the given index."""
 
 152         del self._layers[index]
 
 154     def _fill_area(self, buffer):
 
 158         for layer in self._layers:
 
 159             if isinstance(layer, (Group, TileGrid)):
 
 160                 layer._fill_area(buffer)  # pylint: disable=protected-access
 
 164         """True when the Group and all of it’s layers are not visible. When False, the
 
 165         Group’s layers are visible if they haven’t been hidden.
 
 170     def hidden(self, value):
 
 171         if not isinstance(value, (bool, int)):
 
 172             raise ValueError("Expecting a boolean or integer value")
 
 173         self._hidden = bool(value)
 
 177         """Scales each pixel within the Group in both directions. For example, when
 
 178         scale=2 each pixel will be represented by 2x2 pixels.
 
 183     def scale(self, value):
 
 184         if not isinstance(value, int) or value < 1:
 
 185             raise ValueError("Scale must be >= 1")
 
 186         if self._scale != value:
 
 187             parent_scale = self._absolute_transform.scale / self._scale
 
 188             self._absolute_transform.dx = (
 
 189                 self._absolute_transform.dx / self._scale * value
 
 191             self._absolute_transform.dy = (
 
 192                 self._absolute_transform.dy / self._scale * value
 
 194             self._absolute_transform.scale = parent_scale * value
 
 197             self._update_child_transforms()
 
 201         """X position of the Group in the parent."""
 
 206         if not isinstance(value, int):
 
 207             raise ValueError("x must be an integer")
 
 209             if self._absolute_transform.transpose_xy:
 
 210                 dy_value = self._absolute_transform.dy / self._scale
 
 211                 self._absolute_transform.y += dy_value * (value - self._x)
 
 213                 dx_value = self._absolute_transform.dx / self._scale
 
 214                 self._absolute_transform.x += dx_value * (value - self._x)
 
 216             self._update_child_transforms()
 
 220         """Y position of the Group in the parent."""
 
 225         if not isinstance(value, int):
 
 226             raise ValueError("y must be an integer")
 
 228             if self._absolute_transform.transpose_xy:
 
 229                 dx_value = self._absolute_transform.dx / self._scale
 
 230                 self._absolute_transform.x += dx_value * (value - self._y)
 
 232                 dy_value = self._absolute_transform.dy / self._scale
 
 233                 self._absolute_transform.y += dy_value * (value - self._y)
 
 235             self._update_child_transforms()