Skip to content

Commit c9a64c9

Browse files
authored
Merge pull request #70 from FoamyGuy/tab_layout
Tab layout
2 parents a5865ee + adca2d7 commit c9a64c9

9 files changed

+603
-14
lines changed

adafruit_displayio_layout/layouts/page_layout.py

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -53,11 +53,11 @@ def __init__(
5353
self.x = x
5454
self.y = y
5555

56-
self._page_content_list = []
56+
self.page_content_list = []
5757
self._cur_showing_index = 0
5858

5959
def add_content(self, page_content, page_name=None):
60-
"""Add a child to the grid.
60+
"""Add a child to the page layout.
6161
6262
:param page_content: the content for the page typically a Group
6363
:param page_name: the name of this page
@@ -72,10 +72,10 @@ def add_content(self, page_content, page_name=None):
7272
"page_name": page_name,
7373
}
7474

75-
if len(self._page_content_list) > 0:
75+
if len(self.page_content_list) > 0:
7676
_page_group.hidden = True
7777

78-
self._page_content_list.append(sub_view_obj)
78+
self.page_content_list.append(sub_view_obj)
7979
self.append(_page_group)
8080

8181
def _check_args(self, page_name, page_index):
@@ -95,16 +95,16 @@ def _check_args(self, page_name, page_index):
9595
)
9696

9797
if page_index is not None:
98-
if page_index >= len(self._page_content_list):
98+
if page_index >= len(self.page_content_list):
9999
raise KeyError(
100100
"KeyError at index {} in list length {}".format(
101-
page_index, len(self._page_content_list)
101+
page_index, len(self.page_content_list)
102102
),
103103
)
104104

105105
if page_name is not None:
106106
_found = False
107-
for page in self._page_content_list:
107+
for page in self.page_content_list:
108108
if not _found:
109109
if page_name == page["page_name"]:
110110
_found = True
@@ -125,10 +125,10 @@ def get_page(self, page_name=None, page_index=None):
125125
self._check_args(page_name, page_index)
126126

127127
if page_index is not None:
128-
return self._page_content_list[page_index]
128+
return self.page_content_list[page_index]
129129

130130
if page_name is not None:
131-
for cell in self._page_content_list:
131+
for cell in self.page_content_list:
132132
if cell["page_name"] == page_name:
133133
return cell
134134

@@ -149,7 +149,7 @@ def show_page(self, page_name=None, page_index=None):
149149

150150
self._check_args(page_name, page_index)
151151

152-
for cur_index, page in enumerate(self._page_content_list):
152+
for cur_index, page in enumerate(self.page_content_list):
153153
if page_name is not None:
154154
if page["page_name"] == page_name:
155155
self._cur_showing_index = cur_index
@@ -182,7 +182,7 @@ def showing_page_name(self):
182182
Name of the currently showing page
183183
:return string: showing_page_name
184184
"""
185-
return self._page_content_list[self._cur_showing_index]["page_name"]
185+
return self.page_content_list[self._cur_showing_index]["page_name"]
186186

187187
@showing_page_name.setter
188188
def showing_page_name(self, new_name):
@@ -194,7 +194,7 @@ def showing_page_content(self):
194194
The content object for the currently showing page
195195
:return Displayable: showing_page_content
196196
"""
197-
return self._page_content_list[self._cur_showing_index]["content"][0]
197+
return self.page_content_list[self._cur_showing_index]["content"][0]
198198

199199
def next_page(self, loop=True):
200200
"""
@@ -203,7 +203,7 @@ def next_page(self, loop=True):
203203
:return: None
204204
"""
205205

