// boards/esp32-s3-lcd-43/DisplayDriverGFX.cpp #include "DisplayDriverGFX.h" #include "LovyanPins.h" #include "board_config.h" #include // ── Globals ── static LGFX* _gfx = nullptr; // ── Forward declarations ── static void initDisplay(); // ── Dimensions ── static constexpr int DISP_W = 800; static constexpr int DISP_H = 480; // ── Display initialization ── static void initDisplay() { Serial.println("LovyanGFX init..."); // Note: LovyanGFX handles I2C internally (port 1 for touch, port 0 for CH422G) // No need to call Wire.begin() or Wire1.begin() _gfx = new LGFX(); _gfx->init(); _gfx->setRotation(0); // Landscape _gfx->fillScreen(0x000000); Serial.println("Display ready"); } // ── Singleton ── DisplayDriverGFX& DisplayDriverGFX::instance() { static DisplayDriverGFX inst; return inst; } // ── IDisplayDriver implementation ── void DisplayDriverGFX::begin() { initDisplay(); // Turn on backlight immediately setBacklight(true); } void DisplayDriverGFX::setBacklight(bool on) { if(_gfx) { // LovyanGFX handles backlight via setBrightness _gfx->setBrightness(on ? 255 : 0); } } int DisplayDriverGFX::width() { return DISP_W; } int DisplayDriverGFX::height() { return DISP_H; } // ── Touch handling ── TouchEvent DisplayDriverGFX::readTouch() { TouchEvent evt; if(!_gfx) return evt; int32_t x, y; bool pressed = _gfx->getTouch(&x, &y); // Only report NEW touches (debounce - ignore held touches) evt.pressed = pressed && !_lastTouch.pressed; if(pressed) { evt.x = static_cast(x); evt.y = static_cast(y); _pressStartMs = millis(); } _lastTouch.pressed = pressed; return evt; } int DisplayDriverGFX::dashboardTouch(int x, int y) { // Dashboard tiles: 2 rows × 4 columns constexpr int cols = 4; constexpr int rows = 2; constexpr int tileW = DISP_W / cols; constexpr int tileH = DISP_H / rows; if(x < 0 || x >= DISP_W || y < 0 || y >= DISP_H) { return -1; } int col = x / tileW; int row = y / tileH; return row * cols + col; } HoldState DisplayDriverGFX::updateHold(unsigned long holdMs) { HoldState state; if(!_lastTouch.pressed) { _isHolding = false; return state; } unsigned long elapsed = millis() - _pressStartMs; if(!_isHolding) { _isHolding = true; state.started = true; } state.active = true; state.progress = static_cast(elapsed) / static_cast(holdMs); if(state.progress >= 1.0f) { state.progress = 1.0f; state.completed = true; } return state; } // ── Rendering ── void DisplayDriverGFX::render(const ScreenState& state) { if(!_gfx) return; // Check if we need full redraw if(state.screen != _lastScreen) { _needsRedraw = true; _lastScreen = state.screen; } switch(state.screen) { case ScreenID::BOOT: if(_needsRedraw) { _gfx->fillScreen(0x000000); _gfx->setTextColor(0xFFFF); _gfx->setTextSize(2); _gfx->setCursor(10, 10); _gfx->print("KLUBHAUS BOOT"); _needsRedraw = false; } break; case ScreenID::OFF: if(_needsRedraw) { _gfx->fillScreen(0x000000); _needsRedraw = false; } break; case ScreenID::ALERT: // Only redraw on first entry or screen change if(_needsRedraw) { drawAlert(state); _needsRedraw = false; } break; case ScreenID::DASHBOARD: if(_needsRedraw) { drawDashboard(state); _needsRedraw = false; } break; } } void DisplayDriverGFX::drawAlert(const ScreenState& state) { // Always redraw background for animation uint32_t elapsed = millis() - state.alertStartMs; uint8_cast(180.0f + 75.0f * sinf(elapsed / 300.0f)); uint16_t bg = _gfx->color565(pulse, 0, 0); _gfx->fillScreen(bg); _gfx->setTextColor(0xFFFF, bg); _gfx->setTextSize(3); _gfx->setCursor(10, 20); _gfx->print(state.alertTitle.length() > 0 ? state.alertTitle.c_str() : "ALERT"); _gfx->setTextSize(2); _gfx->setCursor(10, 80); _gfx->print(state.alertBody); _gfx->setTextSize(1); _gfx->setCursor(10, DISP_H - 20); _gfx->print("Hold to silence..."); } void DisplayDriverGFX::drawDashboard(const ScreenState& state) { _gfx->fillScreen(0x001030); // Dark blue // Header _gfx->fillRect(0, 0, DISP_W, 30, 0x1A1A); // Dark gray _gfx->setFont(&fonts::Font0); // Built-in minimal font _gfx->setTextColor(0xFFFF); _gfx->setTextSize(1); _gfx->setCursor(5, 10); _gfx->printf("KLUBHAUS"); // WiFi status _gfx->setCursor(DISP_W - 100, 10); _gfx->printf("WiFi:%s", state.wifiSsid.length() > 0 ? "ON" : "OFF"); // Tiles: 2 rows × 4 columns constexpr int cols = 4; constexpr int rows = 2; constexpr int tileW = DISP_W / cols; constexpr int tileH = (DISP_H - 30) / rows; constexpr int margin = 8; // Draw placeholder tiles (8 total for 2x4 grid) const char* tileLabels[] = { "1", "2", "3", "4", "5", "6", "7", "8" }; for(int i = 0; i < 8; i++) { int col = i % cols; int row = i / cols; int x = col * tileW + margin; int y = 30 + row * tileH + margin; int w = tileW - 2 * margin; int h = tileH - 2 * margin; // Tile background _gfx->fillRoundRect(x, y, w, h, 8, 0x0220); // Tile border _gfx->drawRoundRect(x, y, w, h, 8, 0xFFFF); // Tile number _gfx->setTextColor(0xFFFF); _gfx->setTextSize(2); _gfx->setCursor(x + w / 2 - 10, y + h / 2 - 10); _gfx->print(tileLabels[i]); } } void DisplayDriverGFX::updateHint(int x, int y) { if(!_gfx) return; static uint32_t lastTime = 0; uint32_t now = millis(); // Fixed: throttle to 100ms instead of 50ms if(now - lastTime < 100) return; lastTime = now; float t = (now % 2000) / 2000.0f; uint8_t v = static_cast(30.0f + 30.0f * sinf(t * 2 * 3.14159f)); uint16_t col = ((v >> 3) << 11) | ((v >> 2) << 5) | (v >> 3); // Draw at touch position instead of center _gfx->drawCircle(x, y, 50, col); }