]> Repositories - hackapet/Adafruit_Blinka_Displayio.git/blob - displayio/_group.py
Add vectorio support
[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 = parent_transform.x + parent_transform.dx * x
73             self._absolute_transform.y = parent_transform.y + parent_transform.dy * y
74             self._absolute_transform.dx = parent_transform.dx * self._scale
75             self._absolute_transform.dy = parent_transform.dy * self._scale
76             self._absolute_transform.transpose_xy = parent_transform.transpose_xy
77             self._absolute_transform.mirror_x = parent_transform.mirror_x
78             self._absolute_transform.mirror_y = parent_transform.mirror_y
79             self._absolute_transform.scale = parent_transform.scale * self._scale
80         self._update_child_transforms()
81
82     def _update_child_transforms(self):
83         # pylint: disable=protected-access
84         if self._in_group:
85             for layer in self._layers:
86                 layer._update_transform(self._absolute_transform)
87
88     def _removal_cleanup(self, index):
89         # pylint: disable=protected-access
90         layer = self._layers[index]
91         layer._update_transform(None)
92
93     def _layer_update(self, index):
94         # pylint: disable=protected-access
95         layer = self._layers[index]
96         layer._update_transform(self._absolute_transform)
97
98     def append(self, layer: Union[Group, TileGrid, _VectorShape]) -> None:
99         """Append a layer to the group. It will be drawn
100         above other layers.
101         """
102         self.insert(len(self._layers), layer)
103
104     def insert(self, index: int, layer: Union[Group, TileGrid, _VectorShape]) -> None:
105         """Insert a layer into the group."""
106         if not isinstance(layer, self._supported_types):
107             raise ValueError("Invalid Group Member")
108         if isinstance(layer, (Group, TileGrid)):
109             if layer._in_group:  # pylint: disable=protected-access
110                 raise ValueError("Layer already in a group.")
111         self._layers.insert(index, layer)
112         self._layer_update(index)
113
114     def index(self, layer: Union[Group, TileGrid, _VectorShape]) -> int:
115         """Returns the index of the first copy of layer.
116         Raises ValueError if not found.
117         """
118         return self._layers.index(layer)
119
120     def pop(self, index: int = -1) -> Union[Group, TileGrid, _VectorShape]:
121         """Remove the ith item and return it."""
122         self._removal_cleanup(index)
123         return self._layers.pop(index)
124
125     def remove(self, layer: Union[Group, TileGrid, _VectorShape]) -> None:
126         """Remove the first copy of layer. Raises ValueError
127         if it is not present."""
128         index = self.index(layer)
129         self._layers.pop(index)
130
131     def __bool__(self) -> bool:
132         """Returns if there are any layers"""
133         return len(self._layers) > 0
134
135     def __len__(self) -> int:
136         """Returns the number of layers in a Group"""
137         return len(self._layers)
138
139     def __getitem__(self, index: int) -> Union[Group, TileGrid, _VectorShape]:
140         """Returns the value at the given index."""
141         return self._layers[index]
142
143     def __setitem__(
144         self, index: int, value: Union[Group, TileGrid, _VectorShape]
145     ) -> None:
146         """Sets the value at the given index."""
147         self._removal_cleanup(index)
148         self._layers[index] = value
149         self._layer_update(index)
150
151     def __delitem__(self, index: int) -> None:
152         """Deletes the value at the given index."""
153         del self._layers[index]
154
155     def _fill_area(
156         self,
157         colorspace: Colorspace,
158         area: Area,
159         mask: WriteableBuffer,
160         buffer: WriteableBuffer,
161     ) -> bool:
162         if not self._hidden_group:
163             for layer in reversed(self._layers):
164                 if isinstance(layer, (Group, TileGrid, _VectorShape)):
165                     if layer._fill_area(  # pylint: disable=protected-access
166                         colorspace, area, mask, buffer
167                     ):
168                         return True
169         return False
170
171     def sort(self, key: Callable, reverse: bool) -> None:
172         """Sort the members of the group."""
173         self._layers.sort(key=key, reverse=reverse)
174
175     def _finish_refresh(self):
176         for layer in reversed(self._layers):
177             if isinstance(layer, (Group, TileGrid, _VectorShape)):
178                 layer._finish_refresh()  # pylint: disable=protected-access
179
180     def _get_refresh_areas(self, areas: list[Area]) -> None:
181         # pylint: disable=protected-access
182         for layer in reversed(self._layers):
183             if isinstance(layer, (Group, _VectorShape)):
184                 layer._get_refresh_areas(areas)
185             elif isinstance(layer, TileGrid):
186                 if not layer._get_rendered_hidden():
187                     layer._get_refresh_areas(areas)
188
189     def _set_hidden(self, hidden: bool) -> None:
190         if self._hidden_group == hidden:
191             return
192         self._hidden_group = hidden
193         if self._hidden_by_parent:
194             return
195         for layer in self._layers:
196             if isinstance(layer, (Group, TileGrid)):
197                 layer._set_hidden_by_parent(hidden)  # pylint: disable=protected-access
198             elif isinstance(layer, _VectorShape):
199                 layer._shape_set_dirty()  # pylint: disable=protected-access
200
201     def _set_hidden_by_parent(self, hidden: bool) -> None:
202         if self._hidden_by_parent == hidden:
203             return
204         self._hidden_by_parent = hidden
205         if self._hidden_group:
206             return
207         for layer in self._layers:
208             if isinstance(layer, (Group, TileGrid)):
209                 layer._set_hidden_by_parent(hidden)  # pylint: disable=protected-access
210             elif isinstance(layer, _VectorShape):
211                 layer._shape_set_dirty()  # pylint: disable=protected-access
212
213     @property
214     def hidden(self) -> bool:
215         """True when the Group and all of it's layers are not visible. When False, the
216         Group’s layers are visible if they haven't been hidden.
217         """
218         return self._hidden_group
219
220     @hidden.setter
221     def hidden(self, value: bool) -> None:
222         if not isinstance(value, (bool, int)):
223             raise ValueError("Expecting a boolean or integer value")
224         value = bool(value)
225         self._set_hidden(value)
226
227     @property
228     def scale(self) -> int:
229         """Scales each pixel within the Group in both directions. For example, when
230         scale=2 each pixel will be represented by 2x2 pixels.
231         """
232         return self._scale
233
234     @scale.setter
235     def scale(self, value: int):
236         self._set_scale(value)
237
238     def _set_scale(self, value: int):
239         # This is method allows the scale to be set by this class even when
240         # the scale property is over-ridden by a subclass.
241         if not isinstance(value, int) or value < 1:
242             raise ValueError("Scale must be >= 1")
243         if self._scale != value:
244             parent_scale = self._absolute_transform.scale / self._scale
245             self._absolute_transform.dx = (
246                 self._absolute_transform.dx / self._scale * value
247             )
248             self._absolute_transform.dy = (
249                 self._absolute_transform.dy / self._scale * value
250             )
251             self._absolute_transform.scale = parent_scale * value
252
253             self._scale = value
254             self._update_child_transforms()
255
256     @property
257     def x(self) -> int:
258         """X position of the Group in the parent."""
259         return self._group_x
260
261     @x.setter
262     def x(self, value: int):
263         if not isinstance(value, int):
264             raise ValueError("x must be an integer")
265         if self._group_x != value:
266             if self._absolute_transform.transpose_xy:
267                 dy_value = self._absolute_transform.dy / self._scale
268                 self._absolute_transform.y += dy_value * (value - self._group_x)
269             else:
270                 dx_value = self._absolute_transform.dx / self._scale
271                 self._absolute_transform.x += dx_value * (value - self._group_x)
272             self._group_x = value
273             self._update_child_transforms()
274
275     @property
276     def y(self) -> int:
277         """Y position of the Group in the parent."""
278         return self._group_y
279
280     @y.setter
281     def y(self, value: int):
282         if not isinstance(value, int):
283             raise ValueError("y must be an integer")
284         if self._group_y != value:
285             if self._absolute_transform.transpose_xy:
286                 dx_value = self._absolute_transform.dx / self._scale
287                 self._absolute_transform.x += dx_value * (value - self._group_y)
288             else:
289                 dy_value = self._absolute_transform.dy / self._scale
290                 self._absolute_transform.y += dy_value * (value - self._group_y)
291             self._group_y = value
292             self._update_child_transforms()
293
294
295 circuitpython_splash = Group(scale=2, x=0, y=0)