feat(ui): implementar doble buffer (TFT_eSprite) para transicion 100% libre de parpadeo
This commit is contained in:
parent
7eb50cd389
commit
db38f557a1
1 changed files with 152 additions and 83 deletions
|
|
@ -1236,29 +1236,25 @@ void plotNoData(uint8_t cell) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fast slide-frame rendering of Page 0 (4 Quadrants) shifted by x_offset (optimized for 60 FPS)
|
// Render Page 0 (4 Quadrants) into double-buffered Sprite slice
|
||||||
inline void drawSlidePage0(int x_offset) {
|
inline void renderPage0ToSprite(TFT_eSprite &s, int x_offset, int y_slice, int slice_h) {
|
||||||
tft.fillRect(x_offset, 0, 320, 240, TFT_BLACK);
|
|
||||||
|
|
||||||
uint16_t bgCol = getThemeBgColor();
|
uint16_t bgCol = getThemeBgColor();
|
||||||
uint16_t divCol = getThemeDivColor();
|
uint16_t divCol = getThemeDivColor();
|
||||||
uint16_t priCol = getThemePrimaryColor();
|
uint16_t priCol = getThemePrimaryColor();
|
||||||
|
|
||||||
// 4 Quadrants
|
|
||||||
for (uint8_t c = 0; c < 4; c++) {
|
for (uint8_t c = 0; c < 4; c++) {
|
||||||
int qx = cell4_x[c] + x_offset;
|
int qx = cell4_x[c] + x_offset;
|
||||||
int qy = cell4_y[c];
|
int qy = cell4_y[c] - y_slice;
|
||||||
int rx = (c & 1) ? (161 + x_offset) : (0 + x_offset);
|
int rx = (c & 1) ? (161 + x_offset) : (0 + x_offset);
|
||||||
int ry = (c & 2) ? 121 : 0;
|
int ry = ((c & 2) ? 121 : 0) - y_slice;
|
||||||
|
|
||||||
// Fast hardware fill for quadrant background
|
if (qx + 160 <= 0 || qx >= 320 || ry + 119 <= 0 || ry >= slice_h) continue;
|
||||||
tft.fillRect(rx, ry, 159, 119, bgCol);
|
|
||||||
|
s.fillRect(rx, ry, 159, 119, bgCol);
|
||||||
|
|
||||||
uint8_t pidIdx = pidInCell[c];
|
uint8_t pidIdx = pidInCell[c];
|
||||||
|
s.setTextColor(TFT_WHITE, bgCol);
|
||||||
// Fast header label in font 2
|
s.drawString(pidConfig[pidIdx][0].c_str(), qx + 8, qy + 6, 2);
|
||||||
tft.setTextColor(TFT_WHITE, bgCol);
|
|
||||||
tft.drawString(pidConfig[pidIdx][0].c_str(), qx + 8, qy + 6, 2);
|
|
||||||
|
|
||||||
int warn = warningValue[pidIdx].toInt();
|
int warn = warningValue[pidIdx].toInt();
|
||||||
int min = pidConfig[pidIdx][4].toInt();
|
int min = pidConfig[pidIdx][4].toInt();
|
||||||
|
|
@ -1266,9 +1262,8 @@ inline void drawSlidePage0(int x_offset) {
|
||||||
bool digit = pidConfig[pidIdx][7].toInt();
|
bool digit = pidConfig[pidIdx][7].toInt();
|
||||||
float val = last_valid_data[c];
|
float val = last_valid_data[c];
|
||||||
|
|
||||||
// Horizontal gauge bar
|
|
||||||
int barY = qy + 28;
|
int barY = qy + 28;
|
||||||
tft.fillRoundRect(qx + 8, barY, 144, 6, 2, METER_INACTIVE_COLOR);
|
s.fillRoundRect(qx + 8, barY, 144, 6, 2, METER_INACTIVE_COLOR);
|
||||||
if (val > -900.0f) {
|
if (val > -900.0f) {
|
||||||
uint16_t col = getMeterColor(pidIdx, val, warn);
|
uint16_t col = getMeterColor(pidIdx, val, warn);
|
||||||
float ratio = (val - (float)min) / (float)(max - min);
|
float ratio = (val - (float)min) / (float)(max - min);
|
||||||
|
|
@ -1277,88 +1272,162 @@ inline void drawSlidePage0(int x_offset) {
|
||||||
int fillW = (int)(ratio * 144.0f);
|
int fillW = (int)(ratio * 144.0f);
|
||||||
if (fillW < 4) fillW = 4;
|
if (fillW < 4) fillW = 4;
|
||||||
if (fillW > 144) fillW = 144;
|
if (fillW > 144) fillW = 144;
|
||||||
tft.fillRoundRect(qx + 8, barY, fillW, 6, 2, col);
|
s.fillRoundRect(qx + 8, barY, fillW, 6, 2, col);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Digital value using DSEG7 Modern 34pt
|
|
||||||
String numStr = (val <= -900.0f) ? "---" : String(val, digit);
|
String numStr = (val <= -900.0f) ? "---" : String(val, digit);
|
||||||
tft.loadFont(DSEG7_Modern_BoldItalic_34);
|
s.loadFont(DSEG7_Modern_BoldItalic_34);
|
||||||
tft.setTextColor(priCol, bgCol);
|
s.setTextColor(priCol, bgCol);
|
||||||
tft.setTextPadding(114);
|
s.setTextPadding(114);
|
||||||
tft.drawRightString(numStr.c_str(), qx + 152, qy + 70, 1);
|
s.drawRightString(numStr.c_str(), qx + 152, qy + 70, 1);
|
||||||
tft.setTextPadding(0);
|
s.setTextPadding(0);
|
||||||
tft.unloadFont();
|
s.unloadFont();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Crosshair dividers
|
// Crosshair dividers
|
||||||
tft.fillRect(159 + x_offset, 0, 2, 240, divCol);
|
s.fillRect(159 + x_offset, 0, 2, slice_h, divCol);
|
||||||
tft.fillRect(x_offset, 119, 320, 2, divCol);
|
int hLineY = 119 - y_slice;
|
||||||
|
if (hLineY >= 0 && hLineY < slice_h) {
|
||||||
|
s.fillRect(x_offset, hLineY, 320, 2, divCol);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Butter-smooth mobile slide transition with 14-frame ease-out deceleration
|
// Render Page 1 (3 Temperature Cards) into double-buffered Sprite slice
|
||||||
|
inline void renderPage1ToSprite(TFT_eSprite &s, int x_offset, int y_slice, int slice_h) {
|
||||||
|
for (uint8_t cardIdx = 0; cardIdx < 3; cardIdx++) {
|
||||||
|
int cardY = (6 + cardIdx * 77) - y_slice;
|
||||||
|
int cardH = 73;
|
||||||
|
int cardX = 4 + x_offset;
|
||||||
|
int cardW = 312;
|
||||||
|
|
||||||
|
if (cardX + cardW <= 0 || cardX >= 320 || cardY + cardH <= 0 || cardY >= slice_h) continue;
|
||||||
|
|
||||||
|
uint16_t cardBg = 0x10A2;
|
||||||
|
uint16_t cardBorder = 0x2945;
|
||||||
|
|
||||||
|
s.fillRoundRect(cardX, cardY, cardW, cardH, 6, cardBg);
|
||||||
|
s.drawRoundRect(cardX, cardY, cardW, cardH, 6, cardBorder);
|
||||||
|
|
||||||
|
uint16_t accentCol = getThemePrimaryColor();
|
||||||
|
const char* titleStr = "";
|
||||||
|
const char* subStr = "";
|
||||||
|
float val = 0.0f;
|
||||||
|
float minRange = 0.0f, maxRange = 100.0f;
|
||||||
|
bool isInt = false;
|
||||||
|
|
||||||
|
if (cardIdx == 0) {
|
||||||
|
titleStr = "MOTOR / REFRIGERANTE";
|
||||||
|
subStr = ignitionOn ? "ECU OBD-II (PID 0105)" : "ECU OBD-II (STANDBY)";
|
||||||
|
val = tempEngineCoolant;
|
||||||
|
minRange = 60.0f; maxRange = 120.0f; isInt = true;
|
||||||
|
if (val >= 110.0f) accentCol = TFT_RED;
|
||||||
|
else if (val >= 100.0f) accentCol = TFT_YELLOW;
|
||||||
|
} else if (cardIdx == 1) {
|
||||||
|
titleStr = "HABITACULO / INTERIOR";
|
||||||
|
subStr = (dsSensorCount > 0) ? "DS18B20 SENSOR 1" : "DS18B20 SENSOR 1 (DEMO)";
|
||||||
|
val = tempInterior;
|
||||||
|
minRange = 10.0f; maxRange = 40.0f;
|
||||||
|
accentCol = 0x07F3;
|
||||||
|
} else {
|
||||||
|
titleStr = "AMBIENTE / EXTERIOR";
|
||||||
|
subStr = (dsSensorCount > 1) ? "DS18B20 SENSOR 2" : "DS18B20 SENSOR 2 (DEMO)";
|
||||||
|
val = tempExterior;
|
||||||
|
minRange = -10.0f; maxRange = 50.0f;
|
||||||
|
accentCol = 0x3D7F;
|
||||||
|
}
|
||||||
|
|
||||||
|
s.fillRoundRect(cardX + 6, cardY + 8, 4, cardH - 16, 2, accentCol);
|
||||||
|
s.setTextColor(TFT_WHITE, cardBg);
|
||||||
|
s.drawString(titleStr, cardX + 16, cardY + 8, 2);
|
||||||
|
s.setTextColor(0x8410, cardBg);
|
||||||
|
s.drawString(subStr, cardX + 16, cardY + 28, 1);
|
||||||
|
|
||||||
|
// Progress bar
|
||||||
|
int barX = cardX + 16;
|
||||||
|
int barY = cardY + 50;
|
||||||
|
int barW = 120;
|
||||||
|
s.fillRoundRect(barX, barY, barW, 8, 3, 0x18C3);
|
||||||
|
float ratio = (val - minRange) / (maxRange - minRange);
|
||||||
|
if (ratio < 0.0f) ratio = 0.0f;
|
||||||
|
if (ratio > 1.0f) ratio = 1.0f;
|
||||||
|
int fillW = (int)(barW * ratio);
|
||||||
|
if (fillW < 4) fillW = 4;
|
||||||
|
s.fillRoundRect(barX, barY, fillW, 8, 3, accentCol);
|
||||||
|
|
||||||
|
// Value text
|
||||||
|
String numStr = (val <= -900.0f) ? "---" : (isInt ? String((int)val) : String(val, 1));
|
||||||
|
s.loadFont(DSEG7_Modern_BoldItalic_34);
|
||||||
|
s.setTextColor(accentCol, cardBg);
|
||||||
|
s.setTextPadding(100);
|
||||||
|
s.drawRightString(numStr.c_str(), cardX + 262, cardY + 22, 1);
|
||||||
|
s.setTextPadding(0);
|
||||||
|
s.unloadFont();
|
||||||
|
|
||||||
|
s.setTextColor(0xC618, cardBg);
|
||||||
|
s.drawString("`C", cardX + 268, cardY + 32, 2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 100% Flicker-Free Double-Buffered Mobile Slide Transition
|
||||||
inline void animateSlideTransition(bool swipeLeft) {
|
inline void animateSlideTransition(bool swipeLeft) {
|
||||||
touchBeep();
|
touchBeep();
|
||||||
const uint8_t FRAMES = 14;
|
|
||||||
// Cubic ease-out deceleration curve: fast initial departure, silk-smooth docking
|
|
||||||
const int16_t step_shifts[FRAMES] = { 38, 74, 108, 140, 170, 198, 224, 248, 268, 285, 298, 308, 316, 320 };
|
|
||||||
|
|
||||||
if (swipeLeft) {
|
TFT_eSprite spr(&tft);
|
||||||
// SWIPE LEFT: Page 0 slides out to the left, Page 1 enters from the right
|
int slice_h = 240;
|
||||||
|
// Try 320x240 full double-buffer first; if heap is tight, allocate two 320x120 slices
|
||||||
|
if (spr.createSprite(320, 240) == nullptr) {
|
||||||
|
slice_h = 120;
|
||||||
|
if (spr.createSprite(320, 120) == nullptr) {
|
||||||
|
return; // Fallback: switch instantly without crashing if RAM is fully depleted
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const uint8_t FRAMES = 10;
|
||||||
|
// Smooth cubic ease-out deceleration curve
|
||||||
|
const int16_t step_shifts[FRAMES] = { 45, 92, 140, 186, 228, 264, 290, 308, 317, 320 };
|
||||||
|
uint8_t numSlices = (slice_h == 240) ? 1 : 2;
|
||||||
|
|
||||||
for (uint8_t f = 0; f < FRAMES; f++) {
|
for (uint8_t f = 0; f < FRAMES; f++) {
|
||||||
int16_t shift = step_shifts[f];
|
int16_t shift = step_shifts[f];
|
||||||
int16_t splitX = 320 - shift;
|
int16_t splitX = swipeLeft ? (320 - shift) : shift;
|
||||||
|
int16_t x0 = swipeLeft ? (-shift) : (shift - 320);
|
||||||
|
int16_t x1 = swipeLeft ? (320 - shift) : shift;
|
||||||
|
|
||||||
|
for (uint8_t s = 0; s < numSlices; s++) {
|
||||||
|
int y_slice = s * slice_h;
|
||||||
|
|
||||||
|
spr.fillSprite(TFT_BLACK);
|
||||||
|
|
||||||
if (splitX > 0) {
|
if (splitX > 0) {
|
||||||
tft.setViewport(0, 0, splitX, 240, false);
|
spr.setViewport(0, 0, splitX, slice_h, false);
|
||||||
drawSlidePage0(-shift);
|
renderPage0ToSprite(spr, x0, y_slice, slice_h);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (splitX < 320) {
|
if (splitX < 320) {
|
||||||
tft.setViewport(splitX, 0, 320 - splitX, 240, false);
|
spr.setViewport(splitX, 0, 320 - splitX, slice_h, false);
|
||||||
drawSlidePage1(splitX);
|
renderPage1ToSprite(spr, x1, y_slice, slice_h);
|
||||||
}
|
}
|
||||||
|
spr.resetViewport();
|
||||||
|
|
||||||
tft.resetViewport();
|
// Dividing neon seam line
|
||||||
if (splitX > 0 && splitX < 320) {
|
if (splitX > 0 && splitX < 320) {
|
||||||
tft.drawFastVLine(splitX, 0, 240, getThemePrimaryColor());
|
spr.drawFastVLine(splitX, 0, slice_h, getThemePrimaryColor());
|
||||||
tft.drawFastVLine(splitX + 1, 0, 240, TFT_WHITE);
|
spr.drawFastVLine(splitX + (swipeLeft ? 1 : -1), 0, slice_h, TFT_WHITE);
|
||||||
}
|
}
|
||||||
|
|
||||||
tft.fillCircle(153, 2, 2, (splitX > 160) ? getThemePrimaryColor() : 0x5AEB);
|
// Pagination indicator dots on top slice
|
||||||
tft.fillCircle(167, 2, 2, (splitX <= 160) ? getThemePrimaryColor() : 0x5AEB);
|
if (y_slice == 0) {
|
||||||
|
spr.fillCircle(153, 2, 2, (splitX > 160) ? getThemePrimaryColor() : 0x5AEB);
|
||||||
delay(6);
|
spr.fillCircle(167, 2, 2, (splitX <= 160) ? getThemePrimaryColor() : 0x5AEB);
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// SWIPE RIGHT: Page 1 slides out to the right, Page 0 enters from the left
|
|
||||||
for (uint8_t f = 0; f < FRAMES; f++) {
|
|
||||||
int16_t shift = step_shifts[f];
|
|
||||||
int16_t splitX = shift;
|
|
||||||
|
|
||||||
if (splitX > 0) {
|
|
||||||
tft.setViewport(0, 0, splitX, 240, false);
|
|
||||||
drawSlidePage0(splitX - 320);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (splitX < 320) {
|
// Direct single SPI push: ZERO visual flicker!
|
||||||
tft.setViewport(splitX, 0, 320 - splitX, 240, false);
|
spr.pushSprite(0, y_slice);
|
||||||
drawSlidePage1(splitX);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
tft.resetViewport();
|
delay(8);
|
||||||
if (splitX > 0 && splitX < 320) {
|
|
||||||
tft.drawFastVLine(splitX, 0, 240, getThemePrimaryColor());
|
|
||||||
tft.drawFastVLine(splitX - 1, 0, 240, TFT_WHITE);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
tft.fillCircle(153, 2, 2, (splitX >= 160) ? getThemePrimaryColor() : 0x5AEB);
|
spr.deleteSprite();
|
||||||
tft.fillCircle(167, 2, 2, (splitX < 160) ? getThemePrimaryColor() : 0x5AEB);
|
|
||||||
|
|
||||||
delay(6);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
tft.resetViewport();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*-------------------*/
|
/*-------------------*/
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue