From 760b1a9fc1efa1091bd267bc49ca8bc82299a05a Mon Sep 17 00:00:00 2001 From: Rasmus Date: Sat, 15 Aug 2026 21:29:11 +0200 Subject: [PATCH] search --- src/radar_screen.cpp | 167 +++++++++++++++++++++++++++++++++++++++++++ src/radar_screen.h | 5 ++ 2 files changed, 172 insertions(+) diff --git a/src/radar_screen.cpp b/src/radar_screen.cpp index e6d43fa..5b95c71 100644 --- a/src/radar_screen.cpp +++ b/src/radar_screen.cpp @@ -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); diff --git a/src/radar_screen.h b/src/radar_screen.h index bc348eb..c5a52e2 100644 --- a/src/radar_screen.h +++ b/src/radar_screen.h @@ -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