Round display support (gabbro): round template variants and arc status #62

Open
opened 2026-08-31 18:06:56 +02:00 by robert · 1 comment
robert commented 2026-08-31 18:06:56 +02:00 (Migrated from git.butzei.de)

Goal

Make the Pebble Round 2 a device the app was designed for rather than tolerated on. 260x260 round, but a circle of that size inscribes only about 184x184 - so a screen with more total pixels than emery has less usable area for grids.

Acceptance criteria

  • HERO1 centred with ~170 px digits - the template that suits a circle best
  • HERO2 hero centred, the two cells inset to the chord width at their vertical position
  • QUAD inset to the inscribed square, cells about 92x92
  • GRID6 falls back to QUAD on gabbro, as it does on basalt
  • Status becomes an arc along the top bezel using graphics_draw_arc() and gpoint_from_polar(), with glyphs placed radially - a straight strip has almost no width on a circle
  • NAV centred turn arrow with arc-aligned text
  • MAP round-native: a heading-up centred map, which suits a circle better than a rectangle
  • Variants selected with PBL_ROUND / PBL_IF_ROUND_ELSE(), never with runtime screen-size checks
  • No hard-coded coordinates anywhere - everything derived from layer_get_bounds()
  • gbitmap_get_data_row_info() used wherever the framebuffer is touched directly; rows are shorter near the top and bottom of a round display
  • text_layer_enable_screen_text_flow_and_paging() enabled for text that can overflow, so it follows the curve rather than clipping
  • Every page verified in the gabbro emulator and, where hardware allows, on device

Files

  • watchapp/src/c/page_render.c
  • watchapp/src/c/status_bar.c

Notes

Rectangular layouts do NOT auto-adapt to round - the SDK's automatic scaling is chalk to gabbro, which is round to round. See docs/DESIGN.md section 3 and docs/DECISIONS.md D24.

Update — 2026-09-01: moved to Phase 1b, and the DPI figure was wrong

Moved to Phase 1b — Generalise the display (D32). Round support is not cancelled; it is better
designed after a few real rides on the rectangle than before the first one, and it was landing before
#4 had confirmed the transport the whole architecture depends on.

Correction: the Round 2 is ~200 DPI, not 283. 260 px across a 1.3″ diagonal is about 200 — the
same density as the Time 2 (200 × 228 over 1.5″ ≈ 202 DPI). See the D24 correction.

  • A digit specified in pixels is the same physical size on both watches, so no per-device type
    scale is needed — but gabbro text cannot be smaller in pixels either, because the density it
    was credited with is not there
  • Digit sizes come from the procedural renderer (D33), so the round variants need no extra font
    resources
  • QUAD's emphasised slot 1 (D29) works within the inscribed square
  • Template geometry covered by the host-side tests in #68 — "no slot exceeds its bounds on
    emery, gabbro or basalt" as an assertion rather than a look in the emulator
