]> Repositories - hackapet/Adafruit_Blinka_Displayio.git/blob - displayio/_group.py
a5c6688f5f66d7d8b715dbb4a54159d700fef15b
[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._name = "Group"
52         self._group_x = x
53         self._group_y = y
54         self._hidden_group = False
55         self._hidden_by_parent = False
56         self._layers = []
57         self._supported_types = (TileGrid, Group)
58         self._in_group = False
59         self._item_removed = False
60         self._absolute_transform = TransformStruct(0, 0, 1, 1, 1, False, False, False)
61         self._set_scale(scale)  # Set the scale via the setter
62
63     def _update_transform(self, parent_transform):
64         """Update the parent transform and child transforms"""
65         self._in_group = parent_transform is not None
66         if self._in_group:
67             x = self._group_x
68             y = self._group_y
69             if parent_transform.transpose_xy:
70                 x, y = y, x
71             self._absolute_transform.x = parent_transform.x + parent_transform.dx * x
72             self._absolute_transform.y = parent_transform.y + parent_transform.dy * y
73             self._absolute_transform.dx = parent_transform.dx * self._scale
74             self._absolute_transform.dy = parent_transform.dy * self._scale
75             self._absolute_transform.transpose_xy = parent_transform.transpose_xy
76             self._absolute_transform.mirror_x = parent_transform.mirror_x
77             self._absolute_transform.mirror_y = parent_transform.mirror_y
78             self._absolute_transform.scale = parent_transform.scale * self._scale
79         self._update_child_transforms()
80
81     def _update_child_transforms(self):
82         # pylint: disable=protected-access
83         if self._in_group:
84             for layer in self._layers:
85                 layer._update_transform(self._absolute_transform)
86
87     def _removal_cleanup(self, index):
88         # pylint: disable=protected-access
89         layer = self._layers[index]
90         layer._update_transform(None)
91
92     def _layer_update(self, index):
93         # pylint: disable=protected-access
94         layer = self._layers[index]
95         layer._update_transform(self._absolute_transform)
96
97     def append(self, layer: Union[Group, TileGrid]) -> None:
98         """Append a layer to the group. It will be drawn
99         above other layers.
100         """
101         self.insert(len(self._layers), layer)
102
103     def insert(self, index: int, layer: Union[Group, TileGrid]) -> None:
104         """Insert a layer into the group."""
105         if not isinstance(layer, self._supported_types):
106             raise ValueError("Invalid Group Member")
107         if layer._in_group:  # pylint: disable=protected-access
108             raise ValueError("Layer already in a group.")
109         self._layers.insert(index, layer)
110         self._layer_update(index)
111
112     def index(self, layer: Union[Group, TileGrid]) -> int:
113         """Returns the index of the first copy of layer.
114         Raises ValueError if not found.
115         """
116         return self._layers.index(layer)
117
118     def pop(self, index: int = -1) -> Union[Group, TileGrid]:
119         """Remove the ith item and return it."""
120         self._removal_cleanup(index)
121         return self._layers.pop(index)
122
123     def remove(self, layer: Union[Group, TileGrid]) -> None:
124         """Remove the first copy of layer. Raises ValueError
125         if it is not present."""
126         index = self.index(layer)
127         self._layers.pop(index)
128
129     def __bool__(self) -> bool:
130         """Returns if there are any layers"""
131         return len(self._layers) > 0
132
133     def __len__(self) -> int:
134         """Returns the number of layers in a Group"""
135         return len(self._layers)
136
137     def __getitem__(self, index: int) -> Union[Group, TileGrid]:
138         """Returns the value at the given index."""
139         return self._layers[index]
140
141     def __setitem__(self, index: int, value: Union[Group, TileGrid]) -> None:
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: int) -> None:
148         """Deletes the value at the given index."""
149         del self._layers[index]
150
151     def _fill_area(
152         self,
153         colorspace: Colorspace,
154         area: Area,
155         mask: WriteableBuffer,
156         buffer: WriteableBuffer,
157     ) -> bool:
158         if not self._hidden_group:
159             for layer in reversed(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 reversed(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 reversed(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)