v2.1: Fix persistent radar screen flickering (header overlap, label backgrounds, orphaned trails)

This commit is contained in:
Eiswolf-BG
2026-08-04 14:48:24 +02:00
parent 6e01e56c4c
commit 7fedf860e5
5 changed files with 104 additions and 36 deletions

View File

@@ -28,10 +28,10 @@ namespace {
uint8_t len = 0;
constexpr int16_t KEY_W = 74;
constexpr int16_t KEY_H = 40;
constexpr int16_t KEY_GAP = 4;
constexpr int16_t KEY_H = 34;
constexpr int16_t KEY_GAP = 3;
constexpr int16_t GRID_LEFT = (Config::SCREEN_WIDTH - 3 * KEY_W - 2 * KEY_GAP) / 2;
constexpr int16_t GRID_TOP = 90;
constexpr int16_t GRID_TOP = 84;
const char* keys[12] = {"1","2","3","4","5","6","7","8","9","-","0","."};
Rect keyRects[12];
@@ -43,8 +43,12 @@ namespace {
KEY_W, KEY_H};
}
// WICHTIG: Backspace muss VOR den Cancel/OK-Buttons enden (mit
// Abstand), sonst ueberlappen sich die Tap-Flaechen - Backspace wird
// in der Pruef-Reihenfolge zuerst getestet, wuerde also faelschlich
// jeden Tap auf "Cancel" abfangen (Bug: Cancel-Button reagierte nicht).
Rect backspaceBtn = {(int16_t)GRID_LEFT, (int16_t)(GRID_TOP + 4 * (KEY_H + KEY_GAP)),
(int16_t)(3 * KEY_W + 2 * KEY_GAP), 36};
(int16_t)(3 * KEY_W + 2 * KEY_GAP), 30};
Rect cancelBtn = {10, (int16_t)(Config::SCREEN_HEIGHT - 50), 110, 40};
Rect confirmBtn = {(int16_t)(Config::SCREEN_WIDTH - 120), (int16_t)(Config::SCREEN_HEIGHT - 50), 110, 40};

View File

