feat(Display): Improve touch handling and visual feedback

This commit is contained in:
2026-02-20 04:10:39 -08:00
parent 688b1905e5
commit 72636f9ecf
10 changed files with 43 additions and 18 deletions

View File

@@ -197,9 +197,12 @@ void DisplayDriverTFT::drawAlert(const ScreenState& st) {
uint32_t elapsed = millis() - st.alertStartMs; uint32_t elapsed = millis() - st.alertStartMs;
bool brightPhase = (elapsed / 2000) % 2 == 0; bool brightPhase = (elapsed / 2000) % 2 == 0;
// Only redraw when phase changes (timer-based, not every frame) // Redraw when phase changes OR when touch was released (to clear fill)
if(brightPhase != _lastAlertPhase) { bool needsRedraw = (brightPhase != _lastAlertPhase) || _alertNeedsRedraw;
if(needsRedraw) {
_lastAlertPhase = brightPhase; _lastAlertPhase = brightPhase;
_alertNeedsRedraw = false; // clear the flag
uint16_t bg = brightPhase ? TFT_RED : _tft.color565(180, 0, 0); uint16_t bg = brightPhase ? TFT_RED : _tft.color565(180, 0, 0);
_tft.fillScreen(bg); _tft.fillScreen(bg);
@@ -218,7 +221,7 @@ void DisplayDriverTFT::drawAlert(const ScreenState& st) {
_tft.print("Hold to silence..."); _tft.print("Hold to silence...");
} }
// Progressive fill hint - only redraw when touch state changes // Progressive fill hint - while touch is held
if(_alertTouchDown) { if(_alertTouchDown) {
uint32_t touchElapsed = millis() - _alertTouchStartMs; uint32_t touchElapsed = millis() - _alertTouchStartMs;
float progress = (float)touchElapsed / (float)ALERT_FILL_DURATION_MS; float progress = (float)touchElapsed / (float)ALERT_FILL_DURATION_MS;
@@ -374,6 +377,12 @@ TouchEvent DisplayDriverTFT::readTouch() {
uint16_t tx, ty; uint16_t tx, ty;
uint8_t touched = _tft.getTouch(&tx, &ty, 100); uint8_t touched = _tft.getTouch(&tx, &ty, 100);
// Debug: log touch state changes
if(touched != _touchWasPressed) {
Serial.printf("[TOUCH] raw touched=%d wasPressed=%d (x=%d,y=%d)\n", touched,
_touchWasPressed, tx, ty);
}
// Detect transitions (press/release) // Detect transitions (press/release)
if(touched && !_touchWasPressed) { if(touched && !_touchWasPressed) {
// Press transition: finger just touched down // Press transition: finger just touched down
@@ -406,6 +415,7 @@ TouchEvent DisplayDriverTFT::readTouch() {
_alertTouchStartMs = millis(); _alertTouchStartMs = millis();
} else if(evt.released) { } else if(evt.released) {
_alertTouchDown = false; _alertTouchDown = false;
_alertNeedsRedraw = true; // force redraw to clear fill
} }
return evt; return evt;
@@ -418,11 +428,10 @@ void DisplayDriverTFT::transformTouch(int* x, int* y) {
*y = temp; *y = temp;
} }
HoldState DisplayDriverTFT::updateHold(unsigned long holdMs) { HoldState DisplayDriverTFT::updateHold(const TouchEvent& evt, unsigned long holdMs) {
HoldState h; HoldState h;
TouchEvent t = readTouch();
if(t.pressed) { if(evt.pressed) {
if(!_holdActive) { if(!_holdActive) {
_holdActive = true; _holdActive = true;
_holdStartMs = millis(); _holdStartMs = millis();

View File

@@ -11,7 +11,7 @@ public:
void setBacklight(bool on) override; void setBacklight(bool on) override;
void render(const ScreenState& state) override; void render(const ScreenState& state) override;
TouchEvent readTouch() override; TouchEvent readTouch() override;
HoldState updateHold(unsigned long holdMs) override; HoldState updateHold(const TouchEvent& evt, unsigned long holdMs) override;
int width() override { int width() override {
// Use TFT_eSPI's dimensions after rotation - it's more reliable // Use TFT_eSPI's dimensions after rotation - it's more reliable
return _tft.width(); return _tft.width();
@@ -43,6 +43,7 @@ private:
// Touch hint for alert - progressive fill from bottom // Touch hint for alert - progressive fill from bottom
bool _alertTouchDown = false; bool _alertTouchDown = false;
bool _alertNeedsRedraw = false; // force redraw after touch release
uint32_t _alertTouchStartMs = 0; uint32_t _alertTouchStartMs = 0;
bool _lastAlertPhase = false; // tracks bright/dark phase for 2-color alert bool _lastAlertPhase = false; // tracks bright/dark phase for 2-color alert
static constexpr uint32_t ALERT_FILL_DURATION_MS = 3000; static constexpr uint32_t ALERT_FILL_DURATION_MS = 3000;

View File

@@ -298,11 +298,10 @@ int DisplayDriverTFT::dashboardTouch(int x, int y) {
return row * 2 + col; // 0, 1, 2, or 3 return row * 2 + col; // 0, 1, 2, or 3
} }
HoldState DisplayDriverTFT::updateHold(unsigned long holdMs) { HoldState DisplayDriverTFT::updateHold(const TouchEvent& evt, unsigned long holdMs) {
HoldState h; HoldState h;
TouchEvent t = readTouch();
if(t.pressed) { if(evt.pressed) {
if(!_holdActive) { if(!_holdActive) {
_holdActive = true; _holdActive = true;
_holdStartMs = millis(); _holdStartMs = millis();

View File

@@ -12,7 +12,7 @@ public:
void render(const ScreenState& state) override; void render(const ScreenState& state) override;
TouchEvent readTouch() override; TouchEvent readTouch() override;
int dashboardTouch(int x, int y); int dashboardTouch(int x, int y);
HoldState updateHold(unsigned long holdMs) override; HoldState updateHold(const TouchEvent& evt, unsigned long holdMs) override;
int width() override { return DISPLAY_WIDTH; } int width() override { return DISPLAY_WIDTH; }
int height() override { return DISPLAY_HEIGHT; } int height() override { return DISPLAY_HEIGHT; }

View File

@@ -261,10 +261,10 @@ int DisplayDriverGFX::dashboardTouch(int x, int y) {
return row * cols + col; return row * cols + col;
} }
HoldState DisplayDriverGFX::updateHold(unsigned long holdMs) { HoldState DisplayDriverGFX::updateHold(const TouchEvent& evt, unsigned long holdMs) {
HoldState state; HoldState state;
if(!_lastTouch.pressed) { if(!evt.pressed) {
_isHolding = false; _isHolding = false;
return state; return state;
} }

View File

@@ -13,7 +13,7 @@ public:
void render(const ScreenState& state) override; void render(const ScreenState& state) override;
TouchEvent readTouch() override; TouchEvent readTouch() override;
HoldState updateHold(unsigned long holdMs) override; HoldState updateHold(const TouchEvent& evt, unsigned long holdMs) override;
void drawDebugTouch(int x, int y) override; void drawDebugTouch(int x, int y) override;
int width() override; int width() override;

View File

@@ -13,7 +13,7 @@
#define POLL_INTERVAL_MS 15000 #define POLL_INTERVAL_MS 15000
#define HEARTBEAT_INTERVAL_MS 300000 #define HEARTBEAT_INTERVAL_MS 300000
#define BOOT_GRACE_MS 5000 #define BOOT_GRACE_MS 5000
#define HOLD_TO_SILENCE_MS 3000 #define HOLD_TO_SILENCE_MS 2000
#define ALERT_TIMEOUT_MS 120000 #define ALERT_TIMEOUT_MS 120000
#define SILENCE_DISPLAY_MS 10000 #define SILENCE_DISPLAY_MS 10000
#define INACTIVITY_TIMEOUT_MS 30000 #define INACTIVITY_TIMEOUT_MS 30000

View File

@@ -60,6 +60,14 @@ private:
/// Calculate optimal grid dimensions based on display and tile constraints /// Calculate optimal grid dimensions based on display and tile constraints
static void calculateGrid( static void calculateGrid(
int tileCount, int displayW, int contentH, int* outCols, int* outRows) { int tileCount, int displayW, int contentH, int* outCols, int* outRows) {
// Use 2x2 grid for 4 tiles regardless of aspect ratio
// This provides better visual balance than a single row
if(tileCount == 4) {
*outCols = 2;
*outRows = 2;
return;
}
// Calculate aspect ratio to determine preferred layout // Calculate aspect ratio to determine preferred layout
float aspectRatio = (float)displayW / contentH; float aspectRatio = (float)displayW / contentH;
@@ -138,7 +146,9 @@ public:
TouchEvent readTouch() { return _drv ? _drv->readTouch() : TouchEvent {}; } TouchEvent readTouch() { return _drv ? _drv->readTouch() : TouchEvent {}; }
HoldState updateHold(unsigned long ms) { return _drv ? _drv->updateHold(ms) : HoldState {}; } HoldState updateHold(const TouchEvent& evt, unsigned long ms) {
return _drv ? _drv->updateHold(evt, ms) : HoldState {};
}
void drawDebugTouch(int x, int y) { void drawDebugTouch(int x, int y) {
if(_drv) if(_drv)

View File

@@ -338,6 +338,7 @@ int DoorbellLogic::handleTouch(const TouchEvent& evt) {
// Check hold completion FIRST - before any release handling // Check hold completion FIRST - before any release handling
// This ensures hold-to-silence works on ALERT screen // This ensures hold-to-silence works on ALERT screen
if(evt.released && _state.deviceState == DeviceState::ALERTING) { if(evt.released && _state.deviceState == DeviceState::ALERTING) {
Serial.printf("[TOUCH] checking hold: deviceState=ALERTING\n");
if(updateHold(evt)) { if(updateHold(evt)) {
return (int)TileAction::SILENCE; return (int)TileAction::SILENCE;
} }
@@ -345,6 +346,8 @@ int DoorbellLogic::handleTouch(const TouchEvent& evt) {
// Handle press - show visual feedback // Handle press - show visual feedback
if(evt.pressed) { if(evt.pressed) {
Serial.printf("[TOUCH] pressed screen=%d deviceState=%d\n", (int)_state.screen,
(int)_state.deviceState);
// Reset inactivity timer on any touch // Reset inactivity timer on any touch
_lastActivityMs = millis(); _lastActivityMs = millis();
@@ -377,6 +380,9 @@ int DoorbellLogic::handleTouch(const TouchEvent& evt) {
// Handle release - fire action if same tile // Handle release - fire action if same tile
if(evt.released) { if(evt.released) {
Serial.printf("[TOUCH] released screen=%d deviceState=%d\n", (int)_state.screen,
(int)_state.deviceState);
if(_state.screen == ScreenID::DASHBOARD) { if(_state.screen == ScreenID::DASHBOARD) {
// Only fire action if finger stayed on same tile // Only fire action if finger stayed on same tile
if(evt.downX >= 0 && _display->isSameTile(evt.downX, evt.downY, evt.x, evt.y)) { if(evt.downX >= 0 && _display->isSameTile(evt.downX, evt.downY, evt.x, evt.y)) {
@@ -426,7 +432,7 @@ bool DoorbellLogic::updateHold(const TouchEvent& evt) {
static int holdStartX = -1; static int holdStartX = -1;
static int holdStartY = -1; static int holdStartY = -1;
HoldState h = _display->updateHold(HOLD_TO_SILENCE_MS); HoldState h = _display->updateHold(evt, HOLD_TO_SILENCE_MS);
if(h.completed) { if(h.completed) {
silenceAlert(); silenceAlert();

View File

@@ -31,7 +31,7 @@ public:
// ── Touch ── // ── Touch ──
virtual TouchEvent readTouch() = 0; virtual TouchEvent readTouch() = 0;
virtual HoldState updateHold(unsigned long holdMs) = 0; virtual HoldState updateHold(const TouchEvent& evt, unsigned long holdMs) = 0;
virtual void drawDebugTouch(int x, int y) { /* default: no-op */ } virtual void drawDebugTouch(int x, int y) { /* default: no-op */ }
virtual int width() = 0; virtual int width() = 0;
virtual int height() = 0; virtual int height() = 0;