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