From 63a7f8161a30c64f92aa277a7ee1e8ff4818e2d2 Mon Sep 17 00:00:00 2001 From: Sebastian Nieto Date: Tue, 13 Oct 2026 22:09:37 -0300 Subject: [PATCH] feat(ui): transicion fundido a negro 200ms, temas de color y correccion brillo LDR - Implementa transicion 'Fundido a Negro' (Fade to Black) con atenuacion PWM de ~200ms por curva gamma en reemplazo de deslizamiento. - Corrige el brillo automatico LDR: umbral calibrado a 1000 para ADC de 12 bits, ejecucion continua en el loop sin depender de Bluetooth y telemetria en tiempo real en ajustes. - Agrega nuevos temas de color: Yapa (pastillas pastel multicolor por caracter), Rojo Corsa y Ambar BMW. - Integra efecto arcoiris continuo en menus de configuracion y pantallas completas. - Unifica tipografia de telemetria GPS y renderizado con antialiasing para coordenadas y valores. --- sketch/obd2_gauge/config.h | 1254 +++++++++++++++++++++++++----- sketch/obd2_gauge/coords_font.h | 650 ++++++++++++++++ sketch/obd2_gauge/gps_clock.h | 226 ++++-- sketch/obd2_gauge/gps_nav.h | 169 ++-- sketch/obd2_gauge/meter.h | 197 ++--- sketch/obd2_gauge/obd2_gauge.ino | 51 +- sketch/obd2_gauge/rainbow_text.h | Bin 9147 -> 12396 bytes sketch/obd2_gauge/tri_temp.h | 116 ++- 8 files changed, 2136 insertions(+), 527 deletions(-) create mode 100644 sketch/obd2_gauge/coords_font.h diff --git a/sketch/obd2_gauge/config.h b/sketch/obd2_gauge/config.h index eec8a2b..b552ff6 100644 --- a/sketch/obd2_gauge/config.h +++ b/sketch/obd2_gauge/config.h @@ -4,6 +4,110 @@ Configuration menu for OBD2 Gauge with volume and per-instrument alarm controls #include #include #include +#include "rainbow_text.h" +extern bool isRainbowTheme(); +extern bool isYapaTheme(); + +// ------------------------------------------------------------------- +// Continuous Rainbow Stencil & Alpha Theme Engine +// ------------------------------------------------------------------- +inline uint16_t getRainbowAlphaColor(int16_t x, uint8_t alpha) { + if (alpha >= 255) return getRainbowScreenColor(x); + return tft.alphaBlend(alpha, getRainbowScreenColor(x), TFT_BLACK); +} + +inline void drawRainbowHLine(int16_t y, int16_t h = 2, uint8_t alpha = 255) { + tft.startWrite(); + for (int16_t x = 0; x < 320; x++) { + uint16_t c = (alpha >= 255) ? getRainbowScreenColor(x) : tft.alphaBlend(alpha, getRainbowScreenColor(x), TFT_BLACK); + tft.drawFastVLine(x, y, h, c); + } + tft.endWrite(); +} + +inline int8_t getRainbowCornerInset(int16_t col, int16_t w, int16_t r) { + int16_t d = (col < r) ? (r - 1 - col) : ((col >= w - r) ? (col - (w - r)) : -1); + if (d < 0) return 0; + if (d >= r - 1) return r > 3 ? 3 : 1; + if (d >= r - 2) return r > 3 ? 2 : 1; + return 1; +} + +inline void fillRainbowAlphaRoundRect(int16_t x, int16_t y, int16_t w, int16_t h, int16_t r, uint8_t alpha) { + tft.startWrite(); + for (int16_t col = 0; col < w; col++) { + int16_t curX = x + col; + if (curX < 0 || curX >= 320) continue; + uint16_t colColor = getRainbowAlphaColor(curX, alpha); + int8_t inset = (r > 0) ? getRainbowCornerInset(col, w, r) : 0; + int16_t topY = y + inset; + int16_t lineH = h - 2 * inset; + if (lineH > 0) { + tft.drawFastVLine(curX, topY, lineH, colColor); + } + } + tft.endWrite(); +} + +inline void drawRainbowRoundRect(int16_t x, int16_t y, int16_t w, int16_t h, int16_t r, uint8_t alpha = 255) { + tft.startWrite(); + for (int16_t col = r; col < w - r; col++) { + int16_t curX = x + col; + if (curX < 0 || curX >= 320) continue; + uint16_t c = getRainbowAlphaColor(curX, alpha); + tft.drawPixel(curX, y, c); + tft.drawPixel(curX, y + h - 1, c); + } + uint16_t leftC = getRainbowAlphaColor(x, alpha); + tft.drawFastVLine(x, y + r, h - 2 * r, leftC); + uint16_t rightC = getRainbowAlphaColor(x + w - 1, alpha); + tft.drawFastVLine(x + w - 1, y + r, h - 2 * r, rightC); + + for (int16_t col = 0; col < r; col++) { + int8_t inset = getRainbowCornerInset(col, w, r); + int16_t curX = x + col; + uint16_t cL = getRainbowAlphaColor(curX, alpha); + tft.drawPixel(curX, y + inset, cL); + tft.drawPixel(curX, y + h - 1 - inset, cL); + + int16_t curXR = x + w - 1 - col; + uint16_t cR = getRainbowAlphaColor(curXR, alpha); + tft.drawPixel(curXR, y + inset, cR); + tft.drawPixel(curXR, y + h - 1 - inset, cR); + } + tft.endWrite(); +} + +inline void drawMenuHeaderBanner(const char* title) { + if (isRainbowTheme()) { + tft.fillRect(0, 0, 320, 26, TFT_BLACK); + drawRainbowHLine(26, 2, 255); + drawRainbowCentreString(title, 160, 2, 4); + } else if (isYapaTheme()) { + tft.fillRect(0, 0, 320, 26, TFT_BLACK); + for (int16_t x = 0; x < 320; x++) { + tft.drawFastVLine(x, 26, 2, getYapaColor(x / 53)); + } + drawYapaCentreString(title, 160, 2, 4); + } else { + tft.fillRectVGradient(0, 0, 320, 26, getThemeAccentColor(), getThemeDarkColor()); + tft.fillRect(0, 26, 320, 2, getThemePrimaryColor()); + tft.setTextColor(TFT_WHITE); + tft.drawCentreString(title, 160, 2, 4); + } +} + +inline uint16_t getRainbowItemColor(uint8_t idx) { + switch(idx % 6) { + case 0: return 0xF9C0; + case 1: return 0xFDA0; + case 2: return 0x07E0; + case 3: return 0x07FF; + case 4: return 0x4B3F; + case 5: return 0xE1BF; + default: return 0x07FF; + } +} // Inactivity Auto-Sleep settings (0: OFF, 1: 30s, 2: 1m, 3: 2m, 4: 5m) const uint16_t AUTO_SLEEP_SEC_TABLE[5] = { 0, 30, 60, 120, 300 }; @@ -143,6 +247,28 @@ const String parameterList[4][2] = { // Componente de Boton SALIR con Animacion de Pulsacion Tactil // ------------------------------------------------------------- void drawExitButton(bool pressed) { + if (isRainbowTheme()) { + uint8_t bgAlpha = pressed ? 110 : 40; + uint8_t borderAlpha = pressed ? 255 : 150; + fillRainbowAlphaRoundRect(80, 186, 160, 42, 5, bgAlpha); + drawRainbowRoundRect(80, 186, 160, 42, 5, borderAlpha); + if (pressed) { + drawRainbowRoundRect(81, 187, 158, 40, 4, 255); + } + drawRainbowCentreString("SALIR", 160, 194, 4); + return; + } + if (isYapaTheme()) { + uint16_t bgCol = pressed ? 0x2104 : TFT_BLACK; + uint16_t borderCol = pressed ? 0xFFF2 : 0xDBBF; + tft.fillRoundRect(80, 186, 160, 42, 5, bgCol); + tft.drawRoundRect(80, 186, 160, 42, 5, borderCol); + if (pressed) { + tft.drawRoundRect(81, 187, 158, 40, 4, 0xFD79); + } + drawYapaCentreString("SALIR", 160, 194, 4); + return; + } uint16_t bgCol = pressed ? getThemePrimaryColor() : getThemeAccentColor(); uint16_t txtCol = pressed ? TFT_BLACK : TFT_WHITE; uint16_t borderCol = pressed ? TFT_WHITE : getThemeDarkColor(); @@ -170,26 +296,71 @@ void warningMenu() { tft.fillScreen(TFT_BLACK); auto drawAllRows = [&]() { + if (isRainbowTheme()) { + drawRainbowHLine(2, 2, 255); + for (uint8_t i = 0; i < 4; i++) { + int y = 10 + i * 44; + fillRainbowAlphaRoundRect(8, y, 304, 38, 5, alarmEnabled[i] ? 60 : 25); + drawRainbowRoundRect(8, y, 304, 38, 5, alarmEnabled[i] ? 220 : 80); + drawRainbowString(parameterList[i][0], 22, y + 7, 4); + + // Large tactile ON / OFF badge + uint8_t bAlpha = alarmEnabled[i] ? 120 : 25; + fillRainbowAlphaRoundRect(220, y + 4, 84, 30, 5, bAlpha); + drawRainbowRoundRect(220, y + 4, 84, 30, 5, alarmEnabled[i] ? 255 : 70); + if (alarmEnabled[i]) { + drawRainbowCentreString("ON", 262, y + 7, 4); + } else { + tft.setTextColor(0x7BEF); + tft.drawCentreString("OFF", 262, y + 7, 4); + } + } + return; + } + if (isYapaTheme()) { + for (int16_t x = 0; x < 320; x++) { + tft.drawFastVLine(x, 2, 2, getYapaColor(x / 53)); + } + for (uint8_t i = 0; i < 4; i++) { + int y = 10 + i * 44; + tft.fillRoundRect(8, y, 304, 38, 5, 0x1082); + tft.drawRoundRect(8, y, 304, 38, 5, alarmEnabled[i] ? getYapaColor(i) : 0x3186); + drawYapaString(parameterList[i][0], 22, y + 7, 4); + + // Large tactile ON / OFF badge + tft.fillRoundRect(220, y + 4, 84, 30, 5, alarmEnabled[i] ? 0x2104 : 0x0821); + tft.drawRoundRect(220, y + 4, 84, 30, 5, alarmEnabled[i] ? getYapaColor(i + 2) : 0x3186); + if (alarmEnabled[i]) { + drawYapaCentreString("ON", 262, y + 7, 4); + } else { + drawYapaCentreString("OFF", 262, y + 7, 4); + } + } + return; + } uint16_t onCol = getThemePrimaryColor(); // Clean active accent for (uint8_t i = 0; i < 4; i++) { int y = 10 + i * 44; + uint16_t itemCol = onCol; + uint16_t borderCol = getThemeAccentColor(); + tft.fillRoundRect(8, y, 304, 38, 5, getThemeDarkColor()); - tft.drawRoundRect(8, y, 304, 38, 5, getThemeAccentColor()); + tft.drawRoundRect(8, y, 304, 38, 5, borderCol); if (alarmEnabled[i]) { - tft.fillRoundRect(10, y + 4, 3, 30, 1, onCol); + tft.fillRoundRect(10, y + 4, 3, 30, 1, itemCol); } tft.setTextColor(alarmEnabled[i] ? TFT_WHITE : 0x8410, getThemeDarkColor()); tft.drawString(parameterList[i][0], 22, y + 7, 4); - // Large tactile ON / OFF badge (completely red-free: neutral slate for OFF, theme accent for ON) - uint16_t badgeCol = alarmEnabled[i] ? onCol : 0x2124; + // Large tactile ON / OFF badge + uint16_t badgeCol = alarmEnabled[i] ? itemCol : 0x2124; uint16_t txtCol = alarmEnabled[i] ? TFT_BLACK : 0xAD55; - uint16_t borderCol = alarmEnabled[i] ? TFT_WHITE : 0x4A69; + uint16_t badgeBorder = alarmEnabled[i] ? TFT_WHITE : 0x4A69; tft.fillRoundRect(220, y + 4, 84, 30, 5, badgeCol); - tft.drawRoundRect(220, y + 4, 84, 30, 5, borderCol); + tft.drawRoundRect(220, y + 4, 84, 30, 5, badgeBorder); tft.setTextColor(txtCol, badgeCol); tft.drawCentreString(alarmEnabled[i] ? "ON" : "OFF", 262, y + 7, 4); } @@ -281,8 +452,6 @@ void thresholdMenu() { int valX = 176; int valW = 80; int valH = 30; - tft.fillRoundRect(valX, y + 4, valW, valH, 4, 0x0821); - tft.drawRoundRect(valX, y + 4, valW, valH, 4, 0x2945); String dispVal = ""; if (i == 0) { @@ -295,21 +464,86 @@ void thresholdMenu() { dispVal = warningValue[3] + " km"; } - tft.setTextColor(getThemePrimaryColor(), 0x0821); - tft.setTextDatum(MC_DATUM); - tft.drawString(dispVal, valX + valW / 2, y + 19, 4); - tft.setTextDatum(TL_DATUM); // restore default + if (isRainbowTheme()) { + fillRainbowAlphaRoundRect(valX, y + 4, valW, valH, 4, 45); + drawRainbowRoundRect(valX, y + 4, valW, valH, 4, 180); + drawRainbowCentreString(dispVal, valX + valW / 2, y + 7, 4); + } else if (isYapaTheme()) { + tft.fillRoundRect(valX, y + 4, valW, valH, 4, 0x0821); + tft.drawRoundRect(valX, y + 4, valW, valH, 4, 0x3186); + drawYapaCentreString(dispVal, valX + valW / 2, y + 7, 4); + } else { + tft.fillRoundRect(valX, y + 4, valW, valH, 4, 0x0821); + tft.drawRoundRect(valX, y + 4, valW, valH, 4, 0x2945); + tft.setTextColor(getThemePrimaryColor(), 0x0821); + tft.setTextDatum(MC_DATUM); + tft.drawString(dispVal, valX + valW / 2, y + 19, 4); + tft.setTextDatum(TL_DATUM); // restore default + } }; auto drawAllThresholdRows = [&]() { + if (isRainbowTheme()) { + drawRainbowHLine(2, 2, 255); + const char* const thrNames[4] = { "Temp.", "RPM", "Voltaje", "Veloc." }; + for (uint8_t i = 0; i < 4; i++) { + int y = 10 + i * 44; + fillRainbowAlphaRoundRect(8, y, 304, 38, 5, 25); + drawRainbowRoundRect(8, y, 304, 38, 5, 120); + + drawRainbowString(thrNames[i], 18, y + 7, 4); + + // Decrement button [-] (x: 132, w: 38, h: 30) + fillRainbowAlphaRoundRect(132, y + 4, 38, 30, 4, 45); + drawRainbowRoundRect(132, y + 4, 38, 30, 4, 180); + drawRainbowCentreString("-", 151, y + 7, 4); + + // Value Box (x: 176, w: 80, h: 30) + drawRowValue(i); + + // Increment button [+] (x: 262, w: 38, h: 30) + fillRainbowAlphaRoundRect(262, y + 4, 38, 30, 4, 45); + drawRainbowRoundRect(262, y + 4, 38, 30, 4, 180); + drawRainbowCentreString("+", 281, y + 7, 4); + } + return; + } + if (isYapaTheme()) { + for (int16_t x = 0; x < 320; x++) { + tft.drawFastVLine(x, 2, 2, getYapaColor(x / 53)); + } + const char* const thrNames[4] = { "Temp.", "RPM", "Voltaje", "Veloc." }; + for (uint8_t i = 0; i < 4; i++) { + int y = 10 + i * 44; + tft.fillRoundRect(8, y, 304, 38, 5, 0x1082); + tft.drawRoundRect(8, y, 304, 38, 5, 0x3186); + + drawYapaString(thrNames[i], 18, y + 7, 4); + + // Decrement button [-] (x: 132, w: 38, h: 30) + tft.fillRoundRect(132, y + 4, 38, 30, 4, 0x2104); + tft.drawRoundRect(132, y + 4, 38, 30, 4, getYapaColor(i)); + drawYapaCentreString("-", 151, y + 7, 4); + + // Value Box (x: 176, w: 80, h: 30) + drawRowValue(i); + + // Increment button [+] (x: 262, w: 38, h: 30) + tft.fillRoundRect(262, y + 4, 38, 30, 4, 0x2104); + tft.drawRoundRect(262, y + 4, 38, 30, 4, getYapaColor(i + 1)); + drawYapaCentreString("+", 281, y + 7, 4); + } + return; + } uint16_t onCol = getThemePrimaryColor(); for (uint8_t i = 0; i < 4; i++) { int y = 10 + i * 44; + uint16_t itemCol = onCol; // Card background tft.fillRoundRect(8, y, 304, 38, 5, getThemeDarkColor()); tft.drawRoundRect(8, y, 304, 38, 5, getThemeAccentColor()); - tft.fillRoundRect(10, y + 4, 3, 30, 1, onCol); + tft.fillRoundRect(10, y + 4, 3, 30, 1, itemCol); // Parameter label const char* const thrNames[4] = { "Temp.", "RPM", "Voltaje", "Veloc." }; @@ -317,9 +551,10 @@ void thresholdMenu() { tft.drawString(thrNames[i], 18, y + 7, 4); // Decrement button [-] (x: 132, w: 38, h: 30) - tft.fillRoundRect(132, y + 4, 38, 30, 4, getThemeAccentColor()); + uint16_t btnBg = getThemeAccentColor(); + tft.fillRoundRect(132, y + 4, 38, 30, 4, btnBg); tft.drawRoundRect(132, y + 4, 38, 30, 4, onCol); - tft.setTextColor(TFT_WHITE, getThemeAccentColor()); + tft.setTextColor(TFT_WHITE, btnBg); tft.setTextDatum(MC_DATUM); tft.drawString("-", 132 + 19, y + 19, 4); @@ -327,9 +562,9 @@ void thresholdMenu() { drawRowValue(i); // Increment button [+] (x: 262, w: 38, h: 30) - tft.fillRoundRect(262, y + 4, 38, 30, 4, getThemeAccentColor()); + tft.fillRoundRect(262, y + 4, 38, 30, 4, btnBg); tft.drawRoundRect(262, y + 4, 38, 30, 4, onCol); - tft.setTextColor(TFT_WHITE, getThemeAccentColor()); + tft.setTextColor(TFT_WHITE, btnBg); tft.setTextDatum(MC_DATUM); tft.drawString("+", 262 + 19, y + 19, 4); tft.setTextDatum(TL_DATUM); // restore default @@ -413,6 +648,68 @@ void alarmCategoryMenu() { auto drawCategoryUI = [&](uint8_t sel) { tft.fillScreen(TFT_BLACK); + if (isRainbowTheme()) { + drawRainbowHLine(6, 2, 255); + + // Card 0: Activacion de Alarmas (y: 20 to 88, h: 68) + fillRainbowAlphaRoundRect(10, 20, 300, 68, 5, (sel == 0) ? 80 : 30); + drawRainbowRoundRect(10, 20, 300, 68, 5, (sel == 0) ? 255 : 120); + drawRainbowString("Seteo de Activacion", 24, 28, 4); + + uint8_t activeCount = 0; + for (uint8_t i = 0; i < 4; i++) { + if (alarmEnabled[i]) activeCount++; + } + String actInfo = String(activeCount) + " de 4 alarmas activas (ON/OFF)"; + drawRainbowString(actInfo, 24, 58, 2); + tft.fillTriangle(296, 54, 290, 48, 290, 60, getRainbowScreenColor(293)); + + // Card 1: Seteo de Umbrales (y: 102 to 170, h: 68) + fillRainbowAlphaRoundRect(10, 102, 300, 68, 5, (sel == 1) ? 80 : 30); + drawRainbowRoundRect(10, 102, 300, 68, 5, (sel == 1) ? 255 : 120); + drawRainbowString("Seteo de Umbrales", 24, 110, 4); + String thrInfo = "T: " + warningValue[0] + "`C | RPM: " + warningValue[1] + " | V: " + warningValue[2] + "V | Vel: " + warningValue[3] + "k/h"; + drawRainbowString(thrInfo, 24, 140, 2); + tft.fillTriangle(296, 136, 290, 130, 290, 142, getRainbowScreenColor(293)); + + drawExitButton(false); + return; + } + if (isYapaTheme()) { + for (int16_t x = 0; x < 320; x++) { + tft.drawFastVLine(x, 6, 2, getYapaColor(x / 53)); + } + + // Card 0: Activacion de Alarmas (y: 20 to 88, h: 68) + tft.fillRoundRect(10, 20, 300, 68, 5, (sel == 0) ? 0x2104 : 0x1082); + tft.drawRoundRect(10, 20, 300, 68, 5, (sel == 0) ? getYapaColor(0) : 0x3186); + if (sel == 0) { + tft.drawRoundRect(11, 21, 298, 66, 4, TFT_WHITE); + } + drawYapaString("Seteo de Activacion", 24, 28, 4); + + uint8_t activeCount = 0; + for (uint8_t i = 0; i < 4; i++) { + if (alarmEnabled[i]) activeCount++; + } + String actInfo = String(activeCount) + " de 4 alarmas activas (ON/OFF)"; + drawYapaString(actInfo, 24, 58, 2); + tft.fillTriangle(296, 54, 290, 48, 290, 60, (sel == 0) ? TFT_WHITE : getYapaColor(1)); + + // Card 1: Seteo de Umbrales (y: 102 to 170, h: 68) + tft.fillRoundRect(10, 102, 300, 68, 5, (sel == 1) ? 0x2104 : 0x1082); + tft.drawRoundRect(10, 102, 300, 68, 5, (sel == 1) ? getYapaColor(2) : 0x3186); + if (sel == 1) { + tft.drawRoundRect(11, 103, 298, 66, 4, TFT_WHITE); + } + drawYapaString("Seteo de Umbrales", 24, 110, 4); + String thrInfo = "T: " + warningValue[0] + "`C | RPM: " + warningValue[1] + " | V: " + warningValue[2] + "V | Vel: " + warningValue[3] + "k/h"; + drawYapaString(thrInfo, 24, 140, 2); + tft.fillTriangle(296, 136, 290, 130, 290, 142, (sel == 1) ? TFT_WHITE : getYapaColor(3)); + + drawExitButton(false); + return; + } // Card 0: Activacion de Alarmas (y: 20 to 88, h: 68) uint16_t bg0 = (sel == 0) ? getThemeAccentColor() : getThemeDarkColor(); @@ -595,6 +892,66 @@ void alarmSoundMenu() { auto drawAlarmUI = []() { tft.fillRect(0, 0, 320, 186, TFT_BLACK); + if (isRainbowTheme()) { + drawRainbowHLine(2, 2, 255); + // 6 Pattern Tiles (3 rows x 2 columns) from y = 8 to y = 120 + for (uint8_t i = 0; i < 6; i++) { + int16_t x = (i % 2 == 0) ? 10 : 166; + int16_t y = 8 + (i / 2) * 38; + int16_t w = 144; + int16_t h = 34; + bool isSel = (i == alarmSoundType); + fillRainbowAlphaRoundRect(x, y, w, h, 5, isSel ? 90 : 30); + drawRainbowRoundRect(x, y, w, h, 5, isSel ? 255 : 100); + drawRainbowCentreString(ALARM_NAME_TABLE[i], x + w / 2, y + 9, 2); + } + + // 6 Direct Touch Tiles (0 to 5) for Alarm Volume from y = 126 to y = 180 + for (uint8_t i = 0; i <= 5; i++) { + int16_t bx = 7 + i * 52; + int16_t by = 126; + int16_t bw = 46; + int16_t bh = 54; + bool isSel = (i == alarmVolumeLevel); + fillRainbowAlphaRoundRect(bx, by, bw, bh, 5, isSel ? 90 : 30); + drawRainbowRoundRect(bx, by, bw, bh, 5, isSel ? 255 : 100); + drawRainbowCentreString(String(i).c_str(), bx + bw / 2, by + 8, 6); + } + return; + } + if (isYapaTheme()) { + for (int16_t x = 0; x < 320; x++) { + tft.drawFastVLine(x, 2, 2, getYapaColor(x / 53)); + } + for (uint8_t i = 0; i < 6; i++) { + int16_t x = (i % 2 == 0) ? 10 : 166; + int16_t y = 8 + (i / 2) * 38; + int16_t w = 144; + int16_t h = 34; + bool isSel = (i == alarmSoundType); + tft.fillRoundRect(x, y, w, h, 5, isSel ? 0x2104 : 0x1082); + tft.drawRoundRect(x, y, w, h, 5, isSel ? getYapaColor(i) : 0x3186); + if (isSel) { + tft.drawRoundRect(x + 1, y + 1, w - 2, h - 2, 4, TFT_WHITE); + } + drawYapaCentreString(ALARM_NAME_TABLE[i], x + w / 2, y + 9, 2); + } + + for (uint8_t i = 0; i <= 5; i++) { + int16_t bx = 7 + i * 52; + int16_t by = 126; + int16_t bw = 46; + int16_t bh = 54; + bool isSel = (i == alarmVolumeLevel); + tft.fillRoundRect(bx, by, bw, bh, 5, isSel ? 0x2104 : 0x1082); + tft.drawRoundRect(bx, by, bw, bh, 5, isSel ? getYapaColor(i) : 0x3186); + if (isSel) { + tft.drawRoundRect(bx + 1, by + 1, bw - 2, bh - 2, 4, TFT_WHITE); + } + drawYapaCentreString(String(i), bx + bw / 2, by + 8, 6); + } + return; + } // 6 Pattern Tiles (3 rows x 2 columns) from y = 8 to y = 120 for (uint8_t i = 0; i < 6; i++) { @@ -697,6 +1054,66 @@ void beepSoundMenu() { auto drawBeepUI = []() { tft.fillRect(0, 0, 320, 186, TFT_BLACK); + if (isRainbowTheme()) { + drawRainbowHLine(2, 2, 255); + // 6 Pattern Tiles (3 rows x 2 columns) from y = 8 to y = 120 + for (uint8_t i = 0; i < 6; i++) { + int16_t x = (i % 2 == 0) ? 10 : 166; + int16_t y = 8 + (i / 2) * 38; + int16_t w = 144; + int16_t h = 34; + bool isSel = (i == beepSoundType); + fillRainbowAlphaRoundRect(x, y, w, h, 5, isSel ? 90 : 30); + drawRainbowRoundRect(x, y, w, h, 5, isSel ? 255 : 100); + drawRainbowCentreString(BEEP_NAME_TABLE[i], x + w / 2, y + 9, 2); + } + + // 6 Direct Touch Tiles (0 to 5) for Beep Volume from y = 126 to y = 180 + for (uint8_t i = 0; i <= 5; i++) { + int16_t bx = 7 + i * 52; + int16_t by = 126; + int16_t bw = 46; + int16_t bh = 54; + bool isSel = (i == buzzerVolumeLevel); + fillRainbowAlphaRoundRect(bx, by, bw, bh, 5, isSel ? 90 : 30); + drawRainbowRoundRect(bx, by, bw, bh, 5, isSel ? 255 : 100); + drawRainbowCentreString(String(i).c_str(), bx + bw / 2, by + 8, 6); + } + return; + } + if (isYapaTheme()) { + for (int16_t x = 0; x < 320; x++) { + tft.drawFastVLine(x, 2, 2, getYapaColor(x / 53)); + } + for (uint8_t i = 0; i < 6; i++) { + int16_t x = (i % 2 == 0) ? 10 : 166; + int16_t y = 8 + (i / 2) * 38; + int16_t w = 144; + int16_t h = 34; + bool isSel = (i == beepSoundType); + tft.fillRoundRect(x, y, w, h, 5, isSel ? 0x2104 : 0x1082); + tft.drawRoundRect(x, y, w, h, 5, isSel ? getYapaColor(i) : 0x3186); + if (isSel) { + tft.drawRoundRect(x + 1, y + 1, w - 2, h - 2, 4, TFT_WHITE); + } + drawYapaCentreString(BEEP_NAME_TABLE[i], x + w / 2, y + 9, 2); + } + + for (uint8_t i = 0; i <= 5; i++) { + int16_t bx = 7 + i * 52; + int16_t by = 126; + int16_t bw = 46; + int16_t bh = 54; + bool isSel = (i == buzzerVolumeLevel); + tft.fillRoundRect(bx, by, bw, bh, 5, isSel ? 0x2104 : 0x1082); + tft.drawRoundRect(bx, by, bw, bh, 5, isSel ? getYapaColor(i) : 0x3186); + if (isSel) { + tft.drawRoundRect(bx + 1, by + 1, bw - 2, bh - 2, 4, TFT_WHITE); + } + drawYapaCentreString(String(i), bx + bw / 2, by + 8, 6); + } + return; + } // 6 Pattern Tiles (3 rows x 2 columns) from y = 8 to y = 120 for (uint8_t i = 0; i < 6; i++) { @@ -930,10 +1347,7 @@ void enterLightSleep() { } void drawSleepHeader() { - tft.fillRectVGradient(0, 0, 320, 26, getThemeAccentColor(), getThemeDarkColor()); - tft.fillRect(0, 26, 320, 2, getThemePrimaryColor()); - tft.setTextColor(TFT_WHITE); - tft.drawCentreString("Modo Dormir", 160, 2, 4); + drawMenuHeaderBanner("Modo Dormir"); } void sleepMenu() { @@ -949,9 +1363,34 @@ void sleepMenu() { int16_t bh = 48; const char* label = (idx == 0) ? "DORMIR" : "SALIR"; - uint16_t bgCol = isPressed ? getThemePrimaryColor() : getThemeAccentColor(); + if (isRainbowTheme()) { + uint8_t bgAlpha = isPressed ? 110 : (isSel ? 70 : 35); + uint8_t borderAlpha = (isPressed || isSel) ? 255 : 120; + fillRainbowAlphaRoundRect(bx, by, bw, bh, 6, bgAlpha); + drawRainbowRoundRect(bx, by, bw, bh, 6, borderAlpha); + if (isPressed || isSel) { + drawRainbowRoundRect(bx + 1, by + 1, bw - 2, bh - 2, 5, 255); + } + drawRainbowCentreString(label, bx + bw / 2, by + 12, 4); + return; + } + if (isYapaTheme()) { + uint16_t bgCol = isPressed ? 0x2104 : 0x1082; + uint16_t borderCol = (isPressed || isSel) ? getYapaColor(idx) : 0x3186; + tft.fillRoundRect(bx, by, bw, bh, 6, bgCol); + tft.drawRoundRect(bx, by, bw, bh, 6, borderCol); + if (isPressed || isSel) { + tft.drawRoundRect(bx + 1, by + 1, bw - 2, bh - 2, 5, TFT_WHITE); + } + drawYapaCentreString(label, bx + bw / 2, by + 12, 4); + return; + } + + uint16_t pri = getThemePrimaryColor(); + uint16_t acc = getThemeAccentColor(); + uint16_t bgCol = isPressed ? pri : acc; uint16_t txtCol = isPressed ? TFT_BLACK : TFT_WHITE; - uint16_t borderCol = (isPressed || isSel) ? getThemePrimaryColor() : getThemeDarkColor(); + uint16_t borderCol = (isPressed || isSel) ? pri : getThemeDarkColor(); tft.fillRoundRect(bx, by, bw, bh, 6, bgCol); tft.drawRoundRect(bx, by, bw, bh, 6, borderCol); @@ -970,6 +1409,45 @@ void sleepMenu() { auto drawSleepUI = [&]() { tft.fillRect(0, 28, 320, 134, TFT_BLACK); + if (isRainbowTheme()) { + drawRainbowCentreString("Auto-Sleep por Inactividad", 160, 38, 4); + drawRainbowCentreString("(Apagado al detenerse el motor)", 160, 68, 2); + + // 2. 5 Direct Touch Tiles for Auto-Sleep Timeout: [OFF] [30s] [1m] [2m] [5m] + for (uint8_t i = 0; i < 5; i++) { + int16_t bx = 8 + i * 62; + int16_t by = 94; + int16_t bw = 56; + int16_t bh = 54; + bool isSel = (i == autoSleepIndex); + + fillRainbowAlphaRoundRect(bx, by, bw, bh, 5, isSel ? 90 : 30); + drawRainbowRoundRect(bx, by, bw, bh, 5, isSel ? 255 : 100); + drawRainbowCentreString(AUTO_SLEEP_NAME_TABLE[i], bx + bw / 2, by + 14, 4); + } + return; + } + if (isYapaTheme()) { + drawYapaCentreString("Auto-Sleep por Inactividad", 160, 38, 4); + drawYapaCentreString("(Apagado al detenerse el motor)", 160, 68, 2); + + for (uint8_t i = 0; i < 5; i++) { + int16_t bx = 8 + i * 62; + int16_t by = 94; + int16_t bw = 56; + int16_t bh = 54; + bool isSel = (i == autoSleepIndex); + + tft.fillRoundRect(bx, by, bw, bh, 5, isSel ? 0x2104 : 0x1082); + tft.drawRoundRect(bx, by, bw, bh, 5, isSel ? getYapaColor(i) : 0x3186); + if (isSel) { + tft.drawRoundRect(bx + 1, by + 1, bw - 2, bh - 2, 4, TFT_WHITE); + } + drawYapaCentreString(AUTO_SLEEP_NAME_TABLE[i], bx + bw / 2, by + 14, 4); + } + return; + } + // 1. Inactivity Sleep Section Label tft.setTextColor(TFT_WHITE, TFT_BLACK); tft.drawCentreString("Auto-Sleep por Inactividad", 160, 38, 4); @@ -1095,18 +1573,23 @@ void sleepMenu() { // ------------------------------------------------------------- void restartConfirmationMenu() { tft.fillScreen(TFT_BLACK); - tft.fillRectVGradient(0, 0, 320, 26, getThemeAccentColor(), getThemeDarkColor()); - tft.fillRect(0, 26, 320, 2, getThemePrimaryColor()); - tft.setTextColor(TFT_WHITE); - tft.drawCentreString("Reiniciar Dispositivo", 160, 2, 4); + drawMenuHeaderBanner("Reiniciar Dispositivo"); - // Themed confirmation dialog card - tft.fillRoundRect(14, 44, 292, 104, 6, getThemeDarkColor()); - tft.drawRoundRect(14, 44, 292, 104, 6, getThemeAccentColor()); - tft.fillRoundRect(18, 50, 4, 92, 2, getThemePrimaryColor()); // Left accent pill - - tft.setTextColor(TFT_WHITE, getThemeDarkColor()); - tft.drawCentreString("Desea reiniciar la placa?", 160, 80, 4); + if (isRainbowTheme()) { + fillRainbowAlphaRoundRect(14, 44, 292, 104, 6, 35); + drawRainbowRoundRect(14, 44, 292, 104, 6, 150); + drawRainbowCentreString("Desea reiniciar la placa?", 160, 80, 4); + } else if (isYapaTheme()) { + tft.fillRoundRect(14, 44, 292, 104, 6, 0x1082); + tft.drawRoundRect(14, 44, 292, 104, 6, 0xFD79); + drawYapaCentreString("Desea reiniciar la placa?", 160, 80, 4); + } else { + tft.fillRoundRect(14, 44, 292, 104, 6, getThemeDarkColor()); + tft.drawRoundRect(14, 44, 292, 104, 6, getThemeAccentColor()); + tft.fillRoundRect(18, 50, 4, 92, 2, getThemePrimaryColor()); + tft.setTextColor(TFT_WHITE, getThemeDarkColor()); + tft.drawCentreString("Desea reiniciar la placa?", 160, 80, 4); + } uint8_t subSel = 1; // 0: REINICIAR, 1: SALIR (por defecto SALIR para seguridad) @@ -1117,9 +1600,34 @@ void restartConfirmationMenu() { int16_t bh = 48; const char* label = (idx == 0) ? "REINICIAR" : "SALIR"; - uint16_t bgCol = isPressed ? getThemePrimaryColor() : getThemeAccentColor(); + if (isRainbowTheme()) { + uint8_t bgAlpha = isPressed ? 110 : (isSel ? 70 : 35); + uint8_t borderAlpha = (isPressed || isSel) ? 255 : 120; + fillRainbowAlphaRoundRect(bx, by, bw, bh, 6, bgAlpha); + drawRainbowRoundRect(bx, by, bw, bh, 6, borderAlpha); + if (isPressed || isSel) { + drawRainbowRoundRect(bx + 1, by + 1, bw - 2, bh - 2, 5, 255); + } + drawRainbowCentreString(label, bx + bw / 2, by + 12, 4); + return; + } + if (isYapaTheme()) { + uint16_t bgCol = isPressed ? 0x2104 : 0x1082; + uint16_t borderCol = (isPressed || isSel) ? getYapaColor(idx) : 0x3186; + tft.fillRoundRect(bx, by, bw, bh, 6, bgCol); + tft.drawRoundRect(bx, by, bw, bh, 6, borderCol); + if (isPressed || isSel) { + tft.drawRoundRect(bx + 1, by + 1, bw - 2, bh - 2, 5, TFT_WHITE); + } + drawYapaCentreString(label, bx + bw / 2, by + 12, 4); + return; + } + + uint16_t pri = getThemePrimaryColor(); + uint16_t acc = getThemeAccentColor(); + uint16_t bgCol = isPressed ? pri : acc; uint16_t txtCol = isPressed ? TFT_BLACK : TFT_WHITE; - uint16_t borderCol = (isPressed || isSel) ? getThemePrimaryColor() : getThemeDarkColor(); + uint16_t borderCol = (isPressed || isSel) ? pri : getThemeDarkColor(); tft.fillRoundRect(bx, by, bw, bh, 6, bgCol); tft.drawRoundRect(bx, by, bw, bh, 6, borderCol); @@ -1229,6 +1737,113 @@ void brightnessMenu() { auto drawBrightnessUI = [&]() { tft.fillRect(0, 0, 320, 180, TFT_BLACK); + if (isRainbowTheme()) { + drawRainbowHLine(4, 2, 255); + + // 1. Status Card (y: 16 to 98, h: 82) + fillRainbowAlphaRoundRect(10, 16, 300, 82, 5, 30); + drawRainbowRoundRect(10, 16, 300, 82, 5, 120); + + if (brightnessIndex == 0) { + drawRainbowString("Modo: AUTOMATICO", 24, 24, 4); + int lightVal = analogRead(LDR_PIN); + String ldrText = String("Sensor LDR: ") + String(lightVal) + (lightVal > LIGHT_LEVEL ? " -> Noche (15)" : " -> Dia (255)"); + drawRainbowString(ldrText, 24, 50, 2); + } else { + String title = String("Modo: MANUAL (") + BRIGHTNESS_NAME_TABLE[brightnessIndex] + ")"; + drawRainbowString(title, 24, 24, 4); + String sub = String("Nivel ") + String(brightnessIndex) + " de 5 fijo"; + drawRainbowString(sub, 24, 50, 2); + } + + // Visual Progress Bar (y = 72, x = 20, w = 280, h = 16) + fillRainbowAlphaRoundRect(20, 72, 280, 16, 4, 30); + drawRainbowRoundRect(20, 72, 280, 16, 4, 120); + + int fillW = 0; + if (brightnessIndex == 0) { + fillW = map((int)currentBacklight, 0, 255, 0, 276); + } else { + fillW = (brightnessIndex * 276) / 5; + } + if (fillW > 4) { + for (int bx = 0; bx < fillW; bx++) { + tft.drawFastVLine(22 + bx, 74, 12, getRainbowScreenColor(22 + bx, 74)); + } + } + + // 2. 6 Direct Touch Tiles (y: 112 to 170, h: 58) + for (uint8_t i = 0; i < 6; i++) { + int16_t bx = 7 + i * 52; + int16_t by = 112; + int16_t bw = 46; + int16_t bh = 58; + bool isSel = (i == brightnessIndex); + fillRainbowAlphaRoundRect(bx, by, bw, bh, 5, isSel ? 90 : 30); + drawRainbowRoundRect(bx, by, bw, bh, 5, isSel ? 255 : 100); + if (i == 0) { + drawRainbowCentreString("AUTO", bx + bw / 2, by + 20, 2); + } else { + drawRainbowCentreString(String(i).c_str(), bx + bw / 2, by + 10, 6); + } + } + return; + } + if (isYapaTheme()) { + for (int16_t x = 0; x < 320; x++) { + tft.drawFastVLine(x, 4, 2, getYapaColor(x / 53)); + } + tft.fillRoundRect(10, 16, 300, 82, 5, 0x1082); + tft.drawRoundRect(10, 16, 300, 82, 5, 0x3186); + + if (brightnessIndex == 0) { + drawYapaString("Modo: AUTOMATICO", 24, 24, 4); + int lightVal = analogRead(LDR_PIN); + String ldrText = String("Sensor LDR: ") + String(lightVal) + (lightVal > LIGHT_LEVEL ? " -> Noche (15)" : " -> Dia (255)"); + drawYapaString(ldrText, 24, 50, 2); + } else { + String title = String("Modo: MANUAL (") + BRIGHTNESS_NAME_TABLE[brightnessIndex] + ")"; + drawYapaString(title, 24, 24, 4); + String sub = String("Nivel ") + String(brightnessIndex) + " de 5 fijo"; + drawYapaString(sub, 24, 50, 2); + } + + // Visual Progress Bar (y = 72, x = 20, w = 280, h = 16) + tft.fillRoundRect(20, 72, 280, 16, 4, 0x0821); + tft.drawRoundRect(20, 72, 280, 16, 4, 0x3186); + + int fillW = 0; + if (brightnessIndex == 0) { + fillW = map((int)currentBacklight, 0, 255, 0, 276); + } else { + fillW = (brightnessIndex * 276) / 5; + } + if (fillW > 4) { + for (int bx = 0; bx < fillW; bx++) { + tft.drawFastVLine(22 + bx, 74, 12, getYapaColor(bx / 46)); + } + } + + // 2. 6 Direct Touch Tiles (y: 112 to 170, h: 58) + for (uint8_t i = 0; i < 6; i++) { + int16_t bx = 7 + i * 52; + int16_t by = 112; + int16_t bw = 46; + int16_t bh = 58; + bool isSel = (i == brightnessIndex); + tft.fillRoundRect(bx, by, bw, bh, 5, isSel ? 0x2104 : 0x1082); + tft.drawRoundRect(bx, by, bw, bh, 5, isSel ? getYapaColor(i) : 0x3186); + if (isSel) { + tft.drawRoundRect(bx + 1, by + 1, bw - 2, bh - 2, 4, TFT_WHITE); + } + if (i == 0) { + drawYapaCentreString("AUTO", bx + bw / 2, by + 20, 2); + } else { + drawYapaCentreString(String(i).c_str(), bx + bw / 2, by + 10, 6); + } + } + return; + } // 1. Status Card (y: 16 to 98, h: 82) tft.fillRoundRect(10, 16, 300, 82, 5, getThemeDarkColor()); @@ -1240,7 +1855,7 @@ void brightnessMenu() { tft.drawString("Modo: AUTOMATICO", 24, 24, 4); tft.setTextColor(0xBDD7, getThemeDarkColor()); int lightVal = analogRead(LDR_PIN); - String ldrText = String("Sensor LDR: ") + (lightVal > LIGHT_LEVEL ? "Noche (Dim 15)" : "Dia (255)"); + String ldrText = String("Sensor LDR: ") + String(lightVal) + (lightVal > LIGHT_LEVEL ? " -> Noche (15)" : " -> Dia (255)"); tft.drawString(ldrText, 24, 50, 2); } else { String title = String("Modo: MANUAL (") + BRIGHTNESS_NAME_TABLE[brightnessIndex] + ")"; @@ -1295,9 +1910,19 @@ void brightnessMenu() { drawExitButton(false); waitForTouchRelease(); + uint32_t lastLdrRefresh = 0; + bool lastDimState = dim; + while (true) { if (checkMenuTimeout()) break; autoDim(); + if (brightnessIndex == 0) { + if (dim != lastDimState || millis() - lastLdrRefresh > 500) { + lastDimState = dim; + lastLdrRefresh = millis(); + drawBrightnessUI(); + } + } uint16_t tx = 0, ty = 0; if (getTouch(&tx, &ty)) { @@ -1361,6 +1986,58 @@ void soundsMenu() { auto drawSoundsUI = [&](uint8_t sel) { tft.fillScreen(TFT_BLACK); + if (isRainbowTheme()) { + drawRainbowHLine(6, 2, 255); + + // Card 0: Sonido de Alarma (y: 20 to 88, h: 68) + fillRainbowAlphaRoundRect(10, 20, 300, 68, 5, (sel == 0) ? 80 : 30); + drawRainbowRoundRect(10, 20, 300, 68, 5, (sel == 0) ? 255 : 120); + drawRainbowString("Sonido de Alarma", 24, 28, 4); + String almInfo = String(ALARM_NAME_TABLE[alarmSoundType > 5 ? 0 : alarmSoundType]) + " | " + + (alarmVolumeLevel == 0 ? "Silencio" : ("Vol: " + String(alarmVolumeLevel))); + drawRainbowString(almInfo, 24, 58, 2); + + // Card 1: Sonido de Click (y: 102 to 170, h: 68) + fillRainbowAlphaRoundRect(10, 102, 300, 68, 5, (sel == 1) ? 80 : 30); + drawRainbowRoundRect(10, 102, 300, 68, 5, (sel == 1) ? 255 : 120); + drawRainbowString("Sonido de Click", 24, 110, 4); + String beepInfo = String(BEEP_NAME_TABLE[beepSoundType > 5 ? 0 : beepSoundType]) + " | " + + (buzzerVolumeLevel == 0 ? "Silencio" : ("Vol: " + String(buzzerVolumeLevel))); + drawRainbowString(beepInfo, 24, 140, 2); + + drawExitButton(false); + return; + } + if (isYapaTheme()) { + for (int16_t x = 0; x < 320; x++) { + tft.drawFastVLine(x, 6, 2, getYapaColor(x / 53)); + } + + // Card 0: Sonido de Alarma (y: 20 to 88, h: 68) + tft.fillRoundRect(10, 20, 300, 68, 5, (sel == 0) ? 0x2104 : 0x1082); + tft.drawRoundRect(10, 20, 300, 68, 5, (sel == 0) ? getYapaColor(0) : 0x3186); + if (sel == 0) { + tft.drawRoundRect(11, 21, 298, 66, 4, TFT_WHITE); + } + drawYapaString("Sonido de Alarma", 24, 28, 4); + String almInfo = String(ALARM_NAME_TABLE[alarmSoundType > 5 ? 0 : alarmSoundType]) + " | " + + (alarmVolumeLevel == 0 ? "Silencio" : ("Vol: " + String(alarmVolumeLevel))); + drawYapaString(almInfo, 24, 58, 2); + + // Card 1: Sonido de Click (y: 102 to 170, h: 68) + tft.fillRoundRect(10, 102, 300, 68, 5, (sel == 1) ? 0x2104 : 0x1082); + tft.drawRoundRect(10, 102, 300, 68, 5, (sel == 1) ? getYapaColor(2) : 0x3186); + if (sel == 1) { + tft.drawRoundRect(11, 103, 298, 66, 4, TFT_WHITE); + } + drawYapaString("Sonido de Click", 24, 110, 4); + String beepInfo = String(BEEP_NAME_TABLE[beepSoundType > 5 ? 0 : beepSoundType]) + " | " + + (buzzerVolumeLevel == 0 ? "Silencio" : ("Vol: " + String(buzzerVolumeLevel))); + drawYapaString(beepInfo, 24, 140, 2); + + drawExitButton(false); + return; + } // Card 0: Sonido de Alarma (y: 20 to 88, h: 68) uint16_t bg0 = (sel == 0) ? getThemeAccentColor() : getThemeDarkColor(); @@ -1483,20 +2160,17 @@ void soundsMenu() { } // ------------------------------------------------------------- -// Menu de Apariencia / Temas +// Menu de Apariencia / Temas (Grid 3x3 de 9 Temas) // ------------------------------------------------------------- void drawThemeHeader() { - tft.fillRectVGradient(0, 0, 320, 26, getThemeAccentColor(), getThemeDarkColor()); - tft.fillRect(0, 26, 320, 2, getThemePrimaryColor()); - tft.setTextColor(TFT_WHITE); - tft.drawCentreString("Temas de Color", 160, 2, 4); + drawMenuHeaderBanner("Temas de Color"); } void themeMenu() { tft.fillScreen(TFT_BLACK); - const char* const THEME_SHORT_NAMES[6] = { - "Cian", "Verde", "Arcoiris", "Blanco", "Naranja", "Violeta" + const char* const THEME_SHORT_NAMES[9] = { + "Cian", "Verde", "Arcoiris", "Blanco", "Naranja", "Violeta", "Rojo", "Ambar", "Yapa" }; auto drawThemeUI = [&]() { @@ -1506,74 +2180,45 @@ void themeMenu() { // Sincronizar LED RGB trasero con el tema seleccionado en tiempo real updateRearLedTheme(); - // 1. Live Menu UI & Divider Preview Card (y: 33 to 96, h: 64, x: 10, w: 300) - tft.fillRoundRect(10, 33, 300, 64, 5, 0x1082); - tft.drawRoundRect(10, 33, 300, 64, 5, GAUGE_THEMES[themeIndex].accentColor); - tft.drawRoundRect(11, 34, 298, 62, 4, 0x2104); - - // Left side: Theme Name and Description - tft.setTextColor(GAUGE_THEMES[themeIndex].primaryColor, 0x1082); - tft.drawString(GAUGE_THEMES[themeIndex].name, 20, 40, 4); - if (themeIndex == 2) { - const uint16_t rainbowMini[6] = {0x03FF, 0x07FF, 0x07E0, 0xFFE0, 0xFD20, 0xF800}; - for (uint8_t b = 0; b < 6; b++) { - tft.fillRect(20 + b * 17, 65, 15, 2, rainbowMini[b]); - } - } - tft.setTextColor(0xAD55, 0x1082); - tft.drawString(GAUGE_THEMES[themeIndex].desc, 20, 69, 2); - - // Right side: Simulated Mini Vignette & Menu Preview - // Mini Instrument Vignette + Crosshair Line (x: 168, y: 39, w: 126, h: 18) - tft.fillRectVGradient(168, 39, 126, 12, GAUGE_THEMES[themeIndex].bgColor, TFT_BLACK); - tft.fillRect(168, 51, 126, 6, TFT_BLACK); - tft.fillRect(220, 39, 2, 18, GAUGE_THEMES[themeIndex].divColor); // Sample Crosshair line - if (themeIndex == 2) { - const uint16_t rainbowMini[6] = {0x03FF, 0x07FF, 0x07E0, 0xFFE0, 0xFD20, 0xF800}; - for (uint8_t b = 0; b < 6; b++) { - tft.fillRoundRect(172 + b * 7, 43, 5, 10, 1, rainbowMini[b]); - } - } else { - tft.fillCircle(188, 48, 3, TFT_GREEN); // Sample green gauge block - } - tft.setTextColor(0xBDD7, TFT_BLACK); - tft.drawRightString("VIGNETTE", 290, 42, 1); - - // Mini Selected Card (x: 168, y: 62, w: 72, h: 28) - tft.fillRoundRect(168, 62, 72, 28, 4, GAUGE_THEMES[themeIndex].accentColor); - tft.drawRoundRect(168, 62, 72, 28, 4, GAUGE_THEMES[themeIndex].primaryColor); - tft.setTextColor(TFT_WHITE, GAUGE_THEMES[themeIndex].accentColor); - tft.drawCentreString("MENU", 204, 71, 2); - - // Mini Salir Button (x: 246, y: 62, w: 48, h: 28) - tft.fillRoundRect(246, 62, 48, 28, 4, GAUGE_THEMES[themeIndex].accentColor); - tft.drawRoundRect(246, 62, 48, 28, 4, GAUGE_THEMES[themeIndex].darkColor); - tft.setTextColor(TFT_WHITE, GAUGE_THEMES[themeIndex].accentColor); - tft.drawCentreString("SALIR", 270, 71, 2); - - // 2. 6 Theme Selection Tiles (y: 103 to 175, 2 rows of 3) - for (uint8_t i = 0; i < 6; i++) { + // 9 Theme Selection Tiles (y: 36 to 174, 3 rows of 3 columns, h: 42, w: 94) + for (uint8_t i = 0; i < 9; i++) { uint8_t row = i / 3; uint8_t col = i % 3; - int16_t tx = 10 + col * 102; - int16_t ty = 103 + row * 38; - int16_t tw = 96; - int16_t th = 34; + int16_t tx = 10 + col * 103; + int16_t ty = 36 + row * 48; + int16_t tw = 94; + int16_t th = 42; bool isSel = (i == themeIndex); - uint16_t bg = isSel ? GAUGE_THEMES[i].darkColor : 0x1082; - tft.fillRoundRect(tx, ty, tw, th, 5, bg); - - if (isSel) { - tft.drawRoundRect(tx, ty, tw, th, 5, GAUGE_THEMES[i].primaryColor); - tft.drawRoundRect(tx + 1, ty + 1, tw - 2, th - 2, 4, TFT_WHITE); + if (isRainbowTheme()) { + fillRainbowAlphaRoundRect(tx, ty, tw, th, 5, isSel ? 85 : 30); + drawRainbowRoundRect(tx, ty, tw, th, 5, isSel ? 255 : 100); + if (isSel) { + tft.drawRoundRect(tx + 1, ty + 1, tw - 2, th - 2, 4, TFT_WHITE); + } + } else if (isYapaTheme()) { + uint16_t bg = isSel ? 0x2104 : 0x1082; + tft.fillRoundRect(tx, ty, tw, th, 5, bg); + if (isSel) { + tft.drawRoundRect(tx, ty, tw, th, 5, getYapaColor(i)); + tft.drawRoundRect(tx + 1, ty + 1, tw - 2, th - 2, 4, TFT_WHITE); + } else { + tft.drawRoundRect(tx, ty, tw, th, 5, 0x2104); + } } else { - tft.drawRoundRect(tx, ty, tw, th, 5, 0x2104); + uint16_t bg = isSel ? GAUGE_THEMES[i].darkColor : 0x1082; + tft.fillRoundRect(tx, ty, tw, th, 5, bg); + if (isSel) { + tft.drawRoundRect(tx, ty, tw, th, 5, GAUGE_THEMES[i].primaryColor); + tft.drawRoundRect(tx + 1, ty + 1, tw - 2, th - 2, 4, TFT_WHITE); + } else { + tft.drawRoundRect(tx, ty, tw, th, 5, 0x2104); + } } // Circular color swatch with subtle halo int16_t cx = tx + 16; - int16_t cy = ty + 17; + int16_t cy = ty + 21; tft.drawCircle(cx, cy, 7, isSel ? TFT_WHITE : 0x4208); if (i == 2) { // Rainbow theme: color-wheel swatch (hue by angle) @@ -1585,16 +2230,29 @@ void themeMenu() { tft.drawPixel(cx + dx, cy + dy, hueToRgb565((uint16_t)ang)); } } + } else if (i == 8) { + // Yapa: 4 cute pastel candy dots + tft.fillCircle(cx - 3, cy - 3, 2, 0xFD79); // Rosa + tft.fillCircle(cx + 3, cy - 3, 2, 0xFFF2); // Limon + tft.fillCircle(cx - 3, cy + 3, 2, 0xA7D7); // Menta + tft.fillCircle(cx + 3, cy + 3, 2, 0x971F); // Celeste } else { tft.fillCircle(cx, cy, 5, GAUGE_THEMES[i].primaryColor); } // Label - tft.setTextColor(isSel ? TFT_WHITE : 0xBDD7, bg); - tft.drawString(THEME_SHORT_NAMES[i], tx + 30, ty + 9, 2); + if (i == 8 || isYapaTheme()) { + drawYapaString(THEME_SHORT_NAMES[i], tx + 28, ty + 13, 2); + } else if (isRainbowTheme()) { + drawRainbowString(THEME_SHORT_NAMES[i], tx + 28, ty + 13, 2); + } else { + uint16_t bg = isSel ? GAUGE_THEMES[i].darkColor : 0x1082; + tft.setTextColor(isSel ? TFT_WHITE : 0xBDD7, bg); + tft.drawString(THEME_SHORT_NAMES[i], tx + 28, ty + 13, 2); + } } - // 3. Redraw Exit button in newly selected theme colors + // Redraw Exit button in newly selected theme colors drawExitButton(false); }; @@ -1611,14 +2269,16 @@ void themeMenu() { if (getTouch(&tx, &ty)) { resetMenuActivity(); - // 1. Touch on theme tiles (y: 100 to 180) - if (ty >= 100 && ty < 182) { - uint8_t col = (tx < 110) ? 0 : ((tx < 212) ? 1 : 2); - uint8_t row = (ty < 140) ? 0 : 1; + // 1. Touch on 9 theme tiles (y: 34 to 178) + if (ty >= 34 && ty < 178) { + uint8_t col = (tx < 110) ? 0 : ((tx < 214) ? 1 : 2); + uint8_t row = (ty < 82) ? 0 : ((ty < 130) ? 1 : 2); uint8_t touchedIdx = row * 3 + col; - if (touchedIdx < 6) { + if (touchedIdx < 9) { if (touchedIdx != themeIndex) { themeIndex = touchedIdx; + invalidateTriTempTitles(); + invalidateGpsClockTitles(); pref.begin("setting", false); pref.putUChar("theme_idx", themeIndex); pref.end(); @@ -1628,8 +2288,8 @@ void themeMenu() { } waitForTouchRelease(); } - // 2. Touch on [ SALIR ] button (y: 178 to 240) - else if (ty >= 178 && tx >= 10 && tx <= 310) { + // 2. Touch on [ SALIR ] button (y: 180 to 240) + else if (ty >= 180 && tx >= 10 && tx <= 310) { animateExitButtonPress(); break; } @@ -1646,7 +2306,9 @@ void themeMenu() { if (pressed) { resetMenuActivity(); if (millis() - holdtimer >= 40 && millis() - holdtimer <= 1000) { - themeIndex = (themeIndex + 1) % 6; + themeIndex = (themeIndex + 1) % 9; + invalidateTriTempTitles(); + invalidateGpsClockTitles(); pref.begin("setting", false); pref.putUChar("theme_idx", themeIndex); pref.end(); @@ -1665,10 +2327,7 @@ void themeMenu() { // Sub-screen 4: Menu de Transiciones (Deslizamiento / Desvanecimiento) // ------------------------------------------------------------- void drawTransitionHeader() { - tft.fillRectVGradient(0, 0, 320, 26, getThemeAccentColor(), getThemeDarkColor()); - tft.fillRect(0, 26, 320, 2, getThemePrimaryColor()); - tft.setTextColor(TFT_WHITE); - tft.drawCentreString("Efecto de Transicion", 160, 2, 4); + drawMenuHeaderBanner("Efecto de Transicion"); } void transitionMenu() { @@ -1677,8 +2336,8 @@ void transitionMenu() { auto drawTransitionUI = [&]() { drawTransitionHeader(); - const char* titles[2] = { "Deslizamiento", "Desvanecimiento" }; - const char* descs[2] = { "Barrido lateral continuo", "Mezcla gradual de pantallas" }; + const char* titles[2] = { "Fundido a Negro", "Desvanecimiento" }; + const char* descs[2] = { "Atenuacion de luz estilo cockpit", "Mezcla gradual de pantallas" }; for (uint8_t i = 0; i < 2; i++) { int16_t y = 36 + i * 54; @@ -1687,31 +2346,75 @@ void transitionMenu() { int16_t h = 48; bool isSel = (i == transitionType); - uint16_t bg = isSel ? getThemeDarkColor() : 0x1082; - uint16_t border = isSel ? getThemePrimaryColor() : 0x2104; + if (isRainbowTheme()) { + fillRainbowAlphaRoundRect(x, y, w, h, 6, isSel ? 85 : 30); + drawRainbowRoundRect(x, y, w, h, 6, isSel ? 255 : 110); + if (isSel) { + tft.drawRoundRect(x + 1, y + 1, w - 2, h - 2, 5, TFT_WHITE); + } - tft.fillRoundRect(x, y, w, h, 6, bg); - tft.drawRoundRect(x, y, w, h, 6, border); - if (isSel) { - tft.drawRoundRect(x + 1, y + 1, w - 2, h - 2, 5, TFT_WHITE); - tft.fillRoundRect(x + 2, y + 4, 4, h - 8, 2, getThemePrimaryColor()); - } + // Circular radio indicator + int16_t cx = x + 24; + int16_t cy = y + h / 2; + tft.drawCircle(cx, cy, 9, isSel ? TFT_WHITE : 0x4208); + if (isSel) { + tft.fillCircle(cx, cy, 6, getRainbowScreenColor(cx)); + } else { + tft.fillCircle(cx, cy, 6, TFT_BLACK); + } - // Circular radio indicator - int16_t cx = x + 24; - int16_t cy = y + h / 2; - tft.drawCircle(cx, cy, 9, isSel ? TFT_WHITE : 0x4208); - if (isSel) { - tft.fillCircle(cx, cy, 6, getThemePrimaryColor()); + // Title & Subtitle in continuous rainbow + drawRainbowString(titles[i], x + 44, y + 7, 4); + drawRainbowString(descs[i], x + 44, y + 29, 2); + } else if (isYapaTheme()) { + uint16_t bg = isSel ? 0x2104 : 0x1082; + tft.fillRoundRect(x, y, w, h, 6, bg); + tft.drawRoundRect(x, y, w, h, 6, isSel ? getYapaColor(i) : 0x3186); + if (isSel) { + tft.drawRoundRect(x + 1, y + 1, w - 2, h - 2, 5, TFT_WHITE); + tft.fillRoundRect(x + 2, y + 4, 4, h - 8, 2, getYapaColor(i)); + } + + // Circular radio indicator + int16_t cx = x + 24; + int16_t cy = y + h / 2; + tft.drawCircle(cx, cy, 9, isSel ? TFT_WHITE : 0x4208); + if (isSel) { + tft.fillCircle(cx, cy, 6, getYapaColor(i)); + } else { + tft.fillCircle(cx, cy, 6, TFT_BLACK); + } + + drawYapaString(titles[i], x + 44, y + 7, 4); + drawYapaString(descs[i], x + 44, y + 29, 2); } else { - tft.fillCircle(cx, cy, 6, TFT_BLACK); - } + uint16_t cardCol = getThemePrimaryColor(); + uint16_t bg = isSel ? getThemeDarkColor() : 0x1082; + uint16_t border = isSel ? cardCol : 0x2104; - // Title & Subtitle - tft.setTextColor(isSel ? TFT_WHITE : 0xBDD7, bg); - tft.drawString(titles[i], x + 44, y + 7, 4); - tft.setTextColor(isSel ? getThemePrimaryColor() : 0x8410, bg); - tft.drawString(descs[i], x + 44, y + 29, 2); + tft.fillRoundRect(x, y, w, h, 6, bg); + tft.drawRoundRect(x, y, w, h, 6, border); + if (isSel) { + tft.drawRoundRect(x + 1, y + 1, w - 2, h - 2, 5, TFT_WHITE); + tft.fillRoundRect(x + 2, y + 4, 4, h - 8, 2, cardCol); + } + + // Circular radio indicator + int16_t cx = x + 24; + int16_t cy = y + h / 2; + tft.drawCircle(cx, cy, 9, isSel ? TFT_WHITE : 0x4208); + if (isSel) { + tft.fillCircle(cx, cy, 6, cardCol); + } else { + tft.fillCircle(cx, cy, 6, TFT_BLACK); + } + + // Title & Subtitle + tft.setTextColor(isSel ? TFT_WHITE : 0xBDD7, bg); + tft.drawString(titles[i], x + 44, y + 7, 4); + tft.setTextColor(isSel ? cardCol : 0x8410, bg); + tft.drawString(descs[i], x + 44, y + 29, 2); + } } // Interactive Preview Button [ PROBAR EFECTO ] @@ -1719,10 +2422,21 @@ void transitionMenu() { int16_t py = 146; int16_t pw = 200; int16_t ph = 32; - tft.fillRoundRect(px, py, pw, ph, 5, getThemeDarkColor()); - tft.drawRoundRect(px, py, pw, ph, 5, getThemePrimaryColor()); - tft.setTextColor(getThemePrimaryColor(), getThemeDarkColor()); - tft.drawCentreString("PROBAR EFECTO", 160, py + 7, 2); + if (isRainbowTheme()) { + fillRainbowAlphaRoundRect(px, py, pw, ph, 5, 45); + drawRainbowRoundRect(px, py, pw, ph, 5, 200); + drawRainbowCentreString("PROBAR EFECTO", 160, py + 7, 2); + } else if (isYapaTheme()) { + tft.fillRoundRect(px, py, pw, ph, 5, 0x1082); + tft.drawRoundRect(px, py, pw, ph, 5, 0x971F); + drawYapaCentreString("PROBAR EFECTO", 160, py + 7, 2); + } else { + uint16_t prevBorder = getThemePrimaryColor(); + tft.fillRoundRect(px, py, pw, ph, 5, getThemeDarkColor()); + tft.drawRoundRect(px, py, pw, ph, 5, prevBorder); + tft.setTextColor(prevBorder, getThemeDarkColor()); + tft.drawCentreString("PROBAR EFECTO", 160, py + 7, 2); + } // Bottom action button: [ SALIR ] (x: 80, y: 186, w: 160, h: 42) drawExitButton(false); @@ -1822,10 +2536,7 @@ void transitionMenu() { // Sub-screen 3: Menu de Apariencia (Temas de Color y Transiciones) // ------------------------------------------------------------- void drawAppearanceHeader() { - tft.fillRectVGradient(0, 0, 320, 26, getThemeAccentColor(), getThemeDarkColor()); - tft.fillRect(0, 26, 320, 2, getThemePrimaryColor()); - tft.setTextColor(TFT_WHITE); - tft.drawCentreString("Apariencia", 160, 2, 4); + drawMenuHeaderBanner("Apariencia"); } void appearanceMenu() { @@ -1839,44 +2550,95 @@ void appearanceMenu() { drawAppearanceHeader(); // Card 0: Temas de Color (y: 34 to 102, h: 68) - uint16_t bg0 = (sel == 0) ? getThemeAccentColor() : getThemeDarkColor(); - uint16_t border0 = (sel == 0) ? getThemePrimaryColor() : getThemeAccentColor(); - tft.fillRoundRect(10, 34, 300, 68, 5, bg0); - tft.drawRoundRect(10, 34, 300, 68, 5, border0); - if (sel == 0) { - tft.drawRoundRect(11, 35, 298, 66, 4, TFT_WHITE); + if (isRainbowTheme()) { + fillRainbowAlphaRoundRect(10, 34, 300, 68, 5, (sel == 0) ? 85 : 30); + drawRainbowRoundRect(10, 34, 300, 68, 5, (sel == 0) ? 255 : 120); + if (sel == 0) { + tft.drawRoundRect(11, 35, 298, 66, 4, TFT_WHITE); + } + drawRainbowString("Temas de Color", 24, 42, 4); + String themeInfo = String(GAUGE_THEMES[themeIndex].name) + " | " + String(GAUGE_THEMES[themeIndex].desc); + drawRainbowString(themeInfo, 24, 72, 2); + tft.fillTriangle(296, 68, 290, 62, 290, 74, (sel == 0) ? TFT_WHITE : getRainbowScreenColor(293)); + } else if (isYapaTheme()) { + tft.fillRoundRect(10, 34, 300, 68, 5, (sel == 0) ? 0x2104 : 0x1082); + tft.drawRoundRect(10, 34, 300, 68, 5, (sel == 0) ? getYapaColor(0) : 0x3186); + if (sel == 0) { + tft.drawRoundRect(11, 35, 298, 66, 4, TFT_WHITE); + } + drawYapaString("Temas de Color", 24, 42, 4); + String themeInfo = String(GAUGE_THEMES[themeIndex].name) + " | " + String(GAUGE_THEMES[themeIndex].desc); + drawYapaString(themeInfo, 24, 72, 2); + tft.fillTriangle(296, 68, 290, 62, 290, 74, (sel == 0) ? TFT_WHITE : getYapaColor(1)); } else { - tft.fillRoundRect(13, 40, 4, 56, 2, getThemePrimaryColor()); + uint16_t col0 = getThemePrimaryColor(); + uint16_t bg0 = (sel == 0) ? getThemeAccentColor() : getThemeDarkColor(); + uint16_t border0 = (sel == 0) ? getThemePrimaryColor() : getThemeAccentColor(); + tft.fillRoundRect(10, 34, 300, 68, 5, bg0); + tft.drawRoundRect(10, 34, 300, 68, 5, border0); + if (sel == 0) { + tft.drawRoundRect(11, 35, 298, 66, 4, TFT_WHITE); + } else { + tft.fillRoundRect(13, 40, 4, 56, 2, getThemePrimaryColor()); + } + tft.setTextColor(TFT_WHITE, bg0); + tft.drawString("Temas de Color", 24, 42, 4); + String themeInfo = String(GAUGE_THEMES[themeIndex].name) + " | " + String(GAUGE_THEMES[themeIndex].desc); + tft.setTextColor((sel == 0) ? TFT_YELLOW : 0xBDD7, bg0); + tft.drawString(themeInfo, 24, 72, 2); + tft.fillTriangle(296, 68, 290, 62, 290, 74, (sel == 0) ? TFT_WHITE : col0); } - tft.setTextColor(TFT_WHITE, bg0); - tft.drawString("Temas de Color", 24, 42, 4); - String themeInfo = String(GAUGE_THEMES[themeIndex].name) + " | " + String(GAUGE_THEMES[themeIndex].desc); - tft.setTextColor((sel == 0) ? TFT_YELLOW : 0xBDD7, bg0); - tft.drawString(themeInfo, 24, 72, 2); - tft.fillTriangle(296, 68, 290, 62, 290, 74, (sel == 0) ? TFT_WHITE : getThemePrimaryColor()); // Card 1: Efecto de Transicion (y: 110 to 178, h: 68) - uint16_t bg1 = (sel == 1) ? getThemeAccentColor() : getThemeDarkColor(); - uint16_t border1 = (sel == 1) ? getThemePrimaryColor() : getThemeAccentColor(); - tft.fillRoundRect(10, 110, 300, 68, 5, bg1); - tft.drawRoundRect(10, 110, 300, 68, 5, border1); - if (sel == 1) { - tft.drawRoundRect(11, 111, 298, 66, 4, TFT_WHITE); + if (isRainbowTheme()) { + fillRainbowAlphaRoundRect(10, 110, 300, 68, 5, (sel == 1) ? 85 : 30); + drawRainbowRoundRect(10, 110, 300, 68, 5, (sel == 1) ? 255 : 120); + if (sel == 1) { + tft.drawRoundRect(11, 111, 298, 66, 4, TFT_WHITE); + } + drawRainbowString("Efecto Transicion", 24, 118, 4); + String transInfo = (transitionType == 0) ? "Fundido a Negro | Atenuacion" : "Desvanecimiento | Mezcla gradual"; + drawRainbowString(transInfo, 24, 148, 2); + tft.fillTriangle(296, 144, 290, 138, 290, 150, (sel == 1) ? TFT_WHITE : getRainbowScreenColor(293)); + } else if (isYapaTheme()) { + tft.fillRoundRect(10, 110, 300, 68, 5, (sel == 1) ? 0x2104 : 0x1082); + tft.drawRoundRect(10, 110, 300, 68, 5, (sel == 1) ? getYapaColor(2) : 0x3186); + if (sel == 1) { + tft.drawRoundRect(11, 111, 298, 66, 4, TFT_WHITE); + } + drawYapaString("Efecto Transicion", 24, 118, 4); + String transInfo = (transitionType == 0) ? "Fundido a Negro | Atenuacion" : "Desvanecimiento | Mezcla gradual"; + drawYapaString(transInfo, 24, 148, 2); + tft.fillTriangle(296, 144, 290, 138, 290, 150, (sel == 1) ? TFT_WHITE : getYapaColor(3)); } else { - tft.fillRoundRect(13, 116, 4, 56, 2, getThemePrimaryColor()); + uint16_t col1 = getThemePrimaryColor(); + uint16_t bg1 = (sel == 1) ? getThemeAccentColor() : getThemeDarkColor(); + uint16_t border1 = (sel == 1) ? getThemePrimaryColor() : getThemeAccentColor(); + tft.fillRoundRect(10, 110, 300, 68, 5, bg1); + tft.drawRoundRect(10, 110, 300, 68, 5, border1); + if (sel == 1) { + tft.drawRoundRect(11, 111, 298, 66, 4, TFT_WHITE); + } else { + tft.fillRoundRect(13, 116, 4, 56, 2, getThemePrimaryColor()); + } + tft.setTextColor(TFT_WHITE, bg1); + tft.drawString("Efecto Transicion", 24, 118, 4); + String transInfo = (transitionType == 0) ? "Fundido a Negro | Atenuacion" : "Desvanecimiento | Mezcla gradual"; + tft.setTextColor((sel == 1) ? TFT_YELLOW : 0xBDD7, bg1); + tft.drawString(transInfo, 24, 148, 2); + tft.fillTriangle(296, 144, 290, 138, 290, 150, (sel == 1) ? TFT_WHITE : col1); } - tft.setTextColor(TFT_WHITE, bg1); - tft.drawString("Efecto Transicion", 24, 118, 4); - String transInfo = (transitionType == 0) ? "Deslizamiento | Barrido lateral" : "Desvanecimiento | Mezcla gradual"; - tft.setTextColor((sel == 1) ? TFT_YELLOW : 0xBDD7, bg1); - tft.drawString(transInfo, 24, 148, 2); - tft.fillTriangle(296, 144, 290, 138, 290, 150, (sel == 1) ? TFT_WHITE : getThemePrimaryColor()); // Bottom action button: [ SALIR ] (x: 80, y: 186, w: 160, h: 42) drawExitButton(false); if (sel == 2) { - tft.drawRoundRect(80, 186, 160, 42, 5, getThemePrimaryColor()); - tft.drawRoundRect(81, 187, 158, 40, 4, TFT_WHITE); + if (isRainbowTheme()) { + drawRainbowRoundRect(80, 186, 160, 42, 5, 255); + tft.drawRoundRect(81, 187, 158, 40, 4, TFT_WHITE); + } else { + tft.drawRoundRect(80, 186, 160, 42, 5, getThemePrimaryColor()); + tft.drawRoundRect(81, 187, 158, 40, 4, TFT_WHITE); + } } }; @@ -1967,22 +2729,44 @@ void appearanceMenu() { } // ------------------------------------------------------------- -// Encabezado del Menu de Configuracion (Estilo Azul Oceano & Cian) +// Encabezado del Menu de Configuracion // ------------------------------------------------------------- void drawConfigHeader() { - tft.fillRectVGradient(0, 0, 320, 26, getThemeAccentColor(), getThemeDarkColor()); - tft.fillRect(0, 26, 320, 2, getThemePrimaryColor()); - tft.setTextColor(TFT_WHITE); - tft.drawCentreString("Menu de Configuracion", 160, 2, 4); + drawMenuHeaderBanner("Menu de Configuracion"); } // ------------------------------------------------------------- // Funciones de Botones de Scroll con Animacion Tactil // ------------------------------------------------------------- void drawScrollUpButton(bool canScroll, bool pressed) { - uint16_t bg = pressed ? getThemePrimaryColor() : (canScroll ? getThemeDarkColor() : 0x0841); - uint16_t fg = pressed ? TFT_BLACK : (canScroll ? getThemePrimaryColor() : 0x4208); - uint16_t border = pressed ? getThemePrimaryColor() : (canScroll ? getThemeAccentColor() : 0x2104); + if (isRainbowTheme()) { + uint8_t alpha = pressed ? 90 : (canScroll ? 35 : 12); + fillRainbowAlphaRoundRect(268, 32, 42, 42, 5, alpha); + drawRainbowRoundRect(268, 32, 42, 42, 5, pressed ? 255 : (canScroll ? 150 : 40)); + if (pressed) { + tft.drawRoundRect(269, 33, 40, 40, 4, TFT_WHITE); + } + uint16_t fg = pressed ? TFT_WHITE : (canScroll ? getRainbowScreenColor(289) : 0x4208); + tft.fillTriangle(289, 42, 278, 58, 300, 58, fg); + return; + } + if (isYapaTheme()) { + uint16_t bg = pressed ? 0x2104 : (canScroll ? 0x1082 : 0x0841); + uint16_t fg = pressed ? TFT_WHITE : (canScroll ? 0x971F : 0x4208); + uint16_t border = pressed ? 0x971F : (canScroll ? 0x3186 : 0x2104); + tft.fillRoundRect(268, 32, 42, 42, 5, bg); + tft.drawRoundRect(268, 32, 42, 42, 5, border); + if (pressed) { + tft.drawRoundRect(269, 33, 40, 40, 4, TFT_WHITE); + } + tft.fillTriangle(289, 42, 278, 58, 300, 58, fg); + return; + } + uint16_t pri = getThemePrimaryColor(); + uint16_t acc = getThemeAccentColor(); + uint16_t bg = pressed ? pri : (canScroll ? getThemeDarkColor() : 0x0841); + uint16_t fg = pressed ? TFT_BLACK : (canScroll ? pri : 0x4208); + uint16_t border = pressed ? pri : (canScroll ? acc : 0x2104); tft.fillRoundRect(268, 32, 42, 42, 5, bg); tft.drawRoundRect(268, 32, 42, 42, 5, border); @@ -1993,9 +2777,34 @@ void drawScrollUpButton(bool canScroll, bool pressed) { } void drawScrollDownButton(bool canScroll, bool pressed) { - uint16_t bg = pressed ? getThemePrimaryColor() : (canScroll ? getThemeDarkColor() : 0x0841); - uint16_t fg = pressed ? TFT_BLACK : (canScroll ? getThemePrimaryColor() : 0x4208); - uint16_t border = pressed ? getThemePrimaryColor() : (canScroll ? getThemeAccentColor() : 0x2104); + if (isRainbowTheme()) { + uint8_t alpha = pressed ? 90 : (canScroll ? 35 : 12); + fillRainbowAlphaRoundRect(268, 186, 42, 42, 5, alpha); + drawRainbowRoundRect(268, 186, 42, 42, 5, pressed ? 255 : (canScroll ? 150 : 40)); + if (pressed) { + tft.drawRoundRect(269, 187, 40, 40, 4, TFT_WHITE); + } + uint16_t fg = pressed ? TFT_WHITE : (canScroll ? getRainbowScreenColor(289) : 0x4208); + tft.fillTriangle(289, 214, 278, 198, 300, 198, fg); + return; + } + if (isYapaTheme()) { + uint16_t bg = pressed ? 0x2104 : (canScroll ? 0x1082 : 0x0841); + uint16_t fg = pressed ? TFT_WHITE : (canScroll ? 0x971F : 0x4208); + uint16_t border = pressed ? 0x971F : (canScroll ? 0x3186 : 0x2104); + tft.fillRoundRect(268, 186, 42, 42, 5, bg); + tft.drawRoundRect(268, 186, 42, 42, 5, border); + if (pressed) { + tft.drawRoundRect(269, 187, 40, 40, 4, TFT_WHITE); + } + tft.fillTriangle(289, 214, 278, 198, 300, 198, fg); + return; + } + uint16_t pri = getThemePrimaryColor(); + uint16_t acc = getThemeAccentColor(); + uint16_t bg = pressed ? pri : (canScroll ? getThemeDarkColor() : 0x0841); + uint16_t fg = pressed ? TFT_BLACK : (canScroll ? pri : 0x4208); + uint16_t border = pressed ? pri : (canScroll ? acc : 0x2104); tft.fillRoundRect(268, 186, 42, 42, 5, bg); tft.drawRoundRect(268, 186, 42, 42, 5, border); @@ -2020,23 +2829,43 @@ void listMenu(uint8_t choice, uint8_t scrollOffset) { int y = 32 + i * 50; bool isSel = (choice == itemIdx); - uint16_t bgCol = isSel ? getThemeAccentColor() : getThemeDarkColor(); - uint16_t borderCol = isSel ? getThemePrimaryColor() : getThemeAccentColor(); - - tft.fillRoundRect(10, y, 250, 44, 5, bgCol); - tft.drawRoundRect(10, y, 250, 44, 5, borderCol); - if (isSel) { - tft.drawRoundRect(11, y + 1, 248, 42, 4, TFT_WHITE); + if (isRainbowTheme()) { + fillRainbowAlphaRoundRect(10, y, 250, 44, 5, isSel ? 90 : 35); + drawRainbowRoundRect(10, y, 250, 44, 5, isSel ? 255 : 120); + if (isSel) { + tft.drawRoundRect(11, y + 1, 248, 42, 4, TFT_WHITE); + } + drawRainbowString(menuList[itemIdx], 26, y + 9, 4); + tft.fillTriangle(248, y + 22, 242, y + 16, 242, y + 28, isSel ? TFT_WHITE : getRainbowScreenColor(245)); + } else if (isYapaTheme()) { + tft.fillRoundRect(10, y, 250, 44, 5, isSel ? 0x2104 : 0x1082); + tft.drawRoundRect(10, y, 250, 44, 5, isSel ? getYapaColor(itemIdx) : 0x3186); + if (isSel) { + tft.drawRoundRect(11, y + 1, 248, 42, 4, TFT_WHITE); + } else { + tft.fillRoundRect(13, y + 6, 4, 32, 2, getYapaColor(itemIdx)); + } + drawYapaString(menuList[itemIdx], 26, y + 9, 4); + tft.fillTriangle(248, y + 22, 242, y + 16, 242, y + 28, isSel ? TFT_WHITE : getYapaColor(itemIdx)); } else { - // Sleek vertical theme accent bar on the left of each item card - tft.fillRoundRect(13, y + 6, 4, 32, 2, getThemePrimaryColor()); + uint16_t bgCol = isSel ? getThemeAccentColor() : getThemeDarkColor(); + uint16_t borderCol = isSel ? getThemePrimaryColor() : getThemeAccentColor(); + + tft.fillRoundRect(10, y, 250, 44, 5, bgCol); + tft.drawRoundRect(10, y, 250, 44, 5, borderCol); + if (isSel) { + tft.drawRoundRect(11, y + 1, 248, 42, 4, TFT_WHITE); + } else { + // Sleek vertical theme accent bar on the left of each item card + tft.fillRoundRect(13, y + 6, 4, 32, 2, getThemePrimaryColor()); + } + + tft.setTextColor(TFT_WHITE, bgCol); + tft.drawString(menuList[itemIdx], 26, y + 9, 4); + + // Chevron arrow '>' on right edge of card in theme primary color + tft.fillTriangle(248, y + 22, 242, y + 16, 242, y + 28, isSel ? TFT_WHITE : getThemePrimaryColor()); } - - tft.setTextColor(TFT_WHITE, bgCol); - tft.drawString(menuList[itemIdx], 26, y + 9, 4); - - // Chevron arrow '>' on right edge of card in theme primary color - tft.fillTriangle(248, y + 22, 242, y + 16, 242, y + 28, isSel ? TFT_WHITE : getThemePrimaryColor()); } // 2. Right Scroll Column (x: 268 to 310, w: 42) @@ -2047,9 +2876,15 @@ void listMenu(uint8_t choice, uint8_t scrollOffset) { // Extended Middle Track & Thumb (y: 78 to 182, h: 104) tft.fillRoundRect(286, 78, 6, 104, 3, 0x0841); - tft.drawRoundRect(286, 78, 6, 104, 3, getThemeDarkColor()); + tft.drawRoundRect(286, 78, 6, 104, 3, isRainbowTheme() ? 0x2104 : getThemeDarkColor()); int thumbY = 78 + (scrollOffset * 72) / maxScroll; - tft.fillRoundRect(286, thumbY, 6, 32, 3, getThemePrimaryColor()); + if (isRainbowTheme()) { + tft.fillRoundRect(286, thumbY, 6, 32, 3, getRainbowScreenColor(286)); + } else if (isYapaTheme()) { + tft.fillRoundRect(286, thumbY, 6, 32, 3, getYapaColor(scrollOffset)); + } else { + tft.fillRoundRect(286, thumbY, 6, 32, 3, getThemePrimaryColor()); + } // Down Button [ ▼ ] (x: 268, y: 186, w: 42, h: 42 - aligned with SALIR) drawScrollDownButton(scrollOffset < maxScroll, false); @@ -2057,8 +2892,13 @@ void listMenu(uint8_t choice, uint8_t scrollOffset) { // 3. Bottom action button: [ SALIR ] (x: 80, y: 186, w: 160, h: 42) drawExitButton(false); if (choice == 6) { - tft.drawRoundRect(80, 186, 160, 42, 5, getThemePrimaryColor()); - tft.drawRoundRect(81, 187, 158, 40, 4, TFT_WHITE); + if (isRainbowTheme()) { + drawRainbowRoundRect(80, 186, 160, 42, 5, 255); + tft.drawRoundRect(81, 187, 158, 40, 4, TFT_WHITE); + } else { + tft.drawRoundRect(80, 186, 160, 42, 5, getThemePrimaryColor()); + tft.drawRoundRect(81, 187, 158, 40, 4, TFT_WHITE); + } } } diff --git a/sketch/obd2_gauge/coords_font.h b/sketch/obd2_gauge/coords_font.h new file mode 100644 index 0000000..8ef9c53 --- /dev/null +++ b/sketch/obd2_gauge/coords_font.h @@ -0,0 +1,650 @@ +#pragma once +#include +#include +#include "rainbow_text.h" + +// High-definition 8-bit anti-aliased font data for GPS Navigation & Telemetry +// Generates zero heap fragmentation and provides smooth subpixel alpha blending + +struct CoordsGlyphAA { + uint8_t w; + uint8_t h; + int8_t xOff; + int8_t yOff; + uint8_t xAdv; + uint16_t offset; +}; + +// Title Font (13.5pt Bold AA: ALTITUD, RUMBO / BRUJULA, SATELITES GPS, COORDENADAS) - 2583 bytes +static const uint8_t s_navTitleAlpha[2583] PROGMEM = { + 0x00, 0x00, 0x00, 0x52, 0xFF, 0xFF, 0x68, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xAC, 0xFD, 0xF5, + 0xC2, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0E, 0xF7, 0xCA, 0xAF, 0xFE, 0x1D, 0x00, 0x00, 0x00, 0x00, + 0x60, 0xFF, 0x7A, 0x60, 0xFF, 0x75, 0x00, 0x00, 0x00, 0x00, 0xBA, 0xFF, 0x27, 0x11, 0xFB, 0xCE, + 0x00, 0x00, 0x00, 0x17, 0xFC, 0xD5, 0x00, 0x00, 0xBA, 0xFF, 0x28, 0x00, 0x00, 0x6E, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0x82, 0x00, 0x00, 0xC8, 0xFF, 0x94, 0x84, 0x84, 0x88, 0xFE, 0xDB, 0x00, + 0x22, 0xFF, 0xDF, 0x00, 0x00, 0x00, 0x00, 0xC5, 0xFF, 0x35, 0x7B, 0xFF, 0x88, 0x00, 0x00, 0x00, + 0x00, 0x6C, 0xFF, 0x8F, 0x10, 0xFF, 0xFF, 0xFF, 0xFF, 0xFA, 0xE1, 0x96, 0x11, 0x00, 0x10, 0xFF, + 0xFA, 0x80, 0x80, 0x87, 0xD7, 0xFF, 0xA9, 0x00, 0x10, 0xFF, 0xF4, 0x00, 0x00, 0x00, 0x2D, 0xFF, + 0xE0, 0x00, 0x10, 0xFF, 0xF4, 0x00, 0x00, 0x06, 0x72, 0xFF, 0xAA, 0x00, 0x10, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xBC, 0x10, 0x00, 0x10, 0xFF, 0xF9, 0x7C, 0x7C, 0x7F, 0xAA, 0xFE, 0xC4, 0x08, + 0x10, 0xFF, 0xF4, 0x00, 0x00, 0x00, 0x00, 0xAA, 0xFF, 0x5B, 0x10, 0xFF, 0xF4, 0x00, 0x00, 0x00, + 0x00, 0xAC, 0xFF, 0x63, 0x10, 0xFF, 0xFA, 0x80, 0x80, 0x82, 0xA9, 0xFE, 0xF0, 0x1E, 0x10, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xE9, 0xB4, 0x34, 0x00, 0x00, 0x00, 0x41, 0xB7, 0xEE, 0xF8, 0xDF, 0x96, + 0x1B, 0x00, 0x00, 0x5B, 0xFD, 0xFF, 0xC3, 0xA1, 0xD0, 0xFF, 0xE4, 0x17, 0x09, 0xEC, 0xFF, 0x5F, + 0x00, 0x00, 0x00, 0x84, 0xD4, 0x4C, 0x44, 0xFF, 0xD5, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x64, 0xFF, 0xA9, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x63, 0xFF, 0xAC, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x3E, 0xFF, 0xD9, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0xE4, + 0xFF, 0x68, 0x00, 0x00, 0x00, 0x6B, 0xD3, 0x56, 0x00, 0x49, 0xFA, 0xFF, 0xC6, 0xA0, 0xC5, 0xFF, + 0xEF, 0x29, 0x00, 0x00, 0x35, 0xAF, 0xEB, 0xFA, 0xE2, 0x9D, 0x21, 0x00, 0x10, 0xFF, 0xFF, 0xFF, + 0xFC, 0xE8, 0xAD, 0x39, 0x00, 0x00, 0x10, 0xFF, 0xFA, 0x90, 0x97, 0xC3, 0xFF, 0xFC, 0x5C, 0x00, + 0x10, 0xFF, 0xF4, 0x00, 0x00, 0x00, 0x59, 0xFF, 0xF2, 0x11, 0x10, 0xFF, 0xF4, 0x00, 0x00, 0x00, + 0x00, 0xC0, 0xFF, 0x59, 0x10, 0xFF, 0xF4, 0x00, 0x00, 0x00, 0x00, 0x8F, 0xFF, 0x7B, 0x10, 0xFF, + 0xF4, 0x00, 0x00, 0x00, 0x00, 0x94, 0xFF, 0x74, 0x10, 0xFF, 0xF4, 0x00, 0x00, 0x00, 0x00, 0xC7, + 0xFF, 0x4D, 0x10, 0xFF, 0xF4, 0x00, 0x00, 0x00, 0x5C, 0xFF, 0xE9, 0x07, 0x10, 0xFF, 0xFA, 0x90, + 0x92, 0xBC, 0xFF, 0xFA, 0x4D, 0x00, 0x10, 0xFF, 0xFF, 0xFF, 0xFF, 0xEC, 0xB2, 0x37, 0x00, 0x00, + 0x10, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x84, 0x10, 0xFF, 0xFA, 0x90, 0x90, 0x90, 0x90, + 0x90, 0x4A, 0x10, 0xFF, 0xF4, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0xFF, 0xF4, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x10, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x18, 0x10, 0xFF, 0xFA, + 0x90, 0x90, 0x90, 0x90, 0x90, 0x0D, 0x10, 0xFF, 0xF4, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, + 0xFF, 0xF4, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0xFF, 0xFA, 0x90, 0x90, 0x90, 0x90, 0x90, + 0x72, 0x10, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xCC, 0x10, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0x0C, 0x10, 0xFF, 0xFA, 0x90, 0x90, 0x90, 0x90, 0x90, 0x06, 0x10, 0xFF, 0xF4, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0xFF, 0xF4, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xE0, 0x00, 0x10, 0xFF, 0xFA, 0x90, 0x90, 0x90, 0x90, 0x7E, 0x00, + 0x10, 0xFF, 0xF4, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0xFF, 0xF4, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x10, 0xFF, 0xF4, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0xFF, 0xF4, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x38, 0xAF, 0xE9, 0xFA, 0xE6, 0xAB, 0x35, 0x00, 0x00, 0x00, + 0x52, 0xFB, 0xFF, 0xC9, 0xA1, 0xC4, 0xFF, 0xF8, 0x3E, 0x00, 0x08, 0xE9, 0xFF, 0x6B, 0x00, 0x00, + 0x00, 0x69, 0xD7, 0x68, 0x00, 0x43, 0xFF, 0xD8, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x64, 0xFF, 0xAB, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x63, 0xFF, 0xAD, 0x00, 0x00, + 0x2C, 0xFF, 0xFF, 0xFF, 0xFF, 0x04, 0x3F, 0xFF, 0xDC, 0x02, 0x00, 0x15, 0x7C, 0x7C, 0xE9, 0xFF, + 0x04, 0x06, 0xE5, 0xFF, 0x72, 0x00, 0x00, 0x00, 0x09, 0xDF, 0xFF, 0x04, 0x00, 0x4C, 0xFA, 0xFF, + 0xC9, 0x9E, 0xB5, 0xED, 0xFF, 0xC4, 0x01, 0x00, 0x00, 0x36, 0xAF, 0xEB, 0xFB, 0xE9, 0xB6, 0x5A, + 0x03, 0x00, 0x10, 0xFF, 0xF4, 0x00, 0x00, 0x00, 0x00, 0xD8, 0xFF, 0x2C, 0x10, 0xFF, 0xF4, 0x00, + 0x00, 0x00, 0x00, 0xD8, 0xFF, 0x2C, 0x10, 0xFF, 0xF4, 0x00, 0x00, 0x00, 0x00, 0xD8, 0xFF, 0x2C, + 0x10, 0xFF, 0xF4, 0x00, 0x00, 0x00, 0x00, 0xD8, 0xFF, 0x2C, 0x10, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0x2C, 0x10, 0xFF, 0xFC, 0xAC, 0xAC, 0xAC, 0xAC, 0xF2, 0xFF, 0x2C, 0x10, 0xFF, + 0xF4, 0x00, 0x00, 0x00, 0x00, 0xD8, 0xFF, 0x2C, 0x10, 0xFF, 0xF4, 0x00, 0x00, 0x00, 0x00, 0xD8, + 0xFF, 0x2C, 0x10, 0xFF, 0xF4, 0x00, 0x00, 0x00, 0x00, 0xD8, 0xFF, 0x2C, 0x10, 0xFF, 0xF4, 0x00, + 0x00, 0x00, 0x00, 0xD8, 0xFF, 0x2C, 0x10, 0xFF, 0xF4, 0x10, 0xFF, 0xF4, 0x10, 0xFF, 0xF4, 0x10, + 0xFF, 0xF4, 0x10, 0xFF, 0xF4, 0x10, 0xFF, 0xF4, 0x10, 0xFF, 0xF4, 0x10, 0xFF, 0xF4, 0x10, 0xFF, + 0xF4, 0x10, 0xFF, 0xF4, 0x00, 0x00, 0x18, 0xFF, 0xFF, 0xFF, 0xD4, 0x00, 0x00, 0x0D, 0x94, 0xA8, + 0xFF, 0xD4, 0x00, 0x00, 0x00, 0x00, 0x30, 0xFF, 0xD4, 0x00, 0x00, 0x00, 0x00, 0x30, 0xFF, 0xD4, + 0x00, 0x00, 0x00, 0x00, 0x30, 0xFF, 0xD4, 0x00, 0x00, 0x00, 0x00, 0x30, 0xFF, 0xD4, 0x00, 0x00, + 0x00, 0x00, 0x30, 0xFF, 0xD2, 0x83, 0xE4, 0x60, 0x00, 0x4F, 0xFF, 0xB5, 0x56, 0xFF, 0xF0, 0x9F, + 0xE5, 0xFF, 0x5D, 0x00, 0x70, 0xDB, 0xF7, 0xDB, 0x6F, 0x00, 0x10, 0xFF, 0xF4, 0x00, 0x00, 0x00, + 0x3A, 0xF4, 0xF1, 0x35, 0x10, 0xFF, 0xF4, 0x00, 0x00, 0x33, 0xF0, 0xF4, 0x3B, 0x00, 0x10, 0xFF, + 0xF4, 0x00, 0x2C, 0xEC, 0xF6, 0x40, 0x00, 0x00, 0x10, 0xFF, 0xF4, 0x26, 0xE8, 0xF8, 0x46, 0x00, + 0x00, 0x00, 0x10, 0xFF, 0xF9, 0xE3, 0xFF, 0xC6, 0x02, 0x00, 0x00, 0x00, 0x10, 0xFF, 0xFF, 0xDB, + 0xCA, 0xFF, 0x82, 0x00, 0x00, 0x00, 0x10, 0xFF, 0xF8, 0x10, 0x1C, 0xEC, 0xFE, 0x49, 0x00, 0x00, + 0x10, 0xFF, 0xF4, 0x00, 0x00, 0x49, 0xFE, 0xED, 0x1F, 0x00, 0x10, 0xFF, 0xF4, 0x00, 0x00, 0x00, + 0x86, 0xFF, 0xCB, 0x07, 0x10, 0xFF, 0xF4, 0x00, 0x00, 0x00, 0x03, 0xC1, 0xFF, 0x99, 0x10, 0xFF, + 0xF4, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0xFF, 0xF4, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x10, 0xFF, 0xF4, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0xFF, 0xF4, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x10, 0xFF, 0xF4, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0xFF, 0xF4, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x10, 0xFF, 0xF4, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0xFF, 0xF4, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0xFF, 0xFA, 0x90, 0x90, 0x90, 0x90, 0x90, 0x12, 0x10, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x20, 0x10, 0xFF, 0xFF, 0xCA, 0x00, 0x00, 0x00, 0x1B, + 0xFF, 0xFF, 0xB8, 0x10, 0xFF, 0xFE, 0xFE, 0x1A, 0x00, 0x00, 0x68, 0xFE, 0xFF, 0xB8, 0x10, 0xFF, + 0xD5, 0xFF, 0x68, 0x00, 0x00, 0xB5, 0xD6, 0xFF, 0xB8, 0x10, 0xFF, 0xB8, 0xDE, 0xB6, 0x00, 0x0A, + 0xF7, 0x9A, 0xFF, 0xB8, 0x10, 0xFF, 0xBC, 0x95, 0xF8, 0x0C, 0x4F, 0xFF, 0x51, 0xFF, 0xB8, 0x10, + 0xFF, 0xBC, 0x46, 0xFF, 0x54, 0x9C, 0xED, 0x14, 0xFF, 0xB8, 0x10, 0xFF, 0xBC, 0x05, 0xF0, 0x9B, + 0xE4, 0xA2, 0x10, 0xFF, 0xB8, 0x10, 0xFF, 0xBC, 0x00, 0xA7, 0xF1, 0xFF, 0x52, 0x10, 0xFF, 0xB8, + 0x10, 0xFF, 0xBC, 0x00, 0x58, 0xFF, 0xF7, 0x0B, 0x10, 0xFF, 0xB8, 0x10, 0xFF, 0xBC, 0x00, 0x0E, + 0xF9, 0xB3, 0x00, 0x10, 0xFF, 0xB8, 0x10, 0xFF, 0xFF, 0x81, 0x00, 0x00, 0x00, 0xA0, 0xFF, 0x2C, + 0x10, 0xFF, 0xFF, 0xF4, 0x19, 0x00, 0x00, 0xA0, 0xFF, 0x2C, 0x10, 0xFF, 0xE3, 0xFF, 0x9B, 0x00, + 0x00, 0xA0, 0xFF, 0x2C, 0x10, 0xFF, 0xB5, 0xBC, 0xFC, 0x2B, 0x00, 0xA0, 0xFF, 0x2C, 0x10, 0xFF, + 0xBB, 0x32, 0xFE, 0xB4, 0x00, 0xA0, 0xFF, 0x2C, 0x10, 0xFF, 0xBC, 0x00, 0xA6, 0xFF, 0x41, 0x9F, + 0xFF, 0x2C, 0x10, 0xFF, 0xBC, 0x00, 0x21, 0xF9, 0xCC, 0x96, 0xFF, 0x2C, 0x10, 0xFF, 0xBC, 0x00, + 0x00, 0x90, 0xFF, 0xDB, 0xFF, 0x2C, 0x10, 0xFF, 0xBC, 0x00, 0x00, 0x14, 0xF1, 0xFF, 0xFF, 0x2C, + 0x10, 0xFF, 0xBC, 0x00, 0x00, 0x00, 0x7A, 0xFF, 0xFF, 0x2C, 0x00, 0x00, 0x34, 0xAD, 0xEA, 0xFA, + 0xE4, 0x9F, 0x22, 0x00, 0x00, 0x00, 0x49, 0xF9, 0xFF, 0xC9, 0xA4, 0xD5, 0xFF, 0xEE, 0x2D, 0x00, + 0x06, 0xE5, 0xFF, 0x6E, 0x00, 0x00, 0x02, 0x96, 0xFF, 0xC8, 0x00, 0x41, 0xFF, 0xD9, 0x01, 0x00, + 0x00, 0x00, 0x0D, 0xF4, 0xFF, 0x1F, 0x63, 0xFF, 0xAA, 0x00, 0x00, 0x00, 0x00, 0x00, 0xCF, 0xFF, + 0x42, 0x63, 0xFF, 0xAD, 0x00, 0x00, 0x00, 0x00, 0x00, 0xD2, 0xFF, 0x3D, 0x3C, 0xFF, 0xDE, 0x02, + 0x00, 0x00, 0x00, 0x0C, 0xF4, 0xFE, 0x18, 0x04, 0xE0, 0xFF, 0x78, 0x00, 0x00, 0x01, 0x91, 0xFF, + 0xB8, 0x00, 0x00, 0x44, 0xF8, 0xFF, 0xCB, 0xA4, 0xD3, 0xFF, 0xE8, 0x22, 0x00, 0x00, 0x00, 0x30, + 0xAC, 0xEB, 0xF9, 0xE0, 0x95, 0x19, 0x00, 0x00, 0x10, 0xFF, 0xFF, 0xFF, 0xFF, 0xF7, 0xD6, 0x72, + 0x01, 0x10, 0xFF, 0xFA, 0x90, 0x90, 0x9F, 0xEE, 0xFF, 0x73, 0x10, 0xFF, 0xF4, 0x00, 0x00, 0x00, + 0x4B, 0xFF, 0xC6, 0x10, 0xFF, 0xF4, 0x00, 0x00, 0x00, 0x4F, 0xFF, 0xC7, 0x10, 0xFF, 0xFA, 0x8C, + 0x8C, 0x9A, 0xEE, 0xFF, 0x67, 0x10, 0xFF, 0xFF, 0xFF, 0xFF, 0xFB, 0xD8, 0x6D, 0x00, 0x10, 0xFF, + 0xF4, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0xFF, 0xF4, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x10, 0xFF, 0xF4, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0xFF, 0xF4, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x32, 0xAC, 0xE9, 0xFA, 0xE4, 0x9E, 0x21, 0x00, 0x00, 0x00, 0x47, 0xF8, + 0xFF, 0xCA, 0xA4, 0xD5, 0xFF, 0xED, 0x2A, 0x00, 0x05, 0xE3, 0xFF, 0x71, 0x00, 0x00, 0x02, 0x97, + 0xFF, 0xC3, 0x00, 0x3F, 0xFF, 0xDB, 0x01, 0x00, 0x00, 0x00, 0x0E, 0xF5, 0xFF, 0x1B, 0x63, 0xFF, + 0xAC, 0x00, 0x00, 0x00, 0x00, 0x00, 0xD1, 0xFF, 0x41, 0x64, 0xFF, 0xAC, 0x00, 0x00, 0x00, 0x00, + 0x00, 0xD0, 0xFF, 0x3C, 0x40, 0xFF, 0xDB, 0x01, 0x00, 0x00, 0x00, 0x09, 0xF3, 0xFF, 0x19, 0x05, + 0xE7, 0xFF, 0x69, 0x00, 0x00, 0x00, 0x84, 0xFF, 0xBF, 0x00, 0x00, 0x50, 0xFC, 0xFF, 0xB7, 0x90, + 0xC1, 0xFF, 0xF1, 0x33, 0x00, 0x00, 0x00, 0x3F, 0xBB, 0xF5, 0xFF, 0xFC, 0xB2, 0x30, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x25, 0xFF, 0xE6, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xB5, 0xFF, 0xCB, 0x86, 0x32, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x17, 0xB2, 0xF4, 0xE9, 0x4C, + 0x00, 0x10, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0xE8, 0xAF, 0x2A, 0x00, 0x10, 0xFF, 0xFA, 0x90, 0x90, + 0x93, 0xC3, 0xFF, 0xE5, 0x0E, 0x10, 0xFF, 0xF4, 0x00, 0x00, 0x00, 0x00, 0xCE, 0xFF, 0x44, 0x10, + 0xFF, 0xF4, 0x00, 0x00, 0x00, 0x01, 0xD2, 0xFF, 0x3E, 0x10, 0xFF, 0xFA, 0x90, 0x90, 0x92, 0xC5, + 0xFF, 0xCB, 0x02, 0x10, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x9D, 0x0F, 0x00, 0x10, 0xFF, 0xF4, + 0x00, 0x00, 0x69, 0xFF, 0xBE, 0x00, 0x00, 0x10, 0xFF, 0xF4, 0x00, 0x00, 0x03, 0xD6, 0xFF, 0x57, + 0x00, 0x10, 0xFF, 0xF4, 0x00, 0x00, 0x00, 0x4B, 0xFF, 0xE3, 0x0B, 0x10, 0xFF, 0xF4, 0x00, 0x00, + 0x00, 0x00, 0xBB, 0xFF, 0x87, 0x00, 0x18, 0x97, 0xDF, 0xF8, 0xEB, 0xBB, 0x3B, 0x00, 0x00, 0xC9, + 0xFF, 0xA7, 0x73, 0x9D, 0xFE, 0xF3, 0x1D, 0x14, 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x9A, 0xDC, 0x4F, + 0x09, 0xF8, 0xFE, 0x76, 0x1B, 0x00, 0x00, 0x00, 0x00, 0x00, 0x6C, 0xFB, 0xFF, 0xFF, 0xDD, 0x9B, + 0x35, 0x00, 0x00, 0x00, 0x1E, 0x6E, 0xAC, 0xE9, 0xFF, 0xFD, 0x49, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x02, 0x8D, 0xFF, 0xB2, 0x56, 0xDA, 0x98, 0x00, 0x00, 0x00, 0x47, 0xFF, 0xBE, 0x1D, 0xEC, 0xFF, + 0xBA, 0x8A, 0x9A, 0xEC, 0xFF, 0x65, 0x00, 0x28, 0xA3, 0xE2, 0xF9, 0xEA, 0xC2, 0x58, 0x00, 0xD8, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x68, 0x79, 0x90, 0x90, 0xE2, 0xFF, 0xAF, 0x90, 0x90, + 0x3A, 0x00, 0x00, 0x00, 0xBC, 0xFF, 0x48, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xBC, 0xFF, 0x48, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xBC, 0xFF, 0x48, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xBC, + 0xFF, 0x48, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xBC, 0xFF, 0x48, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0xBC, 0xFF, 0x48, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xBC, 0xFF, 0x48, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0xBC, 0xFF, 0x48, 0x00, 0x00, 0x00, 0x28, 0xFF, 0xDC, 0x00, 0x00, 0x00, 0x00, + 0xC4, 0xFF, 0x40, 0x28, 0xFF, 0xDC, 0x00, 0x00, 0x00, 0x00, 0xC4, 0xFF, 0x40, 0x28, 0xFF, 0xDC, + 0x00, 0x00, 0x00, 0x00, 0xC4, 0xFF, 0x40, 0x28, 0xFF, 0xDC, 0x00, 0x00, 0x00, 0x00, 0xC4, 0xFF, + 0x40, 0x28, 0xFF, 0xDC, 0x00, 0x00, 0x00, 0x00, 0xC4, 0xFF, 0x40, 0x28, 0xFF, 0xDC, 0x00, 0x00, + 0x00, 0x00, 0xC4, 0xFF, 0x40, 0x21, 0xFF, 0xEB, 0x00, 0x00, 0x00, 0x00, 0xD7, 0xFF, 0x36, 0x05, + 0xF2, 0xFF, 0x3E, 0x00, 0x00, 0x34, 0xFD, 0xF7, 0x0C, 0x00, 0x81, 0xFF, 0xF6, 0xA8, 0xA9, 0xF5, + 0xFF, 0x7D, 0x00, 0x00, 0x01, 0x5D, 0xCA, 0xF0, 0xF6, 0xCB, 0x61, 0x00, 0x00, 0xBB, 0xFF, 0x5A, + 0x00, 0x00, 0x00, 0x09, 0xF5, 0xFC, 0x16, 0x5F, 0xFF, 0xA9, 0x00, 0x00, 0x00, 0x4D, 0xFF, 0xB7, + 0x00, 0x0D, 0xF6, 0xF1, 0x06, 0x00, 0x00, 0x9B, 0xFF, 0x5C, 0x00, 0x00, 0xA9, 0xFF, 0x46, 0x00, + 0x01, 0xE7, 0xF5, 0x0C, 0x00, 0x00, 0x4E, 0xFF, 0x95, 0x00, 0x37, 0xFF, 0xA6, 0x00, 0x00, 0x00, + 0x05, 0xEC, 0xE2, 0x00, 0x85, 0xFF, 0x4C, 0x00, 0x00, 0x00, 0x00, 0x97, 0xFF, 0x30, 0xD1, 0xEC, + 0x05, 0x00, 0x00, 0x00, 0x00, 0x3C, 0xFF, 0x8E, 0xFF, 0x96, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, + 0xE0, 0xF7, 0xFF, 0x3B, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x86, 0xFF, 0xE0, 0x01, 0x00, 0x00, + 0x00, 0xDD, 0xFF, 0x2A, 0x00, 0x00, 0x9D, 0xFF, 0xE0, 0x00, 0x00, 0x01, 0xF1, 0xFE, 0x14, 0x9D, + 0xFF, 0x61, 0x00, 0x00, 0xD6, 0xFF, 0xFF, 0x1B, 0x00, 0x27, 0xFF, 0xD4, 0x00, 0x5D, 0xFF, 0x98, + 0x00, 0x11, 0xFE, 0xD1, 0xFF, 0x55, 0x00, 0x5C, 0xFF, 0x94, 0x00, 0x1D, 0xFF, 0xCE, 0x00, 0x49, + 0xFF, 0x6F, 0xFF, 0x8F, 0x00, 0x91, 0xFF, 0x55, 0x00, 0x00, 0xDE, 0xFB, 0x09, 0x81, 0xFF, 0x1B, + 0xE8, 0xC9, 0x00, 0xC6, 0xFF, 0x16, 0x00, 0x00, 0x9F, 0xFF, 0x3C, 0xBA, 0xE2, 0x00, 0xB0, 0xFA, + 0x0C, 0xF6, 0xD5, 0x00, 0x00, 0x00, 0x5F, 0xFF, 0x72, 0xEE, 0xA9, 0x00, 0x77, 0xFF, 0x65, 0xFF, + 0x96, 0x00, 0x00, 0x00, 0x1F, 0xFF, 0xC5, 0xFF, 0x71, 0x00, 0x3E, 0xFF, 0xC7, 0xFF, 0x56, 0x00, + 0x00, 0x00, 0x00, 0xE0, 0xFF, 0xFF, 0x38, 0x00, 0x0A, 0xFB, 0xFF, 0xFF, 0x17, 0x00, 0x00, 0x00, + 0x00, 0xA0, 0xFF, 0xF9, 0x07, 0x00, 0x00, 0xCC, 0xFF, 0xD7, 0x00, 0x00, 0x00, 0x46, 0xFF, 0xD1, + 0x03, 0x00, 0x00, 0x7E, 0xFF, 0xA0, 0x00, 0x00, 0xA1, 0xFF, 0x70, 0x00, 0x21, 0xF7, 0xED, 0x15, + 0x00, 0x00, 0x12, 0xE9, 0xF1, 0x19, 0xB4, 0xFF, 0x65, 0x00, 0x00, 0x00, 0x00, 0x57, 0xFF, 0xD4, + 0xFF, 0xC5, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0xBE, 0xFF, 0xFF, 0x34, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x18, 0xEF, 0xFF, 0xFF, 0x7B, 0x00, 0x00, 0x00, 0x00, 0x00, 0xAA, 0xFF, 0x78, 0xF0, 0xF6, + 0x20, 0x00, 0x00, 0x00, 0x4B, 0xFF, 0xC3, 0x01, 0x6D, 0xFF, 0xB2, 0x00, 0x00, 0x0C, 0xE1, 0xFB, + 0x2D, 0x00, 0x03, 0xCE, 0xFF, 0x4E, 0x00, 0x8F, 0xFF, 0x8D, 0x00, 0x00, 0x00, 0x37, 0xFE, 0xDF, + 0x0A, 0x7B, 0xFF, 0xA4, 0x00, 0x00, 0x00, 0x4B, 0xFF, 0xD0, 0x02, 0x08, 0xDF, 0xFE, 0x38, 0x00, + 0x06, 0xDA, 0xFF, 0x40, 0x00, 0x00, 0x56, 0xFF, 0xC9, 0x01, 0x78, 0xFF, 0xAE, 0x00, 0x00, 0x00, + 0x00, 0xC2, 0xFF, 0x77, 0xF3, 0xF9, 0x23, 0x00, 0x00, 0x00, 0x00, 0x32, 0xFD, 0xFF, 0xFF, 0x8A, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x9D, 0xFF, 0xEA, 0x0E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x54, 0xFF, 0xB0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x54, 0xFF, 0xB0, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x54, 0xFF, 0xB0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x54, 0xFF, + 0xB0, 0x00, 0x00, 0x00, 0x00, 0x10, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xE0, 0x00, 0x09, 0x94, + 0x94, 0x94, 0x94, 0xCD, 0xFF, 0xBC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0xEF, 0xEE, 0x1E, 0x00, + 0x00, 0x00, 0x00, 0x05, 0xC8, 0xFF, 0x4F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x8C, 0xFF, 0x91, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x4B, 0xFE, 0xCC, 0x06, 0x00, 0x00, 0x00, 0x00, 0x1C, 0xEC, 0xF1, 0x23, + 0x00, 0x00, 0x00, 0x00, 0x03, 0xC3, 0xFF, 0x57, 0x00, 0x00, 0x00, 0x00, 0x00, 0x74, 0xFF, 0xEB, + 0x94, 0x94, 0x94, 0x94, 0x94, 0x17, 0x94, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x28, 0x00, + 0x03, 0xF6, 0xA6, 0x00, 0x2C, 0xFF, 0x74, 0x00, 0x5F, 0xFF, 0x42, 0x00, 0x92, 0xFF, 0x11, 0x00, + 0xC5, 0xDE, 0x00, 0x02, 0xF4, 0xAC, 0x00, 0x2A, 0xFF, 0x7A, 0x00, 0x5D, 0xFF, 0x48, 0x00, 0x90, + 0xFF, 0x16, 0x00, 0xC3, 0xE4, 0x00, 0x00, +}; + +static const CoordsGlyphAA s_navTitleGlyphs[28] PROGMEM = { + { 10, 10, 0, 0, 10, 0 }, // 'A' + { 10, 10, 0, 0, 10, 100 }, // 'B' + { 10, 10, 0, 0, 10, 200 }, // 'C' + { 10, 10, 0, 0, 10, 300 }, // 'D' + { 9, 10, 0, 0, 9, 400 }, // 'E' + { 9, 10, 0, 0, 8, 490 }, // 'F' + { 11, 10, 0, 0, 10, 580 }, // 'G' + { 10, 10, 0, 0, 10, 690 }, // 'H' + { 3, 10, 0, 0, 4, 790 }, // 'I' + { 7, 10, 0, 0, 8, 820 }, // 'J' + { 10, 10, 0, 0, 10, 890 }, // 'K' + { 9, 10, 0, 0, 8, 990 }, // 'L' + { 11, 10, 0, 0, 11, 1080 }, // 'M' + { 10, 10, 0, 0, 10, 1190 }, // 'N' + { 11, 10, 0, 0, 10, 1290 }, // 'O' + { 9, 10, 0, 0, 9, 1400 }, // 'P' + { 11, 13, 0, 0, 10, 1490 }, // 'Q' + { 10, 10, 0, 0, 10, 1633 }, // 'R' + { 9, 10, 0, 0, 9, 1733 }, // 'S' + { 9, 10, 0, 0, 8, 1823 }, // 'T' + { 10, 10, 0, 0, 10, 1913 }, // 'U' + { 10, 10, 0, 0, 9, 2013 }, // 'V' + { 14, 10, 0, 0, 13, 2113 }, // 'W' + { 10, 10, 0, 0, 9, 2253 }, // 'X' + { 10, 10, 0, 0, 9, 2353 }, // 'Y' + { 9, 10, 0, 0, 8, 2453 }, // 'Z' + { 4, 10, 0, 0, 4, 2543 }, // '/' + { 0, 0, 0, 0, 4, 2583 }, // ' ' +}; + +// Subword Font (10.5pt Bold AA: LATITUD, LONGITUD, HDOP, MSNM, DIAGS) - 2168 bytes +static const uint8_t s_navSubAlpha[2168] PROGMEM = { + 0x00, 0x00, 0x24, 0xFF, 0xFC, 0x16, 0x00, 0x00, 0x00, 0x00, 0x7D, 0xEC, 0xF5, 0x6B, 0x00, 0x00, + 0x00, 0x00, 0xD4, 0xA4, 0xB3, 0xC3, 0x00, 0x00, 0x00, 0x2C, 0xFF, 0x57, 0x66, 0xFE, 0x1C, 0x00, + 0x00, 0x85, 0xFA, 0x0F, 0x19, 0xFE, 0x73, 0x00, 0x00, 0xDC, 0xFF, 0xFF, 0xFF, 0xFF, 0xCB, 0x00, + 0x35, 0xFF, 0x7E, 0x30, 0x30, 0x89, 0xFF, 0x23, 0x8C, 0xF9, 0x12, 0x00, 0x00, 0x1C, 0xFD, 0x7B, + 0x44, 0xFF, 0xFF, 0xFF, 0xF7, 0xD1, 0x4B, 0x00, 0x44, 0xFF, 0x71, 0x2C, 0x39, 0xD0, 0xED, 0x00, + 0x44, 0xFF, 0x54, 0x00, 0x0C, 0xBD, 0xDB, 0x00, 0x44, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x46, 0x00, + 0x44, 0xFF, 0x71, 0x2C, 0x33, 0x9D, 0xF7, 0x26, 0x44, 0xFF, 0x54, 0x00, 0x00, 0x2E, 0xFF, 0x64, + 0x44, 0xFF, 0x71, 0x2C, 0x31, 0x9A, 0xFF, 0x38, 0x44, 0xFF, 0xFF, 0xFF, 0xFC, 0xDC, 0x68, 0x00, + 0x00, 0x21, 0xAC, 0xEF, 0xF7, 0xD6, 0x6F, 0x01, 0x05, 0xD7, 0xEA, 0x69, 0x4E, 0x85, 0xE8, 0x41, + 0x53, 0xFF, 0x56, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7B, 0xFF, 0x1A, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x78, 0xFF, 0x1B, 0x00, 0x00, 0x00, 0x00, 0x00, 0x4D, 0xFF, 0x59, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x02, 0xCE, 0xEC, 0x6A, 0x4B, 0x83, 0xE5, 0x4D, 0x00, 0x19, 0xA5, 0xED, 0xF9, 0xD9, 0x79, 0x03, + 0x44, 0xFF, 0xFF, 0xFD, 0xE4, 0x96, 0x10, 0x00, 0x44, 0xFF, 0x79, 0x3E, 0x77, 0xF5, 0xCA, 0x02, + 0x44, 0xFF, 0x54, 0x00, 0x00, 0x6F, 0xFF, 0x46, 0x44, 0xFF, 0x54, 0x00, 0x00, 0x2B, 0xFF, 0x73, + 0x44, 0xFF, 0x54, 0x00, 0x00, 0x2F, 0xFF, 0x6D, 0x44, 0xFF, 0x54, 0x00, 0x00, 0x75, 0xFF, 0x38, + 0x44, 0xFF, 0x79, 0x3A, 0x71, 0xF6, 0xBC, 0x00, 0x44, 0xFF, 0xFF, 0xFF, 0xE9, 0x96, 0x0D, 0x00, + 0x44, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xB0, 0x44, 0xFF, 0x79, 0x38, 0x38, 0x38, 0x26, 0x44, 0xFF, + 0x54, 0x00, 0x00, 0x00, 0x00, 0x44, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x5C, 0x44, 0xFF, 0x79, 0x38, + 0x38, 0x38, 0x14, 0x44, 0xFF, 0x54, 0x00, 0x00, 0x00, 0x00, 0x44, 0xFF, 0x79, 0x38, 0x38, 0x38, + 0x32, 0x44, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xE8, 0x44, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x50, 0x44, + 0xFF, 0x79, 0x38, 0x38, 0x38, 0x11, 0x44, 0xFF, 0x54, 0x00, 0x00, 0x00, 0x00, 0x44, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0x30, 0x44, 0xFF, 0x79, 0x38, 0x38, 0x38, 0x0A, 0x44, 0xFF, 0x54, 0x00, 0x00, + 0x00, 0x00, 0x44, 0xFF, 0x54, 0x00, 0x00, 0x00, 0x00, 0x44, 0xFF, 0x54, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x1A, 0xA5, 0xEB, 0xF9, 0xDF, 0x8B, 0x09, 0x03, 0xD0, 0xEE, 0x6E, 0x4B, 0x7C, 0xE6, 0x62, + 0x50, 0xFF, 0x5A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7A, 0xFF, 0x1A, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x78, 0xFF, 0x1C, 0x00, 0x6C, 0xFF, 0xFF, 0xE0, 0x4D, 0xFF, 0x5E, 0x00, 0x12, 0x2C, 0xA6, 0xE0, + 0x02, 0xD0, 0xEF, 0x6C, 0x46, 0x6D, 0xE0, 0xD2, 0x00, 0x1B, 0xA6, 0xED, 0xF9, 0xDB, 0x8F, 0x18, + 0x44, 0xFF, 0x54, 0x00, 0x00, 0x60, 0xFF, 0x34, 0x44, 0xFF, 0x54, 0x00, 0x00, 0x60, 0xFF, 0x34, + 0x44, 0xFF, 0x54, 0x00, 0x00, 0x60, 0xFF, 0x34, 0x44, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x34, + 0x44, 0xFF, 0x89, 0x50, 0x50, 0x92, 0xFF, 0x34, 0x44, 0xFF, 0x54, 0x00, 0x00, 0x60, 0xFF, 0x34, + 0x44, 0xFF, 0x54, 0x00, 0x00, 0x60, 0xFF, 0x34, 0x44, 0xFF, 0x54, 0x00, 0x00, 0x60, 0xFF, 0x34, + 0x44, 0xFF, 0x54, 0x44, 0xFF, 0x54, 0x44, 0xFF, 0x54, 0x44, 0xFF, 0x54, 0x44, 0xFF, 0x54, 0x44, + 0xFF, 0x54, 0x44, 0xFF, 0x54, 0x44, 0xFF, 0x54, 0x00, 0x00, 0xB8, 0xFF, 0xFF, 0x5C, 0x00, 0x00, + 0x2B, 0x66, 0xFF, 0x5C, 0x00, 0x00, 0x00, 0x38, 0xFF, 0x5C, 0x00, 0x00, 0x00, 0x38, 0xFF, 0x5C, + 0x00, 0x00, 0x00, 0x38, 0xFF, 0x5C, 0x00, 0x00, 0x00, 0x3C, 0xFF, 0x54, 0x97, 0xE8, 0x54, 0x9F, + 0xFC, 0x24, 0x27, 0xC6, 0xF6, 0xE3, 0x65, 0x00, 0x44, 0xFF, 0x54, 0x00, 0x05, 0xB6, 0xED, 0x2C, + 0x44, 0xFF, 0x54, 0x02, 0xA9, 0xF1, 0x34, 0x00, 0x44, 0xFF, 0x54, 0x9C, 0xF5, 0x3C, 0x00, 0x00, + 0x44, 0xFF, 0xD1, 0xFF, 0x8E, 0x00, 0x00, 0x00, 0x44, 0xFF, 0xDD, 0xB0, 0xF9, 0x35, 0x00, 0x00, + 0x44, 0xFF, 0x54, 0x0B, 0xD7, 0xDF, 0x11, 0x00, 0x44, 0xFF, 0x54, 0x00, 0x2D, 0xF7, 0xB2, 0x01, + 0x44, 0xFF, 0x54, 0x00, 0x00, 0x67, 0xFF, 0x76, 0x44, 0xFF, 0x54, 0x00, 0x00, 0x00, 0x00, 0x44, + 0xFF, 0x54, 0x00, 0x00, 0x00, 0x00, 0x44, 0xFF, 0x54, 0x00, 0x00, 0x00, 0x00, 0x44, 0xFF, 0x54, + 0x00, 0x00, 0x00, 0x00, 0x44, 0xFF, 0x54, 0x00, 0x00, 0x00, 0x00, 0x44, 0xFF, 0x54, 0x00, 0x00, + 0x00, 0x00, 0x44, 0xFF, 0x79, 0x38, 0x38, 0x38, 0x15, 0x44, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x60, + 0x44, 0xFF, 0xF6, 0x09, 0x00, 0x00, 0xD5, 0xFF, 0x6C, 0x44, 0xFF, 0xEB, 0x4A, 0x00, 0x1D, 0xF6, + 0xF5, 0x6C, 0x44, 0xFF, 0xB3, 0x93, 0x00, 0x66, 0xBB, 0xF9, 0x6C, 0x44, 0xFF, 0x71, 0xDD, 0x00, + 0xAF, 0x74, 0xFC, 0x6C, 0x44, 0xFF, 0x2E, 0xF7, 0x2C, 0xF2, 0x29, 0xFC, 0x6C, 0x44, 0xFF, 0x24, + 0xB7, 0xA5, 0xDE, 0x00, 0xFC, 0x6C, 0x44, 0xFF, 0x24, 0x6C, 0xFC, 0x93, 0x00, 0xFC, 0x6C, 0x44, + 0xFF, 0x24, 0x21, 0xFF, 0x49, 0x00, 0xFC, 0x6C, 0x44, 0xFF, 0xCE, 0x01, 0x00, 0x34, 0xFF, 0x34, + 0x44, 0xFF, 0xFE, 0x5A, 0x00, 0x34, 0xFF, 0x34, 0x44, 0xFF, 0xB4, 0xDD, 0x05, 0x34, 0xFF, 0x34, + 0x44, 0xFF, 0x3E, 0xF6, 0x6D, 0x34, 0xFF, 0x34, 0x44, 0xFF, 0x24, 0x88, 0xEA, 0x40, 0xFF, 0x34, + 0x44, 0xFF, 0x24, 0x10, 0xEE, 0xA8, 0xFF, 0x34, 0x44, 0xFF, 0x24, 0x00, 0x76, 0xFD, 0xFF, 0x34, + 0x44, 0xFF, 0x24, 0x00, 0x08, 0xE3, 0xFF, 0x34, 0x00, 0x19, 0xA4, 0xEC, 0xF9, 0xD3, 0x5F, 0x00, + 0x00, 0x02, 0xCE, 0xEF, 0x6D, 0x4E, 0xAA, 0xFF, 0x64, 0x00, 0x4E, 0xFF, 0x5C, 0x00, 0x00, 0x01, + 0xD3, 0xDF, 0x00, 0x7A, 0xFF, 0x1B, 0x00, 0x00, 0x00, 0x93, 0xFF, 0x0E, 0x82, 0xFF, 0x1D, 0x00, + 0x00, 0x00, 0x95, 0xFF, 0x0A, 0x50, 0xFF, 0x62, 0x00, 0x00, 0x01, 0xD4, 0xD6, 0x00, 0x04, 0xD0, + 0xF1, 0x6F, 0x4E, 0xAA, 0xFF, 0x58, 0x00, 0x00, 0x15, 0xA0, 0xEC, 0xF7, 0xCD, 0x55, 0x00, 0x00, + 0x44, 0xFF, 0xFF, 0xFF, 0xF3, 0xBB, 0x24, 0x44, 0xFF, 0x7C, 0x3C, 0x57, 0xED, 0xC1, 0x44, 0xFF, + 0x54, 0x00, 0x00, 0xAE, 0xEE, 0x44, 0xFF, 0x79, 0x38, 0x54, 0xEF, 0xBA, 0x44, 0xFF, 0xFF, 0xFF, + 0xF8, 0xBA, 0x1E, 0x44, 0xFF, 0x54, 0x00, 0x00, 0x00, 0x00, 0x44, 0xFF, 0x54, 0x00, 0x00, 0x00, + 0x00, 0x44, 0xFF, 0x54, 0x00, 0x00, 0x00, 0x00, 0x00, 0x17, 0xA3, 0xEC, 0xF9, 0xD2, 0x5D, 0x00, + 0x00, 0x02, 0xCB, 0xF0, 0x6E, 0x4F, 0xAC, 0xFF, 0x60, 0x00, 0x4D, 0xFF, 0x5E, 0x00, 0x00, 0x01, + 0xD5, 0xDB, 0x00, 0x79, 0xFF, 0x1C, 0x00, 0x00, 0x00, 0x94, 0xFF, 0x0D, 0x7B, 0xFF, 0x1B, 0x00, + 0x00, 0x00, 0x94, 0xFE, 0x09, 0x4F, 0xFF, 0x5D, 0x00, 0x00, 0x00, 0xD0, 0xDB, 0x00, 0x03, 0xD4, + 0xEC, 0x60, 0x3E, 0x9D, 0xFF, 0x64, 0x00, 0x00, 0x15, 0xA6, 0xF0, 0xFF, 0xE5, 0x6E, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x20, 0xFC, 0xB4, 0x38, 0x15, 0x00, 0x00, 0x00, 0x00, 0x00, 0x64, 0xE9, 0xF2, + 0x4D, 0x00, 0x44, 0xFF, 0xFF, 0xFF, 0xFB, 0xDA, 0x60, 0x00, 0x44, 0xFF, 0x7C, 0x3C, 0x42, 0xB4, + 0xFD, 0x26, 0x44, 0xFF, 0x54, 0x00, 0x00, 0x4A, 0xFF, 0x4D, 0x44, 0xFF, 0x7C, 0x3C, 0x42, 0xB9, + 0xF8, 0x1A, 0x44, 0xFF, 0xFF, 0xFF, 0xFF, 0xE7, 0x40, 0x00, 0x44, 0xFF, 0x54, 0x00, 0x89, 0xFC, + 0x30, 0x00, 0x44, 0xFF, 0x54, 0x00, 0x0C, 0xE5, 0xC8, 0x02, 0x44, 0xFF, 0x54, 0x00, 0x00, 0x5B, + 0xFF, 0x68, 0x00, 0x65, 0xD8, 0xF8, 0xED, 0xAC, 0x13, 0x2C, 0xFF, 0x8D, 0x27, 0x54, 0xEF, 0x77, + 0x47, 0xFF, 0x73, 0x00, 0x00, 0x00, 0x00, 0x08, 0xCC, 0xFF, 0xE3, 0xA3, 0x51, 0x00, 0x00, 0x03, + 0x48, 0x8A, 0xD2, 0xFF, 0x88, 0x00, 0x00, 0x00, 0x00, 0x01, 0xC3, 0xDA, 0x6B, 0xED, 0x68, 0x34, + 0x4F, 0xE7, 0xB2, 0x0A, 0x92, 0xE3, 0xF9, 0xE9, 0xA8, 0x1A, 0xE0, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0x98, 0x31, 0x38, 0x8F, 0xFF, 0x57, 0x38, 0x21, 0x00, 0x00, 0x70, 0xFF, 0x28, 0x00, 0x00, 0x00, + 0x00, 0x70, 0xFF, 0x28, 0x00, 0x00, 0x00, 0x00, 0x70, 0xFF, 0x28, 0x00, 0x00, 0x00, 0x00, 0x70, + 0xFF, 0x28, 0x00, 0x00, 0x00, 0x00, 0x70, 0xFF, 0x28, 0x00, 0x00, 0x00, 0x00, 0x70, 0xFF, 0x28, + 0x00, 0x00, 0x58, 0xFF, 0x40, 0x00, 0x00, 0x50, 0xFF, 0x44, 0x58, 0xFF, 0x40, 0x00, 0x00, 0x50, + 0xFF, 0x44, 0x58, 0xFF, 0x40, 0x00, 0x00, 0x50, 0xFF, 0x44, 0x58, 0xFF, 0x40, 0x00, 0x00, 0x50, + 0xFF, 0x44, 0x57, 0xFF, 0x40, 0x00, 0x00, 0x50, 0xFF, 0x43, 0x41, 0xFF, 0x5A, 0x00, 0x00, 0x6F, + 0xFF, 0x27, 0x0D, 0xE7, 0xD9, 0x51, 0x5A, 0xE6, 0xCE, 0x02, 0x00, 0x32, 0xBF, 0xF1, 0xE9, 0xA9, + 0x1D, 0x00, 0xC0, 0xE1, 0x00, 0x00, 0x00, 0x87, 0xFD, 0x19, 0x66, 0xFF, 0x2F, 0x00, 0x00, 0xD3, + 0xBD, 0x00, 0x12, 0xFA, 0x7C, 0x00, 0x1F, 0xFF, 0x64, 0x00, 0x00, 0xB3, 0xC9, 0x00, 0x6C, 0xFA, + 0x11, 0x00, 0x00, 0x5A, 0xFE, 0x18, 0xB8, 0xB2, 0x00, 0x00, 0x00, 0x0B, 0xF5, 0x6A, 0xF8, 0x59, + 0x00, 0x00, 0x00, 0x00, 0xA7, 0xE0, 0xF5, 0x0B, 0x00, 0x00, 0x00, 0x00, 0x4D, 0xFF, 0xA8, 0x00, + 0x00, 0x00, 0xDE, 0xB4, 0x00, 0x00, 0xC7, 0xFF, 0x32, 0x00, 0x55, 0xFF, 0x40, 0xA0, 0xE7, 0x00, + 0x05, 0xF7, 0xF7, 0x67, 0x00, 0x85, 0xF9, 0x08, 0x62, 0xFF, 0x1A, 0x31, 0xFC, 0xAE, 0x9D, 0x00, + 0xB6, 0xC3, 0x00, 0x24, 0xFF, 0x4C, 0x66, 0xD0, 0x74, 0xD2, 0x00, 0xE7, 0x84, 0x00, 0x00, 0xE5, + 0x7F, 0x9A, 0x99, 0x3D, 0xFC, 0x22, 0xFF, 0x46, 0x00, 0x00, 0xA8, 0xB1, 0xCB, 0x62, 0x09, 0xFB, + 0x82, 0xFB, 0x0C, 0x00, 0x00, 0x6A, 0xE4, 0xF6, 0x2A, 0x00, 0xCD, 0xDD, 0xC9, 0x00, 0x00, 0x00, + 0x2B, 0xFF, 0xF1, 0x02, 0x00, 0x94, 0xFF, 0x8B, 0x00, 0x00, 0x5F, 0xFF, 0x4B, 0x00, 0x0C, 0xE4, + 0xB6, 0x00, 0x00, 0xBC, 0xDB, 0x07, 0x89, 0xF7, 0x23, 0x00, 0x00, 0x23, 0xF6, 0x9D, 0xFA, 0x80, + 0x00, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xE3, 0x08, 0x00, 0x00, 0x00, 0x00, 0xB8, 0xFA, 0xFA, 0x28, + 0x00, 0x00, 0x00, 0x59, 0xFF, 0x4D, 0xE1, 0xBC, 0x00, 0x00, 0x10, 0xE8, 0xA9, 0x00, 0x52, 0xFF, + 0x55, 0x00, 0x98, 0xF4, 0x1C, 0x00, 0x00, 0xB8, 0xE3, 0x0B, 0x8B, 0xF8, 0x18, 0x00, 0x00, 0xBC, + 0xDD, 0x05, 0x11, 0xEF, 0x85, 0x00, 0x31, 0xFF, 0x59, 0x00, 0x00, 0x76, 0xED, 0x0B, 0xA8, 0xCE, + 0x01, 0x00, 0x00, 0x08, 0xE3, 0x8E, 0xFC, 0x46, 0x00, 0x00, 0x00, 0x00, 0x61, 0xFF, 0xBC, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x1C, 0xFF, 0x78, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1C, 0xFF, 0x78, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x1C, 0xFF, 0x78, 0x00, 0x00, 0x00, 0x44, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0x30, 0x0F, 0x3C, 0x3C, 0x3F, 0xE0, 0xD9, 0x0A, 0x00, 0x00, 0x00, 0x80, 0xF9, 0x32, 0x00, 0x00, + 0x00, 0x3C, 0xFC, 0x73, 0x00, 0x00, 0x00, 0x11, 0xE1, 0xB9, 0x01, 0x00, 0x00, 0x00, 0xAC, 0xE9, + 0x18, 0x00, 0x00, 0x00, 0x65, 0xFF, 0x84, 0x3C, 0x3C, 0x3C, 0x18, 0xAC, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0x68, 0x00, 0x73, 0xEB, 0xE8, 0x7C, 0x00, 0x2B, 0xFF, 0x78, 0x65, 0xFF, 0x3B, 0x70, 0xFE, + 0x07, 0x00, 0xF0, 0x87, 0x89, 0xF3, 0x00, 0x00, 0xDB, 0xA0, 0x85, 0xF2, 0x00, 0x00, 0xDC, 0x9F, + 0x6A, 0xFE, 0x07, 0x00, 0xF0, 0x84, 0x1E, 0xFD, 0x78, 0x68, 0xFF, 0x34, 0x00, 0x62, 0xE2, 0xF0, + 0x7E, 0x00, 0x0F, 0x82, 0xE9, 0xFF, 0x14, 0x00, 0x3C, 0xB4, 0xA8, 0xFF, 0x14, 0x00, 0x00, 0x00, + 0x70, 0xFF, 0x14, 0x00, 0x00, 0x00, 0x70, 0xFF, 0x14, 0x00, 0x00, 0x00, 0x70, 0xFF, 0x14, 0x00, + 0x00, 0x00, 0x70, 0xFF, 0x14, 0x00, 0x0A, 0x20, 0x82, 0xFF, 0x31, 0x1A, 0x50, 0xFF, 0xFF, 0xFF, + 0xFF, 0xD0, 0x06, 0x9B, 0xEF, 0xEE, 0xA0, 0x06, 0x60, 0xF9, 0x6B, 0x60, 0xFF, 0x69, 0x00, 0x00, + 0x00, 0x0B, 0xFE, 0x7E, 0x00, 0x00, 0x01, 0x9F, 0xFC, 0x2F, 0x00, 0x06, 0xA8, 0xFD, 0x63, 0x00, + 0x00, 0xAB, 0xF5, 0x4B, 0x00, 0x00, 0x5C, 0xFF, 0x8F, 0x3C, 0x3C, 0x28, 0xA0, 0xFF, 0xFF, 0xFF, + 0xFF, 0xAC, 0x09, 0xA0, 0xEF, 0xF0, 0xA9, 0x0B, 0x6F, 0xF7, 0x60, 0x69, 0xFF, 0x71, 0x00, 0x00, + 0x01, 0x39, 0xFF, 0x5E, 0x00, 0x00, 0xCC, 0xFE, 0x92, 0x01, 0x00, 0x00, 0x2D, 0x6D, 0xFD, 0x64, + 0x00, 0x00, 0x00, 0x00, 0xDE, 0xAD, 0x8D, 0xF0, 0x5A, 0x5E, 0xFC, 0x83, 0x15, 0xAF, 0xF1, 0xEF, + 0xA6, 0x0C, 0x00, 0x00, 0x0F, 0xE6, 0xFF, 0x0C, 0x00, 0x00, 0x00, 0x99, 0xE7, 0xFF, 0x0C, 0x00, + 0x00, 0x3D, 0xE9, 0x77, 0xFF, 0x0C, 0x00, 0x07, 0xD7, 0x5A, 0x64, 0xFF, 0x0C, 0x00, 0x82, 0xB6, + 0x00, 0x64, 0xFF, 0x0C, 0x00, 0xD4, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x10, 0x1A, 0x20, 0x20, 0x77, + 0xFF, 0x2A, 0x02, 0x00, 0x00, 0x00, 0x64, 0xFF, 0x0C, 0x00, 0x34, 0xFF, 0xFF, 0xFF, 0xFF, 0x60, + 0x42, 0xFF, 0x44, 0x20, 0x20, 0x0C, 0x50, 0xFF, 0x1D, 0x00, 0x00, 0x00, 0x5F, 0xFF, 0xC9, 0xF5, + 0xBB, 0x19, 0x4B, 0xB4, 0x68, 0x58, 0xF7, 0x9D, 0x00, 0x00, 0x00, 0x00, 0xC6, 0xC4, 0x75, 0xF4, + 0x5F, 0x56, 0xF7, 0x8E, 0x0C, 0xA5, 0xF1, 0xED, 0xA2, 0x0D, 0x00, 0x55, 0xE2, 0xF5, 0xA5, 0x06, + 0x1E, 0xF9, 0x91, 0x5A, 0xEE, 0x4D, 0x6D, 0xFA, 0x0B, 0x00, 0x00, 0x00, 0x8F, 0xED, 0xAB, 0xF2, + 0xBC, 0x15, 0x8E, 0xFF, 0x5C, 0x3F, 0xF7, 0x8B, 0x72, 0xFF, 0x06, 0x00, 0xD4, 0xAD, 0x22, 0xFD, + 0x80, 0x4E, 0xFA, 0x7B, 0x00, 0x5B, 0xDE, 0xF1, 0xA4, 0x0A, 0x88, 0xFF, 0xFF, 0xFF, 0xFF, 0xA4, + 0x1F, 0x3C, 0x3C, 0x47, 0xF4, 0x72, 0x00, 0x00, 0x00, 0x8D, 0xDB, 0x06, 0x00, 0x00, 0x25, 0xFB, + 0x5C, 0x00, 0x00, 0x00, 0x9A, 0xE1, 0x03, 0x00, 0x00, 0x08, 0xF0, 0x8C, 0x00, 0x00, 0x00, 0x3B, + 0xFF, 0x4C, 0x00, 0x00, 0x00, 0x5C, 0xFF, 0x31, 0x00, 0x00, 0x06, 0x9F, 0xEE, 0xF2, 0xB3, 0x12, + 0x60, 0xFF, 0x5A, 0x3B, 0xFE, 0x81, 0x4F, 0xFF, 0x4E, 0x23, 0xFD, 0x6F, 0x00, 0xA5, 0xFF, 0xFF, + 0xBE, 0x04, 0x5C, 0xFE, 0x44, 0x32, 0xF4, 0x7F, 0x9E, 0xEE, 0x00, 0x00, 0xCF, 0xBE, 0x74, 0xFE, + 0x44, 0x2F, 0xF4, 0x93, 0x07, 0x9B, 0xEC, 0xF0, 0xAC, 0x12, 0x04, 0x96, 0xEC, 0xE6, 0x7A, 0x00, + 0x68, 0xFF, 0x63, 0x6D, 0xFF, 0x40, 0x96, 0xEF, 0x00, 0x00, 0xEE, 0x8F, 0x70, 0xFF, 0x53, 0x51, + 0xFD, 0xA9, 0x0A, 0xAF, 0xF3, 0xC0, 0xDE, 0xAB, 0x00, 0x00, 0x00, 0x02, 0xEC, 0x8A, 0x47, 0xEE, + 0x61, 0x80, 0xFF, 0x38, 0x04, 0xA1, 0xF4, 0xE9, 0x6D, 0x00, 0xF0, 0x9C, 0xF0, 0x9C, 0x00, 0x00, + 0x00, 0x00, 0xF0, 0x9C, 0xF0, 0x9C, 0x40, 0xFF, 0x4C, 0x40, 0xFF, 0x4C, 0x90, 0xFF, 0xFF, 0x38, + 0x2D, 0x50, 0x50, 0x11, 0x1F, 0xC5, 0xD7, 0x68, 0x74, 0x6B, 0x0C, 0xD9, 0x22, 0xCC, 0xD8, 0x69, + 0x00, 0x6E, 0xDA, 0x00, 0xA0, 0xA9, 0x00, 0xD2, 0x78, 0x08, 0xFB, 0x47, 0x36, 0xFF, 0x17, 0x68, + 0xE5, 0x00, 0x9A, 0xB4, 0x00, 0xCB, 0x84, 0x00, +}; + +static const CoordsGlyphAA s_navSubGlyphs[42] PROGMEM = { + { 8, 8, 0, 0, 8, 0 }, // 'A' + { 8, 8, 0, 0, 8, 64 }, // 'B' + { 8, 8, 0, 0, 8, 128 }, // 'C' + { 8, 8, 0, 0, 8, 192 }, // 'D' + { 7, 8, 0, 0, 7, 256 }, // 'E' + { 7, 8, 0, 0, 6, 312 }, // 'F' + { 8, 8, 0, 0, 8, 368 }, // 'G' + { 8, 8, 0, 0, 8, 432 }, // 'H' + { 3, 8, 0, 0, 3, 496 }, // 'I' + { 6, 8, 0, 0, 6, 520 }, // 'J' + { 8, 8, 0, 0, 8, 568 }, // 'K' + { 7, 8, 0, 0, 6, 632 }, // 'L' + { 9, 8, 0, 0, 9, 688 }, // 'M' + { 8, 8, 0, 0, 8, 760 }, // 'N' + { 9, 8, 0, 0, 8, 824 }, // 'O' + { 7, 8, 0, 0, 7, 896 }, // 'P' + { 9, 10, 0, 0, 8, 952 }, // 'Q' + { 8, 8, 0, 0, 8, 1042 }, // 'R' + { 7, 8, 0, 0, 7, 1106 }, // 'S' + { 7, 8, 0, 0, 6, 1162 }, // 'T' + { 8, 8, 0, 0, 8, 1218 }, // 'U' + { 8, 8, 0, 0, 7, 1282 }, // 'V' + { 11, 8, 0, 0, 10, 1346 }, // 'W' + { 8, 8, 0, 0, 7, 1434 }, // 'X' + { 8, 8, 0, 0, 7, 1498 }, // 'Y' + { 7, 8, 0, 0, 6, 1562 }, // 'Z' + { 6, 8, 0, 0, 6, 1618 }, // '0' + { 6, 8, 0, 0, 6, 1666 }, // '1' + { 6, 8, 0, 0, 6, 1714 }, // '2' + { 6, 8, 0, 0, 6, 1762 }, // '3' + { 7, 8, 0, 0, 6, 1810 }, // '4' + { 6, 8, 0, 0, 6, 1866 }, // '5' + { 6, 8, 0, 0, 6, 1914 }, // '6' + { 6, 8, 0, 0, 6, 1962 }, // '7' + { 6, 8, 0, 0, 6, 2010 }, // '8' + { 6, 8, 0, 0, 6, 2058 }, // '9' + { 2, 6, 1, 2, 4, 2106 }, // ':' + { 3, 2, 0, 6, 3, 2118 }, // '.' + { 4, 2, 0, 4, 4, 2124 }, // '-' + { 4, 3, 0, 0, 4, 2132 }, // '°' + { 3, 8, 0, 0, 3, 2144 }, // '/' + { 0, 0, 0, 0, 3, 2168 }, // ' ' +}; + +// Coordinates Value Font (20pt Bold AA: Digits, Sign, Dot, Degree) - 1618 bytes +static const uint8_t s_coordsValueAlpha[1618] PROGMEM = { + 0x00, 0x00, 0x13, 0x8E, 0xDD, 0xF8, 0xE6, 0x9D, 0x18, 0x00, 0x00, 0x00, 0x0A, 0xCF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xDD, 0x11, 0x00, 0x00, 0x71, 0xFF, 0xFF, 0x8A, 0x32, 0x79, 0xFF, 0xFF, 0x86, + 0x00, 0x00, 0xCB, 0xFF, 0xDE, 0x01, 0x00, 0x00, 0xCF, 0xFF, 0xE1, 0x00, 0x07, 0xFA, 0xFF, 0xA8, + 0x00, 0x00, 0x00, 0x98, 0xFF, 0xFF, 0x18, 0x1E, 0xFF, 0xFF, 0x92, 0x00, 0x00, 0x00, 0x80, 0xFF, + 0xFF, 0x38, 0x2D, 0xFF, 0xFF, 0x8B, 0x00, 0x00, 0x00, 0x78, 0xFF, 0xFF, 0x46, 0x2F, 0xFF, 0xFF, + 0x8B, 0x00, 0x00, 0x00, 0x78, 0xFF, 0xFF, 0x45, 0x22, 0xFF, 0xFF, 0x93, 0x00, 0x00, 0x00, 0x81, + 0xFF, 0xFF, 0x37, 0x04, 0xFA, 0xFF, 0xAC, 0x00, 0x00, 0x00, 0x9A, 0xFF, 0xFF, 0x14, 0x00, 0xC6, + 0xFF, 0xE2, 0x01, 0x00, 0x00, 0xD4, 0xFF, 0xD9, 0x00, 0x00, 0x69, 0xFF, 0xFF, 0x8C, 0x32, 0x82, + 0xFF, 0xFF, 0x79, 0x00, 0x00, 0x05, 0xC6, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xCF, 0x0A, 0x00, 0x00, + 0x00, 0x0C, 0x8C, 0xE1, 0xF9, 0xE3, 0x91, 0x10, 0x00, 0x00, 0x00, 0x00, 0x4D, 0xD3, 0xFF, 0xFF, + 0x68, 0x00, 0x00, 0x00, 0x36, 0xC4, 0xFF, 0xFF, 0xFF, 0xFF, 0x68, 0x00, 0x00, 0x00, 0xA0, 0xFF, + 0xE6, 0xAC, 0xFF, 0xFF, 0x68, 0x00, 0x00, 0x00, 0x87, 0x70, 0x08, 0x54, 0xFF, 0xFF, 0x68, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x54, 0xFF, 0xFF, 0x68, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x54, + 0xFF, 0xFF, 0x68, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x54, 0xFF, 0xFF, 0x68, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x54, 0xFF, 0xFF, 0x68, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x54, 0xFF, 0xFF, + 0x68, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x54, 0xFF, 0xFF, 0x68, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x54, 0xFF, 0xFF, 0x68, 0x00, 0x00, 0x00, 0x08, 0x0C, 0x0C, 0x5C, 0xFF, 0xFF, 0x6F, 0x0C, + 0x0C, 0x06, 0xBC, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x90, 0xBC, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x90, 0x00, 0x00, 0x2A, 0xA6, 0xDD, 0xF9, 0xEB, 0xB6, 0x42, 0x00, + 0x00, 0x00, 0x33, 0xF8, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0x57, 0x00, 0x00, 0xCB, 0xFF, 0xFF, + 0x79, 0x39, 0x72, 0xFE, 0xFF, 0xE0, 0x00, 0x0F, 0xE1, 0xF1, 0xB7, 0x00, 0x00, 0x00, 0xC0, 0xFF, + 0xFF, 0x13, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xC8, 0xFF, 0xFE, 0x0A, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x4B, 0xFF, 0xFF, 0xBE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x43, 0xF4, 0xFF, + 0xF8, 0x2B, 0x00, 0x00, 0x00, 0x00, 0x00, 0x60, 0xFA, 0xFF, 0xF9, 0x52, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x80, 0xFF, 0xFF, 0xED, 0x41, 0x00, 0x00, 0x00, 0x00, 0x00, 0x84, 0xFF, 0xFF, 0xD7, 0x22, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x58, 0xFF, 0xFF, 0xCA, 0x12, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0F, + 0xEC, 0xFF, 0xFF, 0x5E, 0x40, 0x40, 0x40, 0x40, 0x40, 0x15, 0x4F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0x54, 0x50, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x54, + 0x00, 0x00, 0x37, 0xAF, 0xEA, 0xF9, 0xDE, 0xAA, 0x32, 0x00, 0x00, 0x00, 0x51, 0xFC, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFD, 0x51, 0x00, 0x07, 0xEB, 0xFF, 0xF8, 0x6B, 0x3A, 0x87, 0xFF, 0xFF, 0xDA, + 0x00, 0x30, 0xDB, 0xEE, 0x91, 0x00, 0x00, 0x00, 0xD4, 0xFF, 0xFF, 0x04, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0xD6, 0xFF, 0xE7, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x32, 0x91, 0xFF, 0xFF, + 0x6F, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xC8, 0x57, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0xFF, 0xFF, 0xFF, 0xF5, 0xAE, 0x21, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x27, 0x62, 0xF1, + 0xFF, 0xDF, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xFF, 0x4D, 0x5F, 0xDA, + 0xEF, 0x69, 0x00, 0x00, 0x00, 0x87, 0xFF, 0xFF, 0x5D, 0x33, 0xFF, 0xFF, 0xF0, 0x6D, 0x44, 0x6E, + 0xF3, 0xFF, 0xFE, 0x26, 0x00, 0x8D, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x91, 0x00, 0x00, + 0x00, 0x46, 0xB5, 0xE0, 0xFA, 0xEF, 0xBF, 0x5B, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x39, + 0xFD, 0xFF, 0xFF, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0xD5, 0xFF, 0xFF, 0xFF, 0x30, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x82, 0xFF, 0xF1, 0xFF, 0xFF, 0x30, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x2D, 0xFA, 0xF6, 0x89, 0xFF, 0xFF, 0x30, 0x00, 0x00, 0x00, 0x00, 0x03, 0xCA, 0xFF, 0x78, + 0x6F, 0xFF, 0xFF, 0x30, 0x00, 0x00, 0x00, 0x00, 0x73, 0xFF, 0xCD, 0x04, 0x70, 0xFF, 0xFF, 0x30, + 0x00, 0x00, 0x00, 0x22, 0xF5, 0xFB, 0x30, 0x00, 0x70, 0xFF, 0xFF, 0x30, 0x00, 0x00, 0x00, 0xBD, + 0xFF, 0x85, 0x00, 0x00, 0x70, 0xFF, 0xFF, 0x30, 0x00, 0x00, 0x63, 0xFF, 0xD7, 0x07, 0x00, 0x00, + 0x70, 0xFF, 0xFF, 0x30, 0x00, 0x00, 0xB4, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0x04, 0xB4, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x04, 0x08, 0x0C, + 0x0C, 0x0C, 0x0C, 0x0C, 0x76, 0xFF, 0xFF, 0x39, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x70, 0xFF, 0xFF, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x70, 0xFF, 0xFF, 0x30, + 0x00, 0x00, 0x00, 0x88, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xC4, 0x00, 0x00, 0x97, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xC4, 0x00, 0x00, 0xA6, 0xFF, 0xF5, 0x0C, 0x0C, 0x0C, 0x0C, + 0x0C, 0x09, 0x00, 0x00, 0xB6, 0xFF, 0xE6, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xC5, + 0xFF, 0xD7, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xD4, 0xFF, 0xD4, 0x8D, 0xE0, 0xF8, + 0xD6, 0x73, 0x02, 0x00, 0x00, 0xE4, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xA0, 0x00, 0x00, + 0xF3, 0xFF, 0xFC, 0x86, 0x44, 0x71, 0xF3, 0xFF, 0xFF, 0x35, 0x00, 0x1B, 0x1C, 0x15, 0x00, 0x00, + 0x00, 0x77, 0xFF, 0xFF, 0x79, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x4A, 0xFF, 0xFF, 0x8A, + 0x35, 0xD7, 0xED, 0x8A, 0x00, 0x00, 0x00, 0x78, 0xFF, 0xFF, 0x6D, 0x07, 0xF0, 0xFF, 0xF9, 0x6D, + 0x35, 0x69, 0xF2, 0xFF, 0xF9, 0x1E, 0x00, 0x51, 0xFE, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0x69, + 0x00, 0x00, 0x00, 0x36, 0xAE, 0xE0, 0xFA, 0xEA, 0xB0, 0x3E, 0x00, 0x00, 0x00, 0x00, 0x02, 0x69, + 0xCF, 0xF6, 0xE8, 0xB1, 0x2D, 0x00, 0x00, 0x00, 0x00, 0xA2, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, + 0x23, 0x00, 0x00, 0x51, 0xFF, 0xFF, 0xB3, 0x3A, 0x6F, 0xFF, 0xFF, 0xA3, 0x00, 0x00, 0xBC, 0xFF, + 0xE8, 0x09, 0x00, 0x00, 0xB3, 0xD6, 0xA2, 0x00, 0x05, 0xFA, 0xFF, 0x9D, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x28, 0xFF, 0xFF, 0x7C, 0x7D, 0xE0, 0xF9, 0xDB, 0x74, 0x01, 0x00, 0x3A, 0xFF, + 0xFF, 0xE5, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x8F, 0x00, 0x40, 0xFF, 0xFF, 0xFE, 0x61, 0x10, 0x43, + 0xED, 0xFF, 0xFC, 0x1B, 0x34, 0xFF, 0xFF, 0xB8, 0x00, 0x00, 0x00, 0x83, 0xFF, 0xFF, 0x54, 0x12, + 0xFF, 0xFF, 0xA9, 0x00, 0x00, 0x00, 0x64, 0xFF, 0xFF, 0x62, 0x00, 0xD6, 0xFF, 0xE2, 0x04, 0x00, + 0x00, 0x8F, 0xFF, 0xFF, 0x48, 0x00, 0x71, 0xFF, 0xFF, 0xA3, 0x34, 0x60, 0xF5, 0xFF, 0xEF, 0x0C, + 0x00, 0x05, 0xBE, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0x5F, 0x00, 0x00, 0x00, 0x08, 0x7B, 0xD6, + 0xF8, 0xF1, 0xBC, 0x45, 0x00, 0x00, 0x24, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0x40, 0x24, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x40, 0x09, 0x40, 0x40, 0x40, + 0x40, 0x40, 0x40, 0xA9, 0xFF, 0xF2, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x27, 0xF7, 0xFF, + 0x71, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xC3, 0xFF, 0xD1, 0x04, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x63, 0xFF, 0xFF, 0x45, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0E, 0xEA, 0xFF, 0xC2, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7E, 0xFF, 0xFF, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x07, 0xE8, 0xFF, 0xD9, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x52, 0xFF, 0xFF, 0x84, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xA4, 0xFF, 0xFF, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0xDF, 0xFF, 0xF4, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0A, 0xFF, 0xFF, 0xCF, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1C, 0xFF, 0xFF, 0xBF, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x3A, 0xB1, 0xEA, 0xFB, 0xEE, 0xBB, 0x4E, 0x00, 0x00, 0x00, 0x4B, 0xFC, 0xFF, 0xFF, + 0xF6, 0xFF, 0xFF, 0xFF, 0x6B, 0x00, 0x00, 0xD5, 0xFF, 0xFD, 0x44, 0x00, 0x2F, 0xF4, 0xFF, 0xF0, + 0x05, 0x04, 0xFF, 0xFF, 0xD1, 0x00, 0x00, 0x00, 0xB5, 0xFF, 0xFF, 0x23, 0x00, 0xED, 0xFF, 0xD7, + 0x00, 0x00, 0x00, 0xBA, 0xFF, 0xFE, 0x0F, 0x00, 0x85, 0xFF, 0xFF, 0x5C, 0x0A, 0x43, 0xF9, 0xFF, + 0xA4, 0x00, 0x00, 0x02, 0x83, 0xEF, 0xFF, 0xFF, 0xFF, 0xF5, 0x94, 0x09, 0x00, 0x00, 0x1E, 0xB2, + 0xF9, 0xFF, 0xF5, 0xFF, 0xFE, 0xC9, 0x36, 0x00, 0x06, 0xD9, 0xFF, 0xE4, 0x29, 0x00, 0x1D, 0xCE, + 0xFF, 0xF2, 0x1A, 0x40, 0xFF, 0xFF, 0x85, 0x00, 0x00, 0x00, 0x64, 0xFF, 0xFF, 0x69, 0x53, 0xFF, + 0xFF, 0x88, 0x00, 0x00, 0x00, 0x69, 0xFF, 0xFF, 0x7A, 0x20, 0xFD, 0xFF, 0xE5, 0x2C, 0x00, 0x1C, + 0xD0, 0xFF, 0xFF, 0x44, 0x00, 0x88, 0xFF, 0xFF, 0xFF, 0xF8, 0xFF, 0xFF, 0xFF, 0xAE, 0x00, 0x00, + 0x00, 0x54, 0xBA, 0xEC, 0xFC, 0xF1, 0xC5, 0x69, 0x03, 0x00, 0x00, 0x00, 0x37, 0xAF, 0xE9, 0xFA, + 0xE3, 0x9A, 0x18, 0x00, 0x00, 0x00, 0x50, 0xFB, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xE0, 0x17, 0x00, + 0x04, 0xE6, 0xFF, 0xFD, 0x74, 0x36, 0x8F, 0xFF, 0xFF, 0x98, 0x00, 0x35, 0xFF, 0xFF, 0xAC, 0x00, + 0x00, 0x00, 0xC9, 0xFF, 0xF1, 0x06, 0x4B, 0xFF, 0xFF, 0x84, 0x00, 0x00, 0x00, 0x90, 0xFF, 0xFF, + 0x2E, 0x39, 0xFF, 0xFF, 0xA6, 0x00, 0x00, 0x00, 0xA6, 0xFF, 0xFF, 0x4F, 0x09, 0xF1, 0xFF, 0xFA, + 0x59, 0x17, 0x5F, 0xFB, 0xFF, 0xFF, 0x5B, 0x00, 0x72, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xE3, 0xFF, + 0xFF, 0x56, 0x00, 0x00, 0x65, 0xD6, 0xF9, 0xE3, 0x89, 0x68, 0xFF, 0xFF, 0x45, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x87, 0xFF, 0xFF, 0x1D, 0x01, 0xA6, 0xD6, 0xA7, 0x00, 0x00, 0x03, 0xD8, + 0xFF, 0xD9, 0x00, 0x00, 0xAC, 0xFF, 0xFD, 0x69, 0x33, 0xA3, 0xFF, 0xFF, 0x6F, 0x00, 0x00, 0x29, + 0xF3, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xBA, 0x04, 0x00, 0x00, 0x00, 0x30, 0xB2, 0xE8, 0xF8, 0xD6, + 0x78, 0x06, 0x00, 0x00, 0xA4, 0xFF, 0xFF, 0x30, 0xA4, 0xFF, 0xFF, 0x30, 0xA4, 0xFF, 0xFF, 0x30, + 0x38, 0xFF, 0xFF, 0xFF, 0xFF, 0xDC, 0x38, 0xFF, 0xFF, 0xFF, 0xFF, 0xDC, 0x15, 0x64, 0x64, 0x64, + 0x64, 0x55, 0x00, 0x0D, 0x98, 0xEE, 0xE8, 0x94, 0x0C, 0x00, 0x00, 0xAC, 0xF9, 0x93, 0x93, 0xFA, + 0xA8, 0x00, 0x0F, 0xFE, 0x86, 0x00, 0x00, 0x8A, 0xFD, 0x0C, 0x0A, 0xFC, 0x86, 0x00, 0x00, 0x8B, + 0xFD, 0x0C, 0x00, 0xA6, 0xF9, 0x8F, 0x90, 0xFA, 0xA7, 0x00, 0x00, 0x0C, 0x94, 0xE9, 0xE9, 0x95, + 0x0C, 0x00, +}; + +static const CoordsGlyphAA s_coordsValueGlyphs[14] PROGMEM = { + { 11, 14, 0, 0, 11, 0 }, // '0' + { 10, 14, 1, 0, 11, 154 }, // '1' + { 11, 14, 0, 0, 11, 294 }, // '2' + { 11, 14, 0, 0, 11, 448 }, // '3' + { 12, 14, 0, 0, 11, 602 }, // '4' + { 11, 14, 0, 0, 11, 770 }, // '5' + { 11, 14, 0, 0, 11, 924 }, // '6' + { 11, 14, 0, 0, 11, 1078 }, // '7' + { 11, 14, 0, 0, 11, 1232 }, // '8' + { 11, 14, 0, 0, 11, 1386 }, // '9' + { 4, 3, 1, 11, 6, 1540 }, // '.' + { 6, 3, 0, 8, 7, 1552 }, // '-' + { 8, 6, 0, 0, 8, 1570 }, // '°' + { 0, 0, 0, 0, 6, 1618 }, // ' ' +}; + +// Draw single anti-aliased character glyph +template +inline void drawCoordsAAChar(T &dst, int x0, int y0, const CoordsGlyphAA &g, const uint8_t *alphaTable, uint16_t color, uint16_t bgCol, int clip_h) { + if (g.w == 0 || g.h == 0) return; + for (uint8_t dy = 0; dy < g.h; dy++) { + int curY = y0 + g.yOff + dy; + if (curY < 0 || curY >= clip_h) continue; + uint32_t rowStart = g.offset + (uint32_t)dy * g.w; + for (uint8_t dx = 0; dx < g.w; dx++) { + int curX = x0 + g.xOff + dx; + if (curX < 0 || curX >= dst.width()) continue; + uint8_t a = pgm_read_byte(&alphaTable[rowStart + dx]); + if (a > 6) { + uint16_t pixCol = (a >= 250) ? color : fastBlend(a, color, bgCol); + dst.drawPixel(curX, curY, pixCol); + } + } + } +} + +// 1. Draw 100% anti-aliased Title (ALTITUD, RUMBO / BRUJULA, SATELITES GPS, COORDENADAS) +template +inline void drawGpsNavTitle(T &dst, const char *str, int x, int y, uint16_t color, uint16_t bgCol, int clip_h = 240) { + int curX = x; + uint8_t colorIdx = 0; + for (int i = 0; str[i] != '\0'; i++) { + char c = str[i]; + int gIdx = -1; + if (c >= 'A' && c <= 'Z') { + gIdx = c - 'A'; + } else if (c >= 'a' && c <= 'z') { + gIdx = c - 'a'; + } else if (c == '/') { + gIdx = 26; + } else if (c == ' ') { + gIdx = 27; + } + + if (gIdx >= 0) { + CoordsGlyphAA g; + memcpy_P(&g, &s_navTitleGlyphs[gIdx], sizeof(CoordsGlyphAA)); + uint16_t cCol = isYapaTheme() ? getYapaColor(colorIdx++) : color; + drawCoordsAAChar(dst, curX, y, g, s_navTitleAlpha, cCol, bgCol, clip_h); + curX += g.xAdv; + } + } +} + +// 2. Draw 100% anti-aliased Subwords (LATITUD, LONGITUD, HDOP, MSNM, DIAGS) +template +inline void drawGpsNavSubword(T &dst, const char *str, int x, int y, uint16_t color, uint16_t bgCol, int clip_h = 240) { + int curX = x; + uint8_t colorIdx = 0; + for (int i = 0; str[i] != '\0'; i++) { + char c = str[i]; + int gIdx = -1; + if (c >= 'A' && c <= 'Z') { + gIdx = c - 'A'; // 0..25 + } else if (c >= 'a' && c <= 'z') { + gIdx = c - 'a'; // 0..25 + } else if (c >= '0' && c <= '9') { + gIdx = 26 + (c - '0'); // 26..35 + } else if (c == ':') { + gIdx = 36; + } else if (c == '.') { + gIdx = 37; + } else if (c == '-') { + gIdx = 38; + } else if ((uint8_t)c == 0xC2 && (uint8_t)str[i + 1] == 0xB0) { + gIdx = 39; + i++; + } else if ((uint8_t)c == 0xB0 || c == '`') { + gIdx = 39; + } else if (c == '/') { + gIdx = 40; + } else if (c == ' ') { + gIdx = 41; + } + + if (gIdx >= 0) { + CoordsGlyphAA g; + memcpy_P(&g, &s_navSubGlyphs[gIdx], sizeof(CoordsGlyphAA)); + uint16_t cCol = isYapaTheme() ? getYapaColor(colorIdx++) : color; + drawCoordsAAChar(dst, curX, y, g, s_navSubAlpha, cCol, bgCol, clip_h); + curX += g.xAdv; + } + } +} + +// 3. Draw 100% anti-aliased Coordinate Value (e.g. "-34.60371°" or "--.-----°") +template +inline void drawCoordsAAValue(T &dst, const char *str, int x, int y, uint16_t color, uint16_t bgCol, int clip_h = 240) { + int curX = x; + uint8_t colorIdx = 0; + for (int i = 0; str[i] != '\0'; i++) { + char c = str[i]; + int gIdx = -1; + if (c >= '0' && c <= '9') { + gIdx = c - '0'; + } else if (c == '.') { + gIdx = 10; + } else if (c == '-') { + gIdx = 11; + } else if ((uint8_t)c == 0xC2 && (uint8_t)str[i + 1] == 0xB0) { + gIdx = 12; + i++; + } else if ((uint8_t)c == 0xB0 || c == '`') { + gIdx = 12; + } else if (c == ' ') { + gIdx = 13; + } + + if (gIdx >= 0) { + CoordsGlyphAA g; + memcpy_P(&g, &s_coordsValueGlyphs[gIdx], sizeof(CoordsGlyphAA)); + uint16_t cCol = isYapaTheme() ? getYapaColor(colorIdx++) : color; + drawCoordsAAChar(dst, curX, y, g, s_coordsValueAlpha, cCol, bgCol, clip_h); + curX += g.xAdv; + } + } +} + +// Backward-compatible alias for subword labels +template +inline void drawCoordsAALabel(T &dst, const char *str, int x, int y, uint16_t color, uint16_t bgCol, int clip_h = 240) { + drawGpsNavSubword(dst, str, x, y, color, bgCol, clip_h); +} + diff --git a/sketch/obd2_gauge/gps_clock.h b/sketch/obd2_gauge/gps_clock.h index ba37d3e..52b2ec8 100644 --- a/sketch/obd2_gauge/gps_clock.h +++ b/sketch/obd2_gauge/gps_clock.h @@ -33,6 +33,10 @@ static uint16_t oldClockYear = 0; static TFT_eSprite sprDayTitle(&tft); static uint8_t sprDayWday = 255; +inline void invalidateGpsClockTitles() { + sprDayWday = 255; +} + inline void updateDayTitleSprite(uint8_t wday) { if (sprDayWday == wday && sprDayTitle.created()) return; if (!sprDayTitle.created()) { @@ -43,8 +47,21 @@ inline void updateDayTitleSprite(uint8_t wday) { sprDayTitle.loadFont(Latin_Sans_Bold_26); sprDayTitle.setTextPadding(0); sprDayTitle.fillSprite(cardBg); - sprDayTitle.setTextColor(TFT_WHITE, cardBg); - sprDayTitle.drawString(SPANISH_DAYS[wday], 0, 0, 1); + if (isYapaTheme()) { + int curX = 0; + char buf[2] = {0, 0}; + String dName = SPANISH_DAYS[wday]; + for (size_t i = 0; i < dName.length(); i++) { + buf[0] = dName[i]; + if (buf[0] == ' ') { curX += sprDayTitle.textWidth(buf); continue; } + sprDayTitle.setTextColor(getYapaColor(i), cardBg); + sprDayTitle.drawString(buf, curX, 0); + curX += sprDayTitle.textWidth(buf); + } + } else { + sprDayTitle.setTextColor(TFT_WHITE, cardBg); + sprDayTitle.drawString(SPANISH_DAYS[wday], 0, 0, 1); + } sprDayTitle.unloadFont(); if (isRainbowTheme()) { applyRainbowToSprite(sprDayTitle, 4 + 116, 125 + 22, cardBg); @@ -67,8 +84,20 @@ inline void updateYearTitleSprite(uint16_t year, uint16_t priCol) { sprYearTitle.loadFont(Latin_Sans_Bold_26); sprYearTitle.setTextPadding(0); sprYearTitle.fillSprite(cardBg); - sprYearTitle.setTextColor(priCol, cardBg); - sprYearTitle.drawString(String(year).c_str(), 0, 0, 1); + if (isYapaTheme()) { + int curX = 0; + char buf[2] = {0, 0}; + String yStr = String(year); + for (size_t i = 0; i < yStr.length(); i++) { + buf[0] = yStr[i]; + sprYearTitle.setTextColor(getYapaColor(i + 2), cardBg); + sprYearTitle.drawString(buf, curX, 0); + curX += sprYearTitle.textWidth(buf); + } + } else { + sprYearTitle.setTextColor(priCol, cardBg); + sprYearTitle.drawString(String(year).c_str(), 0, 0, 1); + } sprYearTitle.unloadFont(); if (isRainbowTheme()) { applyRainbowToSprite(sprYearTitle, 4 + 116, 125 + 64, cardBg); @@ -112,47 +141,102 @@ inline void drawDateCardContent(int cardX, int cardY, int cardW, int cardH, tft.drawRoundRect(bx, by, bw, bh, 6, cardBorder); // Badge top header (Month abbreviated in Accent Color) - tft.fillRoundRect(bx, by, bw, 24, 6, accCol); - tft.fillRect(bx, by + 18, bw, 6, accCol); // flatten lower header corners - tft.setTextColor(TFT_BLACK, accCol); - tft.drawCentreString(mShort.c_str(), bx + (bw / 2), by + 4, 2); + if (isYapaTheme()) { + tft.fillRoundRect(bx, by, bw, 24, 6, 0x3A53); + tft.fillRect(bx, by + 18, bw, 6, 0x3A53); + int totalW = tft.textWidth(mShort.c_str(), 2); + int startX = bx + (bw - totalW) / 2; + char buf[2] = {0, 0}; + for (size_t i = 0; i < mShort.length(); i++) { + buf[0] = mShort[i]; + tft.setTextColor(getYapaColor(i), 0x3A53); + tft.drawString(buf, startX, by + 4, 2); + startX += tft.textWidth(buf, 2); + } + } else { + tft.fillRoundRect(bx, by, bw, 24, 6, accCol); + tft.fillRect(bx, by + 18, bw, 6, accCol); // flatten lower header corners + tft.setTextColor(TFT_BLACK, accCol); + tft.drawCentreString(mShort.c_str(), bx + (bw / 2), by + 4, 2); + } // Badge body: Big Day Number in DSEG7 34pt tft.loadFont(DSEG7_Modern_BoldItalic_34); - tft.setTextColor(TFT_WHITE, 0x18C3); tft.setTextPadding(0); - tft.drawCentreString(dayStr.c_str(), bx + (bw / 2), by + 34, 1); + if (isYapaTheme()) { + int totalW = tft.textWidth(dayStr.c_str()); + int startX = bx + (bw - totalW) / 2; + char buf[2] = {0, 0}; + for (size_t i = 0; i < dayStr.length(); i++) { + buf[0] = dayStr[i]; + tft.setTextColor(getYapaColor(i), 0x18C3); + tft.drawString(buf, startX, by + 34); + startX += tft.textWidth(buf); + } + } else { + tft.setTextColor(TFT_WHITE, 0x18C3); + tft.drawCentreString(dayStr.c_str(), bx + (bw / 2), by + 34, 1); + } tft.unloadFont(); // --- DATE DETAILS (Right side) --- int tx = cardX + 116; // Day of week in Latin Sans Bold 26 (Smooth antialiased, White, uppercase matching Page 1) - updateDayTitleSprite(wday); - if (sprDayTitle.created()) { - sprDayTitle.pushSprite(tx, cardY + 22); - } else { + if (isYapaTheme()) { tft.loadFont(Latin_Sans_Bold_26); - tft.setTextPadding(0); - tft.setTextColor(TFT_WHITE, cardBg); - tft.drawString(SPANISH_DAYS[wday], tx, cardY + 22, 1); + int curX = tx; + char buf[2] = {0, 0}; + String dName = SPANISH_DAYS[wday]; + for (size_t i = 0; i < dName.length(); i++) { + buf[0] = dName[i]; + if (buf[0] == ' ') { curX += tft.textWidth(buf); continue; } + tft.setTextColor(getYapaColor(i), cardBg); + tft.drawString(buf, curX, cardY + 22); + curX += tft.textWidth(buf); + } tft.unloadFont(); + } else { + updateDayTitleSprite(wday); + if (sprDayTitle.created()) { + sprDayTitle.pushSprite(tx, cardY + 22); + } else { + tft.loadFont(Latin_Sans_Bold_26); + tft.setTextPadding(0); + tft.setTextColor(TFT_WHITE, cardBg); + tft.drawString(SPANISH_DAYS[wday], tx, cardY + 22, 1); + tft.unloadFont(); + } } // Elegant two-tone horizontal dividing line tft.drawFastHLine(tx, cardY + 54, 180, cardBorder); - tft.drawFastHLine(tx, cardY + 54, 50, accCol); + tft.drawFastHLine(tx, cardY + 54, 50, isYapaTheme() ? getYapaColor(0) : accCol); // Year in Latin Sans Bold 26 (Smooth antialiased, Accent color) - updateYearTitleSprite(gpsYear, yearCol); - if (sprYearTitle.created()) { - sprYearTitle.pushSprite(tx, cardY + 64); - } else { + if (isYapaTheme()) { tft.loadFont(Latin_Sans_Bold_26); - tft.setTextPadding(0); - tft.setTextColor(yearCol, cardBg); - tft.drawString(String(gpsYear).c_str(), tx, cardY + 64, 1); + int curX = tx; + char buf[2] = {0, 0}; + String yStr = String(gpsYear); + for (size_t i = 0; i < yStr.length(); i++) { + buf[0] = yStr[i]; + tft.setTextColor(getYapaColor(i + 2), cardBg); + tft.drawString(buf, curX, cardY + 64); + curX += tft.textWidth(buf); + } tft.unloadFont(); + } else { + updateYearTitleSprite(gpsYear, yearCol); + if (sprYearTitle.created()) { + sprYearTitle.pushSprite(tx, cardY + 64); + } else { + tft.loadFont(Latin_Sans_Bold_26); + tft.setTextPadding(0); + tft.setTextColor(yearCol, cardBg); + tft.drawString(String(gpsYear).c_str(), tx, cardY + 64, 1); + tft.unloadFont(); + } } } @@ -289,11 +373,15 @@ inline void drawGpsClockScreen(int x_offset = 0) { uint8_t m10 = (gpsMinute / 10) % 10; uint8_t m1 = gpsMinute % 10; bool rbClk = isRainbowTheme(); + uint16_t c_h10 = isYapaTheme() ? getYapaColor(0) : clkAccCol; + uint16_t c_h1 = isYapaTheme() ? getYapaColor(1) : clkAccCol; + uint16_t c_m10 = isYapaTheme() ? getYapaColor(2) : clkAccCol; + uint16_t c_m1 = isYapaTheme() ? getYapaColor(3) : clkAccCol; - drawDseg7Digit80(tft, clkX + 12, clkY + 11, h10, clkAccCol, cardBg, 240, rbClk); - drawDseg7Digit80(tft, clkX + 77, clkY + 11, h1, clkAccCol, cardBg, 240, rbClk); - drawDseg7Digit80(tft, clkX + 170, clkY + 11, m10, clkAccCol, cardBg, 240, rbClk); - drawDseg7Digit80(tft, clkX + 235, clkY + 11, m1, clkAccCol, cardBg, 240, rbClk); + drawDseg7Digit80(tft, clkX + 12, clkY + 11, h10, c_h10, cardBg, 240, rbClk); + drawDseg7Digit80(tft, clkX + 77, clkY + 11, h1, c_h1, cardBg, 240, rbClk); + drawDseg7Digit80(tft, clkX + 170, clkY + 11, m10, c_m10, cardBg, 240, rbClk); + drawDseg7Digit80(tft, clkX + 235, clkY + 11, m1, c_m1, cardBg, 240, rbClk); // Blinking Colon Dots between HH and MM (italic 7-segment style with // anti-aliasing) @@ -339,8 +427,10 @@ inline void updateGpsClockScreen(bool forceRedraw = false) { tft.fillRect(clkX + 166, clkY + 10, 142, 82, cardBg); uint8_t m10 = (gpsMinute / 10) % 10; uint8_t m1 = gpsMinute % 10; - drawDseg7Digit80(tft, clkX + 170, clkY + 11, m10, clkAccCol, cardBg, 240, isRainbowTheme()); - drawDseg7Digit80(tft, clkX + 235, clkY + 11, m1, clkAccCol, cardBg, 240, isRainbowTheme()); + uint16_t c_m10 = isYapaTheme() ? getYapaColor(2) : clkAccCol; + uint16_t c_m1 = isYapaTheme() ? getYapaColor(3) : clkAccCol; + drawDseg7Digit80(tft, clkX + 170, clkY + 11, m10, c_m10, cardBg, 240, isRainbowTheme()); + drawDseg7Digit80(tft, clkX + 235, clkY + 11, m1, c_m1, cardBg, 240, isRainbowTheme()); } // 3. Hours change: update HH digits @@ -349,8 +439,10 @@ inline void updateGpsClockScreen(bool forceRedraw = false) { tft.fillRect(clkX + 12, clkY + 10, 136, 82, cardBg); uint8_t h10 = (gpsHour / 10) % 10; uint8_t h1 = gpsHour % 10; - drawDseg7Digit80(tft, clkX + 12, clkY + 11, h10, clkAccCol, cardBg, 240, isRainbowTheme()); - drawDseg7Digit80(tft, clkX + 77, clkY + 11, h1, clkAccCol, cardBg, 240, isRainbowTheme()); + uint16_t c_h10 = isYapaTheme() ? getYapaColor(0) : clkAccCol; + uint16_t c_h1 = isYapaTheme() ? getYapaColor(1) : clkAccCol; + drawDseg7Digit80(tft, clkX + 12, clkY + 11, h10, c_h10, cardBg, 240, isRainbowTheme()); + drawDseg7Digit80(tft, clkX + 77, clkY + 11, h1, c_h1, cardBg, 240, isRainbowTheme()); } // 4. Date changed (midnight rollover): redraw full date card @@ -403,10 +495,15 @@ inline void renderPage2ToSprite(TFT_eSprite &s, int x_offset, int y_slice, uint8_t m10 = (gpsMinute / 10) % 10; uint8_t m1 = gpsMinute % 10; - drawDseg7Digit80(s, clkX + 12, digitY, h10, clkAccCol, cardBg, slice_h, isRainbowTheme()); - drawDseg7Digit80(s, clkX + 77, digitY, h1, clkAccCol, cardBg, slice_h, isRainbowTheme()); - drawDseg7Digit80(s, clkX + 170, digitY, m10, clkAccCol, cardBg, slice_h, isRainbowTheme()); - drawDseg7Digit80(s, clkX + 235, digitY, m1, clkAccCol, cardBg, slice_h, isRainbowTheme()); + uint16_t c_h10 = isYapaTheme() ? getYapaColor(0) : clkAccCol; + uint16_t c_h1 = isYapaTheme() ? getYapaColor(1) : clkAccCol; + uint16_t c_m10 = isYapaTheme() ? getYapaColor(2) : clkAccCol; + uint16_t c_m1 = isYapaTheme() ? getYapaColor(3) : clkAccCol; + + drawDseg7Digit80(s, clkX + 12, digitY, h10, c_h10, cardBg, slice_h, isRainbowTheme()); + drawDseg7Digit80(s, clkX + 77, digitY, h1, c_h1, cardBg, slice_h, isRainbowTheme()); + drawDseg7Digit80(s, clkX + 170, digitY, m10, c_m10, cardBg, slice_h, isRainbowTheme()); + drawDseg7Digit80(s, clkX + 235, digitY, m1, c_m1, cardBg, slice_h, isRainbowTheme()); } } @@ -433,20 +530,49 @@ inline void renderPage2ToSprite(TFT_eSprite &s, int x_offset, int y_slice, s.fillRoundRect(bx, by, bw, bh, 6, 0x18C3); s.drawRoundRect(bx, by, bw, bh, 6, cardBorder); - // Header strip - s.fillRoundRect(bx, by, bw, 24, 6, dateAccCol); - s.fillRect(bx, by + 18, bw, 6, dateAccCol); - uint8_t mIdx = (gpsMonth >= 1 && gpsMonth <= 12) ? (gpsMonth - 1) : 0; - s.setTextColor(TFT_BLACK, dateAccCol); - s.drawCentreString(SPANISH_MONTHS_SHORT[mIdx], bx + (bw / 2), by + 4, 2); + if (isYapaTheme()) { + s.fillRoundRect(bx, by, bw, 24, 6, 0x3A53); + s.fillRect(bx, by + 18, bw, 6, 0x3A53); + uint8_t mIdx = (gpsMonth >= 1 && gpsMonth <= 12) ? (gpsMonth - 1) : 0; + String mShort = SPANISH_MONTHS_SHORT[mIdx]; + int totalW = s.textWidth(mShort.c_str(), 2); + int startX = bx + (bw - totalW) / 2; + char buf[2] = {0, 0}; + for (size_t i = 0; i < mShort.length(); i++) { + buf[0] = mShort[i]; + s.setTextColor(getYapaColor(i), 0x3A53); + s.drawString(buf, startX, by + 4, 2); + startX += s.textWidth(buf, 2); + } - // Badge day number - String dayStr = (gpsDay < 10) ? "0" + String(gpsDay) : String(gpsDay); - s.fontLoaded = true; - s.setTextColor(TFT_WHITE, 0x18C3); - s.setTextPadding(0); - s.drawCentreString(dayStr.c_str(), bx + (bw / 2), by + 34, 1); - s.fontLoaded = false; + // Badge day number in Yapa + String dayStr = (gpsDay < 10) ? "0" + String(gpsDay) : String(gpsDay); + s.loadFont(DSEG7_Modern_BoldItalic_34); + s.setTextPadding(0); + int dayW = s.textWidth(dayStr.c_str()); + int dStartX = bx + (bw - dayW) / 2; + for (size_t i = 0; i < dayStr.length(); i++) { + buf[0] = dayStr[i]; + s.setTextColor(getYapaColor(i), 0x18C3); + s.drawString(buf, dStartX, by + 34); + dStartX += s.textWidth(buf); + } + s.unloadFont(); + } else { + s.fillRoundRect(bx, by, bw, 24, 6, dateAccCol); + s.fillRect(bx, by + 18, bw, 6, dateAccCol); + uint8_t mIdx = (gpsMonth >= 1 && gpsMonth <= 12) ? (gpsMonth - 1) : 0; + s.setTextColor(TFT_BLACK, dateAccCol); + s.drawCentreString(SPANISH_MONTHS_SHORT[mIdx], bx + (bw / 2), by + 4, 2); + + // Badge day number + String dayStr = (gpsDay < 10) ? "0" + String(gpsDay) : String(gpsDay); + s.fontLoaded = true; + s.setTextColor(TFT_WHITE, 0x18C3); + s.setTextPadding(0); + s.drawCentreString(dayStr.c_str(), bx + (bw / 2), by + 34, 1); + s.fontLoaded = false; + } } int tx = dateX + 116; diff --git a/sketch/obd2_gauge/gps_nav.h b/sketch/obd2_gauge/gps_nav.h index bbf9b09..322fd89 100644 --- a/sketch/obd2_gauge/gps_nav.h +++ b/sketch/obd2_gauge/gps_nav.h @@ -5,12 +5,14 @@ #include "gps_module.h" #include "DSEG7_Modern_BoldItalic_34.h" #include "rainbow_text.h" +#include "coords_font.h" extern TFT_eSPI tft; extern uint16_t getThemePrimaryColor(); extern uint16_t getThemeAccentColor(); extern uint16_t getThemeBgColor(); extern bool isRainbowTheme(); +extern bool isYapaTheme(); extern int8_t currentScreenPage; extern int8_t fullScreenCell; @@ -93,10 +95,10 @@ inline void drawGpsNavScreen(int x_offset = 0) { uint16_t priCol = getThemePrimaryColor(); uint16_t accCol = getThemeAccentColor(); - uint16_t c0Col = isRainbowTheme() ? (uint16_t)0x07FF : priCol; - uint16_t c1Col = isRainbowTheme() ? (uint16_t)0x07E0 : accCol; - uint16_t c2Col = isRainbowTheme() ? (uint16_t)0xFFE0 : priCol; - uint16_t c3Col = isRainbowTheme() ? (uint16_t)0xFD20 : accCol; + uint16_t c0Col = isRainbowTheme() ? (uint16_t)0x07FF : (isYapaTheme() ? getYapaColor(0) : priCol); + uint16_t c1Col = isRainbowTheme() ? (uint16_t)0x07E0 : (isYapaTheme() ? getYapaColor(1) : accCol); + uint16_t c2Col = isRainbowTheme() ? (uint16_t)0xFFE0 : (isYapaTheme() ? getYapaColor(2) : priCol); + uint16_t c3Col = isRainbowTheme() ? (uint16_t)0xFD20 : (isYapaTheme() ? getYapaColor(3) : accCol); uint16_t cardBg = 0x10A2; // Dark graphite navy uint16_t cardBorder = 0x2945; // Subtle edge border bool synced = isGpsSynced(); @@ -117,10 +119,8 @@ inline void drawGpsNavScreen(int x_offset = 0) { tft.drawRoundRect(c0X, c0Y, cW, cH1, 6, cardBorder); tft.fillRoundRect(c0X + 6, c0Y + 8, 4, cH1 - 16, 2, c0Col); - tft.setTextColor(0x8410, cardBg); - tft.drawString("ALTITUD", c0X + 16, c0Y + 8, 2); - tft.setTextColor(0x9CD3, cardBg); - tft.drawString("MSNM", c0X + 96, c0Y + 8, 2); + drawGpsNavTitle(tft, "ALTITUD", c0X + 16, c0Y + 8, 0x8410, cardBg, 240); + drawGpsNavSubword(tft, "MSNM", c0X + 96, c0Y + 9, 0x9CD3, cardBg, 240); bool altValid = isGpsAltitudeValid(); String altStr = altValid ? String((int)round(gpsAltitudeM)) : "---"; @@ -133,8 +133,7 @@ inline void drawGpsNavScreen(int x_offset = 0) { tft.drawRoundRect(c1X, c1Y, cW, cH1, 6, cardBorder); tft.fillRoundRect(c1X + 6, c1Y + 8, 4, cH1 - 16, 2, c1Col); - tft.setTextColor(0x8410, cardBg); - tft.drawString("RUMBO / BRUJULA", c1X + 16, c1Y + 8, 2); + drawGpsNavTitle(tft, "RUMBO / BRUJULA", c1X + 16, c1Y + 8, 0x8410, cardBg, 240); bool hasCourse = synced && isGpsCourseValid(); String crsStr = hasCourse ? String((int)round(gpsCourseDeg)) : "---"; @@ -147,8 +146,7 @@ inline void drawGpsNavScreen(int x_offset = 0) { } const char* cardDir = hasCourse ? getCompassDirectionFull(gpsCourseDeg) : "---"; - tft.setTextColor(TFT_WHITE, cardBg); - tft.drawString(cardDir, c1X + 16, c1Y + 74, 2); + drawGpsNavSubword(tft, cardDir, c1X + 16, c1Y + 76, TFT_WHITE, cardBg, 240); // 4. CARD 2 (Bottom Left): SATÉLITES GPS EN GRANDE (x = 4, y = 125, w = 152, h = 110) int c2X = 4 + x_offset; @@ -158,8 +156,7 @@ inline void drawGpsNavScreen(int x_offset = 0) { tft.drawRoundRect(c2X, c2Y, cW, cH2, 6, cardBorder); tft.fillRoundRect(c2X + 6, c2Y + 8, 4, cH2 - 16, 2, c2Col); - tft.setTextColor(0x8410, cardBg); - tft.drawString("SATELITES GPS", c2X + 16, c2Y + 8, 2); + drawGpsNavTitle(tft, "SATELITES GPS", c2X + 16, c2Y + 8, 0x8410, cardBg, 240); // Large Satellite Icon (animated body + waves) uint8_t waveState = getGpsWaveAnimState(); @@ -171,11 +168,9 @@ inline void drawGpsNavScreen(int x_offset = 0) { // Telemetry details: HDOP precision & live diagnostic status String hdopStr = "HDOP: " + (synced ? String(gpsHdop, 1) : "---"); - tft.setTextColor(TFT_WHITE, cardBg); - tft.drawString(hdopStr.c_str(), c2X + 16, c2Y + 68, 2); + drawGpsNavSubword(tft, hdopStr.c_str(), c2X + 16, c2Y + 70, TFT_WHITE, cardBg, 240); - tft.setTextColor(synced ? 0x9CD3 : priCol, cardBg); - tft.drawString(getGpsDiagMessage(), c2X + 16, c2Y + 90, 1); + drawGpsNavSubword(tft, getGpsDiagMessage(), c2X + 16, c2Y + 90, synced ? 0x9CD3 : priCol, cardBg, 240); // 5. CARD 3 (Bottom Right): COORDENADAS GPS (x = 164, y = 125, w = 152, h = 110) int c3X = 164 + x_offset; @@ -184,20 +179,15 @@ inline void drawGpsNavScreen(int x_offset = 0) { tft.drawRoundRect(c3X, c3Y, cW, cH2, 6, cardBorder); tft.fillRoundRect(c3X + 6, c3Y + 8, 4, cH2 - 16, 2, c3Col); - tft.setTextColor(0x8410, cardBg); - tft.drawString("COORDENADAS", c3X + 16, c3Y + 8, 2); + drawGpsNavTitle(tft, "COORDENADAS", c3X + 16, c3Y + 8, 0x8410, cardBg, 240); - tft.setTextColor(0x8410, cardBg); - tft.drawString("LAT", c3X + 16, c3Y + 26, 1); + drawGpsNavSubword(tft, "LATITUD", c3X + 16, c3Y + 24, 0x8410, cardBg, 240); String latStr = gpsLocationValid ? (String(gpsLatitude, 5) + "°") : "--.-----°"; - tft.setTextColor(c3Col, cardBg); - tft.drawString(latStr.c_str(), c3X + 16, c3Y + 38, 2); + drawCoordsAAValue(tft, latStr.c_str(), c3X + 16, c3Y + 38, c3Col, cardBg, 240); - tft.setTextColor(0x8410, cardBg); - tft.drawString("LON", c3X + 16, c3Y + 64, 1); + drawGpsNavSubword(tft, "LONGITUD", c3X + 16, c3Y + 62, 0x8410, cardBg, 240); String lonStr = gpsLocationValid ? (String(gpsLongitude, 5) + "°") : "--.-----°"; - tft.setTextColor(c3Col, cardBg); - tft.drawString(lonStr.c_str(), c3X + 16, c3Y + 76, 2); + drawCoordsAAValue(tft, lonStr.c_str(), c3X + 16, c3Y + 76, c3Col, cardBg, 240); // Sync state trackers syncGpsNavTrackers(); @@ -217,10 +207,10 @@ inline void updateGpsNavScreen(bool forceRedraw = false) { uint16_t priCol = getThemePrimaryColor(); uint16_t accCol = getThemeAccentColor(); - uint16_t c0Col = isRainbowTheme() ? (uint16_t)0x07FF : priCol; - uint16_t c1Col = isRainbowTheme() ? (uint16_t)0x07E0 : accCol; - uint16_t c2Col = isRainbowTheme() ? (uint16_t)0xFFE0 : priCol; - uint16_t c3Col = isRainbowTheme() ? (uint16_t)0xFD20 : accCol; + uint16_t c0Col = isRainbowTheme() ? (uint16_t)0x07FF : (isYapaTheme() ? getYapaColor(0) : priCol); + uint16_t c1Col = isRainbowTheme() ? (uint16_t)0x07E0 : (isYapaTheme() ? getYapaColor(1) : accCol); + uint16_t c2Col = isRainbowTheme() ? (uint16_t)0xFFE0 : (isYapaTheme() ? getYapaColor(2) : priCol); + uint16_t c3Col = isRainbowTheme() ? (uint16_t)0xFD20 : (isYapaTheme() ? getYapaColor(3) : accCol); uint16_t cardBg = 0x10A2; bool synced = isGpsSynced(); @@ -262,10 +252,8 @@ inline void updateGpsNavScreen(bool forceRedraw = false) { const char* cardDir = hasCourse ? getCompassDirectionFull(gpsCourseDeg) : "---"; if (forceRedraw || strcmp(cardDir, lastNavCardDir) != 0) { lastNavCardDir = cardDir; - tft.setTextPadding(128); - tft.setTextColor(TFT_WHITE, cardBg); - tft.drawString(cardDir, c1X + 16, c1Y + 74, 2); - tft.setTextPadding(0); + tft.fillRect(c1X + 16, c1Y + 74, 128, 16, cardBg); + drawGpsNavSubword(tft, cardDir, c1X + 16, c1Y + 76, TFT_WHITE, cardBg, 240); } // 3. Card 2: Satellites, HDOP and Diagnostic status @@ -288,10 +276,8 @@ inline void updateGpsNavScreen(bool forceRedraw = false) { lastNavCardHdop = gpsHdop; oldNavHdop = gpsHdop; String hdopStr = "HDOP: " + (synced ? String(gpsHdop, 1) : "---"); - tft.setTextPadding(130); - tft.setTextColor(TFT_WHITE, cardBg); - tft.drawString(hdopStr.c_str(), c2X + 16, c2Y + 68, 2); - tft.setTextPadding(0); + tft.fillRect(c2X + 16, c2Y + 68, 130, 16, cardBg); + drawGpsNavSubword(tft, hdopStr.c_str(), c2X + 16, c2Y + 70, TFT_WHITE, cardBg, 240); } // 3c. Diagnostic status message ("PRECISION OPTIMA", "SIN SEÑAL GPS", etc.) @@ -301,10 +287,8 @@ inline void updateGpsNavScreen(bool forceRedraw = false) { strncpy(lastNavCardDiag, curDiag, sizeof(lastNavCardDiag) - 1); lastNavCardDiag[sizeof(lastNavCardDiag) - 1] = '\0'; lastNavDiagSynced = synced; - tft.setTextPadding(130); - tft.setTextColor(synced ? 0x9CD3 : c2Col, cardBg); - tft.drawString(curDiag, c2X + 16, c2Y + 90, 1); - tft.setTextPadding(0); + tft.fillRect(c2X + 16, c2Y + 88, 130, 16, cardBg); + drawGpsNavSubword(tft, curDiag, c2X + 16, c2Y + 90, synced ? 0x9CD3 : c2Col, cardBg, 240); } // 3d. Large Satellite Icon: only redrawn if waveState or sync changes @@ -315,20 +299,20 @@ inline void updateGpsNavScreen(bool forceRedraw = false) { drawLargeSatelliteIcon(c2X + 16, c2Y + 32, c2Col, curWaveState, cardBg); } - // 4. Coordinates changed: update Card 3 (setTextPadding avoids fillRect flicker) + // 4. Coordinates changed: update Card 3 (clear exact value region and redraw anti-aliased value) if (forceRedraw || abs(gpsLatitude - oldNavLat) >= 0.00005 || abs(gpsLongitude - oldNavLon) >= 0.00005) { oldNavLat = gpsLatitude; oldNavLon = gpsLongitude; int c3X = 164; int c3Y = 125; String latStr = gpsLocationValid ? (String(gpsLatitude, 5) + "°") : "--.-----°"; - tft.setTextPadding(130); - tft.setTextColor(c3Col, cardBg); - tft.drawString(latStr.c_str(), c3X + 16, c3Y + 38, 2); - String lonStr = gpsLocationValid ? (String(gpsLongitude, 5) + "°") : "--.-----°"; - tft.drawString(lonStr.c_str(), c3X + 16, c3Y + 76, 2); - tft.setTextPadding(0); + + tft.fillRect(c3X + 16, c3Y + 38, 130, 20, cardBg); + drawCoordsAAValue(tft, latStr.c_str(), c3X + 16, c3Y + 38, c3Col, cardBg, 240); + + tft.fillRect(c3X + 16, c3Y + 76, 130, 20, cardBg); + drawCoordsAAValue(tft, lonStr.c_str(), c3X + 16, c3Y + 76, c3Col, cardBg, 240); } } @@ -336,10 +320,10 @@ inline void updateGpsNavScreen(bool forceRedraw = false) { inline void renderPage3ToSprite(TFT_eSprite &s, int x_offset, int y_slice, int slice_h) { uint16_t priCol = getThemePrimaryColor(); uint16_t accCol = getThemeAccentColor(); - uint16_t c0Col = isRainbowTheme() ? (uint16_t)0x07FF : priCol; - uint16_t c1Col = isRainbowTheme() ? (uint16_t)0x07E0 : accCol; - uint16_t c2Col = isRainbowTheme() ? (uint16_t)0xFFE0 : priCol; - uint16_t c3Col = isRainbowTheme() ? (uint16_t)0xFD20 : accCol; + uint16_t c0Col = isRainbowTheme() ? (uint16_t)0x07FF : (isYapaTheme() ? getYapaColor(0) : priCol); + uint16_t c1Col = isRainbowTheme() ? (uint16_t)0x07E0 : (isYapaTheme() ? getYapaColor(1) : accCol); + uint16_t c2Col = isRainbowTheme() ? (uint16_t)0xFFE0 : (isYapaTheme() ? getYapaColor(2) : priCol); + uint16_t c3Col = isRainbowTheme() ? (uint16_t)0xFD20 : (isYapaTheme() ? getYapaColor(3) : accCol); uint16_t cardBg = 0x10A2; uint16_t cardBorder = 0x2945; bool synced = isGpsSynced(); @@ -354,12 +338,8 @@ inline void renderPage3ToSprite(TFT_eSprite &s, int x_offset, int y_slice, int s s.drawRoundRect(c0X, c0Y, cW, cH1, 6, cardBorder); s.fillRoundRect(c0X + 6, c0Y + 8, 4, cH1 - 16, 2, c0Col); - if (c0Y + 24 > 0 && c0Y + 8 < slice_h) { - s.setTextColor(0x8410, cardBg); - s.drawString("ALTITUD", c0X + 16, c0Y + 8, 2); - s.setTextColor(0x9CD3, cardBg); - s.drawString("MSNM", c0X + 96, c0Y + 8, 2); - } + drawGpsNavTitle(s, "ALTITUD", c0X + 16, c0Y + 8, 0x8410, cardBg, slice_h); + drawGpsNavSubword(s, "MSNM", c0X + 96, c0Y + 9, 0x9CD3, cardBg, slice_h); bool altValid = isGpsAltitudeValid(); String altStr = altValid ? String((int)round(gpsAltitudeM)) : "---"; int valY = c0Y + 28; @@ -376,10 +356,7 @@ inline void renderPage3ToSprite(TFT_eSprite &s, int x_offset, int y_slice, int s s.drawRoundRect(c1X, c1Y, cW, cH1, 6, cardBorder); s.fillRoundRect(c1X + 6, c1Y + 8, 4, cH1 - 16, 2, c1Col); - if (c1Y + 24 > 0 && c1Y + 8 < slice_h) { - s.setTextColor(0x8410, cardBg); - s.drawString("RUMBO / BRUJULA", c1X + 16, c1Y + 8, 2); - } + drawGpsNavTitle(s, "RUMBO / BRUJULA", c1X + 16, c1Y + 8, 0x8410, cardBg, slice_h); bool hasCourse = synced && isGpsCourseValid(); String crsStr = hasCourse ? String((int)round(gpsCourseDeg)) : "---"; int valY = c1Y + 28; @@ -393,12 +370,8 @@ inline void renderPage3ToSprite(TFT_eSprite &s, int x_offset, int y_slice, int s } } } - int dirY = c1Y + 74; - if (dirY + 16 > 0 && dirY < slice_h) { - const char* cardDir = hasCourse ? getCompassDirectionFull(gpsCourseDeg) : "---"; - s.setTextColor(TFT_WHITE, cardBg); - s.drawString(cardDir, c1X + 16, dirY, 2); - } + const char* cardDir = hasCourse ? getCompassDirectionFull(gpsCourseDeg) : "---"; + drawGpsNavSubword(s, cardDir, c1X + 16, c1Y + 76, TFT_WHITE, cardBg, slice_h); } // 4. CARD 2 (Bottom Left): SATÉLITES GPS (y = 125 to 235) @@ -410,10 +383,7 @@ inline void renderPage3ToSprite(TFT_eSprite &s, int x_offset, int y_slice, int s s.drawRoundRect(c2X, c2Y, cW, cH2, 6, cardBorder); s.fillRoundRect(c2X + 6, c2Y + 8, 4, cH2 - 16, 2, c2Col); - if (c2Y + 24 > 0 && c2Y + 8 < slice_h) { - s.setTextColor(0x8410, cardBg); - s.drawString("SATELITES GPS", c2X + 16, c2Y + 8, 2); - } + drawGpsNavTitle(s, "SATELITES GPS", c2X + 16, c2Y + 8, 0x8410, cardBg, slice_h); uint8_t waveState = getGpsWaveAnimState(); int icnY = c2Y + 32; if (icnY + 28 > 0 && icnY < slice_h) { @@ -424,17 +394,9 @@ inline void renderPage3ToSprite(TFT_eSprite &s, int x_offset, int y_slice, int s String satStr = isGpsUartLocked() ? String(gpsSats) : "---"; drawDseg7RightString34(s, satStr.c_str(), c2X + 140, satY, c2Col, cardBg, 0, slice_h, isRainbowTheme()); } - int hdopY = c2Y + 68; - if (hdopY < slice_h && hdopY + 16 > 0) { - String hdopStr = "HDOP: " + (synced ? String(gpsHdop, 1) : "---"); - s.setTextColor(TFT_WHITE, cardBg); - s.drawString(hdopStr.c_str(), c2X + 16, hdopY, 2); - } - int qualY = c2Y + 90; - if (qualY + 10 > 0 && qualY < slice_h) { - s.setTextColor(synced ? 0x9CD3 : c2Col, cardBg); - s.drawString(getGpsDiagMessage(), c2X + 16, qualY, 1); - } + String hdopStr = "HDOP: " + (synced ? String(gpsHdop, 1) : "---"); + drawGpsNavSubword(s, hdopStr.c_str(), c2X + 16, c2Y + 70, TFT_WHITE, cardBg, slice_h); + drawGpsNavSubword(s, getGpsDiagMessage(), c2X + 16, c2Y + 90, synced ? 0x9CD3 : c2Col, cardBg, slice_h); } // 5. CARD 3 (Bottom Right): COORDENADAS (y = 125 to 235) @@ -445,31 +407,14 @@ inline void renderPage3ToSprite(TFT_eSprite &s, int x_offset, int y_slice, int s s.drawRoundRect(c3X, c3Y, cW, cH2, 6, cardBorder); s.fillRoundRect(c3X + 6, c3Y + 8, 4, cH2 - 16, 2, c3Col); - if (c3Y + 24 > 0 && c3Y + 8 < slice_h) { - s.setTextColor(0x8410, cardBg); - s.drawString("COORDENADAS", c3X + 16, c3Y + 8, 2); - } - int latHdrY = c3Y + 26; - if (latHdrY < slice_h && latHdrY + 10 > 0) { - s.setTextColor(0x8410, cardBg); - s.drawString("LAT", c3X + 16, latHdrY, 1); - } - int latValY = c3Y + 38; - if (latValY < slice_h && latValY + 18 > 0) { - String latStr = gpsLocationValid ? (String(gpsLatitude, 5) + "°") : "--.-----°"; - s.setTextColor(c3Col, cardBg); - s.drawString(latStr.c_str(), c3X + 16, latValY, 2); - } - int lonHdrY = c3Y + 64; - if (lonHdrY < slice_h && lonHdrY + 10 > 0) { - s.setTextColor(0x8410, cardBg); - s.drawString("LON", c3X + 16, lonHdrY, 1); - } - int lonValY = c3Y + 76; - if (lonValY < slice_h && lonValY + 18 > 0) { - String lonStr = gpsLocationValid ? (String(gpsLongitude, 5) + "°") : "--.-----°"; - s.setTextColor(c3Col, cardBg); - s.drawString(lonStr.c_str(), c3X + 16, lonValY, 2); - } + drawGpsNavTitle(s, "COORDENADAS", c3X + 16, c3Y + 8, 0x8410, cardBg, slice_h); + + drawGpsNavSubword(s, "LATITUD", c3X + 16, c3Y + 24, 0x8410, cardBg, slice_h); + String latStr = gpsLocationValid ? (String(gpsLatitude, 5) + "°") : "--.-----°"; + drawCoordsAAValue(s, latStr.c_str(), c3X + 16, c3Y + 38, c3Col, cardBg, slice_h); + + drawGpsNavSubword(s, "LONGITUD", c3X + 16, c3Y + 62, 0x8410, cardBg, slice_h); + String lonStr = gpsLocationValid ? (String(gpsLongitude, 5) + "°") : "--.-----°"; + drawCoordsAAValue(s, lonStr.c_str(), c3X + 16, c3Y + 76, c3Col, cardBg, slice_h); } } diff --git a/sketch/obd2_gauge/meter.h b/sketch/obd2_gauge/meter.h index 12e1a42..35f4c65 100644 --- a/sketch/obd2_gauge/meter.h +++ b/sketch/obd2_gauge/meter.h @@ -11,6 +11,9 @@ extern bool sprTransAllocated; extern TFT_eSprite sprFadeB; extern bool sprFadeBAllocated; +extern uint8_t targetBacklight; +extern float currentBacklight; + const uint16_t screenWidth = tft.height(); // landscape const uint16_t screenHeight = tft.width(); /*---- METER ------_* @@ -239,8 +242,14 @@ void drawFullScreenTitle(const String &label, uint16_t color) { tft.drawFastHLine(60, y, 195, bgCol); } if (color != 0) { - tft.setTextColor(color); - tft.drawCentreString(label.c_str(), 175, 12, 4); + if (color == TFT_WHITE && isYapaTheme()) { + drawYapaCentreString(label, 175, 12, 4); + } else if (color == TFT_WHITE && isRainbowTheme()) { + drawRainbowCentreString(label.c_str(), 175, 12, 4); + } else { + tft.setTextColor(color); + tft.drawCentreString(label.c_str(), 175, 12, 4); + } } } @@ -1292,8 +1301,14 @@ void fullScreenMeter(uint8_t cell, uint8_t pid, bool redrawHeader = true) { alarmEnabled[pid]) ? TFT_RED : TFT_WHITE; - tft.setTextColor(titleCol); - tft.drawCentreString(label, 175, 12, 4); + if (titleCol == TFT_WHITE && isYapaTheme()) { + drawYapaCentreString(label, 175, 12, 4); + } else if (titleCol == TFT_WHITE && isRainbowTheme()) { + drawRainbowCentreString(label.c_str(), 175, 12, 4); + } else { + tft.setTextColor(titleCol); + tft.drawCentreString(label, 175, 12, 4); + } if (cell == 1 && isGpsSpeedValid()) { drawFullscreenGpsIcon(getGpsWaveAnimState(), isGpsSynced()); } @@ -1750,8 +1765,12 @@ inline void renderPage1ToSprite(TFT_eSprite &s, int x_offset, int y_slice, s.setTextColor(TFT_WHITE, cardBg); s.drawString(titleStr, cardX + 16, cardY + 5, 4); } - s.setTextColor(0x8410, cardBg); - s.drawString(subStr, cardX + 16, cardY + 32, 1); + if (isYapaTheme()) { + drawYapaStringToSprite(s, subStr, cardX + 16, cardY + 32, 1); + } else { + s.setTextColor(0x8410, cardBg); + s.drawString(subStr, cardX + 16, cardY + 32, 1); + } // Progress bar int barX = cardX + 16; @@ -1779,132 +1798,80 @@ inline void renderPage1ToSprite(TFT_eSprite &s, int x_offset, int y_slice, int unitY = cardY + 22; if (unitY < slice_h && unitY + 26 > 0) { - draw7SegDegreeC(s, cardX + 268, unitY, 0xC618, cardBg); + bool rainbowUnit = isRainbowTheme() && (accentCol != TFT_RED); + uint16_t unitCol = rainbowUnit ? getRainbowScreenColor(cardX + 268, unitY) : (isYapaTheme() && accentCol != TFT_RED ? getYapaColor(numStr.length()) : (accentCol == TFT_RED ? TFT_RED : 0xC618)); + draw7SegDegreeC(s, cardX + 268, unitY, unitCol, cardBg, rainbowUnit); } } } -// 100% Flicker-Free Double-Buffered Mobile Slide Transition -// Safe 25KB sprite with pixel-perfect clipping and smooth ease-out pacing -inline void animateSlideTransition(bool swipeLeft, int8_t fromPage = -1, - int8_t toPage = -1) { - if (fromPage < 0) - fromPage = currentScreenPage; +// 100% Flicker-Free Backlight Dip / Fade-to-Black Transition +// Fades hardware backlight smoothly to black, renders the new page invisibly +// in the dark with zero tear, and smoothly restores backlight. +inline void animateBlackTransition(int8_t toPage = -1) { if (toPage < 0) - toPage = (fromPage == 0) ? 1 : 0; -#ifdef SERIAL_DEBUG - Serial.printf(">>> [DB] animateSlideTransition ENTER: fromPage=%d, " - "toPage=%d, swipeLeft=%d\n", - fromPage, toPage, swipeLeft); -#endif - const int slice_h = 40; - const uint8_t numSlices = 240 / slice_h; // exactly 6 slices + toPage = (currentScreenPage + 1) % 4; if (!sprTransAllocated) { initScreen(true); - return; // Safety fallback + return; } - const uint8_t FRAMES = 28; - // High-density sine ease-out deceleration over 28 frames: - // Ultra-fine 10-18px initial steps smoothly tapering to 1-3px for zero - // visible refresh chop - const int16_t step_shifts[FRAMES] = { - 18, 36, 54, 71, 89, 106, 122, 139, 155, 170, 185, 200, 213, 226, - 239, 250, 261, 271, 280, 288, 296, 302, 307, 312, 315, 318, 319, 320}; + uint8_t startBrightness = (uint8_t)currentBacklight; + if (startBrightness < 20) + startBrightness = 20; - for (uint8_t f = 0; f < FRAMES; f++) { - int16_t shift = step_shifts[f]; - int16_t splitX = swipeLeft ? (320 - shift) : shift; + // 1. Smoothly fade backlight down to black in ~200ms (quadratic ease for natural eye response) + const uint8_t fadeSteps = 16; + for (int8_t i = fadeSteps - 1; i >= 0; i--) { + float t = (float)i / (float)fadeSteps; + uint8_t b = (uint8_t)(startBrightness * (t * t)); + ledcWrite(backlightChannel, b); + delay(12); + } + ledcWrite(backlightChannel, 0); - for (uint8_t s = 0; s < numSlices; s++) { - int y_slice = s * slice_h; + // 2. Render new screen slice-by-slice while backlight is totally off (100% invisible redraw) + const int slice_h = 40; + const uint8_t numSlices = 240 / slice_h; // exactly 6 slices + for (uint8_t s = 0; s < numSlices; s++) { + int y_slice = s * slice_h; + sprTrans.fillSprite(TFT_BLACK); - sprTrans.fillSprite(TFT_BLACK); + if (toPage == 0) + renderPage0ToSprite(sprTrans, 0, y_slice, slice_h); + else if (toPage == 1) + renderPage1ToSprite(sprTrans, 0, y_slice, slice_h); + else if (toPage == 2) + renderPage2ToSprite(sprTrans, 0, y_slice, slice_h); + else + renderPage3ToSprite(sprTrans, 0, y_slice, slice_h); - if (swipeLeft) { - // Swipe Left: fromPage on left moving left, toPage on right moving left - if (splitX > 0) { - sprTrans.setViewport(0, 0, splitX, slice_h, false); - if (fromPage == 0) - renderPage0ToSprite(sprTrans, -shift, y_slice, slice_h); - else if (fromPage == 1) - renderPage1ToSprite(sprTrans, -shift, y_slice, slice_h); - else if (fromPage == 2) - renderPage2ToSprite(sprTrans, -shift, y_slice, slice_h); - else - renderPage3ToSprite(sprTrans, -shift, y_slice, slice_h); - } - if (splitX < 320) { - sprTrans.setViewport(splitX, 0, 320 - splitX, slice_h, false); - if (toPage == 0) - renderPage0ToSprite(sprTrans, 320 - shift, y_slice, slice_h); - else if (toPage == 1) - renderPage1ToSprite(sprTrans, 320 - shift, y_slice, slice_h); - else if (toPage == 2) - renderPage2ToSprite(sprTrans, 320 - shift, y_slice, slice_h); - else - renderPage3ToSprite(sprTrans, 320 - shift, y_slice, slice_h); - } - } else { - // Swipe Right: toPage on left moving right, fromPage on right moving - // right - if (splitX > 0) { - sprTrans.setViewport(0, 0, splitX, slice_h, false); - if (toPage == 0) - renderPage0ToSprite(sprTrans, shift - 320, y_slice, slice_h); - else if (toPage == 1) - renderPage1ToSprite(sprTrans, shift - 320, y_slice, slice_h); - else if (toPage == 2) - renderPage2ToSprite(sprTrans, shift - 320, y_slice, slice_h); - else - renderPage3ToSprite(sprTrans, shift - 320, y_slice, slice_h); - } - if (splitX < 320) { - sprTrans.setViewport(splitX, 0, 320 - splitX, slice_h, false); - if (fromPage == 0) - renderPage0ToSprite(sprTrans, shift, y_slice, slice_h); - else if (fromPage == 1) - renderPage1ToSprite(sprTrans, shift, y_slice, slice_h); - else if (fromPage == 2) - renderPage2ToSprite(sprTrans, shift, y_slice, slice_h); - else - renderPage3ToSprite(sprTrans, shift, y_slice, slice_h); - } - } - sprTrans.resetViewport(); - - // Dividing neon seam line - if (splitX > 0 && splitX < 320) { - sprTrans.drawFastVLine(splitX, 0, slice_h, getThemePrimaryColor()); - int16_t whiteLineX = splitX + (swipeLeft ? 1 : -1); - if (whiteLineX >= 0 && whiteLineX < 320) { - sprTrans.drawFastVLine(whiteLineX, 0, slice_h, TFT_WHITE); - } - } - - // Pagination indicator dots on top slice (4 dots for 4 pages) - if (y_slice == 0) { - uint8_t activeDot = (shift < 160) ? fromPage : toPage; - sprTrans.fillCircle(139, 2, 2, - (activeDot == 0) ? getThemePrimaryColor() : 0x5AEB); - sprTrans.fillCircle(153, 2, 2, - (activeDot == 1) ? getThemePrimaryColor() : 0x5AEB); - sprTrans.fillCircle(167, 2, 2, - (activeDot == 2) ? getThemePrimaryColor() : 0x5AEB); - sprTrans.fillCircle(181, 2, 2, - (activeDot == 3) ? getThemePrimaryColor() : 0x5AEB); - } - - // Direct single SPI push per slice: ZERO visual flicker! - sprTrans.pushSprite(0, y_slice); - yield(); + // Pagination indicator dots on top slice (4 dots for 4 pages) + if (y_slice == 0) { + sprTrans.fillCircle(139, 2, 2, + (toPage == 0) ? getThemePrimaryColor() : 0x5AEB); + sprTrans.fillCircle(153, 2, 2, + (toPage == 1) ? getThemePrimaryColor() : 0x5AEB); + sprTrans.fillCircle(167, 2, 2, + (toPage == 2) ? getThemePrimaryColor() : 0x5AEB); + sprTrans.fillCircle(181, 2, 2, + (toPage == 3) ? getThemePrimaryColor() : 0x5AEB); } - yield(); // Feed watchdog and let Bluetooth run smoothly on background core - delay(7); // Ultra-smooth high density pacing (~330ms total) + sprTrans.pushSprite(0, y_slice); } + // 3. Smoothly fade backlight back up to original level in ~200ms + for (uint8_t i = 1; i <= fadeSteps; i++) { + float t = (float)i / (float)fadeSteps; + uint8_t b = (uint8_t)(startBrightness * (t * t)); + ledcWrite(backlightChannel, b); + delay(12); + } + currentBacklight = (float)startBrightness; + ledcWrite(backlightChannel, (uint32_t)targetBacklight); + sprTrans.fontLoaded = false; } @@ -2024,7 +1991,7 @@ inline void performScreenTransition(bool swipeLeft, int8_t fromPage = -1, if (transitionType == 1) { animateFadeTransition(fromPage, toPage); } else { - animateSlideTransition(swipeLeft, fromPage, toPage); + animateBlackTransition(toPage); } } diff --git a/sketch/obd2_gauge/obd2_gauge.ino b/sketch/obd2_gauge/obd2_gauge.ino index 794190a..f1e8503 100644 --- a/sketch/obd2_gauge/obd2_gauge.ino +++ b/sketch/obd2_gauge/obd2_gauge.ino @@ -126,7 +126,7 @@ struct GaugeTheme { uint8_t ledB; // Rear RGB LED Blue (0..255) }; -const GaugeTheme GAUGE_THEMES[6] = { +const GaugeTheme GAUGE_THEMES[9] = { {"Azul Cian", "Digital Moderno", 0x07FF, 0x03B9, 0x0254, 0x0110, 0x01D3, 0, 180, 255}, // 0: Vivid Electric Cyan {"Verde Sport", "Cockpit Clasico", 0x07E0, 0x0446, 0x0283, 0x0141, 0x0205, @@ -136,47 +136,54 @@ const GaugeTheme GAUGE_THEMES[6] = { {"Blanco Puro", "OLED Precision", 0xFFFF, 0x6B6D, 0x4228, 0x18E3, 0x2965, 220, 230, 255}, // 3: Pure Crisp White {"Naranja Sport", "McLaren Papaya", 0xFD20, 0x9260, 0x5160, 0x20A0, 0x4140, - 255, 80, 0}, // 4: Vivid Papaya Orange (reemplaza rojo) + 255, 80, 0}, // 4: Vivid Papaya Orange {"Cyber Violet", "Neon Nightrunner", 0xD1BF, 0x7119, 0x40CE, 0x1846, 0x2869, - 200, 0, 255} // 5: Vivid Electric Violet + 200, 0, 255}, // 5: Vivid Electric Violet + {"Rojo Corsa", "Pasion Italiana", 0xF800, 0x8800, 0x5000, 0x2000, 0x3800, + 255, 0, 0}, // 6: Vivid Racing Red + {"Ambar BMW", "Cockpit Nocturno", 0xFD60, 0x9380, 0x5240, 0x2100, 0x39A0, + 255, 120, 0}, // 7: Warm Golden Amber (BMW classic) + {"Yapa", "Pastillas Pastel", 0xFD79, 0x3A53, 0x52AA, 0x18E5, 0x2168, + 255, 190, 220} // 8: Pastillas Yapa Pastel Candies }; uint8_t themeIndex = 0; // Default 0: Azul Cian uint8_t transitionType = - 1; // Default 1: Desvanecimiento (Fade), 0: Deslizamiento (Slide) + 0; // Default 0: Fundido a Negro (Fade to Black), 1: Desvanecimiento (Fade) inline uint16_t getThemePrimaryColor() { - if (themeIndex >= 6) + if (themeIndex >= 9) return 0x07FF; return GAUGE_THEMES[themeIndex].primaryColor; } inline uint16_t getThemeAccentColor() { - if (themeIndex >= 6) + if (themeIndex >= 9) return 0x234F; return GAUGE_THEMES[themeIndex].accentColor; } inline uint16_t getThemeDivColor() { - if (themeIndex >= 6) + if (themeIndex >= 9) return 0x3412; return GAUGE_THEMES[themeIndex].divColor; } inline uint16_t getThemeDarkColor() { - if (themeIndex >= 6) + if (themeIndex >= 9) return 0x1105; return GAUGE_THEMES[themeIndex].darkColor; } inline uint16_t getThemeBgColor() { - if (themeIndex >= 6) + if (themeIndex >= 9) return 0x19C8; return GAUGE_THEMES[themeIndex].bgColor; } -// Rainbow theme helpers (theme index 2) +// Special theme helpers inline bool isRainbowTheme() { return themeIndex == 2; } +inline bool isYapaTheme() { return themeIndex == 8; } // Convert hue (0..359, full saturation/value) to RGB565 inline uint16_t hueToRgb565(uint16_t hue) { @@ -249,6 +256,10 @@ inline uint16_t getThemeCellColor(uint8_t cell) { if (isRainbowTheme()) { return getRainbowCellColor(cell); } + if (isYapaTheme()) { + const uint16_t yapaCell[4] = { 0xFD79, 0x971F, 0xFFF2, 0xA7D7 }; + return yapaCell[cell % 4]; + } return getThemePrimaryColor(); } @@ -321,8 +332,8 @@ const uint8_t MAX_TERMINAL_LINES = 13; String line[MAX_TERMINAL_LINES]; // buffer for each line in terminal function uint8_t terminalLineCount = 0; bool dim = false; // back light dim flag -const uint8_t LIGHT_LEVEL = - 40; // backlight control by light level Higher = darker light +const uint16_t LIGHT_LEVEL = + 1000; // Umbral de luz ambiental dia/noche para ADC de 12 bits (0..4095) // BLUETOOTH String bt_message = ""; // bluetooth message buffer @@ -693,11 +704,11 @@ recent_client_addr : {0x00,0x00,0x00,0x00,0x00,0x00} array of bytes[6] if (brightnessIndex > 5) brightnessIndex = 0; themeIndex = pref.getUChar("theme_idx", 0); - if (themeIndex > 5) + if (themeIndex > 8) themeIndex = 0; - transitionType = pref.getUChar("trans_type", 1); + transitionType = pref.getUChar("trans_type", 0); if (transitionType > 1) - transitionType = 1; + transitionType = 0; currentScreenPage = pref.getChar("scr_page", 0); if (currentScreenPage < 0 || currentScreenPage > 3) currentScreenPage = 0; @@ -714,7 +725,7 @@ recent_client_addr : {0x00,0x00,0x00,0x00,0x00,0x00} array of bytes[6] Terminal("[ OK ] Settings: Theme " + String(GAUGE_THEMES[themeIndex].name) + " (LED RGB)"); Terminal("[ OK ] Settings: Transicion " + - String(transitionType == 0 ? "Deslizamiento" : "Desvanecimiento")); + String(transitionType == 0 ? "Fundido a Negro" : "Desvanecimiento")); delay(40); // Initialize persistent transition sprites (pre-allocated once at boot in clean heap) @@ -1035,6 +1046,14 @@ void loop() { } } + // AUTO-DIM BACKLIGHT: Control continuo de brillo en todas las pantallas + // Funciona siempre, este o no conectado el Bluetooth + static uint32_t lastContinuousDimCheck = 0; + if (millis() - lastContinuousDimCheck >= 50) { + lastContinuousDimCheck = millis(); + autoDim(); + } + #ifndef SKIP_CONNECTION // Check Bluetooth connection state: if adapter disconnected or lost power, // switch immediately to GPS Clock Screen (Page 2) and show "---" on OBD diff --git a/sketch/obd2_gauge/rainbow_text.h b/sketch/obd2_gauge/rainbow_text.h index a72e04fa5ad8ba360e20d3a35216e07a4535d320..4b5414204c97b746a177a9a814657c827da08b64 100644 GIT binary patch delta 3204 zcmd5;TW=dh6c(rm$+RjLr9y+0-grXdFK}jNcVjz=lk_6tm))H?*YBL~9RG3SpKpHo zDWtoS3JzPo?*R+ygz+uE-)PegwX(&``0WPilJqK*&qIv_l6f8pKwSZIe9j!`;wkmO zA>4h4A0CyGf>rkeVneAz1Zwy;tWhqB9l{FrD9YPV?g{dU0q;oRJA&f{!uImDivL2ou@;d2$mMhi`v{BDqLy;PepYaiR87#D^)ESCzVRAFzWe< zRJHQyyq5Zkh>-AaNj^?J6~_y8o+y;BU$24+G)5+WduJ~#e9vYE#Y~2Ak8!#OSBpwj zl@;Ifg{46J3^8QfrMp>;l%TRFKH<=9b((?_x7qiFYh64DWvvAt!NMl?#NhYGf1iH! zK_MbD9}a&#^XKdF`%+pcr%i-*i#1`vNY}s@FcuZd!spuQoVT^R=*juNjNiu}TzxG}8op6& zH0gTnCdh5M2knl}TyO>H$23`&g7KDR+SGhpgg^=#VKyxrNFwCDiSG?t7fF?4+vn0$ zLu=f^Bc~_sA#O%f#jDVSt={(i`Aya11se)jZa`fDR=^%nRaU$y6!Ed7{Lgumw`AMO zM*f)#50q*_sX9sv(%76no?(7-5?Owixu}4mwuvbg-3H5n zyEkXv9cZ(YWr5}Bx3^|P$e|qMVIDh2DJzEol@0W1Ix}vTk!yu;46P1G`Lhim2%Uz^ z*cu50Z4WYolmuy|!njl`IBf)ttxhSJgK|{48>`^q6+`T$FNt?|AC{^PPBX-Zl^uzC z%&sbQ>=7;}h7c>2-u^yAQ!xzt;W!|5oq6c@>#Wt5M`tdS*o#X%&iiayE|Yll@9g{G zGbD$@uZ(R0MD;h44*G2>C{*rL>KhQ?fk#0v8UaWP-s@~p0c~7N1YCwoiaW@xhYn82 z7KHJF(mTs+DLsRd37R&K5;z*T6LP$X%kbD7PsX;6@20zp{ZdfP@qgidco_G`=X?@= zE;mB+7cm8}mB!}LVPilq!j~`t!g$V4odGcwCYT`2L_2B(jPxMdndE<%PL=}(fKq+Q z!_>@DI5NJS1?H}v#s?z?q3`KQK%xj= dst.height()) continue; @@ -151,7 +167,8 @@ inline void draw7SegDegreeC(TFT_eSPI &dst, int bx, int by, uint16_t color = 0xC6 if (px < 0 || px >= dst.width()) continue; uint8_t a = pgm_read_byte(&s_dsegUnitAlpha[rowOffset + i]); if (a > 8) { - uint16_t pixCol = (a >= 250) ? color : fastBlend(a, color, bgCol); + uint16_t curCol = rainbow ? getRainbowScreenColor(px, py) : color; + uint16_t pixCol = (a >= 250) ? curCol : fastBlend(a, curCol, bgCol); dst.drawPixel(px, py, pixCol); } } @@ -225,7 +242,7 @@ inline void drawTriTempCard(uint8_t cardIdx, float val, int x_offset = 0) { isInt = true; float warnT = warningValue[0].toFloat(); if (warnT <= 0) warnT = 110.0f; - if (val < (warnT - 10.0f)) accentCol = isRainbowTheme() ? (uint16_t)0x07FF : getThemePrimaryColor(); + if (val < (warnT - 10.0f)) accentCol = isRainbowTheme() ? (uint16_t)0x07FF : (isYapaTheme() ? getYapaColor(0) : getThemePrimaryColor()); else if (val < warnT) accentCol = TFT_YELLOW; else accentCol = TFT_RED; } else if (cardIdx == 1) { @@ -233,32 +250,57 @@ inline void drawTriTempCard(uint8_t cardIdx, float val, int x_offset = 0) { subStr = (dsSensorCount > 0) ? "DS18B20 SENSOR 1 (GPIO 22)" : "DS18B20 SENSOR 1"; minRange = 10.0f; maxRange = 40.0f; - accentCol = isRainbowTheme() ? (uint16_t)0x07E0 : getThemeAccentColor(); // Green in rainbow + accentCol = isRainbowTheme() ? (uint16_t)0x07E0 : (isYapaTheme() ? getYapaColor(1) : getThemeAccentColor()); } else { titleStr = "EXTERIOR"; subStr = (dsSensorCount > 1) ? "DS18B20 SENSOR 2 (GPIO 22)" : (dsSensorCount == 1 ? "ESTIMADA (-4.5`C)" : "DS18B20 SENSOR 2"); minRange = -10.0f; maxRange = 50.0f; - accentCol = isRainbowTheme() ? (uint16_t)0xFD20 : getThemePrimaryColor(); // Orange in rainbow + accentCol = isRainbowTheme() ? (uint16_t)0xFD20 : (isYapaTheme() ? getYapaColor(2) : getThemePrimaryColor()); } // Left vertical accent bar tft.fillRoundRect(cardX + 6, cardY + 6, 4, cardH - 12, 2, accentCol); - // Titles (Smooth anti-aliased font) - if (!sprTriTitleReady) initTriTempTitles(); - if (sprTriTitleReady && sprTriTitle[cardIdx].created()) { - sprTriTitle[cardIdx].pushSprite(cardX + 16, cardY + 5); - } else { + if (isYapaTheme()) { tft.loadFont(Latin_Sans_Bold_26); - tft.setTextPadding(0); - tft.setTextColor(TFT_WHITE, cardBg); - tft.drawString(titleStr, cardX + 16, cardY + 5, 1); + int curX = cardX + 16; + char buf[2] = {0, 0}; + uint8_t cIdx = 0; + for (int i = 0; titleStr[i] != '\0'; i++) { + char c = titleStr[i]; + buf[0] = c; + if (c == ' ') { curX += tft.textWidth(buf); continue; } + tft.setTextColor(getYapaColor(cIdx++), cardBg); + tft.drawString(buf, curX, cardY + 5); + curX += tft.textWidth(buf); + } tft.unloadFont(); - } + int sX = cardX + 16; + for (int i = 0; subStr[i] != '\0'; i++) { + char c = subStr[i]; + buf[0] = c; + int cw = tft.textWidth(buf, 1); + if (c == ' ') { sX += cw; continue; } + tft.setTextColor(getYapaColor(cIdx++), cardBg); + tft.drawString(buf, sX, cardY + 32, 1); + sX += cw; + } + } else { + if (!sprTriTitleReady) initTriTempTitles(); + if (sprTriTitleReady && sprTriTitle[cardIdx].created()) { + sprTriTitle[cardIdx].pushSprite(cardX + 16, cardY + 5); + } else { + tft.loadFont(Latin_Sans_Bold_26); + tft.setTextPadding(0); + tft.setTextColor(TFT_WHITE, cardBg); + tft.drawString(titleStr, cardX + 16, cardY + 5, 1); + tft.unloadFont(); + } - tft.setTextColor(0x8410, cardBg); // Soft gray - tft.drawString(subStr, cardX + 16, cardY + 32, 1); + tft.setTextColor(0x8410, cardBg); // Soft gray + tft.drawString(subStr, cardX + 16, cardY + 32, 1); + } // 3. Horizontal Progress Gauge Bar int barX = cardX + 16; @@ -294,7 +336,9 @@ inline void drawTriTempCard(uint8_t cardIdx, float val, int x_offset = 0) { drawDseg7RightString34(tft, numStr.c_str(), cardX + 262, cardY + 20, accentCol, cardBg, 100, 240, rainbowTxt); // Unit "°C" in authentic digital 7-segment italic typography matching numbers - draw7SegDegreeC(tft, cardX + 268, cardY + 22, isRainbowTheme() ? getRainbowScreenColor(cardX + 268, cardY + 22) : 0xC618, cardBg); + bool rainbowUnit = isRainbowTheme() && (accentCol != TFT_RED); + uint16_t unitCol = rainbowUnit ? getRainbowScreenColor(cardX + 268, cardY + 22) : (isYapaTheme() && accentCol != TFT_RED ? getYapaColor(numStr.length()) : (accentCol == TFT_RED ? TFT_RED : 0xC618)); + draw7SegDegreeC(tft, cardX + 268, cardY + 22, unitCol, cardBg, rainbowUnit); } // Flicker-free dynamic value update for a single temperature card @@ -330,24 +374,42 @@ inline void updateTriTempCardValue(uint8_t cardIdx, float val, float oldVal) { oldTriIgnition = ignitionOn; const char* subStr = ignitionOn ? "ECU OBD-II (PID 0105)" : "ECU OBD-II (STANDBY)"; tft.fillRect(cardX + 16, cardY + 32, 130, 10, cardBg); - tft.setTextColor(0x8410, cardBg); - tft.drawString(subStr, cardX + 16, cardY + 32, 1); + if (isYapaTheme()) { + char buf[2] = {0, 0}; + int sX = cardX + 16; + uint8_t cIdx = 4; + for (int i = 0; subStr[i] != '\0'; i++) { + char c = subStr[i]; + buf[0] = c; + int cw = tft.textWidth(buf, 1); + if (c == ' ') { sX += cw; continue; } + tft.setTextColor(getYapaColor(cIdx++), cardBg); + tft.drawString(buf, sX, cardY + 32, 1); + sX += cw; + } + } else { + tft.setTextColor(0x8410, cardBg); + tft.drawString(subStr, cardX + 16, cardY + 32, 1); + } } } else if (cardIdx == 1) { minRange = 10.0f; maxRange = 40.0f; - accentCol = isRainbowTheme() ? (uint16_t)0x07E0 : getThemeAccentColor(); + accentCol = isRainbowTheme() ? (uint16_t)0x07E0 : (isYapaTheme() ? getYapaColor(1) : getThemeAccentColor()); oldAccentCol = accentCol; } else { minRange = -10.0f; maxRange = 50.0f; - accentCol = isRainbowTheme() ? (uint16_t)0xFD20 : getThemePrimaryColor(); + accentCol = isRainbowTheme() ? (uint16_t)0xFD20 : (isYapaTheme() ? getYapaColor(2) : getThemePrimaryColor()); oldAccentCol = accentCol; } - // 1. Accent color changed: update vertical accent bar + // 1. Accent color changed: update vertical accent bar and unit "°C" if (accentCol != oldAccentCol) { tft.fillRoundRect(cardX + 6, cardY + 6, 4, cardH - 12, 2, accentCol); + bool rainbowUnit = isRainbowTheme() && (accentCol != TFT_RED); + uint16_t unitCol = rainbowUnit ? getRainbowScreenColor(cardX + 268, cardY + 22) : (isYapaTheme() && accentCol != TFT_RED ? getYapaColor(3) : (accentCol == TFT_RED ? TFT_RED : 0xC618)); + draw7SegDegreeC(tft, cardX + 268, cardY + 22, unitCol, cardBg, rainbowUnit); } // 2. Horizontal Progress Gauge Bar update (only redraw when pixel fill changes or accent changes) @@ -393,9 +455,9 @@ inline void syncTriTempTrackers() { float minR[3] = { 60.0f, 10.0f, -10.0f }; float maxR[3] = { 120.0f, 40.0f, 50.0f }; uint16_t defAcc[3] = { - isRainbowTheme() ? (uint16_t)0x07FF : getThemePrimaryColor(), - isRainbowTheme() ? (uint16_t)0x07E0 : getThemeAccentColor(), - isRainbowTheme() ? (uint16_t)0xFD20 : getThemePrimaryColor() + isRainbowTheme() ? (uint16_t)0x07FF : (isYapaTheme() ? getYapaColor(0) : getThemePrimaryColor()), + isRainbowTheme() ? (uint16_t)0x07E0 : (isYapaTheme() ? getYapaColor(1) : getThemeAccentColor()), + isRainbowTheme() ? (uint16_t)0xFD20 : (isYapaTheme() ? getYapaColor(2) : getThemePrimaryColor()) }; for (int i = 0; i < 3; i++) { float ratio = (vals[i] - minR[i]) / (maxR[i] - minR[i]);