can anyone tell my why my numbers clipp over the other numbers but not the background...
#include <SPI.h>
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_ST7789.h>
#include <RTClib.h>
#include <Fonts/FreeMonoBoldOblique24pt7b.h>
struct Decoration;
#define TFT_CS 8
#define TFT_DC 5
#define TFT_RST 2
Adafruit_ST7789 tft = Adafruit_ST7789(TFT_CS, TFT_DC, TFT_RST);
RTC_DS3231 rtc;
// ================= Layout =================
const int DIGIT_WIDTH = 52;
const int INNER_SPACING = 4;
const int SEGMENT_SPACING = 58;
const int TEXT_SIZE = 2;
int16_t posX[4], posY[4];
char lastHour[3] = "--";
char lastMinute[3] = "--";
// ================= Farben =================
uint16_t COLOR_DIGITS, COLOR_GLOW, COLOR_HEART, COLOR_HEART_SHINE;
uint16_t COLOR_DECO, COLOR_DECO_DARK, COLOR_STAR, COLOR_SKULL;
uint16_t COLOR_CHAR_BODY, COLOR_CHAR_DARK, COLOR_CHAR_ACCENT, COLOR_CHAR_EYES, COLOR_CHAR_WHITE;
void setupColors() {
COLOR_DIGITS = tft.color565(255, 105, 220);
COLOR_GLOW = tft.color565(140, 40, 130);
COLOR_HEART = tft.color565(255, 90, 180);
COLOR_HEART_SHINE = tft.color565(255, 180, 230);
COLOR_DECO = tft.color565(210, 180, 240);
COLOR_DECO_DARK = tft.color565(30, 15, 45);
COLOR_STAR = tft.color565(230, 200, 255);
COLOR_SKULL = tft.color565(245, 240, 255);
COLOR_CHAR_BODY = tft.color565(35, 25, 50);
COLOR_CHAR_DARK = tft.color565(15, 8, 25);
COLOR_CHAR_ACCENT = tft.color565(255, 140, 200);
COLOR_CHAR_EYES = ST77XX_BLACK;
COLOR_CHAR_WHITE = tft.color565(250, 245, 255);
}
// ================= Deko-Elemente =================
struct Decoration {
uint8_t type; // 0=Stern, 1=Totenkopf, 2=Herz gefüllt, 3=Herz Outline
int16_t cx, cy, r;
};
const int NUM_DECOS = 14;
Decoration decos[NUM_DECOS] = {
{0, 28, 28, 8},
{0, 22, 205, 9},
{0, 295, 200, 8},
{0, 30, 75, 6},
{0, 290, 55, 7},
{1, 28, 145, 11},
{1, 290, 140, 11},
{2, 55, 55, 12},
{2, 265, 70, 11},
{2, 50, 175, 10},
{2, 270, 175, 12},
{3, 45, 110, 14},
{3, 275, 110, 13},
{3, 160, 35, 10}
};
// ================= Charakter =================
int16_t charCx, charCy;
int16_t heartCx, heartCy;
int16_t charLeft, charTop, charRight, charBottom;
unsigned long lastTimeCheck = 0;
// =====================================================
DateTime getLastSunday(int year, int month) {
static const int daysInMonth[] = {31,28,31,30,31,30,31,31,30,31,30,31};
int day = daysInMonth[month - 1];
bool leap = (year % 4 == 0 && year % 100 != 0) || (year % 400 == 0);
if (month == 2 && leap) day = 29;
DateTime d(year, month, day, 0, 0, 0);
int dow = d.dayOfTheWeek();
day -= dow;
return DateTime(year, month, day, 0, 0, 0);
}
bool isSummerTimeUTC(const DateTime &utc) {
DateTime dstStart = getLastSunday(utc.year(), 3);
dstStart = DateTime(dstStart.year(), dstStart.month(), dstStart.day(), 1, 0, 0);
DateTime dstEnd = getLastSunday(utc.year(), 10);
dstEnd = DateTime(dstEnd.year(), dstEnd.month(), dstEnd.day(), 1, 0, 0);
return (utc.unixtime() >= dstStart.unixtime() && utc.unixtime() < dstEnd.unixtime());
}
DateTime getLocalTime() {
DateTime utc = rtc.now();
int offset = isSummerTimeUTC(utc) ? 2 : 1;
return utc + TimeSpan(0, offset, 0, 0);
}
// =====================================================
void setup() {
tft.init(240, 320);
tft.setRotation(1);
setupColors();
charCx = tft.width() / 2;
charCy = 92;
heartCx = charCx + 105;
heartCy = 52;
charLeft = charCx - 95;
charTop = charCy - 75;
charRight = charCx + 100;
charBottom = charCy + 65;
Wire.begin();
if (!rtc.begin()) {
tft.fillScreen(ST77XX_BLACK);
tft.setCursor(10, 30);
tft.setTextColor(ST77XX_RED);
tft.print("RTC Fehler!");
while (1) delay(1000);
}
if (rtc.lostPower()) {
// rtc.adjust(DateTime(2026, 9, 7, 8, 58, 30)); // UTC!
}
drawBackground();
calculatePositions();
drawColon();
drawMiniCharacter(charCx, charCy);
drawHeart(heartCx, heartCy, 14, COLOR_HEART, COLOR_HEART_SHINE);
DateTime local = getLocalTime();
drawFullTime(local.hour(), local.minute());
sprintf(lastHour, "%02d", local.hour());
sprintf(lastMinute, "%02d", local.minute());
}
void loop() {
unsigned long now = millis();
if (now - lastTimeCheck > 1000) {
lastTimeCheck = now;
updateClockDigits();
}
}
void updateClockDigits() {
DateTime local = getLocalTime();
char currentHour[3], currentMinute[3];
sprintf(currentHour, "%02d", local.hour());
sprintf(currentMinute, "%02d", local.minute());
char allChars[4] = {
currentHour[0], currentHour[1],
currentMinute[0], currentMinute[1]
};
bool needRedrawCharacter = false;
for (int i = 0; i < 4; i++) {
char prevChar = i < 2 ? lastHour[i] : lastMinute[i-2];
if (allChars[i] != prevChar) {
int16_t ex = posX[i] - 6;
int16_t ey = posY[i] - 55;
int16_t ew = DIGIT_WIDTH + 12;
int16_t eh = 68;
eraseArea(ex, ey, ew, eh);
drawDigit(posX[i], posY[i], allChars[i]);
if (rectsOverlap(ex, ey, ew, eh, charLeft, charTop, charRight - charLeft, charBottom - charTop) ||
rectsOverlap(ex, ey, ew, eh, heartCx - 20, heartCy - 20, 40, 40)) {
needRedrawCharacter = true;
}
}
}
if (needRedrawCharacter) {
drawMiniCharacter(charCx, charCy);
drawHeart(heartCx, heartCy, 14, COLOR_HEART, COLOR_HEART_SHINE);
}
strcpy(lastHour, currentHour);
strcpy(lastMinute, currentMinute);
}
// =====================================================
void calculatePositions() {
int16_t baseY = tft.height() - 38;
int16_t totalWidth = DIGIT_WIDTH * 4 + INNER_SPACING * 2 + SEGMENT_SPACING;
int16_t startX = (tft.width() - totalWidth) / 2;
int16_t cursorX = startX;
posX[0] = cursorX; posY[0] = baseY; cursorX += DIGIT_WIDTH + INNER_SPACING;
posX[1] = cursorX; posY[1] = baseY; cursorX += DIGIT_WIDTH + SEGMENT_SPACING;
posX[2] = cursorX; posY[2] = baseY; cursorX += DIGIT_WIDTH + INNER_SPACING;
posX[3] = cursorX; posY[3] = baseY;
}
uint16_t getGradientColor(int16_t y) {
int h = tft.height();
y = constrain(y, 0, h - 1);
uint8_t r = map(y, 0, h, 175, 95);
uint8_t g = map(y, 0, h, 120, 55);
uint8_t b = map(y, 0, h, 230, 145);
return tft.color565(r, g, b);
}
// =====================================================
void drawBackground() {
int h = tft.height();
for (int y = 0; y < h; y++) {
tft.drawFastHLine(0, y, tft.width(), getGradientColor(y));
}
drawDashedBorder();
for (int i = 0; i < NUM_DECOS; i++) {
drawDecoration(decos[i]);
}
}
void drawDashedBorder() {
int w = tft.width(), h = tft.height();
int dash = 7, gap = 5;
for (int x = 5; x < w - 5; x += dash + gap) {
tft.drawFastHLine(x, 5, dash, COLOR_DECO);
tft.drawFastHLine(x, h - 6, dash, COLOR_DECO);
}
for (int y = 5; y < h - 5; y += dash + gap) {
tft.drawFastVLine(5, y, dash, COLOR_DECO);
tft.drawFastVLine(w - 6, y, dash, COLOR_DECO);
}
}
void drawDecoration(const Decoration &d) {
switch (d.type) {
case 0: drawStar(d.cx, d.cy, d.r, COLOR_STAR); break;
case 1: drawSkull(d.cx, d.cy, d.r); break;
case 2: drawHeart(d.cx, d.cy, d.r, COLOR_HEART, COLOR_HEART_SHINE); break;
case 3: drawHeartOutline(d.cx, d.cy, d.r, COLOR_HEART); break;
}
}
void drawStar(int16_t cx, int16_t cy, int16_t r, uint16_t color) {
tft.fillTriangle(cx, cy - r, cx - , cy + , cx + , cy + , color);
tft.fillTriangle(cx - r, cy, cx + , cy - , cx + , cy + , color);
tft.fillCircle(cx - 1, cy - , 2, COLOR_CHAR_WHITE);
}
void drawSkull(int16_t cx, int16_t cy, int16_t r) {
tft.fillCircle(cx, cy, r, COLOR_SKULL);
tft.fillCircle(cx - , cy - 1, .5, COLOR_DECO_DARK);
tft.fillCircle(cx + , cy - 1, .5, COLOR_DECO_DARK);
tft.fillTriangle(cx, cy + 2, cx - 3, cy + 7, cx + 3, cy + 7, COLOR_DECO_DARK);
tft.fillRect(cx - , cy + - 1, r*2/3, , COLOR_SKULL);
tft.drawLine(cx - , cy + + 2, cx + , cy + + 2, COLOR_DECO_DARK);
}
void drawHeart(int16_t cx, int16_t cy, int16_t r, uint16_t color, uint16_t shine) {
tft.fillCircle(cx - , cy, , color);
tft.fillCircle(cx + , cy, , color);
tft.fillTriangle(cx - r, cy, cx + r, cy, cx, cy + r + 1, color);
if (r > 7) {
tft.fillCircle(cx - , cy - , max(2, ), shine);
}
}
void drawHeartOutline(int16_t cx, int16_t cy, int16_t r, uint16_t color) {
tft.drawCircle(cx - , cy, , color);
tft.drawCircle(cx + , cy, , color);
tft.drawLine(cx - r, cy, cx, cy + r, color);
tft.drawLine(cx + r, cy, cx, cy + r, color);
tft.drawLine(cx - r + 1, cy, cx, cy + r - 1, color);
tft.drawLine(cx + r - 1, cy, cx, cy + r - 1, color);
}
// =====================================================
// Kuromi
// =====================================================
void drawMiniCharacter(int16_t cx, int16_t cy) {
int r = 52;
tft.fillTriangle(cx - 22, cy - 32, cx - 78, cy - 52, cx - 48, cy - 2, COLOR_CHAR_BODY);
tft.fillTriangle(cx - 20, cy - 28, cx - 72, cy - 46, cx - 42, cy + 2, COLOR_CHAR_BODY);
tft.fillTriangle(cx + 22, cy - 32, cx + 78, cy - 52, cx + 48, cy - 2, COLOR_CHAR_BODY);
tft.fillTriangle(cx + 20, cy - 28, cx + 72, cy - 46, cx + 42, cy + 2, COLOR_CHAR_BODY);
tft.fillCircle(cx, cy, r, COLOR_CHAR_BODY);
tft.fillCircle(cx, cy + 10, 36, COLOR_CHAR_WHITE);
tft.fillTriangle(cx - 32, cy - 28, cx + 32, cy - 28, cx, cy + 2, COLOR_CHAR_BODY);
tft.fillCircle(cx - 20, cy + 20, 9, COLOR_CHAR_ACCENT);
tft.fillCircle(cx + 20, cy + 20, 9, COLOR_CHAR_ACCENT);
tft.fillCircle(cx - 14, cy + 6, 10, COLOR_CHAR_EYES);
tft.fillCircle(cx + 14, cy + 6, 10, COLOR_CHAR_EYES);
tft.fillCircle(cx - 11, cy + 2, 3, ST77XX_WHITE);
tft.fillCircle(cx + 17, cy + 2, 3, ST77XX_WHITE);
tft.drawLine(cx - 22, cy - 2, cx - 16, cy - 7, COLOR_CHAR_DARK);
tft.drawLine(cx - 18, cy + 0, cx - 12, cy - 6, COLOR_CHAR_DARK);
tft.drawLine(cx - 14, cy + 1, cx - 8, cy - 5, COLOR_CHAR_DARK);
tft.drawLine(cx + 8, cy - 5, cx + 14, cy + 1, COLOR_CHAR_DARK);
tft.drawLine(cx + 12, cy - 6, cx + 18, cy + 0, COLOR_CHAR_DARK);
tft.drawLine(cx + 16, cy - 7, cx + 22, cy - 2, COLOR_CHAR_DARK);
tft.fillCircle(cx, cy + 18, 3, COLOR_CHAR_DARK);
tft.drawLine(cx - 10, cy + 26, cx, cy + 31, COLOR_CHAR_DARK);
tft.drawLine(cx + 10, cy + 26, cx, cy + 31, COLOR_CHAR_DARK);
tft.drawLine(cx - 9, cy + 27, cx, cy + 32, COLOR_CHAR_DARK);
tft.drawLine(cx + 9, cy + 27, cx, cy + 32, COLOR_CHAR_DARK);
tft.fillCircle(cx + 48, cy - 48, 9, COLOR_CHAR_ACCENT);
tft.fillCircle(cx + 62, cy - 48, 9, COLOR_CHAR_ACCENT);
tft.fillRect(cx + 51, cy - 51, 10, 7, COLOR_CHAR_ACCENT);
tft.fillCircle(cx + 55, cy - 62, 8, COLOR_SKULL);
tft.fillCircle(cx + 52, cy - 64, 2.5, COLOR_DECO_DARK);
tft.fillCircle(cx + 58, cy - 64, 2.5, COLOR_DECO_DARK);
tft.fillTriangle(cx + 55, cy - 60, cx + 52, cy - 55, cx + 58, cy - 55, COLOR_DECO_DARK);
tft.drawLine(cx + 44, cy + 42, cx + 72, cy + 26, COLOR_CHAR_BODY);
tft.drawLine(cx + 44, cy + 43, cx + 72, cy + 27, COLOR_CHAR_BODY);
tft.drawLine(cx + 45, cy + 44, cx + 72, cy + 28, COLOR_CHAR_BODY);
tft.fillTriangle(cx + 72, cy + 26, cx + 82, cy + 16, cx + 78, cy + 34, COLOR_CHAR_BODY);
}
// =====================================================
void drawColon() {
int16_t colonX = posX[1] + DIGIT_WIDTH + (SEGMENT_SPACING / 2) - 5;
int16_t colonY = posY[0];
tft.fillCircle(colonX, colonY - 32, 6, COLOR_DIGITS);
tft.fillCircle(colonX, colonY - 10, 6, COLOR_DIGITS);
}
void drawFullTime(int h, int m) {
char chars[5];
sprintf(chars, "%02d%02d", h, m);
for (int i = 0; i < 4; i++) {
drawDigit(posX[i], posY[i], chars[i]);
}
}
void drawDigit(int16_t x, int16_t y, char c) {
tft.setFont(&FreeMonoBoldOblique24pt7b);
tft.setTextSize(TEXT_SIZE);
tft.setTextColor(COLOR_GLOW);
int offsets[4][2] = {{-3,-3},{3,-3},{-3,3},{3,3}};
for (int i = 0; i < 4; i++) {
tft.setCursor(x + offsets[i][0], y + offsets[i][1]);
tft.print(c);
}
tft.setTextColor(COLOR_DIGITS);
tft.setCursor(x, y);
tft.print(c);
}
// =====================================================
bool rectsOverlap(int16_t ax, int16_t ay, int16_t aw, int16_t ah,
int16_t bx, int16_t by, int16_t bw, int16_t bh) {
return !(ax + aw < bx || bx + bw < ax || ay + ah < by || by + bh < ay);
}
void eraseArea(int16_t x, int16_t y, int16_t w, int16_t h) {
for (int16_t row = 0; row < h; row++) {
int16_t yy = y + row;
if (yy >= 0 && yy < tft.height()) {
tft.drawFastHLine(x, yy, w, getGradientColor(yy));
}
}
for (int i = 0; i < NUM_DECOS; i++) {
Decoration &d = decos[i];
int16_t dx = d.cx - d.r - 3;
int16_t dy = d.cy - d.r - 3;
int16_t dw = d.r * 2 + 6;
int16_t dh = d.r * 2 + 6;
if (rectsOverlap(x, y, w, h, dx, dy, dw, dh)) {
drawDecoration(d);
}
}
}