Add touch-down and long-press input detection
Implement touch-down event detection to provide immediate visual feedback when touching list items, mirroring button navigation behavior. Add long-press detection (500ms threshold) to distinguish between tap and hold gestures. Apply touch-down selection updates to file browser, home menu, and recent books activities.
This commit is contained in:
@@ -206,6 +206,14 @@ void FileBrowserActivity::loop() {
|
||||
const int pathReserved = renderer.getLineHeight(SMALL_FONT_ID) + UITheme::getInstance().getMetrics().verticalSpacing;
|
||||
const int pageItems = UITheme::getNumberOfItemsPerPage(renderer, true, false, true, false, pathReserved);
|
||||
|
||||
// Touch-down moves the selector to the pressed entry (shows selected state); release
|
||||
// opens it below.
|
||||
int downId = -1;
|
||||
if (mappedInput.wasItemTouchedDown(downId) && downId >= 0 && downId < static_cast<int>(files.size())) {
|
||||
selectorIndex = downId;
|
||||
requestUpdate();
|
||||
}
|
||||
|
||||
// A tap opens the tapped entry (held-time is 0 on a tap, so it takes the short-press
|
||||
// open path below, never the long-press delete).
|
||||
int tappedId = -1;
|
||||
|
||||
@@ -183,6 +183,14 @@ void HomeActivity::loop() {
|
||||
// menu-local ids (it is drawn with selectorIndex offset by recentBooks.size()),
|
||||
// so map the tapped id back into the global selector space. (The recent-book
|
||||
// cover is a separate, single-item draw path — tappable in a later phase.)
|
||||
// Touch-down moves the selector to the pressed menu button (shows selected state),
|
||||
// like Up/Down; release opens it below.
|
||||
int downId = -1;
|
||||
if (mappedInput.wasItemTouchedDown(downId)) {
|
||||
selectorIndex = static_cast<int>(recentBooks.size()) + downId;
|
||||
requestUpdate();
|
||||
}
|
||||
|
||||
int tappedId = -1;
|
||||
const bool tapped = mappedInput.wasItemTapped(tappedId);
|
||||
if (tapped) {
|
||||
|
||||
@@ -63,6 +63,12 @@ void RecentBooksActivity::loop() {
|
||||
return;
|
||||
}
|
||||
|
||||
int downId = -1;
|
||||
if (mappedInput.wasItemTouchedDown(downId) && downId >= 0 && downId < static_cast<int>(recentBooks.size())) {
|
||||
selectorIndex = downId;
|
||||
requestUpdate();
|
||||
}
|
||||
|
||||
int tappedId = -1;
|
||||
const bool tapped = mappedInput.wasItemTapped(tappedId);
|
||||
if (tapped && tappedId >= 0 && tappedId < static_cast<int>(recentBooks.size())) selectorIndex = tappedId;
|
||||
|
||||
@@ -4,13 +4,13 @@
|
||||
#include <GfxRenderer.h>
|
||||
#include <I18n.h>
|
||||
#include <WiFi.h>
|
||||
#include <esp_task_wdt.h>
|
||||
|
||||
#include "MappedInputManager.h"
|
||||
#include "SilentRestart.h"
|
||||
#include "WifiSelectionActivity.h"
|
||||
#include "components/UITheme.h"
|
||||
#include "fontIds.h"
|
||||
#include "network/TaskWatchdog.h"
|
||||
|
||||
namespace {
|
||||
constexpr const char* HOSTNAME = "crosspoint";
|
||||
@@ -110,12 +110,12 @@ void CalibreConnectActivity::loop() {
|
||||
LOG_DBG("CAL", "WARNING: %lu ms gap since last handleClient", timeSinceLastHandleClient);
|
||||
}
|
||||
|
||||
esp_task_wdt_reset();
|
||||
feedTaskWatchdog();
|
||||
constexpr int MAX_ITERATIONS = 80;
|
||||
for (int i = 0; i < MAX_ITERATIONS && webServer->isRunning(); i++) {
|
||||
webServer->handleClient();
|
||||
if ((i & 0x07) == 0x07) {
|
||||
esp_task_wdt_reset();
|
||||
feedTaskWatchdog();
|
||||
}
|
||||
if ((i & 0x0F) == 0x0F) {
|
||||
yield();
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
#include <GfxRenderer.h>
|
||||
#include <I18n.h>
|
||||
#include <WiFi.h>
|
||||
#include <esp_task_wdt.h>
|
||||
|
||||
#include <cstddef>
|
||||
|
||||
@@ -16,6 +15,7 @@
|
||||
#include "activities/network/CalibreConnectActivity.h"
|
||||
#include "components/UITheme.h"
|
||||
#include "fontIds.h"
|
||||
#include "network/TaskWatchdog.h"
|
||||
#include "util/QrUtils.h"
|
||||
|
||||
namespace {
|
||||
@@ -328,7 +328,7 @@ void CrossPointWebServerActivity::loop() {
|
||||
}
|
||||
|
||||
// Reset watchdog BEFORE processing - HTTP header parsing can be slow
|
||||
esp_task_wdt_reset();
|
||||
feedTaskWatchdog();
|
||||
|
||||
// Process HTTP requests in tight loop for maximum throughput
|
||||
// More iterations = more data processed per main loop cycle
|
||||
@@ -337,7 +337,7 @@ void CrossPointWebServerActivity::loop() {
|
||||
webServer->handleClient();
|
||||
// Reset watchdog every 32 iterations
|
||||
if ((i & 0x1F) == 0x1F) {
|
||||
esp_task_wdt_reset();
|
||||
feedTaskWatchdog();
|
||||
}
|
||||
// Yield and check for exit button every 64 iterations
|
||||
if ((i & 0x3F) == 0x3F) {
|
||||
|
||||
@@ -30,6 +30,12 @@ void NetworkModeSelectionActivity::loop() {
|
||||
return;
|
||||
}
|
||||
|
||||
int downId = -1;
|
||||
if (mappedInput.wasItemTouchedDown(downId) && downId >= 0 && downId < static_cast<int>(MENU_ITEM_COUNT)) {
|
||||
selectedIndex = downId;
|
||||
requestUpdate();
|
||||
}
|
||||
|
||||
// Handle confirm button (or a tap) - select current option
|
||||
int tappedId = -1;
|
||||
const bool tapped = mappedInput.wasItemTapped(tappedId);
|
||||
|
||||
@@ -409,6 +409,19 @@ void WifiSelectionActivity::loop() {
|
||||
|
||||
// Handle network list state
|
||||
if (state == WifiSelectionState::NETWORK_LIST) {
|
||||
// Touch: down-select highlights the pressed network, tap selects it (like Confirm).
|
||||
int downId = -1;
|
||||
if (mappedInput.wasItemTouchedDown(downId) && downId >= 0 && downId < static_cast<int>(networks.size())) {
|
||||
selectedNetworkIndex = downId;
|
||||
requestUpdate();
|
||||
}
|
||||
int tappedId = -1;
|
||||
if (mappedInput.wasItemTapped(tappedId) && tappedId >= 0 && tappedId < static_cast<int>(networks.size())) {
|
||||
selectedNetworkIndex = tappedId;
|
||||
selectNetwork(selectedNetworkIndex);
|
||||
return;
|
||||
}
|
||||
|
||||
// Check for Back button to exit (cancel)
|
||||
if (mappedInput.wasPressed(MappedInputManager::Button::Back)) {
|
||||
onComplete(false);
|
||||
|
||||
@@ -103,6 +103,14 @@ void EpubReaderBookmarksActivity::loop() {
|
||||
}
|
||||
}
|
||||
|
||||
if (confirmingDelete < DELETE_MODE_DISPLAY) {
|
||||
int downId = -1;
|
||||
if (mappedInput.wasItemTouchedDown(downId) && downId >= 0 && downId < static_cast<int>(bookmarks.size())) {
|
||||
selectorIndex = downId;
|
||||
requestUpdate();
|
||||
}
|
||||
}
|
||||
|
||||
int tappedId = -1;
|
||||
const bool tapped = (confirmingDelete < DELETE_MODE_DISPLAY) && mappedInput.wasItemTapped(tappedId);
|
||||
if (tapped && tappedId >= 0 && tappedId < static_cast<int>(bookmarks.size())) selectorIndex = tappedId;
|
||||
|
||||
@@ -31,6 +31,12 @@ void EpubReaderChapterSelectionActivity::loop() {
|
||||
const int pageItems = UITheme::getInstance().getNumberOfItemsPerPage(renderer, true, false, true, false);
|
||||
const int totalItems = getTotalItems();
|
||||
|
||||
int downId = -1;
|
||||
if (mappedInput.wasItemTouchedDown(downId) && downId >= 0 && downId < totalItems) {
|
||||
selectorIndex = downId;
|
||||
requestUpdate();
|
||||
}
|
||||
|
||||
int tappedId = -1;
|
||||
const bool tapped = mappedInput.wasItemTapped(tappedId);
|
||||
if (tapped && tappedId >= 0 && tappedId < totalItems) selectorIndex = tappedId;
|
||||
|
||||
@@ -57,6 +57,14 @@ void EpubReaderMenuActivity::loop() {
|
||||
requestUpdate();
|
||||
});
|
||||
|
||||
// Touch-down moves the selection to the pressed item (shows selected state), like
|
||||
// moving with Up/Down; release activates it below.
|
||||
int downId = -1;
|
||||
if (mappedInput.wasItemTouchedDown(downId) && downId >= 0 && downId < static_cast<int>(menuItems.size())) {
|
||||
selectedIndex = downId;
|
||||
requestUpdate();
|
||||
}
|
||||
|
||||
// A tap selects the item and activates it in one gesture (falls into Confirm below).
|
||||
int tappedId = -1;
|
||||
const bool tapped = mappedInput.wasItemTapped(tappedId);
|
||||
|
||||
@@ -54,6 +54,12 @@ void FontSelectionActivity::loop() {
|
||||
return;
|
||||
}
|
||||
|
||||
int downId = -1;
|
||||
if (mappedInput.wasItemTouchedDown(downId) && downId >= 0) {
|
||||
selectedIndex_ = downId;
|
||||
requestUpdate();
|
||||
}
|
||||
|
||||
int tappedId = -1;
|
||||
if (mappedInput.wasItemTapped(tappedId) && tappedId >= 0) {
|
||||
selectedIndex_ = tappedId;
|
||||
|
||||
@@ -32,6 +32,12 @@ void LanguageSelectActivity::loop() {
|
||||
return;
|
||||
}
|
||||
|
||||
int downId = -1;
|
||||
if (mappedInput.wasItemTouchedDown(downId) && downId >= 0) {
|
||||
selectedIndex = downId;
|
||||
requestUpdate();
|
||||
}
|
||||
|
||||
int tappedId = -1;
|
||||
if (mappedInput.wasItemTapped(tappedId) && tappedId >= 0) {
|
||||
selectedIndex = tappedId;
|
||||
|
||||
@@ -41,6 +41,12 @@ void OpdsServerListActivity::loop() {
|
||||
return;
|
||||
}
|
||||
|
||||
int downId = -1;
|
||||
if (mappedInput.wasItemTouchedDown(downId) && downId >= 0) {
|
||||
selectedIndex = downId;
|
||||
requestUpdate();
|
||||
}
|
||||
|
||||
int tappedId = -1;
|
||||
if (mappedInput.wasItemTapped(tappedId) && tappedId >= 0) {
|
||||
selectedIndex = tappedId;
|
||||
|
||||
@@ -118,6 +118,14 @@ void SettingsActivity::loop() {
|
||||
// A tap on a settings row selects + activates it in one gesture. The list is drawn
|
||||
// with selectedIndex = selectedSettingIndex - 1 (row 0 is the category tab), so map
|
||||
// the tapped 0-based row back by +1. (Category tab bar is tappable in a later phase.)
|
||||
// Touch-down moves the selection to the pressed row (shows selected state); release
|
||||
// toggles/activates it below. (Row 0 is the tab bar, so settings list id 0 -> index 1.)
|
||||
int downId = -1;
|
||||
if (mappedInput.wasItemTouchedDown(downId) && downId >= 0 && downId < settingsCount) {
|
||||
selectedSettingIndex = downId + 1;
|
||||
requestUpdate();
|
||||
}
|
||||
|
||||
int tappedId = -1;
|
||||
if (mappedInput.wasItemTapped(tappedId) && tappedId >= 0 && tappedId < settingsCount) {
|
||||
selectedSettingIndex = tappedId + 1;
|
||||
@@ -133,6 +141,7 @@ void SettingsActivity::loop() {
|
||||
selectedCategoryIndex = tabId;
|
||||
selectedSettingIndex = 0;
|
||||
hasChangedCategory = true;
|
||||
requestUpdate();
|
||||
}
|
||||
|
||||
// Handle actions with early return
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
#include <I18n.h>
|
||||
|
||||
#include "HalDisplay.h"
|
||||
#include "components/TouchRegistry.h"
|
||||
#include "components/UITheme.h"
|
||||
|
||||
ConfirmationActivity::ConfirmationActivity(GfxRenderer& renderer, MappedInputManager& mappedInput,
|
||||
@@ -46,8 +47,26 @@ void ConfirmationActivity::render(RenderLock&& lock) {
|
||||
// Draw Body
|
||||
if (!safeBody.empty()) {
|
||||
renderer.drawCenteredText(fontId, currentY, safeBody.c_str(), true, EpdFontFamily::REGULAR);
|
||||
currentY += lineHeight;
|
||||
}
|
||||
|
||||
// On-screen Cancel / Confirm buttons (also tappable). The footer hints below map
|
||||
// the same actions to Left/Right for button devices, but on touch-only devices
|
||||
// the hints are hidden, so these are the only affordance.
|
||||
const int btnH = lineHeight + 20;
|
||||
const int totalW = renderer.getScreenWidth() - margin * 2;
|
||||
const int btnW = (totalW - spacing) / 2;
|
||||
const int btnY = currentY + spacing * 3;
|
||||
const Rect cancelRect{margin, btnY, btnW, btnH};
|
||||
const Rect confirmRect{margin + btnW + spacing, btnY, btnW, btnH};
|
||||
renderer.drawRect(cancelRect.x, cancelRect.y, cancelRect.width, cancelRect.height);
|
||||
renderer.drawRect(confirmRect.x, confirmRect.y, confirmRect.width, confirmRect.height);
|
||||
const int btnTextY = btnY + (btnH - lineHeight) / 2;
|
||||
UITheme::drawCenteredText(renderer, cancelRect, fontId, btnTextY, I18N.get(StrId::STR_CANCEL));
|
||||
UITheme::drawCenteredText(renderer, confirmRect, fontId, btnTextY, I18N.get(StrId::STR_CONFIRM));
|
||||
TouchRegistry::getInstance().add(cancelRect, 0, TouchRegistry::Item);
|
||||
TouchRegistry::getInstance().add(confirmRect, 1, TouchRegistry::Item);
|
||||
|
||||
// Draw UI Elements
|
||||
const auto labels = mappedInput.mapLabels("", "", I18N.get(StrId::STR_CANCEL), I18N.get(StrId::STR_CONFIRM));
|
||||
GUI.drawButtonHints(renderer, labels.btn1, labels.btn2, labels.btn3, labels.btn4);
|
||||
@@ -56,6 +75,16 @@ void ConfirmationActivity::render(RenderLock&& lock) {
|
||||
}
|
||||
|
||||
void ConfirmationActivity::loop() {
|
||||
// Tap the on-screen buttons: id 1 = Confirm, id 0 = Cancel.
|
||||
int tappedId = -1;
|
||||
if (mappedInput.wasItemTapped(tappedId)) {
|
||||
ActivityResult res;
|
||||
res.isCancelled = (tappedId != 1);
|
||||
setResult(std::move(res));
|
||||
finish();
|
||||
return;
|
||||
}
|
||||
|
||||
if (mappedInput.wasReleased(MappedInputManager::Button::Right)) {
|
||||
ActivityResult res;
|
||||
res.isCancelled = false;
|
||||
|
||||
@@ -337,13 +337,24 @@ 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.
|
||||
// A tap selects the key and presses it. Encoded id = row*100+col (bottom function
|
||||
// row = getContentRowCount()). A touch-and-hold inserts the alternate character
|
||||
// (numbers/symbols on a letter), mirroring the button long-press — wasItemLongPressed
|
||||
// is checked first since it's a subset of wasItemTapped's releases. 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;
|
||||
int longKey = -1;
|
||||
if (mappedInput.wasItemLongPressed(longKey)) {
|
||||
const char alt = getAlternativeChar();
|
||||
if (alt != '\0') {
|
||||
insertChar(alt);
|
||||
requestUpdate();
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (handleKeyPress()) {
|
||||
requestUpdate();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user