]> Repositories - hackapet/Adafruit_Blinka_Displayio.git/blob - displayio/group.py
Split module into multiple files
[hackapet/Adafruit_Blinka_Displayio.git] / displayio / group.py
1 # The MIT License (MIT)
2 #
3 # Copyright (c) 2020 Melissa LeBlanc-Williams for Adafruit Industries
4 #
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:
11 #
12 # The above copyright notice and this permission notice shall be included in
13 # all copies or substantial portions of the Software.
14 #
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
21 # THE SOFTWARE.
22
23 """
24 `displayio`
25 ================================================================================
26
27 displayio for Blinka
28
29 **Software and Dependencies:**
30
31 * Adafruit Blinka:
32   https://github.com/adafruit/Adafruit_Blinka/releases
33
34 * Author(s): Melissa LeBlanc-Williams
35
36 """
37
38 from displayio.tilegrid import TileGrid
39 from displayio import Transform
40
41 __version__ = "0.0.0-auto.0"
42 __repo__ = "https://github.com/adafruit/Adafruit_Blinka_displayio.git"
43
44
45 class Group:
46     """Manage a group of sprites and groups and how they are inter-related."""
47
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.
52         """
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")
58         self._scale = scale
59         self._x = x
60         self._y = y
61         self._hidden = False
62         self._layers = []
63         self._supported_types = (TileGrid, Group)
64         self._absolute_transform = None
65         self.in_group = False
66         self._absolute_transform = Transform(0, 0, 1, 1, 1, False, False, False)
67
68     def update_transform(self, parent_transform):
69         """Update the parent transform and child transforms"""
70         self.in_group = parent_transform is not None
71         if self.in_group:
72             x = self._x
73             y = self._y
74             if parent_transform.transpose_xy:
75                 x, y = y, x
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()
85
86     def _update_child_transforms(self):
87         if self.in_group:
88             for layer in self._layers:
89                 layer.update_transform(self._absolute_transform)
90
91     def _removal_cleanup(self, index):
92         layer = self._layers[index]
93         layer.update_transform(None)
94
95     def _layer_update(self, index):
96         layer = self._layers[index]
97         layer.update_transform(self._absolute_transform)
98
99     def append(self, layer):
100         """Append a layer to the group. It will be drawn
101         above other layers.
102         """
103         self.insert(len(self._layers), layer)
104
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")
109         if layer.in_group:
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)
115
116     def index(self, layer):
117         """Returns the index of the first copy of layer.
118         Raises ValueError if not found.
119         """
120         return self._layers.index(layer)
121
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)
126
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)
132
133     def __len__(self):
134         """Returns the number of layers in a Group"""
135         return len(self._layers)
136
137     def __getitem__(self, index):
138         """Returns the value at the given index."""
139         return self._layers[index]
140
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)
146
147     def __delitem__(self, index):
148         """Deletes the value at the given index."""
149         del self._layers[index]
150
151     def _fill_area(self, buffer):
152         if self._hidden:
153             return
154
155         for layer in self._layers:
156             if isinstance(layer, (Group, TileGrid)):
157                 layer._fill_area(buffer)  # pylint: disable=protected-access
158
159     @property
160     def hidden(self):
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.
163         """
164         return self._hidden
165
166     @hidden.setter
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)
171
172     @property
173     def scale(self):
174         """Scales each pixel within the Group in both directions. For example, when
175         scale=2 each pixel will be represented by 2x2 pixels.
176         """
177         return self._scale
178
179     @scale.setter
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
187             )
188             self._absolute_transform.dy = (
189                 self._absolute_transform.dy / self._scale * value
190             )
191             self._absolute_transform.scale = parent_scale * value
192
193             self._scale = value
194             self._update_child_transforms()
195
196     @property
197     def x(self):
198         """X position of the Group in the parent."""
199         return self._x
200
201     @x.setter
202     def x(self, value):
203         if not isinstance(value, int):
204             raise ValueError("x must be an integer")
205         if self._x != value:
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)
209             else:
210                 dx_value = self._absolute_transform.dx / self._scale
211                 self._absolute_transform.x += dx_value * (value - self._x)
212             self._x = value
213             self._update_child_transforms()
214
215     @property
216     def y(self):
217         """Y position of the Group in the parent."""
218         return self._y
219
220     @y.setter
221     def y(self, value):
222         if not isinstance(value, int):
223             raise ValueError("y must be an integer")
224         if self._y != value:
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)
228             else:
229                 dy_value = self._absolute_transform.dy / self._scale
230                 self._absolute_transform.y += dy_value * (value - self._y)
231             self._y = value
232             self._update_child_transforms()