]> Repositories - hackapet/Adafruit_Blinka_Displayio.git/blob - displayio/_displaycore.py
Start splitting display functions into display core
[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 from typing import Optional, Union
26 import _typing
27 from ._fourwire import FourWire
28 from ._group import Group
29 from ._i2cdisplay import I2CDisplay
30 from ._structs import ColorspaceStruct, TransformStruct, RectangleStruct
31 from ._area import Area
32
33 from paralleldisplay import ParallelBus
34 from ._constants import (
35     DISPLAY_COMMAND,
36     DISPLAY_DATA,
37     CHIP_SELECT_TOGGLE_EVERY_BYTE,
38     CHIP_SELECT_UNTOUCHED,
39 )
40
41 displays = []
42
43 # Functions
44 #* construct
45 #  show
46 #  set_dither
47 #^ bus_reset
48 #  set_region_to_update
49 #* release
50 #  fill_area
51 #  clip_area (_clip)
52
53 class _DisplayCore:
54     # pylint: disable=too-many-arguments, too-many-instance-attributes
55
56     def __init__(
57         self,
58         bus,
59         width: int,
60         height: int,
61         ram_width: int,
62         ram_height: int,
63         colstart: int,
64         rowstart: int,
65         rotation: int,
66         color_depth: int,
67         grayscale: bool,
68         pixels_in_byte_share_row: bool,
69         bytes_per_cell: int,
70         reverse_pixels_in_byte: bool,
71         reverse_bytes_in_word: bool
72     ):
73         self._colorspace = ColorspaceStruct(
74             depth = color_depth,
75             grayscale = grayscale,
76             grayscale_bit = 8 - color_depth,
77             pixels_in_byte_share_row = pixels_in_byte_share_row,
78             bytes_per_cell = bytes_per_cell,
79             reverse_pixels_in_byte = reverse_pixels_in_byte,
80             reverse_bytes_in_word = reverse_bytes_in_word,
81             dither = False
82         )
83         self._current_group = None
84         self._colstart = colstart
85         self._rowstart = rowstart
86         self._last_refresh = 0
87         self._refresh_in_progress = False
88
89         if bus:
90             if isinstance(bus, (FourWire, I2CDisplay, ParallelBus)):
91                 self._bus_reset = bus.reset
92                 self._begin_transaction = bus._begin_transaction
93                 self._send = bus._send
94                 self._end_transaction = bus._end_transaction
95             else:
96                 raise ValueError("Unsupported display bus type")
97
98         self._bus = bus
99         self._area = Area(0, 0, width, height)
100
101         self._width = width
102         self._height = height
103         self._ram_width = ram_width
104         self._ram_height = ram_height
105         self._rotation = rotation
106         self._transform = TransformStruct()
107
108     def set_rotation(self, rotation: int) -> None:
109         """
110         Sets the rotation of the display as an int in degrees.
111         """
112         # pylint: disable=protected-access
113         transposed = self._rotation in (90, 270)
114         will_be_transposed = rotation in (90, 270)
115         if transposed != will_be_transposed:
116             self._width, self._height = self._height, self._width
117
118         height = self._height
119         width = self._width
120
121         rotation %= 360
122         self._rotation = rotation
123         self._transform.x = 0
124         self._transform.y = 0
125         self._transform.scale = 1
126         self._transform.mirror_x = False
127         self._transform.mirror_y = False
128         self._transform.transpose_xy = False
129
130         if rotation in (0, 180):
131             if rotation == 180:
132                 self._transform.mirror_x = True
133                 self._transform.mirror_y = True
134         else:
135             self._transform.transpose_xy = True
136             if rotation == 270:
137                 self._transform.mirror_y = True
138             else:
139                 self._transform.mirror_x = True
140
141         self._area.x1 = 0
142         self._area.y1 = 0
143         self._area.next = None
144
145         self._transform.dx = 1
146         self._transform.dy = 1
147         if self._transform.transpose_xy:
148             self._area.x2 = height
149             self._area.y2 = width
150             if self._transform.mirror_x:
151                 self._transform.x = height
152                 self._transform.dx = -1
153             if self._transform.mirror_y:
154                 self._transform.y = width
155                 self._transform.dy = -1
156         else:
157             self._area.x2 = width
158             self._area.y2 = height
159             if self._transform.mirror_x:
160                 self._transform.x = width
161                 self._transform.dx = -1
162             if self._transform.mirror_y:
163                 self._transform.y = height
164                 self._transform.dy = -1
165
166         if self._current_group is not None:
167             self._current_group._update_transform(self._transform)
168
169     def show(self, root_group: Group) -> bool:
170         # pylint: disable=protected-access
171
172         """
173         Switches to displaying the given group of layers. When group is `None`, the
174         default CircuitPython terminal will be shown.
175
176         :param Optional[displayio.Group] root_group: The group to show.
177         """
178
179         """
180         # TODO: Implement Supervisor
181         if root_group is None:
182             circuitpython_splash = _Supervisor().circuitpython_splash
183             if not circuitpython_splash._in_group:
184                 root_group = circuitpython_splash
185             elif self._current_group == circuitpython_splash:
186                 return True
187         """
188
189         if root_group == self._current_group:
190             return True
191
192         if root_group is not None and root_group._in_group:
193             return False
194
195         if self._current_group is not None:
196             self._current_group._in_group = False
197
198         if root_group is not None:
199             root_group._update_transform(self._transform)
200             root_group._in_group = True
201
202         self._current_group = root_group
203         self._full_refresh = True
204
205         return True
206
207     def set_region_to_update(
208         self,
209         column_command: int,
210         row_command: int,
211         set_current_column_command: Optional[int],
212         set_current_row_command: Optional[int],
213         data_as_commands: bool,
214         always_toggle_chip_select: bool,
215         area: Area,
216         SH1107_addressing: bool,
217     ) -> None:
218         # pylint: disable=invalid-name, too-many-arguments, too-many-locals, too-many-branches,
219         # pylint: disable=too-many-statements
220
221         big_endian = True  # default is True # TODO ????
222
223         x1 = area.x1 + self._colstart
224         x2 = area.x2 + self._colstart
225         y1 = area.y1 + self._rowstart
226         y2 = area.y2 + self._rowstart
227
228         # Collapse down the dimension where multiple pixels are in a byte.
229         if self._colorspace.depth < 8:
230             pixels_per_byte = 8 // self._colorspace.depth
231             if self._colorspace.pixels_in_byte_share_row:
232                 x1 //= pixels_per_byte * self._colorspace.bytes_per_cell
233                 x2 //= pixels_per_byte * self._colorspace.bytes_per_cell
234             else:
235                 y1 //= pixels_per_byte * self._colorspace.bytes_per_cell
236                 y2 //= pixels_per_byte * self._colorspace.bytes_per_cell
237
238         x2 -= 1
239         y2 -= 1
240
241         chip_select = CHIP_SELECT_UNTOUCHED
242         if always_toggle_chip_select or data_as_commands:
243             chip_select = CHIP_SELECT_TOGGLE_EVERY_BYTE
244
245         # Set column
246         self._begin_transaction()
247         data = bytearray(5)
248         data[0] = column_command
249         if not data_as_commands:
250             self._send(DISPLAY_COMMAND, CHIP_SELECT_UNTOUCHED, data, 1)
251             data_type = DISPLAY_DATA
252             data_length = 0
253         else:
254             data_type = DISPLAY_COMMAND
255             data_length = 1
256
257         if self._ram_width < 0x100:
258             data[data_length] = x1
259             data_length += 1
260             data[data_length] = x2
261             data_length += 1
262         else:
263             if big_endian:
264                 data[data_length] = x1 >> 8
265                 data_length += 1
266                 data[data_length] = x1 & 0xFF
267                 data_length += 1
268                 data[data_length] = x2 >> 8
269                 data_length += 1
270                 data[data_length] = x2 & 0xFF
271                 data_length += 1
272             else:
273                 data[data_length] = x1 & 0xFF
274                 data_length += 1
275                 data[data_length] = x1 >> 8
276                 data_length += 1
277                 data[data_length] = x2 & 0xFF
278                 data_length += 1
279                 data[data_length] = x2 >> 8
280                 data_length += 1
281
282         # Quirk for "SH1107_addressing"
283         #     Column lower command = 0x00, Column upper command = 0x10
284         if SH1107_addressing:
285             data[0] = ((x1 >> 4) & 0x0F) | 0x10  # 0x10 to 0x17
286             data[1] = x1 & 0x0F  # 0x00 to 0x0F
287             data_length = 2
288
289         self._send(data_type, chip_select, data, data_length)
290         self._end_transaction()
291
292         if set_current_column_command is not None:
293             command = bytearray(1)
294             command[0] = set_current_column_command
295             self._begin_transaction()
296             self._send(DISPLAY_COMMAND, chip_select, command, 1)
297             self._send(DISPLAY_DATA, chip_select, data, data_length // 2)
298             self._end_transaction()
299
300         # Set row
301         self._begin_transaction()
302         data[0] = row_command
303         data_length = 1
304         if not data_as_commands:
305             self._send(DISPLAY_COMMAND, CHIP_SELECT_UNTOUCHED, data, 1)
306             data_length = 0
307
308         if self._ram_height < 0x100:
309             data[data_length] = y1
310             data_length += 1
311             data[data_length] = y2
312             data_length += 1
313         else:
314             if big_endian:
315                 data[data_length] = y1 >> 8
316                 data_length += 1
317                 data[data_length] = y1 & 0xFF
318                 data_length += 1
319                 data[data_length] = y2 >> 8
320                 data_length += 1
321                 data[data_length] = y2 & 0xFF
322                 data_length += 1
323                 # TODO Which is right? The core uses above
324             else:
325                 data[data_length] = y1 & 0xFF
326                 data_length += 1
327                 data[data_length] = y1 >> 8
328                 data_length += 1
329                 data[data_length] = y2 & 0xFF
330                 data_length += 1
331                 data[data_length] = y2 >> 8
332                 data_length += 1
333
334         # Quirk for "SH1107_addressing"
335         #  Page address command = 0xB0
336         if SH1107_addressing:
337             # Set the page to out y value
338             data[0] = 0xB0 | y1
339             data_length = 1
340
341         self._send(data_type, chip_select, data, data_length)
342         self._end_transaction()
343
344         if set_current_row_command is not None:
345             command = bytearray(1)
346             command[0] = set_current_row_command
347             self._begin_transaction()
348             self._send(DISPLAY_COMMAND, chip_select, command, 1)
349             self._send(DISPLAY_DATA, chip_select, data, data_length // 2)
350             self._end_transaction()
351
352     def start_refresh(self) -> bool:
353         # pylint: disable=protected-access
354
355         if self._refresh_in_progress:
356             return False
357
358         self._refresh_in_progress = True
359         #self._last_refresh = _Supervisor()._ticks_ms64()
360         return True
361
362     def finish_refresh(self) -> None:
363         # pylint: disable=protected-access
364
365         if self._current_group is not None:
366             self._current_group._finish_refresh()
367
368         self._full_refresh = False
369         self._refresh_in_progress = False
370         #self._last_refresh = _Supervisor()._ticks_ms64()
371
372     def get_refresh_areas(self):
373         subrectangles = []
374         if self._current_group is not None:
375             # Eventually calculate dirty rectangles here
376             subrectangles.append(RectangleStruct(0, 0, self._width, self._height))
377         return subrectangles
378
379     def release(self) -> None:
380         # pylint: disable=protected-access
381
382         if self._current_group is not None:
383             self._current_group._in_group = False
384
385     def fill_area(
386         self, area: Area, mask: _typing.WriteableBuffer, buffer: _typing.WriteableBuffer
387     ) -> bool:
388         # pylint: disable=protected-access
389
390         return self._current_group._fill_area(self._colorspace, area, mask, buffer)
391
392     def clip_area(self, area: Area, clipped: Area) -> bool:
393         # pylint: disable=protected-access
394
395         overlaps = self._area._compute_overlap(area, clipped)
396         if not overlaps:
397             return False
398
399         # Expand the area if we have multiple pixels per byte and we need to byte align the bounds
400         if self._colorspace.depth < 8:
401             pixels_per_byte = (
402                 8 // self._colorspace.depth * self._colorspace.bytes_per_cell
403             )
404             if self._colorspace.pixels_in_byte_share_row:
405                 if clipped.x1 % pixels_per_byte != 0:
406                     clipped.x1 -= clipped.x1 % pixels_per_byte
407                 if clipped.x2 % pixels_per_byte != 0:
408                     clipped.x2 += pixels_per_byte - clipped.x2 % pixels_per_byte
409             else:
410                 if clipped.y1 % pixels_per_byte != 0:
411                     clipped.y1 -= clipped.y1 % pixels_per_byte
412                 if clipped.y2 % pixels_per_byte != 0:
413                     clipped.y2 += pixels_per_byte - clipped.y2 % pixels_per_byte
414
415         return True
416
417     def send(self, data_type: int, chip_select: int, data: _typing.ReadableBuffer) -> None:
418         """
419         Send the data to the current bus
420         """
421         self._send(data_type, chip_select, data)
422
423     def begin_transaction(self) -> None:
424         """
425         Begin Bus Transaction
426         """
427         self._begin_transaction()
428
429     def end_transaction(self) -> None:
430         """
431         End Bus Transaction
432         """
433         self._end_transaction()
434
435     def get_width(self) -> int:
436         """
437         Gets the width of the display in pixels.
438         """
439         return self._width
440
441     def get_height(self) -> int:
442         """
443         Gets the height of the display in pixels.
444         """
445         return self._height
446
447     def get_rotation(self) -> int:
448         """
449         Gets the rotation of the display as an int in degrees.
450         """
451         return self._rotation
452             
453     def get_bus(self) -> Union[FourWire,ParallelBus,I2CDisplay]:
454         """
455         The bus being used by the display. [readonly]
456         """
457         return self._bus