Skip to content

Tab layout #70

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 7 commits into from
May 10, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 14 additions & 14 deletions adafruit_displayio_layout/layouts/page_layout.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,11 +53,11 @@ def __init__(
self.x = x
self.y = y

self._page_content_list = []
self.page_content_list = []
self._cur_showing_index = 0

def add_content(self, page_content, page_name=None):
"""Add a child to the grid.
"""Add a child to the page layout.

:param page_content: the content for the page typically a Group
:param page_name: the name of this page
Expand All @@ -72,10 +72,10 @@ def add_content(self, page_content, page_name=None):
"page_name": page_name,
}

if len(self._page_content_list) > 0:
if len(self.page_content_list) > 0:
_page_group.hidden = True

self._page_content_list.append(sub_view_obj)
self.page_content_list.append(sub_view_obj)
self.append(_page_group)

def _check_args(self, page_name, page_index):
Expand All @@ -95,16 +95,16 @@ def _check_args(self, page_name, page_index):
)

if page_index is not None:
if page_index >= len(self._page_content_list):
if page_index >= len(self.page_content_list):
raise KeyError(
"KeyError at index {} in list length {}".format(
page_index, len(self._page_content_list)
page_index, len(self.page_content_list)
),
)

if page_name is not None:
_found = False
for page in self._page_content_list:
for page in self.page_content_list:
if not _found:
if page_name == page["page_name"]:
_found = True
Expand All @@ -125,10 +125,10 @@ def get_page(self, page_name=None, page_index=None):
self._check_args(page_name, page_index)

if page_index is not None:
return self._page_content_list[page_index]
return self.page_content_list[page_index]

if page_name is not None:
for cell in self._page_content_list:
for cell in self.page_content_list:
if cell["page_name"] == page_name:
return cell

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

self._check_args(page_name, page_index)

for cur_index, page in enumerate(self._page_content_list):
for cur_index, page in enumerate(self.page_content_list):
if page_name is not None:
if page["page_name"] == page_name:
self._cur_showing_index = cur_index
Expand Down Expand Up @@ -182,7 +182,7 @@ def showing_page_name(self):
Name of the currently showing page
:return string: showing_page_name
"""
return self._page_content_list[self._cur_showing_index]["page_name"]
return self.page_content_list[self._cur_showing_index]["page_name"]

@showing_page_name.setter
def showing_page_name(self, new_name):
Expand All @@ -194,7 +194,7 @@ def showing_page_content(self):
The content object for the currently showing page
:return Displayable: showing_page_content
"""
return self._page_content_list[self._cur_showing_index]["content"][0]
return self.page_content_list[self._cur_showing_index]["content"][0]

def next_page(self, loop=True):
"""
Expand All @@ -203,7 +203,7 @@ def next_page(self, loop=True):
:return: None
"""

if self._cur_showing_index + 1 < len(self._page_content_list):
if self._cur_showing_index + 1 < len(self.page_content_list):
self.show_page(page_index=self._cur_showing_index + 1)
else:
if not loop:
Expand All @@ -223,4 +223,4 @@ def previous_page(self, loop=True):
if not loop:
print("No more pages")
else:
self.show_page(page_index=len(self._page_content_list) - 1)
self.show_page(page_index=len(self.page_content_list) - 1)
288 changes: 288 additions & 0 deletions adafruit_displayio_layout/layouts/tab_layout.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,288 @@
# SPDX-FileCopyrightText: 2022 Tim Cocks
#
# SPDX-License-Identifier: MIT

"""
`tab_layout`
================================================================================

A layout that organizes pages into tabs.


* Author(s): Tim Cocks

Implementation Notes
--------------------

**Hardware:**

**Software and Dependencies:**

* Adafruit CircuitPython firmware for the supported boards:
https://github.com/adafruit/circuitpython/releases

"""
try:
from typing import Optional, Union, Tuple
from fontio import BuiltinFont
from adafruit_bitmap_font.bdf import BDF
from adafruit_bitmap_font.pcf import PCF
except ImportError:
pass

import terminalio
import displayio
import adafruit_imageload
from adafruit_display_text.bitmap_label import Label
from adafruit_imageload.tilegrid_inflator import inflate_tilegrid
from adafruit_displayio_layout.layouts.page_layout import PageLayout


