]> Repositories - hackapet/Adafruit_Blinka_Displayio.git/blob - displayio/_displaycore.py
ccda898e66a6c268f748921560817244307ac51b
[hackapet/Adafruit_Blinka_Displayio.git] / displayio / _displaycore.py
1 # SPDX-FileCopyrightText: 2021 Melissa LeBlanc-Williams for Adafruit Industries
2 # SPDX-FileCopyrightText: 2021 James Carr
3 #
4 # SPDX-License-Identifier: MIT
5
6 """
7 `displayio._displaycore`
8 ================================================================================
9
10 Super class of the display classes
11
12 **Software and Dependencies:**
13
14 * Adafruit Blinka:
15   https://github.com/adafruit/Adafruit_Blinka/releases
16
17 * Author(s): James Carr, Melissa LeBlanc-Williams
18
19 """
20
21 __version__ = "0.0.0+auto.0"
22 __repo__ = "https://github.com/adafruit/Adafruit_Blinka_Displayio.git"
23
24
25 import time
26 import circuitpython_typing
27 from paralleldisplay import ParallelBus
28 from ._fourwire import FourWire
29 from ._group import Group
30 from ._i2cdisplay import I2CDisplay
31 from ._structs import ColorspaceStruct, TransformStruct, RectangleStruct
32 from ._area import Area
33 from ._displaybus import _DisplayBus
34
35
36 class _DisplayCore:
37     # pylint: disable=too-many-arguments, too-many-instance-attributes, too-many-locals
38
39     def __init__(
40         self,
41         bus,
42         width: int,
43         height: int,
44         ram_width: int,
45         ram_height: int,
46         colstart: int,
47         rowstart: int,
48         rotation: int,
49         color_depth: int,
50         grayscale: bool,
51         pixels_in_byte_share_row: bool,
52         bytes_per_cell: int,
53         reverse_pixels_in_byte: bool,
54         reverse_bytes_in_word: bool,
55         column_command: int,
56         row_command: int,
57         set_current_column_command: int,
58         set_current_row_command: int,
59         data_as_commands: bool,
60         always_toggle_chip_select: bool,
61         sh1107_addressing: bool,
62         address_little_endian: bool,
63     ):
64         self.colorspace = ColorspaceStruct(
65             depth=color_depth,
66             grayscale=grayscale,
67             grayscale_bit=8 - color_depth,
68             pixels_in_byte_share_row=pixels_in_byte_share_row,
69             bytes_per_cell=bytes_per_cell,
70             reverse_pixels_in_byte=reverse_pixels_in_byte,
71             reverse_bytes_in_word=reverse_bytes_in_word,
72             dither=False,
73         )
74         self.current_group = None
75         self.colstart = colstart
76         self.rowstart = rowstart
77         self.last_refresh = 0
78
79         self.column_command = column_command
80         self.row_command = row_command
81         self.set_current_column_command = set_current_column_command
82         self.set_current_row_command = set_current_row_command
83         self.data_as_commands = data_as_commands
84         self.always_toggle_chip_select = always_toggle_chip_select
85         self.sh1107_addressing = sh1107_addressing
86         self.address_little_endian = address_little_endian
87
88         self.refresh_in_progress = False
89         self.full_refresh = False
90         self.last_refresh = 0
91
92         if bus:
93             if isinstance(bus, (FourWire, I2CDisplay, ParallelBus)):
94                 self._bus_reset = bus.reset
95                 self._begin_transaction = bus._begin_transaction
96                 self._send = bus._send
97                 self._end_transaction = bus._end_transaction
98             else:
99                 raise ValueError("Unsupported display bus type")
100
101         self._bus = bus
102         self.area = Area(0, 0, width, height)
103
104         self.width = width
105         self.height = height
106         self.ram_width = ram_width
107         self.ram_height = ram_height
108         self.rotation = rotation
109         self.transform = TransformStruct()
110
111     def set_rotation(self, rotation: int) -> None:
112         """
113         Sets the rotation of the display as an int in degrees.
114         """
115         # pylint: disable=protected-access, too-many-branches
116         transposed = self.rotation in (90, 270)
117         will_be_transposed = rotation in (90, 270)
118         if transposed != will_be_transposed:
119             self.width, self.height = self.height, self.width
120
121         height = self.height
122         width = self.width
123
124         rotation %= 360
125         self.rotation = rotation
126         self.transform.x = 0
127         self.transform.y = 0
128         self.transform.scale = 1
129         self.transform.mirror_x = False
130         self.transform.mirror_y = False
131         self.transform.transpose_xy = False
132
133         if rotation in (0, 180):
134             if rotation == 180:
135                 self.transform.mirror_x = True
136                 self.transform.mirror_y = True
137         else:
138             self.transform.transpose_xy = True
139             if rotation == 270:
140                 self.transform.mirror_y = True
141             else:
142                 self.transform.mirror_x = True
143
144         self.area.x1 = 0
145         self.area.y1 = 0
146         self.area.next = None
147
148         self.transform.dx = 1
149         self.transform.dy = 1
150         if self.transform.transpose_xy:
151             self.area.x2 = height
152             self.area.y2 = width
153             if self.transform.mirror_x:
154                 self.transform.x = height
155                 self.transform.dx = -1
156             if self.transform.mirror_y:
157                 self.transform.y = width
158                 self.transform.dy = -1
159         else:
160             self.area.x2 = width
161             self.area.y2 = height
162             if self.transform.mirror_x:
163                 self.transform.x = width
164                 self.transform.dx = -1
165             if self.transform.mirror_y:
166                 self.transform.y = height
167                 self.transform.dy = -1
168
169         if self.current_group is not None:
170             self.current_group._update_transform(self.transform)
171
172     def show(self, root_group: Group) -> bool:
173         # pylint: disable=protected-access
174
175         """
176         Switches to displaying the given group of layers. When group is `None`, the
177         default CircuitPython terminal will be shown.
178
179         :param Optional[displayio.Group] root_group: The group to show.
180         """
181
182         """
183         # TODO: Implement Supervisor
184         if root_group is None:
185             circuitpython_splash = _Supervisor().circuitpython_splash
186             if not circuitpython_splash._in_group:
187                 root_group = circuitpython_splash
188             elif self.current_group == circuitpython_splash:
189                 return True
190         """
191
192         if root_group == self.current_group:
193             return True
194
195         if root_group is not None and root_group._in_group:
196             return False
197
198         if self.current_group is not None:
199             self.current_group._in_group = False
200
201         if root_group is not None:
202             root_group._update_transform(self.transform)
203             root_group._in_group = True
204
205         self.current_group = root_group
206         self.full_refresh = True
207
208         return True
209
210     def start_refresh(self) -> bool:
211         # pylint: disable=protected-access
212         """Mark the display core as currently being refreshed"""
213
214         if self.refresh_in_progress:
215             return False
216
217         self.refresh_in_progress = True
218         self.last_refresh = time.monotonic() * 1000
219         return True
220
221     def finish_refresh(self) -> None:
222         # pylint: disable=protected-access
223         """Unmark the display core as currently being refreshed"""
224
225         if self.current_group is not None:
226             self.current_group._finish_refresh()
227
228         self.full_refresh = False
229         self.refresh_in_progress = False
230         self.last_refresh = time.monotonic() * 1000
231
232     def get_refresh_areas(self) -> list:
233         """Get a list of areas to be refreshed"""
234         subrectangles = []
235         if self.current_group is not None:
236             # Eventually calculate dirty rectangles here
237             subrectangles.append(RectangleStruct(0, 0, self.width, self.height))
238         return subrectangles
239
240     def release(self) -> None:
241         """Release the display from the current group"""
242         # pylint: disable=protected-access
243
244         if self.current_group is not None:
245             self.current_group._in_group = False
246
247     def fill_area(
248         self,
249         area: Area,
250         mask: circuitpython_typing.WriteableBuffer,
251         buffer: circuitpython_typing.WriteableBuffer,
252     ) -> bool:
253         # pylint: disable=protected-access
254         """Call the current group's fill area function"""
255
256         return self.current_group._fill_area(self.colorspace, area, mask, buffer)
257
258     def clip_area(self, area: Area, clipped: Area) -> bool:
259         """Shrink the area to the region shared by the two areas"""
260         # pylint: disable=protected-access
261
262         overlaps = self.area._compute_overlap(area, clipped)
263         if not overlaps:
264             return False
265
266         # Expand the area if we have multiple pixels per byte and we need to byte align the bounds
267         if self.colorspace.depth < 8:
268             pixels_per_byte = (
269                 8 // self.colorspace.depth * self.colorspace.bytes_per_cell
270             )
271             if self.colorspace.pixels_in_byte_share_row:
272                 if clipped.x1 % pixels_per_byte != 0:
273                     clipped.x1 -= clipped.x1 % pixels_per_byte
274                 if clipped.x2 % pixels_per_byte != 0:
275                     clipped.x2 += pixels_per_byte - clipped.x2 % pixels_per_byte
276             else:
277                 if clipped.y1 % pixels_per_byte != 0:
278                     clipped.y1 -= clipped.y1 % pixels_per_byte
279                 if clipped.y2 % pixels_per_byte != 0:
280                     clipped.y2 += pixels_per_byte - clipped.y2 % pixels_per_byte
281
282         return True
283
284     def send(
285         self,
286         data_type: int,
287         chip_select: int,
288         data: circuitpython_typing.ReadableBuffer,
289     ) -> None:
290         """
291         Send the data to the current bus
292         """
293         self._send(data_type, chip_select, data)
294
295     def begin_transaction(self) -> None:
296         """
297         Begin Bus Transaction
298         """
299         self._begin_transaction()
300
301     def end_transaction(self) -> None:
302         """
303         End Bus Transaction
304         """
305         self._end_transaction()
306
307     def get_width(self) -> int:
308         """
309         Gets the width of the display in pixels.
310         """
311         return self.width
312
313     def get_height(self) -> int:
314         """
315         Gets the height of the display in pixels.
316         """
317         return self.height
318
319     def get_rotation(self) -> int:
320         """
321         Gets the rotation of the display as an int in degrees.
322         """
323         return self.rotation
324
325     def get_bus(self) -> _DisplayBus:
326         """
327         The bus being used by the display. [readonly]
328         """
329         return self._bus