206-
if self._cur_showing_index + 1 < len(self._page_content_list):
206+
if self._cur_showing_index + 1 < len(self.page_content_list):
207207
self.show_page(page_index=self._cur_showing_index + 1)
208208
else:
209209
if not loop:
@@ -223,4 +223,4 @@ def previous_page(self, loop=True):
223223
if not loop:
224224
print("No more pages")
225225
else:
226-
self.show_page(page_index=len(self._page_content_list) - 1)
226+
self.show_page(page_index=len(self.page_content_list) - 1)
Lines changed: 288 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,288 @@
1+
# SPDX-FileCopyrightText: 2022 Tim Cocks
2+
#
3+
# SPDX-License-Identifier: MIT
4+
5+
"""
6+
`tab_layout`
7+
================================================================================
8+
9+
A layout that organizes pages into tabs.
10+
11+
12+
* Author(s): Tim Cocks
13+
14+
Implementation Notes
15+
--------------------
16+
17+
**Hardware:**
18+
19+
**Software and Dependencies:**
20+
21+
* Adafruit CircuitPython firmware for the supported boards:
22+
https://github.com/adafruit/circuitpython/releases
23+
24+
"""
25+
try:
26+
from typing import Optional, Union, Tuple
27+
from fontio import BuiltinFont
28+
from adafruit_bitmap_font.bdf import BDF
29+
from adafruit_bitmap_font.pcf import PCF
30+
except ImportError:
31+
pass
32+
33+
import terminalio
34+
import displayio
35+
import adafruit_imageload
36+
from adafruit_display_text.bitmap_label import Label
37+
from adafruit_imageload.tilegrid_inflator import inflate_tilegrid
38+
from adafruit_displayio_layout.layouts.page_layout import PageLayout
39+
40+
41+
class TabLayout(displayio.Group):
42+
"""
43+
A layout that organizes children into a grid table structure.
44+
45+
.. warning::
46+
Requires CircuitPython version 7.3.0-beta.2 or newer
47+
48+
:param int x: x location the layout should be placed. Pixel coordinates.
49+
:param int y: y location the layout should be placed. Pixel coordinates.
50+
:param displayio.Display display: The Display object to show the tab layout on.
51+
:param int tab_text_scale: Size of the text shown in the tabs.
52+
Whole numbers 1 and greater are valid
53+
:param Optional[Union[BuiltinFont, BDF, PCF]] custom_font: A pre-loaded font object to use
54+
for the tab labels
55+
:param str inactive_tab_spritesheet: Filepath of the spritesheet to show for inactive tabs.
56+
:param str showing_tab_spritesheet: Filepath of the spritesheet to show for the active tab.
57+
:param Optional[int, tuple[int, int, int]] showing_tab_text_color: Hex or tuple color to use
58+
for the active tab label
59+
:param Optional[int, tuple[int, int, int]] inactive_tab_text_color: Hex or tuple color to
60+
use for inactive tab labels
61+
:param Optional[Union[int, tuple[int, int]]] inactive_tab_transparent_indexes: single index
62+
or tuple of multiple indexes to be made transparent in the inactive tab sprite palette.
63+
:param Optional[Union[int, tuple[int, int]]] showing_tab_transparent_indexes: single index
64+
or tuple of multiple indexes to be made transparent in the active tab sprite palette.
65+
:param int tab_count: How many tabs to draw in the layout. Positive whole numbers are valid.
66+
"""
67+
68+
# pylint: disable=too-many-instance-attributes, too-many-arguments, invalid-name, too-many-branches
69+
70+
def __init__(
71+
self,
72+
x: int = 0,
73+
y: int = 0,
74+
display: Optional[displayio.Display] = None,
75+
tab_text_scale: int = 1,
76+
custom_font: Optional[Union[BuiltinFont, BDF, PCF]] = terminalio.FONT,
77+
inactive_tab_spritesheet: Optional[str] = None,
78+
showing_tab_spritesheet: Optional[str] = None,
79+
showing_tab_text_color: Optional[Union[int, Tuple[int, int, int]]] = 0x999999,
80+
inactive_tab_text_color: Optional[Union[int, Tuple[int, int, int]]] = 0xFFFFF,
81+
inactive_tab_transparent_indexes: Optional[Union[int, Tuple[int, int]]] = None,
82+
showing_tab_transparent_indexes: Optional[Union[int, Tuple[int, int]]] = None,
83+
tab_count: int = None,
84+
):
85+
86+
if display is None:
87+
# pylint: disable=import-outside-toplevel
88+
import board
89+
90+
if hasattr(board, "DISPLAY"):
91+
display = board.DISPLAY
92+
if inactive_tab_spritesheet is None:
93+
raise AttributeError("Must pass active_tab_spritesheet")
94+
if showing_tab_spritesheet is None:
95+
raise AttributeError("Must pass inactive_tab_spritesheet")
96+
if tab_count is None:
97+
raise AttributeError("Must pass tab_count")
98+
99+
super().__init__(x=x, y=y)
100+
self.tab_count = tab_count
101+
self._active_bmp, self._active_palette = adafruit_imageload.load(
102+
showing_tab_spritesheet
103+
)
104+
self._inactive_bmp, self._inactive_palette = adafruit_imageload.load(
105+
inactive_tab_spritesheet
106+
)
107+
108+
if isinstance(showing_tab_transparent_indexes, int):
109+
self._active_palette.make_transparent(showing_tab_transparent_indexes)
110+
elif isinstance(showing_tab_transparent_indexes, tuple):
111+
for index in showing_tab_transparent_indexes:
112+
self._active_palette.make_transparent(index)
113+
else:
114+
raise AttributeError("active_tab_transparent_indexes must be int or tuple")
115+
116+
if isinstance(inactive_tab_transparent_indexes, int):
117+
self._inactive_palette.make_transparent(inactive_tab_transparent_indexes)
118+
elif isinstance(inactive_tab_transparent_indexes, tuple):
119+
for index in inactive_tab_transparent_indexes:
120+
self._inactive_palette.make_transparent(index)
121+
else:
122+
raise AttributeError(
123+
"inactive_tab_transparent_indexes must be int or tuple"
124+
)
125+
126+
self.tab_height = self._active_bmp.height
127+
self.display = display
128+
self.active_tab_text_color = showing_tab_text_color
129+
self.inactive_tab_text_color = inactive_tab_text_color
130+
self.custom_font = custom_font
131+
self.tab_text_scale = tab_text_scale
132+
self.tab_group = displayio.Group()
133+
self.tab_dict = {}
134+
self.page_layout = PageLayout(x=x, y=y + self.tab_height)
135+
136+
self.append(self.tab_group)
137+
self.append(self.page_layout)
138+
139+
def _draw_tabs(self):
140+
for i, page_dict in enumerate(self.page_layout.page_content_list):
141+
if i not in self.tab_dict:
142+
print(f"creating tab {i}")
143+
_new_tab_group = displayio.Group()
144+
_tab_tilegrid = inflate_tilegrid(
145+
bmp_obj=self._inactive_bmp,
146+
bmp_palette=self._inactive_palette,
147+
target_size=(
148+
(self.display.width // self.tab_count)
149+
// (self._active_bmp.width // 3),
150+
3,
151+
),
152+
)
153+
154+
_tab_tilegrid.x = (self.display.width // self.tab_count) * i
155+
_new_tab_group.append(_tab_tilegrid)
156+
157+
_tab_label = Label(
158+
self.custom_font,
159+
text=page_dict["page_name"],
160+
color=self.inactive_tab_text_color,
161+
scale=self.tab_text_scale,
162+
)
163+
164+
_tab_label.anchor_point = (0.5, 0.5)
165+
_tab_label.anchored_position = (
166+
_tab_tilegrid.x
167+
+ ((_tab_tilegrid.width * _tab_tilegrid.tile_width) // 2),
168+
(_tab_tilegrid.height * _tab_tilegrid.tile_height) // 2,
169+
)
170+
_new_tab_group.append(_tab_label)
171+
172+
if i == self.page_layout.showing_page_index:
173+
try:
174+
_tab_tilegrid.bitmap = self._active_bmp
175+
except AttributeError as e:
176+
print(e)
177+
raise (
178+
AttributeError(
179+
"TabLayout requires CircuitPython version 7.3.0-beta.2 or newer."
180+
)
181+
) from e
182+
_tab_tilegrid.pixel_shader = self._active_palette
183+
_tab_label.color = self.active_tab_text_color
184+
self.tab_dict[i] = _new_tab_group
185+
self.tab_group.append(_new_tab_group)
186+
187+
def _update_active_tab(self):
188+
for i in range(len(self.page_layout)):
189+
if i == self.page_layout.showing_page_index:
190+
self.tab_group[i][0].bitmap = self._active_bmp
191+
self.tab_group[i][0].pixel_shader = self._active_palette
192+
self.tab_group[i][1].color = self.active_tab_text_color
193+
else:
194+
self.tab_group[i][0].bitmap = self._inactive_bmp
195+
self.tab_group[i][0].pixel_shader = self._inactive_palette
196+
self.tab_group[i][1].color = self.inactive_tab_text_color
197+
198+
def add_content(self, tab_content, tab_name):
199+
"""Add a child to the tab layout.
200+
201+
:param tab_content: the content for the tab typically a Group
202+
:param tab_name: the name of this tab, will be shown inside the tab
203+
204+
:return: None"""
205+
self.page_layout.add_content(tab_content, tab_name)
206+
self._draw_tabs()
207+
208+
def show_page(self, page_name=None, page_index=None):
209+
"""
210+
Show the specified page, and hide all other pages.
211+
212+
:param string page_name: The name of a page to show
213+
:param int page_index: The index of a page to show
214+
:return: None
215+
"""
216+
217+
self.page_layout.show_page(page_name=page_name, page_index=page_index)
218+
self._update_active_tab()
219+
220+
@property
221+
def showing_page_index(self):
222+
"""
223+
Index of the currently showing page
224+
:return int: showing_page_index
225+
"""
226+
return self.page_layout.showing_page_index
227+
228+
@showing_page_index.setter
229+
def showing_page_index(self, new_index):
230+
if self.showing_page_index != new_index:
231+
self.show_page(page_index=new_index)
232+
233+
@property
234+
def showing_page_name(self):
235+
"""
236+
Name of the currently showing page
237+
:return string: showing_page_name
238+
"""
239+
return self.page_layout.showing_page_name
240+
241+
@showing_page_name.setter
242+
def showing_page_name(self, new_name):
243+
self.show_page(page_name=new_name)
244+
245+
@property
246+
def showing_page_content(self):
247+
"""
248+
The content object for the currently showing page
249+
:return Displayable: showing_page_content
250+
"""
251+
return self.page_layout.showing_page_content
252+
253+
def next_page(self, loop=True):
254+
"""
255+
Hide the current page and show the next one in the list by index
256+
:param bool loop: whether to loop from the last page back to the first
257+
:return: None
258+
"""
259+
260+
self.page_layout.next_page(loop=loop)
261+
self._update_active_tab()
262+
263+
def previous_page(self, loop=True):
264+
"""
265+
Hide the current page and show the previous one in the list by index
266+
:param bool loop: whether to loop from the first page to the last one
267+
:return: None
268+
"""
269+
self.page_layout.previous_page(loop=loop)
270+
self._update_active_tab()
271+
272+
def handle_touch_events(self, touch_event):
273+
"""
274+
Check if the touch event is on the tabs and if so change to the touched tab.
275+
276+
:param tuple touch_event: tuple containing x and y coordinates of the
277+
touch event in indexes 0 and 1.
278+
:return: None
279+
"""
280+
281+
if touch_event:
282+
if 0 <= touch_event[1] <= self.tab_height:
283+
284+
touched_tab_index = touch_event[0] // (
285+
self.display.width // self.tab_count
286+
)
287+
print(f"{touch_event[0]} - {touched_tab_index}")
288+
self.showing_page_index = touched_tab_index

0 commit comments

Comments
 (0)