1 # SPDX-FileCopyrightText: 2020 Melissa LeBlanc-Williams for Adafruit Industries
3 # SPDX-License-Identifier: MIT
7 ================================================================================
11 **Software and Dependencies:**
14 https://github.com/adafruit/Adafruit_Blinka/releases
16 * Author(s): Melissa LeBlanc-Williams
20 from recordclass import recordclass
21 from displayio.tilegrid import TileGrid
23 __version__ = "0.0.0-auto.0"
24 __repo__ = "https://github.com/adafruit/Adafruit_Blinka_displayio.git"
27 Transform = recordclass("Transform", "x y dx dy scale transpose_xy mirror_x mirror_y")
31 """Manage a group of sprites and groups and how they are inter-related."""
33 def __init__(self, *, max_size=4, scale=1, x=0, y=0):
34 """Create a Group of a given size and scale. Scale is in
35 one dimension. For example, scale=2 leads to a layer’s
36 pixel being 2x2 pixels when in the group.
38 if not isinstance(max_size, int) or max_size < 1:
39 raise ValueError("Max Size must be >= 1")
40 self._max_size = max_size
41 if not isinstance(scale, int) or scale < 1:
42 raise ValueError("Scale must be >= 1")
46 self._hidden_group = False
48 self._supported_types = (TileGrid, Group)
49 self._absolute_transform = None
51 self._absolute_transform = Transform(
52 0, 0, 1, 1, self._scale, False, False, False
55 def update_transform(self, parent_transform):
56 """Update the parent transform and child transforms"""
57 self.in_group = parent_transform is not None
61 if parent_transform.transpose_xy:
63 self._absolute_transform.x = parent_transform.x + parent_transform.dx * x
64 self._absolute_transform.y = parent_transform.y + parent_transform.dy * y
65 self._absolute_transform.dx = parent_transform.dx * self._scale
66 self._absolute_transform.dy = parent_transform.dy * self._scale
67 self._absolute_transform.transpose_xy = parent_transform.transpose_xy
68 self._absolute_transform.mirror_x = parent_transform.mirror_x
69 self._absolute_transform.mirror_y = parent_transform.mirror_y
70 self._absolute_transform.scale = parent_transform.scale * self._scale
71 self._update_child_transforms()
73 def _update_child_transforms(self):
75 for layer in self._layers:
76 layer.update_transform(self._absolute_transform)
78 def _removal_cleanup(self, index):
79 layer = self._layers[index]
80 layer.update_transform(None)
82 def _layer_update(self, index):
83 layer = self._layers[index]
84 layer.update_transform(self._absolute_transform)
86 def append(self, layer):
87 """Append a layer to the group. It will be drawn
90 self.insert(len(self._layers), layer)
92 def insert(self, index, layer):
93 """Insert a layer into the group."""
94 if not isinstance(layer, self._supported_types):
95 raise ValueError("Invalid Group Member")
97 raise ValueError("Layer already in a group.")
98 if len(self._layers) == self._max_size:
99 raise RuntimeError("Group full")
100 self._layers.insert(index, layer)
101 self._layer_update(index)
103 def index(self, layer):
104 """Returns the index of the first copy of layer.
105 Raises ValueError if not found.
107 return self._layers.index(layer)
109 def pop(self, index=-1):
110 """Remove the ith item and return it."""
111 self._removal_cleanup(index)
112 return self._layers.pop(index)
114 def remove(self, layer):
115 """Remove the first copy of layer. Raises ValueError
116 if it is not present."""
117 index = self.index(layer)
118 self._layers.pop(index)
121 """Returns the number of layers in a Group"""
122 return len(self._layers)
124 def __getitem__(self, index):
125 """Returns the value at the given index."""
126 return self._layers[index]
128 def __setitem__(self, index, value):
129 """Sets the value at the given index."""
130 self._removal_cleanup(index)
131 self._layers[index] = value
132 self._layer_update(index)
134 def __delitem__(self, index):
135 """Deletes the value at the given index."""
136 del self._layers[index]
138 def _fill_area(self, buffer):
139 if self._hidden_group:
142 for layer in self._layers:
143 if isinstance(layer, (Group, TileGrid)):
144 layer._fill_area(buffer) # pylint: disable=protected-access
148 """True when the Group and all of it’s layers are not visible. When False, the
149 Group’s layers are visible if they haven’t been hidden.
151 return self._hidden_group
154 def hidden(self, value):
155 if not isinstance(value, (bool, int)):
156 raise ValueError("Expecting a boolean or integer value")
157 self._hidden_group = bool(value)
161 """Scales each pixel within the Group in both directions. For example, when
162 scale=2 each pixel will be represented by 2x2 pixels.
167 def scale(self, value):
168 if not isinstance(value, int) or value < 1:
169 raise ValueError("Scale must be >= 1")
170 if self._scale != value:
171 parent_scale = self._absolute_transform.scale / self._scale
172 self._absolute_transform.dx = (
173 self._absolute_transform.dx / self._scale * value
175 self._absolute_transform.dy = (
176 self._absolute_transform.dy / self._scale * value
178 self._absolute_transform.scale = parent_scale * value
181 self._update_child_transforms()
185 """X position of the Group in the parent."""
190 if not isinstance(value, int):
191 raise ValueError("x must be an integer")
192 if self._group_x != value:
193 if self._absolute_transform.transpose_xy:
194 dy_value = self._absolute_transform.dy / self._scale
195 self._absolute_transform.y += dy_value * (value - self._group_x)
197 dx_value = self._absolute_transform.dx / self._scale
198 self._absolute_transform.x += dx_value * (value - self._group_x)
199 self._group_x = value
200 self._update_child_transforms()
204 """Y position of the Group in the parent."""
209 if not isinstance(value, int):
210 raise ValueError("y must be an integer")
211 if self._group_y != value:
212 if self._absolute_transform.transpose_xy:
213 dx_value = self._absolute_transform.dx / self._scale
214 self._absolute_transform.x += dx_value * (value - self._group_y)
216 dy_value = self._absolute_transform.dy / self._scale
217 self._absolute_transform.y += dy_value * (value - self._group_y)
218 self._group_y = value
219 self._update_child_transforms()