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 displayio.tilegrid import TileGrid
39 from displayio import Transform
41 __version__ = "0.0.0-auto.0"
42 __repo__ = "https://github.com/adafruit/Adafruit_Blinka_displayio.git"
46 """Manage a group of sprites and groups and how they are inter-related."""
48 def __init__(self, *, max_size=4, scale=1, x=0, y=0):
49 """Create a Group of a given size and scale. Scale is in
50 one dimension. For example, scale=2 leads to a layer’s
51 pixel being 2x2 pixels when in the group.
53 if not isinstance(max_size, int) or max_size < 1:
54 raise ValueError("Max Size must be >= 1")
55 self._max_size = max_size
56 if not isinstance(scale, int) or scale < 1:
57 raise ValueError("Scale must be >= 1")
63 self._supported_types = (TileGrid, Group)
64 self._absolute_transform = None
66 self._absolute_transform = Transform(0, 0, 1, 1, 1, False, False, False)
68 def update_transform(self, parent_transform):
69 """Update the parent transform and child transforms"""
70 self.in_group = parent_transform is not None
74 if parent_transform.transpose_xy:
76 self._absolute_transform.x = parent_transform.x + parent_transform.dx * x
77 self._absolute_transform.y = parent_transform.y + parent_transform.dy * y
78 self._absolute_transform.dx = parent_transform.dx * self._scale
79 self._absolute_transform.dy = parent_transform.dy * self._scale
80 self._absolute_transform.transpose_xy = parent_transform.transpose_xy
81 self._absolute_transform.mirror_x = parent_transform.mirror_x
82 self._absolute_transform.mirror_y = parent_transform.mirror_y
83 self._absolute_transform.scale = parent_transform.scale * self._scale
84 self._update_child_transforms()
86 def _update_child_transforms(self):
88 for layer in self._layers:
89 layer.update_transform(self._absolute_transform)
91 def _removal_cleanup(self, index):
92 layer = self._layers[index]
93 layer.update_transform(None)
95 def _layer_update(self, index):
96 layer = self._layers[index]
97 layer.update_transform(self._absolute_transform)
99 def append(self, layer):
100 """Append a layer to the group. It will be drawn
103 self.insert(len(self._layers), layer)
105 def insert(self, index, layer):
106 """Insert a layer into the group."""
107 if not isinstance(layer, self._supported_types):
108 raise ValueError("Invalid Group Member")
110 raise ValueError("Layer already in a group.")
111 if len(self._layers) == self._max_size:
112 raise RuntimeError("Group full")
113 self._layers.insert(index, layer)
114 self._layer_update(index)
116 def index(self, layer):
117 """Returns the index of the first copy of layer.
118 Raises ValueError if not found.
120 return self._layers.index(layer)
122 def pop(self, index=-1):
123 """Remove the ith item and return it."""
124 self._removal_cleanup(index)
125 return self._layers.pop(index)
127 def remove(self, layer):
128 """Remove the first copy of layer. Raises ValueError
129 if it is not present."""
130 index = self.index(layer)
131 self._layers.pop(index)
134 """Returns the number of layers in a Group"""
135 return len(self._layers)
137 def __getitem__(self, index):
138 """Returns the value at the given index."""
139 return self._layers[index]
141 def __setitem__(self, index, value):
142 """Sets the value at the given index."""
143 self._removal_cleanup(index)
144 self._layers[index] = value
145 self._layer_update(index)
147 def __delitem__(self, index):
148 """Deletes the value at the given index."""
149 del self._layers[index]
151 def _fill_area(self, buffer):
155 for layer in self._layers:
156 if isinstance(layer, (Group, TileGrid)):
157 layer._fill_area(buffer) # pylint: disable=protected-access
161 """True when the Group and all of it’s layers are not visible. When False, the
162 Group’s layers are visible if they haven’t been hidden.
167 def hidden(self, value):
168 if not isinstance(value, (bool, int)):
169 raise ValueError("Expecting a boolean or integer value")
170 self._hidden = bool(value)
174 """Scales each pixel within the Group in both directions. For example, when
175 scale=2 each pixel will be represented by 2x2 pixels.
180 def scale(self, value):
181 if not isinstance(value, int) or value < 1:
182 raise ValueError("Scale must be >= 1")
183 if self._scale != value:
184 parent_scale = self._absolute_transform.scale / self._scale
185 self._absolute_transform.dx = (
186 self._absolute_transform.dx / self._scale * value
188 self._absolute_transform.dy = (
189 self._absolute_transform.dy / self._scale * value
191 self._absolute_transform.scale = parent_scale * value
194 self._update_child_transforms()
198 """X position of the Group in the parent."""
203 if not isinstance(value, int):
204 raise ValueError("x must be an integer")
206 if self._absolute_transform.transpose_xy:
207 dy_value = self._absolute_transform.dy / self._scale
208 self._absolute_transform.y += dy_value * (value - self._x)
210 dx_value = self._absolute_transform.dx / self._scale
211 self._absolute_transform.x += dx_value * (value - self._x)
213 self._update_child_transforms()
217 """Y position of the Group in the parent."""
222 if not isinstance(value, int):
223 raise ValueError("y must be an integer")
225 if self._absolute_transform.transpose_xy:
226 dx_value = self._absolute_transform.dx / self._scale
227 self._absolute_transform.x += dx_value * (value - self._y)
229 dy_value = self._absolute_transform.dy / self._scale
230 self._absolute_transform.y += dy_value * (value - self._y)
232 self._update_child_transforms()