diff --git a/res/app_icon.png b/res/app_icon.png new file mode 100644 index 0000000..574f75f Binary files /dev/null and b/res/app_icon.png differ diff --git a/res/clock_icon.png b/res/clock_icon.png new file mode 100644 index 0000000..165bf51 Binary files /dev/null and b/res/clock_icon.png differ diff --git a/res/down_arrow.png b/res/down_arrow.png new file mode 100644 index 0000000..ef94eb3 Binary files /dev/null and b/res/down_arrow.png differ diff --git a/res/settings_icon.png b/res/settings_icon.png new file mode 100644 index 0000000..d1141b0 Binary files /dev/null and b/res/settings_icon.png differ diff --git a/res/torch_icon.png b/res/torch_icon.png new file mode 100644 index 0000000..bbd4db3 Binary files /dev/null and b/res/torch_icon.png differ diff --git a/res/up_arrow.png b/res/up_arrow.png new file mode 100644 index 0000000..c990949 Binary files /dev/null and b/res/up_arrow.png differ diff --git a/wasp/apps/clock.py b/wasp/apps/clock.py index 4feba63..4236d9a 100644 --- a/wasp/apps/clock.py +++ b/wasp/apps/clock.py @@ -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() diff --git a/wasp/apps/flashlight.py b/wasp/apps/flashlight.py index 13e3443..c4702a0 100644 --- a/wasp/apps/flashlight.py +++ b/wasp/apps/flashlight.py @@ -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.""" diff --git a/wasp/apps/launcher.py b/wasp/apps/launcher.py new file mode 100644 index 0000000..274ea9c --- /dev/null +++ b/wasp/apps/launcher.py @@ -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) diff --git a/wasp/apps/testapp.py b/wasp/apps/testapp.py index 8c15848..478249f 100644 --- a/wasp/apps/testapp.py +++ b/wasp/apps/testapp.py @@ -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) diff --git a/wasp/boards/pinetime/manifest.py b/wasp/boards/pinetime/manifest.py index 191ee3d..30d54e2 100644 --- a/wasp/boards/pinetime/manifest.py +++ b/wasp/boards/pinetime/manifest.py @@ -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', diff --git a/wasp/icons.py b/wasp/icons.py index c3089c4..76b5ee7 100644 --- a/wasp/icons.py +++ b/wasp/icons.py @@ -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 ') diff --git a/wasp/wasp.py b/wasp/wasp.py index a18c466..6016bd5 100644 --- a/wasp/wasp.py +++ b/wasp/wasp.py @@ -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()