]> Repositories - hackapet/Adafruit_Blinka_Displayio.git/blob - displayio/_group.py
Merge pull request #154 from FoamyGuy/docs_build_fix
[hackapet/Adafruit_Blinka_Displayio.git] / displayio / _group.py
1 # SPDX-FileCopyrightText: 2020 Melissa LeBlanc-Williams for Adafruit Industries
2 #
3 # SPDX-License-Identifier: MIT
4
5 """
6 `displayio.group`
7 ================================================================================
8
9 displayio for Blinka
10
11 **Software and Dependencies:**
12
13 * Adafruit Blinka:
14   https://github.com/adafruit/Adafruit_Blinka/releases
15
16 * Author(s): Melissa LeBlanc-Williams
17
18 """
19
20 from __future__ import annotations
21 from typing import Union, Callable
22 from circuitpython_typing import WriteableBuffer
23 from vectorio._rectangle import _VectorShape
24 from ._structs import TransformStruct
25 from ._tilegrid import TileGrid
26 from ._colorspace import Colorspace
27 from ._area import Area
28
29 __version__ = "0.0.0+auto.0"
30 __repo__ = "https://github.com/adafruit/Adafruit_Blinka_displayio.git"
31
32
33 class Group:
34     # pylint: disable=too-many-instance-attributes
35     """
36     Manage a group of sprites and groups and how they are inter-related.
37
38     Create a Group of a given scale. Scale is in one dimension. For example, scale=2
39     leads to a layer's pixel being 2x2 pixels when in the group.
40     """
41
42     def __init__(self, *, scale: int = 1, x: int = 0, y: int = 0):
43         """
44         :param int scale: Scale of layer pixels in one dimension.
45         :param int x: Initial x position within the parent.
46         :param int y: Initial y position within the parent.
47         """
48
49         if not isinstance(scale, int) or scale < 1:
50             raise ValueError("Scale must be >= 1")
51         self._scale = 1  # Use the setter below to actually set the scale
52         self._name = "Group"
53         self._group_x = x
54         self._group_y = y
55         self._hidden_group = False
56         self._hidden_by_parent = False
57         self._layers = []
58         self._supported_types = (TileGrid, Group, _VectorShape)
59         self._in_group = False
60         self._item_removed = False
61         self._absolute_transform = TransformStruct(0, 0, 1, 1, 1, False, False, False)
62         self._set_scale(scale)  # Set the scale via the setter
63
64     def _update_transform(self, parent_transform):
65         """Update the parent transform and child transforms"""
66         self._in_group = parent_transform is not None
67         if self._in_group:
68             x = self._group_x
69             y = self._group_y
70             if parent_transform.transpose_xy:
71                 x, y = y, x
72             self._absolute_transform.x = int(
73                 parent_transform.x + parent_transform.dx * x
74             )
75             self._absolute_transform.y = int(
76                 parent_transform.y + parent_transform.dy * y
77             )
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         # pylint: disable=protected-access
88         if self._in_group:
89             for layer in self._layers:
90                 layer._update_transform(self._absolute_transform)
91
92     def _removal_cleanup(self, index):
93         # pylint: disable=protected-access
94         layer = self._layers[index]
95         layer._update_transform(None)
96
97     def _layer_update(self, index):
98         # pylint: disable=protected-access
99         layer = self._layers[index]
100         layer._update_transform(self._absolute_transform)
101
102     def append(self, layer: Union[Group, TileGrid, _VectorShape]) -> None:
103         """Append a layer to the group. It will be drawn
104         above other layers.
105         """
106         self.insert(len(self._layers), layer)
107
108     def insert(self, index: int, layer: Union[Group, TileGrid, _VectorShape]) -> None:
109         """Insert a layer into the group."""
110         if not isinstance(layer, self._supported_types):
111             raise ValueError("Invalid Group Member")
112         if isinstance(layer, (Group, TileGrid)):
113             if layer._in_group:  # pylint: disable=protected-access
114                 raise ValueError("Layer already in a group.")
115         self._layers.insert(index, layer)
116         self._layer_update(index)
117
118     def index(self, layer: Union[Group, TileGrid, _VectorShape]) -> int:
119         """Returns the index of the first copy of layer.
120         Raises ValueError if not found.
121         """
122         return self._layers.index(layer)
123
124     def pop(self, index: int = -1) -> Union[Group, TileGrid, _VectorShape]:
125         """Remove the ith item and return it."""
126         self._removal_cleanup(index)
127         return self._layers.pop(index)
128
129     def remove(self, layer: Union[Group, TileGrid, _VectorShape]) -> None:
130         """Remove the first copy of layer. Raises ValueError
131         if it is not present."""
132         index = self.index(layer)
133         self._layers.pop(index)
134
135     def __bool__(self) -> bool:
136         """Returns if there are any layers"""
137         return len(self._layers) > 0
138
139     def __len__(self) -> int:
140         """Returns the number of layers in a Group"""
141         return len(self._layers)
142
143     def __getitem__(self, index: int) -> Union[Group, TileGrid, _VectorShape]:
144         """Returns the value at the given index."""
145         return self._layers[index]
146
147     def __setitem__(
148         self, index: int, value: Union[Group, TileGrid, _VectorShape]
149     ) -> None:
150         """Sets the value at the given index."""
151         self._removal_cleanup(index)
152         self._layers[index] = value
153         self._layer_update(index)
154
155     def __delitem__(self, index: int) -> None:
156         """Deletes the value at the given index."""
157         del self._layers[index]
158
159     def _fill_area(
160         self,
161         colorspace: Colorspace,
162         area: Area,
163         mask: WriteableBuffer,
164         buffer: WriteableBuffer,
165     ) -> bool:
166         if not self._hidden_group:
167             for layer in reversed(self._layers):
168                 if isinstance(layer, (Group, TileGrid, _VectorShape)):
169                     if layer._fill_area(  # pylint: disable=protected-access
170                         colorspace, area, mask, buffer
171                     ):
172                         return True
173         return False
174
175     def sort(self, key: Callable, reverse: bool) -> None:
176         """Sort the members of the group."""
177         self._layers.sort(key=key, reverse=reverse)
178
179     def _finish_refresh(self):
180         for layer in reversed(self._layers):
181             if isinstance(layer, (Group, TileGrid, _VectorShape)):
182                 layer._finish_refresh()  # pylint: disable=protected-access
183
184     def _get_refresh_areas(self, areas: list[Area]) -> None:
185         # pylint: disable=protected-access
186         for layer in reversed(self._layers):
187             if isinstance(layer, (Group, _VectorShape)):
188                 layer._get_refresh_areas(areas)
189             elif isinstance(layer, TileGrid):
190                 if not layer._get_rendered_hidden():
191                     layer._get_refresh_areas(areas)
192
193     def _set_hidden(self, hidden: bool) -> None:
194         if self._hidden_group == hidden:
195             return
196         self._hidden_group = hidden
197         if self._hidden_by_parent:
198             return
199         for layer in self._layers:
200             if isinstance(layer, (Group, TileGrid)):
201                 layer._set_hidden_by_parent(hidden)  # pylint: disable=protected-access
202             elif isinstance(layer, _VectorShape):
203                 layer._shape_set_dirty()  # pylint: disable=protected-access
204
205     def _set_hidden_by_parent(self, hidden: bool) -> None:
206         if self._hidden_by_parent == hidden:
207             return
208         self._hidden_by_parent = hidden
209         if self._hidden_group:
210             return
211         for layer in self._layers:
212             if isinstance(layer, (Group, TileGrid)):
213                 layer._set_hidden_by_parent(hidden)  # pylint: disable=protected-access
214             elif isinstance(layer, _VectorShape):
215                 layer._shape_set_dirty()  # pylint: disable=protected-access
216
217     @property
218     def hidden(self) -> bool:
219         """True when the Group and all of it's layers are not visible. When False, the
220         Group’s layers are visible if they haven't been hidden.
221         """
222         return self._hidden_group
223
224     @hidden.setter
225     def hidden(self, value: bool) -> None:
226         if not isinstance(value, (bool, int)):
227             raise ValueError("Expecting a boolean or integer value")
228         value = bool(value)
229         self._set_hidden(value)
230
231     @property
232     def scale(self) -> int:
233         """Scales each pixel within the Group in both directions. For example, when
234         scale=2 each pixel will be represented by 2x2 pixels.
235         """
236         return self._scale
237
238     @scale.setter
239     def scale(self, value: int):
240         self._set_scale(value)
241
242     def _set_scale(self, value: int):
243         # This is method allows the scale to be set by this class even when
244         # the scale property is over-ridden by a subclass.
245         if not isinstance(value, int) or value < 1:
246             raise ValueError("Scale must be >= 1")
247         if self._scale != value:
248             parent_scale = self._absolute_transform.scale // self._scale
249             self._absolute_transform.dx = (
250                 self._absolute_transform.dx // self._scale * value
251             )
252             self._absolute_transform.dy = (
253                 self._absolute_transform.dy // self._scale * value
254             )
255             self._absolute_transform.scale = parent_scale * value
256
257             self._scale = value
258             self._update_child_transforms()
259
260     @property
261     def x(self) -> int:
262         """X position of the Group in the parent."""
263         return self._group_x
264
265     @x.setter
266     def x(self, value: int):
267         if not isinstance(value, int):
268             raise ValueError("x must be an integer")
269         if self._group_x != value:
270             if self._absolute_transform.transpose_xy:
271                 dy_value = self._absolute_transform.dy // self._scale
272                 self._absolute_transform.y += dy_value * (value - self._group_x)
273             else:
274                 dx_value = self._absolute_transform.dx // self._scale
275                 self._absolute_transform.x += dx_value * (value - self._group_x)
276             self._group_x = value
277             self._update_child_transforms()
278
279     @property
280     def y(self) -> int:
281         """Y position of the Group in the parent."""
282         return self._group_y
283
284     @y.setter
285     def y(self, value: int):
286         if not isinstance(value, int):
287             raise ValueError("y must be an integer")
288         if self._group_y != value:
289             if self._absolute_transform.transpose_xy:
290                 dx_value = self._absolute_transform.dx // self._scale
291                 self._absolute_transform.x += dx_value * (value - self._group_y)
292             else:
293                 dy_value = self._absolute_transform.dy // self._scale
294                 self._absolute_transform.y += dy_value * (value - self._group_y)
295             self._group_y = value
296             self._update_child_transforms()
297
298
299 circuitpython_splash = Group(scale=2, x=0, y=0)