Skip to content

Resolution-flexible UI: render SSD1333 (176×176) and SSD1351 (128×128) from shared geometry#453

Merged
brickbots merged 1 commit into
mainfrom
feature/resolution-flexible-ui
Jun 5, 2026
Merged

Resolution-flexible UI: render SSD1333 (176×176) and SSD1351 (128×128) from shared geometry#453
brickbots merged 1 commit into
mainfrom
feature/resolution-flexible-ui

Conversation

@brickbots
Copy link
Copy Markdown
Owner

Summary

Adapts the UI to render at the display's resolution instead of a hardcoded 128×128, so the new 1.91″ 176×176 SSD1333 and the existing 1.5″ 128×128 SSD1351 share one codebase. Layout (row counts, line positions, text anchors, image scaling, entry-box grids, scroll bars) derives from display_class.resX/resY + font metrics; a small set of per-display knobs (font sizes, titlebar_height, menu_visible_items) is hand-tuned per panel. The hybrid approach (derive geometry + per-display knobs) is recorded in docs/adr/0009-resolution-flexible-ui-hybrid.md.

What's in it (29 files, +1032 / −423)

  • ui/layout.py (new) — the geometry engine: carousel_layout, list_layout, rows_below_titlebar, center_box_row.
  • displays.pyLayout176 mixin, DisplayPygame_176 / DisplayHeadless176, the menu_visible_items knob (must be odd).
  • Screens derive layout from resolution: base, text_menu, object_list, object_details, chart, preview, align, console, status, sqm, equipment, software, log, location_list, the time/date/location/radec/text entry grids, and the SQM calibration/sweep/correction screens, ui_utils.
  • Three 176-panel fixes found during hardware validation:
    • carousel selection box now keeps clearance from the row above (was touching it);
    • help screens normalize to the device resolution (fixes a luma image.size == device.size assert crash on 176);
    • the radial marking-menu radius scales with resolution so the curved labels stay inside their pie slices.
  • docs/adr/0009 + docs/ax/ui — the hybrid decision + resolution terminology.

Deferred to new_hardware_features

  • Boot splash (splash.py) is intentionally not in this PR. Its panel auto-detection depends on hardware_detect (the rev-4 battery marker), which isn't on main yet; and on main the splash always runs at 128, so the resolution scaling would be a no-op. It lands with NHF.

Validation (at both 128 and 176)

  • ruff check + ruff format clean; mypy clean (only pre-existing pandas/requests stub errors in plot.py/comets.py/software.py).
  • pytest -m "smoke or unit"285 passed.
  • A crash-sweep of 16 screens through the real device.display() path renders cleanly at 128 and 176 (every screen produces a resX×resY image the device accepts).

Notes for reviewers

  • Built on current main (post the SSD1333 base merge). Independent of new_hardware_features.
  • The screens were originally hardware-validated on top of NHF and re-derived onto main's versions here; only preview.py needed manual work (main's focus-strip rework had replaced the reticle the resolution patch targeted). A quick on-panel pass on the re-derived screens is worthwhile before merge.

🤖 Generated with Claude Code

…from shared geometry

Adapt the PiFinder UI to render at the display's resolution instead of a
hardcoded 128x128, so the 1.91" 176x176 SSD1333 and the 1.5" 128x128
SSD1351 share one codebase. Geometry (row counts, line positions, text
anchors, image scaling, entry-box grids, scroll bars) derives from
display_class.resX/resY + font metrics; a small set of per-display knobs
(font sizes, titlebar_height, menu_visible_items) is hand-tuned per panel.

- ui/layout.py (new): carousel_layout / list_layout / rows_below_titlebar
  / center_box_row -- the shared geometry helpers.
- displays.py: Layout176 mixin + DisplayPygame_176 / DisplayHeadless176;
  menu_visible_items knob (must be odd).
- Screens derive layout from resolution: base, text_menu, object_list,
  object_details, chart, preview, align, console, status, sqm, equipment,
  software, log, location_list, the time/date/location/radec/text entry
  grids, and the SQM calibration/sweep/correction screens, ui_utils.
- Three 176-panel fixes from hardware validation: carousel selection box
  keeps clearance from the row above; help screens normalize to the device
  resolution (no luma size-assert crash); the radial marking-menu radius
  scales with resolution so labels stay inside their slices.
- docs/adr/0009 + docs/ax/ui: the hybrid (derive geometry + per-display
  knobs) decision.

Boot splash (splash.py) is intentionally deferred to new_hardware_features:
its panel auto-detection needs hardware_detect (the rev-4 battery marker),
which isn't on main yet, and on main the splash is always 128 (scaling
would be a no-op).

Validated at 128 and 176: ruff + mypy clean (pre-existing pandas/requests
stub errors only); pytest -m "smoke or unit" -> 285 passed; a crash-sweep
of 16 screens through the real device.display() path renders cleanly at
both resolutions.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@brickbots brickbots merged commit cffcb7e into main Jun 5, 2026
1 check passed
brickbots added a commit that referenced this pull request Jun 5, 2026
The focus-quality (HFD) strip was added in #449 before the
resolution-flexible UI work in #453. #453 converted the camera preview's
image resize and star selectors to derive from the display resolution but
never touched draw_focus_strip(), which kept 128-only literals:

  strip_top = 90      # bottom-band top
  plot_top  = +9      # V-curve top clearance
  plot_bottom = -10   # V-curve bottom clearance
  bottom label = -9

On the 176x176 SSD1333 panel strip_top=90 lands mid-screen (~51% down)
instead of as a bottom band, stranding the exposure/matched-star labels
high up with a large empty gap above the readout, and the bottom
detected-star label clips at the screen edge.

Derive the geometry instead (ADR 0009):

  - strip_top: a fixed fraction of screen height (res_y - res_y*38/128),
    so the band stays ~30% of the screen on any panel.
  - label-row clearances: from self.fonts.small.height, so the rows track
    the (larger) small font on the 176 panel and never collide with the
    V-curve.

The 128 small font is 9 px, so strip_top+9 == strip_top+small_h,
res_y-10 == res_y-small_h-1, and res_y-9 == res_y-small_h: the 128 layout
is reproduced pixel-for-pixel (zero regression), while the 176 layout
becomes a correct proportional bottom band. Verified headless on both
DisplayHeadless (128) and DisplayHeadless176 (176).

Docs: update the CONTEXT.md focus-strip glossary entry, which described a
fixed "~38 px" band, to note it scales with the display.

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant