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