wasp: launcher: Experimental launcher implementation

It is not really the launcher itself that is immature. Rather that the
framework and UI concepts to move between applications isn't complete
yet.
This commit is contained in:
Daniel Thompson 2020-04-06 22:03:05 +01:00
parent 59bb70fa64
commit 8ed80eeeba
13 changed files with 129 additions and 8 deletions

BIN
res/app_icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.0 KiB

BIN
res/clock_icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 965 B

BIN
res/down_arrow.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 599 B

BIN
res/settings_icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

BIN
res/torch_icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 820 B

BIN
res/up_arrow.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 593 B

View File

@ -3,6 +3,7 @@
import wasp
import icons
import fonts.clock as digits
DIGITS = (
@ -25,6 +26,8 @@ class ClockApp():
Shows a time (as HH:MM) together with a battery meter and the date.
"""
NAME = 'Clock'
ICON = icons.clock
def __init__(self):
self.meter = wasp.widgets.BatteryMeter()

View File

@ -3,11 +3,15 @@
import wasp
import icons
class FlashlightApp(object):
"""Trivial flashlight application.
Shows a pure white screen with the backlight set to maximum.
"""
NAME = 'Torch'
ICON = icons.torch
def foreground(self):
"""Activate the application."""

80
wasp/apps/launcher.py Normal file
View File

@ -0,0 +1,80 @@
# SPDX-License-Identifier: LGPL-3.0-or-later
# Copyright (C) 2020 Daniel Thompson
import wasp
import icons
class LauncherApp():
"""An application launcher application.
"""
NAME = 'Launcher'
ICON = icons.app
def foreground(self):
"""Activate the application."""
self._page = 0
self._draw()
wasp.system.request_event(wasp.EventMask.TOUCH |
wasp.EventMask.SWIPE_UPDOWN)
def swipe(self, event):
i = self._page
n = self._num_pages
if event[0] == wasp.EventType.UP:
i += 1
if i >= n:
i -= 1
wasp.watch.vibrator.pulse()
return
else:
i -= 1
if i < 0:
wasp.system.switch(wasp.system.applications[0])
return
self._page = i
wasp.watch.display.mute(True)
self._draw()
wasp.watch.display.mute(False)
def touch(self, event):
page = self._get_page(self._page)
x = event[1]
y = event[2]
app = page[2 * (y // 120) + (x // 120)]
if app:
wasp.system.switch(app)
else:
wasp.watch.vibrator.pulse()
@property
def _num_pages(self):
"""Work out what the highest possible pages it."""
num_apps = len(wasp.system.applications)
return (num_apps + 3) // 4
def _get_page(self, i):
apps = wasp.system.applications
page = apps[4*i: 4*(i+1)]
while len(page) < 4:
page.append(None)
return page
def _draw(self):
"""Redraw the display from scratch."""
def draw_app(app, x, y):
if not app:
return
draw.set_color(0xffff)
draw.rleblit(app.ICON, (x+13, y+12))
draw.set_color(0xbdb6)
draw.string(app.NAME, x, y+120-30, 120)
draw = wasp.watch.drawable
page = self._get_page(self._page)
draw.fill()
draw_app(page[0], 0, 0)
draw_app(page[1], 120, 0)
draw_app(page[2], 0, 120)
draw_app(page[3], 120, 120)

View File

@ -3,10 +3,13 @@
import machine
import wasp
import icons
class TestApp():
"""Simple test application.
"""
NAME = 'Self Test'
ICON = icons.app
def __init__(self):
self.tests = ('Touch', 'String', 'Button', 'Crash')
@ -57,6 +60,7 @@ class TestApp():
def benchmark_string(self):
draw = wasp.watch.drawable
draw.fill(0, 0, 30, 240, 240-30)
self.scroll.draw()
t = machine.Timer(id=1, period=8000000)
t.start()
draw.string("The quick brown", 12, 24+24)

View File

@ -5,8 +5,9 @@ freeze('.', 'watch.py', opt=3)
freeze('../..',
(
'apps/clock.py',
'apps/testapp.py',
'apps/flashlight.py',
'apps/launcher.py',
'apps/testapp.py',
'boot.py',
'demo.py',
'draw565.py',

View File

@ -4,6 +4,15 @@
# 1-bit RLE, generated from res/battery.png, 189 bytes
battery = (36, 48, b'\x97\x0e\x14\x12\x11\x14\x10\x14\x0c\x08\x0c\x08\x08\x08\x0c\x08\x08\x08\x0c\x08\x08\x08\x0c\x08\x08\x04\x14\x04\x08\x04\x14\x04\x08\x04\x0c\x04\x04\x04\x08\x04\x0b\x05\x04\x04\x08\x04\n\x06\x04\x04\x08\x04\t\x07\x04\x04\x08\x04\x08\x07\x05\x04\x08\x04\x07\x07\x06\x04\x08\x04\x06\x07\x07\x04\x08\x04\x05\x07\x08\x04\x08\x04\x04\x0e\x02\x04\x08\x04\x03\x0f\x02\x04\x08\x04\x02\x10\x02\x04\x08\x04\x02\x10\x02\x04\x08\x04\x02\x0f\x03\x04\x08\x04\x02\x0e\x04\x04\x08\x04\x08\x07\x05\x04\x08\x04\x07\x07\x06\x04\x08\x04\x06\x07\x07\x04\x08\x04\x05\x07\x08\x04\x08\x04\x04\x07\t\x04\x08\x04\x04\x06\n\x04\x08\x04\x04\x05\x0b\x04\x08\x04\x04\x04\x0c\x04\x08\x04\x14\x04\x08\x04\x14\x04\x08\x04\x14\x04\x08\x04\x14\x04\x08\x1c\x08\x1c\x08\x1c\x08\x1c\x98')
# 1-bit RLE, generated from res/app_icon.png, 441 bytes
app = (96, 64, b'\x1e$<$<$;&\x97,20/2-4,\x03.\x03,\x03.\x03,\x03.\x03,\x03.\x03,\x03.\x03,\x03\x0c\x03\x10\x03\x0c\x03,\x03\n\x07\x0c\x07\n\x03,\x03\t\x03\x02\x04\n\x04\x02\x03\t\x03,\x03\x08\x02\x07\x02\x08\x02\x07\x02\x08\x03,\x03\x07\x02\t\x02\x06\x02\t\x02\x07\x03,\x03\x06\x02\x0b\x02\x04\x02\x0b\x02\x06\x03,\x03\x06\x02\x0b\x02\x04\x02\x0b\x02\x06\x03,\x03\x05\x02\x0c\x02\x04\x02\x0c\x02\x05\x03,\x03\x05\x02\x0c\x02\x04\x02\x0c\x02\x05\x03,\x03\x05\x03\x0b\x02\x04\x02\x0b\x03\x05\x03,\x03\x06\x02\x0b\x02\x04\x02\x0b\x02\x06\x03,\x03\x06\x02\x0b\x02\x04\x02\x0b\x01\x07\x03,\x03\x07\x02\n\x02\x04\x02\n\x02\x07\x03+\x04\x08\x02\t\x02\x04\x02\t\x02\x08\x03*\x05\t\x0c\x04\x0c\t\x03*\x05\n\x0b\x04\x0b\n\x03*\x05.\x03*\x05.\x03*\x05.\x03*\x05.\x03*\x05\n\x0b\x04\x0b\n\x03+\x04\t\x0c\x04\x0c\t\x03,\x03\x08\x02\t\x02\x04\x02\t\x02\x08\x03,\x03\x07\x02\n\x02\x04\x02\n\x02\x07\x03,\x03\x06\x02\x0b\x02\x04\x02\x0b\x01\x07\x03,\x03\x06\x02\x0b\x02\x04\x02\x0b\x02\x06\x03,\x03\x05\x03\x0b\x02\x04\x02\x0b\x03\x05\x03,\x03\x05\x02\x0c\x02\x04\x02\x0c\x02\x05\x03,\x03\x05\x02\x0c\x02\x04\x02\x0c\x02\x05\x03,\x03\x06\x02\x0b\x02\x04\x02\x0b\x02\x06\x03,\x03\x06\x02\x0b\x02\x04\x02\x0b\x02\x06\x03,\x03\x07\x02\t\x02\x06\x02\t\x02\x07\x03,\x03\x08\x02\x07\x02\x08\x02\x07\x02\x08\x03,\x03\t\x03\x02\x04\n\x04\x02\x03\t\x03,\x03\n\x06\x0e\x06\n\x03,\x03\x0c\x03\x10\x03\x0c\x03,\x03.\x03,\x03.\x03,\x03.\x03,\x03.\x03,\x03.\x03,4-2/02,\x97&;$<$<$\x1e')
# 1-bit RLE, generated from res/clock_icon.png, 301 bytes
clock = (96, 64, b'\xff\x00\xff\x00\xff\x00\xff\x00\xff\x00\xff\x00\xcd\x06\r\x06!\x05\x0b\x08\x0c\x08\x0b\n\x1d\t\x07\x0c\n\x08\n\x0c\x1b\x0b\x06\x0e\x08\x03\x02\x03\n\x04\x05\x04\x1a\x04\x03\x04\x06\x02\x08\x04\r\x03\t\x04\x07\x03\x19\x04\x05\x04\x10\x04\x0c\x03\t\x03\t\x02\x19\x03\x07\x03\x11\x03\x0c\x03\t\x03\t\x03\n\x04\t\x04\x07\x04\x10\x03\x0c\x03\t\x03\t\x03\n\x04\t\x03\t\x03\x10\x03\x0c\x03\t\x03\t\x03\n\x04\t\x03\t\x03\x10\x03\x0c\x03\t\x04\x07\x04\n\x04\t\x03\t\x03\x0f\x04\x0c\x03\n\x04\x05\x05\n\x04\t\x03\x03\x02\x04\x03\x0e\x04\r\x03\n\n\x01\x03\x17\x03\x02\x04\x03\x03\r\x05\r\x03\x0b\t\x01\x03\x17\x03\x02\x03\x04\x03\x0c\x05\x0e\x03\r\x05\x03\x03\x17\x03\t\x03\x0b\x05\x0f\x03\x15\x03\x17\x03\t\x03\n\x05\x10\x03\x14\x04\x17\x03\t\x03\t\x05\x11\x03\x14\x03\x18\x04\x07\x04\x08\x04\x13\x03\x14\x03\x19\x03\x07\x03\x08\x04\x14\x03\x13\x04\x0b\x04\n\x03\x06\x04\x07\x04\x15\x03\x0b\x01\x05\x05\x0c\x04\x0b\x04\x03\x04\x07\x03\x12\r\x06\n\r\x04\x0b\x0b\x06\x0f\x07\r\x06\t\x0e\x04\x0c\t\x07\x0f\x07\r\x07\x06\x10\x04\x0e\x05\t\x0f\xff\x00\xff\x00\xff\x00\xff\x00\xff\x00\xff\x00\xff\x00\xff\x00\xaa')
# 1-bit RLE, generated from res/torch_icon.png, 283 bytes
torch = (96, 64, b'\xff\x00\xff\x00\xff\x00\xff\x00\xff\x00\xff\x00\xff\x00e\x06W\nT\x04\x06\x02S\x03\x07\x02S\x02\n\x01\x0b\x029\x05\x08\x02\t\x02\x08\x03:\x07\x06\x02\x0b\x01\x06\x02$(\n\x02\x03\x03%(\x0c\x01+\x02%\x01\x0b\x02+\x02%\x01\x0c\x01+\x02\x05\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x05\x01\x0b\x02+\x02\x04\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x04\x01\x0c\x01+\x02%\x01\x0b\x02\x03\n\x1e\x02\x05\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x05\x01\x0c\x01+\x02\x04\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x04\x01\x0b\x02+\x02%\x01\x0c\x01+\x02%\x01\x0b\x02+(\x0c\x01,(\n\x02\x03\x03L\x02\x0b\x01\x06\x02K\x02\t\x02\x08\x03H\x02\n\x01\x0b\x02G\x03\x07\x02U\x04\x06\x02V\nY\x06\xff\x00\xff\x00\xff\x00\xff\x00\xff\x00\xff\x00\xe2')
# 1-bit RLE, generated from res/up_arrow.png, 16 bytes
up_arrow = (16, 9, b'\x07\x02\r\x04\x0b\x06\t\x08\x07\n\x05\x0c\x03\x0e\x01 ')

View File

@ -15,6 +15,7 @@ import widgets
from apps.clock import ClockApp
from apps.flashlight import FlashlightApp
from apps.launcher import LauncherApp
from apps.testapp import TestApp
class EventType():
@ -86,6 +87,8 @@ class Manager():
self.applications = []
self.blank_after = 15
self.charging = True
self.launcher = LauncherApp()
self._brightness = 2
self._button = PinHandler(watch.button)
@ -142,23 +145,40 @@ class Manager():
quick application ring. Applications on the ring are not permitted
to subscribe to :py:data`EventMask.SWIPE_LEFTRIGHT` events.
Swipe up is used to bring up the launcher. Clock applications are not
permitted to subscribe to :py:data`EventMask.SWIPE_UPDOWN` events since
they should expect to be the default application (and is important that
we can trigger the launcher from the default application).
:param int direction: The direction of the navigation
"""
app_list = self.applications
if direction == EventType.LEFT:
i = app_list.index(self.app) + 1
if i >= len(app_list):
if self.app in app_list:
i = app_list.index(self.app) + 1
if i >= len(app_list):
i = 0
else:
i = 0
self.switch(app_list[i])
elif direction == EventType.RIGHT:
i = app_list.index(self.app) - 1
if i < 0:
i = len(app_list)-1
if self.app in app_list:
i = app_list.index(self.app) - 1
if i < 0:
i = len(app_list)-1
else:
i = 0
self.switch(app_list[i])
elif direction == EventType.UP:
self.switch(self.launcher)
elif direction == EventType.DOWN:
if self.app != app_list[0]:
self.switch(app_list[0])
else:
watch.vibrator.pulse()
elif direction == EventType.HOME:
i = app_list.index(self.app)
if i != 0:
if self.app != app_list[0]:
self.switch(app_list[0])
else:
self.sleep()