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")
43 self._scale = 1 # Use the setter below to actually set the scale
46 self._hidden_group = False
48 self._supported_types = (TileGrid, Group)
50 self._absolute_transform = Transform(0, 0, 1, 1, 1, False, False, False)
51 self._set_scale(scale) # Set the scale via the setter
53 def update_transform(self, parent_transform):
54 """Update the parent transform and child transforms"""
55 self.in_group = parent_transform is not None
59 if parent_transform.transpose_xy:
61 self._absolute_transform.x = parent_transform.x + parent_transform.dx * x
62 self._absolute_transform.y = parent_transform.y + parent_transform.dy * y
63 self._absolute_transform.dx = parent_transform.dx * self._scale
64 self._absolute_transform.dy = parent_transform.dy * self._scale
65 self._absolute_transform.transpose_xy = parent_transform.transpose_xy
66 self._absolute_transform.mirror_x = parent_transform.mirror_x
67 self._absolute_transform.mirror_y = parent_transform.mirror_y
68 self._absolute_transform.scale = parent_transform.scale * self._scale
69 self._update_child_transforms()
71 def _update_child_transforms(self):
73 for layer in self._layers:
74 layer.update_transform(self._absolute_transform)
76 def _removal_cleanup(self, index):
77 layer = self._layers[index]
78 layer.update_transform(None)
80 def _layer_update(self, index):
81 layer = self._layers[index]
82 layer.update_transform(self._absolute_transform)
84 def append(self, layer):
85 """Append a layer to the group. It will be drawn
88 self.insert(len(self._layers), layer)
90 def insert(self, index, layer):
91 """Insert a layer into the group."""
92 if not isinstance(layer, self._supported_types):
93 raise ValueError("Invalid Group Member")
95 raise ValueError("Layer already in a group.")
96 if len(self._layers) == self._max_size:
97 raise RuntimeError("Group full")
98 self._layers.insert(index, layer)
99 self._layer_update(index)
101 def index(self, layer):
102 """Returns the index of the first copy of layer.
103 Raises ValueError if not found.
105 return self._layers.index(layer)
107 def pop(self, index=-1):
108 """Remove the ith item and return it."""
109 self._removal_cleanup(index)
110 return self._layers.pop(index)
112 def remove(self, layer):
113 """Remove the first copy of layer. Raises ValueError
114 if it is not present."""
115 index = self.index(layer)
116 self._layers.pop(index)
119 """Returns the number of layers in a Group"""
120 return len(self._layers)
122 def __getitem__(self, index):
123 """Returns the value at the given index."""
124 return self._layers[index]
126 def __setitem__(self, index, value):
127 """Sets the value at the given index."""
128 self._removal_cleanup(index)
129 self._layers[index] = value
130 self._layer_update(index)
132 def __delitem__(self, index):
133 """Deletes the value at the given index."""
134 del self._layers[index]
136 def _fill_area(self, buffer):
137 if self._hidden_group:
140 for layer in self._layers:
141 if isinstance(layer, (Group, TileGrid)):
142 layer._fill_area(buffer) # pylint: disable=protected-access
146 """True when the Group and all of it’s layers are not visible. When False, the
147 Group’s layers are visible if they haven’t been hidden.
149 return self._hidden_group
152 def hidden(self, value):
153 if not isinstance(value, (bool, int)):
154 raise ValueError("Expecting a boolean or integer value")
155 self._hidden_group = bool(value)
159 """Scales each pixel within the Group in both directions. For example, when
160 scale=2 each pixel will be represented by 2x2 pixels.
165 def scale(self, value):
166 self._set_scale(value)
168 def _set_scale(self, value):
169 # This is method allows the scale to be set by this class even when
170 # the scale property is over-ridden by a subclass.
171 if not isinstance(value, int) or value < 1:
172 raise ValueError("Scale must be >= 1")
173 if self._scale != value:
174 parent_scale = self._absolute_transform.scale / self._scale
175 self._absolute_transform.dx = (
176 self._absolute_transform.dx / self._scale * value
178 self._absolute_transform.dy = (
179 self._absolute_transform.dy / self._scale * value
181 self._absolute_transform.scale = parent_scale * value
184 self._update_child_transforms()
188 """X position of the Group in the parent."""
193 if not isinstance(value, int):
194 raise ValueError("x must be an integer")
195 if self._group_x != value:
196 if self._absolute_transform.transpose_xy:
197 dy_value = self._absolute_transform.dy / self._scale
198 self._absolute_transform.y += dy_value * (value - self._group_x)
200 dx_value = self._absolute_transform.dx / self._scale
201 self._absolute_transform.x += dx_value * (value - self._group_x)
202 self._group_x = value
203 self._update_child_transforms()
207 """Y position of the Group in the parent."""
212 if not isinstance(value, int):
213 raise ValueError("y must be an integer")
214 if self._group_y != value:
215 if self._absolute_transform.transpose_xy:
216 dx_value = self._absolute_transform.dx / self._scale
217 self._absolute_transform.x += dx_value * (value - self._group_y)
219 dy_value = self._absolute_transform.dy / self._scale
220 self._absolute_transform.y += dy_value * (value - self._group_y)
221 self._group_y = value
222 self._update_child_transforms()