Page template renderer: HERO1, HERO2, QUAD, GRID6 #59
Labels
No labels
area:companion
area:docs
area:shared
area:tooling
area:watchapp
blocker
kind:chore
kind:feature
kind:spike
kind:test
No milestone
No project
No assignees
2 participants
Notifications
Due date
No due date set.
Blocks
Depends on
#35 view_nav: turn arrow, countdown, street, then-turn, ETA
robert/PedalPebble
#51 Internationalisation scaffolding: German and English
robert/PedalPebble
#62 Round display support (gabbro): round template variants and arc status
robert/PedalPebble
#56 Colour-blind safe palette and a no-colour-alone rule
robert/PedalPebble
#58 Field value store and page descriptor model
robert/PedalPebble
Reference
robert/PedalPebble#59
Loading…
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Goal
A generic renderer that draws any page descriptor. Replaces the hand-built ride layout originally planned in #9.
Acceptance criteria
HERO1: one field, full 200x208, ~140 px digitsHERO2: hero 200x118 plus two cells 100x90; 96 px and 44 px digitsQUAD: 2x2 of 100x104, 56 px digitsGRID6: 2x3 of 100x69, 40 px digits.,:,-,/- system fonts stop around 42-49 px and cannot serve HERO1 or HERO2basalt-GRID6may fall back toQUADFiles
watchapp/src/c/page_render.cwatchapp/resources/fonts/Notes
See docs/DESIGN.md section 3.
Update — 2026-09-01: drawn digits, an emphasised slot, and a narrower Phase 1
Three changes.
1 — Digits are drawn, not shipped as fonts (D33). "A custom numeric font", singular, understated
the cost: Pebble bitmap fonts are generated per size, and this design needs 140, 96, 56, 44 and 40
px on
emeryplus round variants — about eight font resources. Since the glyph set is a dozencharacters, draw them procedurally instead.
0–9,.,:,-,/drawn procedurally from rounded barsCustom numeric font subsetandresource size measured— no longer applicable2 —
QUADandGRID6get an emphasised slot (D29). As written, both present only equal-weightnumbers, which contradicts NFR-U7 — and two of the three shipped pages are
QUAD.QUADandGRID6drawn larger than the rest: one emphasised cell plus three, andone plus five
3 — Phase 1 narrows to
emery,HERO2andQUAD(D32).HERO1andGRID6move to Phase 1balongside #51 and #62, so the first rideable build lands sooner.
them on the host — this is what makes the deferred round work safe to do later
At the corrected ~200 DPI (see the D24 correction),
GRID6's 40 px digits are 5.0 mm tall. It ships,but it is not a default and should not be recommended in the configuration UI.
Update — 2026-09-02: render the two states differently
--(unavailable) are visually distinct, not the same treatment (D44)docs/DESIGN.md, neither state is signalled by colour alone (#56)PR #87 (merged): Phase 1 scope only, per this issue's own 2026-09-01 update (D32) \u2014 emery, HERO2 and QUAD. Delivers: procedurally-drawn digits (D33, seven-segment/rounded-bar, zero font resources, verified correct segment masks and a real '.' vs ':' distinction), the D29 emphasised-slot size lookup, host-testable bounds-in/rects-out geometry (28 new test cases), D44's stale-vs-unavailable visual distinction (hollow+strike vs solid dashes, confirmed in emulator screenshots), and a real measured render cost (24\u201337ms/frame in the emery emulator, comfortably inside the 1Hz budget). Caught and fixed one real bug from the screenshots themselves (GPS-quality icon bars rendering as a solid blob).\n\nLeft open \u2014 not this PR's job, per D32's own phasing:
HERO1andGRID6templates (Phase 1b, alongside #51/#62),gabbro/basaltrendering (builds clean, geometry stays bounds-in/rects-out so this stays possible, but nothing has been visually verified on those two \u2014 that's #62's job), German labels (#51). Status strip's GPS-quality and speed-source icons are placeholders \u2014 no GPS or BLE sensor code exists anywhere yet to source real data from; clock and ride-state/Bluetooth are real. No periodic UI-repaint tick exists either, so the clock is correct per-render but doesn't advance while the screen sits idle \u2014 flagged as a gap for a future issue, not silently accepted.PR #120 merged — the Phase 1b remainder:
HERO1(full content area, 140px per DESIGN.md's literal figure) andGRID6(2x3, 40px ordinary / 46px emphasised slot 0 per D29), pluspage_render_resolve_template()implementing "GRID6 may fall back to QUAD on basalt" as a pixel comparison against DESIGN.md's own 100x69 reference cell (deliberately platform-blind, so it correctly does NOT special-case gabbro — round geometry stays #62's job). Reuses D44's stale-vs-unavailable distinction and #87's ellipsis overflow handling with zero new code —prv_draw_cell()doesn't know which template called it. Real verification:pebble buildclean on all three targets, 38/38 host tests (hand-compiled with gcc, no cmake in this sandbox), real emulator screenshots on emery/gabbro/basalt, render cost measured (HERO1 25-46ms, GRID6 61-69ms on emery, up to 360ms on basalt — all comfortably under the 1Hz budget).This issue stays open. Its own "degrades without clipping on basalt" acceptance criterion is not actually met yet: PR #120's own basalt verification — the first time anyone actually looked at basalt output for these templates — found that HERO1 clips on both emery and basalt at the literal 140px figure, and that pre-existing QUAD (unchanged since Phase 1/PR #87) also clips on basalt, meaning GRID6's fallback target isn't itself basalt-safe. Independently re-verified this by direct arithmetic against the shipped glyph-width-ratio constants before accepting the claim (a "28.4"-shaped value needs
2.3 x digit_heightpx of run width; 2.3x140=322px against emery's 200px/basalt's 144px content — confirms the clip without needing to trust the screenshot description alone).Both findings share one root cause:
page_render_digit_height(template, slot)has no notion of the cell's actual width, so nothing currently prevents a digit run from overflowing regardless of template. This is a mechanism change touching every template already shipped, correctly out of scope for a two-template Phase 1b PR — filed separately as #121 with the exact numbers, and #59 now depends on it.44 issues closed (unrelated running count) — this one stays open pending #121.
Re-checked every acceptance criterion honestly now that #121 (PR #123) has landed, and this issue is genuinely done:
page_render.c:field_unit()is appended into the label text (prv_format_label), never a separate value-adjacent element.GTextOverflowModeTrailingEllipsisis applied to every label draw call, universally across all templates (not a per-template special case), so this was never just a GRID6-specific check; PR #120 additionally verified it with a real worst-case German label.page_render_fit_digit_height()), verified with 47 real host tests plus a cleanpebble buildon all three targets.Not required by this issue's own literal criteria, correctly left to #62: gabbro's round-bezel overshoot (a
page_render_content_rect()limitation affecting every template on round, not something #59 ever asked to be clipping-free against — this issue only names basalt). #121 stays open tracking that remainder without a formal dependency on #62 (would have created a cycle, since #62 itself already depends on #59 existing first — removed both problematic edges).45 issues closed.