Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions User Guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,14 +22,14 @@ Track duration, rest duration, and launch sensitivity are saved to `user.json` w
It is possible to change the duration of both the ``Track Session`` and the ``Rest in Pits``.

* From the ``Primary Screen``, ``Swipe Left`` to edit the ``Track Session`` and ``Swipe Right`` to edit the ``Rest in Pits Session``.
* Once in either edit modes, use a ``Swipe Right`` to increment the duration and a ``Swipe Left`` to decrement the value. Note duration values are predefined as [1, 5, 10, 15, 20, 25, 30, 40, 50, 60] minutes.
* When the desired duration value is shown ``Swipe Up`` to save and return to the ``Primary Screen``
* Once in either edit mode, follow the on-screen prompts: ``Swipe Right`` to increment the duration and ``Swipe Left`` to decrement the value. Note duration values are predefined as [1, 5, 10, 15, 20, 25, 30, 40, 50, 60] minutes.
* When the desired duration value is shown, follow the ``Swipe UP: save`` prompt to save and return to the ``Primary Screen``.

### Launch Mode
``Launch mode`` is disabled by default.
``Launch mode`` can be enabled by defining a ``Launch Sensitivity`` value above zero.
* From the ``Primary Screen``, ``Swipe Up`` to edit ``Launch Sensitivity``.
* Swipe ``Left`` or ``Right`` to select an appropriate value greater than zero.
* Follow the on-screen prompts and swipe ``Left`` or ``Right`` to select an appropriate value greater than zero.
* Swipe ``UP`` to save and enable ``Launch mode``.
* Select `0` and swipe ``UP`` to save and disable ``Launch mode``.

Expand Down
36 changes: 25 additions & 11 deletions configuration.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,16 @@
"""Touch-driven configuration editors with injected display dependencies."""


CONFIGURATION_PROMPTS = (
("Swipe L/R to change", None, 205, 1, "black"),
("Swipe UP: save", None, 222, 1, "black"),
)


def _with_prompts(text_array):
return text_array + list(CONFIGURATION_PROMPTS)


