Add touch-to-logical coordinate mapping for UI

Implements tapToLogical() to convert normalized touch coordinates to orientation-aware logical screen coordinates. Adds TouchRegistry hit testing for interactive UI elements and header back button. Touch gestures now work correctly across all screen orientations (Portrait, Landscape, etc.) by inverting the rotateCoordinates transform.
This commit is contained in:
Justin Mitchell
2026-06-15 15:32:22 -04:00
parent a51098725d
commit 7294610894
25 changed files with 275 additions and 14 deletions
+11
View File
@@ -12,6 +12,7 @@
#include "I18n.h"
#include "RecentBooksStore.h"
#include "components/TouchRegistry.h"
#include "components/UITheme.h"
#include "fontIds.h"
@@ -303,6 +304,7 @@ void BaseTheme::drawList(const GfxRenderer& renderer, Rect rect, int itemCount,
const auto pageStartIndex = selectedIndex / pageItems * pageItems;
for (int i = pageStartIndex; i < itemCount && i < pageStartIndex + pageItems; i++) {
const int itemY = rect.y + (i % pageItems) * rowHeight;
TouchRegistry::getInstance().add(Rect{rect.x, itemY - 2, rect.width, rowHeight}, i, TouchRegistry::Item);
int rowTextWidth = contentWidth - BaseMetrics::values.contentSidePadding * 2;
std::string valueText;
@@ -353,6 +355,11 @@ void BaseTheme::drawList(const GfxRenderer& renderer, Rect rect, int itemCount,
}
void BaseTheme::drawHeader(const GfxRenderer& renderer, Rect rect, const char* title, const char* subtitle) const {
// Left strip of the header is a tap-to-go-back zone (orientation-mapped; the
// title is centered so this area is normally empty). Mirrors the legacy corner
// gesture but as a real, theme-positioned target.
TouchRegistry::getInstance().add(Rect{rect.x, rect.y, 64, rect.height + 8}, -1, TouchRegistry::Back);
// Hide last battery draw
constexpr int maxBatteryWidth = 80;
renderer.fillRect(rect.x + rect.width - maxBatteryWidth, rect.y + 5, maxBatteryWidth,
@@ -669,6 +676,10 @@ void BaseTheme::drawButtonMenu(GfxRenderer& renderer, Rect rect, int buttonCount
for (int i = 0; i < buttonCount; ++i) {
const int tileY = BaseMetrics::values.verticalSpacing + rect.y +
static_cast<int>(i) * (BaseMetrics::values.menuRowHeight + BaseMetrics::values.menuSpacing);
TouchRegistry::getInstance().add(
Rect{rect.x + BaseMetrics::values.contentSidePadding, tileY,
rect.width - BaseMetrics::values.contentSidePadding * 2, BaseMetrics::values.menuRowHeight},
i, TouchRegistry::Item);
const bool selected = selectedIndex == i;
+3
View File
@@ -16,6 +16,9 @@ struct Rect {
int height;
explicit Rect(int x = 0, int y = 0, int width = 0, int height = 0) : x(x), y(y), width(width), height(height) {}
// Logical-coordinate point hit-test (used for touch target hit-testing).
bool contains(int px, int py) const { return px >= x && px < x + width && py >= y && py < y + height; }
};
struct TabInfo {
+4
View File
@@ -11,6 +11,7 @@
#include <vector>
#include "RecentBooksStore.h"
#include "components/TouchRegistry.h"
#include "components/UITheme.h"
#include "components/icons/book.h"
#include "components/icons/book24.h"
@@ -106,6 +107,7 @@ void LyraTheme::fillBatteryIcon(const GfxRenderer& renderer, Rect rect, uint16_t
void LyraTheme::drawHeader(const GfxRenderer& renderer, Rect rect, const char* title, const char* subtitle) const {
renderer.fillRect(rect.x, rect.y, rect.width, rect.height, false);
TouchRegistry::getInstance().add(Rect{rect.x, rect.y, 64, rect.height + 8}, -1, TouchRegistry::Back);
const bool showBatteryPercentage =
SETTINGS.hideBatteryPercentage != CrossPointSettings::HIDE_BATTERY_PERCENTAGE::HIDE_ALWAYS;
@@ -258,6 +260,7 @@ void LyraTheme::drawList(const GfxRenderer& renderer, Rect rect, int itemCount,
int iconY = (rowSubtitle != nullptr) ? 16 : 10;
for (int i = pageStartIndex; i < itemCount && i < pageStartIndex + pageItems; i++) {
const int itemY = rect.y + (i % pageItems) * rowHeight;
TouchRegistry::getInstance().add(Rect{rect.x, itemY, rect.width, rowHeight}, i, TouchRegistry::Item);
int rowTextWidth = textWidth;
// Draw name
@@ -517,6 +520,7 @@ void LyraTheme::drawButtonMenu(GfxRenderer& renderer, Rect rect, int buttonCount
Rect tileRect = Rect{rect.x + LyraMetrics::values.contentSidePadding,
rect.y + i * (LyraMetrics::values.menuRowHeight + LyraMetrics::values.menuSpacing), tileWidth,
LyraMetrics::values.menuRowHeight};
TouchRegistry::getInstance().add(tileRect, i, TouchRegistry::Item);
const bool selected = selectedIndex == i;
@@ -9,6 +9,7 @@
#include <vector>
#include "RecentBooksStore.h"
#include "components/TouchRegistry.h"
#include "components/UITheme.h"
#include "components/icons/cover.h"
#include "fontIds.h"
@@ -216,6 +217,7 @@ void RoundedRaffTheme::drawButtonMenu(GfxRenderer& renderer, Rect rect, int butt
renderer.truncatedText(kTitleFontId, label.c_str(), maxLabelWidth, EpdFontFamily::BOLD);
const int rowWidth = std::min(
menuMaxWidth, renderer.getTextWidth(kTitleFontId, truncatedLabel.c_str(), EpdFontFamily::BOLD) + kRowPaddingX);
TouchRegistry::getInstance().add(Rect{rowX, rowY, rowWidth, rowHeight}, i, TouchRegistry::Item);
const bool isSelected = selectedIndex == i;
renderer.fillRoundedRect(rowX, rowY, rowWidth, rowHeight, kMenuRadius, isSelected ? Color::Black : Color::White);
const int textY = rowY + (rowHeight - textLineHeight) / 2;
@@ -329,6 +331,7 @@ void RoundedRaffTheme::drawList(const GfxRenderer& renderer, Rect rect, int item
for (int i = pageStartIndex; i < itemCount && i < pageStartIndex + pageItems; i++) {
const int rowY = rect.y + (i % pageItems) * rowStep;
TouchRegistry::getInstance().add(Rect{rowX, rowY, rowWidth, rowHeight}, i, TouchRegistry::Item);
const bool isSelected = i == selectedIndex;
renderer.fillRoundedRect(rowX, rowY, rowWidth, rowHeight, kRowRadius, isSelected ? Color::Black : Color::White);