This commit is contained in:
2026-08-15 21:29:11 +02:00
parent a5e9dc7df1
commit 760b1a9fc1
2 changed files with 172 additions and 0 deletions

View File

@@ -34,6 +34,7 @@ struct Layout {
int16_t cx, cy, radius;
Rect rangeBtn;
Rect listBtn;
Rect searchBtn;
int16_t infoTop;
};
@@ -60,6 +61,7 @@ Layout computeLayout(int16_t top) {
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};
L.searchBtn = {(int16_t)(Config::SCREEN_WIDTH - 150), (int16_t)(L.infoTop + 4), 36, 22};
return L;
}
@@ -1047,6 +1049,7 @@ constexpr int16_t INFO_TEXT_X = 8;
}
drawButton(tft, L.rangeBtn, rangeLabel);
drawButton(tft, L.listBtn, "L");
drawButton(tft, L.searchBtn, "S");
drawLegend(tft, infoTop + 44);
}
@@ -1230,6 +1233,11 @@ bool handleTap(TFT_eSPI& tft, int16_t x, int16_t y, int16_t top) {
return true;
}
if (L.searchBtn.contains(x, y)) {
runSearch(tft, top);
return true;
}
if (L.listBtn.contains(x, y)) {
bool hadSelection = runTable(tft, top);
return true;
@@ -1418,6 +1426,165 @@ bool runTable(TFT_eSPI& tft, int16_t top) {
return selected;
}
void runSearch(TFT_eSPI& tft, int16_t top) {
MenuStars::reset();
constexpr const char* ROW1 = "QWERTYUIOP";
constexpr const char* ROW2 = "ASDFGHJKL";
constexpr const char* ROW3 = "ZXCVBNM";
constexpr const char* ROW4 = "0123456789";
char buf[9] = {0};
uint8_t len = 0;
constexpr uint8_t MAX_LEN = 8;
constexpr int16_t KEY_H = 32;
constexpr int16_t KEY_GAP = 3;
constexpr int16_t ROW0_Y = 64;
auto layoutRow = [&](const char* row, int16_t y, Rect* outRects, uint8_t n) {
int16_t usableW = Config::SCREEN_WIDTH - 8;
int16_t keyW = (usableW - (n - 1) * KEY_GAP) / n;
int16_t x = 4;
for (uint8_t i = 0; i < n; i++) {
outRects[i] = {x, y, keyW, KEY_H};
x += keyW + KEY_GAP;
}
};
Rect row1Rects[10], row2Rects[9], row3Rects[7], row4Rects[10];
layoutRow(ROW1, ROW0_Y, row1Rects, 10);
layoutRow(ROW2, ROW0_Y + KEY_H + KEY_GAP, row2Rects, 9);
layoutRow(ROW3, ROW0_Y + 2 * (KEY_H + KEY_GAP), row3Rects, 7);
layoutRow(ROW4, ROW0_Y + 3 * (KEY_H + KEY_GAP), row4Rects, 10);
int16_t actionY = ROW0_Y + 4 * (KEY_H + KEY_GAP);
Rect backspaceBtn = {4, actionY, 60, KEY_H};
Rect cancelBtn = {(int16_t)(Config::SCREEN_WIDTH / 2 - 48), actionY, 96, KEY_H};
Rect searchBtn = {(int16_t)(Config::SCREEN_WIDTH - 4 - 60), actionY, 60, KEY_H};
uint32_t notFoundUntil = 0;
bool showingNotFound = false;
auto redraw = [&]() {
tft.fillScreen(TFT_BLACK);
tft.setTextColor(TFT_GREEN, TFT_BLACK);
tft.setCursor(10, 14);
tft.print("Search");
tft.fillRect(8, 30, Config::SCREEN_WIDTH - 16, 28, TFT_BLACK);
tft.drawRect(8, 30, Config::SCREEN_WIDTH - 16, 28, TFT_GREEN);
tft.setTextSize(2);
tft.setTextColor(TFT_GREEN, TFT_BLACK);
tft.setCursor(14, 52);
tft.print(buf);
tft.setTextSize(1);
if (showingNotFound) {
tft.setTextColor(TFT_RED, TFT_BLACK);
tft.setTextDatum(MC_DATUM);
tft.drawString("Not found", Config::SCREEN_WIDTH / 2, actionY + KEY_H + 14);
tft.setTextDatum(TL_DATUM);
}
for (uint8_t i = 0; i < 10; i++) drawButton(tft, row1Rects[i], String(ROW1[i]));
for (uint8_t i = 0; i < 9; i++) drawButton(tft, row2Rects[i], String(ROW2[i]));
for (uint8_t i = 0; i < 7; i++) drawButton(tft, row3Rects[i], String(ROW3[i]));
for (uint8_t i = 0; i < 10; i++) drawButton(tft, row4Rects[i], String(ROW4[i]));
drawButton(tft, backspaceBtn, "<-");
drawButton(tft, cancelBtn, I18n::t(StringId::CANCEL));
drawButton(tft, searchBtn, I18n::t(StringId::OK));
};
redraw();
while (true) {
if (showingNotFound && millis() >= notFoundUntil) {
showingNotFound = false;
redraw();
}
TouchInput::Point tap;
if (!TouchInput::wasTapped(tap)) {
MenuStars::update(tft);
delay(20);
continue;
}
if (showingNotFound) {
showingNotFound = false;
redraw();
continue;
}
bool handled = false;
for (uint8_t i = 0; i < 10 && !handled; i++) {
if (row1Rects[i].contains(tap.x, tap.y) && len < MAX_LEN) { buf[len++] = ROW1[i]; handled = true; }
}
for (uint8_t i = 0; i < 9 && !handled; i++) {
if (row2Rects[i].contains(tap.x, tap.y) && len < MAX_LEN) { buf[len++] = ROW2[i]; handled = true; }
}
for (uint8_t i = 0; i < 7 && !handled; i++) {
if (row3Rects[i].contains(tap.x, tap.y) && len < MAX_LEN) { buf[len++] = ROW3[i]; handled = true; }
}
for (uint8_t i = 0; i < 10 && !handled; i++) {
if (row4Rects[i].contains(tap.x, tap.y) && len < MAX_LEN) { buf[len++] = ROW4[i]; handled = true; }
}
if (!handled && backspaceBtn.contains(tap.x, tap.y)) {
if (len > 0) { len--; buf[len] = 0; }
handled = true;
}
if (!handled && cancelBtn.contains(tap.x, tap.y)) {
return;
}
if (!handled && searchBtn.contains(tap.x, tap.y) && len > 0) {
auto prefixMatch = [&](const char* str) -> bool {
for (uint8_t j = 0; j < len; j++) {
if (str[j] == 0) return false;
char a = str[j], b = buf[j];
if (a >= 'a' && a <= 'z') a -= 32;
if (b >= 'a' && b <= 'z') b -= 32;
if (a != b) return false;
}
return true;
};
int bestIdx = -1;
float bestDist = 1e9f;
AircraftTable::lock();
Aircraft* table = AircraftTable::raw();
for (uint8_t i = 0; i < AircraftTable::capacity(); i++) {
if (!table[i].valid) continue;
if (!prefixMatch(table[i].callsign) && !prefixMatch(table[i].hex)) continue;
if (table[i].distanceKm < bestDist) {
bestDist = table[i].distanceKm;
bestIdx = i;
}
}
char mHex[7] = {0}, mCall[9] = {0};
if (bestIdx >= 0) {
strncpy(mHex, table[bestIdx].hex, sizeof(mHex) - 1);
strncpy(mCall, table[bestIdx].callsign, sizeof(mCall) - 1);
}
AircraftTable::unlock();
if (bestIdx >= 0) {
selectAircraft(mHex, mCall);
return;
} else {
showingNotFound = true;
notFoundUntil = millis() + 1500;
redraw();
continue;
}
}
if (handled) redraw();
}
}
void selectAircraft(const char* hex, const char* callsign) {
strncpy(selectedHex, hex, sizeof(selectedHex) - 1);
AircraftDetails::request(hex, callsign);

View File

@@ -13,6 +13,11 @@ void render(TFT_eSPI& tft, int16_t top);
// Returns true if user selected an aircraft (opens detail panel).
bool runTable(TFT_eSPI& tft, int16_t top);
// Opens an alphanumeric keypad to search for an aircraft by callsign
// or hex code. If found, opens the detail panel. If not, shows a brief
// "not found" message and returns to the keypad.
void runSearch(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