v2.7.4: Named location presets and tappable nearest-airport ticker
Location presets can now optionally be given a name (e.g. "Home"), shown in the overview instead of the raw coordinates. The nearest-airport ticker on the Location Presets screen is now tappable (shown with a thin green border) and adds that airport directly as a new preset with its name and coordinates, using the existing local airport database on the SD card - no internet lookup needed. AirportLookup::findNearest() now also returns the found airport's own lat/lon for this purpose. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -74,7 +74,9 @@ live air traffic *there* instead, regardless of where your device physically is.
|
||||
A "?" info button right on the Location Presets screen explains all of this
|
||||
again directly on the device.
|
||||
|
||||
The screen also shows the **nearest known airport** (from a built-in list of ~34 major airports worldwide) to whichever location is currently active - handy to see exactly where a foreign preset points to, or why a location has a lot of air traffic. If the airport name doesn't fit on one line, it scrolls horizontally like a marquee.
|
||||
**Naming presets:** when adding a preset, you can optionally give it a name (e.g. "Home"), which is then shown in the overview instead of the raw coordinates.
|
||||
|
||||
The screen also shows the **nearest known airport** (from a built-in list of ~34 major airports worldwide) to whichever location is currently active - handy to see exactly where a foreign preset points to, or why a location has a lot of air traffic. If the airport name doesn't fit on one line, it scrolls horizontally like a marquee. This ticker is tappable (shown with a thin green border) - tapping it adds that airport directly as a new preset with its name and coordinates, using the same local airport database on the SD card, no internet lookup needed.
|
||||
|
||||
### 📶 WiFi Manager (up to 3 saved networks)
|
||||
Under **Menu → WiFi/Network** you can save up to 3 WiFi networks at once (not
|
||||
|
||||
@@ -39,6 +39,8 @@ Nearest findNearest(double lat, double lon) {
|
||||
bestDistanceKm = distanceKm;
|
||||
result.found = true;
|
||||
result.distanceKm = distanceKm;
|
||||
result.lat = aptLat;
|
||||
result.lon = aptLon;
|
||||
strncpy(result.icao, icao.c_str(), sizeof(result.icao) - 1);
|
||||
strncpy(result.name, name.c_str(), sizeof(result.name) - 1);
|
||||
}
|
||||
|
||||
@@ -7,6 +7,11 @@ namespace AirportLookup {
|
||||
char icao[5] = {0};
|
||||
char name[32] = {0};
|
||||
float distanceKm = 0;
|
||||
// Koordinaten des gefundenen Flughafens selbst (nicht der
|
||||
// abgefragten Position) - z.B. um ihn direkt als Standort-Preset
|
||||
// uebernehmen zu koennen (siehe location_presets_screen.cpp).
|
||||
double lat = 0;
|
||||
double lon = 0;
|
||||
};
|
||||
Nearest findNearest(double lat, double lon);
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ namespace Config {
|
||||
// CLAUDE.md-Workflow "Standard-Workflow: Push & Release") - erscheint
|
||||
// im Info-Screen (Menue > System > Info) und muss zum jeweiligen
|
||||
// Git-Tag passen.
|
||||
constexpr const char* APP_VERSION = "2.7.3";
|
||||
constexpr const char* APP_VERSION = "2.7.4";
|
||||
|
||||
// Display-Helligkeit (Menue > System > Helligkeit), in Prozent.
|
||||
// MIN bewusst nicht 0 - ein komplett dunkles Display koennte sonst wie
|
||||
|
||||
@@ -196,6 +196,14 @@ enum class StringId : uint8_t {
|
||||
MENU_LOGBOOK_WARNING_TITLE,
|
||||
MENU_LOGBOOK_WARNING_BODY,
|
||||
|
||||
// Preset-Name beim Anlegen (optional), "Presets voll"-Hinweis beim
|
||||
// Antippen der Naechster-Flughafen-Laufschrift, sowie ein zusaetzlicher
|
||||
// Info-Absatz dazu (Menue > Flugoptionen > Standort-Presets > "?").
|
||||
LOCATION_NAME_PROMPT,
|
||||
LOCATION_NAME_SKIP,
|
||||
LOCATION_PRESETS_FULL,
|
||||
LOCATION_INFO_PARA5,
|
||||
|
||||
COUNT
|
||||
};
|
||||
|
||||
|
||||
@@ -194,6 +194,10 @@ static const char* const I18N_DE[] = {
|
||||
|
||||
"Achtung!!!",
|
||||
"Das Flugbuch speichert dauerhaft alle sichtbaren Flugzeuge auf der SD-Karte. Damit sie sich nicht unbemerkt füllt und die Logbuch-Menüs nicht langsam werden, schaltet es sich nach 24 Stunden automatisch wieder aus.",
|
||||
"Name (optional):",
|
||||
"Ohne Namen",
|
||||
"Presets voll - zuerst einen entfernen",
|
||||
"Beim Anlegen eines Presets kannst du ihm einen Namen geben (z.B. \"Zuhause\"), der dann in der Übersicht angezeigt wird. Tippe auf die Laufschrift mit dem nächsten Flughafen, um ihn direkt als neuen Preset mit seinem Namen zu übernehmen.",
|
||||
};
|
||||
|
||||
static_assert(sizeof(I18N_DE) / sizeof(I18N_DE[0]) == (size_t)StringId::COUNT,
|
||||
|
||||
@@ -195,6 +195,10 @@ static const char* const I18N_EN[] = {
|
||||
|
||||
"Warning!!!",
|
||||
"The logbook permanently saves every visible aircraft to the SD card. To stop it filling up unnoticed and slowing down the logbook menus, it automatically switches off again after 24 hours.",
|
||||
"Name (optional):",
|
||||
"Skip name",
|
||||
"Presets full - remove one first",
|
||||
"When adding a preset you can give it a name (e.g. \"Home\"), which is then shown in the overview. Tap the nearest-airport ticker to add it directly as a new preset with its name.",
|
||||
};
|
||||
|
||||
static_assert(sizeof(I18N_EN) / sizeof(I18N_EN[0]) == (size_t)StringId::COUNT,
|
||||
|
||||
@@ -194,6 +194,10 @@ static const char* const I18N_ES[] = {
|
||||
|
||||
"¡Atención!!!",
|
||||
"El registro de vuelos guarda permanentemente todos los aviones visibles en la tarjeta SD. Para evitar que se llene sin que lo note y que los menús se vuelvan lentos, se desactiva automáticamente después de 24 horas.",
|
||||
"Nombre (opcional):",
|
||||
"Sin nombre",
|
||||
"Ubicaciones llenas - elimine una primero",
|
||||
"Al añadir una ubicación puede darle un nombre (ej. \"Casa\"), que luego se muestra en la lista. Toque el texto en movimiento del aeropuerto más cercano para añadirlo directamente como nueva ubicación con su nombre.",
|
||||
};
|
||||
|
||||
static_assert(sizeof(I18N_ES) / sizeof(I18N_ES[0]) == (size_t)StringId::COUNT,
|
||||
|
||||
@@ -194,6 +194,10 @@ static const char* const I18N_FR[] = {
|
||||
|
||||
"Attention !!!",
|
||||
"Le journal de vol enregistre en continu tous les avions visibles sur la carte SD. Pour éviter qu'elle se remplisse sans que vous le remarquiez, il se désactive automatiquement au bout de 24 heures.",
|
||||
"Nom (optionnel) :",
|
||||
"Sans nom",
|
||||
"Presets pleins - supprimez-en un d'abord",
|
||||
"Lors de l'ajout d'une position, vous pouvez lui donner un nom (ex. \"Maison\"), affiché ensuite dans la liste. Touchez le texte défilant de l'aéroport le plus proche pour l'ajouter directement comme nouvelle position avec son nom.",
|
||||
};
|
||||
|
||||
static_assert(sizeof(I18N_FR) / sizeof(I18N_FR[0]) == (size_t)StringId::COUNT,
|
||||
|
||||
@@ -194,6 +194,10 @@ static const char* const I18N_IT[] = {
|
||||
|
||||
"Attenzione!!!",
|
||||
"Il diario di volo salva permanentemente tutti gli aerei visibili sulla scheda SD. Per evitare che si riempia senza che te ne accorga e che i menu diventino lenti, si disattiva automaticamente dopo 24 ore.",
|
||||
"Nome (opzionale):",
|
||||
"Senza nome",
|
||||
"Posizioni piene - rimuovine una prima",
|
||||
"Quando aggiungi una posizione puoi darle un nome (es. \"Casa\"), mostrato poi nell'elenco. Tocca il testo scorrevole dell'aeroporto più vicino per aggiungerlo direttamente come nuova posizione con il suo nome.",
|
||||
};
|
||||
|
||||
static_assert(sizeof(I18N_IT) / sizeof(I18N_IT[0]) == (size_t)StringId::COUNT,
|
||||
|
||||
@@ -194,6 +194,10 @@ static const char* const I18N_TR[] = {
|
||||
|
||||
"Dikkat!!!",
|
||||
"Kayıt defteri, görüntülenen tüm uçakları SD karta sürekli kaydeder. Kartın fark edilmeden dolmaması ve menülerin yavaşlamaması için 24 saat sonra otomatik olarak kapanır.",
|
||||
"İsim (opsiyonel):",
|
||||
"İsimsiz devam et",
|
||||
"Ön ayarlar dolu - önce birini kaldırın",
|
||||
"Bir ön ayar eklerken ona bir isim verebilirsiniz (örn. \"Ev\"), bu isim daha sonra listede gösterilir. En yakın havalimanı kayan yazısına dokunarak onu doğrudan ismiyle yeni bir ön ayar olarak ekleyebilirsiniz.",
|
||||
};
|
||||
|
||||
static_assert(sizeof(I18N_TR) / sizeof(I18N_TR[0]) == (size_t)StringId::COUNT,
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
#include "sd_mutex.h"
|
||||
#include "sd_storage.h"
|
||||
#include <SD.h>
|
||||
#include <cstring>
|
||||
|
||||
namespace LocationPresets {
|
||||
|
||||
@@ -12,6 +13,11 @@ namespace {
|
||||
struct Preset {
|
||||
double lat = 0;
|
||||
double lon = 0;
|
||||
// Optionaler, vom Nutzer vergebener Name (z.B. "Zuhause") oder vom
|
||||
// naechstgelegenen Flughafen uebernommen - leer, wenn keiner gesetzt
|
||||
// wurde. 16 Zeichen + Nullterminierung reichen fuer eine gut lesbare
|
||||
// Zeile in der Preset-Liste.
|
||||
char name[17] = {0};
|
||||
};
|
||||
|
||||
Preset presets[MAX_PRESETS];
|
||||
@@ -26,7 +32,7 @@ namespace {
|
||||
if (!f) return;
|
||||
f.printf("active=%d\n", (int)activeIdx);
|
||||
for (uint8_t i = 0; i < presetCount; i++) {
|
||||
f.printf("%.6f,%.6f\n", presets[i].lat, presets[i].lon);
|
||||
f.printf("%.6f,%.6f,%s\n", presets[i].lat, presets[i].lon, presets[i].name);
|
||||
}
|
||||
f.close();
|
||||
}
|
||||
@@ -55,12 +61,23 @@ namespace {
|
||||
}
|
||||
}
|
||||
|
||||
int comma = line.indexOf(',');
|
||||
if (comma < 0) continue;
|
||||
int comma1 = line.indexOf(',');
|
||||
if (comma1 < 0) continue;
|
||||
if (presetCount >= MAX_PRESETS) break;
|
||||
|
||||
presets[presetCount].lat = line.substring(0, comma).toDouble();
|
||||
presets[presetCount].lon = line.substring(comma + 1).toDouble();
|
||||
// Der Name ist ein optionales drittes Feld - aeltere,
|
||||
// gespeicherte Presets (vor diesem Feature) haben nur
|
||||
// "lat,lon" ohne zweites Komma, dann bleibt der Name leer.
|
||||
int comma2 = line.indexOf(',', comma1 + 1);
|
||||
presets[presetCount].lat = line.substring(0, comma1).toDouble();
|
||||
if (comma2 < 0) {
|
||||
presets[presetCount].lon = line.substring(comma1 + 1).toDouble();
|
||||
presets[presetCount].name[0] = 0;
|
||||
} else {
|
||||
presets[presetCount].lon = line.substring(comma1 + 1, comma2).toDouble();
|
||||
String nm = line.substring(comma2 + 1);
|
||||
strncpy(presets[presetCount].name, nm.c_str(), sizeof(presets[presetCount].name) - 1);
|
||||
}
|
||||
presetCount++;
|
||||
}
|
||||
f.close();
|
||||
@@ -81,10 +98,16 @@ void getLatLon(uint8_t index, double& lat, double& lon) {
|
||||
lon = presets[index].lon;
|
||||
}
|
||||
|
||||
bool addPreset(double lat, double lon) {
|
||||
String getName(uint8_t index) {
|
||||
if (index >= presetCount) return String();
|
||||
return String(presets[index].name);
|
||||
}
|
||||
|
||||
bool addPreset(double lat, double lon, const String& name) {
|
||||
if (presetCount >= MAX_PRESETS) return false;
|
||||
presets[presetCount].lat = lat;
|
||||
presets[presetCount].lon = lon;
|
||||
strncpy(presets[presetCount].name, name.c_str(), sizeof(presets[presetCount].name) - 1);
|
||||
presetCount++;
|
||||
saveToSd();
|
||||
return true;
|
||||
@@ -114,4 +137,4 @@ void setActiveIndex(int8_t index) {
|
||||
saveToSd();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,10 +8,14 @@ namespace LocationPresets {
|
||||
|
||||
uint8_t count();
|
||||
void getLatLon(uint8_t index, double& lat, double& lon);
|
||||
// Vom Nutzer vergebener Name (z.B. "Zuhause") - leerer String, wenn
|
||||
// keiner gesetzt wurde (dann zeigt der Screen stattdessen die
|
||||
// Koordinaten an).
|
||||
String getName(uint8_t index);
|
||||
|
||||
bool addPreset(double lat, double lon);
|
||||
bool addPreset(double lat, double lon, const String& name = String());
|
||||
void removePreset(uint8_t index);
|
||||
|
||||
int8_t activeIndex();
|
||||
void setActiveIndex(int8_t index);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -114,6 +114,121 @@ namespace {
|
||||
return confirmed ? String(buf) : String();
|
||||
}
|
||||
|
||||
// Vier Tastaturreihen wie bei der Rufzeichen-Eingabe (siehe
|
||||
// aircraft_watchlist_screen.cpp::runCallsignKeypad), aber mit Leertaste
|
||||
// (Ortsnamen enthalten oft Leerzeichen, z.B. "Bei Oma") und OHNE
|
||||
// Eingabepflicht - der Name ist optional, "Ohne Namen" bestaetigt mit
|
||||
// leerem Puffer statt die Eingabe abzubrechen.
|
||||
String runPresetNameKeypad(TFT_eSPI& tft) {
|
||||
MenuStars::reset();
|
||||
constexpr const char* DIGITS = "1234567890";
|
||||
constexpr const char* ROW1 = "QWERTYUIOP";
|
||||
constexpr const char* ROW2 = "ASDFGHJKL";
|
||||
constexpr const char* ROW3 = "ZXCVBNM";
|
||||
|
||||
char buf[17] = {0};
|
||||
uint8_t len = 0;
|
||||
|
||||
constexpr int16_t KEY_H = 30;
|
||||
constexpr int16_t KEY_GAP = 3;
|
||||
constexpr int16_t ROW0_Y = 78;
|
||||
|
||||
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 digitRects[10], row1Rects[10], row2Rects[9], row3Rects[7];
|
||||
layoutRow(DIGITS, ROW0_Y, digitRects, 10);
|
||||
layoutRow(ROW1, ROW0_Y + (KEY_H + KEY_GAP), row1Rects, 10);
|
||||
layoutRow(ROW2, ROW0_Y + 2 * (KEY_H + KEY_GAP), row2Rects, 9);
|
||||
layoutRow(ROW3, ROW0_Y + 3 * (KEY_H + KEY_GAP), row3Rects, 7);
|
||||
|
||||
Rect spaceBtn = {4, (int16_t)(ROW0_Y + 4 * (KEY_H + KEY_GAP)), 150, KEY_H};
|
||||
Rect backspaceBtn = {158, (int16_t)(ROW0_Y + 4 * (KEY_H + KEY_GAP)), (int16_t)(Config::SCREEN_WIDTH - 8 - 154), KEY_H};
|
||||
Rect skipBtn = {4, (int16_t)(ROW0_Y + 5 * (KEY_H + KEY_GAP)), 110, KEY_H};
|
||||
Rect confirmBtn = {118, (int16_t)(ROW0_Y + 5 * (KEY_H + KEY_GAP)), (int16_t)(Config::SCREEN_WIDTH - 8 - 114), KEY_H};
|
||||
|
||||
bool done = false;
|
||||
bool confirmed = false;
|
||||
|
||||
auto redraw = [&]() {
|
||||
tft.fillScreen(TFT_BLACK);
|
||||
tft.setTextColor(TFT_GREEN, TFT_BLACK);
|
||||
tft.setCursor(10, 14);
|
||||
tft.println(I18n::t(StringId::LOCATION_NAME_PROMPT));
|
||||
|
||||
tft.fillRect(8, 40, Config::SCREEN_WIDTH - 16, 34, TFT_BLACK);
|
||||
tft.drawRect(8, 40, Config::SCREEN_WIDTH - 16, 34, TFT_GREEN);
|
||||
tft.setTextSize(2);
|
||||
tft.setTextColor(TFT_GREEN, TFT_BLACK);
|
||||
tft.setCursor(14, 66);
|
||||
tft.print(buf);
|
||||
tft.setTextSize(1);
|
||||
|
||||
for (uint8_t i = 0; i < 10; i++) drawButton(tft, digitRects[i], String(DIGITS[i]));
|
||||
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]));
|
||||
|
||||
drawButton(tft, spaceBtn, I18n::t(StringId::WIFI_SPACE));
|
||||
drawButton(tft, backspaceBtn, "<-");
|
||||
drawButton(tft, skipBtn, I18n::t(StringId::LOCATION_NAME_SKIP));
|
||||
drawButton(tft, confirmBtn, I18n::t(StringId::OK));
|
||||
};
|
||||
|
||||
redraw();
|
||||
|
||||
while (!done) {
|
||||
TouchInput::Point tap;
|
||||
if (!TouchInput::wasTapped(tap)) { MenuStars::update(tft); delay(20); continue; }
|
||||
|
||||
bool handled = false;
|
||||
for (uint8_t i = 0; i < 10 && !handled; i++) {
|
||||
if (digitRects[i].contains(tap.x, tap.y) && len < sizeof(buf) - 1) { buf[len++] = DIGITS[i]; buf[len] = 0; handled = true; }
|
||||
}
|
||||
for (uint8_t i = 0; i < 10 && !handled; i++) {
|
||||
if (row1Rects[i].contains(tap.x, tap.y) && len < sizeof(buf) - 1) { buf[len++] = ROW1[i]; buf[len] = 0; handled = true; }
|
||||
}
|
||||
for (uint8_t i = 0; i < 9 && !handled; i++) {
|
||||
if (row2Rects[i].contains(tap.x, tap.y) && len < sizeof(buf) - 1) { buf[len++] = ROW2[i]; buf[len] = 0; handled = true; }
|
||||
}
|
||||
for (uint8_t i = 0; i < 7 && !handled; i++) {
|
||||
if (row3Rects[i].contains(tap.x, tap.y) && len < sizeof(buf) - 1) { buf[len++] = ROW3[i]; buf[len] = 0; handled = true; }
|
||||
}
|
||||
if (!handled && spaceBtn.contains(tap.x, tap.y) && len < sizeof(buf) - 1) {
|
||||
buf[len++] = ' ';
|
||||
buf[len] = 0;
|
||||
handled = true;
|
||||
}
|
||||
if (!handled && backspaceBtn.contains(tap.x, tap.y)) {
|
||||
if (len > 0) { len--; buf[len] = 0; }
|
||||
handled = true;
|
||||
}
|
||||
if (!handled && skipBtn.contains(tap.x, tap.y)) {
|
||||
buf[0] = 0;
|
||||
len = 0;
|
||||
done = true;
|
||||
confirmed = true;
|
||||
handled = true;
|
||||
}
|
||||
if (!handled && confirmBtn.contains(tap.x, tap.y)) {
|
||||
done = true;
|
||||
confirmed = true;
|
||||
handled = true;
|
||||
}
|
||||
|
||||
if (handled) redraw();
|
||||
}
|
||||
|
||||
return confirmed ? String(buf) : String();
|
||||
}
|
||||
|
||||
bool addPresetFlow(TFT_eSPI& tft) {
|
||||
String latStr = runNumericKeypad(tft, I18n::t(StringId::LOCATION_LAT_PROMPT));
|
||||
if (latStr.length() == 0) return false;
|
||||
@@ -125,7 +240,19 @@ namespace {
|
||||
double lon = lonStr.toDouble();
|
||||
if (lat == 0.0 && lon == 0.0) return false;
|
||||
|
||||
return LocationPresets::addPreset(lat, lon);
|
||||
String name = runPresetNameKeypad(tft);
|
||||
return LocationPresets::addPreset(lat, lon, name);
|
||||
}
|
||||
|
||||
// Kurze Meldung unten am Bildschirmrand, z.B. wenn beim Antippen des
|
||||
// Naechster-Flughafen-Textes bereits alle 3 Preset-Slots belegt sind.
|
||||
void showBriefMessage(TFT_eSPI& tft, const String& msg, uint16_t color) {
|
||||
tft.fillRect(0, Config::SCREEN_HEIGHT - 18, Config::SCREEN_WIDTH, 18, TFT_BLACK);
|
||||
tft.setTextColor(color, TFT_BLACK);
|
||||
tft.setTextDatum(MC_DATUM);
|
||||
tft.drawString(msg, Config::SCREEN_WIDTH / 2, Config::SCREEN_HEIGHT - 9);
|
||||
tft.setTextDatum(TL_DATUM);
|
||||
delay(1200);
|
||||
}
|
||||
|
||||
int16_t layoutWrapped(TFT_eSPI& tft, int16_t x, int16_t startY, int16_t maxWidth,
|
||||
@@ -277,6 +404,8 @@ namespace {
|
||||
totalH = layoutWrapped(tft, 10, totalH, textMaxWidth, LINE_H, I18n::t(StringId::LOCATION_INFO_PARA3), 0, 0, 0, false);
|
||||
totalH += 8;
|
||||
totalH = layoutWrapped(tft, 10, totalH, textMaxWidth, LINE_H, I18n::t(StringId::LOCATION_INFO_PARA4), 0, 0, 0, false);
|
||||
totalH += 8;
|
||||
totalH = layoutWrapped(tft, 10, totalH, textMaxWidth, LINE_H, I18n::t(StringId::LOCATION_INFO_PARA5), 0, 0, 0, false);
|
||||
|
||||
int16_t maxScroll = totalH - VIEW_BOTTOM;
|
||||
if (maxScroll < 0) maxScroll = 0;
|
||||
@@ -304,7 +433,9 @@ namespace {
|
||||
y += 8;
|
||||
y = layoutWrapped(tft, 10, y, textMaxWidth, LINE_H, I18n::t(StringId::LOCATION_INFO_PARA3), scrollY, VIEW_TOP, VIEW_BOTTOM, true);
|
||||
y += 8;
|
||||
layoutWrapped(tft, 10, y, textMaxWidth, LINE_H, I18n::t(StringId::LOCATION_INFO_PARA4), scrollY, VIEW_TOP, VIEW_BOTTOM, true);
|
||||
y = layoutWrapped(tft, 10, y, textMaxWidth, LINE_H, I18n::t(StringId::LOCATION_INFO_PARA4), scrollY, VIEW_TOP, VIEW_BOTTOM, true);
|
||||
y += 8;
|
||||
layoutWrapped(tft, 10, y, textMaxWidth, LINE_H, I18n::t(StringId::LOCATION_INFO_PARA5), scrollY, VIEW_TOP, VIEW_BOTTOM, true);
|
||||
|
||||
drawButton(tft, backBtn, I18n::t(StringId::BACK));
|
||||
if (scrollable) {
|
||||
@@ -373,9 +504,18 @@ void run(TFT_eSPI& tft) {
|
||||
if (i < count) {
|
||||
double lat = 0, lon = 0;
|
||||
LocationPresets::getLatLon(i, lat, lon);
|
||||
char coords[24];
|
||||
snprintf(coords, sizeof(coords), "%d: %.2f, %.2f", i + 1, lat, lon);
|
||||
String label = (active == (int8_t)i ? "> " : "") + presetWord + " " + coords;
|
||||
String presetName = LocationPresets::getName(i);
|
||||
String label;
|
||||
if (presetName.length() > 0) {
|
||||
// Benannte Presets (manuell vergeben oder vom
|
||||
// Flughafen-Antippen uebernommen) zeigen den Namen statt
|
||||
// der Koordinaten - besser lesbar in der Uebersicht.
|
||||
label = (active == (int8_t)i ? "> " : "") + presetName;
|
||||
} else {
|
||||
char coords[24];
|
||||
snprintf(coords, sizeof(coords), "%d: %.2f, %.2f", i + 1, lat, lon);
|
||||
label = (active == (int8_t)i ? "> " : "") + presetWord + " " + coords;
|
||||
}
|
||||
label = truncateForWidth(tft, label, (int16_t)(rowRect.w - 10));
|
||||
drawButton(tft, rowRect, label, active == (int8_t)i);
|
||||
drawButton(tft, removeRect, "X", false, true);
|
||||
@@ -400,6 +540,13 @@ void run(TFT_eSPI& tft) {
|
||||
constexpr int16_t AIRPORT_LINE_X = 10;
|
||||
constexpr int16_t AIRPORT_LINE_W = Config::SCREEN_WIDTH - 20;
|
||||
int16_t airportLineY = (int16_t)(Config::SCREEN_HEIGHT - 60);
|
||||
// Deckt denselben Bereich ab, den drawMarquee() bei jedem Frame
|
||||
// loescht (siehe MARQUEE_CLEAR_TOP/-H) - dient sowohl als Tap-Zone
|
||||
// als auch als sichtbarer Rahmen, der anzeigt, dass man hier
|
||||
// antippen kann, um den Flughafen als neuen Preset zu uebernehmen.
|
||||
Rect airportRect = {(int16_t)(AIRPORT_LINE_X - 4), (int16_t)(airportLineY - 20),
|
||||
(int16_t)(AIRPORT_LINE_W + 8), 26};
|
||||
AirportLookup::Nearest nearest;
|
||||
{
|
||||
double activeLat = 0, activeLon = 0;
|
||||
if (active < 0) {
|
||||
@@ -407,13 +554,14 @@ void run(TFT_eSPI& tft) {
|
||||
} else {
|
||||
LocationPresets::getLatLon((uint8_t)active, activeLat, activeLon);
|
||||
}
|
||||
AirportLookup::Nearest nearest = AirportLookup::findNearest(activeLat, activeLon);
|
||||
nearest = AirportLookup::findNearest(activeLat, activeLon);
|
||||
if (nearest.found) {
|
||||
char buf[48];
|
||||
snprintf(buf, sizeof(buf), "%s %s (%.0f km)", nearest.icao, nearest.name, nearest.distanceKm);
|
||||
String line = String(I18n::t(StringId::LOCATION_NEAREST_AIRPORT_PREFIX)) + buf;
|
||||
setupMarquee(tft, line, AIRPORT_LINE_W);
|
||||
drawMarquee(tft, AIRPORT_LINE_X, airportLineY, AIRPORT_LINE_W, 20);
|
||||
tft.drawRoundRect(airportRect.x, airportRect.y, airportRect.w, airportRect.h, 4, TFT_DARKGREEN);
|
||||
} else {
|
||||
airportMarquee.text = "";
|
||||
}
|
||||
@@ -451,10 +599,18 @@ void run(TFT_eSPI& tft) {
|
||||
addPresetFlow(tft);
|
||||
handled = true;
|
||||
}
|
||||
if (!handled && nearest.found && airportRect.contains(tap.x, tap.y)) {
|
||||
if (canAdd) {
|
||||
LocationPresets::addPreset(nearest.lat, nearest.lon, nearest.name);
|
||||
} else {
|
||||
showBriefMessage(tft, I18n::t(StringId::LOCATION_PRESETS_FULL), TFT_RED);
|
||||
}
|
||||
handled = true;
|
||||
}
|
||||
if (!handled && backBtn.contains(tap.x, tap.y)) {
|
||||
done = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user