From a5e9dc7df1b2568365ea01ef8174a8fc345d6b40 Mon Sep 17 00:00:00 2001 From: Rasmus Date: Sat, 15 Aug 2026 20:58:16 +0200 Subject: [PATCH] list view --- src/main.cpp | 2 +- src/radar_screen.cpp | 173 +++++++++++++++++++++++++++++++++++++++++-- src/radar_screen.h | 9 ++- 3 files changed, 174 insertions(+), 10 deletions(-) diff --git a/src/main.cpp b/src/main.cpp index 5ff9921..87396ec 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -696,7 +696,7 @@ void loop() { updateStatusLine(); forceRedraw = true; } else if (tap.y >= CONTENT_TOP) { - if (RadarScreen::handleTap(tap.x, tap.y, CONTENT_TOP)) { + if (RadarScreen::handleTap(tft, tap.x, tap.y, CONTENT_TOP)) { forceRedraw = true; } } diff --git a/src/radar_screen.cpp b/src/radar_screen.cpp index 7813abb..e6d43fa 100644 --- a/src/radar_screen.cpp +++ b/src/radar_screen.cpp @@ -14,8 +14,11 @@ #include "airline_filter.h" #include "aircraft_watchlist.h" #include "i18n.h" +#include "menu_stars.h" +#include "touch_input.h" #include #include +#include namespace RadarScreen { @@ -27,9 +30,10 @@ namespace { } }; - struct Layout { +struct Layout { int16_t cx, cy, radius; Rect rangeBtn; + Rect listBtn; int16_t infoTop; }; @@ -45,7 +49,7 @@ namespace { return SettingsStore::hideGroundVehicles() ? INFO_BAR_H : (int16_t)(INFO_BAR_H + INFO_BAR_GROUND_ROW_H); } - Layout computeLayout(int16_t top) { +Layout computeLayout(int16_t top) { Layout L; L.infoTop = Config::SCREEN_HEIGHT - infoBarHeight(); constexpr int16_t TOP_LABEL_MARGIN = 10; @@ -55,6 +59,7 @@ namespace { L.cx = Config::SCREEN_WIDTH / 2; L.cy = top + L.radius + 6 + TOP_LABEL_MARGIN; L.rangeBtn = {(int16_t)(Config::SCREEN_WIDTH - 70), (int16_t)(L.infoTop + 4), 62, 22}; + L.listBtn = {(int16_t)(Config::SCREEN_WIDTH - 110), (int16_t)(L.infoTop + 4), 36, 22}; return L; } @@ -1025,10 +1030,10 @@ void render(TFT_eSPI& tft, int16_t top) { // Heartbeat-Blinken) - ohne dieses sofortige Nachzeichnen blieb die // Zeile bis zu 80ms lang leer, sichtbar als kurzes Blinken im // Heartbeat-Takt. - constexpr int16_t INFO_TEXT_X = 8; +constexpr int16_t INFO_TEXT_X = 8; constexpr int16_t INFO_TEXT_GAP = 6; int16_t infoTextY = infoTop + 20; - int16_t infoTextW = L.rangeBtn.x - INFO_TEXT_X - INFO_TEXT_GAP; + int16_t infoTextW = L.listBtn.x - INFO_TEXT_X - INFO_TEXT_GAP; drawInfoMarquee(tft, INFO_TEXT_X, infoTextY, infoTextW); // Respektiert jetzt die Einheiten-Einstellung (Menue > Einheiten) - @@ -1041,6 +1046,7 @@ void render(TFT_eSPI& tft, int16_t top) { snprintf(rangeLabel, sizeof(rangeLabel), "%.0fnm", Units::kmToNm(rangeKm)); } drawButton(tft, L.rangeBtn, rangeLabel); + drawButton(tft, L.listBtn, "L"); drawLegend(tft, infoTop + 44); } @@ -1184,7 +1190,7 @@ void tick(TFT_eSPI& tft, int16_t top, uint32_t deltaMs) { constexpr int16_t INFO_TEXT_X = 8; constexpr int16_t INFO_TEXT_GAP = 6; int16_t infoTextY = L.infoTop + 20; - int16_t infoTextW = L.rangeBtn.x - INFO_TEXT_X - INFO_TEXT_GAP; + int16_t infoTextW = L.listBtn.x - INFO_TEXT_X - INFO_TEXT_GAP; if (kind != infoMarqueeKind) { // Die ART der Nachricht hat sich geaendert (z.B. letztes Flugzeug @@ -1205,7 +1211,7 @@ void tick(TFT_eSPI& tft, int16_t top, uint32_t deltaMs) { tft.endWrite(); } -bool handleTap(int16_t x, int16_t y, int16_t top) { +bool handleTap(TFT_eSPI& tft, int16_t x, int16_t y, int16_t top) { Layout L = computeLayout(top); if (selectedHex[0]) { @@ -1224,6 +1230,11 @@ bool handleTap(int16_t x, int16_t y, int16_t top) { return true; } + if (L.listBtn.contains(x, y)) { + bool hadSelection = runTable(tft, top); + return true; + } + if (L.rangeBtn.contains(x, y)) { uint8_t idx = (SettingsStore::rangeIndex() + 1) % Config::RANGE_STEP_COUNT; SettingsStore::setRangeIndex(idx); @@ -1256,7 +1267,155 @@ bool handleTap(int16_t x, int16_t y, int16_t top) { lastEmptyTapMs = 0; } - return true; +return true; +} + +bool runTable(TFT_eSPI& tft, int16_t top) { + constexpr int16_t ROW_H = 22; + constexpr int16_t ROW_GAP = 3; + constexpr int16_t PADDING = 6; + constexpr int16_t TABLE_LEFT = PADDING; + constexpr int16_t TABLE_RIGHT = Config::SCREEN_WIDTH - PADDING; + constexpr int16_t TABLE_W = TABLE_RIGHT - TABLE_LEFT; + int16_t TABLE_TOP = top + 4; + constexpr int16_t NAV_H = 24; + constexpr int16_t BTN_H = 30; + constexpr int16_t BTN_Y = Config::SCREEN_HEIGHT - BTN_H - 4; + constexpr int16_t NAV_Y = BTN_Y - ROW_H - ROW_GAP - 2; + + int16_t tableBottom = NAV_Y - 4; + uint8_t rowsVisible = (tableBottom - TABLE_TOP) / (ROW_H + ROW_GAP); + if (rowsVisible < 1) rowsVisible = 1; + + uint8_t scrollTop = 0; + bool done = false; + bool selected = false; + MenuStars::reset(); + + while (!done && !selected) { + static Aircraft snapshot[Config::MAX_TRACKED_AIRCRAFT]; + uint8_t count = 0; + float rangeKm = Config::RANGE_STEPS_KM[SettingsStore::rangeIndex()]; + + AircraftTable::lock(); + Aircraft* table = AircraftTable::raw(); + for (uint8_t i = 0; i < AircraftTable::capacity(); i++) { + if (!table[i].valid) continue; + if (table[i].distanceKm > rangeKm * 1.05f) continue; + if (SettingsStore::hideGroundVehicles() && table[i].category[0] == 'C') continue; + if (AirlineFilter::isHidden(table[i].callsign)) continue; + snapshot[count++] = table[i]; + } + AircraftTable::unlock(); + + std::sort(snapshot, snapshot + count, [](const Aircraft& a, const Aircraft& b) { + return a.distanceKm < b.distanceKm; + }); + + if (scrollTop > 0 && scrollTop + rowsVisible > count) { + scrollTop = (count > rowsVisible) ? (uint8_t)(count - rowsVisible) : 0; + } + + tft.fillRect(TABLE_LEFT, TABLE_TOP, TABLE_W, tableBottom - TABLE_TOP, TFT_BLACK); + + bool metric = LocationManager::useMetricUnits(); + int16_t y = TABLE_TOP; + uint8_t drawn = 0; + + for (uint8_t i = scrollTop; i < count && drawn < rowsVisible; i++) { + Aircraft& a = snapshot[i]; + Rect r = {TABLE_LEFT, y, TABLE_W, ROW_H}; + + bool emergency = isEmergencySquawk(a.squawk); + bool watched = AircraftWatchlist::isWatched(a.callsign); + uint16_t borderColor = emergency ? TFT_RED : (watched ? TFT_CYAN : TFT_GREEN); + tft.drawRoundRect(r.x, r.y, r.w, r.h, 3, borderColor); + + int16_t midY = r.y + r.h / 2; + + const char* label = a.callsign[0] ? a.callsign : a.hex; + tft.setTextColor(TFT_GREEN, TFT_BLACK); + tft.setTextDatum(ML_DATUM); + tft.drawString(label, r.x + 4, midY); + + char buf[16]; + if (metric) { + snprintf(buf, sizeof(buf), "%.0fm", Units::feetToMeters((float)a.altBaroFt)); + } else { + snprintf(buf, sizeof(buf), "%.0fft", (float)a.altBaroFt); + } + tft.setTextColor(colorForAltitude(tft, a.altBaroFt), TFT_BLACK); + tft.setTextDatum(MC_DATUM); + tft.drawString(buf, r.x + r.w / 2, midY); + + if (metric) { + snprintf(buf, sizeof(buf), "%.1fkm", a.distanceKm); + } else { + snprintf(buf, sizeof(buf), "%.1fnm", Units::kmToNm(a.distanceKm)); + } + tft.setTextColor(TFT_GREEN, TFT_BLACK); + tft.setTextDatum(MR_DATUM); + tft.drawString(buf, r.x + r.w - 4, midY); + + tft.setTextDatum(TL_DATUM); + + y += ROW_H + ROW_GAP; + drawn++; + } + + if (count == 0) { + tft.setTextColor(TFT_DARKGREY, TFT_BLACK); + tft.setTextDatum(MC_DATUM); + tft.drawString(I18n::t(StringId::AIRCRAFT_LIST_EMPTY), + Config::SCREEN_WIDTH / 2, (int16_t)(TABLE_TOP + ROW_H + 10)); + tft.setTextDatum(TL_DATUM); + } + + bool canUp = scrollTop > 0; + bool canDown = (uint16_t)(scrollTop + rowsVisible) < count; + + tft.fillRect(0, NAV_Y, Config::SCREEN_WIDTH, Config::SCREEN_HEIGHT - NAV_Y, TFT_BLACK); + + Rect upBtn = {TABLE_LEFT, NAV_Y, 44, NAV_H}; + Rect downBtn = {(int16_t)(TABLE_LEFT + 48), NAV_Y, 44, NAV_H}; + Rect radarBtn = {(int16_t)(TABLE_LEFT + 100), NAV_Y, (int16_t)(TABLE_W - 100), NAV_H}; + + drawButton(tft, upBtn, "^"); + drawButton(tft, downBtn, "v"); + drawButton(tft, radarBtn, I18n::t(StringId::BACK)); + + TouchInput::Point tap; + while (true) { + if (TouchInput::wasTapped(tap)) break; + MenuStars::update(tft); + delay(20); + } + + if (canUp && upBtn.contains(tap.x, tap.y)) { + scrollTop--; + } else if (canDown && downBtn.contains(tap.x, tap.y)) { + scrollTop++; + } else if (radarBtn.contains(tap.x, tap.y)) { + done = true; + } else { + y = TABLE_TOP; + for (uint8_t i = scrollTop; i < count; i++) { + uint8_t idx = (uint8_t)(i - scrollTop); + if (idx >= rowsVisible) break; + Rect r = {TABLE_LEFT, y, TABLE_W, ROW_H}; + if (r.contains(tap.x, tap.y)) { + selectAircraft(snapshot[i].hex, snapshot[i].callsign); + selected = true; + break; + } + y += ROW_H + ROW_GAP; + } + } + } + + // Wait briefly to let the tap release propagate before render() runs + delay(50); + return selected; } void selectAircraft(const char* hex, const char* callsign) { diff --git a/src/radar_screen.h b/src/radar_screen.h index 15ca6d2..bc348eb 100644 --- a/src/radar_screen.h +++ b/src/radar_screen.h @@ -3,11 +3,16 @@ #include namespace RadarScreen { - void render(TFT_eSPI& tft, int16_t top); +void render(TFT_eSPI& tft, int16_t top); void tick(TFT_eSPI& tft, int16_t top, uint32_t deltaMs); - bool handleTap(int16_t x, int16_t y, int16_t top); + bool handleTap(TFT_eSPI& tft, int16_t x, int16_t y, int16_t top); void updateProximityAlert(uint32_t nowMs); + // Switchable table view: shows a sortable list of aircraft directly + // on the radar screen (toggled via a "L" button in the info bar). + // Returns true if user selected an aircraft (opens detail panel). + bool runTable(TFT_eSPI& tft, int16_t top); + // Waehlt ein Flugzeug programmgesteuert aus (z.B. von der Flugzeugliste // aus, nicht per Antippen auf dem Radar) - damit beim naechsten render() // sofort das Detail-Panel fuer dieses Flugzeug erscheint, so als haette