Fix touchscreen navigation and add runtime touch diagnostics

This commit is contained in:
mikecarper
2026-09-09 17:38:41 -07:00
parent 23fe7c8a9d
commit 14796e6573
32 changed files with 1785 additions and 123 deletions
@@ -15,6 +15,76 @@ TouchAction release(TouchInput& input, int width = 137, int height = 137) {
} // namespace
TEST(TouchInput, ReaderFooterHasFiveFullWidthTargetsIncludingExit) {
using mesh::ui::TouchNavigationBar;
// Same logical positions on the Indicator's 320 and 480 rendering profiles.
for (int width : {160, 320, 480}) {
for (bool mirrored : {false, true}) {
TouchInput input(true, true, 70, mirrored);
TouchNavigationBar bar;
bar.top = width - 24;
bar.height = 24;
bar.exit_height = 12;
const TouchAction expected[] = {TouchAction::VerticalPrevious,
TouchAction::Previous, TouchAction::Next,
TouchAction::VerticalNext, TouchAction::Select};
// Every point of each cell is usable, not only the arrow glyph.
for (int visual_x = 0; visual_x < width; ++visual_x) {
const int x = mirrored ? width - 1 - visual_x : visual_x;
input.update(true, x, bar.top, width, width, true, nullptr, &bar);
EXPECT_EQ(input.update(false, -1, -1, width, width, true, nullptr, &bar), TouchAction::None);
EXPECT_EQ(input.update(false, -1, -1, width, width, true, nullptr, &bar),
expected[visual_x / (width / 5)]);
EXPECT_EQ(input.update(false, -1, -1, width, width, true, nullptr, &bar), TouchAction::None);
}
// The top header is an explicit exit target across its entire width.
for (int visual_x : {0, width / 2, width - 1}) {
const int x = mirrored ? width - 1 - visual_x : visual_x;
input.update(true, x, 0, width, width, true, nullptr, &bar);
input.update(false, -1, -1, width, width, true, nullptr, &bar);
EXPECT_EQ(input.update(false, -1, -1, width, width, true, nullptr, &bar),
TouchAction::Select);
}
}
}
}
TEST(TouchInput, ReaderFooterDragDoesNotActivateExitOrEmitAnEndpointTap) {
mesh::ui::TouchNavigationBar bar;
bar.top = 130; bar.height = 16;
TouchInput input(true, true, 70, true);
input.update(true, 10, 135, 160, 160, true, nullptr, &bar);
input.update(true, 90, 135, 160, 160, true, nullptr, &bar);
EXPECT_EQ(input.update(false, -1, -1, 160, 160, true, nullptr, &bar), TouchAction::None);
// A transient lost sample cannot end the swipe.
input.update(true, 120, 135, 160, 160, true, nullptr, &bar);
input.update(false, -1, -1, 160, 160, true, nullptr, &bar);
EXPECT_EQ(input.update(false, -1, -1, 160, 160, true, nullptr, &bar), TouchAction::None);
EXPECT_EQ(input.update(false, -1, -1, 160, 160, true, nullptr, &bar), TouchAction::None);
// A one-sample contact away from the new targets is still ignored.
input.update(true, 80, 50, 160, 160, false, nullptr, &bar);
input.update(false, -1, -1, 160, 160, false, nullptr, &bar);
EXPECT_EQ(input.update(false, -1, -1, 160, 160, false, nullptr, &bar), TouchAction::None);
}
TEST(TouchInput, RemovedChannelBarDoesNotLeaveInvisibleSelectorAboveNavigation) {
mesh::ui::TouchNavigationBar bar;
bar.top = 144; bar.height = 16;
TouchInput input(true, true, 70, true);
auto tap = [&](int visual_x, int y) {
const int raw_x = 159 - visual_x;
input.update(true, raw_x, y, 160, 160, false, nullptr, &bar);
input.update(true, raw_x, y, 160, 160, false, nullptr, &bar);
input.update(false, -1, -1, 160, 160, false, nullptr, &bar);
return input.update(false, -1, -1, 160, 160, false, nullptr, &bar);
};
EXPECT_EQ(tap(0, 135), TouchAction::Previous);
EXPECT_EQ(tap(159, 135), TouchAction::Next);
EXPECT_EQ(tap(80, 135), TouchAction::Next);
EXPECT_EQ(tap(0, 144), TouchAction::VerticalPrevious);
EXPECT_EQ(tap(159, 159), TouchAction::Select); // X now owns the bottom-right.
}
TEST(TouchInput, EmitsOnlyAfterRelease) {
TouchInput input;
EXPECT_EQ(input.update(true, 70, 50, 137, 137), TouchAction::None);