Merge branch 'main' into feature/companion-repeater-presets

This commit is contained in:
Jakub
2026-06-18 18:01:33 +02:00
3 changed files with 50 additions and 29 deletions

View File

@@ -12,6 +12,7 @@
// being recorded.
#include "../GeoUtils.h"
#include "icons.h" // miniIconScale() — keeps the tape ticks/pointer legible on e-ink
class CompassScreen : public UIScreen {
UITask* _task;
@@ -66,17 +67,23 @@ public:
const float HALF_SPAN = 80.0f; // degrees visible to each side
const float ppd = (area_w / 2.0f) / HALF_SPAN;
const int pointerH = 6;
const int tickMajor = 6;
const int tickMinor = 3;
const int blockH = pointerH + 1 + tickMajor + 2 + ch;
// Tick/pointer sizes are authored at 1x and scaled by the font (same factor
// the mini-icon framework uses), so they stay legible next to the 2x
// cardinal labels and numeric readout on landscape e-ink.
const int s = miniIconScale(display);
const int sh = display.sepH(); // baseline thickness (2px landscape e-ink, else 1)
const int pointerH_units = 6;
const int pointerH = pointerH_units * s;
const int tickMajor = 6 * s;
const int tickMinor = 3 * s;
const int blockH = pointerH + sh + tickMajor + 2 + ch;
const int block_top = top + ((readout_y - top) - blockH) / 2;
const int line_y = block_top + pointerH; // scale baseline
const int label_y = line_y + 1 + tickMajor + 2;
const int label_y = line_y + sh + tickMajor + 2;
const int x_lo = area_x, x_hi = area_x + area_w;
// Scale baseline.
display.fillRect(area_x, line_y, area_w, 1);
display.fillRect(area_x, line_y, area_w, sh);
// Ticks + cardinal labels every 30°; majors (N/E/S/W) are taller + labelled.
for (int deg = 0; deg < 360; deg += 30) {
@@ -86,19 +93,21 @@ public:
int x = cx + (int)(delta * ppd);
if (x < x_lo || x > x_hi) continue;
bool major = (deg % 90 == 0);
display.fillRect(x, line_y + 1, 1, major ? tickMajor : tickMinor);
display.fillRect(x - s / 2, line_y + sh, s, major ? tickMajor : tickMinor);
if (major) {
const char* s = cardinalLetter(deg);
const char* letter = cardinalLetter(deg);
display.setCursor(x - cw / 2, label_y);
display.print(s);
display.print(letter);
}
}
// Fixed travel-direction pointer: a downward triangle whose tip touches the
// baseline at screen centre (your current course always sits under it).
for (int i = 0; i < pointerH; i++) {
int half = pointerH - 1 - i;
display.fillRect(cx - half, block_top + i, 2 * half + 1, 1);
// Each of the pointerH_units logical rows is drawn as an s-pixel-tall strip
// — the same block-scaling miniIconDraw uses for bitmap icons.
for (int i = 0; i < pointerH_units; i++) {
int half = (pointerH_units - 1 - i) * s;
display.fillRect(cx - half, block_top + i * s, 2 * half + 1, s);
}
// ── numeric readout ──────────────────────────────────────────────────────

View File

@@ -1,6 +1,7 @@
#pragma once
#include <helpers/ui/DisplayDriver.h>
#include <Arduino.h>
#include "icons.h" // scalable scroll indicator (track + thumb), matches the rest of the UI
// Generic scrollable popup menu overlay.
// Caller owns item string lifetimes — addItem() stores const char* pointers.
@@ -63,7 +64,10 @@ struct PopupMenu {
if (_scroll < 0) _scroll = 0;
bool can_scroll = (_count > vis);
int arrow_w = can_scroll ? (cw + 2) : 0; // right gutter for ^/v markers
// Right gutter for the scroll indicator column, plus a couple px of
// clearance from the box border (full-screen lists sit flush against the
// screen edge instead, so they don't need this extra margin).
int arrow_w = can_scroll ? (scrollIndicatorColWidth(display) + 2) : 0;
// Box width: widest of the title / all items, plus padding and the arrow
// gutter; clamped to the screen with a sane minimum.
@@ -111,20 +115,10 @@ struct PopupMenu {
display.setColor(DisplayDriver::LIGHT);
}
// Scroll markers in the reserved gutter, drawn on top of their row and
// inverted on the selection bar so they stay visible.
if (can_scroll) {
int ax = bx + bw - arrow_w;
if (_scroll > 0) {
display.setColor(_scroll == _sel ? DisplayDriver::DARK : DisplayDriver::LIGHT);
display.setCursor(ax, list_y + 1); display.print("^");
}
if (_scroll + vis < _count) {
int last = _scroll + vis - 1;
display.setColor(last == _sel ? DisplayDriver::DARK : DisplayDriver::LIGHT);
display.setCursor(ax, list_y + (vis - 1) * item_h + 1); display.print("v");
}
}
// Same proportional track + thumb indicator the rest of the UI uses,
// anchored to the box's own right edge (a couple px clear of the border)
// instead of the screen edge since this box floats centred on screen.
drawScrollIndicator(display, bx + bw - 2, list_y, vis * item_h, _count, vis, _scroll);
display.setColor(DisplayDriver::LIGHT);
return 50;
}

View File

@@ -285,7 +285,11 @@ inline int scrollIndicatorReserve(DisplayDriver& d, int total, int visible) {
// total_px : total extent of the whole list (items or pixels)
// view_px : extent currently visible
// scroll_px : extent scrolled past (offset of the first visible item)
inline void drawScrollIndicatorPx(DisplayDriver& d, int top_y, int track_h,
// right_x : column origin's right edge, in screen pixels (the indicator
// occupies [right_x - col, right_x)). Full-screen lists pass
// d.width(); a narrower container (e.g. a centred popup) passes its
// own right edge so the indicator lands inside the box, not the screen.
inline void drawScrollIndicatorPx(DisplayDriver& d, int right_x, int top_y, int track_h,
long total_px, long view_px, long scroll_px) {
if (total_px <= view_px || track_h <= 0) return; // whole list fits — no indicator
const long span = total_px - view_px; // > 0 here
@@ -295,7 +299,7 @@ inline void drawScrollIndicatorPx(DisplayDriver& d, int top_y, int track_h,
const int s = miniIconScale(d);
const int col = 5 * s; // triangle / column width
const int th = 3 * s; // triangle height
const int x = d.width() - col; // indicator column origin
const int x = right_x - col; // indicator column origin
// Caps are static end-markers, always drawn flush at the very top / bottom of
// the track; the thumb travels in the fixed band between them. (They mark the
@@ -330,6 +334,13 @@ inline void drawScrollIndicatorPx(DisplayDriver& d, int top_y, int track_h,
miniIconDrawHalo(d, x, top_y + track_h - th, ICON_SCROLL_DOWN);
}
// Convenience overload anchored to the screen's right edge — the common case
// for full-width lists.
inline void drawScrollIndicatorPx(DisplayDriver& d, int top_y, int track_h,
long total_px, long view_px, long scroll_px) {
drawScrollIndicatorPx(d, d.width(), top_y, track_h, total_px, view_px, scroll_px);
}
// Uniform-height wrapper: one item = one unit. Drop-in replacement for
// DisplayDriver::drawScrollArrows.
// total : total number of items
@@ -340,6 +351,13 @@ inline void drawScrollIndicator(DisplayDriver& d, int top_y, int track_h,
drawScrollIndicatorPx(d, top_y, track_h, total, visible, first);
}
// right_x variant — see drawScrollIndicatorPx's right_x for why a narrower
// container (e.g. a popup) needs this instead of the screen-edge default.
inline void drawScrollIndicator(DisplayDriver& d, int right_x, int top_y, int track_h,
int total, int visible, int first) {
drawScrollIndicatorPx(d, right_x, top_y, track_h, total, visible, first);
}
// Scrollable item-list skeleton shared by the list screens. Computes the visible
// window from the font metrics, keeps `sel` in view, reserves the scroll-column,
// draws each visible row through `row`, then the indicator. The callback