From 9f7a383b38ddb525bf35d21fdef31d98360793da Mon Sep 17 00:00:00 2001 From: David Gwilliam Date: Wed, 18 Feb 2026 13:08:56 -0800 Subject: [PATCH] feat(touch): add press/release detection with touch-down tracking --- boards/esp32-32e-4/DisplayDriverTFT.cpp | 23 +++++- boards/esp32-32e-4/DisplayDriverTFT.h | 5 ++ boards/esp32-s3-lcd-43/DisplayDriverGFX.cpp | 24 +++++- boards/esp32-s3-lcd-43/DisplayDriverGFX.h | 2 +- libraries/KlubhausCore/src/DisplayManager.h | 62 +++++++++++++++ libraries/KlubhausCore/src/DoorbellLogic.cpp | 82 ++++++++++++-------- libraries/KlubhausCore/src/IDisplayDriver.h | 9 ++- 7 files changed, 166 insertions(+), 41 deletions(-) diff --git a/boards/esp32-32e-4/DisplayDriverTFT.cpp b/boards/esp32-32e-4/DisplayDriverTFT.cpp index 81c1c33..77eb1e3 100644 --- a/boards/esp32-32e-4/DisplayDriverTFT.cpp +++ b/boards/esp32-32e-4/DisplayDriverTFT.cpp @@ -173,11 +173,32 @@ TouchEvent DisplayDriverTFT::readTouch() { TouchEvent evt; uint16_t tx, ty; uint8_t touched = _tft.getTouch(&tx, &ty, 100); - if(touched) { + + // Detect transitions (press/release) + if(touched && !_touchWasPressed) { + // Press transition: finger just touched down evt.pressed = true; + _touchDownX = tx; + _touchDownY = ty; + evt.downX = _touchDownX; + evt.downY = _touchDownY; + } else if(!touched && _touchWasPressed) { + // Release transition: finger just lifted + evt.released = true; + evt.downX = _touchDownX; + evt.downY = _touchDownY; + } + + // Current position if still touched + if(touched) { evt.x = tx; evt.y = ty; + evt.downX = _touchDownX; + evt.downY = _touchDownY; } + + // Track previous state for next call + _touchWasPressed = touched; return evt; } diff --git a/boards/esp32-32e-4/DisplayDriverTFT.h b/boards/esp32-32e-4/DisplayDriverTFT.h index 7654426..fa087d9 100644 --- a/boards/esp32-32e-4/DisplayDriverTFT.h +++ b/boards/esp32-32e-4/DisplayDriverTFT.h @@ -32,4 +32,9 @@ private: ScreenID _lastScreen = ScreenID::BOOT; BootStage _lastBootStage = BootStage::SPLASH; bool _needsRedraw = true; + + // Touch tracking for press/release detection + bool _touchWasPressed = false; + int _touchDownX = -1; + int _touchDownY = -1; }; diff --git a/boards/esp32-s3-lcd-43/DisplayDriverGFX.cpp b/boards/esp32-s3-lcd-43/DisplayDriverGFX.cpp index ba9251f..7d05f85 100644 --- a/boards/esp32-s3-lcd-43/DisplayDriverGFX.cpp +++ b/boards/esp32-s3-lcd-43/DisplayDriverGFX.cpp @@ -68,16 +68,36 @@ TouchEvent DisplayDriverGFX::readTouch() { int32_t x, y; bool pressed = _gfx->getTouch(&x, &y); - // Only report NEW touches (debounce - ignore held touches) - evt.pressed = pressed && !_lastTouch.pressed; + // Detect transitions (press/release) + if(pressed && !_lastTouch.pressed) { + // Press transition: finger just touched down + evt.pressed = true; + evt.downX = static_cast(x); + evt.downY = static_cast(y); + _lastTouch.downX = evt.downX; + _lastTouch.downY = evt.downY; + } else if(!pressed && _lastTouch.pressed) { + // Release transition: finger just lifted + evt.released = true; + evt.downX = _lastTouch.downX; + evt.downY = _lastTouch.downY; + } + // Current position if still touched if(pressed) { evt.x = static_cast(x); evt.y = static_cast(y); + evt.downX = _lastTouch.downX; + evt.downY = _lastTouch.downY; _pressStartMs = millis(); } + // Track previous state _lastTouch.pressed = pressed; + if(pressed) { + _lastTouch.x = evt.x; + _lastTouch.y = evt.y; + } return evt; } diff --git a/boards/esp32-s3-lcd-43/DisplayDriverGFX.h b/boards/esp32-s3-lcd-43/DisplayDriverGFX.h index b0f8ec7..d0c1cbb 100644 --- a/boards/esp32-s3-lcd-43/DisplayDriverGFX.h +++ b/boards/esp32-s3-lcd-43/DisplayDriverGFX.h @@ -28,7 +28,7 @@ private: void drawDashboard(const ScreenState& state); // Touch handling - TouchEvent _lastTouch = { false, 0, 0 }; + TouchEvent _lastTouch = { false, false, 0, 0, -1, -1 }; unsigned long _pressStartMs = 0; bool _isHolding = false; diff --git a/libraries/KlubhausCore/src/DisplayManager.h b/libraries/KlubhausCore/src/DisplayManager.h index 7441866..240533b 100644 --- a/libraries/KlubhausCore/src/DisplayManager.h +++ b/libraries/KlubhausCore/src/DisplayManager.h @@ -143,6 +143,68 @@ public: _drv->updateHint(x, y, active); } + /// Show touch feedback - highlights the tile at given coordinates + /// Returns true if a valid tile is being touched + bool showTouchFeedback(int x, int y) { + if(!_drv || _gridCols <= 0) return false; + + // Transform touch coordinates + _drv->transformTouch(&x, &y); + + int headerH = 30; + if(y < headerH) return false; + + // Calculate which cell + int cellW = _drv->width() / _gridCols; + int cellH = (_drv->height() - headerH) / _gridRows; + + int col = x / cellW; + int row = (y - headerH) / cellH; + + if(col < 0 || col >= _gridCols || row < 0 || row >= _gridRows) + return false; + + // Find which tile is at this position + for(int i = 0; i < _tileCount; i++) { + const TileLayout& lay = _layouts[i]; + if(lay.col <= col && col < lay.col + lay.cols && + lay.row <= row && lay.row + lay.rows > row) { + // Found the tile - draw highlight via driver + _drv->updateHint(lay.x, lay.y, true); // active=true means show feedback + return true; + } + } + return false; + } + + /// Clear touch feedback + void clearTouchFeedback() { + if(_drv) + _drv->updateHint(0, 0, false); // active=false means clear + } + + /// Check if current position is still in same tile as touch-down + bool isSameTile(int downX, int downY, int currentX, int currentY) const { + if(!_drv || _gridCols <= 0 || downX < 0) return false; + + int dx = downX, dy = downY; + int cx = currentX, cy = currentY; + + _drv->transformTouch(&dx, &dy); + _drv->transformTouch(&cx, &cy); + + int headerH = 30; + int cellW = _drv->width() / _gridCols; + int cellH = (_drv->height() - headerH) / _gridRows; + + int downCol = dx / cellW; + int downRow = (dy - headerH) / cellH; + int curCol = cx / cellW; + int curRow = (cy - headerH) / cellH; + + return downCol == curCol && downRow == curRow; + } + int width() { return _drv ? _drv->width() : 0; } int height() { return _drv ? _drv->height() : 0; } diff --git a/libraries/KlubhausCore/src/DoorbellLogic.cpp b/libraries/KlubhausCore/src/DoorbellLogic.cpp index 04c31d0..c8644ff 100644 --- a/libraries/KlubhausCore/src/DoorbellLogic.cpp +++ b/libraries/KlubhausCore/src/DoorbellLogic.cpp @@ -310,48 +310,62 @@ void DoorbellLogic::setScreen(ScreenID s) { } int DoorbellLogic::handleTouch(const TouchEvent& evt) { - if(!evt.pressed) - return -1; + // Handle press - show visual feedback + if(evt.pressed) { + // Reset inactivity timer on any touch + _lastActivityMs = millis(); - // Reset inactivity timer on any touch - _lastActivityMs = millis(); + if(_state.screen == ScreenID::OFF) { + Serial.printf("[%lu] [TOUCH] OFF → DASHBOARD\n", millis()); + setScreen(ScreenID::DASHBOARD); + return -1; + } - if(_state.screen == ScreenID::OFF) { - Serial.printf("[%lu] [TOUCH] OFF → DASHBOARD\n", millis()); - setScreen(ScreenID::DASHBOARD); + // Show touch feedback on press + if(_state.screen == ScreenID::DASHBOARD) { + _display->showTouchFeedback(evt.x, evt.y); + } return -1; } - if(_state.screen == ScreenID::DASHBOARD) { - TileAction action = _display->handleDashboardTouch(evt.x, evt.y); - if(action != TileAction::NONE) { - Serial.printf("[DASH] Action: %d\n", (int)action); - switch(action) { - case TileAction::ALERT: - onAlert("Manual Alert", "Tile tap"); - break; - case TileAction::SILENCE: - if(_state.deviceState == DeviceState::ALERTING) - silenceAlert(); - break; - case TileAction::STATUS: - heartbeat(); - break; - case TileAction::REBOOT: - flushStatus("REBOOT (tile)"); - delay(500); - ESP.restart(); - break; - default: - break; + // Handle release - fire action if same tile + if(evt.released) { + // Clear feedback + _display->clearTouchFeedback(); + + if(_state.screen == ScreenID::DASHBOARD) { + // Only fire action if finger stayed on same tile + if(evt.downX >= 0 && _display->isSameTile(evt.downX, evt.downY, evt.x, evt.y)) { + TileAction action = _display->handleDashboardTouch(evt.downX, evt.downY); + if(action != TileAction::NONE) { + Serial.printf("[DASH] Action: %d\n", (int)action); + switch(action) { + case TileAction::ALERT: + onAlert("Manual Alert", "Tile tap"); + break; + case TileAction::SILENCE: + if(_state.deviceState == DeviceState::ALERTING) + silenceAlert(); + break; + case TileAction::STATUS: + heartbeat(); + break; + case TileAction::REBOOT: + flushStatus("REBOOT (tile)"); + delay(500); + ESP.restart(); + break; + default: + break; + } + return (int)action; + } } } - return (int)action; - } - if(_state.screen == ScreenID::ALERT) { - Serial.println("[TOUCH] ALERT tap"); - return -1; + if(_state.screen == ScreenID::ALERT) { + Serial.println("[TOUCH] ALERT tap"); + } } return -1; diff --git a/libraries/KlubhausCore/src/IDisplayDriver.h b/libraries/KlubhausCore/src/IDisplayDriver.h index 3409336..fe56b27 100644 --- a/libraries/KlubhausCore/src/IDisplayDriver.h +++ b/libraries/KlubhausCore/src/IDisplayDriver.h @@ -2,9 +2,12 @@ #include "ScreenState.h" struct TouchEvent { - bool pressed = false; - int x = 0; - int y = 0; + bool pressed = false; // finger just touched down + bool released = false; // finger just lifted (after being pressed) + int x = 0; // current x position + int y = 0; // current y position + int downX = -1; // x position where touch started + int downY = -1; // y position where touch started }; struct HoldState {