@@ -35,11 +35,12 @@ namespace {
Layout computeLayout(int16_t top) {
Layout L;
L.infoTop = Config::SCREEN_HEIGHT - INFO_BAR_H;
constexpr int16_t TOP_LABEL_MARGIN = 10;
int16_t maxRadiusByWidth = Config::SCREEN_WIDTH / 2 - 6;
int16_t maxRadiusByHeight = (L.infoTop - top) / 2 - 6;
int16_t maxRadiusByHeight = (L.infoTop - top - TOP_LABEL_MARGIN) / 2 - 6;
L.radius = min(maxRadiusByWidth, maxRadiusByHeight);
L.cx = Config::SCREEN_WIDTH / 2;
L.cy = top + L.radius + 6;
L.cy = top + L.radius + 6 + TOP_LABEL_MARGIN;
L.rangeBtn = {(int16_t)(Config::SCREEN_WIDTH - 70), (int16_t)(L.infoTop + 4), 62, 22};
return L;
}
@@ -112,6 +113,7 @@ namespace {
int16_t ys[TRAIL_LEN] = {0};
uint8_t count = 0;
uint32_t lastUpdateMs = 0;
uint16_t dimColor = TFT_DARKGREEN;
};
constexpr uint8_t MAX_TRAILS = Config::MAX_TRACKED_AIRCRAFT;
TrailEntry trails[MAX_TRAILS];
@@ -452,8 +454,10 @@ void render(TFT_eSPI& tft, int16_t top) {
bool isEmergency = SettingsStore::emergencyAlertEnabled() && isEmergencySquawk(a.squawk);
TrailEntry* trail = findOrCreateTrail(a.hex);
drawTrail(tft, trail, dimColorForAltitude(a.altBaroFt));
uint16_t trailColor = dimColorForAltitude(a.altBaroFt);
drawTrail(tft, trail, trailColor);
pushTrailPoint(trail, pt.x, pt.y);
if (trail) trail->dimColor = trailColor;
if (isSelected) {
tft.drawCircle(pt.x, pt.y, 9, TFT_WHITE);
@@ -466,7 +470,13 @@ void render(TFT_eSPI& tft, int16_t top) {
tft.drawCircle(pt.x, pt.y, 12, TFT_RED);
}
tft.setTextColor(color, TFT_BLACK);
// Rufzeichen als kleines farbiges Label ueber dem Punkt
// Transparent zeichnen (KEIN schwarzer Hintergrund-Kasten) - sonst
// "beisst" der Label-Hintergrund ein Stueck der Ring-/Fadenkreuz-
// Linie weg, wenn sich beides ueberschneidet. Da das Label bei jedem
// Sweep-Tick am selben Fleck neu gezeichnet wird, sah das wie
// Dauerflackern genau an den Beschriftungen aus.
tft.setTextColor(color);
tft.setTextDatum(BC_DATUM);
const char* label = a.callsign[0] ? a.callsign : a.hex;
tft.drawString(label, pt.x, pt.y - 8);
@@ -515,6 +525,8 @@ void tick(TFT_eSPI& tft, int16_t top, uint32_t deltaMs) {
Layout L = computeLayout(top);
float rangeKm = Config::RANGE_STEPS_KM[SettingsStore::rangeIndex()];
tft.startWrite();
if (prevSweepAngleDeg >= 0.0f) {
drawSweepLine(tft, L, prevSweepAngleDeg, TFT_BLACK);
drawStaticBackground(tft, L, rangeKm);
@@ -527,6 +539,21 @@ void tick(TFT_eSPI& tft, int16_t top, uint32_t deltaMs) {
drawSweepLine(tft, L, sweepAngleDeg, TFT_GREEN);
prevSweepAngleDeg = sweepAngleDeg;
for (uint8_t i = 0; i < MAX_TRAILS; i++) {
if (!trails[i].active) continue;
bool stillVisible = false;
for (uint8_t j = 0; j < MAX_HIT_POINTS; j++) {
if (hitPoints[j].valid && strcmp(hitPoints[j].hex, trails[i].hex) == 0) {
stillVisible = true;
break;
}
}
if (stillVisible) {
drawTrail(tft, &trails[i], trails[i].dimColor);
}
}
for (uint8_t i = 0; i < MAX_HIT_POINTS; i++) {
if (!hitPoints[i].valid) continue;
const HitPoint& hp = hitPoints[i];
@@ -543,12 +570,14 @@ void tick(TFT_eSPI& tft, int16_t top, uint32_t deltaMs) {
tft.drawCircle(hp.x, hp.y, 12, TFT_RED);
}
tft.setTextColor(hp.color, TFT_BLACK);
tft.setTextColor(hp.color);
tft.setTextDatum(BC_DATUM);
const char* label = hp.callsign[0] ? hp.callsign : hp.hex;
tft.drawString(label, hp.x, hp.y - 8);
tft.setTextDatum(TL_DATUM);
}
tft.endWrite();
}
bool handleTap(int16_t x, int16_t y, int16_t top) {

View File

@@ -10,10 +10,7 @@ namespace {
constexpr int16_t STATUS_START_Y = 260;
constexpr uint8_t MAX_STATUS_LINES = 3;
// Dezente konzentrische Ringe + Fadenkreuz als "Zielfernrohr"-Hintergrund,
// in gedaempftem Gruen (gleicher Ton wie die Sweep-Nachzieh-Linie im
// Radar-Bildschirm), damit das Flugzeug wie ins Visier genommen wirkt.
// UNVERAENDERT gegenueber vorher - nur das Flugzeug wurde verkleinert/gedreht.
// Dezente konzentrische Ringe + Fadenkreuz als "Zielfernrohr"-Hintergrund
void drawRadarReticle(TFT_eSPI& tft, int16_t cx, int16_t cy) {
uint16_t dim = 0x0320;
tft.drawCircle(cx, cy, 80, dim);
@@ -23,10 +20,22 @@ namespace {
tft.drawFastVLine(cx, cy - 80, 160, dim);
}
// Flugzeug-Silhouette: 40% kleiner (Skalierung 0.6) und 25 Grad im
// Uhrzeigersinn gedreht (Nase zeigt jetzt leicht nach rechts), um den
// Reticle-Mittelpunkt (cx, 174) - der Radar-Kreis selbst bleibt exakt
// gleich gross wie vorher.
// Echte kleine Jet-Silhouette für den Hintergrund
void drawMiniJet(TFT_eSPI& tft, int16_t x, int16_t y, uint16_t color) {
tft.drawLine(x, y - 10, x, y + 12, color);
tft.drawLine(x - 12, y + 2, x + 12, y + 2, color);
tft.drawLine(x - 5, y + 10, x + 5, y + 10, color);
}
// Echte kleine Helikopter-Silhouette für den Hintergrund
void drawMiniHeli(TFT_eSPI& tft, int16_t x, int16_t y, uint16_t color) {
tft.drawLine(x - 8, y - 6, x + 8, y - 6, color);
tft.drawLine(x, y - 6, x, y + 4, color);
tft.drawCircle(x, y + 2, 4, color);
tft.drawLine(x - 4, y + 7, x + 4, y + 7, color);
}
// Haupt-Flugzeug in der Mitte (Unverändert)
void drawAirplane(TFT_eSPI& tft, int16_t cx) {
uint16_t color = TFT_GREEN;
@@ -49,6 +58,18 @@ void begin(TFT_eSPI& tft) {
tft.fillScreen(TFT_BLACK);
drawRadarReticle(tft, cx, 174);
// Hintergrund-Flugzeuge & Helikopter dezent im Hintergrund - gleicher
// gedaempfter Gruenton wie die Radar-Ringe (0x0320), NICHT das volle
// TFT_GREEN (das war vorher gleich hell wie das zentrale Flugzeug und
// wirkte dadurch nicht wie ein dezenter Hintergrund).
uint16_t dimGreen = 0x0320;
drawMiniJet(tft, cx - 45, 135, dimGreen);
drawMiniJet(tft, cx + 50, 160, dimGreen);
drawMiniHeli(tft, cx - 35, 215, dimGreen);
drawMiniHeli(tft, cx + 45, 115, dimGreen);
// Zentrales Flugzeug (unverändert im Vordergrund)
drawAirplane(tft, cx);
tft.setTextDatum(MC_DATUM);

View File

@@ -3,18 +3,7 @@
#include <TFT_eSPI.h>
namespace SplashScreen {
// Zeichnet den Splash-Hintergrund (Titel + gruene Flugzeug-Silhouette auf
// schwarzem Grund) und merkt sich den Startzeitpunkt. NICHT blockierend -
// der Aufrufer kann danach normal weiter booten und Status-Zeilen
// draufschreiben (siehe setStatusLine()).
void begin(TFT_eSPI& tft);
// Schreibt/ueberschreibt eine der (begrenzt vielen) Status-Zeilen unten
// im Splash-Screen, z.B. "SD-Karte: OK" oder "WLAN verbunden".
// slot: 0 = erste Zeile, 1 = zweite Zeile, usw.
void setStatusLine(TFT_eSPI& tft, uint8_t slot, const String& text, uint16_t color = TFT_WHITE);
// Blockiert, bis seit begin() mindestens MIN_DISPLAY_MS vergangen sind.
// Direkt vor dem Verlassen des Splash-Screens aufrufen.
void waitRemaining();
}
}

View File

@@ -78,7 +78,14 @@ namespace {
char passwordBuf[64] = {0};
uint8_t passwordLen = 0;
bool shiftOn = false;
// Shift-Verhalten wie am Handy: einmal antippen = NUR der naechste
// Buchstabe wird gross geschrieben, danach automatisch wieder klein.
// Zweimal schnell hintereinander antippen (Doppeltipp) = Feststelltaste
// (bleibt an, bis erneut einmal angetippt wird).
enum class ShiftState { Off, OneShot, Locked };
ShiftState shiftState = ShiftState::Off;
uint32_t lastShiftTapMs = 0;
constexpr uint32_t SHIFT_DOUBLE_TAP_MS = 400;
uint8_t page = 0;
bool skipped = false;
@@ -110,7 +117,7 @@ namespace {
String keyLabel(const KeyDef& k) {
if (k.label) return String(k.label);
char c = shiftOn ? (char)toupper(k.ch) : k.ch;
char c = (shiftState != ShiftState::Off) ? (char)toupper(k.ch) : k.ch;
return String(c);
}
@@ -124,13 +131,31 @@ namespace {
switch (k.type) {
case KeyType::Char:
if (passwordLen < sizeof(passwordBuf) - 1) {
passwordBuf[passwordLen++] = shiftOn ? (char)toupper(k.ch) : k.ch;
bool upper = (shiftState != ShiftState::Off);
passwordBuf[passwordLen++] = upper ? (char)toupper(k.ch) : k.ch;
passwordBuf[passwordLen] = 0;
// Nach genau einem Buchstaben automatisch wieder auf
// klein zurueckfallen - AUSSER die Feststelltaste
// (Doppeltipp) ist aktiv, die bleibt an.
if (shiftState == ShiftState::OneShot) shiftState = ShiftState::Off;
}
break;
case KeyType::Shift:
shiftOn = !shiftOn;
case KeyType::Shift: {
uint32_t nowMs = millis();
bool isDoubleTap = (nowMs - lastShiftTapMs) <= SHIFT_DOUBLE_TAP_MS;
lastShiftTapMs = nowMs;
if (isDoubleTap) {
// Doppeltipp: Feststelltaste an-/ausschalten.
shiftState = (shiftState == ShiftState::Locked) ? ShiftState::Off : ShiftState::Locked;
} else if (shiftState == ShiftState::Off) {
// Einfacher Tipp: nur der naechste Buchstabe wird gross.
shiftState = ShiftState::OneShot;
} else {
// Einfacher Tipp waehrend OneShot/Locked schaltet ab.
shiftState = ShiftState::Off;
}
break;
}
case KeyType::Backspace:
if (passwordLen > 0) {
passwordLen--;
@@ -153,7 +178,7 @@ namespace {
Rect rects[N];
layoutRow(row, y, rects);
for (size_t i = 0; i < N; i++) {
bool hl = (row[i].type == KeyType::Shift && shiftOn);
bool hl = (row[i].type == KeyType::Shift && shiftState != ShiftState::Off);
drawButton(tft, rects[i], keyLabel(row[i]), hl);
}
}
@@ -161,7 +186,7 @@ namespace {
void resetKeyboardState() {
passwordLen = 0;
passwordBuf[0] = 0;
shiftOn = false;
shiftState = ShiftState::Off;
page = 0;
}