Files
wadamesh/test/test_nav_spatial.cpp
Kaj SchittecatandClaude Opus 5.5 c2d7cd4d75 touch: telemetry Request button reachable with keys while a chart shows
Reported on Discord (benanderson, M9 beta_100): with the history chart open
the d-pad could not reach the Request button until the history was cleared.
The chart started 26 px down the card, inside the 32 px row of the Request /
gear / X buttons, and overlapped them horizontally, so it counted as the
nearest control left of the gear (and of the X). Left from the gear went to
the chart, Up from the chart back to the gear; Request was never chosen.
The chart now starts below that row.

The spatial ranking navMoveDir uses moved unchanged into NavSpatial.h
(navSpatialMetrics / navSpatialPick on plain rects) so it can be tested on
the host: test/test_nav_spatial.cpp lays out the M9 telemetry window and
shows Request unreachable from every control before, reachable from every
control after, plus the existing band rule (Send advert).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-28 10:14:37 +01:00

107 lines
4.2 KiB
C++

// SPDX-License-Identifier: GPL-3.0-or-later
//
// Host test for the keypad cursor's spatial step (src/ui-touch/NavSpatial.h, used by
// navMoveDir). The geometry is the telemetry window on the ThinkNode M9 (320x240):
// with a history chart showing, the Request button could not be reached with the
// d-pad (reported on Discord by benanderson, beta_100) because the chart reached up
// into the row of the Request / gear / X buttons and so counted as the nearest thing
// left of the gear.
//
// c++ -std=c++17 -Wall -Wextra -I src test/test_nav_spatial.cpp -o /tmp/navsp && /tmp/navsp
#include <assert.h>
#include <stdio.h>
#include "ui-touch/NavSpatial.h"
// The window's controls in the card's content coordinates, in the order UITask collects
// them (tree order): the X badge, the gear, Request, then the chart, the two hour
// fields and Show. Content width 282 (card 304, padding 10, border 1).
enum { X_BADGE, GEAR, REQUEST, CHART, FROM, TO, SHOW, N_CTRL };
static void telemetryLayout(NavRect* r, int chart_top) {
const int cw = 282;
r[X_BADGE] = { cw - 24 - 2, 2, cw - 2 - 1, 2 + 24 - 1 }; // 24 px, TOP_RIGHT -2,2
r[GEAR] = { cw - 32 - 34, 0, cw - 34 - 1, 31 }; // 32 px, TOP_RIGHT -34
r[REQUEST] = { cw - 32 - 68, 0, cw - 68 - 1, 31 }; // 32 px, TOP_RIGHT -68
r[CHART] = { 30, chart_top, 30 + (304 - 20 - 60) - 1, chart_top + 108 - 1 };
const int fy = chart_top - 4 + 108 + 28 + 18; // legend, then "Time range"
r[FROM] = { 38, fy, 38 + 44 - 1, fy + 31 };
r[TO] = { 132, fy, 132 + 44 - 1, fy + 31 };
r[SHOW] = { 200, fy, 200 + 64 - 1, fy + 31 };
}
// One arrow press from `from`: the candidates are every other control.
static int step(const NavRect* r, int from, int dir) {
NavRect cand[N_CTRL];
int idx[N_CTRL], n = 0;
for (int i = 0; i < N_CTRL; i++) {
if (i == from) continue;
cand[n] = r[i];
idx[n++] = i;
}
const int pick = navSpatialPick(r[from], cand, n, dir);
return pick < 0 ? from : idx[pick];
}
// Every control the cursor can reach from `start` with any number of arrow presses.
static unsigned reachable(const NavRect* r, int start) {
unsigned seen = 1u << start;
bool grew = true;
while (grew) {
grew = false;
for (int c = 0; c < N_CTRL; c++) {
if (!(seen & (1u << c))) continue;
for (int d = NAV_UP; d <= NAV_RIGHT; d++) {
const int to = step(r, c, d);
if (!(seen & (1u << to))) { seen |= 1u << to; grew = true; }
}
}
}
return seen;
}
int main() {
NavRect r[N_CTRL];
// Before: the chart started 26 px down (the title line plus 4), inside the 32 px
// button row. Left from the gear went to the chart, Up from the chart to the gear:
// Request was never reached, from anywhere.
telemetryLayout(r, 26);
assert(step(r, GEAR, NAV_LEFT) == CHART);
assert(step(r, CHART, NAV_UP) == GEAR);
for (int s = 0; s < N_CTRL; s++)
if (s != REQUEST) assert(!(reachable(r, s) & (1u << REQUEST)));
// After: the chart starts below the button row (32 + 4). Left from the gear is
// Request, and Request is reachable from every control in the window.
telemetryLayout(r, 36);
assert(step(r, X_BADGE, NAV_LEFT) == GEAR);
assert(step(r, GEAR, NAV_LEFT) == REQUEST);
assert(step(r, REQUEST, NAV_RIGHT) == GEAR);
assert(step(r, REQUEST, NAV_DOWN) == CHART);
assert(step(r, CHART, NAV_UP) == GEAR);
assert(step(r, CHART, NAV_DOWN) == FROM);
for (int s = 0; s < N_CTRL; s++) assert(reachable(r, s) == (1u << N_CTRL) - 1);
// The band rule the ranking is built on (Send advert): Down from a button lands on
// the narrow switch right below it, not the wide dropdown a row further down.
{
const NavRect flood = { 0, 0, 99, 29 };
const NavRect cand[] = {
{ 0, 80, 299, 109 }, // "Flood every" dropdown, full width, two rows down
{ 250, 40, 299, 69 }, // "On boot" switch, right-aligned, the next row
};
assert(navSpatialPick(flood, cand, 2, NAV_DOWN) == 1);
}
// Nothing in that direction: -1, and the caller keeps the focus.
{
const NavRect cur = { 0, 0, 10, 10 };
const NavRect cand[] = { { 0, 20, 10, 30 } };
assert(navSpatialPick(cur, cand, 1, NAV_UP) == -1);
}
printf("test_nav_spatial: OK\n");
return 0;
}