feat: add display driver abstraction layer
Gfx / GfxSprite types: - TFT_eSPI path: zero-cost typedefs to TFT_eSPI / TFT_eSprite - Arduino_GFX path: adapter classes with TFT_eSPI-compatible API DisplayDriverGFX.cpp compiles to nothing on TFT_eSPI targets. GfxSprite on Arduino_GFX uses direct-draw (no offscreen buffer yet).
This commit is contained in:
117
sketches/doorbell-touch-esp32-32e/DisplayDriver.h
Normal file
117
sketches/doorbell-touch-esp32-32e/DisplayDriver.h
Normal file
@@ -0,0 +1,117 @@
|
||||
#pragma once
|
||||
#include "BoardConfig.h"
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════════
|
||||
// Display driver abstraction
|
||||
//
|
||||
// TFT_eSPI path: zero-cost typedefs — compiles identically to before
|
||||
// Arduino_GFX path: adapter classes providing TFT_eSPI-compatible API
|
||||
// ═══════════════════════════════════════════════════════════════════
|
||||
|
||||
#if USE_TFT_ESPI
|
||||
// ─────────────────────────────────────────────────────────────────
|
||||
// TFT_eSPI — straight typedefs, zero overhead
|
||||
// ─────────────────────────────────────────────────────────────────
|
||||
#include <TFT_eSPI.h>
|
||||
|
||||
using Gfx = TFT_eSPI;
|
||||
using GfxSprite = TFT_eSprite;
|
||||
|
||||
#elif USE_ARDUINO_GFX
|
||||
// ─────────────────────────────────────────────────────────────────
|
||||
// Arduino_GFX — adapter wrapping Arduino_GFX with a
|
||||
// TFT_eSPI-compatible interface for Dashboard / DisplayManager
|
||||
// ─────────────────────────────────────────────────────────────────
|
||||
#include <Arduino_GFX_Library.h>
|
||||
|
||||
// Text datum constants (matching TFT_eSPI definitions)
|
||||
#ifndef MC_DATUM
|
||||
#define TL_DATUM 0
|
||||
#define TC_DATUM 1
|
||||
#define TR_DATUM 2
|
||||
#define ML_DATUM 3
|
||||
#define CL_DATUM 3
|
||||
#define MC_DATUM 4
|
||||
#define CC_DATUM 4
|
||||
#define MR_DATUM 5
|
||||
#define CR_DATUM 5
|
||||
#define BL_DATUM 6
|
||||
#define BC_DATUM 7
|
||||
#define BR_DATUM 8
|
||||
#endif
|
||||
|
||||
class Gfx; // forward declaration for GfxSprite
|
||||
|
||||
// ── Sprite adapter ──────────────────────────────────────────────
|
||||
class GfxSprite {
|
||||
public:
|
||||
explicit GfxSprite(Gfx* parent);
|
||||
|
||||
void createSprite(int16_t w, int16_t h);
|
||||
void deleteSprite();
|
||||
void fillSprite(uint16_t color);
|
||||
void fillRoundRect(int32_t x, int32_t y, int32_t w, int32_t h,
|
||||
int32_t r, uint16_t color);
|
||||
void drawRoundRect(int32_t x, int32_t y, int32_t w, int32_t h,
|
||||
int32_t r, uint16_t color);
|
||||
void setTextColor(uint16_t fg, uint16_t bg);
|
||||
void setTextFont(uint8_t font);
|
||||
void setTextSize(uint8_t size);
|
||||
void setTextDatum(uint8_t datum);
|
||||
void drawString(const char* str, int32_t x, int32_t y);
|
||||
void pushSprite(int32_t x, int32_t y);
|
||||
|
||||
private:
|
||||
Gfx* _parent;
|
||||
int16_t _w = 0;
|
||||
int16_t _h = 0;
|
||||
int32_t _pushX = 0;
|
||||
int32_t _pushY = 0;
|
||||
uint8_t _textDatum = TL_DATUM;
|
||||
uint8_t _textSize = 1;
|
||||
uint16_t _textFg = 0xFFFF;
|
||||
uint16_t _textBg = 0x0000;
|
||||
};
|
||||
|
||||
// ── Display adapter ─────────────────────────────────────────────
|
||||
class Gfx {
|
||||
public:
|
||||
Gfx();
|
||||
|
||||
void init();
|
||||
void setRotation(uint8_t r);
|
||||
|
||||
// Drawing primitives
|
||||
void fillScreen(uint16_t color);
|
||||
void fillRect(int32_t x, int32_t y, int32_t w, int32_t h,
|
||||
uint16_t color);
|
||||
void fillRoundRect(int32_t x, int32_t y, int32_t w, int32_t h,
|
||||
int32_t r, uint16_t color);
|
||||
void drawRoundRect(int32_t x, int32_t y, int32_t w, int32_t h,
|
||||
int32_t r, uint16_t color);
|
||||
void drawFastVLine(int32_t x, int32_t y, int32_t h, uint16_t color);
|
||||
|
||||
// Text (datum-based API matching TFT_eSPI)
|
||||
void setTextColor(uint16_t fg, uint16_t bg);
|
||||
void setTextFont(uint8_t font);
|
||||
void setTextSize(uint8_t size);
|
||||
void setTextDatum(uint8_t datum);
|
||||
void drawString(const char* str, int32_t x, int32_t y);
|
||||
int16_t textWidth(const char* str);
|
||||
void setCursor(int32_t x, int32_t y);
|
||||
void print(const char* str);
|
||||
|
||||
// Escape hatch for direct Arduino_GFX access
|
||||
Arduino_GFX* raw() { return _gfx; }
|
||||
|
||||
private:
|
||||
Arduino_GFX* _gfx = nullptr;
|
||||
uint8_t _textDatum = TL_DATUM;
|
||||
uint8_t _textSize = 1;
|
||||
uint16_t _textFg = 0xFFFF;
|
||||
uint16_t _textBg = 0x0000;
|
||||
};
|
||||
|
||||
#else
|
||||
#error "No display driver selected — check BoardConfig.h"
|
||||
#endif
|
||||
185
sketches/doorbell-touch-esp32-32e/DisplayDriverGFX.cpp
Normal file
185
sketches/doorbell-touch-esp32-32e/DisplayDriverGFX.cpp
Normal file
@@ -0,0 +1,185 @@
|
||||
// ═══════════════════════════════════════════════════════════════════
|
||||
// Arduino_GFX adapter implementation
|
||||
// Only compiled when USE_ARDUINO_GFX is set (Waveshare path).
|
||||
// For the TFT_eSPI path this file compiles to nothing.
|
||||
// ═══════════════════════════════════════════════════════════════════
|
||||
#include "BoardConfig.h"
|
||||
|
||||
#if USE_ARDUINO_GFX
|
||||
|
||||
#include "DisplayDriver.h"
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────
|
||||
// Gfx adapter
|
||||
// ─────────────────────────────────────────────────────────────────
|
||||
|
||||
Gfx::Gfx() {}
|
||||
|
||||
void Gfx::init() {
|
||||
// Waveshare ESP32-S3 Touch LCD 4.3" — RGB parallel, ST7262 panel
|
||||
Arduino_ESP32RGBPanel *rgbpanel = new Arduino_ESP32RGBPanel(
|
||||
LCD_DE, LCD_VSYNC, LCD_HSYNC, LCD_PCLK,
|
||||
LCD_R0, LCD_R1, LCD_R2, LCD_R3, LCD_R4,
|
||||
LCD_G0, LCD_G1, LCD_G2, LCD_G3, LCD_G4, LCD_G5,
|
||||
LCD_B0, LCD_B1, LCD_B2, LCD_B3, LCD_B4,
|
||||
1 /* hsync_polarity */, 46 /* hsync_front_porch */,
|
||||
2 /* hsync_pulse_width */, 44 /* hsync_back_porch */,
|
||||
1 /* vsync_polarity */, 50 /* vsync_front_porch */,
|
||||
16 /* vsync_pulse_width */, 16 /* vsync_back_porch */
|
||||
);
|
||||
|
||||
_gfx = new Arduino_RGB_Display(
|
||||
SCREEN_WIDTH, SCREEN_HEIGHT, rgbpanel,
|
||||
DISPLAY_ROTATION, true /* auto_flush */);
|
||||
|
||||
if (!_gfx->begin()) {
|
||||
Serial.println("[GFX] Display init FAILED");
|
||||
return;
|
||||
}
|
||||
Serial.printf("[GFX] Display OK: %dx%d\n", SCREEN_WIDTH, SCREEN_HEIGHT);
|
||||
}
|
||||
|
||||
void Gfx::setRotation(uint8_t r) { if (_gfx) _gfx->setRotation(r); }
|
||||
void Gfx::fillScreen(uint16_t c) { if (_gfx) _gfx->fillScreen(c); }
|
||||
|
||||
void Gfx::fillRect(int32_t x, int32_t y, int32_t w, int32_t h, uint16_t c) {
|
||||
if (_gfx) _gfx->fillRect(x, y, w, h, c);
|
||||
}
|
||||
void Gfx::fillRoundRect(int32_t x, int32_t y, int32_t w, int32_t h,
|
||||
int32_t r, uint16_t c) {
|
||||
if (_gfx) _gfx->fillRoundRect(x, y, w, h, r, c);
|
||||
}
|
||||
void Gfx::drawRoundRect(int32_t x, int32_t y, int32_t w, int32_t h,
|
||||
int32_t r, uint16_t c) {
|
||||
if (_gfx) _gfx->drawRoundRect(x, y, w, h, r, c);
|
||||
}
|
||||
void Gfx::drawFastVLine(int32_t x, int32_t y, int32_t h, uint16_t c) {
|
||||
if (_gfx) _gfx->drawFastVLine(x, y, h, c);
|
||||
}
|
||||
|
||||
void Gfx::setTextColor(uint16_t fg, uint16_t bg) {
|
||||
_textFg = fg; _textBg = bg;
|
||||
if (_gfx) _gfx->setTextColor(fg, bg);
|
||||
}
|
||||
|
||||
void Gfx::setTextFont(uint8_t font) {
|
||||
// TFT_eSPI font IDs don't map 1:1 to Arduino_GFX.
|
||||
// Using default built-in font; setTextSize controls scale.
|
||||
// TODO: Map to GFXfont pointers for better visual fidelity.
|
||||
(void)font;
|
||||
}
|
||||
|
||||
void Gfx::setTextSize(uint8_t s) {
|
||||
_textSize = s;
|
||||
if (_gfx) _gfx->setTextSize(s);
|
||||
}
|
||||
|
||||
void Gfx::setTextDatum(uint8_t d) { _textDatum = d; }
|
||||
|
||||
void Gfx::drawString(const char* str, int32_t x, int32_t y) {
|
||||
if (!_gfx || !str) return;
|
||||
|
||||
int16_t bx, by;
|
||||
uint16_t tw, th;
|
||||
_gfx->getTextBounds(str, 0, 0, &bx, &by, &tw, &th);
|
||||
|
||||
// Horizontal alignment from datum
|
||||
int hAlign = _textDatum % 3;
|
||||
if (hAlign == 1) x -= (int32_t)tw / 2; // center
|
||||
else if (hAlign == 2) x -= (int32_t)tw; // right
|
||||
|
||||
// Vertical alignment from datum
|
||||
int vAlign = _textDatum / 3;
|
||||
if (vAlign == 1) y -= (int32_t)th / 2; // middle
|
||||
else if (vAlign == 2) y -= (int32_t)th; // bottom
|
||||
|
||||
_gfx->setCursor(x - bx, y - by);
|
||||
_gfx->print(str);
|
||||
}
|
||||
|
||||
int16_t Gfx::textWidth(const char* str) {
|
||||
if (!_gfx || !str) return 0;
|
||||
int16_t bx, by;
|
||||
uint16_t tw, th;
|
||||
_gfx->getTextBounds(str, 0, 0, &bx, &by, &tw, &th);
|
||||
return (int16_t)tw;
|
||||
}
|
||||
|
||||
void Gfx::setCursor(int32_t x, int32_t y) {
|
||||
if (_gfx) _gfx->setCursor(x, y);
|
||||
}
|
||||
|
||||
void Gfx::print(const char* str) {
|
||||
if (_gfx) _gfx->print(str);
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────
|
||||
// GfxSprite adapter
|
||||
//
|
||||
// On the 800x480 RGB panel the LCD controller has its own GRAM,
|
||||
// so direct drawing rarely tears. This implementation is
|
||||
// intentionally minimal — it renders tiles as direct draws.
|
||||
//
|
||||
// TODO: For flicker-free tile updates, allocate an Arduino_Canvas
|
||||
// backed by PSRAM and blit via draw16bitBeRGBBitmap().
|
||||
// ─────────────────────────────────────────────────────────────────
|
||||
|
||||
GfxSprite::GfxSprite(Gfx* parent) : _parent(parent) {}
|
||||
|
||||
void GfxSprite::createSprite(int16_t w, int16_t h) {
|
||||
_w = w; _h = h;
|
||||
Serial.printf("[GFX] Sprite %dx%d created (direct-draw mode)\n", w, h);
|
||||
}
|
||||
|
||||
void GfxSprite::deleteSprite() { _w = 0; _h = 0; }
|
||||
|
||||
void GfxSprite::fillSprite(uint16_t color) {
|
||||
if (_parent && _parent->raw())
|
||||
_parent->raw()->fillRect(_pushX, _pushY, _w, _h, color);
|
||||
}
|
||||
|
||||
void GfxSprite::fillRoundRect(int32_t x, int32_t y, int32_t w, int32_t h,
|
||||
int32_t r, uint16_t c) {
|
||||
if (_parent && _parent->raw())
|
||||
_parent->raw()->fillRoundRect(_pushX + x, _pushY + y, w, h, r, c);
|
||||
}
|
||||
|
||||
void GfxSprite::drawRoundRect(int32_t x, int32_t y, int32_t w, int32_t h,
|
||||
int32_t r, uint16_t c) {
|
||||
if (_parent && _parent->raw())
|
||||
_parent->raw()->drawRoundRect(_pushX + x, _pushY + y, w, h, r, c);
|
||||
}
|
||||
|
||||
void GfxSprite::setTextColor(uint16_t fg, uint16_t bg) {
|
||||
_textFg = fg; _textBg = bg;
|
||||
if (_parent && _parent->raw()) _parent->raw()->setTextColor(fg, bg);
|
||||
}
|
||||
|
||||
void GfxSprite::setTextFont(uint8_t font) { (void)font; }
|
||||
|
||||
void GfxSprite::setTextSize(uint8_t size) {
|
||||
_textSize = size;
|
||||
if (_parent && _parent->raw()) _parent->raw()->setTextSize(size);
|
||||
}
|
||||
|
||||
void GfxSprite::setTextDatum(uint8_t datum) { _textDatum = datum; }
|
||||
|
||||
void GfxSprite::drawString(const char* str, int32_t x, int32_t y) {
|
||||
if (!_parent) return;
|
||||
_parent->setTextDatum(_textDatum);
|
||||
_parent->setTextSize(_textSize);
|
||||
_parent->setTextColor(_textFg, _textBg);
|
||||
_parent->drawString(str, _pushX + x, _pushY + y);
|
||||
}
|
||||
|
||||
void GfxSprite::pushSprite(int32_t x, int32_t y) {
|
||||
// Record the offset for subsequent draw calls in the next cycle.
|
||||
// NOTE: In the TFT_eSPI path, sprite drawing happens BEFORE pushSprite
|
||||
// (draws into offscreen buffer, then blits). In this direct-draw stub,
|
||||
// the offset from the PREVIOUS pushSprite call is used. After one full
|
||||
// drawAll() cycle, all tiles render at the correct positions.
|
||||
_pushX = x;
|
||||
_pushY = y;
|
||||
}
|
||||
|
||||
#endif // USE_ARDUINO_GFX
|
||||
Reference in New Issue
Block a user