#pragma once #include "DSEG7_Modern_BoldItalic_34.h" #include "DSEG7_Modern_BoldItalic_80.h" #include "Latin_Sans_Bold_26.h" #include "rainbow_text.h" #include "gps_module.h" #include #include extern TFT_eSPI tft; extern uint16_t getThemePrimaryColor(); extern uint16_t getThemeAccentColor(); extern uint16_t getThemeBgColor(); extern bool isRainbowTheme(); extern int8_t currentScreenPage; extern int8_t fullScreenCell; // Spanish 3-letter Month abbreviations static const char *const SPANISH_MONTHS_SHORT[12] = { "ENE", "FEB", "MAR", "ABR", "MAY", "JUN", "JUL", "AGO", "SEP", "OCT", "NOV", "DIC"}; // Previous values for flicker-free redrawing static uint8_t oldClockHour = 255; static uint8_t oldClockMin = 255; static uint8_t oldClockSec = 255; static uint8_t oldClockDay = 255; static uint8_t oldClockMonth = 255; static uint16_t oldClockYear = 0; // Smooth pre-rendered title sprites for Day of Week and Year (100% anti-aliased Latin_Sans_Bold_26 matching Page 1) 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()) { sprDayTitle.createSprite(160, 26); } if (sprDayTitle.created()) { uint16_t cardBg = 0x10A2; sprDayTitle.loadFont(Latin_Sans_Bold_26); sprDayTitle.setTextPadding(0); sprDayTitle.fillSprite(cardBg); 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); } sprDayWday = wday; } } static TFT_eSprite sprYearTitle(&tft); static uint16_t sprYearVal = 0; static uint16_t sprYearCol = 0; inline void updateYearTitleSprite(uint16_t year, uint16_t priCol) { if (sprYearVal == year && sprYearCol == priCol && sprYearTitle.created()) return; if (!sprYearTitle.created()) { sprYearTitle.createSprite(80, 26); } if (sprYearTitle.created()) { uint16_t cardBg = 0x10A2; sprYearTitle.loadFont(Latin_Sans_Bold_26); sprYearTitle.setTextPadding(0); sprYearTitle.fillSprite(cardBg); 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); } sprYearVal = year; sprYearCol = priCol; } } inline void initGpsClockTitles() { updateDayTitleSprite(gpsDayOfWeek % 7); updateYearTitleSprite(gpsYear, isRainbowTheme() ? (uint16_t)0xFFE0 : getThemeAccentColor()); } // Draw Full-Width Date Card (Bottom) - Clean, prominent, automotive badge // layout inline void drawDateCardContent(int cardX, int cardY, int cardW, int cardH, uint16_t cardBg, int x_offset = 0) { if (cardX + cardW <= 0 || cardX >= 320) return; uint16_t accCol = isRainbowTheme() ? (uint16_t)0xFD20 : getThemeAccentColor(); uint16_t yearCol = isRainbowTheme() ? (uint16_t)0xFFE0 : accCol; uint16_t cardBorder = 0x2945; uint8_t wday = gpsDayOfWeek % 7; uint8_t mIdx = (gpsMonth >= 1 && gpsMonth <= 12) ? (gpsMonth - 1) : 0; String mShort = SPANISH_MONTHS_SHORT[mIdx]; String dayStr = (gpsDay < 10) ? "0" + String(gpsDay) : String(gpsDay); // Left vertical accent bar tft.fillRoundRect(cardX + 6, cardY + 8, 4, cardH - 16, 2, accCol); // --- CALENDAR BADGE (Left side: 80x82 px) --- int bx = cardX + 20; int by = cardY + 14; int bw = 80; int bh = 82; // Badge body tft.fillRoundRect(bx, by, bw, bh, 6, 0x18C3); tft.drawRoundRect(bx, by, bw, bh, 6, cardBorder); // Badge top header (Month abbreviated in Accent Color) 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.setTextPadding(0); 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) if (isYapaTheme()) { tft.loadFont(Latin_Sans_Bold_26); 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, isYapaTheme() ? getYapaColor(0) : accCol); // Year in Latin Sans Bold 26 (Smooth antialiased, Accent color) if (isYapaTheme()) { tft.loadFont(Latin_Sans_Bold_26); 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(); } } } // Authentic DSEG7 antialiased dot alpha matrix (from // DSEG7_Modern_BoldItalic_80) static const uint8_t DSEG7_DOT_ALPHA[11][13] PROGMEM = { {0x21, 0xEB, 0xEE, 0xED, 0xEE, 0xEE, 0xEE, 0xEE, 0xEE, 0xEC, 0xEE, 0xD7, 0x0A}, {0x35, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xDC, 0x00}, {0x47, 0xFE, 0xFB, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFB, 0xFE, 0xC5, 0x00}, {0x5C, 0xFF, 0xFB, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, 0xFF, 0xB2, 0x00}, {0x71, 0xFF, 0xFB, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFB, 0xFF, 0x9E, 0x00}, {0x86, 0xFF, 0xFB, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFB, 0xFF, 0x88, 0x00}, {0x9C, 0xFF, 0xFB, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFB, 0xFF, 0x73, 0x00}, {0xB1, 0xFF, 0xFC, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFB, 0xFF, 0x5E, 0x00}, {0xC5, 0xFF, 0xFC, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFD, 0xFF, 0x49, 0x00}, {0xD7, 0xFF, 0xFD, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0xFF, 0x35, 0x00}, {0xE6, 0xFF, 0xFD, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0xFF, 0xFC, 0x24, 0x00}, }; // Helper: Draw authentic 7-segment italic colon dots with true anti-aliasing // matching DSEG7 typography and optional rainbow gradient template inline void draw7SegDot(T &dst, int x0, int y0, uint16_t color, uint16_t bgCol, int clip_h = 240, bool rainbow = false) { dst.startWrite(); for (int dy = 0; dy < 11; dy++) { int curY = y0 + dy; if (curY < 0 || curY >= clip_h) continue; int fl = 0; int fxs = 0; for (int dx = 0; dx < 13; dx++) { uint8_t a = pgm_read_byte(&(DSEG7_DOT_ALPHA[dy][dx])); int curX = x0 + dx; if (a == 0) { if (fl) { dst.drawFastHLine(fxs, curY, fl, color); fl = 0; } dst.drawPixel(curX, curY, bgCol); } else if (a >= 235) { if (fl == 0) fxs = curX; fl++; } else { if (fl) { dst.drawFastHLine(fxs, curY, fl, color); fl = 0; } uint16_t fg = rainbow ? getRainbowScreenColor(curX, curY) : color; dst.drawPixel(curX, curY, dst.alphaBlend(a, fg, bgCol)); } } if (fl) { if (rainbow) { for (int i = 0; i < fl; i++) { dst.drawPixel(fxs + i, curY, getRainbowScreenColor(fxs + i, curY)); } } else { dst.drawFastHLine(fxs, curY, fl, color); } } } dst.endWrite(); } template inline void draw7SegColon(T &dst, int clkX, int clkY, uint16_t color, uint16_t bgCol = 0x10A2, int clip_h = 240, bool rainbow = false) { // Top dot: centered at clkX + 151, font row 19..29 (y = clkY + 30) draw7SegDot(dst, clkX + 151, clkY + 30, color, bgCol, clip_h, rainbow); // Bottom dot: centered at clkX + 148, font row 53..63 (y = clkY + 64) draw7SegDot(dst, clkX + 148, clkY + 64, color, bgCol, clip_h, rainbow); } // Synchronize tracking variables without touching TFT display (zero flicker) inline void syncGpsClockTrackers() { oldClockHour = gpsHour; oldClockMin = gpsMinute; oldClockSec = gpsSecond; oldClockDay = gpsDay; oldClockMonth = gpsMonth; oldClockYear = gpsYear; } // Draw full Page 2 (GPS Clock Screen) inline void drawGpsClockScreen(int x_offset = 0) { if (x_offset == 0) { tft.fillScreen(TFT_BLACK); } else { tft.fillRect(0, 0, x_offset, 240, TFT_BLACK); } uint16_t priCol = getThemePrimaryColor(); uint16_t clkAccCol = isRainbowTheme() ? (uint16_t)0x07FF : priCol; uint16_t cardBg = 0x10A2; // Dark graphite navy uint16_t cardBorder = 0x2945; // Subtle edge border bool synced = isGpsSynced(); // Center: 5 Pagination Indicator Dots [○ ○ ● ○ ○] (Page 2 of 5 active) tft.fillCircle(132 + x_offset, 2, 2, 0x5AEB); // Page 0 inactive tft.fillCircle(146 + x_offset, 2, 2, 0x5AEB); // Page 1 inactive tft.fillCircle(160 + x_offset, 2, 2, priCol); // Page 2 active tft.fillCircle(174 + x_offset, 2, 2, 0x5AEB); // Page 3 inactive tft.fillCircle(188 + x_offset, 2, 2, 0x5AEB); // Page 4 inactive // 2. MAIN CLOCK CARD (y = 19 to 121, height = 102, width = 312, x = 4) int clkX = 4 + x_offset; int clkY = 19; int clkW = 312; int clkH = 102; tft.fillRoundRect(clkX, clkY, clkW, clkH, 6, cardBg); tft.drawRoundRect(clkX, clkY, clkW, clkH, 6, cardBorder); // Left vertical accent bar tft.fillRoundRect(clkX + 6, clkY + 8, 4, clkH - 16, 2, clkAccCol); // Digits: Hours HH & Minutes MM with smooth rainbow gradient uint8_t h10 = (gpsHour / 10) % 10; uint8_t h1 = gpsHour % 10; 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, 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) bool colOn = (gpsSecond % 2 == 0); uint16_t colonCol = colOn ? clkAccCol : 0x18C3; draw7SegColon(tft, clkX, clkY, colonCol, cardBg, 240, colOn && rbClk); // 3. FULL-WIDTH DATE CARD (y = 125 to 235, height = 110, width = 312, x = 4) int dateX = 4 + x_offset; int dateY = 125; int dateW = 312; int dateH = 110; tft.fillRoundRect(dateX, dateY, dateW, dateH, 6, cardBg); tft.drawRoundRect(dateX, dateY, dateW, dateH, 6, cardBorder); drawDateCardContent(dateX, dateY, dateW, dateH, cardBg, x_offset); // Sync state trackers syncGpsClockTrackers(); } // 100% Flicker-Free Live Refresh for resting Page 2 inline void updateGpsClockScreen(bool forceRedraw = false) { if (currentScreenPage != 2 || fullScreenCell != -1) return; uint16_t priCol = getThemePrimaryColor(); uint16_t clkAccCol = isRainbowTheme() ? (uint16_t)0x07FF : priCol; uint16_t cardBg = 0x10A2; int clkX = 4; int clkY = 19; // 1. Seconds change: update colon dots only (no seconds text) if (forceRedraw || gpsSecond != oldClockSec) { oldClockSec = gpsSecond; bool colOn = (gpsSecond % 2 == 0); uint16_t colonCol = colOn ? clkAccCol : 0x18C3; draw7SegColon(tft, clkX, clkY, colonCol, cardBg, 240, colOn && isRainbowTheme()); } // 2. Minutes change: update MM digits if (forceRedraw || gpsMinute != oldClockMin) { oldClockMin = gpsMinute; tft.fillRect(clkX + 166, clkY + 10, 142, 82, cardBg); uint8_t m10 = (gpsMinute / 10) % 10; uint8_t m1 = gpsMinute % 10; 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 if (forceRedraw || gpsHour != oldClockHour) { oldClockHour = gpsHour; tft.fillRect(clkX + 12, clkY + 10, 136, 82, cardBg); uint8_t h10 = (gpsHour / 10) % 10; uint8_t h1 = gpsHour % 10; 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 if (forceRedraw || gpsDay != oldClockDay || gpsMonth != oldClockMonth || gpsYear != oldClockYear) { oldClockDay = gpsDay; oldClockMonth = gpsMonth; oldClockYear = gpsYear; int dateX = 4; int dateY = 125; int dateW = 312; int dateH = 110; tft.fillRoundRect(dateX, dateY, dateW, dateH, 6, cardBg); tft.drawRoundRect(dateX, dateY, dateW, dateH, 6, 0x2945); drawDateCardContent(dateX, dateY, dateW, dateH, cardBg, 0); } } // Render Page 2 into double-buffered sprite slice for slide / fade transitions inline void renderPage2ToSprite(TFT_eSprite &s, int x_offset, int y_slice, int slice_h) { uint16_t priCol = getThemePrimaryColor(); uint16_t accCol = getThemeAccentColor(); uint16_t clkAccCol = isRainbowTheme() ? (uint16_t)0x07FF : priCol; uint16_t cardBg = 0x10A2; uint16_t cardBorder = 0x2945; // 1. TOP BAR in top slice (pagination dots rendered by caller or top bar) // 2. MAIN CLOCK CARD (y = 19 to 121) int clkX = 4 + x_offset; int clkY = 19 - y_slice; int clkW = 312; int clkH = 102; if (clkX + clkW > 0 && clkX < 320 && clkY + clkH > 0 && clkY < slice_h) { s.fillRoundRect(clkX, clkY, clkW, clkH, 6, cardBg); s.drawRoundRect(clkX, clkY, clkW, clkH, 6, cardBorder); s.fillRoundRect(clkX + 6, clkY + 8, 4, clkH - 16, 2, clkAccCol); // Colon dots during transition (italic 7-segment style with anti-aliasing) draw7SegColon(s, clkX, clkY, clkAccCol, cardBg, slice_h, isRainbowTheme()); // Digits: Hours HH (Right aligned to clkX + 142) & Minutes MM (Left aligned // to clkX + 170) int digitY = clkY + 11; if (digitY + 80 > 0 && digitY < slice_h) { uint8_t h10 = (gpsHour / 10) % 10; uint8_t h1 = gpsHour % 10; uint8_t m10 = (gpsMinute / 10) % 10; uint8_t m1 = gpsMinute % 10; 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()); } } // 3. FULL-WIDTH DATE CARD (y = 125 to 235) - Calendar Badge + Details int dateX = 4 + x_offset; int dateY = 125 - y_slice; int dateW = 312; int dateH = 110; if (dateX + dateW > 0 && dateX < 320 && dateY + dateH > 0 && dateY < slice_h) { uint16_t dateAccCol = isRainbowTheme() ? (uint16_t)0xFD20 : accCol; uint16_t yearCol = isRainbowTheme() ? (uint16_t)0xFFE0 : accCol; s.fillRoundRect(dateX, dateY, dateW, dateH, 6, cardBg); s.drawRoundRect(dateX, dateY, dateW, dateH, 6, cardBorder); s.fillRoundRect(dateX + 6, dateY + 8, 4, dateH - 16, 2, dateAccCol); // Badge container int bx = dateX + 20; int by = dateY + 14; int bw = 80; int bh = 82; if (by + bh > 0 && by < slice_h) { s.fillRoundRect(bx, by, bw, bh, 6, 0x18C3); s.drawRoundRect(bx, by, bw, bh, 6, cardBorder); 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 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; uint8_t wday = gpsDayOfWeek % 7; int dwY = dateY + 22; if (dwY + 26 > 0 && dwY < slice_h) { updateDayTitleSprite(wday); if (sprDayTitle.created()) { sprDayTitle.pushToSprite(&s, tx, dwY); } else { s.setTextColor(TFT_WHITE, cardBg); s.drawString(SPANISH_DAYS[wday], tx, dwY, 4); } } int divY = dateY + 54; if (divY > 0 && divY < slice_h) { s.drawFastHLine(tx, divY, 180, cardBorder); s.drawFastHLine(tx, divY, 50, dateAccCol); } int yrY = dateY + 64; if (yrY + 26 > 0 && yrY < slice_h) { updateYearTitleSprite(gpsYear, yearCol); if (sprYearTitle.created()) { sprYearTitle.pushToSprite(&s, tx, yrY); } else { s.setTextColor(yearCol, cardBg); s.drawString(String(gpsYear).c_str(), tx, yrY, 4); } } } }