#include "DisplayDriverTFT.h" void DisplayDriverTFT::begin() { // Backlight pinMode(PIN_LCD_BL, OUTPUT); digitalWrite(PIN_LCD_BL, LOW); _tft.init(); _tft.setRotation(DISPLAY_ROTATION); _tft.fillScreen(TFT_BLACK); Serial.printf("[GFX] Display OK: %dx%d\n", DISPLAY_WIDTH, DISPLAY_HEIGHT); drawBoot(); digitalWrite(PIN_LCD_BL, HIGH); Serial.println("[GFX] Backlight ON"); } void DisplayDriverTFT::setBacklight(bool on) { digitalWrite(PIN_LCD_BL, on ? HIGH : LOW); } // ── Rendering ─────────────────────────────────────────────── void DisplayDriverTFT::render(const ScreenState& st) { if(st.screen != _lastScreen) { _needsRedraw = true; _lastScreen = st.screen; } switch(st.screen) { case ScreenID::BOOT: if(_needsRedraw) { drawBoot(); _needsRedraw = false; } break; case ScreenID::ALERT: drawAlert(st); break; case ScreenID::DASHBOARD: if(_needsRedraw) { drawDashboard(st); _needsRedraw = false; } break; case ScreenID::OFF: if(_needsRedraw) { _tft.fillScreen(TFT_BLACK); _needsRedraw = false; } break; } } void DisplayDriverTFT::drawBoot() { _tft.fillScreen(TFT_BLACK); _tft.setTextColor(TFT_WHITE, TFT_BLACK); _tft.setTextSize(2); _tft.setCursor(10, 10); _tft.printf("KLUBHAUS v%s", FW_VERSION); _tft.setTextSize(1); _tft.setCursor(10, 40); _tft.print(BOARD_NAME); _tft.setCursor(10, 60); _tft.print("Booting..."); } void DisplayDriverTFT::drawAlert(const ScreenState& st) { uint32_t elapsed = millis() - st.alertStartMs; uint8_t pulse = 180 + (uint8_t)(75.0f * sinf(elapsed / 300.0f)); uint16_t bg = _tft.color565(pulse, 0, 0); _tft.fillScreen(bg); _tft.setTextColor(TFT_WHITE, bg); _tft.setTextSize(3); _tft.setCursor(10, 20); _tft.print(st.alertTitle.length() > 0 ? st.alertTitle : "ALERT"); _tft.setTextSize(2); _tft.setCursor(10, 80); _tft.print(st.alertBody); _tft.setTextSize(1); _tft.setCursor(10, DISPLAY_HEIGHT - 20); _tft.print("Hold to silence..."); } void DisplayDriverTFT::drawDashboard(const ScreenState& st) { _tft.fillScreen(TFT_BLACK); _tft.setTextColor(TFT_WHITE, TFT_BLACK); _tft.setTextSize(1); _tft.setCursor(5, 5); _tft.printf("KLUBHAUS — %s", deviceStateStr(st.deviceState)); int y = 30; _tft.setCursor(5, y); y += 18; _tft.printf("WiFi: %s %ddBm", st.wifiSsid.c_str(), st.wifiRssi); _tft.setCursor(5, y); y += 18; _tft.printf("IP: %s", st.ipAddr.c_str()); _tft.setCursor(5, y); y += 18; _tft.printf("Up: %lus Heap: %d", st.uptimeMs / 1000, ESP.getFreeHeap()); _tft.setCursor(5, y); y += 18; _tft.printf("Last poll: %lus ago", st.lastPollMs > 0 ? (millis() - st.lastPollMs) / 1000 : 0); } // ── Touch ─────────────────────────────────────────────────── TouchEvent DisplayDriverTFT::readTouch() { TouchEvent evt; uint16_t tx, ty; if(_tft.getTouch(&tx, &ty)) { evt.pressed = true; evt.x = tx; evt.y = ty; } return evt; } int DisplayDriverTFT::dashboardTouch(int x, int y) { // 2x2 grid, accounting for 30px header if(y < 30) return -1; int col = (x * 2) / DISPLAY_WIDTH; // 0 or 1 int row = ((y - 30) * 2) / (DISPLAY_HEIGHT - 30); // 0 or 1 if(col < 0 || col > 1 || row < 0 || row > 1) return -1; return row * 2 + col; // 0, 1, 2, or 3 } HoldState DisplayDriverTFT::updateHold(unsigned long holdMs) { HoldState h; TouchEvent t = readTouch(); if(t.pressed) { if(!_holdActive) { _holdActive = true; _holdStartMs = millis(); h.started = true; } uint32_t held = millis() - _holdStartMs; h.active = true; h.progress = constrain((float)held / (float)holdMs, 0.0f, 1.0f); h.completed = (held >= holdMs); // Simple progress bar at bottom of screen int barW = (int)(DISPLAY_WIDTH * h.progress); _tft.fillRect(0, DISPLAY_HEIGHT - 8, barW, 8, TFT_WHITE); _tft.fillRect(barW, DISPLAY_HEIGHT - 8, DISPLAY_WIDTH - barW, 8, TFT_DARKGREY); } else { if(_holdActive) { // Clear the progress bar when released _tft.fillRect(0, DISPLAY_HEIGHT - 8, DISPLAY_WIDTH, 8, TFT_DARKGREY); } _holdActive = false; } return h; } void DisplayDriverTFT::updateHint(int x, int y) { float t = (millis() % 2000) / 2000.0f; uint8_t v = (uint8_t)(30.0f + 30.0f * sinf(t * 2 * PI)); uint16_t col = _tft.color565(v, v, v); // Draw at touch position instead of center _tft.drawRect(x - 40, y - 20, 80, 40, col); }