Progress bars can show the percentage on top

The percentage (rounded down, in the element's font) is centred inside the
bar and its dots invert the bar, so it reads on the filled and the empty
part. It uses the bar's unused format and font fields, so the page format
stays the same. The Bad page shows it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
mangavalkandClaude Opus 5.5 committed 2026-10-18 16:27:56 +02:00
1 parent 9959c1aa74
commit 948087077d
9 files changed
+187 -9

No files matched your search

+105
View File
@@ -2443,6 +2443,109 @@ void test_bar_render()
TEST_ASSERT_TRUE(bitmapPixel(b, 61, 17));
}
void test_bar_percent_text()
{
PageElement e = barPage().elements[0];
char t[8];
barPercent(e, nullptr, t, sizeof(t));
TEST_ASSERT_EQUAL_STRING("--", t);
barPercent(e, "unavailable", t, sizeof(t));
TEST_ASSERT_EQUAL_STRING("--", t);
barPercent(e, "-5", t, sizeof(t));
TEST_ASSERT_EQUAL_STRING("0%", t);
barPercent(e, "42.9", t, sizeof(t)); // rounded down
TEST_ASSERT_EQUAL_STRING("42%", t);
barPercent(e, "29", t, sizeof(t));
TEST_ASSERT_EQUAL_STRING("29%", t);
barPercent(e, "99.99", t, sizeof(t)); // 100% only when full
TEST_ASSERT_EQUAL_STRING("99%", t);
barPercent(e, "250", t, sizeof(t));
TEST_ASSERT_EQUAL_STRING("100%", t);
e.bar_max = 7200;
barPercent(e, "1800", t, sizeof(t));
TEST_ASSERT_EQUAL_STRING("25%", t);
}
// The dots that differ between two bitmaps
static int countDiff(const uint8_t *a, const uint8_t *b, int &left, int &top, int &right, int &bottom)
{
int n = 0;
left = top = 99;
right = bottom = -1;
for (int y = 0; y < BITMAP_HEIGHT; y++)
for (int x = 0; x < BITMAP_WIDTH; x++)
if (bitmapPixel(a, x, y) != bitmapPixel(b, x, y))
{
n++;
left = x < left ? x : left;
right = x > right ? x : right;
top = y < top ? y : top;
bottom = y > bottom ? y : bottom;
}
return n;
}
void test_bar_percent_render()
{
FakeResources res;
DynamicPage page = barPage();
PageElement &e = page.elements[0];
e.x = 10;
e.y = 5;
e.bar_width = 30; // 28 x 7 inside the frame
e.bar_height = 9;
strcpy(e.font, "Small");
TEST_ASSERT_NULL(validatePage(page));
uint8_t plain[BITMAP_BYTES], with[BITMAP_BYTES], text[BITMAP_BYTES];
res.value = "50";
renderPage(page, at(12, 0), 0, res, plain);
e.format = BAR_PERCENT;
TEST_ASSERT_NULL(validatePage(page));
ElementBox box;
renderPage(page, at(12, 0), 0, res, with, &box);
TEST_ASSERT_EQUAL(30, box.width); // the box stays the bar
// Every dot of the text flips the bar: as many changes as the text has dots, all inside
memset(text, 0, BITMAP_BYTES);
drawText(text, builtinFont(*findBuiltinFont("Small")), 0, 0, "50%", DrawMode::Set);
int left, top, right, bottom;
TEST_ASSERT_EQUAL(countOn(text), countDiff(plain, with, left, top, right, bottom));
TEST_ASSERT_TRUE(left >= 11 && right <= 38 && top >= 6 && bottom <= 12);
// Centred: 5 dots high in 7 rows, one row free above and below
TEST_ASSERT_EQUAL(7, top);
TEST_ASSERT_EQUAL(11, bottom);
int free_left = left - 11, free_right = 38 - right;
TEST_ASSERT_TRUE(free_left - free_right <= 1 && free_right - free_left <= 1);
// Columns 11-24 are filled: the text is dark there and lit over the empty part
int dark = 0, lit = 0;
for (int y = top; y <= bottom; y++)
for (int x = left; x <= right; x++)
if (bitmapPixel(plain, x, y) != bitmapPixel(with, x, y))
{
TEST_ASSERT_EQUAL(x > 24, bitmapPixel(with, x, y));
(x > 24 ? lit : dark)++;
}
TEST_ASSERT_TRUE(dark > 0 && lit > 0);
// Unknown: "--" on the empty bar
res.value = nullptr;
renderPage(page, at(12, 0), 0, res, with);
TEST_ASSERT_TRUE(countOn(with) > 2 * 30 + 2 * 7);
// Doesn't fit (Small digits are 5 dots high, 4 rows inside): only the bar
res.value = "50";
e.bar_height = 6;
e.format = BAR_PLAIN;
renderPage(page, at(12, 0), 0, res, plain);
e.format = BAR_PERCENT;
renderPage(page, at(12, 0), 0, res, with);
TEST_ASSERT_EQUAL(0, countDiff(plain, with, left, top, right, bottom));
// Format 2 doesn't exist
e.format = BAR_FORMAT_COUNT;
TEST_ASSERT_NOT_NULL(validatePage(page));
}
void test_date_days_ahead()
{
LocalTime t; // Thursday 31 December 2026
@@ -2703,6 +2806,8 @@ int main()
RUN_TEST(test_frame_check_periodic_resync);
RUN_TEST(test_bar_fill);
RUN_TEST(test_bar_render);
RUN_TEST(test_bar_percent_text);
RUN_TEST(test_bar_percent_render);
RUN_TEST(test_bar_validation);
RUN_TEST(test_date_days_ahead);
RUN_TEST(test_attribute_paths_and_16_elements);