From e42503f1a4b7ccf95b726cf3c9efb27998cb6d00 Mon Sep 17 00:00:00 2001 From: Justin Mitchell Date: Sun, 21 Jun 2026 01:14:40 -0400 Subject: [PATCH] Preserve touch held time across gesture detection Touch held time was being lost when gestures were detected because the gesture detection happens before the touch release event. Now remembers the held time at the moment of gesture detection and returns it within a 250ms window, allowing UI elements to properly respond to long-press gestures. Also refactors deep sleep code to use PowerManager methods. --- lib/hal/HalGPIO.cpp | 13 +++------- lib/hal/HalPowerManager.cpp | 14 +++-------- src/MappedInputManager.cpp | 48 ++++++++++++++++++++++++++++++------- src/MappedInputManager.h | 4 ++++ 4 files changed, 49 insertions(+), 30 deletions(-) diff --git a/lib/hal/HalGPIO.cpp b/lib/hal/HalGPIO.cpp index 3940a4d6..c6f8a7f3 100644 --- a/lib/hal/HalGPIO.cpp +++ b/lib/hal/HalGPIO.cpp @@ -1,5 +1,6 @@ #include #include +#include #include #include #include @@ -272,16 +273,8 @@ void HalGPIO::startDeepSleep() { delay(50); inputMgr.update(); } - // Arm the wakeup trigger *after* the button is released -#if SOC_PM_SUPPORT_EXT1_WAKEUP - // Xtensa (classic ESP32 / S3): GPIO deep-sleep wake is via RTC ext1. - esp_sleep_enable_ext1_wakeup(1ULL << InputManager::POWER_BUTTON_PIN, ESP_EXT1_WAKEUP_ALL_LOW); -#else - // RISC-V (C3): direct GPIO deep-sleep wakeup source. - esp_deep_sleep_enable_gpio_wakeup(1ULL << InputManager::POWER_BUTTON_PIN, ESP_GPIO_WAKEUP_GPIO_LOW); -#endif - // Enter Deep Sleep - esp_deep_sleep_start(); + freeink::PowerManager::armPowerButtonWakeup(); + freeink::PowerManager::deepSleep(); } void HalGPIO::verifyPowerButtonWakeup(uint16_t requiredDurationMs, bool shortPressAllowed) { diff --git a/lib/hal/HalPowerManager.cpp b/lib/hal/HalPowerManager.cpp index b9637ca2..b839e354 100644 --- a/lib/hal/HalPowerManager.cpp +++ b/lib/hal/HalPowerManager.cpp @@ -1,6 +1,7 @@ #include "HalPowerManager.h" #include +#include #include #include @@ -103,18 +104,9 @@ void HalPowerManager::startDeepSleep(HalGPIO& gpio) const { esp_sleep_config_gpio_isolate(); gpio_deep_sleep_hold_en(); gpio_hold_en(GPIO_SPIWP); - pinMode(InputManager::POWER_BUTTON_PIN, INPUT_PULLUP); - // Arm the wakeup trigger *after* the button is released - // Note: this is only useful for waking up on USB power. On battery, the MCU will be completely powered off, so the - // power button is hard-wired to briefly provide power to the MCU, waking it up regardless of the wakeup source - // configuration - esp_deep_sleep_enable_gpio_wakeup(1ULL << InputManager::POWER_BUTTON_PIN, ESP_GPIO_WAKEUP_GPIO_LOW); -#else // Xtensa (classic ESP32 / S3): RTC ext1 GPIO wake - pinMode(InputManager::POWER_BUTTON_PIN, INPUT_PULLUP); - esp_sleep_enable_ext1_wakeup(1ULL << InputManager::POWER_BUTTON_PIN, ESP_EXT1_WAKEUP_ALL_LOW); #endif - // Enter Deep Sleep - esp_deep_sleep_start(); + freeink::PowerManager::armPowerButtonWakeup(); + freeink::PowerManager::deepSleep(); } uint16_t HalPowerManager::getBatteryPercentage() const { diff --git a/src/MappedInputManager.cpp b/src/MappedInputManager.cpp index 594f19f2..f658efa1 100644 --- a/src/MappedInputManager.cpp +++ b/src/MappedInputManager.cpp @@ -65,6 +65,13 @@ static constexpr float BACK_GESTURE_FRAC_X = 0.22f; static constexpr float BACK_GESTURE_FRAC_Y = 0.12f; static constexpr float BOTTOM_EDGE_BACK_GESTURE_FRAC_Y = 0.14f; static constexpr unsigned long TOUCH_DOWN_SELECT_DELAY_MS = 90; +static constexpr unsigned long TOUCH_HELD_OVERRIDE_WINDOW_MS = 250; + +void MappedInputManager::rememberTouchHeldTime() const { + touchHeldOverrideValid = true; + touchHeldOverrideMs = gpio.lastTouchHeldMs(); + touchHeldOverrideAt = millis(); +} bool MappedInputManager::wasBottomEdgeSwipeUp() const { float nxs = 0.0f, nys = 0.0f, nxe = 0.0f, nye = 0.0f; @@ -76,7 +83,9 @@ bool MappedInputManager::wasBottomEdgeSwipeUp() const { const int screenHeight = renderer.getScreenHeight(); const int bottomEdgeTop = screenHeight - static_cast(screenHeight * BOTTOM_EDGE_BACK_GESTURE_FRAC_Y); - return sy >= bottomEdgeTop && ey < sy && std::abs(ey - sy) > std::abs(ex - sx); + const bool isBackSwipe = sy >= bottomEdgeTop && ey < sy && std::abs(ey - sy) > std::abs(ex - sx); + if (isBackSwipe) rememberTouchHeldTime(); + return isBackSwipe; } bool MappedInputManager::wasBackGesture() const { @@ -88,10 +97,15 @@ bool MappedInputManager::wasBackGesture() const { renderer.tapToLogical(nx, ny, lx, ly); // A tap on the theme's header Back target acts as Back. int id = 0; - if (TouchRegistry::getInstance().hitTest(lx, ly, TouchRegistry::Back, id)) return true; + if (TouchRegistry::getInstance().hitTest(lx, ly, TouchRegistry::Back, id)) { + rememberTouchHeldTime(); + return true; + } // Else the top-left corner, for screens with no Back target (e.g. the reader). - return lx <= renderer.getScreenWidth() * BACK_GESTURE_FRAC_X && - ly <= renderer.getScreenHeight() * BACK_GESTURE_FRAC_Y; + const bool isTopLeftBack = lx <= renderer.getScreenWidth() * BACK_GESTURE_FRAC_X && + ly <= renderer.getScreenHeight() * BACK_GESTURE_FRAC_Y; + if (isTopLeftBack) rememberTouchHeldTime(); + return isTopLeftBack; } bool MappedInputManager::wasItemTapped(int& id) const { @@ -99,7 +113,9 @@ bool MappedInputManager::wasItemTapped(int& id) const { 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::Item, id); + const bool hit = TouchRegistry::getInstance().hitTest(lx, ly, TouchRegistry::Item, id); + if (hit) rememberTouchHeldTime(); + return hit; } bool MappedInputManager::wasItemTouchedDown(int& id) const { @@ -143,7 +159,9 @@ bool MappedInputManager::wasItemLongPressed(int& id) const { if (gpio.lastTouchHeldMs() < TOUCH_LONG_PRESS_MS) return false; int lx = 0, ly = 0; renderer.tapToLogical(nx, ny, lx, ly); - return TouchRegistry::getInstance().hitTest(lx, ly, TouchRegistry::Item, id); + const bool hit = TouchRegistry::getInstance().hitTest(lx, ly, TouchRegistry::Item, id); + if (hit) rememberTouchHeldTime(); + return hit; } bool MappedInputManager::wasTabTapped(int& id) const { @@ -151,7 +169,9 @@ bool MappedInputManager::wasTabTapped(int& id) const { 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); + const bool hit = TouchRegistry::getInstance().hitTest(lx, ly, TouchRegistry::Tab, id); + if (hit) rememberTouchHeldTime(); + return hit; } bool MappedInputManager::wasCoverTapped(int& id) const { @@ -159,13 +179,16 @@ bool MappedInputManager::wasCoverTapped(int& id) const { 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); + const bool hit = TouchRegistry::getInstance().hitTest(lx, ly, TouchRegistry::Cover, id); + if (hit) rememberTouchHeldTime(); + return hit; } bool MappedInputManager::wasScreenTapped(int& x, int& y) const { float nx = 0.0f, ny = 0.0f; if (!gpio.wasTouchTap(nx, ny)) return false; renderer.tapToLogical(nx, ny, x, y); + rememberTouchHeldTime(); return true; } @@ -220,7 +243,14 @@ bool MappedInputManager::wasAnyPressed() const { return gpio.wasAnyPressed(); } bool MappedInputManager::wasAnyReleased() const { return gpio.wasAnyReleased(); } -unsigned long MappedInputManager::getHeldTime() const { return gpio.getHeldTime(); } +unsigned long MappedInputManager::getHeldTime() const { + if (!gpio.wasAnyPressed() && !gpio.wasAnyReleased() && touchHeldOverrideValid && + millis() - touchHeldOverrideAt <= TOUCH_HELD_OVERRIDE_WINDOW_MS) { + return touchHeldOverrideMs; + } + touchHeldOverrideValid = false; + return gpio.getHeldTime(); +} MappedInputManager::Labels MappedInputManager::mapLabels(const char* back, const char* confirm, const char* previous, const char* next) const { diff --git a/src/MappedInputManager.h b/src/MappedInputManager.h index d435efe0..c40d5f45 100644 --- a/src/MappedInputManager.h +++ b/src/MappedInputManager.h @@ -61,9 +61,13 @@ class MappedInputManager { GfxRenderer& renderer; bool mapButton(Button button, bool (HalGPIO::*fn)(uint8_t) const) const; + void rememberTouchHeldTime() const; bool wasBottomEdgeSwipeUp() const; mutable bool touchSelectTracking = false; mutable bool touchSelectEmitted = false; mutable int touchSelectId = -1; + mutable bool touchHeldOverrideValid = false; + mutable unsigned long touchHeldOverrideMs = 0; + mutable unsigned long touchHeldOverrideAt = 0; };