]> Repositories - hackapet/Adafruit_Blinka_Displayio.git/blob - displayio/group.py
Merge pull request #68 from lesamouraipourpre/max-size
[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 recordclass import recordclass
21 from displayio.tilegrid import TileGrid
22
23 __version__ = "0.0.0-auto.0"
24 __repo__ = "https://github.com/adafruit/Adafruit_Blinka_displayio.git"
25
26
27 Transform = recordclass("Transform", "x y dx dy scale transpose_xy mirror_x mirror_y")
28
29
30 class Group:
31     """
32     Manage a group of sprites and groups and how they are inter-related.
33
34     Create a Group of a given scale. Scale is in one dimension. For example, scale=2
35     leads to a layer's pixel being 2x2 pixels when in the group.
36     """
37
38     def __init__(self, *, max_size=None, scale=1, x=0, y=0):
39         """
40         :param Optional(int) max_size: *DEPRECATED* This has been removed in CircuitPython 7 and
41             will be removed in a future version of ``Adafruit_Blinka_Displayio``
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 max_size is not None:
48             print(
49                 "The max_size parameter displayio.Group() has been deprecated. "
50                 "Please remove max_size from your code."
51             )
52
53         if not isinstance(scale, int) or scale < 1:
54             raise ValueError("Scale must be >= 1")
55         self._scale = 1  # Use the setter below to actually set the scale
56         self._group_x = x
57         self._group_y = y
58         self._hidden_group = False
59         self._layers = []
60         self._supported_types = (TileGrid, Group)
61         self.in_group = False
62         self._absolute_transform = Transform(0, 0, 1, 1, 1, False, False, False)
63         self._set_scale(scale)  # Set the scale via the setter
64
65     def update_transform(self, parent_transform):
66         """Update the parent transform and child transforms"""
67         self.in_group = parent_transform is not None
68         if self.in_group:
69             x = self._group_x
70             y = self._group_y
71             if parent_transform.transpose_xy:
72                 x, y = y, x
73             self._absolute_transform.x = parent_transform.x + parent_transform.dx * x
74             self._absolute_transform.y = parent_transform.y + parent_transform.dy * y
75             self._absolute_transform.dx = parent_transform.dx * self._scale
76             self._absolute_transform.dy = parent_transform.dy * self._scale
77             self._absolute_transform.transpose_xy = parent_transform.transpose_xy
78             self._absolute_transform.mirror_x = parent_transform.mirror_x
79             self._absolute_transform.mirror_y = parent_transform.mirror_y
80             self._absolute_transform.scale = parent_transform.scale * self._scale
81         self._update_child_transforms()
82
83     def _update_child_transforms(self):
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         layer = self._layers[index]
90         layer.update_transform(None)
91
92     def _layer_update(self, index):
93         layer = self._layers[index]
94         layer.update_transform(self._absolute_transform)
95
96     def append(self, layer):
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, layer):
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:
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):
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=-1):
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):
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 __len__(self):
129         """Returns the number of layers in a Group"""
130         return len(self._layers)
131
132     def __getitem__(self, index):
133         """Returns the value at the given index."""
134         return self._layers[index]
135
136     def __setitem__(self, index, value):
137         """Sets the value at the given index."""
138         self._removal_cleanup(index)
139         self._layers[index] = value
140         self._layer_update(index)
141
142     def __delitem__(self, index):
143         """Deletes the value at the given index."""
144         del self._layers[index]
145
146     def _fill_area(self, buffer):
147         if self._hidden_group:
148             return
149
150         for layer in self._layers:
151             if isinstance(layer, (Group, TileGrid)):
152                 layer._fill_area(buffer)  # pylint: disable=protected-access
153
154     @property
155     def hidden(self):
156         """True when the Group and all of it’s layers are not visible. When False, the
157         Group’s layers are visible if they haven’t been hidden.
158         """
159         return self._hidden_group
160
161     @hidden.setter
162     def hidden(self, value):
163         if not isinstance(value, (bool, int)):
164             raise ValueError("Expecting a boolean or integer value")
165         self._hidden_group = bool(value)
166
167     @property
168     def scale(self):
169         """Scales each pixel within the Group in both directions. For example, when
170         scale=2 each pixel will be represented by 2x2 pixels.
171         """
172         return self._scale
173
174     @scale.setter
175     def scale(self, value):
176         self._set_scale(value)
177
178     def _set_scale(self, value):
179         # This is method allows the scale to be set by this class even when
180         # the scale property is over-ridden by a subclass.
181         if not isinstance(value, int) or value < 1:
182             raise ValueError("Scale must be >= 1")
183         if self._scale != value:
184             parent_scale = self._absolute_transform.scale / self._scale
185             self._absolute_transform.dx = (
186                 self._absolute_transform.dx / self._scale * value
187             )
188             self._absolute_transform.dy = (
189                 self._absolute_transform.dy / self._scale * value
190             )
191             self._absolute_transform.scale = parent_scale * value
192
193             self._scale = value
194             self._update_child_transforms()
195
196     @property
197     def x(self):
198         """X position of the Group in the parent."""
199         return self._group_x
200
201     @x.setter
202     def x(self, value):
203         if not isinstance(value, int):
204             raise ValueError("x must be an integer")
205         if self._group_x != value:
206             if self._absolute_transform.transpose_xy:
207                 dy_value = self._absolute_transform.dy / self._scale
208                 self._absolute_transform.y += dy_value * (value - self._group_x)
209             else:
210                 dx_value = self._absolute_transform.dx / self._scale
211                 self._absolute_transform.x += dx_value * (value - self._group_x)
212             self._group_x = value
213             self._update_child_transforms()
214
215     @property
216     def y(self):
217         """Y position of the Group in the parent."""
218         return self._group_y
219
220     @y.setter
221     def y(self, value):
222         if not isinstance(value, int):
223             raise ValueError("y must be an integer")
224         if self._group_y != value:
225             if self._absolute_transform.transpose_xy:
226                 dx_value = self._absolute_transform.dx / self._scale
227                 self._absolute_transform.x += dx_value * (value - self._group_y)
228             else:
229                 dy_value = self._absolute_transform.dy / self._scale
230                 self._absolute_transform.y += dy_value * (value - self._group_y)
231             self._group_y = value
232             self._update_child_transforms()