From de647b324cd5731eed54fe38e3efe93b26a8be42 Mon Sep 17 00:00:00 2001 From: Daniel Thompson Date: Sun, 24 May 2020 14:20:02 +0100 Subject: [PATCH] widgets: Introduce a slider widget Currently the slider doesn't actually slide (because we process touch events rather than swipe events) but we've called is a slider anyway. --- res/knob.png | Bin 0 -> 4068 bytes wasp/apps/testapp.py | 33 +++++++++++++++++-- wasp/icons.py | 11 +++++++ wasp/widgets.py | 77 +++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 118 insertions(+), 3 deletions(-) create mode 100644 res/knob.png diff --git a/res/knob.png b/res/knob.png new file mode 100644 index 0000000000000000000000000000000000000000..579d26b1c94033928139f868ee33c50fd7ba687b GIT binary patch literal 4068 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 90: + s = self._sliders[(event[2] - 90) // 50] + s.touch(event) + s.update() + self.scroll.draw() + self._update_colours() + elif self.test == 'RLE': self._benchmark_rle() elif self.test == 'String': self._benchmark_string() @@ -119,12 +132,26 @@ class TestApp(): draw.fill() draw.string('{} test'.format(self.test), 0, 6, width=240) - self.scroll.draw() if self.test == 'Crash': draw.string("Press button to", 12, 24+24) draw.string("throw exception.", 12, 24+48) + elif self.test == 'Colours': + for s in self._sliders: + s.draw() + self._update_colours() elif self.test == 'RLE': draw.blit(self.ICON, 120-48, 120-32) + self.scroll.draw() wasp.watch.display.mute(False) + + def _update_colours(self): + draw = wasp.watch.drawable + r = self._sliders[0].value + g = self._sliders[1].value + b = self._sliders[2].value + rgb = (r << 11) + (g << 5) + b + + draw.string('RGB565 #{:04x}'.format(rgb), 0, 6, width=240) + draw.fill(rgb, 60, 35, 120, 50) diff --git a/wasp/icons.py b/wasp/icons.py index dec982a..ff39f8a 100644 --- a/wasp/icons.py +++ b/wasp/icons.py @@ -139,3 +139,14 @@ up_arrow = (16, 9, b'\x07\x02\r\x04\x0b\x06\t\x08\x07\n\x05\x0c\x03\x0e\x01 ') # 1-bit RLE, generated from res/down_arrow.png, 17 bytes down_arrow = (16, 9, b'\x00 \x01\x0e\x03\x0c\x05\n\x07\x08\t\x06\x0b\x04\r\x02\x07') + +# 2-bit RLE, generated from res/knob.png, 72 bytes +knob = ( + b'\x02' + b'((' + b'\x10\xc8\x1c\xd0\x16\xd4\x13\xd6\x10\xda\r\xdc\x0b\xde\t\xe0' + b'\x08\xe0\x07\xe2\x05\xe4\x04\xe4\x03\xe6\x02\xe6\x02\xe6\x02\xe6' + b'\x01\xff\xff\x02\x01\xe6\x02\xe6\x02\xe6\x02\xe6\x03\xe4\x04\xe4' + b'\x05\xe2\x07\xe0\x08\xe0\t\xde\x0b\xdc\r\xda\x10\xd6\x13\xd4' + b'\x16\xd0\x1c\xc8\x10' +) diff --git a/wasp/widgets.py b/wasp/widgets.py index 9271fe6..97410b0 100644 --- a/wasp/widgets.py +++ b/wasp/widgets.py @@ -10,6 +10,7 @@ shared between applications. import icons import watch +from micropython import const class BatteryMeter(object): """Battery meter widget. @@ -94,3 +95,79 @@ class ScrollIndicator(): draw.rleblit(icons.up_arrow, pos=self._pos, fg=0x7bef) if self.down: draw.rleblit(icons.down_arrow, pos=(self._pos[0], self._pos[1] + 13), fg=0x7bef) + +_SLIDER_KNOB_DIAMETER = const(40) +_SLIDER_KNOB_RADIUS = const(_SLIDER_KNOB_DIAMETER // 2) +_SLIDER_WIDTH = const(220) +_SLIDER_TRACK = const(_SLIDER_WIDTH - _SLIDER_KNOB_DIAMETER) +_SLIDER_TRACK_HEIGHT = const(8) +_SLIDER_TRACK_Y1 = const(_SLIDER_KNOB_RADIUS - (_SLIDER_TRACK_HEIGHT // 2)) +_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): + 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 + + def draw(self): + """Draw the slider.""" + draw = watch.drawable + x = self._x + y = self._y + color = self._color + light = self._lowlight + + knob_x = x + ((_SLIDER_TRACK * self.value) // (self._steps-1)) + draw.blit(icons.knob2, knob_x, y, color) + + w = knob_x - x + if w > 0: + draw.fill(0, x, y, w, _SLIDER_TRACK_Y1) + if w > _SLIDER_KNOB_RADIUS: + draw.fill(0, x, y+_SLIDER_TRACK_Y1, + _SLIDER_KNOB_RADIUS, _SLIDER_TRACK_HEIGHT) + draw.fill(color, x+_SLIDER_KNOB_RADIUS, y+_SLIDER_TRACK_Y1, + w-_SLIDER_KNOB_RADIUS, _SLIDER_TRACK_HEIGHT) + else: + draw.fill(0, x, y+_SLIDER_TRACK_Y1, w, _SLIDER_TRACK_HEIGHT) + draw.fill(0, x, y+_SLIDER_TRACK_Y2, w, _SLIDER_TRACK_Y1) + + sx = knob_x + _SLIDER_KNOB_DIAMETER + w = _SLIDER_WIDTH - _SLIDER_KNOB_DIAMETER - w + if w > 0: + draw.fill(0, sx, y, w, _SLIDER_TRACK_Y1) + if w > _SLIDER_KNOB_RADIUS: + draw.fill(0, sx+w-_SLIDER_KNOB_RADIUS, y+_SLIDER_TRACK_Y1, + _SLIDER_KNOB_RADIUS, _SLIDER_TRACK_HEIGHT) + draw.fill(light, sx, y+_SLIDER_TRACK_Y1, + w-_SLIDER_KNOB_RADIUS, _SLIDER_TRACK_HEIGHT) + else: + draw.fill(0, sx, y+_SLIDER_TRACK_Y1, w, _SLIDER_TRACK_HEIGHT) + draw.fill(0, sx, y+_SLIDER_TRACK_Y2, w, _SLIDER_TRACK_Y1) + + def update(self): + self.draw() + + def touch(self, event): + tx = event[1] + threshold = self._x + 20 - (self._stepsize / 2) + v = int((tx - threshold) / self._stepsize) + if v < 0: + v = 0 + elif v >= self._steps: + v = self._steps - 1 + self.value = v