## Goal Make the Pebble Round 2 a device the app was designed for rather than tolerated on. 260x260 round, but a circle of that size inscribes only about 184x184 - so a screen with more total pixels than emery has less usable area for grids. ## Acceptance criteria - [ ] `HERO1` centred with ~170 px digits - the template that suits a circle best - [ ] `HERO2` hero centred, the two cells inset to the chord width at their vertical position - [ ] `QUAD` inset to the inscribed square, cells about 92x92 - [ ] **`GRID6` falls back to `QUAD` on `gabbro`**, as it does on `basalt` - [ ] **Status becomes an arc** along the top bezel using `graphics_draw_arc()` and `gpoint_from_polar()`, with glyphs placed radially - a straight strip has almost no width on a circle - [ ] `NAV` centred turn arrow with arc-aligned text - [ ] `MAP` round-native: a heading-up centred map, which suits a circle better than a rectangle - [ ] Variants selected with `PBL_ROUND` / `PBL_IF_ROUND_ELSE()`, never with runtime screen-size checks - [ ] **No hard-coded coordinates anywhere** - everything derived from `layer_get_bounds()` - [ ] `gbitmap_get_data_row_info()` used wherever the framebuffer is touched directly; rows are shorter near the top and bottom of a round display - [ ] `text_layer_enable_screen_text_flow_and_paging()` enabled for text that can overflow, so it follows the curve rather than clipping - [ ] Every page verified in the `gabbro` emulator and, where hardware allows, on device ## Files - `watchapp/src/c/page_render.c` - `watchapp/src/c/status_bar.c` ## Notes Rectangular layouts do NOT auto-adapt to round - the SDK's automatic scaling is chalk to gabbro, which is round to round. See docs/DESIGN.md section 3 and docs/DECISIONS.md D24. ## Update — 2026-09-01: moved to Phase 1b, and the DPI figure was wrong Moved to **Phase 1b — Generalise the display** (D32). Round support is not cancelled; it is better designed after a few real rides on the rectangle than before the first one, and it was landing before #4 had confirmed the transport the whole architecture depends on. **Correction: the Round 2 is ~200 DPI, not 283.** 260 px across a 1.3″ diagonal is about 200 — the same density as the Time 2 (200 × 228 over 1.5″ ≈ 202 DPI). See the D24 correction. - [ ] A digit specified in pixels is the **same physical size on both watches**, so no per-device type scale is needed — but `gabbro` text cannot be smaller in pixels either, because the density it was credited with is not there - [ ] Digit sizes come from the procedural renderer (D33), so the round variants need no extra font resources - [ ] `QUAD`'s emphasised slot 1 (D29) works within the inscribed square - [ ] Template geometry covered by the host-side tests in #68 — "no slot exceeds its bounds on `emery`, `gabbro` or `basalt`" as an assertion rather than a look in the emulator
Owner

PR #125 (area/round-display-support) merged — but this issue depends on #35 and #42 (both still open, NAV/MAP round layouts), so Forgejo will correctly refuse to close it until those land. Leaving open with those edges in place (checked both for cycles before adding — no cycle, neither depends on #62).

Everything buildable without a live NAV/MAP page is done for real:

  • Real round-aware content rect (page_render_content_rect_round()): the largest square inscribed in the circle, computed via an integer Newton's-method isqrt (no <math.h>, matching this file's existing no-float rule) rather than a hard-coded gabbro constant — correct for any future round screen size on day one. I independently re-derived this by hand (2×130²=33800, isqrt→184) and it matches the PR's own claimed 184×184 exactly.
  • HERO1: unchanged function, just a bigger content square (170px ceiling via new page_render_digit_height_round()) — composes for free.
  • QUAD: composes for free — 184/2 = 92×92 cells, matching D24/DESIGN.md's own figure exactly (independently verified).
  • GRID6→QUAD fallback on gabbro: composes for free through the existing, still round-blind page_render_resolve_template() — a 92×61 cell now correctly fails its existing 100×69 threshold with no gabbro-specific code added to that function at all. Independently verified this arithmetic too.
  • HERO2: the one genuinely new geometry (chord-width math via Pythagoras, same integer isqrt) — sized off each row's farther edge from centre, a real correctness proof (checked directly in a host test, not just argued in a comment) that every corner stays inside the circle. Independently re-derived the exact hero/cell dimensions by hand (196px hero, 99×65 cells) — both matched the PR's own numbers precisely.
  • Arc status strip: page_render_status_arc_slot() (pure, host-tested angular spans) plus graphics_draw_arc()/gpoint_from_polar()/grect_centered_from_polar() in page_render.c — checked every one of those calls against the actual installed Core Devices SDK 4.33.1 header before trusting them; all real, matching signatures.
  • Composes with #121, doesn't bypass it: new page_render_fit_digit_height_2d() (height-aware) with the original width-only page_render_fit_digit_height() now a thin wrapper — needed since round's tighter cells exposed real vertical overflow (HERO1's round 170px ceiling vs. a 164px value area) that the width-only check couldn't catch. Every existing #121 test still passes unmodified.
  • No fabricated framebuffer/NAV/MAP work: confirmed via grep that nothing in this codebase touches the framebuffer directly (so gbitmap_get_data_row_info() genuinely has no call site — not a skipped criterion, a correctly-inapplicable one), and did not fabricate a temporary NAV/MAP page just to exercise those criteria.

