localization
This commit is contained in:
@@ -41,11 +41,11 @@ int16_t cx, cy, radius;
|
||||
};
|
||||
|
||||
constexpr int16_t INFO_BAR_H = 64;
|
||||
// Zusaetzliche Hoehe fuer die zweite Legenden-Zeile (Bodenfahrzeug-
|
||||
// Marker-Erklaerung), nur reserviert, wenn Bodenfahrzeuge tatsaechlich
|
||||
// angezeigt werden (SettingsStore::hideGroundVehicles() == false) -
|
||||
// ansonsten bleibt die Infoleiste genau so hoch wie bisher und der
|
||||
// Radarkreis behaelt seine gewohnte Groesse.
|
||||
// Additional height for the second legend row (ground vehicle
|
||||
// marker explanation), only reserved when ground vehicles are actually
|
||||
// displayed (SettingsStore::hideGroundVehicles() == false) -
|
||||
// otherwise the info bar stays exactly as tall as before and the
|
||||
// radar circle keeps its usual size.
|
||||
constexpr int16_t INFO_BAR_GROUND_ROW_H = 18;
|
||||
|
||||
int16_t infoBarHeight() {
|
||||
@@ -69,10 +69,10 @@ L.rangeBtn = {(int16_t)(Config::SCREEN_WIDTH - 72), (int16_t)(L.infoTop + 4), 64
|
||||
return L;
|
||||
}
|
||||
|
||||
// +22px gegenueber frueher (264) fuer die neue Route-Zeile (siehe
|
||||
// drawDetailPanel()) - beeinflusst NUR die Groesse des Detail-Panel-
|
||||
// Overlays, nicht das normale Radar-Layout (computeLayout() oben
|
||||
// richtet sich ausschliesslich nach infoBarHeight(), nicht nach
|
||||
// +22px compared to the previous value (264) for the new route line (see
|
||||
// drawDetailPanel()) - only affects the size of the detail panel
|
||||
// overlay, not the normal radar layout (computeLayout() above
|
||||
// only depends on infoBarHeight(), not
|
||||
// DETAIL_PANEL_H).
|
||||
constexpr int16_t DETAIL_PANEL_H = 286;
|
||||
|
||||
@@ -103,15 +103,15 @@ L.rangeBtn = {(int16_t)(Config::SCREEN_WIDTH - 72), (int16_t)(L.infoTop + 4), 64
|
||||
|
||||
bool ledBlinkOn = true;
|
||||
|
||||
// "Leerer Himmel"-Timer: merkt sich, wann zuletzt mindestens ein
|
||||
// Flugzeug sichtbar war (nach allen Filtern) - namespace-weit statt
|
||||
// lokal in render(), da tick() (siehe dort) den Wert bei jedem Tick
|
||||
// (alle 80ms) braucht, um den Sekundenzaehler fluessig hochzuzaehlen,
|
||||
// statt nur alle paar Sekunden bei einem render()-Aufruf.
|
||||
// "Empty Sky" timer: remembers when at least one
|
||||
// aircraft was last visible (after all filters) - namespace-wide instead of
|
||||
// local to render(), since tick() (see there) needs the value on every tick
|
||||
// (every 80ms) to count up the seconds smoothly,
|
||||
// instead of only every few seconds on a render() call.
|
||||
uint32_t lastAircraftSeenMs = millis();
|
||||
|
||||
// Wird von der Detail-Panel-Laufschrift (LineMarquee in drawDetailPanel)
|
||||
// fuer das Scrollen von Routentext verwendet.
|
||||
// Used by the detail panel marquee (LineMarquee in drawDetailPanel)
|
||||
// for scrolling route text.
|
||||
constexpr uint32_t INFO_MARQUEE_STEP_MS = 200;
|
||||
|
||||
String infoMarqueeWindow(TFT_eSPI& tft, const String& src, int32_t startIdx, int16_t maxWidth) {
|
||||
@@ -133,16 +133,16 @@ L.rangeBtn = {(int16_t)(Config::SCREEN_WIDTH - 72), (int16_t)(L.infoTop + 4), 64
|
||||
float prevSweepAngleDeg = -1.0f;
|
||||
constexpr float SWEEP_DEGREES_PER_SEC = 45.0f;
|
||||
|
||||
// Gleiche Logik wie main.cpp::isNightDimHours() - Nacht = zwischen
|
||||
// Sonnenuntergang und Sonnenaufgang am aktiven Standort (siehe
|
||||
// sun_times.h), mit Rueckfall auf ein festes 22:00-06:00-Fenster,
|
||||
// solange Standort/Uhrzeit noch nicht bekannt sind. Hier bewusst
|
||||
// dupliziert statt geteilt, siehe CLAUDE.md-Konvention ("jeder Screen
|
||||
// unabhaengig lauffaehig, kein gemeinsames Modul fuer solche
|
||||
// Kleinigkeiten").
|
||||
// Same logic as main.cpp::isNightDimHours() - night = between
|
||||
// sunset and sunrise at the active location (see
|
||||
// sun_times.h), with fallback to a fixed 22:00-06:00 window
|
||||
// as long as location/time are not yet known. Deliberately
|
||||
// duplicated instead of shared, see CLAUDE.md convention ("each screen
|
||||
// independently runnable, no shared module for such
|
||||
// small items").
|
||||
bool isNightHours() {
|
||||
time_t now = time(nullptr);
|
||||
if (now <= 8 * 3600 * 2) return false; // Uhrzeit noch nicht per NTP synchronisiert
|
||||
if (now <= 8 * 3600 * 2) return false; // time not yet synced via NTP
|
||||
|
||||
struct tm tmNow;
|
||||
localtime_r(&now, &tmNow);
|
||||
@@ -164,22 +164,23 @@ L.rangeBtn = {(int16_t)(Config::SCREEN_WIDTH - 72), (int16_t)(L.infoTop + 4), 64
|
||||
return (hour >= 22 || hour < 6);
|
||||
}
|
||||
|
||||
// Nur aktiv, wenn der Nutzer die Nachtdimmung (Menue > System) eingeschaltet
|
||||
// hat UND wir gerade im Nachtfenster sind - dieselbe Einstellung, die sonst
|
||||
// nur die Hintergrundbeleuchtung dimmt, dimmt jetzt zusaetzlich auch die
|
||||
// Radar-Farben (Flugzeug-Marker + Sweep-Linie), damit das Display nachts
|
||||
// insgesamt weniger blendet.
|
||||
// Only active when the user has enabled night dimming (Menu > System)
|
||||
// AND we are currently in the night window - the same setting that otherwise
|
||||
// only dims the backlight, now additionally also dims the
|
||||
// radar colors (aircraft markers + sweep line), so the display
|
||||
// is less glaring overall at night.
|
||||
bool nightDimActiveNow() {
|
||||
return SettingsStore::nightDimmingEnabled() && isNightHours();
|
||||
}
|
||||
|
||||
// Farbe je nach Flughoehe - gedaempft (dunkleres Gruen/Oliv) waehrend der
|
||||
// Nachtdimmung.
|
||||
// Color by altitude - dimmed (darker green/olive) during
|
||||
// night dimming.
|
||||
//
|
||||
// Die rote Hoehenstufe wird bewusst NICHT gedaempft: der bisherige
|
||||
// Dimm-Ton (160,30,0) sah auf dem Display eher orange-braun statt rot
|
||||
// aus und war dadurch nicht mehr klar von der gelben Stufe zu
|
||||
// unterscheiden. Rot bleibt deshalb Tag und Nacht der reine TFT_RED-Ton.
|
||||
// The red altitude level is deliberately NOT dimmed: the previous
|
||||
// dimmed tone (160,30,0) looked orange-brown instead of red
|
||||
// on the display and was therefore no longer clearly distinguishable
|
||||
// from the yellow level. Red therefore remains the pure TFT_RED tone
|
||||
// day and night.
|
||||
uint16_t colorForAltitude(TFT_eSPI& gfx, int32_t altFt) {
|
||||
bool dim = nightDimActiveNow();
|
||||
if (altFt < Config::COLOR_LOW_ALT_THRESHOLD_FT)
|
||||
@@ -193,34 +194,34 @@ L.rangeBtn = {(int16_t)(Config::SCREEN_WIDTH - 72), (int16_t)(L.infoTop + 4), 64
|
||||
return nightDimActiveNow() ? gfx.color565(0, 110, 0) : TFT_GREEN;
|
||||
}
|
||||
|
||||
// Feste Blau-Farbe fuer Bodenfahrzeug-Marker (ADS-B-Emitter-Kategorie
|
||||
// "C*", z.B. Flughafen-Fahrzeuge) - bewusst unabhaengig von
|
||||
// colorForAltitude(), da Bodenfahrzeuge praktisch immer auf 0ft stehen
|
||||
// und sonst dieselbe Farbe wie niedrig fliegende Flugzeuge haetten,
|
||||
// was die optische Unterscheidung (Quadrat vs. Kreis) unnoetig
|
||||
// erschweren wuerde. Bei Nachtdimmung gedaempft, gleiches Muster wie
|
||||
// Fixed blue color for ground vehicle markers (ADS-B emitter category
|
||||
// "C*", e.g. airport vehicles) - deliberately independent of
|
||||
// colorForAltitude(), since ground vehicles are practically always at 0ft
|
||||
// and would otherwise have the same color as low-flying aircraft,
|
||||
// which would unnecessarily complicate the visual distinction
|
||||
// (square vs. circle). Dimmed during night dimming, same pattern as
|
||||
// colorForAltitude().
|
||||
uint16_t colorForGroundVehicle(TFT_eSPI& gfx) {
|
||||
return nightDimActiveNow() ? gfx.color565(0, 0, 160) : TFT_BLUE;
|
||||
}
|
||||
|
||||
// Zeigt die PEILUNG (nicht zu verwechseln mit der Flugrichtung/heading)
|
||||
// zum aktuell ausgewaehlten Flugzeug: eine duenne, gepunktete Linie vom
|
||||
// Radar-Zentrum zum Kreisrand in Richtung bearingDeg, plus die Gradzahl
|
||||
// direkt ausserhalb des Rings. Hilft dabei, das Flugzeug tatsaechlich am
|
||||
// Himmel zu finden ("in diese Richtung schauen"). Nur fuer den Moment
|
||||
// des Zeichnens relevant - beim naechsten Sweep-Tick/Redraw wird sie vom
|
||||
// normalen Hintergrund-Redraw automatisch mit geloescht und neu gesetzt.
|
||||
// Shows the BEARING (not to be confused with the heading)
|
||||
// to the currently selected aircraft: a thin, dotted line from the
|
||||
// radar center to the circle edge in the direction of bearingDeg, plus the degree number
|
||||
// directly outside the ring. Helps to actually find the aircraft
|
||||
// in the sky ("look in this direction"). Only relevant for the moment
|
||||
// of drawing - on the next sweep tick/redraw it is automatically
|
||||
// erased and redrawn by the normal background redraw.
|
||||
void drawBearingIndicator(TFT_eSPI& gfx, const Layout& L, float bearingDeg) {
|
||||
double rad = bearingDeg * PI / 180.0;
|
||||
double s = sin(rad), c = cos(rad);
|
||||
|
||||
// Gepunktete Linie: kurze Segmente statt einer durchgezogenen Linie,
|
||||
// damit sie sich optisch von den Kompass-Kreuzlinien und Ringen
|
||||
// unterscheidet und nicht wie ein staendiges UI-Element wirkt.
|
||||
// Dotted line: short segments instead of a solid line,
|
||||
// so it visually differs from the compass cross lines and rings
|
||||
// and does not look like a permanent UI element.
|
||||
constexpr uint8_t DOT_COUNT = 10;
|
||||
for (uint8_t i = 0; i < DOT_COUNT; i++) {
|
||||
if (i % 2 != 0) continue; // nur jedes zweite Segment zeichnen
|
||||
if (i % 2 != 0) continue; // only draw every second segment
|
||||
float t0 = (float)i / DOT_COUNT;
|
||||
float t1 = (float)(i + 1) / DOT_COUNT;
|
||||
int16_t x0 = L.cx + (int16_t)lround(t0 * L.radius * s);
|
||||
@@ -230,12 +231,12 @@ L.rangeBtn = {(int16_t)(Config::SCREEN_WIDTH - 72), (int16_t)(L.infoTop + 4), 64
|
||||
gfx.drawLine(x0, y0, x1, y1, TFT_WHITE);
|
||||
}
|
||||
|
||||
// Gradzahl knapp ausserhalb des Rings, an der Stelle wo die Peilung
|
||||
// den Kreisrand durchstoesst. Der Radius-Kreis nutzt fast die volle
|
||||
// Bildschirmbreite (siehe computeLayout(): nur 6px Rand) - ein Label
|
||||
// ausserhalb des Rings wuerde bei Ost/West-Peilungen ueber den
|
||||
// sichtbaren Bereich hinausragen. Deshalb: Label-Position an die
|
||||
// Bildschirmgrenzen clampen statt stur dem Winkel zu folgen.
|
||||
// Bearing degree just outside the ring, at the point where the bearing
|
||||
// line intersects the circle edge. The radius circle uses almost the full
|
||||
// screen width (see computeLayout(): only 6px margin) - a label
|
||||
// outside the ring would extend beyond the visible area
|
||||
// for east/west bearings. Therefore: clamp label position to
|
||||
// screen boundaries instead of blindly following the angle.
|
||||
int16_t labelX = L.cx + (int16_t)lround((L.radius + 10) * s);
|
||||
int16_t labelY = L.cy - (int16_t)lround((L.radius + 10) * c);
|
||||
labelX = constrain(labelX, (int16_t)14, (int16_t)(Config::SCREEN_WIDTH - 14));
|
||||
@@ -248,11 +249,11 @@ L.rangeBtn = {(int16_t)(Config::SCREEN_WIDTH - 72), (int16_t)(L.infoTop + 4), 64
|
||||
gfx.setTextDatum(TL_DATUM);
|
||||
}
|
||||
|
||||
// kurzer Kursstrich in Flugrichtung (headingDeg) MIT Pfeilspitze am Ende -
|
||||
// eine reine Linie ohne Spitze war nicht eindeutig lesbar (nicht erkennbar,
|
||||
// welches Ende "vorne" ist). Die Spitze besteht aus zwei kurzen Strichen,
|
||||
// die knapp vor der Linienspitze schraeg nach aussen abzweigen (klassisches
|
||||
// Chevron-/Pfeilkopf-Symbol, wie bei ATC-Radardarstellungen ueblich).
|
||||
// Short course line in flight direction (headingDeg) WITH arrowhead at the end -
|
||||
// a plain line without a tip was not clearly readable (not recognizable
|
||||
// which end is "front"). The tip consists of two short strokes
|
||||
// that branch diagonally outward just before the line tip (classic
|
||||
// chevron/arrowhead symbol, as typical in ATC radar displays).
|
||||
void drawAircraftMarker(TFT_eSPI& gfx, int16_t x, int16_t y, float headingDeg, uint16_t color) {
|
||||
gfx.fillCircle(x, y, 5, color);
|
||||
|
||||
@@ -263,9 +264,9 @@ L.rangeBtn = {(int16_t)(Config::SCREEN_WIDTH - 72), (int16_t)(L.infoTop + 4), 64
|
||||
int16_t tipY = y + dy;
|
||||
gfx.drawLine(x, y, tipX, tipY, color);
|
||||
|
||||
// Pfeilspitze: zwei kurze Striche von der Spitze aus, je 150 Grad
|
||||
// zur Kurslinie zurueckgeklappt (also leicht "nach hinten" zeigend,
|
||||
// wie bei einem Pfeilkopf "^" ueblich).
|
||||
// Arrowhead: two short strokes from the tip, each folded back 150 degrees
|
||||
// to the course line (i.e. slightly pointing "backward",
|
||||
// as is common for an arrowhead "^").
|
||||
constexpr double WING_ANGLE_RAD = 150.0 * PI / 180.0;
|
||||
constexpr int16_t WING_LEN = 4;
|
||||
double wing1 = rad + WING_ANGLE_RAD;
|
||||
@@ -278,21 +279,21 @@ L.rangeBtn = {(int16_t)(Config::SCREEN_WIDTH - 72), (int16_t)(L.infoTop + 4), 64
|
||||
gfx.drawLine(tipX, tipY, w2x, w2y, color);
|
||||
}
|
||||
|
||||
// Eigener Marker fuer Bodenfahrzeuge (ADS-B-Kategorie "C*") - ein
|
||||
// gefuelltes Quadrat statt Kreis+Pfeilkopf, damit sie auf dem Radar
|
||||
// klar von echten Flugzeugen zu unterscheiden sind (Heading/Kurs ist
|
||||
// bei Bodenfahrzeugen ausserdem meist nicht aussagekraeftig).
|
||||
// Separate marker for ground vehicles (ADS-B category "C*") - a
|
||||
// filled square instead of circle+arrowhead, so they are clearly
|
||||
// distinguishable from real aircraft on the radar (heading/course is
|
||||
// also usually not meaningful for ground vehicles).
|
||||
void drawGroundVehicleMarker(TFT_eSPI& gfx, int16_t x, int16_t y, uint16_t color) {
|
||||
constexpr int16_t HALF = 4;
|
||||
gfx.fillRect((int16_t)(x - HALF), (int16_t)(y - HALF), (int16_t)(2 * HALF), (int16_t)(2 * HALF), color);
|
||||
}
|
||||
|
||||
// Eigener Marker fuer Hubschrauber (ADS-B-Emitter-Kategorie "A7" =
|
||||
// Rotorcraft) - ein gefuellter Kreis mit durchgehendem Rotorkreuz statt
|
||||
// Pfeilkopf, da Hubschrauber im Schwebeflug keinen aussagekraeftigen
|
||||
// "nach vorne"-Kurs wie ein Flugzeug haben (gleiche Ueberlegung wie beim
|
||||
// Bodenfahrzeug-Marker, der aus demselben Grund ebenfalls ohne
|
||||
// Kurslinie auskommt).
|
||||
// Separate marker for helicopters (ADS-B emitter category "A7" =
|
||||
// Rotorcraft) - a filled circle with a solid rotor cross instead of
|
||||
// an arrowhead, since helicopters in hover have no meaningful
|
||||
// "forward" course like a fixed-wing aircraft (same reasoning as the
|
||||
// ground vehicle marker, which for the same reason also
|
||||
// does without a course line).
|
||||
void drawHelicopterMarker(TFT_eSPI& gfx, int16_t x, int16_t y, uint16_t color) {
|
||||
constexpr int16_t ROTOR_LEN = 8;
|
||||
gfx.fillCircle(x, y, 4, color);
|
||||
@@ -300,14 +301,14 @@ L.rangeBtn = {(int16_t)(Config::SCREEN_WIDTH - 72), (int16_t)(L.infoTop + 4), 64
|
||||
gfx.drawLine(x, (int16_t)(y - ROTOR_LEN), x, (int16_t)(y + ROTOR_LEN), color);
|
||||
}
|
||||
|
||||
// Eigener Marker fuer "Heavy"-Flugzeuge (ADS-B-Emitter-Kategorie "A5" -
|
||||
// Flugzeuge ueber 136t Starthoechstgewicht, z.B. A380/B747/B777/A330).
|
||||
// Gleicher Aufbau wie drawAircraftMarker() (Kreis + Kurslinie mit
|
||||
// Pfeilkopf), aber mit groesserem Kreis und einem zusaetzlichen duennen
|
||||
// Aussenring - auf den ersten Blick als "groesser/schwerer" erkennbar,
|
||||
// ohne eine komplett neue Formsprache einzufuehren. Die Farbe bleibt wie
|
||||
// gewohnt die Hoehenfarbe (colorForAltitude()) - die Form transportiert
|
||||
// "Heavy", kein Alarm-/Auffaelligkeitszustand.
|
||||
// Separate marker for "Heavy" aircraft (ADS-B emitter category "A5" -
|
||||
// aircraft over 136t maximum takeoff weight, e.g. A380/B747/B777/A330).
|
||||
// Same structure as drawAircraftMarker() (circle + course line with
|
||||
// arrowhead), but with a larger circle and an additional thin
|
||||
// outer ring - recognizable at first glance as "larger/heavier",
|
||||
// without introducing a completely new shape language. The color remains
|
||||
// the usual altitude color (colorForAltitude()) - the shape conveys
|
||||
// "Heavy", not an alarm/conspicuity state.
|
||||
void drawHeavyMarker(TFT_eSPI& gfx, int16_t x, int16_t y, float headingDeg, uint16_t color) {
|
||||
gfx.fillCircle(x, y, 6, color);
|
||||
gfx.drawCircle(x, y, 8, color);
|
||||
@@ -368,12 +369,12 @@ L.rangeBtn = {(int16_t)(Config::SCREEN_WIDTH - 72), (int16_t)(L.infoTop + 4), 64
|
||||
snprintf(highLabel, sizeof(highLabel), ">30k ft");
|
||||
}
|
||||
|
||||
// Dieselbe colorForAltitude()-Funktion wie fuer die Flugzeug-Marker
|
||||
// selbst verwenden (mit einer typischen Hoehe je Band) - sonst zeigt
|
||||
// die Legende bei aktiver Nachtdimmung (Sonnenunter- bis -aufgang) die HELLEN Farben,
|
||||
// waehrend die Marker/Beschriftungen auf dem Radar bereits gedaempft
|
||||
// sind. Das fuehrte dazu, dass z.B. ein rotes Flugzeug nachts eher
|
||||
// dunkelorange wirkte, obwohl die Legende noch reines Rot zeigte.
|
||||
// Use the same colorForAltitude() function as for the aircraft markers
|
||||
// themselves (with a typical altitude per band) - otherwise the
|
||||
// legend would show the BRIGHT colors during active night dimming (sunset to sunrise),
|
||||
// while the markers/labels on the radar are already dimmed.
|
||||
// This caused e.g. a red aircraft to appear dark orange at night,
|
||||
// even though the legend still showed pure red.
|
||||
struct { uint16_t color; const char* label; } items[3] = {
|
||||
{colorForAltitude(gfx, 0), lowLabel},
|
||||
{colorForAltitude(gfx, Config::COLOR_LOW_ALT_THRESHOLD_FT), midLabel},
|
||||
@@ -382,15 +383,15 @@ L.rangeBtn = {(int16_t)(Config::SCREEN_WIDTH - 72), (int16_t)(L.infoTop + 4), 64
|
||||
int16_t segW = Config::SCREEN_WIDTH / 3;
|
||||
gfx.setTextColor(TFT_WHITE, TFT_BLACK);
|
||||
|
||||
// Aussenspalten (gruen/rot) bleiben auf ihrer festen 1/3-Raster-
|
||||
// Position (x0 = i*segW+6). Die mittlere (gelbe) Spalte wurde
|
||||
// bisher genauso starr positioniert, sass dadurch aber optisch zu
|
||||
// weit rechts: ihr Label ("3000-9100") ist deutlich laenger als
|
||||
// die Aussenlabels, wodurch rechts kaum noch Luft zum roten
|
||||
// Eintrag blieb, waehrend links viel Freiraum zum kurzen gruenen
|
||||
// Label uebrig war. Jetzt wird die gelbe Spalte stattdessen mittig
|
||||
// in die Luecke zwischen Ende des gruenen Textes und Anfang des
|
||||
// roten Punkts gesetzt.
|
||||
// Outer columns (green/red) stay at their fixed 1/3 grid
|
||||
// position (x0 = i*segW+6). The middle (yellow) column was
|
||||
// previously positioned just as rigidly, but visually sat too
|
||||
// far right: its label ("3000-9100") is noticeably longer than
|
||||
// the outer labels, leaving hardly any space on the right to the
|
||||
// red entry, while lots of empty space remained on the left next to the short green
|
||||
// label. Now the yellow column is instead placed
|
||||
// centered in the gap between the end of the green text and the start of the
|
||||
// red dot.
|
||||
int16_t x0Low = 0 * segW + 6;
|
||||
int16_t x0High = 2 * segW + 6;
|
||||
|
||||
@@ -412,10 +413,10 @@ L.rangeBtn = {(int16_t)(Config::SCREEN_WIDTH - 72), (int16_t)(L.infoTop + 4), 64
|
||||
gfx.setCursor(x0Mid + 7, y);
|
||||
gfx.print(items[1].label);
|
||||
|
||||
// Zweite Legenden-Zeile fuer die blauen Bodenfahrzeug-Quadrate - nur
|
||||
// wenn sie ueberhaupt sichtbar sind (Flugoptionen >
|
||||
// "Bodenfahrzeuge ausblenden" == aus). infoBarHeight() reserviert
|
||||
// den dafuer noetigen zusaetzlichen Platz nur in diesem Fall, siehe
|
||||
// Second legend row for the blue ground vehicle squares - only
|
||||
// when they are actually visible (Flight Options >
|
||||
// "Hide ground vehicles" == off). infoBarHeight() reserves
|
||||
// additional space for this only in that case, see
|
||||
// computeLayout().
|
||||
if (!SettingsStore::hideGroundVehicles()) {
|
||||
int16_t gy = (int16_t)(y + INFO_BAR_GROUND_ROW_H);
|
||||
@@ -469,10 +470,10 @@ L.rangeBtn = {(int16_t)(Config::SCREEN_WIDTH - 72), (int16_t)(L.infoTop + 4), 64
|
||||
gfx.drawString("S", L.cx, L.cy + L.radius - 10);
|
||||
gfx.drawString("E", L.cx + L.radius - 10, L.cy);
|
||||
gfx.drawString("W", L.cx - L.radius + 10, L.cy);
|
||||
// Ring-Beschriftungen (Zwischenabstaende) respektieren jetzt die
|
||||
// Einheiten-Einstellung (Menue > Einheiten) - vorher immer in km,
|
||||
// auch wenn Imperial (nm) eingestellt war. Gleiches Umrechnungs-
|
||||
// Muster wie beim Range-Button unten und der Legende oben.
|
||||
// Ring labels (intermediate distances) now respect the
|
||||
// unit setting (Menu > Units) - previously always in km,
|
||||
// even when Imperial (nm) was set. Same conversion
|
||||
// pattern as the range button below and the legend above.
|
||||
bool metric = LocationManager::useMetricUnits();
|
||||
float displayRange = metric ? rangeKm : Units::kmToNm(rangeKm);
|
||||
char ringLabel[8];
|
||||
@@ -483,14 +484,14 @@ L.rangeBtn = {(int16_t)(Config::SCREEN_WIDTH - 72), (int16_t)(L.infoTop + 4), 64
|
||||
gfx.setTextDatum(TL_DATUM);
|
||||
}
|
||||
|
||||
// Laufschrift-Zustand fuer EINE Detail-Panel-Zeile - gleiches
|
||||
// Grundprinzip wie InfoMarquee oben, aber das Detail-Panel hat bis zu
|
||||
// neun solcher Zeilen gleichzeitig (Airline, Modell, Typ, Hoehe,
|
||||
// Geschw., Steig-/Sinkrate, Distanz/Peilung, Squawk, Sitzplaetze),
|
||||
// deshalb ein eigener Zustand pro Zeile statt einer einzigen globalen
|
||||
// Instanz. y/h/maxWidth/fg merken sich die Zeilen-Geometrie, damit
|
||||
// tickDetailPanelMarquees() (siehe unten) ohne Zusatzparameter weiss,
|
||||
// wo/wie jede Zeile neu zu zeichnen ist.
|
||||
// Marquee state for ONE detail panel line - same basic
|
||||
// principle as InfoMarquee above, but the detail panel has up to
|
||||
// nine such lines simultaneously (airline, model, type, altitude,
|
||||
// speed, climb/sink rate, distance/bearing, squawk, seats),
|
||||
// hence a separate state per line instead of a single global
|
||||
// instance. y/h/maxWidth/fg remember the line geometry so that
|
||||
// tickDetailPanelMarquees() (see below) knows where/how to
|
||||
// redraw each line without extra parameters.
|
||||
struct LineMarquee {
|
||||
String text;
|
||||
String ring;
|
||||
@@ -509,22 +510,22 @@ L.rangeBtn = {(int16_t)(Config::SCREEN_WIDTH - 72), (int16_t)(L.infoTop + 4), 64
|
||||
};
|
||||
PanelState lastPanel;
|
||||
|
||||
// Zeichnet eine Detail-Panel-Zeile NEU, wenn sich ihr Text geaendert
|
||||
// hat (oder das Panel komplett neu aufgebaut wird) - merkt sich dabei
|
||||
// auch, ob der Text zu breit fuer die verfuegbare Breite ist
|
||||
// (needsScroll) und baut bei Bedarf den Ring-Puffer fuer die
|
||||
// Laufschrift auf (gleiches Muster wie infoMarqueeWindow() oben).
|
||||
// Passt zu breite Zeilen ("Modell: ...", "Distanz: ...") NICHT mehr
|
||||
// mit "..." ab, sondern laesst sie horizontal durchscrollen - siehe
|
||||
// tickDetailPanelMarquees() weiter unten fuer den Teil, der das
|
||||
// tatsaechliche Weiterscrollen zwischen zwei Datenaktualisierungen
|
||||
// uebernimmt (render() liefert i.d.R. nur alle ~300ms neue Werte,
|
||||
// das waere fuer eine fluessige Laufschrift viel zu selten).
|
||||
// Redraws a detail panel line when its text has changed
|
||||
// (or the panel is being rebuilt) - remembers whether
|
||||
// the text is too wide for the available width
|
||||
// (needsScroll) and builds the ring buffer for the
|
||||
// marquee if needed (same pattern as infoMarqueeWindow() above).
|
||||
// Lines that are too wide ("Model: ...", "Distance: ...") are NO
|
||||
// LONGER abbreviated with "..." but scrolled horizontally - see
|
||||
// tickDetailPanelMarquees() below for the part that
|
||||
// actually advances the scrolling between data updates
|
||||
// (render() typically only supplies new values every ~300ms,
|
||||
// which would be far too rare for smooth scrolling).
|
||||
void updateMarqueeLine(TFT_eSPI& gfx, int16_t y, int16_t h, int16_t maxWidth,
|
||||
uint16_t fg, LineMarquee& m, const String& newText, bool forceFull) {
|
||||
// Geometrie/Farbe IMMER aktualisieren (auch ohne Textaenderung) -
|
||||
// tickDetailPanelMarquees() braucht diese Werte, um beim naechsten
|
||||
// Scroll-Schritt an der richtigen Stelle neu zu zeichnen.
|
||||
// Always update geometry/color (even without text change) -
|
||||
// tickDetailPanelMarquees() needs these values to redraw
|
||||
// at the correct position on the next scroll step.
|
||||
m.y = y;
|
||||
m.h = h;
|
||||
m.maxWidth = maxWidth;
|
||||
@@ -534,7 +535,7 @@ L.rangeBtn = {(int16_t)(Config::SCREEN_WIDTH - 72), (int16_t)(L.infoTop + 4), 64
|
||||
|
||||
m.text = newText;
|
||||
m.needsScroll = gfx.textWidth(newText) > maxWidth;
|
||||
String withGap = newText + " "; // 3 Leerzeichen Luecke vor der Wiederholung
|
||||
String withGap = newText + " "; // 3 space gap before repetition
|
||||
m.ring = withGap + withGap;
|
||||
m.charOffset = 0;
|
||||
m.lastStepMs = millis();
|
||||
@@ -545,11 +546,11 @@ L.rangeBtn = {(int16_t)(Config::SCREEN_WIDTH - 72), (int16_t)(L.infoTop + 4), 64
|
||||
gfx.print(m.needsScroll ? infoMarqueeWindow(gfx, m.ring, 0, maxWidth) : newText);
|
||||
}
|
||||
|
||||
// Laesst eine einzelne Detail-Panel-Zeile weiterscrollen, falls sie zu
|
||||
// breit ist (needsScroll) und genug Zeit seit dem letzten Schritt
|
||||
// vergangen ist - sonst passiert nichts (kein unnoetiges Neuzeichnen
|
||||
// fuer Zeilen, die ohnehin komplett passen). Wird von
|
||||
// tickDetailPanelMarquees() fuer alle neun Zeilen aufgerufen.
|
||||
// Advances a single detail panel line if it is too
|
||||
// wide (needsScroll) and enough time has passed since the last step
|
||||
// - otherwise nothing happens (no unnecessary redrawing
|
||||
// for lines that fit completely). Called by
|
||||
// tickDetailPanelMarquees() for all nine lines.
|
||||
void advanceAndDrawMarqueeLine(TFT_eSPI& gfx, LineMarquee& m) {
|
||||
if (!m.needsScroll) return;
|
||||
|
||||
@@ -567,12 +568,12 @@ L.rangeBtn = {(int16_t)(Config::SCREEN_WIDTH - 72), (int16_t)(L.infoTop + 4), 64
|
||||
gfx.print(infoMarqueeWindow(gfx, m.ring, m.charOffset, m.maxWidth));
|
||||
}
|
||||
|
||||
// Laesst alle Detail-Panel-Zeilen weiterscrollen - waehrend das Panel
|
||||
// offen ist, ruft tick() (alle ~80ms) das hier auf, DAZWISCHEN also
|
||||
// viel oefter als render() (das nur bei neuen Flugzeug-Daten,
|
||||
// ~alle 300ms, feuert) neue Werte liefert. Ohne diesen zusaetzlichen
|
||||
// Aufruf wuerde eine lange Zeile bei jedem render()-Aufruf zwar einen
|
||||
// Schritt weiterspringen, aber nicht fluessig durchlaufen.
|
||||
// Lets all detail panel lines scroll - while the panel
|
||||
// is open, tick() (every ~80ms) calls this, in BETWEEN much
|
||||
// more often than render() (which only fires on new aircraft data,
|
||||
// every ~300ms). Without this additional
|
||||
// call, a long line would jump one step on each render() call
|
||||
// but would not scroll smoothly.
|
||||
void tickDetailPanelMarquees(TFT_eSPI& gfx) {
|
||||
if (!lastPanel.valid) return;
|
||||
advanceAndDrawMarqueeLine(gfx, lastPanel.airline);
|
||||
@@ -632,11 +633,11 @@ L.rangeBtn = {(int16_t)(Config::SCREEN_WIDTH - 72), (int16_t)(L.infoTop + 4), 64
|
||||
updateMarqueeLine(gfx, y, LINE_H, textMaxWidth, TFT_GREEN, lastPanel.type, typeLine, forceFull);
|
||||
y += LINE_H;
|
||||
|
||||
// Start-/Zielflughafen (ICAO-Code) ueber AircraftDetails::get() -
|
||||
// wird zusammen mit dem Modell abgefragt (siehe aircraft_details.cpp),
|
||||
// aber ueber das Rufzeichen statt den Hex-Code aufgeloest. Ohne
|
||||
// Rufzeichen (z.B. manche Sichtflug-Maschinen) bleibt die Route
|
||||
// grundsaetzlich unbekannt, kein Abruf noetig.
|
||||
// Origin/destination airport (ICAO code) via AircraftDetails::get() -
|
||||
// is queried together with the model (see aircraft_details.cpp),
|
||||
// but resolved via the callsign instead of the hex code. Without
|
||||
// a callsign (e.g. some VFR aircraft) the route remains
|
||||
// fundamentally unknown, no lookup needed.
|
||||
String routeLine;
|
||||
if (!a.callsign[0]) {
|
||||
routeLine = String(I18n::t(StringId::DETAIL_ROUTE)) + I18n::t(StringId::DETAIL_UNKNOWN);
|
||||
@@ -674,11 +675,11 @@ L.rangeBtn = {(int16_t)(Config::SCREEN_WIDTH - 72), (int16_t)(L.infoTop + 4), 64
|
||||
updateMarqueeLine(gfx, y, LINE_H, textMaxWidth, TFT_GREEN, lastPanel.climb, climbLine, forceFull);
|
||||
y += LINE_H;
|
||||
|
||||
// Zusaetzlich zu km/nm auch Meilen (mi) - nm allein war fuer
|
||||
// Laien-Nutzer aus Ländern mit imperialen Einheiten (v.a. USA)
|
||||
// nicht selbsterklaerend, "Meilen" sind dort die gebraeuchlichere
|
||||
// Alltags-Distanzeinheit (nm bleibt trotzdem stehen, da es zur
|
||||
// Geschwindigkeit in kt passt: 1 kt = 1 nm/h).
|
||||
// In addition to km/nm also miles (mi) - nm alone was
|
||||
// not self-explanatory for lay users from countries with
|
||||
// imperial units (especially the US), "miles" is the more common
|
||||
// everyday distance unit there (nm still remains since it matches
|
||||
// speed in kt: 1 kt = 1 nm/h).
|
||||
snprintf(buf, sizeof(buf), "%s%.0fkm / %.0fnm / %.0fmi %s%.0f", I18n::t(StringId::DETAIL_DIST),
|
||||
a.distanceKm, Units::kmToNm(a.distanceKm), Units::kmToMi(a.distanceKm),
|
||||
I18n::t(StringId::DETAIL_HDG), a.headingDeg);
|
||||
@@ -738,15 +739,15 @@ L.rangeBtn = {(int16_t)(Config::SCREEN_WIDTH - 72), (int16_t)(L.infoTop + 4), 64
|
||||
}
|
||||
}
|
||||
|
||||
// Hintergrund-Sterne AUSSERHALB des Radarkreises (auf Wunsch: dasselbe
|
||||
// dekorative Sternenfunkeln wie im Detail-Panel/Menue, aber ohne mit
|
||||
// dem staendig neu gezeichneten Kreisinhalt zu kollidieren - genau DAS
|
||||
// wuerde "ruckeln" verursachen, da Sweep-Linie/Blips/Ringe innerhalb
|
||||
// des Kreises bei jedem tick() sowieso neu gezeichnet werden und dabei
|
||||
// die Sterne dort staendig wieder verschlucken wuerden). Ausserhalb
|
||||
// des Kreises bleibt der Hintergrund dauerhaft schwarz und wird von
|
||||
// sonst niemandem angefasst - dort koennen die Sterne frei vor sich
|
||||
// hin twinkeln, voellig unabhaengig von Sweep-Linie/Blip-Redraws.
|
||||
// Background stars OUTSIDE the radar circle (requested: the same
|
||||
// decorative star twinkling as in the detail panel/menu, but without
|
||||
// colliding with the constantly redrawn circle content - THAT would
|
||||
// cause stuttering, since the sweep line/blips/rings within the
|
||||
// circle are redrawn on every tick() anyway and would constantly
|
||||
// swallow the stars there). Outside the circle the background remains
|
||||
// permanently black and is touched by no one else - the stars can
|
||||
// freely twinkle there, completely independent of sweep line/blip
|
||||
// redraws.
|
||||
constexpr uint8_t BG_STAR_COUNT = 20;
|
||||
struct BgStar {
|
||||
int16_t x, y;
|
||||
@@ -756,15 +757,15 @@ L.rangeBtn = {(int16_t)(Config::SCREEN_WIDTH - 72), (int16_t)(L.infoTop + 4), 64
|
||||
BgStar bgStars[BG_STAR_COUNT];
|
||||
bool bgStarsInitialized = false;
|
||||
|
||||
// Platziert jeden Stern per Rejection-Sampling irgendwo im Bereich
|
||||
// [top..L.infoTop) x [0..SCREEN_WIDTH), aber nur wenn der Punkt
|
||||
// ausserhalb von Radius+MARGIN um den Kreismittelpunkt liegt (MARGIN
|
||||
// haelt zusaetzlich Abstand zum Ring und zum "N"-Kompasslabel knapp
|
||||
// ausserhalb des Kreises). Findet ein Versuch nach 25 Anlaeufen keinen
|
||||
// passenden Punkt (praktisch nie, da links/rechts vom Kreis immer
|
||||
// reichlich Platz ist), wird einfach der letzte Versuchspunkt
|
||||
// uebernommen - rein kosmetisches Feature, kein Grund fuer eine
|
||||
// Endlosschleife.
|
||||
// Places each star via rejection sampling somewhere in the area
|
||||
// [top..L.infoTop) x [0..SCREEN_WIDTH), but only if the point
|
||||
// is outside radius+MARGIN around the circle center (MARGIN
|
||||
// additionally keeps distance from the ring and the "N" compass label
|
||||
// just outside the circle). If an attempt after 25 tries finds no
|
||||
// suitable point (practically never, since there is always plenty
|
||||
// of space left/right of the circle), the last attempted point is
|
||||
// simply used - purely cosmetic feature, no reason for an
|
||||
// infinite loop.
|
||||
void initBgStarsIfNeeded(const Layout& L, int16_t top) {
|
||||
if (bgStarsInitialized) return;
|
||||
randomSeed((uint32_t)esp_random());
|
||||
@@ -855,7 +856,7 @@ void render(TFT_eSPI& tft, int16_t top) {
|
||||
|
||||
bool selectionStillPresent = false;
|
||||
Aircraft selected{};
|
||||
uint8_t visibleCount = 0; // nach allen Filtern (Reichweite, Bodenfahrzeuge, Airline-Filter)
|
||||
uint8_t visibleCount = 0; // after all filters (range, ground vehicles, airline filter)
|
||||
|
||||
for (uint8_t i = 0; i < MAX_HIT_POINTS; i++) hitPoints[i].valid = false;
|
||||
|
||||
@@ -876,14 +877,14 @@ if (AirlineFilter::isHidden(a.callsign)) continue;
|
||||
RadarMath::PolarCoord polar{a.distanceKm, a.bearingDeg};
|
||||
RadarMath::ScreenPoint pt = RadarMath::toScreen(polar, L.cx, L.cy, L.radius, rangeKm);
|
||||
|
||||
// ADS-B-Emitter-Kategorie "C*" = Bodenfahrzeug (nur ueberhaupt
|
||||
// sichtbar, wenn "Bodenfahrzeuge ausblenden" aus ist, siehe Filter
|
||||
// oben) - eigene Farbe/Marker statt der hoehenbasierten Flugzeug-
|
||||
// Darstellung, siehe drawGroundVehicleMarker()/colorForGroundVehicle().
|
||||
// ADS-B emitter category "C*" = ground vehicle (only ever
|
||||
// visible if "Hide ground vehicles" is off, see filter
|
||||
// above) - separate color/marker instead of altitude-based
|
||||
// rendering, see drawGroundVehicleMarker()/colorForGroundVehicle().
|
||||
bool isGroundVehicle = a.category[0] == 'C';
|
||||
bool isRotorcraft = a.category[0] == 'A' && a.category[1] == '7';
|
||||
// ADS-B-Emitter-Kategorie "A5" = "Heavy" - eigene Markerform (siehe
|
||||
// drawHeavyMarker()), unabhaengig von jeglicher Ring-Markierung.
|
||||
// ADS-B emitter category "A5" = "Heavy" - separate marker shape (see
|
||||
// drawHeavyMarker()), independent of any ring marking.
|
||||
bool isHeavy = a.category[0] == 'A' && a.category[1] == '5';
|
||||
uint16_t color = isGroundVehicle ? colorForGroundVehicle(tft) : colorForAltitude(tft, a.altBaroFt);
|
||||
bool isSelected = selectedHex[0] && strcmp(a.hex, selectedHex) == 0;
|
||||
@@ -947,7 +948,7 @@ if (AirlineFilter::isHidden(a.callsign)) continue;
|
||||
lastPanel.valid = false;
|
||||
int16_t infoTop = L.infoTop;
|
||||
tft.drawFastHLine(0, infoTop, Config::SCREEN_WIDTH, TFT_DARKGREY);
|
||||
// Info-Bar Button-Leiste: [?] [S] [L] [50km]
|
||||
// Info bar button row: [?] [S] [L] [50km]
|
||||
char rangeLabel[8];
|
||||
bool rangeMetric = LocationManager::useMetricUnits();
|
||||
if (rangeMetric) {
|
||||
@@ -973,11 +974,11 @@ void tick(TFT_eSPI& tft, int16_t top, uint32_t deltaMs) {
|
||||
if (selectedHex[0]) {
|
||||
tft.startWrite();
|
||||
updateDetailPanelStars(tft);
|
||||
// Laesst zu breite Detail-Panel-Zeilen (z.B. "Modell: ..." oder
|
||||
// "Distanz: ...") weiterscrollen, statt wie zuvor mit "..." starr
|
||||
// abgeschnitten zu bleiben - render() liefert neue Werte nur alle
|
||||
// ~300ms, das reicht fuer eine fluessige Laufschrift nicht aus,
|
||||
// deshalb hier zusaetzlich im 80ms-Tick weiterschieben.
|
||||
// Lets overly wide detail panel lines (e.g. "Model: ..." or
|
||||
// "Distance: ...") scroll, instead of being statically truncated
|
||||
// with "..." - render() only supplies new values every
|
||||
// ~300ms, which is not enough for smooth scrolling,
|
||||
// so it is additionally advanced here in the 80ms tick.
|
||||
tickDetailPanelMarquees(tft);
|
||||
tft.endWrite();
|
||||
return;
|
||||
@@ -988,13 +989,13 @@ void tick(TFT_eSPI& tft, int16_t top, uint32_t deltaMs) {
|
||||
|
||||
tft.startWrite();
|
||||
|
||||
// Twinkeln ausserhalb des Radarkreises - laeuft im selben 80ms-Takt wie
|
||||
// die Sweep-Linie, damit es fluessig wirkt. render() (bei jedem Aircraft-
|
||||
// Update, alle ~300ms) loescht den kompletten Inhaltsbereich per fillRect
|
||||
// und zeichnet ihn neu, OHNE die Sterne erneut zu setzen - das ist
|
||||
// bewusst so (wie beim Detail-Panel-Sternenfeld auch): die naechste
|
||||
// tick()-Runde (spaetestens 80ms spaeter) laesst sie einfach wieder
|
||||
// aufblitzen, das ist zu kurz, um als Ruckeln wahrgenommen zu werden.
|
||||
// Twinkling outside the radar circle - runs at the same 80ms rate as
|
||||
// the sweep line so it looks smooth. render() (on every aircraft
|
||||
// update, every ~300ms) clears the entire content area with fillRect
|
||||
// and redraws it WITHOUT re-setting the stars - this is
|
||||
// intentional (just like the detail panel star field): the next
|
||||
// tick() round (at most 80ms later) will simply make them
|
||||
// twinkle again, which is too brief to be perceived as stuttering.
|
||||
updateBgStars(tft, L, top);
|
||||
|
||||
if (prevSweepAngleDeg >= 0.0f) {
|
||||
@@ -1015,16 +1016,16 @@ void tick(TFT_eSPI& tft, int16_t top, uint32_t deltaMs) {
|
||||
|
||||
bool inAlertRange = hp.distanceKm <= Config::LED_ALERT_RADIUS_KM;
|
||||
if (inAlertRange && !ledBlinkOn) {
|
||||
// Blink-Aus-Phase: frueher wurde hier ein pauschales 40x30px
|
||||
// schwarzes Rechteck gezeichnet, um den Marker zu "verstecken" -
|
||||
// das hat dabei aber auch die Radar-Ringe/Kompasslinien darunter
|
||||
// ueberdeckt (bei jeder Hoehenfarbe gleichermassen, da diese
|
||||
// Blink-Logik rein distanzbasiert ist). Stattdessen werden hier
|
||||
// jetzt exakt dieselben Formen, die beim normalen Zeichnen weiter
|
||||
// unten entstehen (Marker, Notfall-/Beobachtungs-Ring, Label),
|
||||
// einfach in Schwarz nachgezeichnet - das macht sie pixelgenau
|
||||
// wieder unsichtbar, ohne irgendetwas ausserhalb dieser Formen zu
|
||||
// beruehren.
|
||||
// Blink-off phase: previously a blanket 40x30px
|
||||
// black rectangle was drawn to "hide" the marker -
|
||||
// but that also covered the radar rings/compass lines
|
||||
// underneath (equally for all altitude colors, since this
|
||||
// blink logic is purely distance-based). Instead, exactly
|
||||
// the same shapes that are drawn during normal rendering
|
||||
// below (marker, emergency/watchlist ring, label) are
|
||||
// now drawn in black - this makes them pixel-perfectly
|
||||
// invisible again without touching anything outside
|
||||
// those shapes.
|
||||
if (hp.isGroundVehicle) {
|
||||
drawGroundVehicleMarker(tft, hp.x, hp.y, TFT_BLACK);
|
||||
} else if (hp.isRotorcraft) {
|
||||
@@ -1068,9 +1069,9 @@ void tick(TFT_eSPI& tft, int16_t top, uint32_t deltaMs) {
|
||||
tft.setTextDatum(TL_DATUM);
|
||||
}
|
||||
|
||||
// Info-Zeile unten (Tap-Hinweis bzw. "Leerer Himmel"-Timer) als
|
||||
// Laufende Aktualisierung der letzten Sichtung fuer den "Leerer
|
||||
// Himmel"-Zaehler.
|
||||
// Info line at bottom (tap hint or "Empty Sky" timer) as
|
||||
// continuous update of the last sighting for the "Empty Sky"
|
||||
// counter.
|
||||
uint8_t visibleCountNow = 0;
|
||||
for (uint8_t i = 0; i < MAX_HIT_POINTS; i++) {
|
||||
if (hitPoints[i].valid) visibleCountNow++;
|
||||
@@ -1539,10 +1540,10 @@ void runHelp(TFT_eSPI& tft, int16_t top) {
|
||||
void selectAircraft(const char* hex, const char* callsign) {
|
||||
strncpy(selectedHex, hex, sizeof(selectedHex) - 1);
|
||||
AircraftDetails::request(hex, callsign);
|
||||
// Erzwingt einen kompletten Neuaufbau des Detail-Panels beim naechsten
|
||||
// render() - wichtig, falls der Bildschirm zwischenzeitlich von einem
|
||||
// anderen Screen (z.B. der Flugzeugliste) ueberschrieben wurde, sonst
|
||||
// wuerden unveraenderte Zeilen faelschlich als "schon da" uebersprungen.
|
||||
// Forces a complete rebuild of the detail panel on the next
|
||||
// render() - important if the screen was overwritten by another
|
||||
// screen (e.g. the aircraft list), otherwise unchanged lines would
|
||||
// be incorrectly skipped as "already there".
|
||||
lastPanel.valid = false;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user