diff --git a/src/MappedInputManager.cpp b/src/MappedInputManager.cpp index 317958a8..9fbb1bb7 100644 --- a/src/MappedInputManager.cpp +++ b/src/MappedInputManager.cpp @@ -82,6 +82,22 @@ bool MappedInputManager::wasItemTapped(int& id) const { return TouchRegistry::getInstance().hitTest(lx, ly, TouchRegistry::Item, id); } +bool MappedInputManager::wasTabTapped(int& id) const { + float nx = 0.0f, ny = 0.0f; + if (!gpio.wasTouchTap(nx, ny)) return false; + int lx = 0, ly = 0; + renderer.tapToLogical(nx, ny, lx, ly); + return TouchRegistry::getInstance().hitTest(lx, ly, TouchRegistry::Tab, id); +} + +bool MappedInputManager::wasCoverTapped(int& id) const { + float nx = 0.0f, ny = 0.0f; + if (!gpio.wasTouchTap(nx, ny)) return false; + int lx = 0, ly = 0; + renderer.tapToLogical(nx, ny, lx, ly); + return TouchRegistry::getInstance().hitTest(lx, ly, TouchRegistry::Cover, id); +} + bool MappedInputManager::wasPressed(const Button button) const { // A top-left tap fires on the release frame; expose it on Back's press edge too // so menus that act on wasPressed(Back) also respond. Deliberately NOT folded diff --git a/src/MappedInputManager.h b/src/MappedInputManager.h index 5480e40e..1655a46f 100644 --- a/src/MappedInputManager.h +++ b/src/MappedInputManager.h @@ -31,6 +31,11 @@ class MappedInputManager { // element's id. Activities treat the id as "select + activate". False on // non-touch devices or when the tap missed every target. bool wasItemTapped(int& id) const; + // Like wasItemTapped, but for tab-bar tabs (id = tab index) and cover/card + // targets (id = item index). Distinct kinds so screens with both a list and a + // tab bar / cover (Home, Settings) don't confuse them. + bool wasTabTapped(int& id) const; + bool wasCoverTapped(int& id) const; bool wasAnyPressed() const; bool wasAnyReleased() const; unsigned long getHeldTime() const; diff --git a/src/activities/home/HomeActivity.cpp b/src/activities/home/HomeActivity.cpp index d9fdd148..09ab4ad4 100644 --- a/src/activities/home/HomeActivity.cpp +++ b/src/activities/home/HomeActivity.cpp @@ -189,7 +189,14 @@ void HomeActivity::loop() { selectorIndex = static_cast(recentBooks.size()) + tappedId; } - if (tapped || mappedInput.wasReleased(MappedInputManager::Button::Confirm)) { + // A tap on the continue-reading cover selects that book (home selector index). + int coverId = -1; + const bool coverTapped = mappedInput.wasCoverTapped(coverId); + if (coverTapped && coverId >= 0 && coverId < static_cast(recentBooks.size())) { + selectorIndex = coverId; + } + + if (tapped || coverTapped || mappedInput.wasReleased(MappedInputManager::Button::Confirm)) { if (selectorIndex < recentBooks.size()) { onSelectBook(recentBooks[selectorIndex].path); } else { diff --git a/src/activities/settings/SettingsActivity.cpp b/src/activities/settings/SettingsActivity.cpp index d11a1f14..4295b143 100644 --- a/src/activities/settings/SettingsActivity.cpp +++ b/src/activities/settings/SettingsActivity.cpp @@ -126,6 +126,15 @@ void SettingsActivity::loop() { return; } + // A tap on a category tab switches to it (keeps focus on the tab row); the + // hasChangedCategory block below swaps in that category's settings list. + int tabId = -1; + if (mappedInput.wasTabTapped(tabId) && tabId >= 0 && tabId < categoryCount) { + selectedCategoryIndex = tabId; + selectedSettingIndex = 0; + hasChangedCategory = true; + } + // Handle actions with early return if (mappedInput.wasPressed(MappedInputManager::Button::Confirm)) { if (selectedSettingIndex == 0) { diff --git a/src/activities/util/KeyboardEntryActivity.cpp b/src/activities/util/KeyboardEntryActivity.cpp index 7e5e1a7e..c5ebac16 100644 --- a/src/activities/util/KeyboardEntryActivity.cpp +++ b/src/activities/util/KeyboardEntryActivity.cpp @@ -6,6 +6,7 @@ #include #include "MappedInputManager.h" +#include "components/TouchRegistry.h" #include "components/UITheme.h" #include "fontIds.h" @@ -336,6 +337,18 @@ void KeyboardEntryActivity::loop() { } } + // A tap selects the key and presses it in one gesture. Encoded id = row*100+col + // (bottom function row = getContentRowCount()). Skipped in cursor mode, where a + // tap on a key would be ambiguous with cursor editing. + int tappedKey = -1; + if (!cursorMode && mappedInput.wasItemTapped(tappedKey)) { + selectedRow = tappedKey / 100; + selectedCol = tappedKey % 100; + if (handleKeyPress()) { + requestUpdate(); + } + } + if (mappedInput.wasReleased(MappedInputManager::Button::Confirm)) { if (confirmHeld && !confirmLongHandled && !cursorMode) { if (handleKeyPress()) { @@ -646,6 +659,8 @@ void KeyboardEntryActivity::render(RenderLock&&) { if (snippetIdx < URL_SNIPPET_COUNT) { GUI.drawKeyboardKey(renderer, Rect{keyX, rowY, keyWidth, keyHeight}, urlSnippets[snippetIdx], activeKeySelected, nullptr); + TouchRegistry::getInstance().add(Rect{keyX, rowY, keyWidth, keyHeight}, row * 100 + col, + TouchRegistry::Item); } } else { const KeyDef& key = layout[row][col]; @@ -663,6 +678,7 @@ void KeyboardEntryActivity::render(RenderLock&&) { const bool showSecondary = !symMode && row == 0 && secondaryChar != '\0'; GUI.drawKeyboardKey(renderer, Rect{keyX, rowY, keyWidth, keyHeight}, primaryBuf, activeKeySelected, showSecondary ? secondaryBuf : nullptr); + TouchRegistry::getInstance().add(Rect{keyX, rowY, keyWidth, keyHeight}, row * 100 + col, TouchRegistry::Item); } } } @@ -691,6 +707,8 @@ void KeyboardEntryActivity::render(RenderLock&&) { const bool activeKeySelected = isSelected && !cursorMode; GUI.drawKeyboardKey(renderer, Rect{keyX, bottomRowY, bottomKeyWidth, bottomKeyHeight}, bottomKeys[i].label, activeKeySelected, nullptr, bottomKeys[i].themeType); + TouchRegistry::getInstance().add(Rect{keyX, bottomRowY, bottomKeyWidth, bottomKeyHeight}, contentRows * 100 + i, + TouchRegistry::Item); } if (cursorMode) { diff --git a/src/components/TouchRegistry.h b/src/components/TouchRegistry.h index c5313d69..1b027834 100644 --- a/src/components/TouchRegistry.h +++ b/src/components/TouchRegistry.h @@ -19,7 +19,7 @@ // so add()/hitTest() are a single branch on the C3. class TouchRegistry { public: - enum Kind : uint8_t { Item = 0, Back = 1 }; + enum Kind : uint8_t { Item = 0, Back = 1, Tab = 2, Cover = 3 }; static TouchRegistry& getInstance(); @@ -36,7 +36,7 @@ class TouchRegistry { bool hitTest(int x, int y, Kind kind, int& outId) const; private: - static constexpr size_t CAPACITY = 48; // worst case excl. keyboard grid (Phase 2) + static constexpr size_t CAPACITY = 64; // worst case is the keyboard grid (~45 keys) struct Target { Rect rect; diff --git a/src/components/themes/BaseTheme.cpp b/src/components/themes/BaseTheme.cpp index b44d8c5b..869411f8 100644 --- a/src/components/themes/BaseTheme.cpp +++ b/src/components/themes/BaseTheme.cpp @@ -419,10 +419,16 @@ void BaseTheme::drawTabBar(const GfxRenderer& renderer, const Rect rect, const s int currentX = rect.x + BaseMetrics::values.contentSidePadding; + int tabIndex = 0; for (const auto& tab : tabs) { const int textWidth = renderer.getTextWidth(UI_12_FONT_ID, tab.label, tab.selected ? EpdFontFamily::BOLD : EpdFontFamily::REGULAR); + // Tap target spans the label plus the inter-tab gap, full bar height. + TouchRegistry::getInstance().add( + Rect{currentX - 3, rect.y, textWidth + BaseMetrics::values.tabSpacing, rect.height}, tabIndex++, + TouchRegistry::Tab); + // Draw underline for selected tab if (tab.selected) { if (selected) { @@ -495,6 +501,11 @@ void BaseTheme::drawRecentBookCover(GfxRenderer& renderer, Rect rect, const std: const int bookY = rect.y; const int bookHeight = baseHeight; + // Tapping the continue-reading cover opens recentBooks[0] (home selector 0). + if (hasContinueReading) { + TouchRegistry::getInstance().add(Rect{bookX, bookY, bookWidth, bookHeight}, 0, TouchRegistry::Cover); + } + // Bookmark dimensions (used in multiple places) const int bookmarkWidth = bookWidth / 8; const int bookmarkHeight = bookHeight / 5; diff --git a/src/components/themes/lyra/LyraTheme.cpp b/src/components/themes/lyra/LyraTheme.cpp index cf60bdd1..ecb69a35 100644 --- a/src/components/themes/lyra/LyraTheme.cpp +++ b/src/components/themes/lyra/LyraTheme.cpp @@ -183,9 +183,14 @@ void LyraTheme::drawTabBar(const GfxRenderer& renderer, Rect rect, const std::ve renderer.fillRectDither(rect.x, rect.y, rect.width, rect.height, Color::LightGray); } + int tabIndex = 0; for (const auto& tab : tabs) { const int textWidth = renderer.getTextWidth(UI_10_FONT_ID, tab.label, EpdFontFamily::REGULAR); + TouchRegistry::getInstance().add( + Rect{currentX, rect.y, textWidth + 2 * hPaddingInSelection + LyraMetrics::values.tabSpacing, rect.height}, + tabIndex++, TouchRegistry::Tab); + if (tab.selected) { if (selected) { renderer.fillRoundedRect(currentX, rect.y + 1, textWidth + 2 * hPaddingInSelection, rect.height - 4, @@ -419,6 +424,14 @@ void LyraTheme::drawRecentBookCover(GfxRenderer& renderer, Rect rect, const std: coverWidth = LyraMetrics::values.homeCoverHeight * 0.6; } + // Tapping the continue-reading cover opens recentBooks[0] (home selector 0). + if (hasContinueReading) { + TouchRegistry::getInstance().add( + Rect{LyraMetrics::values.contentSidePadding, tileY, coverWidth + 2 * hPaddingInSelection, + LyraMetrics::values.homeCoverHeight + 2 * hPaddingInSelection}, + 0, TouchRegistry::Cover); + } + // Draw book card regardless, fill with message based on `hasContinueReading` // Draw cover image as background if available (inside the box) // Only load from SD on first render, then use stored buffer diff --git a/src/components/themes/roundedraff/RoundedRaffTheme.cpp b/src/components/themes/roundedraff/RoundedRaffTheme.cpp index 4f2cdbe2..b3c8b0cb 100644 --- a/src/components/themes/roundedraff/RoundedRaffTheme.cpp +++ b/src/components/themes/roundedraff/RoundedRaffTheme.cpp @@ -99,6 +99,9 @@ void RoundedRaffTheme::drawTabBar(const GfxRenderer& renderer, Rect rect, const const int tabWidth = slotWidth - 8; const auto& tab = tabs[i]; + TouchRegistry::getInstance().add(Rect{slotX, rect.y, slotWidth, rect.height}, static_cast(i), + TouchRegistry::Tab); + if (tab.selected) { renderer.fillRoundedRect(tabX, tabY, tabWidth, tabHeight, 18, selected ? Color::Black : Color::DarkGray); } @@ -126,6 +129,13 @@ void RoundedRaffTheme::drawRecentBookCover(GfxRenderer& renderer, Rect rect, con const int imgY = tileY + (tileHeight - RoundedRaffMetrics::values.homeCoverHeight) / 2; const int tileX = RoundedRaffMetrics::values.contentSidePadding; + // Tapping the continue-reading cover opens recentBooks[0] (home selector 0). + if (hasContinueReading) { + TouchRegistry::getInstance().add( + Rect{tileX + (tileWidth - coverWidth) / 2, imgY, coverWidth, RoundedRaffMetrics::values.homeCoverHeight}, 0, + TouchRegistry::Cover); + } + // Draw book card regardless, fill with message based on `hasContinueReading` // Draw cover image as background if available (inside the box) // Only load from SD on first render, then use stored buffer