Real verification: genuine pebble build clean on all three targets, real gabbro/emery/basalt emulator screenshots, and 193/193 host assertions across all five test suites hand-compiled with gcc -Wall -Wextra -Werror (no cmake in this sandbox) — I re-ran every one of them myself, exact match to the PR's own claimed count.

47 issues closed.

PR #125 (`area/round-display-support`) merged — but this issue depends on #35 and #42 (both still open, NAV/MAP round layouts), so Forgejo will correctly refuse to close it until those land. Leaving open with those edges in place (checked both for cycles before adding — no cycle, neither depends on #62). Everything buildable without a live NAV/MAP page is done for real: - **Real round-aware content rect** (`page_render_content_rect_round()`): the largest square inscribed in the circle, computed via an integer Newton's-method isqrt (no `<math.h>`, matching this file's existing no-float rule) rather than a hard-coded gabbro constant — correct for any future round screen size on day one. I independently re-derived this by hand (2×130²=33800, isqrt→184) and it matches the PR's own claimed 184×184 exactly. - **HERO1**: unchanged function, just a bigger content square (170px ceiling via new `page_render_digit_height_round()`) — composes for free. - **QUAD**: composes for free — 184/2 = 92×92 cells, matching D24/DESIGN.md's own figure exactly (independently verified). - **GRID6→QUAD fallback on gabbro**: composes for free through the *existing*, still round-blind `page_render_resolve_template()` — a 92×61 cell now correctly fails its existing 100×69 threshold with no gabbro-specific code added to that function at all. Independently verified this arithmetic too. - **HERO2**: the one genuinely new geometry (chord-width math via Pythagoras, same integer isqrt) — sized off each row's *farther* edge from centre, a real correctness proof (checked directly in a host test, not just argued in a comment) that every corner stays inside the circle. Independently re-derived the exact hero/cell dimensions by hand (196px hero, 99×65 cells) — both matched the PR's own numbers precisely. - **Arc status strip**: `page_render_status_arc_slot()` (pure, host-tested angular spans) plus `graphics_draw_arc()`/`gpoint_from_polar()`/`grect_centered_from_polar()` in `page_render.c` — checked every one of those calls against the actual installed Core Devices SDK 4.33.1 header before trusting them; all real, matching signatures. - **Composes with #121, doesn't bypass it**: new `page_render_fit_digit_height_2d()` (height-aware) with the original width-only `page_render_fit_digit_height()` now a thin wrapper — needed since round's tighter cells exposed real vertical overflow (HERO1's round 170px ceiling vs. a 164px value area) that the width-only check couldn't catch. Every existing #121 test still passes unmodified. - **No fabricated framebuffer/NAV/MAP work**: confirmed via grep that nothing in this codebase touches the framebuffer directly (so `gbitmap_get_data_row_info()` genuinely has no call site — not a skipped criterion, a correctly-inapplicable one), and did not fabricate a temporary NAV/MAP page just to exercise those criteria. **Real verification**: genuine `pebble build` clean on all three targets, real gabbro/emery/basalt emulator screenshots, and 193/193 host assertions across all five test suites hand-compiled with `gcc -Wall -Wextra -Werror` (no cmake in this sandbox) — I re-ran every one of them myself, exact match to the PR's own claimed count. 47 issues closed.
Sign in to join this conversation.
No project
No assignees
2 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Reference
robert/PedalPebble#62
No description provided.