class TabLayout(displayio.Group):
"""
A layout that organizes children into a grid table structure.

.. warning::
Requires CircuitPython version 7.3.0-beta.2 or newer

:param int x: x location the layout should be placed. Pixel coordinates.
:param int y: y location the layout should be placed. Pixel coordinates.
:param displayio.Display display: The Display object to show the tab layout on.
:param int tab_text_scale: Size of the text shown in the tabs.
Whole numbers 1 and greater are valid
:param Optional[Union[BuiltinFont, BDF, PCF]] custom_font: A pre-loaded font object to use
for the tab labels
:param str inactive_tab_spritesheet: Filepath of the spritesheet to show for inactive tabs.
:param str showing_tab_spritesheet: Filepath of the spritesheet to show for the active tab.
:param Optional[int, tuple[int, int, int]] showing_tab_text_color: Hex or tuple color to use
for the active tab label
:param Optional[int, tuple[int, int, int]] inactive_tab_text_color: Hex or tuple color to
use for inactive tab labels
:param Optional[Union[int, tuple[int, int]]] inactive_tab_transparent_indexes: single index
or tuple of multiple indexes to be made transparent in the inactive tab sprite palette.
:param Optional[Union[int, tuple[int, int]]] showing_tab_transparent_indexes: single index
or tuple of multiple indexes to be made transparent in the active tab sprite palette.
:param int tab_count: How many tabs to draw in the layout. Positive whole numbers are valid.
"""

# pylint: disable=too-many-instance-attributes, too-many-arguments, invalid-name, too-many-branches

def __init__(
self,
x: int = 0,
y: int = 0,
display: Optional[displayio.Display] = None,
tab_text_scale: int = 1,
custom_font: Optional[Union[BuiltinFont, BDF, PCF]] = terminalio.FONT,
inactive_tab_spritesheet: Optional[str] = None,
showing_tab_spritesheet: Optional[str] = None,
showing_tab_text_color: Optional[Union[int, Tuple[int, int, int]]] = 0x999999,
inactive_tab_text_color: Optional[Union[int, Tuple[int, int, int]]] = 0xFFFFF,
inactive_tab_transparent_indexes: Optional[Union[int, Tuple[int, int]]] = None,
showing_tab_transparent_indexes: Optional[Union[int, Tuple[int, int]]] = None,
tab_count: int = None,
):

if display is None:
# pylint: disable=import-outside-toplevel
import board

if hasattr(board, "DISPLAY"):
display = board.DISPLAY
if inactive_tab_spritesheet is None:
raise AttributeError("Must pass active_tab_spritesheet")
if showing_tab_spritesheet is None:
raise AttributeError("Must pass inactive_tab_spritesheet")
if tab_count is None:
raise AttributeError("Must pass tab_count")

super().__init__(x=x, y=y)
self.tab_count = tab_count
self._active_bmp, self._active_palette = adafruit_imageload.load(
showing_tab_spritesheet
)
self._inactive_bmp, self._inactive_palette = adafruit_imageload.load(
inactive_tab_spritesheet
)

if isinstance(showing_tab_transparent_indexes, int):
self._active_palette.make_transparent(showing_tab_transparent_indexes)
elif isinstance(showing_tab_transparent_indexes, tuple):
for index in showing_tab_transparent_indexes:
self._active_palette.make_transparent(index)
else:
raise AttributeError("active_tab_transparent_indexes must be int or tuple")

if isinstance(inactive_tab_transparent_indexes, int):
self._inactive_palette.make_transparent(inactive_tab_transparent_indexes)
elif isinstance(inactive_tab_transparent_indexes, tuple):
for index in inactive_tab_transparent_indexes:
self._inactive_palette.make_transparent(index)
else:
raise AttributeError(
"inactive_tab_transparent_indexes must be int or tuple"
)

self.tab_height = self._active_bmp.height
self.display = display
self.active_tab_text_color = showing_tab_text_color
self.inactive_tab_text_color = inactive_tab_text_color
self.custom_font = custom_font
self.tab_text_scale = tab_text_scale
self.tab_group = displayio.Group()
self.tab_dict = {}
self.page_layout = PageLayout(x=x, y=y + self.tab_height)

self.append(self.tab_group)
self.append(self.page_layout)

