Adafruit_Learning_System_Gu.../CircuitPython_Touch_Deck/code.py
2021-03-21 20:33:54 -05:00

305 lines
No EOL
11 KiB
Python

# SPDX-FileCopyrightText: 2020 Tim C, written for Adafruit Industries
#
# SPDX-License-Identifier: Unlicense
"""
This version runs on Feather RP2040 with a 3.5" FeatherWing
"""
import time
import displayio
import terminalio
from adafruit_display_text import label, bitmap_label
from adafruit_displayio_layout.layouts.grid_layout import GridLayout
from touch_deck_layers import touch_deck_config, KEY, STRING, MEDIA, KEY_PRESS, KEY_RELEASE
import usb_hid
from adafruit_hid.keyboard import Keyboard
from adafruit_hid.keyboard_layout_us import KeyboardLayoutUS
from adafruit_hid.consumer_control import ConsumerControl
from adafruit_displayio_layout.widgets.icon_widget import IconWidget
from adafruit_featherwing import tft_featherwing_35
# seems to help the touchscreen not get stuck with chip not found
time.sleep(3)
# display and touchscreen initialization
displayio.release_displays()
tft_featherwing = tft_featherwing_35.TFTFeatherWing35()
display = tft_featherwing.display
touchscreen = tft_featherwing.touchscreen
# HID setup
kbd = Keyboard(usb_hid.devices)
cc = ConsumerControl(usb_hid.devices)
kbd_layout = KeyboardLayoutUS(kbd)
# variables to envorce timout between icon presses
COOLDOWN_TIME = 0.5
LAST_PRESS_TIME = -1
# 'mock' icon indexes for the layer buttons
# used for debouncing
PREV_LAYER_INDEX = -1
NEXT_LAYER_INDEX = -2
HOME_LAYER_INDEX = -3
# start on first layer
current_layer = 0
# Make the main_group to hold everything
main_group = displayio.Group(max_size=10)
display.show(main_group)
# loading screen
loading_group = displayio.Group()
# black background, screen size minus side buttons
loading_background = displayio.Bitmap((display.width-40)//20, display.height//20, 1)
loading_palette = displayio.Palette(1)
loading_palette[0] = 0x0
# scaled group to match screen size minus side buttons
loading_background_scale_group = displayio.Group(scale=20)
loading_background_tilegrid = displayio.TileGrid(loading_background, pixel_shader=loading_palette)
loading_background_scale_group.append(loading_background_tilegrid)
# loading screen label
loading_label = bitmap_label.Label(terminalio.FONT, text="Loading...", scale=3)
loading_label.anchor_point = (0.5, 0.5)
loading_label.anchored_position = (display.width // 2, display.height // 2)
# append background and label to the group
loading_group.append(loading_background_scale_group)
loading_group.append(loading_label)
# GridLayout to hold the icons
# size and location can be adjusted to fit
# different sized screens.
layout = GridLayout(
x=20,
y=20,
width=420,
height=290,
grid_size=(4, 3),
cell_padding=6,
max_size=20,
)
# list that holds the IconWidget objects for each icon.
_icons = []
# list that holds indexes of currently pressed icons and layer buttons
# used for debouncing
_pressed_icons = []
# layer label at the top of the screen
layer_label = bitmap_label.Label(terminalio.FONT)
layer_label.anchor_point = (0.5, 0.0)
layer_label.anchored_position = (display.width // 2, 4)
main_group.append(layer_label)
# right side layer buttons
next_layer_btn = IconWidget(
"",
"touch_deck_icons/layer_next.bmp",
on_disk=True
)
next_layer_btn.x = display.width - 40
next_layer_btn.y = display.height - 100
next_layer_btn.resize = (40, 100)
main_group.append(next_layer_btn)
prev_layer_btn = IconWidget(
"",
"touch_deck_icons/layer_prev.bmp",
on_disk=True
)
prev_layer_btn.x = display.width - 40
prev_layer_btn.y = 110
prev_layer_btn.resize = (40, 100)
main_group.append(prev_layer_btn)
home_layer_btn = IconWidget(
"",
"touch_deck_icons/layer_home.bmp",
on_disk=True
)
home_layer_btn.x = display.width - 40
home_layer_btn.y = 0
home_layer_btn.resize = (40, 100)
main_group.append(home_layer_btn)
# helper method to laod icons for an index by its index in the
# list of layers
def load_layer(layer_index):
# show the loading screen
main_group.append(loading_group)
time.sleep(0.05)
# resets icon lists to empty
global _icons
_icons = []
layout._cell_content_list = []
# remove previous layer icons from the layout
while len(layout) > 0:
layout.pop()
# set the layer labed at the top of the screen
layer_label.text = touch_deck_config["layers"][layer_index]["name"]
# loop over each shortcut and it's index
for i, shortcut in enumerate(touch_deck_config["layers"][layer_index]["shortcuts"]):
# create an icon for the current shortcut
_new_icon = IconWidget(shortcut["label"], shortcut["icon"], on_disk=True)
# add it to the list of icons
_icons.append(_new_icon)
# add it to the grid layout
# calculate it's position from the index
layout.add_content(_new_icon, grid_position=(i % 4, i // 4), cell_size=(1, 1))
# hide the loading screen
time.sleep(0.05)
main_group.pop()
# append the grid layout to the main_group
# so it gets shown on the display
main_group.append(layout)
# load the first layer to start
load_layer(current_layer)
# main loop
while True:
if touchscreen.touched:
# loop over all data in touchscreen buffer
while not touchscreen.buffer_empty:
touches = touchscreen.touches
# loop over all points touched
for point in touches:
if point:
# current time, used for timeout between icon presses
_now = time.monotonic()
# if the timeout has passed
if _now - LAST_PRESS_TIME > COOLDOWN_TIME:
# print(point)
# map the observed minimum and maximum touch values
# to the screen size
y = point["y"] - 250
x = 4096 - point["x"] - 250
y = y * display.width // (3820 - 250)
x = x * display.height // (3820 - 250)
# touch data is 90 degrees rotated
# flip x, and y here to account for that
p = (y, x)
# print(p)
# Next layer button pressed
if next_layer_btn.contains(p) and NEXT_LAYER_INDEX not in _pressed_icons:
# increment layer
current_layer += 1
# wrap back to beginning from end
if current_layer >= len(touch_deck_config["layers"]):
current_layer = 0
# load the new layer
load_layer(current_layer)
# save current time to check for timeout
LAST_PRESS_TIME = _now
# append this index to pressed icons for debouncing
_pressed_icons.append(NEXT_LAYER_INDEX)
# home layer button pressed
if home_layer_btn.contains(p) and HOME_LAYER_INDEX not in _pressed_icons:
# 0 index is home layer
current_layer = 0
# load the home layer
load_layer(current_layer)
# save current time to check for timeout
LAST_PRESS_TIME = _now
# append this index to pressed icons for debouncing
_pressed_icons.append(HOME_LAYER_INDEX)
# Previous layer button pressed
if prev_layer_btn.contains(p) and PREV_LAYER_INDEX not in _pressed_icons:
# decrement layer
current_layer -= 1
# wrap back to end from beginning
if current_layer < 0:
current_layer = len(touch_deck_config["layers"]) - 1
# load the new layer
load_layer(current_layer)
# save current time to check for timeout
LAST_PRESS_TIME = _now
# append this index to pressed icons for debouncing
_pressed_icons.append(PREV_LAYER_INDEX)
# loop over current layer icons and their indexes
for index, icon_shortcut in enumerate(_icons):
# if this icon was pressed
if icon_shortcut.contains(p):
# debounce logic, check that it wasn't already pressed
if index not in _pressed_icons:
# print("pressed {}".format(index))
# get actions for this icon from config object
_cur_actions = touch_deck_config["layers"][current_layer]["shortcuts"][index][
"actions"]
# tuple means it's a single action
if isinstance(_cur_actions, tuple):
# put it in a list by itself
_cur_actions = [_cur_actions]
# loop over the actions
for _action in _cur_actions:
# HID keyboard keys
if _action[0] == KEY:
kbd.press(*_action[1])
kbd.release(*_action[1])
# String to write from layout
elif _action[0] == STRING:
kbd_layout.write(_action[1])
# Consumer control code
elif _action[0] == MEDIA:
cc.send(_action[1])
# Key press
elif _action[0] == KEY_PRESS:
kbd.press(*_action[1])
# Key release
elif _action[0] == KEY_RELEASE:
kbd.release(*_action[1])
# if there are multiple actions
if len(_cur_actions) > 1:
# small sleep to make sure
# OS can respond to previous action
time.sleep(0.2)
# save current time to check for timeout
LAST_PRESS_TIME = _now
# append this index to pressed icons for debouncing
_pressed_icons.append(index)
else: # screen not touched
# empty the pressed icons list
_pressed_icons.clear()