Skip to content
Snippets Groups Projects
Verified Commit 7d32047b authored by rahix's avatar rahix
Browse files

refactor(menu.py): Use simple_menu module


Signed-off-by: default avatarRahix <rahix@rahix.de>
parent b5dfc265
No related branches found
No related tags found
No related merge requests found
...@@ -5,286 +5,91 @@ You can customize this script however you want :) If you want to go back to ...@@ -5,286 +5,91 @@ You can customize this script however you want :) If you want to go back to
the default version, just delete this file; the firmware will recreate it on the default version, just delete this file; the firmware will recreate it on
next run. next run.
""" """
import buttons import collections
import color import color
import display import display
import os import os
import utime import simple_menu
import ujson
import sys import sys
import ujson
import utime
BUTTON_TIMER_POPPED = -1 App = collections.namedtuple("App", ["name", "path"])
COLOR_BG = color.CHAOSBLUE_DARK
COLOR_BG_SEL = color.CHAOSBLUE
COLOR_ARROW = color.COMMYELLOW
COLOR_TEXT = color.COMMYELLOW
MAXCHARS = 11
HOMEAPP = "main.py"
def create_folders():
try:
os.mkdir("/apps")
except:
pass
def read_metadata(app_folder):
try:
info_file = "/apps/%s/metadata.json" % (app_folder)
with open(info_file) as f:
information = f.read()
return ujson.loads(information)
except Exception as e:
print("Failed to read metadata for %s" % (app_folder))
sys.print_exception(e)
return {
"author": "",
"name": app_folder,
"description": "",
"category": "",
"revision": 0,
}
def list_apps(): def enumerate_apps():
"""Create a list of available apps.""" """List all installed apps."""
apps = [] for f in os.listdir("/"):
if f == "main.py":
yield App("Home", f)
# add main application for app in sorted(os.listdir("/apps")):
for mainFile in os.listdir("/"): if app.startswith("."):
if mainFile == HOMEAPP: continue
apps.append(
[
"/%s" % HOMEAPP,
{
"author": "card10badge Team",
"name": "Home",
"description": "",
"category": "",
"revision": 0,
},
]
)
dirlist = [
entry for entry in sorted(os.listdir("/apps")) if not entry.startswith(".")
]
# list all hatchary style apps (not .elf and not .py) if app.endswith(".py") or app.endswith(".elf"):
# with or without metadata.json yield App(app, "/apps/" + app)
for appFolder in dirlist: continue
if not (appFolder.endswith(".py") or appFolder.endswith(".elf")):
metadata = read_metadata(appFolder)
if not metadata.get("bin", None):
fileName = "/apps/%s/__init__.py" % appFolder
else:
fileName = "/apps/%s/%s" % (appFolder, metadata["bin"])
apps.append([fileName, metadata])
# list simple python scripts try:
for pyFile in dirlist: with open("/apps/" + app + "/metadata.json") as f:
if pyFile.endswith(".py"): info = ujson.load(f)
apps.append(
[
"/apps/%s" % pyFile,
{
"author": "",
"name": pyFile,
"description": "",
"category": "",
"revision": 0,
},
]
)
# list simple elf binaries yield App(
for elfFile in dirlist: info["name"], "/apps/{}/{}".format(app, info.get("bin", "__init__.py"))
if elfFile.endswith(".elf"):
apps.append(
[
"/apps/%s" % elfFile,
{
"author": "",
"name": elfFile,
"description": "",
"category": "",
"revision": 0,
},
]
) )
except Exception as e:
print("Could not load /apps/{}/metadata.json!".format(app))
sys.print_exception(e)
class MainMenu(simple_menu.Menu):
timeout = 30.0
def entry2name(self, app):
return app.name
def on_select(self, app, index):
self.disp.clear().update()
try:
print("Trying to load " + app.path)
os.exec(app.path)
except OSError as e:
print("Loading failed: ")
sys.print_exception(e)
self.error("Loading", "failed")
utime.sleep(1.0)
os.exit(1)
def on_timeout(self):
try:
f = open("main.py")
f.close()
os.exec("main.py")
except OSError:
pass
def no_apps_message():
"""Display a warning if no apps are installed."""
with display.open() as disp:
disp.clear(color.COMMYELLOW)
disp.print(
" No apps ", posx=17, posy=20, fg=color.COMMYELLOW_DARK, bg=color.COMMYELLOW
)
disp.print(
"available", posx=17, posy=40, fg=color.COMMYELLOW_DARK, bg=color.COMMYELLOW
)
disp.update()
return apps
def button_events(timeout=0):
"""Iterate over button presses (event-loop)."""
yield 0
button_pressed = False
count = 0
while True: while True:
v = buttons.read(buttons.BOTTOM_LEFT | buttons.BOTTOM_RIGHT | buttons.TOP_RIGHT) utime.sleep(0.5)
if timeout > 0 and count > 0 and count % timeout == 0:
yield BUTTON_TIMER_POPPED
if timeout > 0:
count += 1
if v == 0:
button_pressed = False
if not button_pressed and v & buttons.BOTTOM_LEFT != 0:
button_pressed = True
yield buttons.BOTTOM_LEFT
if not button_pressed and v & buttons.BOTTOM_RIGHT != 0:
button_pressed = True
yield buttons.BOTTOM_RIGHT
if not button_pressed and v & buttons.TOP_RIGHT != 0:
button_pressed = True
yield buttons.TOP_RIGHT
utime.sleep_ms(10)
def triangle(disp, x, y, left, scale=6, color=[255, 0, 0]):
"""Draw a triangle to show there's more text in this line"""
yf = 1 if left else -1
disp.line(x - scale * yf, int(y + scale / 2), x, y, col=color)
disp.line(x, y, x, y + scale, col=color)
disp.line(x, y + scale, x - scale * yf, y + int(scale / 2), col=color)
if __name__ == "__main__":
apps = list(enumerate_apps())
def draw_menu(disp, applist, pos, appcount, lineoffset): if apps == []:
disp.clear() no_apps_message()
start = 0
if pos > 0:
start = pos - 1
if start + 4 > appcount:
start = appcount - 4
if start < 0:
start = 0
for i, app in enumerate(applist):
if i >= start + 4 or i >= appcount:
break
if i >= start:
disp.rect(
0,
(i - start) * 20,
159,
(i - start) * 20 + 20,
col=COLOR_BG_SEL if i == pos else COLOR_BG,
)
line = app[1]["name"]
linelength = len(line)
off = 0
# calc line offset for scrolling
if i == pos and linelength > (MAXCHARS - 1) and lineoffset > 0:
off = (
lineoffset
if lineoffset + (MAXCHARS - 1) < linelength
else linelength - (MAXCHARS - 1)
)
if lineoffset > linelength:
off = 0
disp.print(
" " + line[off : (off + (MAXCHARS - 1))],
posy=(i - start) * 20,
fg=COLOR_TEXT,
bg=COLOR_BG_SEL if i == pos else COLOR_BG,
)
if i == pos:
disp.print(">", posy=(i - start) * 20, fg=COLOR_ARROW, bg=COLOR_BG_SEL)
if linelength > (MAXCHARS - 1) and off < linelength - (MAXCHARS - 1):
triangle(disp, 153, (i - start) * 20 + 6, False, 6)
triangle(disp, 154, (i - start) * 20 + 7, False, 4)
triangle(disp, 155, (i - start) * 20 + 8, False, 2)
if off > 0:
triangle(disp, 24, (i - start) * 20 + 6, True, 6)
triangle(disp, 23, (i - start) * 20 + 7, True, 4)
triangle(disp, 22, (i - start) * 20 + 8, True, 2)
disp.update()
def main():
create_folders()
disp = display.open()
applist = list_apps()
numapps = len(applist)
current = 0
lineoffset = 0
timerscrollspeed = 1
timerstartscroll = 5
timercountpopped = 0
timerinactivity = 100
for ev in button_events(10):
if numapps == 0:
disp.clear(COLOR_BG)
disp.print(" No apps ", posx=17, posy=20, fg=COLOR_TEXT, bg=COLOR_BG)
disp.print("available", posx=17, posy=40, fg=COLOR_TEXT, bg=COLOR_BG)
disp.update()
continue
if ev == buttons.BOTTOM_RIGHT:
# Scroll down
current = (current + 1) % numapps
lineoffset = 0
timercountpopped = 0
elif ev == buttons.BOTTOM_LEFT:
# Scroll up
current = (current + numapps - 1) % numapps
lineoffset = 0
timercountpopped = 0
elif ev == BUTTON_TIMER_POPPED:
timercountpopped += 1
if (
timercountpopped >= timerstartscroll
and (timercountpopped - timerstartscroll) % timerscrollspeed == 0
):
lineoffset += 1
if applist[0][0] == "/%s" % HOMEAPP and timercountpopped >= timerinactivity:
print("Inactivity timer popped")
disp.clear().update()
disp.close()
try:
os.exec("/%s" % HOMEAPP)
except OSError as e:
print("Loading failed: ", e)
os.exit(1)
elif ev == buttons.TOP_RIGHT:
# Select & start
disp.clear().update()
disp.close()
try:
os.exec(applist[current][0])
except OSError as e:
print("Loading failed: ", e)
os.exit(1)
draw_menu(disp, applist, current, numapps, lineoffset)
if __name__ == "__main__": MainMenu(apps).run()
try:
main()
except Exception as e:
sys.print_exception(e)
with display.open() as d:
d.clear(color.COMMYELLOW)
d.print("Menu", posx=52, posy=20, fg=color.COMMYELLOW_DARK, bg=color.COMMYELLOW)
d.print("crashed", posx=31, posy=40, fg=color.COMMYELLOW_DARK, bg=color.COMMYELLOW)
d.update()
utime.sleep(2)
os.exit(1)
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment