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