feat(Display): Improve touch handling and visual feedback
This commit is contained in:
@@ -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();
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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();
|
||||||
|
|||||||
@@ -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; }
|
||||||
|
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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();
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
Reference in New Issue
Block a user