def _draw_tabs(self):
for i, page_dict in enumerate(self.page_layout.page_content_list):
if i not in self.tab_dict:
print(f"creating tab {i}")
_new_tab_group = displayio.Group()
_tab_tilegrid = inflate_tilegrid(
bmp_obj=self._inactive_bmp,
bmp_palette=self._inactive_palette,
target_size=(
(self.display.width // self.tab_count)
// (self._active_bmp.width // 3),
3,
),
)

_tab_tilegrid.x = (self.display.width // self.tab_count) * i
_new_tab_group.append(_tab_tilegrid)

_tab_label = Label(
self.custom_font,
text=page_dict["page_name"],
color=self.inactive_tab_text_color,
scale=self.tab_text_scale,
)

_tab_label.anchor_point = (0.5, 0.5)
_tab_label.anchored_position = (
_tab_tilegrid.x
+ ((_tab_tilegrid.width * _tab_tilegrid.tile_width) // 2),
(_tab_tilegrid.height * _tab_tilegrid.tile_height) // 2,
)
_new_tab_group.append(_tab_label)

if i == self.page_layout.showing_page_index:
try:
_tab_tilegrid.bitmap = self._active_bmp
except AttributeError as e:
print(e)
raise (
AttributeError(
"TabLayout requires CircuitPython version 7.3.0-beta.2 or newer."
)
) from e
_tab_tilegrid.pixel_shader = self._active_palette
_tab_label.color = self.active_tab_text_color
self.tab_dict[i] = _new_tab_group
self.tab_group.append(_new_tab_group)

def _update_active_tab(self):
for i in range(len(self.page_layout)):
if i == self.page_layout.showing_page_index:
self.tab_group[i][0].bitmap = self._active_bmp
self.tab_group[i][0].pixel_shader = self._active_palette
self.tab_group[i][1].color = self.active_tab_text_color
else:
self.tab_group[i][0].bitmap = self._inactive_bmp
self.tab_group[i][0].pixel_shader = self._inactive_palette
self.tab_group[i][1].color = self.inactive_tab_text_color

def add_content(self, tab_content, tab_name):
"""Add a child to the tab layout.

:param tab_content: the content for the tab typically a Group
:param tab_name: the name of this tab, will be shown inside the tab

:return: None"""
self.page_layout.add_content(tab_content, tab_name)
self._draw_tabs()

def show_page(self, page_name=None, page_index=None):
"""
Show the specified page, and hide all other pages.

:param string page_name: The name of a page to show
:param int page_index: The index of a page to show
:return: None
"""

self.page_layout.show_page(page_name=page_name, page_index=page_index)
self._update_active_tab()

@property
def showing_page_index(self):
"""
Index of the currently showing page
:return int: showing_page_index
"""
return self.page_layout.showing_page_index

@showing_page_index.setter
def showing_page_index(self, new_index):
if self.showing_page_index != new_index:
self.show_page(page_index=new_index)

@property
def showing_page_name(self):
"""
Name of the currently showing page
:return string: showing_page_name
"""
return self.page_layout.showing_page_name

@showing_page_name.setter
def showing_page_name(self, new_name):
self.show_page(page_name=new_name)

@property
def showing_page_content(self):
"""
The content object for the currently showing page
:return Displayable: showing_page_content
"""
return self.page_layout.showing_page_content

def next_page(self, loop=True):
"""
Hide the current page and show the next one in the list by index
:param bool loop: whether to loop from the last page back to the first
:return: None
"""

self.page_layout.next_page(loop=loop)
self._update_active_tab()

def previous_page(self, loop=True):
"""
Hide the current page and show the previous one in the list by index
:param bool loop: whether to loop from the first page to the last one
:return: None
"""
self.page_layout.previous_page(loop=loop)
self._update_active_tab()

def handle_touch_events(self, touch_event):
"""
Check if the touch event is on the tabs and if so change to the touched tab.

:param tuple touch_event: tuple containing x and y coordinates of the
touch event in indexes 0 and 1.
:return: None
"""

if touch_event:
if 0 <= touch_event[1] <= self.tab_height:

touched_tab_index = touch_event[0] // (
self.display.width // self.tab_count
)
print(f"{touch_event[0]} - {touched_tab_index}")
self.showing_page_index = touched_tab_index
Loading