summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--tools/test_theme.py15
-rwxr-xr-xtools/themer.py81
-rw-r--r--wasp/apps/clock.py15
-rw-r--r--wasp/wasp.py29
-rw-r--r--wasp/widgets.py45
5 files changed, 162 insertions, 23 deletions
diff --git a/tools/test_theme.py b/tools/test_theme.py
new file mode 100644
index 0000000..dc2b990
--- /dev/null
+++ b/tools/test_theme.py
@@ -0,0 +1,15 @@
+from themer import DefaultTheme
+
+class Theme(DefaultTheme):
+ # These colors were chosen specifically because they're hard to miss.
+ # Using this theme on an actual device is not advised
+ # The default theme was generated by removing all the lines below and adding `pass` instead.
+ BLE_COLOR = 0xfb80
+ SCROLL_INDICATOR_COLOR = 0xf800
+ BATTERY_CHARGING_COLOR = 0x07ff
+ SMALL_CLOCK_COLOR = 0x599f
+ NOTIFICATION_COLOR = 0x8fe0
+ ACCENT_MID = 0xf800
+ ACCENT_LO = 0x001f
+ ACCENT_HI = 0x07e0
+ SLIDER_DEFAULT_COLOR = 0x7777
diff --git a/tools/themer.py b/tools/themer.py
new file mode 100755
index 0000000..819c35c
--- /dev/null
+++ b/tools/themer.py
@@ -0,0 +1,81 @@
+#!/usr/bin/env python3
+"""Compiles themes for wasp-os"""
+
+from argparse import ArgumentParser, RawTextHelpFormatter
+from importlib import import_module
+from typing import Tuple
+
+class DefaultTheme():
+ """This represents the default theme.
+
+ Import this file and extend the Theme class, only changing the variables.
+ Export the resulting class as 'Theme'.
+ serialize() should NEVER be overriden!
+ """
+ BLE_COLOR = 0x7bef
+ SCROLL_INDICATOR_COLOR = 0x7bef
+ BATTERY_CHARGING_COLOR = 0x7bef
+ SMALL_CLOCK_COLOR = 0xe73c
+ NOTIFICATION_COLOR = 0x7bef
+ ACCENT_MID = 0xb5b6
+ ACCENT_LO = 0xbdb6
+ ACCENT_HI = 0xffff
+ SLIDER_DEFAULT_COLOR = 0x39ff
+
+ def serialize(self) -> bytes:
+ """Serializes the theme for use in wasp-os"""
+ def split_bytes(x: int) -> Tuple[int, int]:
+ return (x & 0xFF, (x >> 8) & 0xFF)
+ theme_bytes = bytes([
+ *split_bytes(self.BLE_COLOR),
+ *split_bytes(self.SCROLL_INDICATOR_COLOR),
+ *split_bytes(self.BATTERY_CHARGING_COLOR),
+ *split_bytes(self.SMALL_CLOCK_COLOR),
+ *split_bytes(self.NOTIFICATION_COLOR),
+ *split_bytes(self.ACCENT_MID),
+ *split_bytes(self.ACCENT_LO),
+ *split_bytes(self.ACCENT_HI),
+ *split_bytes(self.SLIDER_DEFAULT_COLOR),
+ ])
+ return theme_bytes
+
+
+if __name__ == '__main__':
+ parser = ArgumentParser(
+ description='''Compiles themes into a format understood by wasp-os.
+ The resulting string should be put in main.py like this:
+
+ theme_string = THEME_STRING_GOES_HERE
+
+ for the theme to take effect.
+ ''',
+ epilog=''' To create a theme,
+ import this file and extend the DefaultTheme class, only changing the variables.
+ Export the resulting class as 'Theme'.
+ Example:
+ --------
+ theme.py:
+ from themer import DefaultTheme
+
+ class Theme(DefaultTheme):
+ BLE_ICON_COLOR = 0x041F
+
+ shell:
+ $ ./themer.py theme # NOTE: do not include .py at end of file!
+ > b'\xef{\xef{\xef{<\xe7\xef{\xb6\xb5\xb6\xbd\xff\xff\xff9'
+
+ main.py:
+ ...
+ wasp.system.set_theme(b'\xef{\xef{\xef{<\xe7\xef{\xb6\xb5\xb6\xbd\xff\xff\xff9')
+ ...
+ ''',
+ formatter_class=RawTextHelpFormatter
+ )
+
+ parser.add_argument('input_file', type=str, nargs=1)
+ args = parser.parse_args()
+
+ theme = DefaultTheme()
+ theme = import_module(args.input_file[0]).Theme()
+ print(theme.serialize())
+
diff --git a/wasp/apps/clock.py b/wasp/apps/clock.py
index 106e712..c062d3e 100644
--- a/wasp/apps/clock.py
+++ b/wasp/apps/clock.py
@@ -76,7 +76,8 @@ class ClockApp():
# Clear the display and draw that static parts of the watch face
draw.fill()
- draw.rleblit(digits.clock_colon, pos=(2*48, 80), fg=0xb5b6)
+ draw.rleblit(digits.clock_colon, pos=(2*48, 80),
+ fg=wasp.system.theme('accent-mid'))
# Redraw the status bar
wasp.system.bar.draw()
@@ -95,10 +96,14 @@ class ClockApp():
month = MONTH[month*3:(month+1)*3]
# Draw the changeable parts of the watch face
- draw.rleblit(DIGITS[now[4] % 10], pos=(4*48, 80))
- draw.rleblit(DIGITS[now[4] // 10], pos=(3*48, 80), fg=0xbdb6)
- draw.rleblit(DIGITS[now[3] % 10], pos=(1*48, 80))
- draw.rleblit(DIGITS[now[3] // 10], pos=(0*48, 80), fg=0xbdb6)
+ draw.rleblit(DIGITS[now[4] % 10], pos=(4*48, 80),
+ fg=wasp.system.theme('accent-hi'))
+ draw.rleblit(DIGITS[now[4] // 10], pos=(3*48, 80),
+ fg=wasp.system.theme('accent-lo'))
+ draw.rleblit(DIGITS[now[3] % 10], pos=(1*48, 80),
+ fg=wasp.system.theme('accent-hi'))
+ draw.rleblit(DIGITS[now[3] // 10], pos=(0*48, 80),
+ fg=wasp.system.theme('accent-lo'))
draw.string('{} {} {}'.format(now[2], month, now[0]),
0, 180, width=240)
diff --git a/wasp/wasp.py b/wasp/wasp.py
index 7cf2883..4837e12 100644
--- a/wasp/wasp.py
+++ b/wasp/wasp.py
@@ -14,7 +14,6 @@
wasp.watch is an import of :py:mod:`watch` and is simply provided as a
shortcut (and to reduce memory by keeping it out of other namespaces).
"""
-
import gc
import machine
import micropython
@@ -118,6 +117,8 @@ class Manager():
self.musicstate = {}
self.musicinfo = {}
+ self._theme = b'\xef{\xef{\xef{<\xe7\xef{\xb6\xb5\xb6\xbd\xff\xff\xff9'
+
self.blank_after = 15
self._alarms = []
@@ -514,4 +515,30 @@ class Manager():
self._scheduling = enable
+ def set_theme(self, new_theme) -> bool:
+ """Sets the system theme.
+
+ Accepts anything that supports indexing,
+ and has a len() equivalent to the default theme."""
+ if len(self._theme) != len(new_theme):
+ return False
+ self._theme = new_theme
+ return True
+
+ def theme(self, theme_part: str) -> int:
+ """Returns the relevant part of theme. For more see ../tools/themer.py"""
+ theme_parts = ("ble",
+ "scroll-indicator",
+ "battery-charging",
+ "status-clock",
+ "notify-icon",
+ "accent-mid",
+ "accent-lo",
+ "accent-hi",
+ "slider-default")
+ if theme_part not in theme_parts:
+ raise IndexError('Theme part {} does not exist'.format(theme_part))
+ idx = theme_parts.index(theme_part) * 2
+ return self._theme[idx] | (self._theme[idx+1] << 8)
+
system = Manager()
diff --git a/wasp/widgets.py b/wasp/widgets.py
index d5abd16..1d79e22 100644
--- a/wasp/widgets.py
+++ b/wasp/widgets.py
@@ -39,7 +39,8 @@ class BatteryMeter:
if watch.battery.charging():
if self.level != -1:
- draw.rleblit(icon, pos=(239-icon[0], 0), fg=0x7bef)
+ draw.rleblit(icon, pos=(239-icon[0], 0),
+ fg=wasp.system.theme('battery-charging'))
self.level = -1
else:
level = watch.battery.level()
@@ -108,7 +109,7 @@ class Clock:
draw = wasp.watch.drawable
draw.set_font(fonts.sans28)
- draw.set_color(0xe73c)
+ draw.set_color(wasp.system.theme('status-clock'))
draw.string(t1, 52, 12, 138)
self.on_screen = now
@@ -137,13 +138,15 @@ class NotificationBar:
(x, y) = self._pos
if wasp.watch.connected():
- draw.blit(icons.blestatus, x, y, fg=0x7bef)
+ draw.blit(icons.blestatus, x, y, fg=wasp.system.theme('ble'))
if wasp.system.notifications:
- draw.blit(icons.notification, x+22, y, fg=0x7bef)
+ draw.blit(icons.notification, x+22, y,
+ fg=wasp.system.theme('notify-icon'))
else:
draw.fill(0, x+22, y, 30, 32)
elif wasp.system.notifications:
- draw.blit(icons.notification, x, y, fg=0x7bef)
+ draw.blit(icons.notification, x, y,
+ fg=wasp.system.theme('notify-icon'))
draw.fill(0, x+30, y, 22, 32)
else:
draw.fill(0, x, y, 52, 32)
@@ -206,10 +209,13 @@ class ScrollIndicator:
def update(self):
"""Update from scrolling indicator."""
draw = watch.drawable
+ color = wasp.system.theme('scroll-indicator')
+
if self.up:
- draw.rleblit(icons.up_arrow, pos=self._pos, fg=0x7bef)
+ draw.rleblit(icons.up_arrow, pos=self._pos, fg=color)
if self.down:
- draw.rleblit(icons.down_arrow, pos=(self._pos[0], self._pos[1] + 13), fg=0x7bef)
+ draw.rleblit(icons.down_arrow, pos=(self._pos[0], self._pos[1] + 13),
+ fg=color)
_SLIDER_KNOB_DIAMETER = const(40)
_SLIDER_KNOB_RADIUS = const(_SLIDER_KNOB_DIAMETER // 2)
@@ -221,22 +227,14 @@ _SLIDER_TRACK_Y2 = const(_SLIDER_TRACK_Y1 + _SLIDER_TRACK_HEIGHT)
class Slider():
"""A slider to select values."""
- def __init__(self, steps, x=10, y=90, color=0x39ff):
+ def __init__(self, steps, x=10, y=90, color=None):
self.value = 0
self._steps = steps
self._stepsize = _SLIDER_TRACK / (steps-1)
self._x = x
self._y = y
self._color = color
-
- # Automatically generate a lowlight color
- if color < 0b10110_000000_00000:
- color = (color | 0b10110_000000_00000) & 0b10110_111111_11111
- if (color & 0b111111_00000) < 0b101100_00000:
- color = (color | 0b101100_00000) & 0b11111_101100_11111
- if (color & 0b11111) < 0b10110:
- color = (color | 0b11000) & 0b11111_111111_10110
- self._lowlight = color
+ self._lowlight = None
def draw(self):
"""Draw the slider."""
@@ -244,6 +242,19 @@ class Slider():
x = self._x
y = self._y
color = self._color
+ if self._color is None:
+ self._color = wasp.system.theme('slider-default')
+ color = self._color
+ if self._lowlight is None:
+ # Automatically generate a lowlight color
+ if color < 0b10110_000000_00000:
+ color = (color | 0b10110_000000_00000) & 0b10110_111111_11111
+ if (color & 0b111111_00000) < 0b101100_00000:
+ color = (color | 0b101100_00000) & 0b11111_101100_11111
+ if (color & 0b11111) < 0b10110:
+ color = (color | 0b11000) & 0b11111_111111_10110
+ self._lowlight = color
+ color = self._color
light = self._lowlight
knob_x = x + ((_SLIDER_TRACK * self.value) // (self._steps-1))