New: - Flight logbook safety net: disabled by default, requires confirming a full-screen warning before it can be turned on, and automatically switches off again after exactly 24 hours - survives restarts and power cycles (no valid enable timestamp = guaranteed off). Each activation now writes its own CSV file instead of endlessly appending to one growing file, and every file can be deleted individually from Logbook files. - Adjustable display brightness (10-100% in 10% steps, Menu -> System -> "Brightness") with a live preview. - About screen (Menu -> System -> "About") showing project name, description, and firmware version. Fixed: - Radar's red altitude band (>9100m/30000ft) no longer gets dimmed at night - the previous dim tone looked orange-brown and was hard to tell apart from yellow. Legend and markers are consistent again. - Radius change no longer leaves a stale "tail" trail behind each aircraft between the old and new position. - Splash screen no longer shows a time-of-day greeting that appeared too late in the boot sequence to be useful; the star animation now starts immediately instead of only near the end of boot. - Clock stayed on raw UTC after the first boot instead of local time (missing DST handling in some timezones) because the location wait loop skipped the timezone lookup once a location was already saved. - Header clock no longer leaves stale digit fragments behind the current time, most visible on the last minute digit. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
154 lines
5.4 KiB
C++
154 lines
5.4 KiB
C++
#include "splash_screen.h"
|
|
#include "menu_stars.h"
|
|
#include <math.h>
|
|
|
|
namespace SplashScreen {
|
|
|
|
namespace {
|
|
constexpr uint32_t MIN_DISPLAY_MS = 5000;
|
|
uint32_t startMs = 0;
|
|
|
|
constexpr int16_t STATUS_LINE_H = 18;
|
|
constexpr int16_t STATUS_START_Y = 260;
|
|
constexpr uint8_t MAX_STATUS_LINES = 3;
|
|
|
|
void drawRadarReticle(TFT_eSPI& tft, int16_t cx, int16_t cy) {
|
|
uint16_t dim = 0x0320;
|
|
tft.drawCircle(cx, cy, 80, dim);
|
|
tft.drawCircle(cx, cy, 54, dim);
|
|
tft.drawCircle(cx, cy, 29, dim);
|
|
tft.drawFastHLine(cx - 80, cy, 160, dim);
|
|
tft.drawFastVLine(cx, cy - 80, 160, dim);
|
|
}
|
|
|
|
// Dreht einen Punkt (px, py) im lokalen Koordinatensystem der kleinen
|
|
// Deko-Symbole um angleDeg und verschiebt ihn nach (x, y).
|
|
void rotatePoint(int16_t x, int16_t y, float sinA, float cosA,
|
|
float px, float py, int16_t& outX, int16_t& outY) {
|
|
outX = x + (int16_t)lroundf(px * cosA - py * sinA);
|
|
outY = y + (int16_t)lroundf(px * sinA + py * cosA);
|
|
}
|
|
|
|
// Kleines Flugzeug-Silhouette aus gefuellten Dreiecken (Rumpf, Tragflaechen,
|
|
// Leitwerk), analog zum grossen Flugzeug in der Mitte, aber schlichter und
|
|
// in beliebiger Richtung (angleDeg, 0 = Nase zeigt nach oben).
|
|
void drawMiniJet(TFT_eSPI& tft, int16_t x, int16_t y, uint16_t color, float angleDeg) {
|
|
float rad = angleDeg * (PI / 180.0f);
|
|
float s = sinf(rad), c = cosf(rad);
|
|
|
|
int16_t nx, ny, flx, fly, frx, fry;
|
|
rotatePoint(x, y, s, c, 0, -11, nx, ny);
|
|
rotatePoint(x, y, s, c, -3, 11, flx, fly);
|
|
rotatePoint(x, y, s, c, 3, 11, frx, fry);
|
|
tft.fillTriangle(nx, ny, flx, fly, frx, fry, color);
|
|
|
|
int16_t wlx, wly, wrx, wry, wcx, wcy;
|
|
rotatePoint(x, y, s, c, -13, 3, wlx, wly);
|
|
rotatePoint(x, y, s, c, 13, 3, wrx, wry);
|
|
rotatePoint(x, y, s, c, 0, -3, wcx, wcy);
|
|
tft.fillTriangle(wlx, wly, wrx, wry, wcx, wcy, color);
|
|
|
|
int16_t tlx, tly, trx, try_, ttx, tty;
|
|
rotatePoint(x, y, s, c, -5, 8, tlx, tly);
|
|
rotatePoint(x, y, s, c, 5, 8, trx, try_);
|
|
rotatePoint(x, y, s, c, 0, 13, ttx, tty);
|
|
tft.fillTriangle(tlx, tly, trx, try_, ttx, tty, color);
|
|
}
|
|
|
|
// Kleiner Helikopter: Rotorkreis mit Blaettern, gefuellter Rumpf, Heckausleger
|
|
// mit Leitwerk. angleDeg bestimmt die Flugrichtung (0 = Nase nach oben).
|
|
void drawMiniHeli(TFT_eSPI& tft, int16_t x, int16_t y, uint16_t color, float angleDeg) {
|
|
float rad = angleDeg * (PI / 180.0f);
|
|
float s = sinf(rad), c = cosf(rad);
|
|
|
|
int16_t rcx, rcy;
|
|
rotatePoint(x, y, s, c, 0, -7, rcx, rcy);
|
|
tft.drawCircle(rcx, rcy, 6, color);
|
|
|
|
int16_t b1x, b1y, b2x, b2y;
|
|
rotatePoint(x, y, s, c, -7, -7, b1x, b1y);
|
|
rotatePoint(x, y, s, c, 7, -7, b2x, b2y);
|
|
tft.drawLine(b1x, b1y, b2x, b2y, color);
|
|
rotatePoint(x, y, s, c, 0, -13, b1x, b1y);
|
|
rotatePoint(x, y, s, c, 0, -1, b2x, b2y);
|
|
tft.drawLine(b1x, b1y, b2x, b2y, color);
|
|
|
|
int16_t cx1, cy1, cx2, cy2, cx3, cy3;
|
|
rotatePoint(x, y, s, c, 0, -6, cx1, cy1);
|
|
rotatePoint(x, y, s, c, -4, 4, cx2, cy2);
|
|
rotatePoint(x, y, s, c, 4, 4, cx3, cy3);
|
|
tft.fillTriangle(cx1, cy1, cx2, cy2, cx3, cy3, color);
|
|
|
|
int16_t tbx1, tby1, tbx2, tby2;
|
|
rotatePoint(x, y, s, c, 0, 4, tbx1, tby1);
|
|
rotatePoint(x, y, s, c, 0, 12, tbx2, tby2);
|
|
tft.drawLine(tbx1, tby1, tbx2, tby2, color);
|
|
|
|
int16_t fx1, fy1, fx2, fy2, fx3, fy3;
|
|
rotatePoint(x, y, s, c, -3, 10, fx1, fy1);
|
|
rotatePoint(x, y, s, c, 3, 10, fx2, fy2);
|
|
rotatePoint(x, y, s, c, 0, 15, fx3, fy3);
|
|
tft.fillTriangle(fx1, fy1, fx2, fy2, fx3, fy3, color);
|
|
}
|
|
|
|
void drawAirplane(TFT_eSPI& tft, int16_t cx) {
|
|
uint16_t color = TFT_GREEN;
|
|
|
|
tft.drawTriangle(cx + 15, 142, cx - 15, 200, cx - 10, 202, color);
|
|
|
|
tft.drawTriangle(cx + 5, 164, cx - 47, 175, cx - 3, 179, color);
|
|
tft.drawTriangle(cx + 5, 164, cx + 30, 211, cx - 3, 179, color);
|
|
|
|
tft.drawTriangle(cx - 11, 198, cx - 23, 201, cx - 6, 210, color);
|
|
}
|
|
}
|
|
|
|
void begin(TFT_eSPI& tft) {
|
|
startMs = millis();
|
|
MenuStars::reset();
|
|
|
|
int16_t cx = tft.width() / 2;
|
|
|
|
tft.fillScreen(TFT_BLACK);
|
|
drawRadarReticle(tft, cx, 174);
|
|
|
|
uint16_t dimGreen = 0x0320;
|
|
drawMiniJet(tft, cx - 45, 135, dimGreen, 25.0f);
|
|
drawMiniJet(tft, cx + 50, 160, dimGreen, 160.0f);
|
|
drawMiniHeli(tft, cx - 35, 215, dimGreen, 250.0f);
|
|
drawMiniHeli(tft, cx + 45, 115, dimGreen, 100.0f);
|
|
|
|
drawAirplane(tft, cx);
|
|
|
|
tft.setTextDatum(MC_DATUM);
|
|
|
|
tft.setTextColor(TFT_GREEN, TFT_BLACK);
|
|
tft.setTextSize(2);
|
|
tft.drawString("Eiswolfs", cx, 28);
|
|
tft.drawString("Flightradar", cx, 60);
|
|
|
|
tft.setTextDatum(TL_DATUM);
|
|
tft.setTextSize(1);
|
|
}
|
|
|
|
void setStatusLine(TFT_eSPI& tft, uint8_t slot, const String& text, uint16_t color) {
|
|
if (slot >= MAX_STATUS_LINES) return;
|
|
|
|
int16_t y = STATUS_START_Y + slot * STATUS_LINE_H;
|
|
tft.fillRect(0, y, tft.width(), STATUS_LINE_H, TFT_BLACK);
|
|
tft.setTextDatum(MC_DATUM);
|
|
tft.setTextColor(color, TFT_BLACK);
|
|
tft.drawString(text, tft.width() / 2, y + STATUS_LINE_H / 2);
|
|
tft.setTextDatum(TL_DATUM);
|
|
}
|
|
|
|
void waitRemaining(TFT_eSPI& tft) {
|
|
uint32_t elapsed = millis() - startMs;
|
|
while (elapsed < MIN_DISPLAY_MS) {
|
|
MenuStars::update(tft);
|
|
delay(20);
|
|
elapsed = millis() - startMs;
|
|
}
|
|
}
|
|
|
|
} |