From 502979778389b415bbaacc185e79b8584d0f4b83 Mon Sep 17 00:00:00 2001 From: mrsqr Date: Mon, 10 Aug 2026 09:22:52 +0100 Subject: [PATCH] Increase live countdown font size --- README.md | 2 ++ live_display.py | 13 ++++++---- tests/test_live_display.py | 49 +++++++++++++++++++++++++++++++++++++- 3 files changed, 58 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index db4d3d0..b031438 100644 --- a/README.md +++ b/README.md @@ -39,6 +39,8 @@ The timer utilizes the Waveshare 1.28-inch round touch display, allowing for con The firmware includes a compact proportional bitmap font rendered directly at the display's native resolution. It replaces enlargement of MicroPython's 8x8 framebuffer font, so large countdown digits and labels retain smooth shapes instead of scaling into square pixels. +The running track and rest countdown uses the 74-pixel native font, the closest available pre-rendered size to a 10% increase from the previous 64-pixel countdown. + `font_data.py` and its flash-backed `font_data*.bin` glyph assets are generated from Montserrat SemiBold. The assets contain pre-rasterized native UI sizes, allowing the Pico to use its fast framebuffer blitter without holding the complete font in RAM. To regenerate them, install Pillow and run: ```sh diff --git a/live_display.py b/live_display.py index e25ef9a..a87aede 100644 --- a/live_display.py +++ b/live_display.py @@ -6,6 +6,9 @@ LIVE_LOOP_DELAY_SEC = 0.05 +# Native font size 7 is 74 px: the nearest available size to 10% above the +# previous 64 px countdown (target 70.4 px). +COUNTDOWN_TEXT_SIZE = 7 def _visible_times(session, now): @@ -21,13 +24,13 @@ def track_live_frame(session, now, lcd): """Return all values visible on the track-session screen.""" remaining, elapsed = _visible_times(session, now) if now >= session.end_time: - return ("00:00", elapsed, 6, lcd.red, lcd.black) + return ("00:00", elapsed, COUNTDOWN_TEXT_SIZE, lcd.red, lcd.black) if now < session.last_15: - return (remaining, elapsed, 6, None, None) + return (remaining, elapsed, COUNTDOWN_TEXT_SIZE, None, None) if now < session.last_5: - return (remaining, elapsed, 6, lcd.salmon, lcd.black) - return (remaining, elapsed, 6, lcd.lilac, None) + return (remaining, elapsed, COUNTDOWN_TEXT_SIZE, lcd.salmon, lcd.black) + return (remaining, elapsed, COUNTDOWN_TEXT_SIZE, lcd.lilac, None) def rest_live_frame(session, now, lcd): @@ -38,7 +41,7 @@ def rest_live_frame(session, now, lcd): return ( remaining, elapsed, - 6, + COUNTDOWN_TEXT_SIZE, lcd.blue, None, ) diff --git a/tests/test_live_display.py b/tests/test_live_display.py index 7c1342f..eeea621 100644 --- a/tests/test_live_display.py +++ b/tests/test_live_display.py @@ -1,6 +1,13 @@ +import math import unittest -from live_display import rest_live_frame, run_live_display, track_live_frame +from font_renderer import measure_text, pixel_height +from live_display import ( + COUNTDOWN_TEXT_SIZE, + rest_live_frame, + run_live_display, + track_live_frame, +) from timing import SessionTracker @@ -26,6 +33,31 @@ class FakeLCD: class LiveDisplayTests(unittest.TestCase): + def test_countdown_uses_nearest_native_size_to_ten_percent_larger(self): + previous_height = pixel_height(6) + target_height = previous_height * 1.10 + selected_height = pixel_height(COUNTDOWN_TEXT_SIZE) + + self.assertEqual(7, COUNTDOWN_TEXT_SIZE) + self.assertEqual(74, selected_height) + self.assertLess( + abs(selected_height - target_height), + abs(previous_height - target_height), + ) + + def test_maximum_countdown_width_fits_the_live_screen_position(self): + display_radius = 120 + y_position = 82 + text_height = pixel_height(COUNTDOWN_TEXT_SIZE) + text_width = measure_text("60:00", COUNTDOWN_TEXT_SIZE) + + for edge_y in (y_position, y_position + text_height - 1): + distance_from_center = edge_y - display_radius + visible_width = 2 * math.sqrt( + (display_radius ** 2) - (distance_from_center ** 2) + ) + self.assertLessEqual(text_width, visible_width) + def test_simulated_session_redraws_at_most_once_per_visible_second(self): clock = FakeClock() lcd = FakeLCD() @@ -111,12 +143,27 @@ def test_track_frames_include_warning_and_overrun_state(self): last_5 = track_live_frame(session, 670, lcd) overrun = track_live_frame(session, 700, lcd) + self.assertTrue( + all( + frame[2] == COUNTDOWN_TEXT_SIZE + for frame in (running, last_15, last_5, overrun) + ) + ) self.assertEqual((None, None), running[3:]) self.assertEqual((lcd.salmon, lcd.black), last_15[3:]) self.assertEqual((lcd.lilac, None), last_5[3:]) self.assertEqual("00:00", overrun[0]) self.assertEqual((lcd.red, lcd.black), overrun[3:]) + def test_rest_frame_uses_larger_countdown_size(self): + lcd = FakeLCD() + session = SessionTracker(duration_mins=10, clock=lambda: 100) + session.start_session() + + frame = rest_live_frame(session, 100, lcd) + + self.assertEqual(COUNTDOWN_TEXT_SIZE, frame[2]) + def test_loop_delay_must_be_bounded(self): session = SessionTracker(duration_mins=1, live=True) with self.assertRaises(ValueError):