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)
49 self._absolute_transform = None
51 self._absolute_transform = Transform(0, 0, 1, 1, 1, False, False, False)
52 self.scale = scale # Set the scale via the setter
54 def update_transform(self, parent_transform):
55 """Update the parent transform and child transforms"""
56 self.in_group = parent_transform is not None
60 if parent_transform.transpose_xy:
62 self._absolute_transform.x = parent_transform.x + parent_transform.dx * x
63 self._absolute_transform.y = parent_transform.y + parent_transform.dy * y
64 self._absolute_transform.dx = parent_transform.dx * self._scale
65 self._absolute_transform.dy = parent_transform.dy * self._scale
66 self._absolute_transform.transpose_xy = parent_transform.transpose_xy
67 self._absolute_transform.mirror_x = parent_transform.mirror_x
68 self._absolute_transform.mirror_y = parent_transform.mirror_y
69 self._absolute_transform.scale = parent_transform.scale * self._scale
70 self._update_child_transforms()
72 def _update_child_transforms(self):
74 for layer in self._layers:
75 layer.update_transform(self._absolute_transform)
77 def _removal_cleanup(self, index):
78 layer = self._layers[index]
79 layer.update_transform(None)
81 def _layer_update(self, index):
82 layer = self._layers[index]
83 layer.update_transform(self._absolute_transform)
85 def append(self, layer):
86 """Append a layer to the group. It will be drawn
89 self.insert(len(self._layers), layer)
91 def insert(self, index, layer):
92 """Insert a layer into the group."""
93 if not isinstance(layer, self._supported_types):
94 raise ValueError("Invalid Group Member")
96 raise ValueError("Layer already in a group.")
97 if len(self._layers) == self._max_size:
98 raise RuntimeError("Group full")
99 self._layers.insert(index, layer)
100 self._layer_update(index)
102 def index(self, layer):
103 """Returns the index of the first copy of layer.
104 Raises ValueError if not found.
106 return self._layers.index(layer)
108 def pop(self, index=-1):
109 """Remove the ith item and return it."""
110 self._removal_cleanup(index)
111 return self._layers.pop(index)
113 def remove(self, layer):
114 """Remove the first copy of layer. Raises ValueError
115 if it is not present."""
116 index = self.index(layer)
117 self._layers.pop(index)
120 """Returns the number of layers in a Group"""
121 return len(self._layers)
123 def __getitem__(self, index):
124 """Returns the value at the given index."""
125 return self._layers[index]
127 def __setitem__(self, index, value):
128 """Sets the value at the given index."""
129 self._removal_cleanup(index)
130 self._layers[index] = value
131 self._layer_update(index)
133 def __delitem__(self, index):
134 """Deletes the value at the given index."""
135 del self._layers[index]
137 def _fill_area(self, buffer):
138 if self._hidden_group:
141 for layer in self._layers:
142 if isinstance(layer, (Group, TileGrid)):
143 layer._fill_area(buffer) # pylint: disable=protected-access
147 """True when the Group and all of it’s layers are not visible. When False, the
148 Group’s layers are visible if they haven’t been hidden.
150 return self._hidden_group
153 def hidden(self, value):
154 if not isinstance(value, (bool, int)):
155 raise ValueError("Expecting a boolean or integer value")
156 self._hidden_group = bool(value)
160 """Scales each pixel within the Group in both directions. For example, when
161 scale=2 each pixel will be represented by 2x2 pixels.
166 def scale(self, value):
167 if not isinstance(value, int) or value < 1:
168 raise ValueError("Scale must be >= 1")
169 if self._scale != value:
170 parent_scale = self._absolute_transform.scale / self._scale
171 self._absolute_transform.dx = (
172 self._absolute_transform.dx / self._scale * value
174 self._absolute_transform.dy = (
175 self._absolute_transform.dy / self._scale * value
177 self._absolute_transform.scale = parent_scale * value
180 self._update_child_transforms()
184 """X position of the Group in the parent."""
189 if not isinstance(value, int):
190 raise ValueError("x must be an integer")
191 if self._group_x != value:
192 if self._absolute_transform.transpose_xy:
193 dy_value = self._absolute_transform.dy / self._scale
194 self._absolute_transform.y += dy_value * (value - self._group_x)
196 dx_value = self._absolute_transform.dx / self._scale
197 self._absolute_transform.x += dx_value * (value - self._group_x)
198 self._group_x = value
199 self._update_child_transforms()
203 """Y position of the Group in the parent."""
208 if not isinstance(value, int):
209 raise ValueError("y must be an integer")
210 if self._group_y != value:
211 if self._absolute_transform.transpose_xy:
212 dx_value = self._absolute_transform.dx / self._scale
213 self._absolute_transform.x += dx_value * (value - self._group_y)
215 dy_value = self._absolute_transform.dy / self._scale
216 self._absolute_transform.y += dy_value * (value - self._group_y)
217 self._group_y = value
218 self._update_child_transforms()