def _selected_index(values, current):
if not values:
raise ValueError("At least one selectable value is required")
Expand All @@ -17,12 +27,14 @@ def set_sensitivity(LCD=None, Touch=None, sensitivity_values=None, sensitivity=0
index = _selected_index(values, sensitivity)

def draw():
text_array = [
[str(values[index]), None, 80, 5, "white"],
["Launch", None, 145, 2, "black"],
["Sensitivity", None, 175, 2, "black"],
[operation, None, 35, 2, "black"],
]
text_array = _with_prompts(
[
[str(values[index]), None, 80, 5, "white"],
["Launch", None, 145, 2, "black"],
["Sensitivity", None, 175, 2, "black"],
[operation, None, 35, 2, "black"],
]
)
Touch.ControlScreen(LCD, text_array=text_array, back_colour=back_colour)

draw()
Expand All @@ -46,11 +58,13 @@ def set_session(LCD=None, Touch=None, session=None, session_values=None,
index = _selected_index(values, current)

def draw():
text_array = [
[str(values[index]), None, 90, 5, "white"],
[session_name, None, 180, 2, "black"],
[operation, None, 35, 2, "black"],
]
text_array = _with_prompts(
[
[str(values[index]), None, 90, 5, "white"],
[session_name, None, 180, 2, "black"],
[operation, None, 35, 2, "black"],
]
)
Touch.ControlScreen(LCD, text_array=text_array, back_colour=back_colour)

draw()
Expand Down
31 changes: 27 additions & 4 deletions font_renderer.py
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,16 @@ def _draw_scanlines(surface, bitmap, width, height, x, y, color):
surface.hline(x + run_start, y + row, width - run_start, color)


def _blit_glyph(surface, bitmap, width, height, x, y, palette):
def _blit_glyph(
surface,
bitmap,
width,
height,
x,
y,
palette,
transparent_key,
):
glyph_buffer = bytearray(bitmap)
# Generated rows are padded to whole bytes and store the left-most pixel in
# bit 7. MicroPython needs an explicit padded stride and MONO_HLSB for that
Expand All @@ -79,7 +88,7 @@ def _blit_glyph(surface, bitmap, width, height, x, y, palette):
glyph = framebuf.FrameBuffer(
glyph_buffer, width, height, framebuf.MONO_HLSB, stride,
)
surface.blit(glyph, x, y, 0, palette)
surface.blit(glyph, x, y, transparent_key, palette)


def draw_text(surface, text, x, y, size, color):
Expand All @@ -93,10 +102,15 @@ def draw_text(surface, text, x, y, size, color):

palette = None
palette_buffer = None
transparent_key = None
if use_blitter:
# A distinct palette value is required for transparency. Using black
# as the key also discards black foreground glyphs after palette
# mapping, which made configuration labels and prompts invisible.
transparent_key = 1 if color != 1 else 2
palette_buffer = bytearray(4)
palette = framebuf.FrameBuffer(palette_buffer, 2, 1, framebuf.RGB565)
palette.pixel(0, 0, 0)
palette.pixel(0, 0, transparent_key)
palette.pixel(1, 0, color)

with open(BITMAP_FILES[size_index], "rb") as bitmap_file:
Expand All @@ -110,7 +124,16 @@ def draw_text(surface, text, x, y, size, color):
raise OSError("Font bitmap is missing or truncated")

if use_blitter:
_blit_glyph(surface, bitmap, width, height, cursor, int(y), palette)
_blit_glyph(
surface,
bitmap,
width,
height,
cursor,
int(y),
palette,
transparent_key,
)
else:
_draw_scanlines(surface, bitmap, width, height, cursor, int(y), color)
cursor += width
Expand Down
44 changes: 43 additions & 1 deletion tests/test_configuration.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import math
import unittest
from types import SimpleNamespace

from configuration import set_sensitivity, set_session
from configuration import CONFIGURATION_PROMPTS, set_sensitivity, set_session
from font_renderer import measure_text, pixel_height


class FakeTouch:
Expand All @@ -20,6 +22,46 @@ class ConfigurationEditorTests(unittest.TestCase):
duration_values = [1, 5, 10, 15, 20, 25, 30, 40, 50, 60]
sensitivity_values = [0, 0.5, 1, 1.25, 1.5, 1.75, 2, 2.5, 3.5, 4]

def assert_prompts_shown(self, touch):
expected = list(CONFIGURATION_PROMPTS)
for _, text_array, _ in touch.screens:
self.assertEqual(expected, text_array[-len(expected):])

def test_prompts_fit_inside_the_round_display(self):
display_radius = 120
for text, _, y_position, size, _ in CONFIGURATION_PROMPTS:
text_center_y = y_position + (pixel_height(size) / 2)
distance_from_center = text_center_y - display_radius
visible_width = 2 * math.sqrt(
(display_radius ** 2) - (distance_from_center ** 2)
)
self.assertLessEqual(measure_text(text, size), visible_width)

def test_session_prompts_are_shown_on_every_redraw(self):
touch = FakeTouch(["right", "up"])
set_session(
LCD=object(),
Touch=touch,
session=SimpleNamespace(duration_mins=self.duration_values[0]),
session_values=self.duration_values,
session_name="Track",
)

self.assertEqual(2, len(touch.screens))
self.assert_prompts_shown(touch)

def test_sensitivity_prompts_are_shown_on_every_redraw(self):
touch = FakeTouch(["left", "up"])
set_sensitivity(
LCD=object(),
Touch=touch,
sensitivity_values=self.sensitivity_values,
sensitivity=self.sensitivity_values[0],
)

self.assertEqual(2, len(touch.screens))
self.assert_prompts_shown(touch)

def test_session_immediate_save_preserves_every_allowed_value(self):
for current in self.duration_values:
with self.subTest(current=current):
Expand Down
13 changes: 13 additions & 0 deletions tests/test_font_renderer.py
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,19 @@ def test_hardware_blit_uses_msb_first_bits_and_padded_stride(self):
self.assertEqual(((glyph_width + 7) // 8) * 8, glyph_call[4])
self.assertEqual(1, len(surface.blits))

def test_hardware_blit_does_not_treat_black_text_as_transparent(self):
fake_framebuf = FakeFrameBufferModule()
surface = FakeBlitSurface()
original_framebuf = font_renderer.framebuf
font_renderer.framebuf = fake_framebuf
try:
draw_text(surface, "Config", 3, 4, 1, 0)
finally:
font_renderer.framebuf = original_framebuf

self.assertTrue(surface.blits)
self.assertTrue(all(blit[3] != 0 for blit in surface.blits))

def test_centering_uses_measured_width(self):
surface = FakeSurface()
x, width = draw_centered(surface, "Ready", 20, 5, 1)
Expand Down
Loading