]> Repositories - hackapet/Adafruit_Blinka_Displayio.git/blob - displayio/group.py
Add pre-commit 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 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     """Manage a group of sprites and groups and how they are inter-related."""
32
33     def __init__(self, *, max_size=4, scale=1, x=0, y=0):
34         """Create a Group of a given size and scale. Scale is in
35         one dimension. For example, scale=2 leads to a layer’s
36         pixel being 2x2 pixels when in the group.
37         """
38         if not isinstance(max_size, int) or max_size < 1:
39             raise ValueError("Max Size must be >= 1")
40         self._max_size = max_size
41         if not isinstance(scale, int) or scale < 1:
42             raise ValueError("Scale must be >= 1")
43         self._scale = scale
44         self._group_x = x
45         self._group_y = y
46         self._hidden_group = False
47         self._layers = []
48         self._supported_types = (TileGrid, Group)
49         self._absolute_transform = None
50         self.in_group = False
51         self._absolute_transform = Transform(
52             0, 0, 1, 1, self._scale, False, False, False
53         )
54
55     def update_transform(self, parent_transform):
56         """Update the parent transform and child transforms"""
57         self.in_group = parent_transform is not None
58         if self.in_group:
59             x = self._group_x
60             y = self._group_y
61             if parent_transform.transpose_xy:
62                 x, y = y, x
63             self._absolute_transform.x = parent_transform.x + parent_transform.dx * x
64             self._absolute_transform.y = parent_transform.y + parent_transform.dy * y
65             self._absolute_transform.dx = parent_transform.dx * self._scale
66             self._absolute_transform.dy = parent_transform.dy * self._scale
67             self._absolute_transform.transpose_xy = parent_transform.transpose_xy
68             self._absolute_transform.mirror_x = parent_transform.mirror_x
69             self._absolute_transform.mirror_y = parent_transform.mirror_y
70             self._absolute_transform.scale = parent_transform.scale * self._scale
71         self._update_child_transforms()
72
73     def _update_child_transforms(self):
74         if self.in_group:
75             for layer in self._layers:
76                 layer.update_transform(self._absolute_transform)
77
78     def _removal_cleanup(self, index):
79         layer = self._layers[index]
80         layer.update_transform(None)
81
82     def _layer_update(self, index):
83         layer = self._layers[index]
84         layer.update_transform(self._absolute_transform)
85
86     def append(self, layer):
87         """Append a layer to the group. It will be drawn
88         above other layers.
89         """
90         self.insert(len(self._layers), layer)
91
92     def insert(self, index, layer):
93         """Insert a layer into the group."""
94         if not isinstance(layer, self._supported_types):
95             raise ValueError("Invalid Group Member")
96         if layer.in_group:
97             raise ValueError("Layer already in a group.")
98         if len(self._layers) == self._max_size:
99             raise RuntimeError("Group full")
100         self._layers.insert(index, layer)
101         self._layer_update(index)
102
103     def index(self, layer):
104         """Returns the index of the first copy of layer.
105         Raises ValueError if not found.
106         """
107         return self._layers.index(layer)
108
109     def pop(self, index=-1):
110         """Remove the ith item and return it."""
111         self._removal_cleanup(index)
112         return self._layers.pop(index)
113
114     def remove(self, layer):
115         """Remove the first copy of layer. Raises ValueError
116         if it is not present."""
117         index = self.index(layer)
118         self._layers.pop(index)
119
120     def __len__(self):
121         """Returns the number of layers in a Group"""
122         return len(self._layers)
123
124     def __getitem__(self, index):
125         """Returns the value at the given index."""
126         return self._layers[index]
127
128     def __setitem__(self, index, value):
129         """Sets the value at the given index."""
130         self._removal_cleanup(index)
131         self._layers[index] = value
132         self._layer_update(index)
133
134     def __delitem__(self, index):
135         """Deletes the value at the given index."""
136         del self._layers[index]
137
138     def _fill_area(self, buffer):
139         if self._hidden_group:
140             return
141
142         for layer in self._layers:
143             if isinstance(layer, (Group, TileGrid)):
144                 layer._fill_area(buffer)  # pylint: disable=protected-access
145
146     @property
147     def hidden(self):
148         """True when the Group and all of it’s layers are not visible. When False, the
149         Group’s layers are visible if they haven’t been hidden.
150         """
151         return self._hidden_group
152
153     @hidden.setter
154     def hidden(self, value):
155         if not isinstance(value, (bool, int)):
156             raise ValueError("Expecting a boolean or integer value")
157         self._hidden_group = bool(value)
158
159     @property
160     def scale(self):
161         """Scales each pixel within the Group in both directions. For example, when
162         scale=2 each pixel will be represented by 2x2 pixels.
163         """
164         return self._scale
165
166     @scale.setter
167     def scale(self, value):
168         if not isinstance(value, int) or value < 1:
169             raise ValueError("Scale must be >= 1")
170         if self._scale != value:
171             parent_scale = self._absolute_transform.scale / self._scale
172             self._absolute_transform.dx = (
173                 self._absolute_transform.dx / self._scale * value
174             )
175             self._absolute_transform.dy = (
176                 self._absolute_transform.dy / self._scale * value
177             )
178             self._absolute_transform.scale = parent_scale * value
179
180             self._scale = value
181             self._update_child_transforms()
182
183     @property
184     def x(self):
185         """X position of the Group in the parent."""
186         return self._group_x
187
188     @x.setter
189     def x(self, value):
190         if not isinstance(value, int):
191             raise ValueError("x must be an integer")
192         if self._group_x != value:
193             if self._absolute_transform.transpose_xy:
194                 dy_value = self._absolute_transform.dy / self._scale
195                 self._absolute_transform.y += dy_value * (value - self._group_x)
196             else:
197                 dx_value = self._absolute_transform.dx / self._scale
198                 self._absolute_transform.x += dx_value * (value - self._group_x)
199             self._group_x = value
200             self._update_child_transforms()
201
202     @property
203     def y(self):
204         """Y position of the Group in the parent."""
205         return self._group_y
206
207     @y.setter
208     def y(self, value):
209         if not isinstance(value, int):
210             raise ValueError("y must be an integer")
211         if self._group_y != value:
212             if self._absolute_transform.transpose_xy:
213                 dx_value = self._absolute_transform.dx / self._scale
214                 self._absolute_transform.x += dx_value * (value - self._group_y)
215             else:
216                 dy_value = self._absolute_transform.dy / self._scale
217                 self._absolute_transform.y += dy_value * (value - self._group_y)
218             self._group_y = value
219             self._update_child_transforms()