X-Git-Url: https://git.ayoreis.com/hackapet/Adafruit_Blinka_Displayio.git/blobdiff_plain/1ef9f0a96fa6e1cfd07144d5bdc6c123027c48f3..5cfe68b419b1e014ae334c500569d87b661e4281:/displayio/group.py?ds=inline diff --git a/displayio/group.py b/displayio/group.py new file mode 100644 index 0000000..2444861 --- /dev/null +++ b/displayio/group.py @@ -0,0 +1,232 @@ +# The MIT License (MIT) +# +# Copyright (c) 2020 Melissa LeBlanc-Williams for Adafruit Industries +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. + +""" +`displayio` +================================================================================ + +displayio for Blinka + +**Software and Dependencies:** + +* Adafruit Blinka: + https://github.com/adafruit/Adafruit_Blinka/releases + +* Author(s): Melissa LeBlanc-Williams + +""" + +from displayio.tilegrid import TileGrid +from displayio import Transform + +__version__ = "0.0.0-auto.0" +__repo__ = "https://github.com/adafruit/Adafruit_Blinka_displayio.git" + + +class Group: + """Manage a group of sprites and groups and how they are inter-related.""" + + def __init__(self, *, max_size=4, scale=1, x=0, y=0): + """Create a Group of a given size and scale. Scale is in + one dimension. For example, scale=2 leads to a layer’s + pixel being 2x2 pixels when in the group. + """ + if not isinstance(max_size, int) or max_size < 1: + raise ValueError("Max Size must be >= 1") + self._max_size = max_size + if not isinstance(scale, int) or scale < 1: + raise ValueError("Scale must be >= 1") + self._scale = scale + self._x = x + self._y = y + self._hidden = False + self._layers = [] + self._supported_types = (TileGrid, Group) + self._absolute_transform = None + self.in_group = False + self._absolute_transform = Transform(0, 0, 1, 1, 1, False, False, False) + + def update_transform(self, parent_transform): + """Update the parent transform and child transforms""" + self.in_group = parent_transform is not None + if self.in_group: + x = self._x + y = self._y + if parent_transform.transpose_xy: + x, y = y, x + self._absolute_transform.x = parent_transform.x + parent_transform.dx * x + self._absolute_transform.y = parent_transform.y + parent_transform.dy * y + self._absolute_transform.dx = parent_transform.dx * self._scale + self._absolute_transform.dy = parent_transform.dy * self._scale + self._absolute_transform.transpose_xy = parent_transform.transpose_xy + self._absolute_transform.mirror_x = parent_transform.mirror_x + self._absolute_transform.mirror_y = parent_transform.mirror_y + self._absolute_transform.scale = parent_transform.scale * self._scale + self._update_child_transforms() + + def _update_child_transforms(self): + if self.in_group: + for layer in self._layers: + layer.update_transform(self._absolute_transform) + + def _removal_cleanup(self, index): + layer = self._layers[index] + layer.update_transform(None) + + def _layer_update(self, index): + layer = self._layers[index] + layer.update_transform(self._absolute_transform) + + def append(self, layer): + """Append a layer to the group. It will be drawn + above other layers. + """ + self.insert(len(self._layers), layer) + + def insert(self, index, layer): + """Insert a layer into the group.""" + if not isinstance(layer, self._supported_types): + raise ValueError("Invalid Group Member") + if layer.in_group: + raise ValueError("Layer already in a group.") + if len(self._layers) == self._max_size: + raise RuntimeError("Group full") + self._layers.insert(index, layer) + self._layer_update(index) + + def index(self, layer): + """Returns the index of the first copy of layer. + Raises ValueError if not found. + """ + return self._layers.index(layer) + + def pop(self, index=-1): + """Remove the ith item and return it.""" + self._removal_cleanup(index) + return self._layers.pop(index) + + def remove(self, layer): + """Remove the first copy of layer. Raises ValueError + if it is not present.""" + index = self.index(layer) + self._layers.pop(index) + + def __len__(self): + """Returns the number of layers in a Group""" + return len(self._layers) + + def __getitem__(self, index): + """Returns the value at the given index.""" + return self._layers[index] + + def __setitem__(self, index, value): + """Sets the value at the given index.""" + self._removal_cleanup(index) + self._layers[index] = value + self._layer_update(index) + + def __delitem__(self, index): + """Deletes the value at the given index.""" + del self._layers[index] + + def _fill_area(self, buffer): + if self._hidden: + return + + for layer in self._layers: + if isinstance(layer, (Group, TileGrid)): + layer._fill_area(buffer) # pylint: disable=protected-access + + @property + def hidden(self): + """True when the Group and all of it’s layers are not visible. When False, the + Group’s layers are visible if they haven’t been hidden. + """ + return self._hidden + + @hidden.setter + def hidden(self, value): + if not isinstance(value, (bool, int)): + raise ValueError("Expecting a boolean or integer value") + self._hidden = bool(value) + + @property + def scale(self): + """Scales each pixel within the Group in both directions. For example, when + scale=2 each pixel will be represented by 2x2 pixels. + """ + return self._scale + + @scale.setter + def scale(self, value): + if not isinstance(value, int) or value < 1: + raise ValueError("Scale must be >= 1") + if self._scale != value: + parent_scale = self._absolute_transform.scale / self._scale + self._absolute_transform.dx = ( + self._absolute_transform.dx / self._scale * value + ) + self._absolute_transform.dy = ( + self._absolute_transform.dy / self._scale * value + ) + self._absolute_transform.scale = parent_scale * value + + self._scale = value + self._update_child_transforms() + + @property + def x(self): + """X position of the Group in the parent.""" + return self._x + + @x.setter + def x(self, value): + if not isinstance(value, int): + raise ValueError("x must be an integer") + if self._x != value: + if self._absolute_transform.transpose_xy: + dy_value = self._absolute_transform.dy / self._scale + self._absolute_transform.y += dy_value * (value - self._x) + else: + dx_value = self._absolute_transform.dx / self._scale + self._absolute_transform.x += dx_value * (value - self._x) + self._x = value + self._update_child_transforms() + + @property + def y(self): + """Y position of the Group in the parent.""" + return self._y + + @y.setter + def y(self, value): + if not isinstance(value, int): + raise ValueError("y must be an integer") + if self._y != value: + if self._absolute_transform.transpose_xy: + dx_value = self._absolute_transform.dx / self._scale + self._absolute_transform.x += dx_value * (value - self._y) + else: + dy_value = self._absolute_transform.dy / self._scale + self._absolute_transform.y += dy_value * (value - self._y) + self._y = value + self._update_child_transforms()