From e37dd469af0b416663ba0a272cae8822e5c88167 Mon Sep 17 00:00:00 2001 From: Eiswolf-BG <37225519+Eiswolf-BG@users.noreply.github.com> Date: Wed, 5 Aug 2026 15:27:29 +0200 Subject: [PATCH] v2.3.1: Info button for location presets, fix menu titles clipped at top edge, scrollable info screen --- src/airline_filter_screen.cpp | 4 +- src/calibration_screen.cpp | 2 +- src/i18n.h | 4 ++ src/i18n_de.h | 4 ++ src/i18n_en.h | 5 ++ src/i18n_es.h | 4 ++ src/i18n_fr.h | 4 ++ src/i18n_it.h | 4 ++ src/i18n_tr.h | 4 ++ src/language_screen.cpp | 2 +- src/location_presets_screen.cpp | 122 ++++++++++++++++++++++++++++++-- src/logbook_files_screen.cpp | 4 +- src/menu_screen.cpp | 8 +-- src/stats_screen.cpp | 4 +- src/units_screen.cpp | 2 +- src/wifi_manage_screen.cpp | 2 +- src/wifi_setup_screen.cpp | 14 ++-- 17 files changed, 168 insertions(+), 25 deletions(-) diff --git a/src/airline_filter_screen.cpp b/src/airline_filter_screen.cpp index e231455..787d39d 100644 --- a/src/airline_filter_screen.cpp +++ b/src/airline_filter_screen.cpp @@ -63,7 +63,7 @@ namespace { auto redraw = [&]() { tft.fillScreen(TFT_BLACK); tft.setTextColor(TFT_GREEN, TFT_BLACK); - tft.setCursor(10, 10); + tft.setCursor(10, 14); tft.println(I18n::t(StringId::AIRLINE_ADD_TITLE)); tft.fillRect(8, 40, Config::SCREEN_WIDTH - 16, 34, TFT_BLACK); @@ -131,7 +131,7 @@ void run(TFT_eSPI& tft) { while (!done) { tft.fillScreen(TFT_BLACK); tft.setTextColor(TFT_GREEN, TFT_BLACK); - tft.setCursor(10, 8); + tft.setCursor(10, 14); tft.println(I18n::t(StringId::AIRLINE_FILTER_TITLE)); tft.setTextColor(TFT_GREEN, TFT_BLACK); tft.setCursor(10, 26); diff --git a/src/calibration_screen.cpp b/src/calibration_screen.cpp index 50f08b6..f338bad 100644 --- a/src/calibration_screen.cpp +++ b/src/calibration_screen.cpp @@ -92,7 +92,7 @@ void run(TFT_eSPI& tft) { tft.fillScreen(TFT_BLACK); tft.setTextColor(TFT_GREEN, TFT_BLACK); - tft.setCursor(10, 10); + tft.setCursor(10, 14); tft.println(I18n::t(StringId::CALIB_SAVED)); delay(800); } diff --git a/src/i18n.h b/src/i18n.h index 13c693f..cb3e2fe 100644 --- a/src/i18n.h +++ b/src/i18n.h @@ -87,6 +87,10 @@ enum class StringId : uint8_t { LOCATION_ADD, LOCATION_LAT_PROMPT, LOCATION_LON_PROMPT, + LOCATION_INFO_TITLE, + LOCATION_INFO_PARA1, + LOCATION_INFO_PARA2, + LOCATION_INFO_PARA3, STATS_TITLE, STATS_TODAY, diff --git a/src/i18n_de.h b/src/i18n_de.h index 60e5775..0482f34 100644 --- a/src/i18n_de.h +++ b/src/i18n_de.h @@ -87,6 +87,10 @@ static const char* const I18N_DE[] = { "Preset hinzufügen", "Breitengrad (z.B. 48.6833)", "Längengrad (z.B. 9.8333)", + "Wie Presets funktionieren", + "Nur EIN Standort ist zur selben Zeit aktiv - entweder Auto oder genau ein Preset. Tippe einen Eintrag an, um ihn zu aktivieren.", + "Du kannst auch die Koordinaten eines beliebigen anderen Ortes eintragen - z.B. Mailand, New York oder Tokio - und siehst DORT den aktuellen Flugverkehr an, egal wo dein Gerät tatsächlich steht!", + "Praktisch auch für mehr Genauigkeit als die automatische IP-Standortbestimmung, z.B. wenn das Gerät dauerhaft an einem festen Ort steht.", "Statistik", "Heute geloggte Flugzeuge", diff --git a/src/i18n_en.h b/src/i18n_en.h index 12c91a1..fcbb6df 100644 --- a/src/i18n_en.h +++ b/src/i18n_en.h @@ -88,6 +88,11 @@ static const char* const I18N_EN[] = { "Latitude (e.g. 48.6833)", "Longitude (e.g. 9.8333)", + "How presets work", + "Only ONE location is active at a time - either Auto or exactly one preset. Tap an entry to activate it.", + "You can also enter the coordinates of any other place - e.g. Milan, New York or Tokyo - and see the live air traffic THERE, no matter where your device actually is!", + "Also handy for more precision than automatic IP-based location, e.g. when the device stays in one fixed spot.", + "Statistics", "Aircraft logged today", "Aircraft logged all-time", diff --git a/src/i18n_es.h b/src/i18n_es.h index 1d19371..68baa87 100644 --- a/src/i18n_es.h +++ b/src/i18n_es.h @@ -87,6 +87,10 @@ static const char* const I18N_ES[] = { "Añadir ubicación", "Latitud (ej. 48.6833)", "Longitud (ej. 9.8333)", + "Como funcionan los presets", + "Solo UNA ubicacion esta activa a la vez - Auto o exactamente un preset. Toque una entrada para activarla.", + "Tambien puede introducir las coordenadas de cualquier otro lugar - ej. Milan, Nueva York o Tokio - y ver el trafico aereo en tiempo real ALLI, sin importar donde este su dispositivo!", + "Tambien es util para mas precision que la geolocalizacion automatica por IP, por ejemplo si el dispositivo permanece siempre en un lugar fijo.", "Estadísticas", "Aviones registrados hoy", diff --git a/src/i18n_fr.h b/src/i18n_fr.h index 5849eca..16a9f52 100644 --- a/src/i18n_fr.h +++ b/src/i18n_fr.h @@ -87,6 +87,10 @@ static const char* const I18N_FR[] = { "Ajouter une position", "Latitude (ex. 48.6833)", "Longitude (ex. 9.8333)", + "Comment marchent les presets", + "Un SEUL lieu est actif a la fois - soit Auto, soit un preset precis. Touchez une entree pour l'activer.", + "Vous pouvez aussi entrer les coordonnees de n'importe quel autre lieu - ex. Milan, New York ou Tokyo - et voir le trafic aerien LA-BAS, peu importe ou se trouve votre appareil !", + "Pratique aussi pour plus de precision que la geolocalisation IP automatique, par exemple si l'appareil reste toujours au meme endroit.", "Statistiques", "Avions enregistrés aujourd'hui", diff --git a/src/i18n_it.h b/src/i18n_it.h index 701cefc..1491ac6 100644 --- a/src/i18n_it.h +++ b/src/i18n_it.h @@ -87,6 +87,10 @@ static const char* const I18N_IT[] = { "Aggiungi posizione", "Latitudine (es. 48.6833)", "Longitudine (es. 9.8333)", + "Come funzionano i preset", + "Solo UNA posizione e attiva alla volta - Auto oppure esattamente un preset. Tocca una voce per attivarla.", + "Puoi anche inserire le coordinate di qualsiasi altro luogo - es. Milano, New York o Tokyo - e vedere il traffico aereo in tempo reale LI, indipendentemente da dove si trovi il dispositivo!", + "Utile anche per maggiore precisione rispetto alla geolocalizzazione IP automatica, ad esempio se il dispositivo resta sempre in un punto fisso.", "Statistiche", "Aerei registrati oggi", diff --git a/src/i18n_tr.h b/src/i18n_tr.h index cc04140..7c61b69 100644 --- a/src/i18n_tr.h +++ b/src/i18n_tr.h @@ -87,6 +87,10 @@ static const char* const I18N_TR[] = { "Ön ayar ekle", "Enlem (örn. 48.6833)", "Boylam (örn. 9.8333)", + "On ayarlar nasil calisir", + "Aynı anda SADECE bir konum aktiftir - Otomatik veya tam olarak bir on ayar. Aktif etmek icin bir girise dokunun.", + "Ayrica baska herhangi bir yerin koordinatlarini da girebilirsiniz - orn. Milano, New York veya Tokyo - ve cihazinizin gercek konumundan bagimsiz olarak ORADAKI canli hava trafigini gorebilirsiniz!", + "Cihaz surekli sabit bir yerde kaliyorsa, otomatik IP tabanli konumdan daha hassas olmasi icin de kullanislidir.", "İstatistikler", "Bugün kaydedilen uçaklar", diff --git a/src/language_screen.cpp b/src/language_screen.cpp index 98dede7..67f8e4d 100644 --- a/src/language_screen.cpp +++ b/src/language_screen.cpp @@ -42,7 +42,7 @@ void run(TFT_eSPI& tft) { while (!done) { tft.fillScreen(TFT_BLACK); tft.setTextColor(TFT_GREEN, TFT_BLACK); - tft.setCursor(10, 6); + tft.setCursor(10, 14); tft.println(I18n::t(StringId::LANGUAGE_TITLE)); uint8_t current = SettingsStore::language(); diff --git a/src/location_presets_screen.cpp b/src/location_presets_screen.cpp index 36e4ade..d34b23c 100644 --- a/src/location_presets_screen.cpp +++ b/src/location_presets_screen.cpp @@ -60,7 +60,7 @@ namespace { auto redraw = [&]() { tft.fillScreen(TFT_BLACK); tft.setTextColor(TFT_GREEN, TFT_BLACK); - tft.setCursor(10, 10); + tft.setCursor(10, 14); tft.println(title); tft.fillRect(8, 40, Config::SCREEN_WIDTH - 16, 34, TFT_BLACK); @@ -125,6 +125,113 @@ namespace { return LocationPresets::addPreset(lat, lon); } + + int16_t layoutWrapped(TFT_eSPI& tft, int16_t x, int16_t startY, int16_t maxWidth, + int16_t lineHeight, const String& text, int16_t scrollY, + int16_t viewTop, int16_t viewBottom, bool draw) { + int16_t y = startY; + int32_t start = 0; + int32_t len = text.length(); + while (start < len) { + while (start < len && text[start] == ' ') start++; + if (start >= len) break; + + String line = text.substring(start, len); + while (tft.textWidth(line) > maxWidth) { + int32_t lastSpace = line.lastIndexOf(' '); + if (lastSpace <= 0) break; + line = line.substring(0, lastSpace); + } + + if (draw) { + int16_t screenY = y - scrollY; + if (screenY >= viewTop && screenY <= viewBottom) { + tft.setCursor(x, screenY); + tft.print(line); + } + } + y += lineHeight; + start += line.length(); + } + return y; + } + + String truncateForWidth(TFT_eSPI& tft, const String& text, int16_t maxWidth) { + String s = text; + if (tft.textWidth(s) <= maxWidth) return s; + while (s.length() > 1 && tft.textWidth(s + "...") > maxWidth) { + s.remove(s.length() - 1); + } + return s + "..."; + } + + void runInfoScreen(TFT_eSPI& tft) { + MenuStars::reset(); + + constexpr int16_t textMaxWidth = Config::SCREEN_WIDTH - 20; + constexpr int16_t LINE_H = 16; + constexpr int16_t VIEW_TOP = 40; + Rect backBtn = {10, (int16_t)(Config::SCREEN_HEIGHT - 50), (int16_t)(Config::SCREEN_WIDTH - 20), 40}; + constexpr int16_t VIEW_BOTTOM = Config::SCREEN_HEIGHT - 60; + + int16_t totalH = VIEW_TOP; + totalH = layoutWrapped(tft, 10, totalH, textMaxWidth, LINE_H, I18n::t(StringId::LOCATION_INFO_PARA1), 0, 0, 0, false); + totalH += 8; + totalH = layoutWrapped(tft, 10, totalH, textMaxWidth, LINE_H, I18n::t(StringId::LOCATION_INFO_PARA2), 0, 0, 0, false); + totalH += 8; + totalH = layoutWrapped(tft, 10, totalH, textMaxWidth, LINE_H, I18n::t(StringId::LOCATION_INFO_PARA3), 0, 0, 0, false); + + int16_t maxScroll = totalH - VIEW_BOTTOM; + if (maxScroll < 0) maxScroll = 0; + bool scrollable = maxScroll > 0; + int16_t scrollY = 0; + + Rect upBtn = {(int16_t)(Config::SCREEN_WIDTH - 34), 4, 30, 24}; + Rect downBtn = {(int16_t)(Config::SCREEN_WIDTH - 34), 30, 30, 24}; + constexpr int16_t SCROLL_STEP = 48; + + auto redraw = [&]() { + tft.fillScreen(TFT_BLACK); + tft.setTextColor(TFT_GREEN, TFT_BLACK); + tft.setCursor(10, 14); + tft.println(I18n::t(StringId::LOCATION_INFO_TITLE)); + + if (scrollable) { + drawButton(tft, upBtn, "^"); + drawButton(tft, downBtn, "v"); + } + + tft.setTextColor(TFT_GREEN, TFT_BLACK); + int16_t y = VIEW_TOP; + y = layoutWrapped(tft, 10, y, textMaxWidth, LINE_H, I18n::t(StringId::LOCATION_INFO_PARA1), scrollY, VIEW_TOP, VIEW_BOTTOM, true); + y += 8; + y = layoutWrapped(tft, 10, y, textMaxWidth, LINE_H, I18n::t(StringId::LOCATION_INFO_PARA2), scrollY, VIEW_TOP, VIEW_BOTTOM, true); + y += 8; + layoutWrapped(tft, 10, y, textMaxWidth, LINE_H, I18n::t(StringId::LOCATION_INFO_PARA3), scrollY, VIEW_TOP, VIEW_BOTTOM, true); + + drawButton(tft, backBtn, I18n::t(StringId::BACK)); + }; + + redraw(); + + while (true) { + TouchInput::Point tap; + if (TouchInput::wasTapped(tap)) { + if (backBtn.contains(tap.x, tap.y)) return; + if (scrollable && upBtn.contains(tap.x, tap.y) && scrollY > 0) { + scrollY -= SCROLL_STEP; + if (scrollY < 0) scrollY = 0; + redraw(); + } else if (scrollable && downBtn.contains(tap.x, tap.y) && scrollY < maxScroll) { + scrollY += SCROLL_STEP; + if (scrollY > maxScroll) scrollY = maxScroll; + redraw(); + } + } + MenuStars::update(tft); + delay(20); + } + } } void run(TFT_eSPI& tft) { @@ -137,9 +244,12 @@ void run(TFT_eSPI& tft) { while (!done) { tft.fillScreen(TFT_BLACK); tft.setTextColor(TFT_GREEN, TFT_BLACK); - tft.setCursor(10, 8); + tft.setCursor(10, 14); tft.println(I18n::t(StringId::LOCATION_TITLE)); + Rect infoBtn = {(int16_t)(Config::SCREEN_WIDTH - 40), 2, 30, 24}; + drawButton(tft, infoBtn, "?"); + int8_t active = LocationPresets::activeIndex(); uint8_t count = LocationPresets::count(); int16_t y = 36; @@ -163,8 +273,9 @@ void run(TFT_eSPI& tft) { double lat = 0, lon = 0; LocationPresets::getLatLon(i, lat, lon); char coords[24]; - snprintf(coords, sizeof(coords), "%d: %.3f, %.3f", i + 1, lat, lon); + snprintf(coords, sizeof(coords), "%d: %.2f, %.2f", i + 1, lat, lon); String 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); } else { @@ -196,7 +307,10 @@ void run(TFT_eSPI& tft) { } bool handled = false; - if (autoRect.contains(tap.x, tap.y)) { + if (infoBtn.contains(tap.x, tap.y)) { + runInfoScreen(tft); + handled = true; + } else if (autoRect.contains(tap.x, tap.y)) { LocationPresets::setActiveIndex(-1); handled = true; } diff --git a/src/logbook_files_screen.cpp b/src/logbook_files_screen.cpp index 635f0d8..962fd8d 100644 --- a/src/logbook_files_screen.cpp +++ b/src/logbook_files_screen.cpp @@ -31,7 +31,7 @@ namespace { void run(TFT_eSPI& tft) { tft.fillScreen(TFT_BLACK); tft.setTextColor(TFT_GREEN, TFT_BLACK); - tft.setCursor(10, 8); + tft.setCursor(10, 14); tft.println(I18n::t(StringId::LOGFILES_TITLE)); tft.setTextColor(TFT_DARKGREEN, TFT_BLACK); tft.setCursor(10, 30); @@ -44,7 +44,7 @@ void run(TFT_eSPI& tft) { tft.fillScreen(TFT_BLACK); tft.setTextColor(TFT_GREEN, TFT_BLACK); - tft.setCursor(10, 8); + tft.setCursor(10, 14); tft.println(I18n::t(StringId::LOGFILES_TITLE)); if (count == 0) { diff --git a/src/menu_screen.cpp b/src/menu_screen.cpp index 1fbe498..5874a3a 100644 --- a/src/menu_screen.cpp +++ b/src/menu_screen.cpp @@ -85,7 +85,7 @@ void run(TFT_eSPI& tft) { if (page == Page::Main) { tft.setTextColor(TFT_GREEN, TFT_BLACK); - tft.setCursor(10, 6); + tft.setCursor(10, 14); tft.println(I18n::t(StringId::MENU_SETTINGS)); Rect regionBtn = catRowRect(0); @@ -121,7 +121,7 @@ void run(TFT_eSPI& tft) { } else if (page == Page::Region) { tft.setTextColor(TFT_GREEN, TFT_BLACK); - tft.setCursor(10, 2); + tft.setCursor(10, 14); tft.println(I18n::t(StringId::MENU_CATEGORY_REGION)); Rect languageBtn = rowRect(0); @@ -149,7 +149,7 @@ void run(TFT_eSPI& tft) { } else if (page == Page::System) { tft.setTextColor(TFT_GREEN, TFT_BLACK); - tft.setCursor(10, 2); + tft.setCursor(10, 14); tft.println(I18n::t(StringId::MENU_CATEGORY_SYSTEM)); Rect calibBtn = rowRect(0); @@ -204,7 +204,7 @@ void run(TFT_eSPI& tft) { } else { // Page::Flight tft.setTextColor(TFT_GREEN, TFT_BLACK); - tft.setCursor(10, 2); + tft.setCursor(10, 14); tft.println(I18n::t(StringId::MENU_CATEGORY_FLIGHT)); Rect statsBtn = rowRect(0); diff --git a/src/stats_screen.cpp b/src/stats_screen.cpp index d3cf4fa..2e63a04 100644 --- a/src/stats_screen.cpp +++ b/src/stats_screen.cpp @@ -46,7 +46,7 @@ namespace { void run(TFT_eSPI& tft) { tft.fillScreen(TFT_BLACK); tft.setTextColor(TFT_GREEN, TFT_BLACK); - tft.setCursor(10, 10); + tft.setCursor(10, 14); tft.println(I18n::t(StringId::STATS_TITLE)); tft.setTextColor(TFT_DARKGREEN, TFT_BLACK); tft.setCursor(10, ROW1_Y); @@ -67,7 +67,7 @@ void run(TFT_eSPI& tft) { auto redraw = [&]() { tft.fillScreen(TFT_BLACK); tft.setTextColor(TFT_GREEN, TFT_BLACK); - tft.setCursor(10, 10); + tft.setCursor(10, 14); tft.println(I18n::t(StringId::STATS_TITLE)); drawStatRow(tft, ROW1_Y, I18n::t(StringId::STATS_TODAY), String(today)); diff --git a/src/units_screen.cpp b/src/units_screen.cpp index 1356814..f6be0fa 100644 --- a/src/units_screen.cpp +++ b/src/units_screen.cpp @@ -45,7 +45,7 @@ void run(TFT_eSPI& tft) { while (!done) { tft.fillScreen(TFT_BLACK); tft.setTextColor(TFT_GREEN, TFT_BLACK); - tft.setCursor(10, 10); + tft.setCursor(10, 14); tft.println(I18n::t(StringId::UNITS_TITLE)); uint8_t current = SettingsStore::unitsMode(); diff --git a/src/wifi_manage_screen.cpp b/src/wifi_manage_screen.cpp index f2c02c5..d588795 100644 --- a/src/wifi_manage_screen.cpp +++ b/src/wifi_manage_screen.cpp @@ -37,7 +37,7 @@ void run(TFT_eSPI& tft) { while (!done) { tft.fillScreen(TFT_BLACK); tft.setTextColor(TFT_GREEN, TFT_BLACK); - tft.setCursor(10, 10); + tft.setCursor(10, 14); tft.println(I18n::t(StringId::WIFI_NETWORKS_TITLE)); uint8_t count = WifiMgr::networkCount(); diff --git a/src/wifi_setup_screen.cpp b/src/wifi_setup_screen.cpp index cc422d2..3eae877 100644 --- a/src/wifi_setup_screen.cpp +++ b/src/wifi_setup_screen.cpp @@ -203,7 +203,7 @@ bool run(TFT_eSPI& tft) { tft.fillScreen(TFT_BLACK); tft.setTextColor(TFT_GREEN, TFT_BLACK); tft.setTextSize(1); - tft.setCursor(10, 10); + tft.setCursor(10, 14); tft.println(I18n::t(StringId::WIFI_SCANNING)); drawCancelButton(tft); @@ -223,7 +223,7 @@ bool run(TFT_eSPI& tft) { needsRedraw = true; tft.fillScreen(TFT_BLACK); - tft.setCursor(10, 10); + tft.setCursor(10, 14); tft.setTextColor(TFT_GREEN, TFT_BLACK); tft.println(ssidCount == 0 ? I18n::t(StringId::WIFI_NO_NETWORKS) : I18n::t(StringId::WIFI_SELECT)); drawCancelButton(tft); @@ -236,7 +236,7 @@ bool run(TFT_eSPI& tft) { WifiMgr::addNetwork(ssidList[selectedIndex].c_str(), passwordBuf); stage = Stage::Done; tft.fillScreen(TFT_BLACK); - tft.setCursor(10, 10); + tft.setCursor(10, 14); tft.setTextColor(TFT_GREEN, TFT_BLACK); tft.println(I18n::t(StringId::WIFI_CONNECTED_BANG)); delay(900); @@ -244,7 +244,7 @@ bool run(TFT_eSPI& tft) { } else if (WifiMgr::getState() == WifiMgr::State::Failed) { stage = Stage::Done; tft.fillScreen(TFT_BLACK); - tft.setCursor(10, 10); + tft.setCursor(10, 14); tft.setTextColor(TFT_RED, TFT_BLACK); tft.println(I18n::t(StringId::WIFI_CONNECTION_FAILED)); tft.setTextColor(TFT_GREEN, TFT_BLACK); @@ -309,7 +309,7 @@ bool run(TFT_eSPI& tft) { } else if (connectBtn.contains(tap.x, tap.y)) { if (WifiMgr::networkCount() >= Config::MAX_WIFI_NETWORKS) { tft.fillScreen(TFT_BLACK); - tft.setCursor(10, 10); + tft.setCursor(10, 14); tft.setTextColor(TFT_RED, TFT_BLACK); tft.println(I18n::t(StringId::WIFI_ALREADY_3)); tft.setTextColor(TFT_GREEN, TFT_BLACK); @@ -322,7 +322,7 @@ bool run(TFT_eSPI& tft) { WifiMgr::connectTo(ssidList[selectedIndex].c_str(), passwordBuf); stage = Stage::Connecting; tft.fillScreen(TFT_BLACK); - tft.setCursor(10, 10); + tft.setCursor(10, 14); tft.setTextColor(TFT_GREEN, TFT_BLACK); tft.println(I18n::t(StringId::WIFI_CONNECTING)); } else if (backBtn.contains(tap.x, tap.y)) { @@ -358,7 +358,7 @@ bool run(TFT_eSPI& tft) { } else if (stage == Stage::EnterPassword) { tft.fillRect(0, 0, Config::SCREEN_WIDTH, KB_TOP - 4, TFT_BLACK); tft.setTextColor(TFT_GREEN, TFT_BLACK); - tft.setCursor(10, 6); + tft.setCursor(10, 14); tft.printf("%s%s", I18n::t(StringId::WIFI_LABEL_PREFIX), ssidList[selectedIndex].c_str()); tft.setCursor(10, 22); tft.setTextColor(TFT_GREEN, TFT_BLACK);