list view

This commit is contained in:
2026-08-15 20:58:16 +02:00
parent 811d4f0ac9
commit a5e9dc7df1
3 changed files with 174 additions and 10 deletions

View File

@@ -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;
}
}

View File

@@ -14,8 +14,11 @@
#include "airline_filter.h"
#include "aircraft_watchlist.h"
#include "i18n.h"
#include "menu_stars.h"
#include "touch_input.h"
#include <math.h>
#include <time.h>
#include <algorithm>
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) {

View File

@@ -3,11 +3,16 @@
#include <TFT_eSPI.h>
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