v2.2: Custom Unicode font for accented characters (DE/TR/FR/ES/IT), 4-category menu reorganization, black/green UI theme, header layout improvements

This commit is contained in:
Eiswolf-BG
2026-08-05 01:07:08 +02:00
parent 7fedf860e5
commit 2b07fd036f
20 changed files with 1115 additions and 478 deletions

View File

@@ -14,11 +14,12 @@ namespace {
}
};
void drawButton(TFT_eSPI& tft, const Rect& r, const String& label, uint16_t bg = TFT_NAVY) {
tft.fillRoundRect(r.x, r.y, r.w, r.h, 4, bg);
tft.drawRoundRect(r.x, r.y, r.w, r.h, 4, TFT_DARKGREY);
void drawButton(TFT_eSPI& tft, const Rect& r, const String& label, bool danger = false) {
uint16_t accent = danger ? TFT_RED : TFT_GREEN;
tft.fillRoundRect(r.x, r.y, r.w, r.h, 4, TFT_BLACK);
tft.drawRoundRect(r.x, r.y, r.w, r.h, 4, accent);
tft.setTextDatum(MC_DATUM);
tft.setTextColor(TFT_WHITE, bg);
tft.setTextColor(accent, TFT_BLACK);
tft.drawString(label, r.x + r.w / 2, r.y + r.h / 2);
tft.setTextDatum(TL_DATUM);
}
@@ -59,14 +60,14 @@ namespace {
auto redraw = [&]() {
tft.fillScreen(TFT_BLACK);
tft.setTextColor(TFT_WHITE, TFT_BLACK);
tft.setTextColor(TFT_GREEN, TFT_BLACK);
tft.setCursor(10, 10);
tft.println(I18n::t(StringId::AIRLINE_ADD_TITLE));
tft.fillRect(8, 40, Config::SCREEN_WIDTH - 16, 30, TFT_NAVY);
tft.drawRect(8, 40, Config::SCREEN_WIDTH - 16, 30, TFT_DARKGREY);
tft.fillRect(8, 40, Config::SCREEN_WIDTH - 16, 30, TFT_BLACK);
tft.drawRect(8, 40, Config::SCREEN_WIDTH - 16, 30, TFT_GREEN);
tft.setTextSize(2);
tft.setTextColor(TFT_WHITE, TFT_NAVY);
tft.setTextColor(TFT_GREEN, TFT_BLACK);
tft.setCursor(14, 47);
tft.print(buf);
tft.setTextSize(1);
@@ -76,7 +77,7 @@ namespace {
for (uint8_t i = 0; i < 7; i++) drawButton(tft, row3Rects[i], String(ROW3[i]));
drawButton(tft, backspaceBtn, "<-");
drawButton(tft, cancelBtn, I18n::t(StringId::CANCEL), TFT_MAROON);
drawButton(tft, cancelBtn, I18n::t(StringId::CANCEL), true);
drawButton(tft, confirmBtn, I18n::t(StringId::ADD));
};
@@ -126,10 +127,10 @@ void run(TFT_eSPI& tft) {
bool done = false;
while (!done) {
tft.fillScreen(TFT_BLACK);
tft.setTextColor(TFT_WHITE, TFT_BLACK);
tft.setTextColor(TFT_GREEN, TFT_BLACK);
tft.setCursor(10, 8);
tft.println(I18n::t(StringId::AIRLINE_FILTER_TITLE));
tft.setTextColor(TFT_DARKGREY, TFT_BLACK);
tft.setTextColor(TFT_GREEN, TFT_BLACK);
tft.setCursor(10, 26);
tft.println(I18n::t(StringId::AIRLINE_FILTER_DESC1));
tft.setCursor(10, 38);
@@ -147,13 +148,13 @@ void run(TFT_eSPI& tft) {
rowRects[i] = rowRect;
removeRects[i] = removeRect;
tft.fillRoundRect(rowRect.x, rowRect.y, rowRect.w, rowRect.h, 4, TFT_NAVY);
tft.drawRoundRect(rowRect.x, rowRect.y, rowRect.w, rowRect.h, 4, TFT_DARKGREY);
tft.fillRoundRect(rowRect.x, rowRect.y, rowRect.w, rowRect.h, 4, TFT_BLACK);
tft.drawRoundRect(rowRect.x, rowRect.y, rowRect.w, rowRect.h, 4, TFT_GREEN);
tft.setTextDatum(MC_DATUM);
tft.setTextColor(TFT_WHITE, TFT_NAVY);
tft.setTextColor(TFT_GREEN, TFT_BLACK);
tft.drawString(AirlineFilter::icaoAt(i), rowRect.x + rowRect.w / 2, rowRect.y + rowRect.h / 2);
tft.setTextDatum(TL_DATUM);
drawButton(tft, removeRect, "X", TFT_MAROON);
drawButton(tft, removeRect, "X", true);
y += ROW_H + ROW_GAP;
}

View File

@@ -64,7 +64,7 @@ void run(TFT_eSPI& tft) {
for (uint8_t i = 0; i < 4; i++) {
tft.fillScreen(TFT_BLACK);
tft.setTextColor(TFT_WHITE, TFT_BLACK);
tft.setTextColor(TFT_GREEN, TFT_BLACK);
tft.setTextSize(1);
tft.setTextDatum(MC_DATUM);
tft.drawString(I18n::t(StringId::CALIB_TITLE), W / 2, H / 2 - 12);
@@ -88,7 +88,7 @@ void run(TFT_eSPI& tft) {
TouchInput::saveCalibration();
tft.fillScreen(TFT_BLACK);
tft.setTextColor(TFT_WHITE, TFT_BLACK);
tft.setTextColor(TFT_GREEN, TFT_BLACK);
tft.setCursor(10, 10);
tft.println(I18n::t(StringId::CALIB_SAVED));
delay(800);

View File

@@ -1,12 +1,6 @@
#pragma once
#include <Arduino.h>
// Zentrales Uebersetzungssystem: jeder feste UI-Text der App hat eine
// StringId. Die eigentlichen Uebersetzungen liegen in je einer Datei pro
// Sprache (i18n_en.h, i18n_de.h, i18n_fr.h, i18n_tr.h, i18n_es.h, i18n_it.h) -
// jede Datei enthaelt ein Array in GENAU derselben Reihenfolge wie dieses
// Enum. Neue Menuepunkte/Texte: hier eine neue StringId VOR COUNT einfuegen,
// dann in ALLEN 6 Sprachdateien an derselben Position ergaenzen.
enum class StringId : uint8_t {
OK = 0,
CANCEL,
@@ -139,17 +133,20 @@ enum class StringId : uint8_t {
LANGUAGE_TITLE,
COUNT // IMMER als letztes Element - markiert die Gesamtanzahl
MENU_CATEGORY_REGION,
MENU_CATEGORY_WIFI,
MENU_CATEGORY_SYSTEM,
MENU_CATEGORY_FLIGHT,
WIFI_NETWORKS_TITLE,
WIFI_ADD_NETWORK,
WIFI_EMPTY_SLOT,
COUNT
};
namespace I18n {
constexpr uint8_t LANG_COUNT = 6;
// Liefert den uebersetzten Text fuer 'id' in der aktuell in den
// Einstellungen gewaehlten Sprache (SettingsStore::language()).
const char* t(StringId id);
// Eigennamen der Sprachen (fuer das Sprachauswahl-Menue), IMMER im
// jeweiligen Idiom selbst geschrieben (z.B. "Deutsch", nicht "German").
const char* languageName(uint8_t index);
}

View File

@@ -4,10 +4,10 @@
static const char* const I18N_DE[] = {
"OK",
"Abbrechen",
"Zurueck",
"< Zurueck",
"Laedt...",
"Hinzufuegen",
"Zurück",
"< Zurück",
"Lädt...",
"Hinzufügen",
"AN",
"AUS",
@@ -20,13 +20,13 @@ static const char* const I18N_DE[] = {
"Ermittle Standort...",
"Bereit!",
"Fuer diese App wird",
"Für diese App wird",
"eine SD-Karte",
"benoetigt",
"SD-Karte einlegen und Geraet neu starten",
"benötigt",
"SD-Karte einlegen und Gerät neu starten",
"Touch-Kalibrierung",
"Bitte den Kreis beruehren:",
"Bitte den Kreis berühren:",
"Oben links",
"Oben rechts",
"Unten rechts",
@@ -35,14 +35,14 @@ static const char* const I18N_DE[] = {
"Suche WLAN...",
"Keine Netzwerke gefunden",
"WLAN waehlen:",
"WLAN wählen:",
"Verbunden!",
"Verbindung fehlgeschlagen.",
"Zurueck zur Netzwerkliste...",
"Zurück zur Netzwerkliste...",
"Passwort:",
"Leerzeichen",
"Verbinden",
"Zurueck zur Liste",
"Zurück zur Liste",
"Bereits 3 Netzwerke gespeichert.",
"Erst eins entfernen.",
"Verbinde...",
@@ -60,7 +60,7 @@ static const char* const I18N_DE[] = {
"Logbuch-Dateien",
"Weitere Einstellungen >",
"Notfall-Alarm: ",
"Naeherungs-LED: ",
"Näherungs-LED: ",
"LED-Herzschlag: ",
"Flugbuch: ",
"Bodenfahrzeuge ausblenden: ",
@@ -69,7 +69,7 @@ static const char* const I18N_DE[] = {
"Einstellungen wiederherstellen",
"Backup gespeichert.",
"Backup fehlgeschlagen.",
"Wiederhergestellt. Geraet neu starten!",
"Wiederhergestellt. Gerät neu starten!",
"Wiederherstellung fehlgeschlagen.",
"Sprache",
"Einheiten",
@@ -77,46 +77,46 @@ static const char* const I18N_DE[] = {
"Ausgeblendete Airlines",
"Flugzeuge dieser Airlines werden",
"im Radar ausgeblendet.",
"Airline hinzufuegen",
"Airline hinzufuegen (ICAO-Code)",
"Airline hinzufügen",
"Airline hinzufügen (ICAO-Code)",
"Standort-Presets",
"Automatisch (GPS/IP)",
"Preset",
"(leer)",
"Preset hinzufuegen",
"Preset hinzufügen",
"Breitengrad (z.B. 48.6833)",
"Laengengrad (z.B. 9.8333)",
"Längengrad (z.B. 9.8333)",
"Statistik",
"Heute geloggte Flugzeuge",
"Insgesamt geloggte Flugzeuge",
"Tage mit Sichtungen",
"Durchschnitt pro Tag",
"Flugbuch zuruecksetzen",
"Nochmal tippen zum Bestaetigen!",
"Flugbuch-Daten zurueckgesetzt.",
"Flugbuch zurücksetzen",
"Nochmal tippen zum Bestätigen!",
"Flugbuch-Daten zurückgesetzt.",
"Laufzeit: ",
"Logbuch-Dateien",
"Noch keine Flugbuch-Eintraege.",
"Noch keine Flugbuch-Einträge.",
"Zeige die letzten ",
" von ",
" Tagen",
" Flugzeuge",
"Fuer Details antippen",
"Für Details antippen",
"Modell: ",
"Typ: ",
"laedt...",
"lädt...",
"unbekannt",
"Hoehe: ",
"Höhe: ",
"Geschw.: ",
"Distanz: ",
"Kurs: ",
"Sitzplaetze (geschaetzt): ",
"Sitzplaetze: unbekannt",
"Sitzplätze (geschätzt): ",
"Sitzplätze: unbekannt",
"Woanders antippen zum Schliessen",
"Steigen: ",
"Sinken: ",
@@ -132,6 +132,15 @@ static const char* const I18N_DE[] = {
"Imperial (ft, kt, nm)",
"Sprache",
"Land/Region",
"WLAN/Netzwerk",
"System",
"Flugoptionen",
"WLAN-Netzwerke",
"Netzwerk hinzufügen",
"(leer)",
};
static_assert(sizeof(I18N_DE) / sizeof(I18N_DE[0]) == (size_t)StringId::COUNT,

View File

@@ -132,6 +132,15 @@ static const char* const I18N_EN[] = {
"Imperial (ft, kt, nm)",
"Language",
"Region & Language",
"WiFi / Network",
"System",
"Flight Options",
"WiFi networks",
"Add network",
"(empty)",
};
static_assert(sizeof(I18N_EN) / sizeof(I18N_EN[0]) == (size_t)StringId::COUNT,

View File

@@ -4,10 +4,10 @@
static const char* const I18N_ES[] = {
"OK",
"Cancelar",
"Atras",
"< Atras",
"Atrás",
"< Atrás",
"Cargando...",
"Anadir",
"Añadir",
"ON",
"OFF",
@@ -16,30 +16,30 @@ static const char* const I18N_ES[] = {
"Tarjeta SD: OK",
"Conectando WiFi...",
"WiFi OK: ",
"WiFi FALLO",
"Obteniendo ubicacion...",
"Listo!",
"WiFi FALLÓ",
"Obteniendo ubicación...",
"¡Listo!",
"Esta app necesita",
"una tarjeta SD",
"requerida",
"Inserte una tarjeta y reinicie el dispositivo",
"Calibracion tactil",
"Toque el circulo:",
"Calibración táctil",
"Toque el círculo:",
"Arriba izquierda",
"Arriba derecha",
"Abajo derecha",
"Abajo izquierda",
"Calibracion guardada!",
"¡Calibración guardada!",
"Buscando WiFi...",
"No se encontraron redes",
"Elegir WiFi:",
"Conectado!",
"Conexion fallida.",
"¡Conectado!",
"Conexión fallida.",
"Volviendo a la lista...",
"Contrasena:",
"Contraseña:",
"Espacio",
"Conectar",
"Volver a la lista",
@@ -50,59 +50,59 @@ static const char* const I18N_ES[] = {
"Ajustes",
"Ajustes (2/2)",
"Calibrar tactil",
"Calibrar táctil",
"Pantalla: invertida (toque)",
"Pantalla: normal (toque)",
"Gestionar redes WiFi",
"Ubicaciones guardadas",
"Apagado de pantalla: ",
"Estadisticas",
"Estadísticas",
"Archivos del registro",
"Mas ajustes >",
"Más ajustes >",
"Alerta de emergencia: ",
"LED de proximidad: ",
"Latido LED: ",
"Registro de vuelos: ",
"Ocultar vehiculos terrestres: ",
"Filtro de aerolineas",
"Ocultar vehículos terrestres: ",
"Filtro de aerolíneas",
"Copia de seguridad",
"Restaurar ajustes",
"Copia guardada.",
"Fallo la copia de seguridad.",
"Restaurado. Reinicie el dispositivo!",
"Fallo la restauracion.",
"Falló la copia de seguridad.",
"¡Restaurado. Reinicie el dispositivo!",
"Falló la restauración.",
"Idioma",
"Unidades",
"Aerolineas ocultas",
"Los aviones de estas aerolineas",
"Aerolíneas ocultas",
"Los aviones de estas aerolíneas",
"se ocultan del radar.",
"Anadir aerolinea",
"Anadir aerolinea (codigo OACI)",
"Añadir aerolínea",
"Añadir aerolínea (código OACI)",
"Ubicaciones guardadas",
"Auto (GPS/IP)",
"Ubicacion",
"(vacio)",
"Anadir ubicacion",
"Ubicación",
"(vacío)",
"Añadir ubicación",
"Latitud (ej. 48.6833)",
"Longitud (ej. 9.8333)",
"Estadisticas",
"Estadísticas",
"Aviones registrados hoy",
"Aviones registrados en total",
"Dias con avistamientos",
"Promedio por dia",
"Días con avistamientos",
"Promedio por día",
"Reiniciar registro",
"Toque de nuevo para confirmar!",
"¡Toque de nuevo para confirmar!",
"Registro reiniciado.",
"Tiempo activo: ",
"Archivos del registro",
"Aun no hay registros.",
"Mostrando los ultimos ",
"Aún no hay registros.",
"Mostrando los últimos ",
" de ",
" dias",
" días",
" aviones",
"Toque para ver detalles",
@@ -124,14 +124,23 @@ static const char* const I18N_ES[] = {
"Squawk: ",
"Guardado: ",
"Fallo la captura de pantalla",
"Falló la captura de pantalla",
"Unidades",
"Auto (segun ubicacion)",
"Metrico (km, m, km/h)",
"Auto (según ubicación)",
"Métrico (km, m, km/h)",
"Imperial (ft, kt, nm)",
"Idioma",
"Región e idioma",
"WiFi / Red",
"Sistema",
"Opciones de vuelo",
"Redes WiFi",
"Añadir red",
"(vacío)",
};
static_assert(sizeof(I18N_ES) / sizeof(I18N_ES[0]) == (size_t)StringId::COUNT,

View File

@@ -16,71 +16,71 @@ static const char* const I18N_FR[] = {
"Carte SD: OK",
"Connexion WiFi...",
"WiFi OK: ",
"ECHEC WiFi",
"ÉCHEC WiFi",
"Localisation...",
"Pret !",
"Prêt !",
"Pour cette appli une",
"carte SD est",
"requise",
"Inserez une carte et redemarrez l'appareil",
"Insérez une carte et redémarrez l'appareil",
"Etalonnage tactile",
"Étalonnage tactile",
"Touchez le cercle :",
"Haut gauche",
"Haut droite",
"Bas droite",
"Bas gauche",
"Etalonnage enregistre !",
"Étalonnage enregistré !",
"Recherche WiFi...",
"Aucun reseau trouve",
"Aucun réseau trouvé",
"Choisir WiFi :",
"Connecte !",
"Echec de connexion.",
"Retour a la liste...",
"Connecté !",
"Échec de connexion.",
"Retour à la liste...",
"Mot de passe :",
"Espace",
"Connecter",
"Retour a la liste",
"Deja 3 reseaux enregistres.",
"Retour à la liste",
"Déjà 3 réseaux enregistrés.",
"Supprimez-en un d'abord.",
"Connexion...",
"WiFi : ",
"Reglages",
"Reglages (2/2)",
"Etalonner le tactile",
"Ecran : inverse (touchez)",
"Ecran : normal (touchez)",
"Gerer les reseaux WiFi",
"Positions enregistrees",
"Extinction ecran : ",
"Réglages",
"Réglages (2/2)",
"Étalonner le tactile",
"Écran : inversé (touchez)",
"Écran : normal (touchez)",
"Gérer les réseaux WiFi",
"Positions enregistrées",
"Extinction écran : ",
"Statistiques",
"Fichiers du journal",
"Plus de reglages >",
"Plus de réglages >",
"Alerte urgence : ",
"LED de proximite : ",
"LED de proximité : ",
"Battement LED : ",
"Journal de vol : ",
"Masquer vehicules au sol : ",
"Masquer véhicules au sol : ",
"Filtre compagnies",
"Sauvegarder reglages",
"Restaurer reglages",
"Sauvegarde enregistree.",
"Echec de la sauvegarde.",
"Restaure. Redemarrez l'appareil !",
"Echec de la restauration.",
"Sauvegarder réglages",
"Restaurer réglages",
"Sauvegarde enregistrée.",
"Échec de la sauvegarde.",
"Restauré. Redémarrez l'appareil !",
"Échec de la restauration.",
"Langue",
"Unites",
"Unités",
"Compagnies masquees",
"Compagnies masquées",
"Les avions de ces compagnies sont",
"masques du radar.",
"masqués du radar.",
"Ajouter une compagnie",
"Ajouter (code OACI)",
"Positions enregistrees",
"Positions enregistrées",
"Auto (GPS/IP)",
"Position",
"(vide)",
@@ -89,25 +89,25 @@ static const char* const I18N_FR[] = {
"Longitude (ex. 9.8333)",
"Statistiques",
"Avions enregistres aujourd'hui",
"Avions enregistres au total",
"Avions enregistrés aujourd'hui",
"Avions enregistrés au total",
"Jours avec observations",
"Moyenne par jour",
"Reinitialiser le journal",
"Réinitialiser le journal",
"Retouchez pour confirmer !",
"Journal reinitialise.",
"Duree de fonctionnement : ",
"Journal réinitialisé.",
"Durée de fonctionnement : ",
"Fichiers du journal",
"Aucune entree pour le moment.",
"Aucune entrée pour le moment.",
"Affichage des ",
" derniers sur ",
" jours",
" avions",
"Touchez pour les details",
"Touchez pour les détails",
"Modele : ",
"Modèle : ",
"Type : ",
"chargement...",
"inconnu",
@@ -115,23 +115,32 @@ static const char* const I18N_FR[] = {
"Vitesse : ",
"Dist. : ",
"Cap : ",
"Sieges (estime) : ",
"Sieges : inconnu",
"Sièges (estimé) : ",
"Sièges : inconnu",
"Touchez ailleurs pour fermer",
"Montee : ",
"Montée : ",
"Descente : ",
"Vol en palier",
"Squawk : ",
"Enregistre : ",
"Echec de la capture d'ecran",
"Enregistré : ",
"Échec de la capture d'écran",
"Unites",
"Unités",
"Auto (selon la position)",
"Metrique (km, m, km/h)",
"Imperial (ft, kt, nm)",
"Métrique (km, m, km/h)",
"Impérial (ft, kt, nm)",
"Langue",
"Région et langue",
"WiFi / Réseau",
"Système",
"Options de vol",
"Réseaux WiFi",
"Ajouter un réseau",
"(vide)",
};
static_assert(sizeof(I18N_FR) / sizeof(I18N_FR[0]) == (size_t)StringId::COUNT,

View File

@@ -43,7 +43,7 @@ static const char* const I18N_IT[] = {
"Spazio",
"Connetti",
"Torna alla lista",
"Gia 3 reti salvate.",
"Già 3 reti salvate.",
"Rimuovine una prima.",
"Connessione...",
"WiFi: ",
@@ -60,7 +60,7 @@ static const char* const I18N_IT[] = {
"File del registro",
"Altre impostazioni >",
"Allarme emergenza: ",
"LED di prossimita: ",
"LED di prossimità: ",
"Battito LED: ",
"Diario di volo: ",
"Nascondi veicoli a terra: ",
@@ -72,7 +72,7 @@ static const char* const I18N_IT[] = {
"Ripristinato. Riavviare il dispositivo!",
"Ripristino fallito.",
"Lingua",
"Unita",
"Unità",
"Compagnie nascoste",
"Gli aerei di queste compagnie",
@@ -96,7 +96,7 @@ static const char* const I18N_IT[] = {
"Reimposta registro",
"Tocca di nuovo per confermare!",
"Registro reimpostato.",
"Tempo di attivita: ",
"Tempo di attività: ",
"File del registro",
"Ancora nessun dato.",
@@ -126,12 +126,21 @@ static const char* const I18N_IT[] = {
"Salvato: ",
"Screenshot fallito",
"Unita",
"Unità",
"Auto (in base alla posizione)",
"Metrico (km, m, km/h)",
"Imperiale (ft, kt, nm)",
"Lingua",
"Regione e lingua",
"WiFi / Rete",
"Sistema",
"Opzioni di volo",
"Reti WiFi",
"Aggiungi rete",
"(vuoto)",
};
static_assert(sizeof(I18N_IT) / sizeof(I18N_IT[0]) == (size_t)StringId::COUNT,

View File

@@ -3,135 +3,144 @@
static const char* const I18N_TR[] = {
"Tamam",
"Iptal",
"İptal",
"Geri",
"< Geri",
"Yukleniyor...",
"Yükleniyor...",
"Ekle",
"ACIK",
"AÇIK",
"KAPALI",
"Asla",
"SD Kart: OK",
"WiFi'ye baglaniyor...",
"WiFi'ye bağlanıyor...",
"WiFi OK: ",
"WiFi BASARISIZ",
"Konum aliniyor...",
"Hazir!",
"WiFi BAŞARISIZ",
"Konum alınıyor...",
"Hazır!",
"Bu uygulama icin bir",
"Bu uygulama için bir",
"SD kart",
"gereklidir",
"Bir kart takin ve cihazi yeniden baslatin",
"Bir kart takın ve cihazı yeniden başlatın",
"Dokunmatik Kalibrasyon",
"Lutfen daireye dokunun:",
"Sol ust",
"Sag ust",
"Sag alt",
"Lütfen daireye dokunun:",
"Sol üst",
"Sağ üst",
"Sağ alt",
"Sol alt",
"Kalibrasyon kaydedildi!",
"WiFi taraniyor...",
"Ag bulunamadi",
"WiFi secin:",
"Baglandi!",
"Baglanti basarisiz.",
"Ag listesine donuluyor...",
"Sifre:",
"Bosluk",
"Baglan",
"Listeye don",
"Zaten 3 ag kayitli.",
"Once birini kaldirin.",
"Baglaniyor...",
"WiFi taranıyor...",
"Ağ bulunamadı",
"WiFi seçin:",
"Bağlandı!",
"Bağlantı başarısız.",
"Ağ listesine dönülüyor...",
"Şifre:",
"Boşluk",
"Bağlan",
"Listeye dön",
"Zaten 3 ağ kayıtlı.",
"Önce birini kaldırın.",
"Bağlanıyor...",
"WiFi: ",
"Ayarlar",
"Ayarlar (2/2)",
"Dokunmatigi kalibre et",
"Dokunmatiği kalibre et",
"Ekran: ters (dokun)",
"Ekran: normal (dokun)",
"WiFi aglarini yonet",
"Konum on ayarlari",
"Ekran zaman asimi: ",
"Istatistikler",
"Kayit defteri dosyalari",
"Diger ayarlar >",
"Acil durum alarmi: ",
"Yakinlik LED'i: ",
"LED kalp atisi: ",
"Ucus kayit defteri: ",
"Kara araclarini gizle: ",
"WiFi ağlarını yönet",
"Konum ön ayarları",
"Ekran zaman aşımı: ",
"İstatistikler",
"Kayıt defteri dosyaları",
"Diğer ayarlar >",
"Acil durum alarmı: ",
"Yakınlık LED'i: ",
"LED kalp atışı: ",
"Uçuş kayıt defteri: ",
"Kara araçlarını gizle: ",
"Havayolu filtresi",
"Ayarlari yedekle",
"Ayarlari geri yukle",
"Ayarları yedekle",
"Ayarları geri yükle",
"Yedekleme kaydedildi.",
"Yedekleme basarisiz.",
"Geri yuklendi. Cihazi yeniden baslatin!",
"Geri yukleme basarisiz.",
"Yedekleme başarısız.",
"Geri yüklendi. Cihazı yeniden başlatın!",
"Geri yükleme başarısız.",
"Dil",
"Birimler",
"Gizli havayollari",
"Bu havayollarina ait ucaklar",
"Gizli havayolları",
"Bu havayollarına ait uçaklar",
"radarda gizlenir.",
"Havayolu ekle",
"Havayolu ekle (ICAO kodu)",
"Konum on ayarlari",
"Konum ön ayarları",
"Otomatik (GPS/IP)",
"On ayar",
"(bos)",
"On ayar ekle",
"Enlem (orn. 48.6833)",
"Boylam (orn. 9.8333)",
"Ön ayar",
"(boş)",
"Ön ayar ekle",
"Enlem (örn. 48.6833)",
"Boylam (örn. 9.8333)",
"Istatistikler",
"Bugun kaydedilen ucaklar",
"Toplam kaydedilen ucaklar",
"Gozlem yapilan gunler",
"Gunluk ortalama",
"Kayit defterini sifirla",
"Onaylamak icin tekrar dokunun!",
"Kayit defteri sifirlandi.",
"Calisma suresi: ",
"İstatistikler",
"Bugün kaydedilen uçaklar",
"Toplam kaydedilen uçaklar",
"Gözlem yapılan günler",
"Günlük ortalama",
"Kayıt defterini sıfırla",
"Onaylamak için tekrar dokunun!",
"Kayıt defteri sıfırlandı.",
"Çalışma süresi: ",
"Kayit defteri dosyalari",
"Henuz kayit yok.",
"Kayıt defteri dosyaları",
"Henüz kayıt yok.",
"Son ",
" / ",
" gun gosteriliyor",
" ucak",
" gün gösteriliyor",
" uçak",
"Detaylar icin dokunun",
"Detaylar için dokunun",
"Model: ",
"Tip: ",
"yukleniyor...",
"yükleniyor...",
"bilinmiyor",
"Yuk.: ",
"Hiz: ",
"Yük.: ",
"Hız: ",
"Mesafe: ",
"Yon: ",
"Yön: ",
"Koltuk (tahmini): ",
"Koltuk: bilinmiyor",
"Kapatmak icin baska yere dokunun",
"Tirmanis: ",
"Alcalma: ",
"Duz ucus",
"Kapatmak için başka yere dokunun",
"Tırmanış: ",
"Alçalma: ",
"Düz uçuş",
"Squawk: ",
"Kaydedildi: ",
"Ekran goruntusu basarisiz",
"Ekran görüntüsü başarısız",
"Birimler",
"Otomatik (konuma gore)",
"Otomatik (konuma göre)",
"Metrik (km, m, km/sa)",
"Imperyal (ft, kt, nm)",
"İmperyal (ft, kt, nm)",
"Dil",
"Bölge ve Dil",
"WiFi / Ağ",
"Sistem",
"Uçuş Seçenekleri",
"WiFi ağları",
"Ağ ekle",
"(boş)",
};
static_assert(sizeof(I18N_TR) / sizeof(I18N_TR[0]) == (size_t)StringId::COUNT,

View File

@@ -14,11 +14,13 @@ namespace {
}
};
void drawButton(TFT_eSPI& tft, const Rect& r, const String& label, uint16_t bg = TFT_NAVY) {
void drawButton(TFT_eSPI& tft, const Rect& r, const String& label, bool active = false) {
uint16_t bg = active ? TFT_GREEN : TFT_BLACK;
uint16_t fg = active ? TFT_BLACK : TFT_GREEN;
tft.fillRoundRect(r.x, r.y, r.w, r.h, 4, bg);
tft.drawRoundRect(r.x, r.y, r.w, r.h, 4, TFT_DARKGREY);
tft.drawRoundRect(r.x, r.y, r.w, r.h, 4, TFT_GREEN);
tft.setTextDatum(MC_DATUM);
tft.setTextColor(TFT_WHITE, bg);
tft.setTextColor(fg, bg);
tft.drawString(label, r.x + r.w / 2, r.y + r.h / 2);
tft.setTextDatum(TL_DATUM);
}
@@ -37,7 +39,7 @@ void run(TFT_eSPI& tft) {
bool done = false;
while (!done) {
tft.fillScreen(TFT_BLACK);
tft.setTextColor(TFT_WHITE, TFT_BLACK);
tft.setTextColor(TFT_GREEN, TFT_BLACK);
tft.setCursor(10, 6);
tft.println(I18n::t(StringId::LANGUAGE_TITLE));
@@ -46,8 +48,7 @@ void run(TFT_eSPI& tft) {
for (uint8_t i = 0; i < I18n::LANG_COUNT; i++) {
langRects[i] = rowRect(i);
drawButton(tft, langRects[i], I18n::languageName(i),
(i == current) ? TFT_DARKGREEN : TFT_NAVY);
drawButton(tft, langRects[i], I18n::languageName(i), i == current);
}
Rect backBtn = rowRect(I18n::LANG_COUNT);

View File

@@ -14,11 +14,15 @@ namespace {
}
};
void drawButton(TFT_eSPI& tft, const Rect& r, const String& label, uint16_t bg = TFT_NAVY) {
void drawButton(TFT_eSPI& tft, const Rect& r, const String& label,
bool active = false, bool danger = false) {
uint16_t accent = danger ? TFT_RED : TFT_GREEN;
uint16_t bg = active ? accent : TFT_BLACK;
uint16_t fg = active ? TFT_BLACK : accent;
tft.fillRoundRect(r.x, r.y, r.w, r.h, 4, bg);
tft.drawRoundRect(r.x, r.y, r.w, r.h, 4, TFT_DARKGREY);
tft.drawRoundRect(r.x, r.y, r.w, r.h, 4, accent);
tft.setTextDatum(MC_DATUM);
tft.setTextColor(TFT_WHITE, bg);
tft.setTextColor(fg, bg);
tft.drawString(label, r.x + r.w / 2, r.y + r.h / 2);
tft.setTextDatum(TL_DATUM);
}
@@ -43,10 +47,6 @@ namespace {
KEY_W, KEY_H};
}
// WICHTIG: Backspace muss VOR den Cancel/OK-Buttons enden (mit
// Abstand), sonst ueberlappen sich die Tap-Flaechen - Backspace wird
// in der Pruef-Reihenfolge zuerst getestet, wuerde also faelschlich
// jeden Tap auf "Cancel" abfangen (Bug: Cancel-Button reagierte nicht).
Rect backspaceBtn = {(int16_t)GRID_LEFT, (int16_t)(GRID_TOP + 4 * (KEY_H + KEY_GAP)),
(int16_t)(3 * KEY_W + 2 * KEY_GAP), 30};
Rect cancelBtn = {10, (int16_t)(Config::SCREEN_HEIGHT - 50), 110, 40};
@@ -57,21 +57,21 @@ namespace {
auto redraw = [&]() {
tft.fillScreen(TFT_BLACK);
tft.setTextColor(TFT_WHITE, TFT_BLACK);
tft.setTextColor(TFT_GREEN, TFT_BLACK);
tft.setCursor(10, 10);
tft.println(title);
tft.fillRect(8, 40, Config::SCREEN_WIDTH - 16, 34, TFT_NAVY);
tft.drawRect(8, 40, Config::SCREEN_WIDTH - 16, 34, TFT_DARKGREY);
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_WHITE, TFT_NAVY);
tft.setTextColor(TFT_GREEN, TFT_BLACK);
tft.setCursor(14, 49);
tft.print(buf);
tft.setTextSize(1);
for (uint8_t i = 0; i < 12; i++) drawButton(tft, keyRects[i], keys[i]);
drawButton(tft, backspaceBtn, "<- Backspace");
drawButton(tft, cancelBtn, I18n::t(StringId::CANCEL), TFT_MAROON);
drawButton(tft, cancelBtn, I18n::t(StringId::CANCEL), false, true);
drawButton(tft, confirmBtn, I18n::t(StringId::OK));
};
@@ -133,7 +133,7 @@ void run(TFT_eSPI& tft) {
bool done = false;
while (!done) {
tft.fillScreen(TFT_BLACK);
tft.setTextColor(TFT_WHITE, TFT_BLACK);
tft.setTextColor(TFT_GREEN, TFT_BLACK);
tft.setCursor(10, 8);
tft.println(I18n::t(StringId::LOCATION_TITLE));
@@ -143,8 +143,7 @@ void run(TFT_eSPI& tft) {
Rect autoRect = {10, y, (int16_t)(Config::SCREEN_WIDTH - 20), ROW_H};
String autoLabel = I18n::t(StringId::LOCATION_AUTO);
drawButton(tft, autoRect, active < 0 ? ("> " + autoLabel) : autoLabel,
active < 0 ? TFT_DARKGREEN : TFT_NAVY);
drawButton(tft, autoRect, active < 0 ? ("> " + autoLabel) : autoLabel, active < 0);
y += ROW_H + ROW_GAP;
Rect rowRects[LocationPresets::MAX_PRESETS];
@@ -163,13 +162,13 @@ void run(TFT_eSPI& tft) {
char coords[24];
snprintf(coords, sizeof(coords), "%d: %.3f, %.3f", i + 1, lat, lon);
String label = (active == (int8_t)i ? "> " : "") + presetWord + " " + coords;
drawButton(tft, rowRect, label, (active == (int8_t)i) ? TFT_DARKGREEN : TFT_NAVY);
drawButton(tft, removeRect, "X", TFT_MAROON);
drawButton(tft, rowRect, label, active == (int8_t)i);
drawButton(tft, removeRect, "X", false, true);
} else {
tft.fillRoundRect(rowRect.x, rowRect.y, rowRect.w, rowRect.h, 4, TFT_BLACK);
tft.drawRoundRect(rowRect.x, rowRect.y, rowRect.w, rowRect.h, 4, TFT_DARKGREY);
tft.drawRoundRect(rowRect.x, rowRect.y, rowRect.w, rowRect.h, 4, TFT_DARKGREEN);
tft.setTextDatum(MC_DATUM);
tft.setTextColor(TFT_DARKGREY, TFT_BLACK);
tft.setTextColor(TFT_DARKGREEN, TFT_BLACK);
String label = presetWord + " " + String(i + 1) + " " + I18n::t(StringId::LOCATION_PRESET_EMPTY);
tft.drawString(label, rowRect.x + rowRect.w / 2, rowRect.y + rowRect.h / 2);
tft.setTextDatum(TL_DATUM);

View File

@@ -15,10 +15,10 @@ namespace {
};
void drawButton(TFT_eSPI& tft, const Rect& r, const String& label) {
tft.fillRoundRect(r.x, r.y, r.w, r.h, 4, TFT_NAVY);
tft.drawRoundRect(r.x, r.y, r.w, r.h, 4, TFT_DARKGREY);
tft.fillRoundRect(r.x, r.y, r.w, r.h, 4, TFT_BLACK);
tft.drawRoundRect(r.x, r.y, r.w, r.h, 4, TFT_GREEN);
tft.setTextDatum(MC_DATUM);
tft.setTextColor(TFT_WHITE, TFT_NAVY);
tft.setTextColor(TFT_GREEN, TFT_BLACK);
tft.drawString(label, r.x + r.w / 2, r.y + r.h / 2);
tft.setTextDatum(TL_DATUM);
}
@@ -29,10 +29,10 @@ namespace {
void run(TFT_eSPI& tft) {
tft.fillScreen(TFT_BLACK);
tft.setTextColor(TFT_WHITE, TFT_BLACK);
tft.setTextColor(TFT_GREEN, TFT_BLACK);
tft.setCursor(10, 8);
tft.println(I18n::t(StringId::LOGFILES_TITLE));
tft.setTextColor(TFT_DARKGREY, TFT_BLACK);
tft.setTextColor(TFT_DARKGREEN, TFT_BLACK);
tft.setCursor(10, 30);
tft.print(I18n::t(StringId::LOADING));
@@ -42,12 +42,12 @@ void run(TFT_eSPI& tft) {
Rect backBtn = {10, (int16_t)(Config::SCREEN_HEIGHT - 50), (int16_t)(Config::SCREEN_WIDTH - 20), 40};
tft.fillScreen(TFT_BLACK);
tft.setTextColor(TFT_WHITE, TFT_BLACK);
tft.setTextColor(TFT_GREEN, TFT_BLACK);
tft.setCursor(10, 8);
tft.println(I18n::t(StringId::LOGFILES_TITLE));
if (count == 0) {
tft.setTextColor(TFT_DARKGREY, TFT_BLACK);
tft.setTextColor(TFT_DARKGREEN, TFT_BLACK);
tft.setCursor(10, 40);
tft.println(I18n::t(StringId::LOGFILES_EMPTY));
} else {
@@ -55,7 +55,7 @@ void run(TFT_eSPI& tft) {
int16_t y = 30;
if (count > VISIBLE_ROWS) {
tft.setTextColor(TFT_DARKGREY, TFT_BLACK);
tft.setTextColor(TFT_DARKGREEN, TFT_BLACK);
tft.setCursor(10, y);
tft.print(String(I18n::t(StringId::LOGFILES_SHOWING_PREFIX)) + VISIBLE_ROWS +
I18n::t(StringId::LOGFILES_OF) + count + I18n::t(StringId::LOGFILES_DAYS_SUFFIX));
@@ -66,7 +66,7 @@ void run(TFT_eSPI& tft) {
tft.setTextColor(TFT_GREEN, TFT_BLACK);
tft.setCursor(10, y);
tft.print(days[i].date);
tft.setTextColor(TFT_WHITE, TFT_BLACK);
tft.setTextColor(TFT_GREEN, TFT_BLACK);
tft.setCursor(110, y);
tft.print(String(days[i].count) + I18n::t(StringId::LOGFILES_AIRCRAFT_SUFFIX));
y += 20;

View File

@@ -29,6 +29,7 @@
#include "screenshot.h"
#include "i18n.h"
#include "first_run_language_screen.h"
#include "ui_font.h"
TFT_eSPI tft = TFT_eSPI();
@@ -59,35 +60,70 @@ struct Rect {
}
};
Rect menuBtn = {Config::SCREEN_WIDTH - 60, 3, 54, 22};
Rect menuBtn = {Config::SCREEN_WIDTH - 90, 3, 54, 22};
Rect camBtn = {(int16_t)(menuBtn.x - 46), 3, 42, 22};
void drawMenuButton() {
tft.fillRoundRect(menuBtn.x, menuBtn.y, menuBtn.w, menuBtn.h, 4, TFT_NAVY);
tft.drawRoundRect(menuBtn.x, menuBtn.y, menuBtn.w, menuBtn.h, 4, TFT_DARKGREY);
tft.fillRoundRect(menuBtn.x, menuBtn.y, menuBtn.w, menuBtn.h, 4, TFT_BLACK);
tft.drawRoundRect(menuBtn.x, menuBtn.y, menuBtn.w, menuBtn.h, 4, TFT_GREEN);
tft.setTextDatum(MC_DATUM);
tft.setTextColor(TFT_WHITE, TFT_NAVY);
tft.setTextColor(TFT_GREEN, TFT_BLACK);
tft.drawString("Menu", menuBtn.x + menuBtn.w / 2, menuBtn.y + menuBtn.h / 2);
tft.setTextDatum(TL_DATUM);
}
void drawCamButton() {
tft.fillRoundRect(camBtn.x, camBtn.y, camBtn.w, camBtn.h, 4, TFT_NAVY);
tft.drawRoundRect(camBtn.x, camBtn.y, camBtn.w, camBtn.h, 4, TFT_DARKGREY);
tft.fillRoundRect(camBtn.x, camBtn.y, camBtn.w, camBtn.h, 4, TFT_BLACK);
tft.drawRoundRect(camBtn.x, camBtn.y, camBtn.w, camBtn.h, 4, TFT_GREEN);
tft.setTextDatum(MC_DATUM);
tft.setTextColor(TFT_WHITE, TFT_NAVY);
tft.setTextColor(TFT_GREEN, TFT_BLACK);
tft.drawString("Cam", camBtn.x + camBtn.w / 2, camBtn.y + camBtn.h / 2);
tft.setTextDatum(TL_DATUM);
}
void drawWifiIcon(int16_t rightX, int16_t rowTop, int16_t rowH, int8_t rssi) {
uint8_t level;
if (rssi >= -55) level = 4;
else if (rssi >= -65) level = 3;
else if (rssi >= -75) level = 2;
else if (rssi >= -85) level = 1;
else level = 0;
constexpr uint8_t BAR_W = 3;
constexpr uint8_t BAR_GAP = 2;
constexpr uint8_t BAR_COUNT = 4;
int16_t baseline = rowTop + rowH - 1;
int16_t x = rightX - BAR_COUNT * (BAR_W + BAR_GAP);
for (uint8_t i = 0; i < BAR_COUNT; i++) {
int16_t barH = 3 + i * 2;
uint16_t color = (i < level) ? TFT_GREEN : TFT_DARKGREY;
tft.fillRect(x, baseline - barH, BAR_W, barH, color);
x += BAR_W + BAR_GAP;
}
}
constexpr int16_t WIFI_ICON_W = 22;
constexpr int16_t WIFI_ICON_H = 14;
void updateWifiIcon() {
int16_t iconX = Config::SCREEN_WIDTH - WIFI_ICON_W - 2;
int16_t iconY = 2;
tft.fillRect(iconX, iconY, WIFI_ICON_W, WIFI_ICON_H, TFT_BLACK);
if (WiFi.status() == WL_CONNECTED) {
drawWifiIcon(Config::SCREEN_WIDTH - 4, iconY, WIFI_ICON_H, WiFi.RSSI());
}
}
void drawHeader() {
tft.fillRect(0, 0, Config::SCREEN_WIDTH, CONTENT_TOP, TFT_BLACK);
tft.setTextColor(TFT_WHITE, TFT_BLACK);
tft.setTextSize(1);
tft.setCursor(6, 10);
tft.println("Eiswolfs Flightradar");
tft.println("Eiswolfs FR");
drawCamButton();
drawMenuButton();
updateWifiIcon();
}
void updateStatusLine() {
@@ -106,13 +142,7 @@ void updateStatusLine() {
tft.print(timeBuf);
}
if (WiFi.status() == WL_CONNECTED) {
char rssiBuf[14];
snprintf(rssiBuf, sizeof(rssiBuf), "WiFi %ddBm", WiFi.RSSI());
tft.setTextDatum(TR_DATUM);
tft.drawString(rssiBuf, Config::SCREEN_WIDTH - 4, HEADER_TITLE_H + 2);
tft.setTextDatum(TL_DATUM);
}
updateWifiIcon();
}
void takeScreenshotWithFeedback() {
@@ -183,6 +213,7 @@ void setup() {
tft.init();
tft.setRotation(0);
tft.setFreeFont(&UiFont11pt);
tft.invertDisplay(true);
tft.fillScreen(TFT_BLACK);
@@ -200,10 +231,6 @@ void setup() {
}
SdStorage::seedDefaultDataFiles();
// "Allererster Start" erkennen: config.txt existiert noch nicht (wird
// erst beim ersten SettingsStore::save()-Aufruf angelegt). Muss VOR
// SettingsStore::load() geprueft werden, da load() die Datei nicht
// erzeugt, nur liest.
bool isFirstRun = !SD.exists(Config::SD_SETTINGS_FILE);
SettingsStore::load();
@@ -239,9 +266,6 @@ void setup() {
}
}
// --- Sprachauswahl: NUR beim allerersten Start, direkt nach Touch-
// Kalibrierung und WLAN-Ersteinrichtung, bevor irgendwelche weiteren
// Texte in (falscher) Standardsprache gezeigt werden. ---
if (isFirstRun) {
FirstRunLanguageScreen::run(tft);
SplashScreen::begin(tft);

View File

@@ -23,20 +23,33 @@ namespace {
}
};
constexpr int16_t ROW_H = 26;
constexpr int16_t ROW_GAP = 3;
constexpr int16_t ROW_START_Y = 20;
constexpr int16_t ROW_H = 24;
constexpr int16_t ROW_GAP = 2;
constexpr int16_t ROW_START_Y = 18;
Rect rowRect(uint8_t index) {
return {10, (int16_t)(ROW_START_Y + index * (ROW_H + ROW_GAP)),
(int16_t)(Config::SCREEN_WIDTH - 20), ROW_H};
}
void drawButton(TFT_eSPI& tft, const Rect& r, const String& label, uint16_t bg = TFT_NAVY) {
constexpr int16_t CAT_ROW_H = 50;
constexpr int16_t CAT_ROW_GAP = 10;
constexpr int16_t CAT_START_Y = 30;
Rect catRowRect(uint8_t index) {
return {10, (int16_t)(CAT_START_Y + index * (CAT_ROW_H + CAT_ROW_GAP)),
(int16_t)(Config::SCREEN_WIDTH - 20), CAT_ROW_H};
}
void drawButton(TFT_eSPI& tft, const Rect& r, const String& label,
bool active = false, bool danger = false) {
uint16_t accent = danger ? TFT_RED : TFT_GREEN;
uint16_t bg = active ? accent : TFT_BLACK;
uint16_t fg = active ? TFT_BLACK : accent;
tft.fillRoundRect(r.x, r.y, r.w, r.h, 4, bg);
tft.drawRoundRect(r.x, r.y, r.w, r.h, 4, TFT_DARKGREY);
tft.drawRoundRect(r.x, r.y, r.w, r.h, 4, accent);
tft.setTextDatum(MC_DATUM);
tft.setTextColor(TFT_WHITE, bg);
tft.setTextColor(fg, bg);
tft.drawString(label, r.x + r.w / 2, r.y + r.h / 2);
tft.setTextDatum(TL_DATUM);
}
@@ -57,28 +70,113 @@ namespace {
tft.setTextDatum(TL_DATUM);
delay(1200);
}
enum class Page { Main, Region, Wifi, System, Flight };
}
void run(TFT_eSPI& tft) {
uint8_t page = 0;
Page page = Page::Main;
bool done = false;
while (!done) {
tft.fillScreen(TFT_BLACK);
tft.setTextColor(TFT_WHITE, TFT_BLACK);
tft.setCursor(10, 2);
tft.println(page == 0 ? I18n::t(StringId::MENU_SETTINGS) : I18n::t(StringId::MENU_SETTINGS_PAGE2));
if (page == 0) {
Rect calibBtn = rowRect(0);
Rect invertBtn = rowRect(1);
Rect wifiBtn = rowRect(2);
Rect locationBtn = rowRect(3);
Rect timeoutBtn = rowRect(4);
Rect statsBtn = rowRect(5);
Rect logFilesBtn = rowRect(6);
Rect moreBtn = rowRect(7);
Rect backBtn = rowRect(8);
if (page == Page::Main) {
tft.setTextColor(TFT_GREEN, TFT_BLACK);
tft.setCursor(10, 6);
tft.println(I18n::t(StringId::MENU_SETTINGS));
Rect regionBtn = catRowRect(0);
Rect wifiBtn = catRowRect(1);
Rect systemBtn = catRowRect(2);
Rect flightBtn = catRowRect(3);
Rect backBtn = catRowRect(4);
drawButton(tft, regionBtn, I18n::t(StringId::MENU_CATEGORY_REGION));
drawButton(tft, wifiBtn, I18n::t(StringId::MENU_CATEGORY_WIFI));
drawButton(tft, systemBtn, I18n::t(StringId::MENU_CATEGORY_SYSTEM));
drawButton(tft, flightBtn, I18n::t(StringId::MENU_CATEGORY_FLIGHT));
drawButton(tft, backBtn, I18n::t(StringId::BACK));
TouchInput::Point tap;
while (true) {
if (TouchInput::wasTapped(tap)) break;
delay(20);
}
if (regionBtn.contains(tap.x, tap.y)) {
page = Page::Region;
} else if (wifiBtn.contains(tap.x, tap.y)) {
page = Page::Wifi;
} else if (systemBtn.contains(tap.x, tap.y)) {
page = Page::System;
} else if (flightBtn.contains(tap.x, tap.y)) {
page = Page::Flight;
} else if (backBtn.contains(tap.x, tap.y)) {
done = true;
}
} else if (page == Page::Region) {
tft.setTextColor(TFT_GREEN, TFT_BLACK);
tft.setCursor(10, 2);
tft.println(I18n::t(StringId::MENU_CATEGORY_REGION));
Rect languageBtn = rowRect(0);
Rect unitsBtn = rowRect(1);
Rect backBtn = rowRect(2);
drawButton(tft, languageBtn, String(I18n::t(StringId::MENU_LANGUAGE)) + ": " + I18n::languageName(SettingsStore::language()));
drawButton(tft, unitsBtn, I18n::t(StringId::MENU_UNITS));
drawButton(tft, backBtn, I18n::t(StringId::BACK_ARROW));
TouchInput::Point tap;
while (true) {
if (TouchInput::wasTapped(tap)) break;
delay(20);
}
if (languageBtn.contains(tap.x, tap.y)) {
LanguageScreen::run(tft);
} else if (unitsBtn.contains(tap.x, tap.y)) {
UnitsScreen::run(tft);
} else if (backBtn.contains(tap.x, tap.y)) {
page = Page::Main;
}
} else if (page == Page::Wifi) {
tft.setTextColor(TFT_GREEN, TFT_BLACK);
tft.setCursor(10, 2);
tft.println(I18n::t(StringId::MENU_CATEGORY_WIFI));
Rect wifiManageBtn = rowRect(0);
Rect backBtn = rowRect(1);
drawButton(tft, wifiManageBtn, I18n::t(StringId::MENU_MANAGE_WIFI));
drawButton(tft, backBtn, I18n::t(StringId::BACK_ARROW));
TouchInput::Point tap;
while (true) {
if (TouchInput::wasTapped(tap)) break;
delay(20);
}
if (wifiManageBtn.contains(tap.x, tap.y)) {
WifiManageScreen::run(tft);
} else if (backBtn.contains(tap.x, tap.y)) {
page = Page::Main;
}
} else if (page == Page::System) {
tft.setTextColor(TFT_GREEN, TFT_BLACK);
tft.setCursor(10, 2);
tft.println(I18n::t(StringId::MENU_CATEGORY_SYSTEM));
Rect calibBtn = rowRect(0);
Rect invertBtn = rowRect(1);
Rect timeoutBtn = rowRect(2);
Rect backupBtn = rowRect(3);
Rect restoreBtn = rowRect(4);
Rect backBtn = rowRect(5);
drawButton(tft, calibBtn, I18n::t(StringId::MENU_CALIBRATE));
@@ -87,13 +185,10 @@ void run(TFT_eSPI& tft) {
: I18n::t(StringId::MENU_DISPLAY_NORMAL);
drawButton(tft, invertBtn, invertLabel);
drawButton(tft, wifiBtn, I18n::t(StringId::MENU_MANAGE_WIFI));
drawButton(tft, locationBtn, I18n::t(StringId::MENU_LOCATION_PRESETS));
drawButton(tft, timeoutBtn, screenTimeoutLabel(SettingsStore::screenTimeoutMinutes()));
drawButton(tft, statsBtn, I18n::t(StringId::MENU_STATISTICS));
drawButton(tft, logFilesBtn, I18n::t(StringId::MENU_LOGBOOK_FILES));
drawButton(tft, moreBtn, I18n::t(StringId::MENU_MORE_SETTINGS));
drawButton(tft, backBtn, I18n::t(StringId::BACK));
drawButton(tft, backupBtn, I18n::t(StringId::MENU_BACKUP));
drawButton(tft, restoreBtn, I18n::t(StringId::MENU_RESTORE));
drawButton(tft, backBtn, I18n::t(StringId::BACK_ARROW));
TouchInput::Point tap;
while (true) {
@@ -107,89 +202,10 @@ void run(TFT_eSPI& tft) {
bool newState = !SettingsStore::displayInverted();
SettingsStore::setDisplayInverted(newState);
tft.invertDisplay(newState);
} else if (wifiBtn.contains(tap.x, tap.y)) {
WifiManageScreen::run(tft);
} else if (locationBtn.contains(tap.x, tap.y)) {
LocationPresetsScreen::run(tft);
} else if (timeoutBtn.contains(tap.x, tap.y)) {
uint8_t current = SettingsStore::screenTimeoutMinutes();
uint8_t next = (current >= 10) ? 0 : (current + 1);
SettingsStore::setScreenTimeoutMinutes(next);
} else if (statsBtn.contains(tap.x, tap.y)) {
StatsScreen::run(tft);
} else if (logFilesBtn.contains(tap.x, tap.y)) {
LogbookFilesScreen::run(tft);
} else if (moreBtn.contains(tap.x, tap.y)) {
page = 1;
} else if (backBtn.contains(tap.x, tap.y)) {
done = true;
}
} else if (page == 1) {
Rect emergencyBtn = rowRect(0);
Rect proximityBtn = rowRect(1);
Rect heartbeatBtn = rowRect(2);
Rect logbookBtn = rowRect(3);
Rect groundBtn = rowRect(4);
Rect airlineBtn = rowRect(5);
Rect moreBtn = rowRect(6);
Rect backBtn = rowRect(7);
drawButton(tft, emergencyBtn, I18n::t(StringId::MENU_EMERGENCY_ALERT) + onOff(SettingsStore::emergencyAlertEnabled()));
drawButton(tft, proximityBtn, I18n::t(StringId::MENU_PROXIMITY_LED) + onOff(SettingsStore::proximityAlertEnabled()));
drawButton(tft, heartbeatBtn, I18n::t(StringId::MENU_LED_HEARTBEAT) + onOff(SettingsStore::ledHeartbeatEnabled()));
drawButton(tft, logbookBtn, I18n::t(StringId::MENU_FLIGHT_LOGBOOK) + onOff(SettingsStore::flightLogbookEnabled()));
drawButton(tft, groundBtn, I18n::t(StringId::MENU_HIDE_GROUND) + onOff(SettingsStore::hideGroundVehicles()));
drawButton(tft, airlineBtn, I18n::t(StringId::MENU_AIRLINE_FILTER));
drawButton(tft, moreBtn, I18n::t(StringId::MENU_MORE_SETTINGS));
drawButton(tft, backBtn, I18n::t(StringId::BACK_ARROW));
TouchInput::Point tap;
while (true) {
if (TouchInput::wasTapped(tap)) break;
delay(20);
}
if (emergencyBtn.contains(tap.x, tap.y)) {
SettingsStore::setEmergencyAlertEnabled(!SettingsStore::emergencyAlertEnabled());
} else if (proximityBtn.contains(tap.x, tap.y)) {
SettingsStore::setProximityAlertEnabled(!SettingsStore::proximityAlertEnabled());
} else if (heartbeatBtn.contains(tap.x, tap.y)) {
SettingsStore::setLedHeartbeatEnabled(!SettingsStore::ledHeartbeatEnabled());
} else if (logbookBtn.contains(tap.x, tap.y)) {
SettingsStore::setFlightLogbookEnabled(!SettingsStore::flightLogbookEnabled());
} else if (groundBtn.contains(tap.x, tap.y)) {
SettingsStore::setHideGroundVehicles(!SettingsStore::hideGroundVehicles());
} else if (airlineBtn.contains(tap.x, tap.y)) {
AirlineFilterScreen::run(tft);
} else if (moreBtn.contains(tap.x, tap.y)) {
page = 2;
} else if (backBtn.contains(tap.x, tap.y)) {
page = 0;
}
} else {
Rect languageBtn = rowRect(0);
Rect unitsBtn = rowRect(1);
Rect backupBtn = rowRect(2);
Rect restoreBtn = rowRect(3);
Rect backBtn = rowRect(4);
drawButton(tft, languageBtn, String(I18n::t(StringId::MENU_LANGUAGE)) + ": " + I18n::languageName(SettingsStore::language()));
drawButton(tft, unitsBtn, I18n::t(StringId::MENU_UNITS));
drawButton(tft, backupBtn, I18n::t(StringId::MENU_BACKUP));
drawButton(tft, restoreBtn, I18n::t(StringId::MENU_RESTORE),
SettingsBackup::hasBackup() ? TFT_NAVY : TFT_DARKGREY);
drawButton(tft, backBtn, I18n::t(StringId::BACK_ARROW));
TouchInput::Point tap;
while (true) {
if (TouchInput::wasTapped(tap)) break;
delay(20);
}
if (languageBtn.contains(tap.x, tap.y)) {
LanguageScreen::run(tft);
} else if (unitsBtn.contains(tap.x, tap.y)) {
UnitsScreen::run(tft);
} else if (backupBtn.contains(tap.x, tap.y)) {
bool ok = SettingsBackup::backup();
showBriefMessage(tft, I18n::t(ok ? StringId::MENU_BACKUP_SAVED : StringId::MENU_BACKUP_FAILED),
@@ -201,7 +217,62 @@ void run(TFT_eSPI& tft) {
ok ? TFT_GREEN : TFT_RED);
}
} else if (backBtn.contains(tap.x, tap.y)) {
page = 1;
page = Page::Main;
}
} else { // Page::Flight
tft.setTextColor(TFT_GREEN, TFT_BLACK);
tft.setCursor(10, 2);
tft.println(I18n::t(StringId::MENU_CATEGORY_FLIGHT));
Rect statsBtn = rowRect(0);
Rect logFilesBtn = rowRect(1);
Rect logbookBtn = rowRect(2);
Rect heartbeatBtn = rowRect(3);
Rect proximityBtn = rowRect(4);
Rect emergencyBtn = rowRect(5);
Rect locationBtn = rowRect(6);
Rect airlineBtn = rowRect(7);
Rect groundBtn = rowRect(8);
Rect backBtn = rowRect(9);
drawButton(tft, statsBtn, I18n::t(StringId::MENU_STATISTICS));
drawButton(tft, logFilesBtn, I18n::t(StringId::MENU_LOGBOOK_FILES));
drawButton(tft, logbookBtn, I18n::t(StringId::MENU_FLIGHT_LOGBOOK) + onOff(SettingsStore::flightLogbookEnabled()));
drawButton(tft, heartbeatBtn, I18n::t(StringId::MENU_LED_HEARTBEAT) + onOff(SettingsStore::ledHeartbeatEnabled()));
drawButton(tft, proximityBtn, I18n::t(StringId::MENU_PROXIMITY_LED) + onOff(SettingsStore::proximityAlertEnabled()));
drawButton(tft, emergencyBtn, I18n::t(StringId::MENU_EMERGENCY_ALERT) + onOff(SettingsStore::emergencyAlertEnabled()));
drawButton(tft, locationBtn, I18n::t(StringId::MENU_LOCATION_PRESETS));
drawButton(tft, airlineBtn, I18n::t(StringId::MENU_AIRLINE_FILTER));
drawButton(tft, groundBtn, I18n::t(StringId::MENU_HIDE_GROUND) + onOff(SettingsStore::hideGroundVehicles()));
drawButton(tft, backBtn, I18n::t(StringId::BACK_ARROW));
TouchInput::Point tap;
while (true) {
if (TouchInput::wasTapped(tap)) break;
delay(20);
}
if (statsBtn.contains(tap.x, tap.y)) {
StatsScreen::run(tft);
} else if (logFilesBtn.contains(tap.x, tap.y)) {
LogbookFilesScreen::run(tft);
} else if (logbookBtn.contains(tap.x, tap.y)) {
SettingsStore::setFlightLogbookEnabled(!SettingsStore::flightLogbookEnabled());
} else if (heartbeatBtn.contains(tap.x, tap.y)) {
SettingsStore::setLedHeartbeatEnabled(!SettingsStore::ledHeartbeatEnabled());
} else if (proximityBtn.contains(tap.x, tap.y)) {
SettingsStore::setProximityAlertEnabled(!SettingsStore::proximityAlertEnabled());
} else if (emergencyBtn.contains(tap.x, tap.y)) {
SettingsStore::setEmergencyAlertEnabled(!SettingsStore::emergencyAlertEnabled());
} else if (locationBtn.contains(tap.x, tap.y)) {
LocationPresetsScreen::run(tft);
} else if (airlineBtn.contains(tap.x, tap.y)) {
AirlineFilterScreen::run(tft);
} else if (groundBtn.contains(tap.x, tap.y)) {
SettingsStore::setHideGroundVehicles(!SettingsStore::hideGroundVehicles());
} else if (backBtn.contains(tap.x, tap.y)) {
page = Page::Main;
}
}
}

View File

@@ -30,7 +30,7 @@ namespace {
int16_t infoTop;
};
constexpr int16_t INFO_BAR_H = 50;
constexpr int16_t INFO_BAR_H = 64;
Layout computeLayout(int16_t top) {
Layout L;
@@ -45,7 +45,7 @@ namespace {
return L;
}
constexpr int16_t DETAIL_PANEL_H = 190;
constexpr int16_t DETAIL_PANEL_H = 264;
struct HitPoint {
int16_t x, y;
@@ -177,10 +177,10 @@ namespace {
}
void drawButton(TFT_eSPI& gfx, const Rect& r, const String& label) {
gfx.fillRoundRect(r.x, r.y, r.w, r.h, 4, TFT_NAVY);
gfx.drawRoundRect(r.x, r.y, r.w, r.h, 4, TFT_DARKGREY);
gfx.fillRoundRect(r.x, r.y, r.w, r.h, 4, TFT_BLACK);
gfx.drawRoundRect(r.x, r.y, r.w, r.h, 4, TFT_GREEN);
gfx.setTextDatum(MC_DATUM);
gfx.setTextColor(TFT_WHITE, TFT_NAVY);
gfx.setTextColor(TFT_GREEN, TFT_BLACK);
gfx.drawString(label, r.x + r.w / 2, r.y + r.h / 2);
gfx.setTextDatum(TL_DATUM);
}
@@ -210,7 +210,7 @@ namespace {
gfx.setTextColor(TFT_WHITE, TFT_BLACK);
for (uint8_t i = 0; i < 3; i++) {
int16_t x0 = i * segW + 6;
gfx.fillCircle(x0, y + 4, 3, items[i].color);
gfx.fillCircle(x0, y - 5, 3, items[i].color);
gfx.setCursor(x0 + 7, y);
gfx.print(items[i].label);
}
@@ -279,8 +279,8 @@ namespace {
void updateLine(TFT_eSPI& gfx, int16_t y, int16_t h, int16_t maxWidth,
uint16_t fg, String& cached, const String& newText, bool forceFull) {
if (!forceFull && cached == newText) return;
gfx.fillRect(0, y - 2, Config::SCREEN_WIDTH, h, TFT_NAVY);
gfx.setTextColor(fg, TFT_NAVY);
gfx.fillRect(0, y - 14, Config::SCREEN_WIDTH, h, TFT_BLACK);
gfx.setTextColor(fg, TFT_BLACK);
printLineTruncated(gfx, 8, y, maxWidth, newText);
cached = newText;
}
@@ -291,54 +291,55 @@ namespace {
int16_t panelTop = Config::SCREEN_HEIGHT - DETAIL_PANEL_H;
constexpr int16_t textMaxWidth = Config::SCREEN_WIDTH - 16;
constexpr int16_t LINE_H = 22;
bool forceFull = !lastPanel.valid || strcmp(lastPanel.hex, a.hex) != 0;
if (forceFull) {
gfx.fillRect(0, panelTop, Config::SCREEN_WIDTH, DETAIL_PANEL_H, TFT_NAVY);
gfx.drawRect(0, panelTop, Config::SCREEN_WIDTH, DETAIL_PANEL_H, TFT_DARKGREY);
gfx.fillRect(0, panelTop, Config::SCREEN_WIDTH, DETAIL_PANEL_H, TFT_BLACK);
gfx.drawRect(0, panelTop, Config::SCREEN_WIDTH, DETAIL_PANEL_H, TFT_GREEN);
lastPanel = PanelState{};
strncpy(lastPanel.hex, a.hex, sizeof(lastPanel.hex) - 1);
}
int16_t y = panelTop + 6;
int16_t y = panelTop + 26;
{
String txt = a.callsign[0] ? a.callsign : a.hex;
if (forceFull || lastPanel.callsignText != txt) {
gfx.fillRect(0, y - 2, Config::SCREEN_WIDTH, 20, TFT_NAVY);
gfx.setTextColor(TFT_WHITE, TFT_NAVY);
gfx.fillRect(0, y - 22, Config::SCREEN_WIDTH, 28, TFT_BLACK);
gfx.setTextColor(TFT_GREEN, TFT_BLACK);
gfx.setTextSize(2);
printLineTruncated(gfx, 8, y, textMaxWidth, txt);
gfx.setTextSize(1);
lastPanel.callsignText = txt;
}
}
y += 22;
y += 30;
updateLine(gfx, y, 15, textMaxWidth, TFT_GREEN, lastPanel.airlineText,
updateLine(gfx, y, LINE_H, textMaxWidth, TFT_GREEN, lastPanel.airlineText,
String(a.airlineName), forceFull);
y += 15;
y += LINE_H;
String modelLine = details.loading
? String(I18n::t(StringId::DETAIL_MODEL)) + I18n::t(StringId::DETAIL_LOADING_DOTS)
: String(I18n::t(StringId::DETAIL_MODEL)) + (details.model[0] ? details.model : I18n::t(StringId::DETAIL_UNKNOWN));
updateLine(gfx, y, 15, textMaxWidth, TFT_WHITE, lastPanel.modelText, modelLine, forceFull);
y += 15;
updateLine(gfx, y, LINE_H, textMaxWidth, TFT_GREEN, lastPanel.modelText, modelLine, forceFull);
y += LINE_H;
String typeLine = String(I18n::t(StringId::DETAIL_TYPE)) + (a.typeCode[0] ? a.typeCode : I18n::t(StringId::DETAIL_UNKNOWN));
updateLine(gfx, y, 15, textMaxWidth, TFT_WHITE, lastPanel.typeText, typeLine, forceFull);
y += 15;
updateLine(gfx, y, LINE_H, textMaxWidth, TFT_GREEN, lastPanel.typeText, typeLine, forceFull);
y += LINE_H;
char buf[48];
snprintf(buf, sizeof(buf), "%s%.0fm / %.0fft", I18n::t(StringId::DETAIL_ALT),
Units::feetToMeters((float)a.altBaroFt), (float)a.altBaroFt);
updateLine(gfx, y, 15, textMaxWidth, TFT_WHITE, lastPanel.altText, String(buf), forceFull);
y += 15;
updateLine(gfx, y, LINE_H, textMaxWidth, TFT_GREEN, lastPanel.altText, String(buf), forceFull);
y += LINE_H;
snprintf(buf, sizeof(buf), "%s%.0fkm/h / %.0fkt", I18n::t(StringId::DETAIL_SPEED),
Units::ktToKmh(a.groundSpeedKt), a.groundSpeedKt);
updateLine(gfx, y, 15, textMaxWidth, TFT_WHITE, lastPanel.speedText, String(buf), forceFull);
y += 15;
updateLine(gfx, y, LINE_H, textMaxWidth, TFT_GREEN, lastPanel.speedText, String(buf), forceFull);
y += LINE_H;
String climbLine;
if (a.vertRateFtMin > 100) {
@@ -350,32 +351,66 @@ namespace {
} else {
climbLine = I18n::t(StringId::DETAIL_LEVEL);
}
updateLine(gfx, y, 15, textMaxWidth, TFT_WHITE, lastPanel.climbText, climbLine, forceFull);
y += 15;
updateLine(gfx, y, LINE_H, textMaxWidth, TFT_GREEN, lastPanel.climbText, climbLine, forceFull);
y += LINE_H;
snprintf(buf, sizeof(buf), "%s%.0fkm / %.0fnm %s%.0f", I18n::t(StringId::DETAIL_DIST),
a.distanceKm, Units::kmToNm(a.distanceKm), I18n::t(StringId::DETAIL_HDG), a.headingDeg);
updateLine(gfx, y, 15, textMaxWidth, TFT_WHITE, lastPanel.distHeadingText, String(buf), forceFull);
y += 15;
updateLine(gfx, y, LINE_H, textMaxWidth, TFT_GREEN, lastPanel.distHeadingText, String(buf), forceFull);
y += LINE_H;
String squawkLine = String(I18n::t(StringId::DETAIL_SQUAWK)) + (a.squawk[0] ? a.squawk : I18n::t(StringId::DETAIL_UNKNOWN));
updateLine(gfx, y, 15, textMaxWidth, TFT_WHITE, lastPanel.squawkText, squawkLine, forceFull);
y += 15;
updateLine(gfx, y, LINE_H, textMaxWidth, TFT_GREEN, lastPanel.squawkText, squawkLine, forceFull);
y += LINE_H;
String seatsLine = a.estSeats > 0
? String(I18n::t(StringId::DETAIL_SEATS_EST)) + a.estSeats
: String(I18n::t(StringId::DETAIL_SEATS_UNKNOWN));
updateLine(gfx, y, 15, textMaxWidth, TFT_WHITE, lastPanel.seatsText, seatsLine, forceFull);
y += 18;
updateLine(gfx, y, LINE_H, textMaxWidth, TFT_GREEN, lastPanel.seatsText, seatsLine, forceFull);
y += 22;
if (forceFull) {
gfx.setTextColor(TFT_DARKGREY, TFT_NAVY);
gfx.setTextColor(TFT_DARKGREEN, TFT_BLACK);
gfx.setCursor(8, y);
gfx.print(I18n::t(StringId::DETAIL_TAP_CLOSE));
}
lastPanel.valid = true;
}
constexpr uint8_t STAR_COUNT = 28;
struct Star {
int16_t x, y;
uint8_t phase;
uint8_t speed;
};
Star stars[STAR_COUNT];
bool starsInitialized = false;
void initStarsIfNeeded() {
if (starsInitialized) return;
int16_t panelTop = Config::SCREEN_HEIGHT - DETAIL_PANEL_H;
randomSeed((uint32_t)esp_random());
for (uint8_t i = 0; i < STAR_COUNT; i++) {
stars[i].x = 6 + random(0, Config::SCREEN_WIDTH - 12);
stars[i].y = panelTop + 10 + random(0, DETAIL_PANEL_H - 20);
stars[i].phase = (uint8_t)random(0, 256);
stars[i].speed = 1 + random(0, 3);
}
starsInitialized = true;
}
void updateDetailPanelStars(TFT_eSPI& gfx) {
initStarsIfNeeded();
for (uint8_t i = 0; i < STAR_COUNT; i++) {
stars[i].phase += stars[i].speed;
uint8_t bright = (stars[i].phase < 128)
? (uint8_t)(stars[i].phase * 2)
: (uint8_t)((255 - stars[i].phase) * 2);
uint16_t color = gfx.color565(0, bright, 0);
gfx.drawPixel(stars[i].x, stars[i].y, color);
}
}
}
void render(TFT_eSPI& tft, int16_t top) {
@@ -470,12 +505,6 @@ void render(TFT_eSPI& tft, int16_t top) {
tft.drawCircle(pt.x, pt.y, 12, TFT_RED);
}
// Rufzeichen als kleines farbiges Label ueber dem Punkt
// Transparent zeichnen (KEIN schwarzer Hintergrund-Kasten) - sonst
// "beisst" der Label-Hintergrund ein Stueck der Ring-/Fadenkreuz-
// Linie weg, wenn sich beides ueberschneidet. Da das Label bei jedem
// Sweep-Tick am selben Fleck neu gezeichnet wird, sah das wie
// Dauerflackern genau an den Beschriftungen aus.
tft.setTextColor(color);
tft.setTextDatum(BC_DATUM);
const char* label = a.callsign[0] ? a.callsign : a.hex;
@@ -506,21 +535,26 @@ void render(TFT_eSPI& tft, int16_t top) {
int16_t infoTop = L.infoTop;
tft.drawFastHLine(0, infoTop, Config::SCREEN_WIDTH, TFT_DARKGREY);
tft.setTextColor(TFT_WHITE, TFT_BLACK);
tft.setCursor(8, infoTop + 6);
tft.setCursor(8, infoTop + 20);
tft.print(I18n::t(StringId::RADAR_TAP_FOR_DETAILS));
char rangeLabel[8];
snprintf(rangeLabel, sizeof(rangeLabel), "%.0fkm", rangeKm);
drawButton(tft, L.rangeBtn, rangeLabel);
drawLegend(tft, infoTop + 30);
drawLegend(tft, infoTop + 44);
}
tft.endWrite();
}
void tick(TFT_eSPI& tft, int16_t top, uint32_t deltaMs) {
if (selectedHex[0]) return;
if (selectedHex[0]) {
tft.startWrite();
updateDetailPanelStars(tft);
tft.endWrite();
return;
}
Layout L = computeLayout(top);
float rangeKm = Config::RANGE_STEPS_KM[SettingsStore::rangeIndex()];

View File

@@ -14,17 +14,18 @@ namespace {
}
};
void drawButton(TFT_eSPI& tft, const Rect& r, const String& label, uint16_t bg = TFT_NAVY) {
tft.fillRoundRect(r.x, r.y, r.w, r.h, 4, bg);
tft.drawRoundRect(r.x, r.y, r.w, r.h, 4, TFT_DARKGREY);
void drawButton(TFT_eSPI& tft, const Rect& r, const String& label, bool danger = false) {
uint16_t accent = danger ? TFT_RED : TFT_GREEN;
tft.fillRoundRect(r.x, r.y, r.w, r.h, 4, TFT_BLACK);
tft.drawRoundRect(r.x, r.y, r.w, r.h, 4, accent);
tft.setTextDatum(MC_DATUM);
tft.setTextColor(TFT_WHITE, bg);
tft.setTextColor(accent, TFT_BLACK);
tft.drawString(label, r.x + r.w / 2, r.y + r.h / 2);
tft.setTextDatum(TL_DATUM);
}
void drawStatRow(TFT_eSPI& tft, int16_t y, const String& label, const String& value) {
tft.setTextColor(TFT_DARKGREY, TFT_BLACK);
tft.setTextColor(TFT_DARKGREEN, TFT_BLACK);
tft.setCursor(10, y);
tft.print(label);
tft.setTextColor(TFT_GREEN, TFT_BLACK);
@@ -39,10 +40,10 @@ namespace {
void run(TFT_eSPI& tft) {
tft.fillScreen(TFT_BLACK);
tft.setTextColor(TFT_WHITE, TFT_BLACK);
tft.setTextColor(TFT_GREEN, TFT_BLACK);
tft.setCursor(10, 10);
tft.println(I18n::t(StringId::STATS_TITLE));
tft.setTextColor(TFT_DARKGREY, TFT_BLACK);
tft.setTextColor(TFT_DARKGREEN, TFT_BLACK);
tft.setCursor(10, 44);
tft.print(I18n::t(StringId::LOADING));
@@ -60,7 +61,7 @@ void run(TFT_eSPI& tft) {
auto redraw = [&]() {
tft.fillScreen(TFT_BLACK);
tft.setTextColor(TFT_WHITE, TFT_BLACK);
tft.setTextColor(TFT_GREEN, TFT_BLACK);
tft.setCursor(10, 10);
tft.println(I18n::t(StringId::STATS_TITLE));
@@ -84,12 +85,12 @@ void run(TFT_eSPI& tft) {
uint32_t upM = (upSec % 3600) / 60;
char upBuf[8];
snprintf(upBuf, sizeof(upBuf), "%luh %lum", (unsigned long)upH, (unsigned long)upM);
tft.setTextColor(TFT_DARKGREY, TFT_BLACK);
tft.setTextColor(TFT_DARKGREEN, TFT_BLACK);
tft.setCursor(10, 210);
tft.print(String(I18n::t(StringId::STATS_UPTIME_PREFIX)) + upBuf);
if (confirmPending) {
drawButton(tft, resetBtn, I18n::t(StringId::STATS_RESET_CONFIRM), TFT_MAROON);
drawButton(tft, resetBtn, I18n::t(StringId::STATS_RESET_CONFIRM), true);
} else {
drawButton(tft, resetBtn, I18n::t(StringId::STATS_RESET_BTN));
}

461
src/ui_font.h Normal file
View File

@@ -0,0 +1,461 @@
#pragma once
// Automatisch generierter Font (DejaVu Sans Mono, 11px) fuer TFT_eSPI GFXFF.
// Deckt Basis-ASCII + Umlaute/Akzente fuer DE/TR/FR/ES/IT ab (U+0020-U+015F).
// NICHT von Hand bearbeiten - siehe Kommentar in i18n.h fuer den Generator-Hinweis.
#include <pgmspace.h>
const uint8_t UiFont11ptBitmaps[] PROGMEM = {
0x00, 0xFD, 0xB6, 0x80, 0x14, 0x49, 0xF9, 0x42, 0x9F, 0x92, 0x28, 0x23, 0xE9, 0x47, 0x14, 0xBE,
0x21, 0x00, 0xE2, 0x8E, 0x46, 0x23, 0x71, 0x47, 0x71, 0x04, 0x18, 0xB6, 0x58, 0x9F, 0xE0, 0x6A,
0xAA, 0x90, 0xA5, 0x55, 0xA0, 0x25, 0x5C, 0xEA, 0x90, 0x21, 0x3E, 0x42, 0x00, 0x58, 0xE0, 0xC0,
0x08, 0x84, 0x42, 0x11, 0x08, 0x80, 0x7B, 0x38, 0x65, 0x86, 0x1C, 0xDE, 0xE1, 0x08, 0x42, 0x10,
0x9F, 0x7A, 0x10, 0x43, 0x18, 0xC4, 0x3F, 0x7A, 0x10, 0x5E, 0x0C, 0x18, 0x5E, 0x18, 0x62, 0x92,
0xCB, 0xF0, 0x82, 0xFA, 0x08, 0x3E, 0x0C, 0x10, 0x7E, 0x3D, 0x08, 0x2E, 0xC6, 0x18, 0x5E, 0xFC,
0x20, 0x84, 0x10, 0x82, 0x10, 0x7A, 0x18, 0x5E, 0x86, 0x18, 0x5E, 0x7A, 0x18, 0x61, 0x7C, 0x10,
0xBC, 0xCC, 0x50, 0x58, 0x04, 0xEC, 0x1C, 0x0C, 0xFC, 0x0F, 0xC0, 0x81, 0xC0, 0xCE, 0xC0, 0xE1,
0x36, 0x44, 0x04, 0x39, 0x38, 0x67, 0xA6, 0x99, 0xF0, 0x40, 0xE0, 0x30, 0xC3, 0x12, 0x49, 0xE8,
0x61, 0xFA, 0x18, 0x7E, 0x86, 0x18, 0x7E, 0x39, 0x18, 0x20, 0x82, 0x04, 0x4E, 0xF2, 0x28, 0x61,
0x86, 0x18, 0xBC, 0xFE, 0x08, 0x3F, 0x82, 0x08, 0x3F, 0xFE, 0x08, 0x3F, 0x82, 0x08, 0x20, 0x39,
0x18, 0x20, 0x8E, 0x14, 0x4E, 0x86, 0x18, 0x7F, 0x86, 0x18, 0x61, 0xF9, 0x08, 0x42, 0x10, 0x9F,
0x38, 0x42, 0x10, 0x86, 0x2E, 0x8A, 0x4A, 0x30, 0xA2, 0x48, 0xA1, 0x82, 0x08, 0x20, 0x82, 0x08,
0x3F, 0x87, 0x3C, 0xED, 0xB6, 0x18, 0x61, 0x87, 0x1A, 0x69, 0x96, 0x58, 0xE1, 0x7B, 0x38, 0x61,
0x86, 0x1C, 0xDE, 0xFA, 0x18, 0x61, 0xFA, 0x08, 0x20, 0x7B, 0x38, 0x61, 0x86, 0x1C, 0xDE, 0x0C,
0xF9, 0x0A, 0x14, 0x2F, 0x91, 0x21, 0x41, 0x7A, 0x18, 0x3C, 0x0C, 0x18, 0x5E, 0xFE, 0x20, 0x40,
0x81, 0x02, 0x04, 0x08, 0x86, 0x18, 0x61, 0x86, 0x18, 0x5E, 0x86, 0x14, 0x92, 0x48, 0xC3, 0x0C,
0x83, 0x26, 0x4D, 0x56, 0xCD, 0x91, 0x22, 0x85, 0x24, 0x8C, 0x31, 0x24, 0xA1, 0xC6, 0x88, 0xA1,
0xC1, 0x02, 0x04, 0x08, 0xFC, 0x20, 0x84, 0x21, 0x84, 0x3F, 0xEA, 0xAA, 0xB0, 0x82, 0x10, 0x42,
0x10, 0x42, 0x08, 0xD5, 0x55, 0x70, 0x31, 0x28, 0x40, 0xFE, 0x44, 0xF0, 0x5F, 0x18, 0xBC, 0x84,
0x21, 0xE8, 0xC6, 0x31, 0xF0, 0x7E, 0x21, 0x0C, 0x3C, 0x08, 0x42, 0xF8, 0xC6, 0x31, 0x78, 0x74,
0x7F, 0x08, 0x3C, 0x19, 0x09, 0xF2, 0x10, 0x84, 0x20, 0x7C, 0x63, 0x18, 0xBC, 0x2E, 0x84, 0x21,
0x6C, 0xC6, 0x31, 0x88, 0x20, 0x01, 0xC2, 0x10, 0x84, 0xF8, 0x20, 0x72, 0x49, 0x27, 0x00, 0x84,
0x21, 0x2A, 0x62, 0x92, 0x88, 0xE1, 0x08, 0x42, 0x10, 0x84, 0x18, 0xFD, 0x6B, 0x5A, 0xD4, 0xB6,
0x63, 0x18, 0xC4, 0x74, 0x63, 0x18, 0xB8, 0xF4, 0x63, 0x18, 0xFA, 0x10, 0x7C, 0x63, 0x18, 0xBC,
0x21, 0xF9, 0x88, 0x88, 0x7C, 0x38, 0x30, 0xF8, 0x21, 0x3E, 0x42, 0x10, 0x87, 0x8C, 0x63, 0x18,
0xBC, 0x8C, 0x54, 0xA5, 0x10, 0x83, 0x05, 0x52, 0xA2, 0x85, 0x00, 0xDA, 0x88, 0x45, 0x6C, 0x8C,
0x94, 0xA6, 0x11, 0x18, 0xF8, 0x8C, 0xC4, 0x7C, 0x39, 0x08, 0x4C, 0x10, 0x84, 0x21, 0xC0, 0xFF,
0xE0, 0xE1, 0x08, 0x41, 0x90, 0x84, 0x27, 0x00, 0xE0, 0x70, 0xFE, 0x18, 0x61, 0x86, 0x18, 0x61,
0x87, 0xF0, 0xFE, 0x18, 0x61, 0x86, 0x18, 0x61, 0x87, 0xF0, 0xFE, 0x18, 0x61, 0x86, 0x18, 0x61,
0x87, 0xF0, 0xFE, 0x18, 0x61, 0x86, 0x18, 0x61, 0x87, 0xF0, 0xFE, 0x18, 0x61, 0x86, 0x18, 0x61,
0x87, 0xF0, 0xFE, 0x18, 0x61, 0x86, 0x18, 0x61, 0x87, 0xF0, 0xFE, 0x18, 0x61, 0x86, 0x18, 0x61,
0x87, 0xF0, 0xFE, 0x18, 0x61, 0x86, 0x18, 0x61, 0x87, 0xF0, 0xFE, 0x18, 0x61, 0x86, 0x18, 0x61,
0x87, 0xF0, 0xFE, 0x18, 0x61, 0x86, 0x18, 0x61, 0x87, 0xF0, 0xFE, 0x18, 0x61, 0x86, 0x18, 0x61,
0x87, 0xF0, 0xFE, 0x18, 0x61, 0x86, 0x18, 0x61, 0x87, 0xF0, 0xFE, 0x18, 0x61, 0x86, 0x18, 0x61,
0x87, 0xF0, 0xFE, 0x18, 0x61, 0x86, 0x18, 0x61, 0x87, 0xF0, 0xFE, 0x18, 0x61, 0x86, 0x18, 0x61,
0x87, 0xF0, 0xFE, 0x18, 0x61, 0x86, 0x18, 0x61, 0x87, 0xF0, 0xFE, 0x18, 0x61, 0x86, 0x18, 0x61,
0x87, 0xF0, 0xFE, 0x18, 0x61, 0x86, 0x18, 0x61, 0x87, 0xF0, 0xFE, 0x18, 0x61, 0x86, 0x18, 0x61,
0x87, 0xF0, 0xFE, 0x18, 0x61, 0x86, 0x18, 0x61, 0x87, 0xF0, 0xFE, 0x18, 0x61, 0x86, 0x18, 0x61,
0x87, 0xF0, 0xFE, 0x18, 0x61, 0x86, 0x18, 0x61, 0x87, 0xF0, 0xFE, 0x18, 0x61, 0x86, 0x18, 0x61,
0x87, 0xF0, 0xFE, 0x18, 0x61, 0x86, 0x18, 0x61, 0x87, 0xF0, 0xFE, 0x18, 0x61, 0x86, 0x18, 0x61,
0x87, 0xF0, 0xFE, 0x18, 0x61, 0x86, 0x18, 0x61, 0x87, 0xF0, 0xFE, 0x18, 0x61, 0x86, 0x18, 0x61,
0x87, 0xF0, 0xFE, 0x18, 0x61, 0x86, 0x18, 0x61, 0x87, 0xF0, 0xFE, 0x18, 0x61, 0x86, 0x18, 0x61,
0x87, 0xF0, 0xFE, 0x18, 0x61, 0x86, 0x18, 0x61, 0x87, 0xF0, 0xFE, 0x18, 0x61, 0x86, 0x18, 0x61,
0x87, 0xF0, 0xFE, 0x18, 0x61, 0x86, 0x18, 0x61, 0x87, 0xF0, 0xFE, 0x18, 0x61, 0x86, 0x18, 0x61,
0x87, 0xF0, 0x00, 0xBF, 0x10, 0x9F, 0xA9, 0x4B, 0x4F, 0x10, 0x80, 0x3A, 0x10, 0x8F, 0x21, 0x1F,
0x85, 0xE4, 0x92, 0x7A, 0x10, 0x82, 0x88, 0xA3, 0x61, 0x0F, 0x84, 0x08, 0xF7, 0x80, 0x7C, 0x19,
0x68, 0xB4, 0x41, 0xF0, 0xA0, 0x38, 0xBA, 0x8D, 0x19, 0xE8, 0x8E, 0x00, 0xFF, 0x9F, 0x0F, 0x25,
0x29, 0x12, 0x24, 0xFC, 0x10, 0x40, 0xE0, 0x38, 0xFA, 0xFD, 0x5A, 0x68, 0x8E, 0x00, 0xE0, 0xF7,
0x80, 0x21, 0x3E, 0x42, 0x03, 0xE0, 0xE5, 0x70, 0xE8, 0xF0, 0x50, 0x8A, 0x28, 0xA2, 0x8B, 0xF8,
0x20, 0x7F, 0xDF, 0x7D, 0x74, 0x51, 0x45, 0x14, 0xC0, 0x70, 0xC9, 0x70, 0x69, 0x96, 0x0F, 0x91,
0x22, 0x52, 0x90, 0x60, 0x82, 0x1C, 0x1F, 0x80, 0x8E, 0x3C, 0x20, 0x60, 0x82, 0x1C, 0x1F, 0x81,
0xC1, 0x08, 0x70, 0x70, 0x81, 0x18, 0x1F, 0x80, 0x8E, 0x3C, 0x20, 0x20, 0x22, 0x6C, 0x87, 0x40,
0x80, 0x0C, 0x30, 0xC4, 0x92, 0x7A, 0x18, 0x40, 0x08, 0x40, 0x0C, 0x30, 0xC4, 0x92, 0x7A, 0x18,
0x40, 0x31, 0x20, 0x0C, 0x30, 0xC4, 0x92, 0x7A, 0x18, 0x40, 0x69, 0x60, 0x0C, 0x30, 0xC4, 0x92,
0x7A, 0x18, 0x40, 0x48, 0x03, 0x0C, 0x31, 0x24, 0x9E, 0x86, 0x10, 0x30, 0xC2, 0x08, 0x30, 0xC4,
0x9E, 0x4A, 0x10, 0x3C, 0xC5, 0x17, 0x51, 0xC9, 0x27, 0x39, 0x18, 0x20, 0x82, 0x04, 0x4E, 0x20,
0x80, 0x40, 0x80, 0x3F, 0x82, 0x0F, 0xE0, 0x82, 0x0F, 0xC0, 0x08, 0x40, 0x3F, 0x82, 0x0F, 0xE0,
0x82, 0x0F, 0xC0, 0x21, 0x40, 0x3F, 0x82, 0x0F, 0xE0, 0x82, 0x0F, 0xC0, 0x50, 0x0F, 0xE0, 0x83,
0xF8, 0x20, 0x83, 0xF0, 0x41, 0x01, 0xF2, 0x10, 0x84, 0x21, 0x3E, 0x08, 0x81, 0xF2, 0x10, 0x84,
0x21, 0x3E, 0x22, 0x81, 0xF2, 0x10, 0x84, 0x21, 0x3E, 0x50, 0x3E, 0x42, 0x10, 0x84, 0x27, 0xC0,
0x78, 0x89, 0x0F, 0x14, 0x28, 0x51, 0x3C, 0x69, 0x60, 0x21, 0xC6, 0x9A, 0x65, 0x96, 0x38, 0x40,
0x40, 0x80, 0x1E, 0xCE, 0x18, 0x61, 0x87, 0x37, 0x80, 0x08, 0x40, 0x1E, 0xCE, 0x18, 0x61, 0x87,
0x37, 0x80, 0x31, 0x20, 0x1E, 0xCE, 0x18, 0x61, 0x87, 0x37, 0x80, 0x69, 0x60, 0x1E, 0xCE, 0x18,
0x61, 0x87, 0x37, 0x80, 0x48, 0x07, 0xB3, 0x86, 0x18, 0x61, 0xCD, 0xE0, 0x8A, 0x88, 0xA8, 0x80,
0x3E, 0xCD, 0x1A, 0x55, 0x2C, 0x59, 0xDE, 0x40, 0x80, 0x21, 0x86, 0x18, 0x61, 0x86, 0x17, 0x80,
0x08, 0x40, 0x21, 0x86, 0x18, 0x61, 0x86, 0x17, 0x80, 0x31, 0x20, 0x21, 0x86, 0x18, 0x61, 0x86,
0x17, 0x80, 0x48, 0x08, 0x61, 0x86, 0x18, 0x61, 0x85, 0xE0, 0x08, 0x20, 0x06, 0x34, 0x45, 0x0E,
0x08, 0x10, 0x20, 0x40, 0x83, 0xE8, 0x61, 0x87, 0xE8, 0x20, 0x64, 0xA5, 0x4A, 0x4A, 0x31, 0xB8,
0x20, 0x81, 0xE0, 0xBE, 0x31, 0x78, 0x11, 0x01, 0xE0, 0xBE, 0x31, 0x78, 0x22, 0x81, 0xE0, 0xBE,
0x31, 0x78, 0x6A, 0xC1, 0xE0, 0xBE, 0x31, 0x78, 0x50, 0x3C, 0x17, 0xC6, 0x2F, 0x72, 0x9C, 0x0F,
0x05, 0xF1, 0x8B, 0xC0, 0xD9, 0x7F, 0x4A, 0x6C, 0x7E, 0x21, 0x0C, 0x3C, 0x84, 0x20, 0x80, 0xE8,
0xFE, 0x10, 0x78, 0x18, 0x80, 0xE8, 0xFE, 0x10, 0x78, 0x22, 0x80, 0xE8, 0xFE, 0x10, 0x78, 0x50,
0x1D, 0x1F, 0xC2, 0x0F, 0x20, 0x81, 0xC2, 0x10, 0x84, 0xF8, 0x11, 0x01, 0xC2, 0x10, 0x84, 0xF8,
0x22, 0x81, 0xC2, 0x10, 0x84, 0xF8, 0x50, 0x38, 0x42, 0x10, 0x9F, 0x43, 0x84, 0xE8, 0xC6, 0x31,
0x70, 0x6A, 0xC1, 0x6C, 0xC6, 0x31, 0x88, 0x20, 0x80, 0xE8, 0xC6, 0x31, 0x70, 0x11, 0x00, 0xE8,
0xC6, 0x31, 0x70, 0x22, 0x80, 0xE8, 0xC6, 0x31, 0x70, 0xED, 0xC0, 0xE8, 0xC6, 0x31, 0x70, 0x50,
0x1D, 0x18, 0xC6, 0x2E, 0x20, 0x3E, 0x02, 0x00, 0x3D, 0x15, 0xD9, 0x65, 0xE0, 0x20, 0x81, 0x18,
0xC6, 0x31, 0x78, 0x11, 0x01, 0x18, 0xC6, 0x31, 0x78, 0x22, 0x81, 0x18, 0xC6, 0x31, 0x78, 0x50,
0x23, 0x18, 0xC6, 0x2F, 0x11, 0x01, 0x19, 0x29, 0x4C, 0x22, 0x30, 0x84, 0x21, 0xE8, 0xC6, 0x31,
0xF4, 0x20, 0x50, 0x23, 0x25, 0x29, 0x84, 0x46, 0x00, 0x78, 0x03, 0x0C, 0x31, 0x24, 0x9E, 0x86,
0x10, 0x70, 0x3C, 0x17, 0xC6, 0x2F, 0x48, 0xC0, 0x0C, 0x30, 0xC4, 0x92, 0x7A, 0x18, 0x40, 0x53,
0x81, 0xE0, 0xBE, 0x31, 0x78, 0x30, 0xC3, 0x12, 0x49, 0xE8, 0x61, 0x08, 0x20, 0xF0, 0x5F, 0x18,
0xBC, 0x43, 0x08, 0x20, 0x0E, 0x46, 0x08, 0x20, 0x81, 0x13, 0x80, 0x08, 0x80, 0xFC, 0x42, 0x18,
0x78, 0x30, 0xC0, 0x0E, 0x46, 0x08, 0x20, 0x81, 0x13, 0x80, 0x32, 0x80, 0xFC, 0x42, 0x18, 0x78,
0x20, 0x03, 0x91, 0x82, 0x08, 0x20, 0x44, 0xE0, 0x20, 0x1F, 0x88, 0x43, 0x0F, 0x50, 0xC0, 0x0E,
0x46, 0x08, 0x20, 0x81, 0x13, 0x80, 0x51, 0x80, 0xFC, 0x42, 0x18, 0x78, 0x61, 0x80, 0x3C, 0x8A,
0x18, 0x61, 0x86, 0x2F, 0x00, 0x0A, 0x14, 0x23, 0xC8, 0x91, 0x22, 0x44, 0x78, 0x78, 0x89, 0x0F,
0x14, 0x28, 0x51, 0x3C, 0x08, 0xF0, 0x9E, 0x8A, 0x28, 0xA2, 0x78, 0x70, 0x0F, 0xE0, 0x83, 0xF8,
0x20, 0x83, 0xF0, 0x70, 0x1D, 0x1F, 0xC2, 0x0F, 0x50, 0xC0, 0x3F, 0x82, 0x0F, 0xE0, 0x82, 0x0F,
0xC0, 0x51, 0x80, 0xE8, 0xFE, 0x10, 0x78, 0x20, 0x0F, 0xE0, 0x83, 0xF8, 0x20, 0x83, 0xF0, 0x20,
0x1D, 0x1F, 0xC2, 0x0F, 0xFE, 0x08, 0x3F, 0x82, 0x08, 0x3F, 0x10, 0x60, 0x74, 0x7F, 0x08, 0x3C,
0x86, 0x50, 0x80, 0x3F, 0x82, 0x0F, 0xE0, 0x82, 0x0F, 0xC0, 0x51, 0x00, 0xE8, 0xFE, 0x10, 0x78,
0x21, 0x40, 0x0E, 0x46, 0x08, 0x23, 0x85, 0x13, 0x80, 0x22, 0x80, 0xF8, 0xC6, 0x31, 0x78, 0x5C,
0x50, 0xC0, 0x0E, 0x46, 0x08, 0x23, 0x85, 0x13, 0x80, 0x53, 0x80, 0xF8, 0xC6, 0x31, 0x78, 0x5C,
0x20, 0x03, 0x91, 0x82, 0x08, 0xE1, 0x44, 0xE0, 0x20, 0x1F, 0x18, 0xC6, 0x2F, 0x0B, 0x80, 0x39,
0x18, 0x20, 0x8E, 0x14, 0x4E, 0x00, 0x82, 0x00, 0x21, 0x00, 0xF8, 0xC6, 0x31, 0x78, 0x5C, 0x31,
0x20, 0x21, 0x86, 0x1F, 0xE1, 0x86, 0x18, 0x40, 0x22, 0x81, 0x08, 0x42, 0xD9, 0x8C, 0x63, 0x10,
0x42, 0xFF, 0x42, 0x7E, 0x42, 0x42, 0x42, 0x42, 0x43, 0xC4, 0x1E, 0x45, 0x14, 0x51, 0x44, 0xED,
0xC1, 0xF2, 0x10, 0x84, 0x21, 0x3E, 0x6A, 0xC1, 0xC2, 0x10, 0x84, 0xF8, 0x70, 0x3E, 0x42, 0x10,
0x84, 0x27, 0xC0, 0x70, 0x38, 0x42, 0x10, 0x9F, 0x53, 0x81, 0xF2, 0x10, 0x84, 0x21, 0x3E, 0x53,
0x81, 0xC2, 0x10, 0x84, 0xF8, 0xF9, 0x08, 0x42, 0x10, 0x9F, 0x43, 0x00, 0x20, 0x01, 0xC2, 0x10,
0x84, 0xF9, 0x08, 0x20, 0x3E, 0x42, 0x10, 0x84, 0x27, 0xC0, 0xE1, 0x08, 0x42, 0x7C, 0xE6, 0x85,
0x0A, 0x14, 0x28, 0x50, 0xF7, 0x24, 0x00, 0x1F, 0x24, 0x92, 0x49, 0xFC, 0x11, 0xC0, 0x22, 0x80,
0x70, 0x84, 0x21, 0x0C, 0x5C, 0x25, 0x0E, 0x22, 0x22, 0x22, 0xC0, 0x8A, 0x4A, 0x30, 0xA2, 0x48,
0xA1, 0x00, 0x82, 0x00, 0x84, 0x21, 0x2A, 0x62, 0x92, 0x88, 0x0C, 0x40, 0x95, 0x31, 0x49, 0x44,
0x41, 0x00, 0x20, 0x82, 0x08, 0x20, 0x82, 0x0F, 0xC0, 0x08, 0x41, 0xC2, 0x10, 0x84, 0x21, 0x08,
0x30, 0x82, 0x08, 0x20, 0x82, 0x08, 0x3F, 0x00, 0x82, 0x00, 0xE1, 0x08, 0x42, 0x10, 0x84, 0x18,
0x0C, 0x40, 0x92, 0x48, 0x20, 0x82, 0x08, 0x3F, 0xE4, 0x92, 0x08, 0x20, 0x82, 0x08, 0x18, 0x82,
0x08, 0xA2, 0x82, 0x08, 0x3F, 0xE0, 0x40, 0x81, 0x12, 0x24, 0x08, 0x10, 0x18, 0x40, 0x81, 0x63,
0x0C, 0x08, 0x10, 0x3F, 0xE1, 0x0A, 0x66, 0x50, 0x84, 0x18, 0x08, 0x40, 0x21, 0xC6, 0x9A, 0x65,
0x96, 0x38, 0x40, 0x18, 0x81, 0x6C, 0xC6, 0x31, 0x88, 0x87, 0x1A, 0x69, 0x96, 0x58, 0xE1, 0x00,
0x84, 0x00, 0xB6, 0x63, 0x18, 0xC4, 0x04, 0x40, 0x50, 0x80, 0x21, 0xC6, 0x9A, 0x65, 0x96, 0x38,
0x40, 0x51, 0x01, 0x6C, 0xC6, 0x31, 0x88, 0x41, 0x02, 0x01, 0xE2, 0x64, 0x48, 0x91, 0x22, 0xBB,
0x18, 0x61, 0x86, 0x18, 0x61, 0x04, 0x60, 0xB6, 0x63, 0x18, 0xC4, 0x27, 0x78, 0x07, 0xB3, 0x86,
0x18, 0x61, 0xCD, 0xE0, 0x70, 0x1D, 0x18, 0xC6, 0x2E, 0x48, 0xC0, 0x1E, 0xCE, 0x18, 0x61, 0x87,
0x37, 0x80, 0x53, 0x80, 0xE8, 0xC6, 0x31, 0x70, 0x50, 0x07, 0xB3, 0x86, 0x18, 0x61, 0xCD, 0xE0,
0x2A, 0x80, 0xE8, 0xC6, 0x31, 0x70, 0x7E, 0x49, 0x27, 0x92, 0x49, 0x1F, 0xDD, 0x6F, 0x4A, 0x6C,
0x10, 0x20, 0x07, 0xC8, 0x50, 0xA1, 0x7C, 0x89, 0x0A, 0x08, 0x18, 0x81, 0xE9, 0x42, 0x10, 0x80,
0xF9, 0x0A, 0x14, 0x2F, 0x91, 0x21, 0x41, 0x00, 0x40, 0x80, 0x7A, 0x50, 0x84, 0x20, 0x08, 0xC0,
0x60, 0xC0, 0x07, 0xC8, 0x50, 0xA1, 0x7C, 0x89, 0x0A, 0x08, 0xA6, 0x0F, 0x98, 0x88, 0x80, 0x08,
0x40, 0x1E, 0x86, 0x0F, 0x03, 0x06, 0x17, 0x80, 0x18, 0x80, 0xF8, 0x70, 0x61, 0xF0, 0x21, 0x40,
0x1E, 0x86, 0x0F, 0x03, 0x06, 0x17, 0x80, 0x22, 0x80, 0xF8, 0x70, 0x61, 0xF0, 0x7A, 0x18, 0x3C,
0x0C, 0x18, 0x5E, 0x21, 0x80, 0x7C, 0x38, 0x30, 0xF8, 0x8C,
};
const GFXglyph UiFont11ptGlyphs[] PROGMEM = {
{ 0, 1, 1, 7, 0, -1 }, // 0x0020
{ 1, 1, 8, 7, 3, -8 }, // 0x0021
{ 2, 3, 3, 7, 2, -8 }, // 0x0022
{ 4, 7, 8, 7, 0, -8 }, // 0x0023
{ 11, 5, 10, 7, 1, -8 }, // 0x0024
{ 18, 6, 8, 7, 0, -8 }, // 0x0025
{ 24, 6, 8, 7, 1, -8 }, // 0x0026
{ 30, 1, 3, 7, 3, -8 }, // 0x0027
{ 31, 2, 10, 7, 2, -9 }, // 0x0028
{ 34, 2, 10, 7, 2, -9 }, // 0x0029
{ 37, 5, 6, 7, 1, -8 }, // 0x002A
{ 41, 5, 5, 7, 1, -6 }, // 0x002B
{ 45, 2, 3, 7, 2, -2 }, // 0x002C
{ 46, 3, 1, 7, 2, -4 }, // 0x002D
{ 47, 1, 2, 7, 3, -2 }, // 0x002E
{ 48, 5, 9, 7, 1, -8 }, // 0x002F
{ 54, 6, 8, 7, 1, -8 }, // 0x0030
{ 60, 5, 8, 7, 1, -8 }, // 0x0031
{ 65, 6, 8, 7, 1, -8 }, // 0x0032
{ 71, 6, 8, 7, 1, -8 }, // 0x0033
{ 77, 6, 8, 7, 1, -8 }, // 0x0034
{ 83, 6, 8, 7, 1, -8 }, // 0x0035
{ 89, 6, 8, 7, 1, -8 }, // 0x0036
{ 95, 6, 8, 7, 1, -8 }, // 0x0037
{ 101, 6, 8, 7, 1, -8 }, // 0x0038
{ 107, 6, 8, 7, 1, -8 }, // 0x0039
{ 113, 1, 6, 7, 3, -6 }, // 0x003A
{ 114, 2, 7, 7, 2, -6 }, // 0x003B
{ 116, 6, 5, 7, 1, -6 }, // 0x003C
{ 120, 6, 3, 7, 0, -5 }, // 0x003D
{ 123, 6, 5, 7, 1, -6 }, // 0x003E
{ 127, 4, 8, 7, 2, -8 }, // 0x003F
{ 131, 6, 10, 7, 1, -8 }, // 0x0040
{ 139, 6, 8, 7, 1, -8 }, // 0x0041
{ 145, 6, 8, 7, 1, -8 }, // 0x0042
{ 151, 6, 8, 7, 1, -8 }, // 0x0043
{ 157, 6, 8, 7, 1, -8 }, // 0x0044
{ 163, 6, 8, 7, 1, -8 }, // 0x0045
{ 169, 6, 8, 7, 1, -8 }, // 0x0046
{ 175, 6, 8, 7, 1, -8 }, // 0x0047
{ 181, 6, 8, 7, 1, -8 }, // 0x0048
{ 187, 5, 8, 7, 1, -8 }, // 0x0049
{ 192, 5, 8, 7, 1, -8 }, // 0x004A
{ 197, 6, 8, 7, 1, -8 }, // 0x004B
{ 203, 6, 8, 7, 1, -8 }, // 0x004C
{ 209, 6, 8, 7, 1, -8 }, // 0x004D
{ 215, 6, 8, 7, 1, -8 }, // 0x004E
{ 221, 6, 8, 7, 1, -8 }, // 0x004F
{ 227, 6, 8, 7, 1, -8 }, // 0x0050
{ 233, 6, 9, 7, 1, -8 }, // 0x0051
{ 240, 7, 8, 7, 1, -8 }, // 0x0052
{ 247, 6, 8, 7, 1, -8 }, // 0x0053
{ 253, 7, 8, 7, 0, -8 }, // 0x0054
{ 260, 6, 8, 7, 1, -8 }, // 0x0055
{ 266, 6, 8, 7, 1, -8 }, // 0x0056
{ 272, 7, 8, 7, 0, -8 }, // 0x0057
{ 279, 6, 8, 7, 1, -8 }, // 0x0058
{ 285, 7, 8, 7, 0, -8 }, // 0x0059
{ 292, 6, 8, 7, 1, -8 }, // 0x005A
{ 298, 2, 10, 7, 2, -9 }, // 0x005B
{ 301, 5, 9, 7, 1, -8 }, // 0x005C
{ 307, 2, 10, 7, 2, -9 }, // 0x005D
{ 310, 6, 3, 7, 0, -8 }, // 0x005E
{ 313, 7, 1, 7, 0, 2 }, // 0x005F
{ 314, 3, 2, 7, 2, -9 }, // 0x0060
{ 315, 5, 6, 7, 1, -6 }, // 0x0061
{ 319, 5, 9, 7, 1, -9 }, // 0x0062
{ 325, 5, 6, 7, 1, -6 }, // 0x0063
{ 329, 5, 9, 7, 1, -9 }, // 0x0064
{ 335, 5, 6, 7, 1, -6 }, // 0x0065
{ 339, 5, 9, 7, 1, -9 }, // 0x0066
{ 345, 5, 8, 7, 1, -6 }, // 0x0067
{ 350, 5, 9, 7, 1, -9 }, // 0x0068
{ 356, 5, 9, 7, 1, -9 }, // 0x0069
{ 362, 3, 11, 7, 1, -9 }, // 0x006A
{ 367, 5, 9, 7, 1, -9 }, // 0x006B
{ 373, 5, 9, 7, 0, -9 }, // 0x006C
{ 379, 5, 6, 7, 1, -6 }, // 0x006D
{ 383, 5, 6, 7, 1, -6 }, // 0x006E
{ 387, 5, 6, 7, 1, -6 }, // 0x006F
{ 391, 5, 8, 7, 1, -6 }, // 0x0070
{ 396, 5, 8, 7, 1, -6 }, // 0x0071
{ 401, 4, 6, 7, 2, -6 }, // 0x0072
{ 404, 5, 6, 7, 1, -6 }, // 0x0073
{ 408, 5, 8, 7, 0, -8 }, // 0x0074
{ 413, 5, 6, 7, 1, -6 }, // 0x0075
{ 417, 5, 6, 7, 1, -6 }, // 0x0076
{ 421, 7, 6, 7, 0, -6 }, // 0x0077
{ 427, 5, 6, 7, 1, -6 }, // 0x0078
{ 431, 5, 8, 7, 1, -6 }, // 0x0079
{ 436, 5, 6, 7, 1, -6 }, // 0x007A
{ 440, 5, 10, 7, 1, -9 }, // 0x007B
{ 447, 1, 11, 7, 3, -9 }, // 0x007C
{ 449, 5, 10, 7, 1, -9 }, // 0x007D
{ 456, 6, 2, 7, 1, -4 }, // 0x007E
{ 458, 6, 10, 7, 1, -8 }, // 0x007F
{ 466, 6, 10, 7, 1, -8 }, // 0x0080
{ 474, 6, 10, 7, 1, -8 }, // 0x0081
{ 482, 6, 10, 7, 1, -8 }, // 0x0082
{ 490, 6, 10, 7, 1, -8 }, // 0x0083
{ 498, 6, 10, 7, 1, -8 }, // 0x0084
{ 506, 6, 10, 7, 1, -8 }, // 0x0085
{ 514, 6, 10, 7, 1, -8 }, // 0x0086
{ 522, 6, 10, 7, 1, -8 }, // 0x0087
{ 530, 6, 10, 7, 1, -8 }, // 0x0088
{ 538, 6, 10, 7, 1, -8 }, // 0x0089
{ 546, 6, 10, 7, 1, -8 }, // 0x008A
{ 554, 6, 10, 7, 1, -8 }, // 0x008B
{ 562, 6, 10, 7, 1, -8 }, // 0x008C
{ 570, 6, 10, 7, 1, -8 }, // 0x008D
{ 578, 6, 10, 7, 1, -8 }, // 0x008E
{ 586, 6, 10, 7, 1, -8 }, // 0x008F
{ 594, 6, 10, 7, 1, -8 }, // 0x0090
{ 602, 6, 10, 7, 1, -8 }, // 0x0091
{ 610, 6, 10, 7, 1, -8 }, // 0x0092
{ 618, 6, 10, 7, 1, -8 }, // 0x0093
{ 626, 6, 10, 7, 1, -8 }, // 0x0094
{ 634, 6, 10, 7, 1, -8 }, // 0x0095
{ 642, 6, 10, 7, 1, -8 }, // 0x0096
{ 650, 6, 10, 7, 1, -8 }, // 0x0097
{ 658, 6, 10, 7, 1, -8 }, // 0x0098
{ 666, 6, 10, 7, 1, -8 }, // 0x0099
{ 674, 6, 10, 7, 1, -8 }, // 0x009A
{ 682, 6, 10, 7, 1, -8 }, // 0x009B
{ 690, 6, 10, 7, 1, -8 }, // 0x009C
{ 698, 6, 10, 7, 1, -8 }, // 0x009D
{ 706, 6, 10, 7, 1, -8 }, // 0x009E
{ 714, 6, 10, 7, 1, -8 }, // 0x009F
{ 722, 1, 1, 7, 0, -1 }, // 0x00A0
{ 723, 1, 8, 7, 3, -6 }, // 0x00A1
{ 724, 5, 10, 7, 1, -8 }, // 0x00A2
{ 731, 5, 8, 7, 1, -8 }, // 0x00A3
{ 736, 6, 6, 7, 1, -6 }, // 0x00A4
{ 741, 7, 8, 7, 0, -8 }, // 0x00A5
{ 748, 1, 9, 7, 3, -8 }, // 0x00A6
{ 750, 5, 9, 7, 1, -8 }, // 0x00A7
{ 756, 3, 1, 7, 2, -8 }, // 0x00A8
{ 757, 7, 7, 7, 0, -7 }, // 0x00A9
{ 764, 4, 6, 7, 1, -8 }, // 0x00AA
{ 767, 6, 5, 7, 1, -6 }, // 0x00AB
{ 771, 6, 3, 7, 1, -5 }, // 0x00AC
{ 774, 3, 1, 7, 2, -4 }, // 0x00AD
{ 775, 7, 7, 7, 0, -7 }, // 0x00AE
{ 782, 3, 1, 7, 2, -8 }, // 0x00AF
{ 783, 3, 3, 7, 2, -8 }, // 0x00B0
{ 785, 5, 7, 7, 1, -7 }, // 0x00B1
{ 790, 3, 4, 7, 2, -8 }, // 0x00B2
{ 792, 3, 4, 7, 2, -8 }, // 0x00B3
{ 794, 3, 2, 7, 3, -9 }, // 0x00B4
{ 795, 6, 8, 7, 1, -6 }, // 0x00B5
{ 801, 6, 9, 7, 1, -8 }, // 0x00B6
{ 808, 1, 2, 7, 3, -5 }, // 0x00B7
{ 809, 2, 2, 7, 2, 0 }, // 0x00B8
{ 810, 3, 4, 7, 2, -8 }, // 0x00B9
{ 812, 4, 6, 7, 1, -8 }, // 0x00BA
{ 815, 6, 5, 7, 1, -6 }, // 0x00BB
{ 819, 6, 10, 7, 0, -9 }, // 0x00BC
{ 827, 6, 10, 7, 0, -9 }, // 0x00BD
{ 835, 6, 10, 7, 0, -9 }, // 0x00BE
{ 843, 4, 8, 7, 1, -6 }, // 0x00BF
{ 847, 6, 11, 7, 1, -11 }, // 0x00C0
{ 856, 6, 11, 7, 1, -11 }, // 0x00C1
{ 865, 6, 11, 7, 1, -11 }, // 0x00C2
{ 874, 6, 11, 7, 1, -11 }, // 0x00C3
{ 883, 6, 10, 7, 1, -10 }, // 0x00C4
{ 891, 6, 10, 7, 1, -10 }, // 0x00C5
{ 899, 6, 8, 7, 0, -8 }, // 0x00C6
{ 905, 6, 10, 7, 1, -8 }, // 0x00C7
{ 913, 6, 11, 7, 1, -11 }, // 0x00C8
{ 922, 6, 11, 7, 1, -11 }, // 0x00C9
{ 931, 6, 11, 7, 1, -11 }, // 0x00CA
{ 940, 6, 10, 7, 1, -10 }, // 0x00CB
{ 948, 5, 11, 7, 1, -11 }, // 0x00CC
{ 955, 5, 11, 7, 1, -11 }, // 0x00CD
{ 962, 5, 11, 7, 1, -11 }, // 0x00CE
{ 969, 5, 10, 7, 1, -10 }, // 0x00CF
{ 976, 7, 8, 7, 0, -8 }, // 0x00D0
{ 983, 6, 11, 7, 1, -11 }, // 0x00D1
{ 992, 6, 11, 7, 1, -11 }, // 0x00D2
{ 1001, 6, 11, 7, 1, -11 }, // 0x00D3
{ 1010, 6, 11, 7, 1, -11 }, // 0x00D4
{ 1019, 6, 11, 7, 1, -11 }, // 0x00D5
{ 1028, 6, 10, 7, 1, -10 }, // 0x00D6
{ 1036, 5, 5, 7, 1, -6 }, // 0x00D7
{ 1040, 7, 8, 7, 0, -8 }, // 0x00D8
{ 1047, 6, 11, 7, 1, -11 }, // 0x00D9
{ 1056, 6, 11, 7, 1, -11 }, // 0x00DA
{ 1065, 6, 11, 7, 1, -11 }, // 0x00DB
{ 1074, 6, 10, 7, 1, -10 }, // 0x00DC
{ 1082, 7, 11, 7, 0, -11 }, // 0x00DD
{ 1092, 6, 8, 7, 1, -8 }, // 0x00DE
{ 1098, 5, 9, 7, 1, -9 }, // 0x00DF
{ 1104, 5, 9, 7, 1, -9 }, // 0x00E0
{ 1110, 5, 9, 7, 1, -9 }, // 0x00E1
{ 1116, 5, 9, 7, 1, -9 }, // 0x00E2
{ 1122, 5, 9, 7, 1, -9 }, // 0x00E3
{ 1128, 5, 8, 7, 1, -8 }, // 0x00E4
{ 1133, 5, 10, 7, 1, -10 }, // 0x00E5
{ 1140, 5, 6, 7, 1, -6 }, // 0x00E6
{ 1144, 5, 8, 7, 1, -6 }, // 0x00E7
{ 1149, 5, 9, 7, 1, -9 }, // 0x00E8
{ 1155, 5, 9, 7, 1, -9 }, // 0x00E9
{ 1161, 5, 9, 7, 1, -9 }, // 0x00EA
{ 1167, 5, 8, 7, 1, -8 }, // 0x00EB
{ 1172, 5, 9, 7, 1, -9 }, // 0x00EC
{ 1178, 5, 9, 7, 1, -9 }, // 0x00ED
{ 1184, 5, 9, 7, 1, -9 }, // 0x00EE
{ 1190, 5, 8, 7, 1, -8 }, // 0x00EF
{ 1195, 5, 9, 7, 1, -9 }, // 0x00F0
{ 1201, 5, 9, 7, 1, -9 }, // 0x00F1
{ 1207, 5, 9, 7, 1, -9 }, // 0x00F2
{ 1213, 5, 9, 7, 1, -9 }, // 0x00F3
{ 1219, 5, 9, 7, 1, -9 }, // 0x00F4
{ 1225, 5, 9, 7, 1, -9 }, // 0x00F5
{ 1231, 5, 8, 7, 1, -8 }, // 0x00F6
{ 1236, 5, 5, 7, 1, -6 }, // 0x00F7
{ 1240, 6, 6, 7, 0, -6 }, // 0x00F8
{ 1245, 5, 9, 7, 1, -9 }, // 0x00F9
{ 1251, 5, 9, 7, 1, -9 }, // 0x00FA
{ 1257, 5, 9, 7, 1, -9 }, // 0x00FB
{ 1263, 5, 8, 7, 1, -8 }, // 0x00FC
{ 1268, 5, 11, 7, 1, -9 }, // 0x00FD
{ 1275, 5, 11, 7, 1, -9 }, // 0x00FE
{ 1282, 5, 10, 7, 1, -8 }, // 0x00FF
{ 1289, 6, 10, 7, 1, -10 }, // 0x0100
{ 1297, 5, 8, 7, 1, -8 }, // 0x0101
{ 1302, 6, 11, 7, 1, -11 }, // 0x0102
{ 1311, 5, 9, 7, 1, -9 }, // 0x0103
{ 1317, 6, 10, 7, 1, -8 }, // 0x0104
{ 1325, 5, 8, 7, 1, -6 }, // 0x0105
{ 1330, 6, 11, 7, 1, -11 }, // 0x0106
{ 1339, 5, 9, 7, 1, -9 }, // 0x0107
{ 1345, 6, 11, 7, 1, -11 }, // 0x0108
{ 1354, 5, 9, 7, 1, -9 }, // 0x0109
{ 1360, 6, 10, 7, 1, -10 }, // 0x010A
{ 1368, 5, 8, 7, 1, -8 }, // 0x010B
{ 1373, 6, 11, 7, 1, -11 }, // 0x010C
{ 1382, 5, 9, 7, 1, -9 }, // 0x010D
{ 1388, 6, 11, 7, 1, -11 }, // 0x010E
{ 1397, 7, 9, 7, 1, -9 }, // 0x010F
{ 1405, 7, 8, 7, 0, -8 }, // 0x0110
{ 1412, 6, 9, 7, 1, -9 }, // 0x0111
{ 1419, 6, 10, 7, 1, -10 }, // 0x0112
{ 1427, 5, 8, 7, 1, -8 }, // 0x0113
{ 1432, 6, 11, 7, 1, -11 }, // 0x0114
{ 1441, 5, 9, 7, 1, -9 }, // 0x0115
{ 1447, 6, 10, 7, 1, -10 }, // 0x0116
{ 1455, 5, 8, 7, 1, -8 }, // 0x0117
{ 1460, 6, 10, 7, 1, -8 }, // 0x0118
{ 1468, 5, 8, 7, 1, -6 }, // 0x0119
{ 1473, 6, 11, 7, 1, -11 }, // 0x011A
{ 1482, 5, 9, 7, 1, -9 }, // 0x011B
{ 1488, 6, 11, 7, 1, -11 }, // 0x011C
{ 1497, 5, 11, 7, 1, -9 }, // 0x011D
{ 1504, 6, 11, 7, 1, -11 }, // 0x011E
{ 1513, 5, 11, 7, 1, -9 }, // 0x011F
{ 1520, 6, 10, 7, 1, -10 }, // 0x0120
{ 1528, 5, 10, 7, 1, -8 }, // 0x0121
{ 1535, 6, 11, 7, 1, -8 }, // 0x0122
{ 1544, 5, 11, 7, 1, -9 }, // 0x0123
{ 1551, 6, 11, 7, 1, -11 }, // 0x0124
{ 1560, 5, 12, 7, 1, -12 }, // 0x0125
{ 1568, 8, 8, 7, 0, -8 }, // 0x0126
{ 1576, 6, 9, 7, 0, -9 }, // 0x0127
{ 1583, 5, 11, 7, 1, -11 }, // 0x0128
{ 1590, 5, 9, 7, 1, -9 }, // 0x0129
{ 1596, 5, 10, 7, 1, -10 }, // 0x012A
{ 1603, 5, 8, 7, 1, -8 }, // 0x012B
{ 1608, 5, 11, 7, 1, -11 }, // 0x012C
{ 1615, 5, 9, 7, 1, -9 }, // 0x012D
{ 1621, 5, 10, 7, 1, -8 }, // 0x012E
{ 1628, 5, 11, 7, 1, -9 }, // 0x012F
{ 1635, 5, 10, 7, 1, -10 }, // 0x0130
{ 1642, 5, 6, 7, 1, -6 }, // 0x0131
{ 1646, 7, 8, 7, 0, -8 }, // 0x0132
{ 1653, 6, 11, 7, 0, -9 }, // 0x0133
{ 1662, 5, 11, 7, 1, -11 }, // 0x0134
{ 1669, 4, 11, 7, 1, -9 }, // 0x0135
{ 1675, 6, 11, 7, 1, -8 }, // 0x0136
{ 1684, 5, 12, 7, 1, -9 }, // 0x0137
{ 1692, 5, 6, 7, 1, -6 }, // 0x0138
{ 1696, 6, 11, 7, 1, -11 }, // 0x0139
{ 1705, 5, 12, 7, 0, -12 }, // 0x013A
{ 1713, 6, 11, 7, 1, -8 }, // 0x013B
{ 1722, 5, 12, 7, 0, -9 }, // 0x013C
{ 1730, 6, 8, 7, 1, -8 }, // 0x013D
{ 1736, 6, 9, 7, 0, -9 }, // 0x013E
{ 1743, 6, 8, 7, 1, -8 }, // 0x013F
{ 1749, 7, 9, 7, 0, -9 }, // 0x0140
{ 1757, 7, 8, 7, 0, -8 }, // 0x0141
{ 1764, 5, 9, 7, 0, -9 }, // 0x0142
{ 1770, 6, 11, 7, 1, -11 }, // 0x0143
{ 1779, 5, 9, 7, 1, -9 }, // 0x0144
{ 1785, 6, 11, 7, 1, -8 }, // 0x0145
{ 1794, 5, 9, 7, 1, -6 }, // 0x0146
{ 1800, 6, 11, 7, 1, -11 }, // 0x0147
{ 1809, 5, 9, 7, 1, -9 }, // 0x0148
{ 1815, 7, 9, 7, 0, -9 }, // 0x0149
{ 1823, 6, 10, 7, 1, -8 }, // 0x014A
{ 1831, 5, 8, 7, 1, -6 }, // 0x014B
{ 1836, 6, 10, 7, 1, -10 }, // 0x014C
{ 1844, 5, 8, 7, 1, -8 }, // 0x014D
{ 1849, 6, 11, 7, 1, -11 }, // 0x014E
{ 1858, 5, 9, 7, 1, -9 }, // 0x014F
{ 1864, 6, 10, 7, 1, -10 }, // 0x0150
{ 1872, 5, 9, 7, 1, -9 }, // 0x0151
{ 1878, 6, 8, 7, 1, -8 }, // 0x0152
{ 1884, 5, 6, 7, 1, -6 }, // 0x0153
{ 1888, 7, 11, 7, 1, -11 }, // 0x0154
{ 1898, 5, 9, 7, 2, -9 }, // 0x0155
{ 1904, 7, 11, 7, 1, -8 }, // 0x0156
{ 1914, 5, 9, 7, 1, -6 }, // 0x0157
{ 1920, 7, 11, 7, 1, -11 }, // 0x0158
{ 1930, 4, 9, 7, 2, -9 }, // 0x0159
{ 1935, 6, 11, 7, 1, -11 }, // 0x015A
{ 1944, 5, 9, 7, 1, -9 }, // 0x015B
{ 1950, 6, 11, 7, 1, -11 }, // 0x015C
{ 1959, 5, 9, 7, 1, -9 }, // 0x015D
{ 1965, 6, 10, 7, 1, -8 }, // 0x015E
{ 1973, 5, 8, 7, 1, -6 }, // 0x015F
};
const GFXfont UiFont11pt PROGMEM = {
(uint8_t *)UiFont11ptBitmaps,
(GFXglyph *)UiFont11ptGlyphs,
0x0020, 0x015F, 17
};

View File

@@ -14,11 +14,13 @@ namespace {
}
};
void drawButton(TFT_eSPI& tft, const Rect& r, const String& label, uint16_t bg = TFT_NAVY) {
void drawButton(TFT_eSPI& tft, const Rect& r, const String& label, bool active = false) {
uint16_t bg = active ? TFT_GREEN : TFT_BLACK;
uint16_t fg = active ? TFT_BLACK : TFT_GREEN;
tft.fillRoundRect(r.x, r.y, r.w, r.h, 4, bg);
tft.drawRoundRect(r.x, r.y, r.w, r.h, 4, TFT_DARKGREY);
tft.drawRoundRect(r.x, r.y, r.w, r.h, 4, TFT_GREEN);
tft.setTextDatum(MC_DATUM);
tft.setTextColor(TFT_WHITE, bg);
tft.setTextColor(fg, bg);
tft.drawString(label, r.x + r.w / 2, r.y + r.h / 2);
tft.setTextDatum(TL_DATUM);
}
@@ -40,7 +42,7 @@ void run(TFT_eSPI& tft) {
bool done = false;
while (!done) {
tft.fillScreen(TFT_BLACK);
tft.setTextColor(TFT_WHITE, TFT_BLACK);
tft.setTextColor(TFT_GREEN, TFT_BLACK);
tft.setCursor(10, 10);
tft.println(I18n::t(StringId::UNITS_TITLE));
@@ -49,8 +51,7 @@ void run(TFT_eSPI& tft) {
for (uint8_t i = 0; i < MODE_COUNT; i++) {
modeRects[i] = rowRect(i);
drawButton(tft, modeRects[i], I18n::t(labels[i]),
(i == current) ? TFT_DARKGREEN : TFT_NAVY);
drawButton(tft, modeRects[i], I18n::t(labels[i]), i == current);
}
Rect backBtn = rowRect(MODE_COUNT);

View File

@@ -3,6 +3,7 @@
#include "wifi_setup_screen.h"
#include "touch_input.h"
#include "config.h"
#include "i18n.h"
namespace WifiManageScreen {
@@ -14,11 +15,12 @@ namespace {
}
};
void drawButton(TFT_eSPI& tft, const Rect& r, const String& label, uint16_t bg = TFT_NAVY) {
tft.fillRoundRect(r.x, r.y, r.w, r.h, 4, bg);
tft.drawRoundRect(r.x, r.y, r.w, r.h, 4, TFT_DARKGREY);
void drawButton(TFT_eSPI& tft, const Rect& r, const String& label, bool danger = false) {
uint16_t accent = danger ? TFT_RED : TFT_GREEN;
tft.fillRoundRect(r.x, r.y, r.w, r.h, 4, TFT_BLACK);
tft.drawRoundRect(r.x, r.y, r.w, r.h, 4, accent);
tft.setTextDatum(MC_DATUM);
tft.setTextColor(TFT_WHITE, bg);
tft.setTextColor(accent, TFT_BLACK);
tft.drawString(label, r.x + r.w / 2, r.y + r.h / 2);
tft.setTextDatum(TL_DATUM);
}
@@ -32,9 +34,9 @@ void run(TFT_eSPI& tft) {
bool done = false;
while (!done) {
tft.fillScreen(TFT_BLACK);
tft.setTextColor(TFT_WHITE, TFT_BLACK);
tft.setTextColor(TFT_GREEN, TFT_BLACK);
tft.setCursor(10, 10);
tft.println("WiFi networks");
tft.println(I18n::t(StringId::WIFI_NETWORKS_TITLE));
uint8_t count = WifiMgr::networkCount();
int16_t y = 40;
@@ -50,13 +52,13 @@ void run(TFT_eSPI& tft) {
if (i < count) {
drawButton(tft, rowRect, WifiMgr::networkSsid(i));
drawButton(tft, removeRect, "X", TFT_MAROON);
drawButton(tft, removeRect, "X", true);
} else {
tft.fillRoundRect(rowRect.x, rowRect.y, rowRect.w, rowRect.h, 4, TFT_BLACK);
tft.drawRoundRect(rowRect.x, rowRect.y, rowRect.w, rowRect.h, 4, TFT_DARKGREY);
tft.drawRoundRect(rowRect.x, rowRect.y, rowRect.w, rowRect.h, 4, TFT_DARKGREEN);
tft.setTextDatum(MC_DATUM);
tft.setTextColor(TFT_DARKGREY, TFT_BLACK);
tft.drawString("(empty)", rowRect.x + rowRect.w / 2, rowRect.y + rowRect.h / 2);
tft.setTextColor(TFT_DARKGREEN, TFT_BLACK);
tft.drawString(I18n::t(StringId::WIFI_EMPTY_SLOT), rowRect.x + rowRect.w / 2, rowRect.y + rowRect.h / 2);
tft.setTextDatum(TL_DATUM);
}
y += ROW_H + ROW_GAP;
@@ -65,12 +67,12 @@ void run(TFT_eSPI& tft) {
Rect addBtn = {10, y, (int16_t)(Config::SCREEN_WIDTH - 20), 40};
bool canAdd = count < Config::MAX_WIFI_NETWORKS;
if (canAdd) {
drawButton(tft, addBtn, "Add network");
drawButton(tft, addBtn, I18n::t(StringId::WIFI_ADD_NETWORK));
}
y += 48;
Rect backBtn = {10, (int16_t)(Config::SCREEN_HEIGHT - 50), (int16_t)(Config::SCREEN_WIDTH - 20), 40};
drawButton(tft, backBtn, "Back");
drawButton(tft, backBtn, I18n::t(StringId::BACK));
TouchInput::Point tap;
while (true) {

View File

@@ -78,10 +78,6 @@ namespace {
char passwordBuf[64] = {0};
uint8_t passwordLen = 0;
// Shift-Verhalten wie am Handy: einmal antippen = NUR der naechste
// Buchstabe wird gross geschrieben, danach automatisch wieder klein.
// Zweimal schnell hintereinander antippen (Doppeltipp) = Feststelltaste
// (bleibt an, bis erneut einmal angetippt wird).
enum class ShiftState { Off, OneShot, Locked };
ShiftState shiftState = ShiftState::Off;
uint32_t lastShiftTapMs = 0;
@@ -95,11 +91,12 @@ namespace {
Rect cancelBtn = {Config::SCREEN_WIDTH - 34, 4, 30, 24};
void drawButton(TFT_eSPI& tft, const Rect& r, const String& label, bool highlighted = false) {
uint16_t bg = highlighted ? TFT_DARKGREEN : TFT_NAVY;
uint16_t bg = highlighted ? TFT_GREEN : TFT_BLACK;
uint16_t fg = highlighted ? TFT_BLACK : TFT_GREEN;
tft.fillRoundRect(r.x, r.y, r.w, r.h, 4, bg);
tft.drawRoundRect(r.x, r.y, r.w, r.h, 4, TFT_DARKGREY);
tft.drawRoundRect(r.x, r.y, r.w, r.h, 4, TFT_GREEN);
tft.setTextDatum(MC_DATUM);
tft.setTextColor(TFT_WHITE, bg);
tft.setTextColor(fg, bg);
tft.drawString(label, r.x + r.w / 2, r.y + r.h / 2);
tft.setTextDatum(TL_DATUM);
}
@@ -134,9 +131,6 @@ namespace {
bool upper = (shiftState != ShiftState::Off);
passwordBuf[passwordLen++] = upper ? (char)toupper(k.ch) : k.ch;
passwordBuf[passwordLen] = 0;
// Nach genau einem Buchstaben automatisch wieder auf
// klein zurueckfallen - AUSSER die Feststelltaste
// (Doppeltipp) ist aktiv, die bleibt an.
if (shiftState == ShiftState::OneShot) shiftState = ShiftState::Off;
}
break;
@@ -145,13 +139,10 @@ namespace {
bool isDoubleTap = (nowMs - lastShiftTapMs) <= SHIFT_DOUBLE_TAP_MS;
lastShiftTapMs = nowMs;
if (isDoubleTap) {
// Doppeltipp: Feststelltaste an-/ausschalten.
shiftState = (shiftState == ShiftState::Locked) ? ShiftState::Off : ShiftState::Locked;
} else if (shiftState == ShiftState::Off) {
// Einfacher Tipp: nur der naechste Buchstabe wird gross.
shiftState = ShiftState::OneShot;
} else {
// Einfacher Tipp waehrend OneShot/Locked schaltet ab.
shiftState = ShiftState::Off;
}
break;
@@ -208,7 +199,7 @@ bool run(TFT_eSPI& tft) {
WifiMgr::beginScan();
tft.fillScreen(TFT_BLACK);
tft.setTextColor(TFT_WHITE, TFT_BLACK);
tft.setTextColor(TFT_GREEN, TFT_BLACK);
tft.setTextSize(1);
tft.setCursor(10, 10);
tft.println(I18n::t(StringId::WIFI_SCANNING));
@@ -231,7 +222,7 @@ bool run(TFT_eSPI& tft) {
tft.fillScreen(TFT_BLACK);
tft.setCursor(10, 10);
tft.setTextColor(TFT_WHITE, TFT_BLACK);
tft.setTextColor(TFT_GREEN, TFT_BLACK);
tft.println(ssidCount == 0 ? I18n::t(StringId::WIFI_NO_NETWORKS) : I18n::t(StringId::WIFI_SELECT));
drawCancelButton(tft);
}
@@ -244,7 +235,7 @@ bool run(TFT_eSPI& tft) {
stage = Stage::Done;
tft.fillScreen(TFT_BLACK);
tft.setCursor(10, 10);
tft.setTextColor(TFT_WHITE, TFT_BLACK);
tft.setTextColor(TFT_GREEN, TFT_BLACK);
tft.println(I18n::t(StringId::WIFI_CONNECTED_BANG));
delay(900);
return true;
@@ -254,7 +245,7 @@ bool run(TFT_eSPI& tft) {
tft.setCursor(10, 10);
tft.setTextColor(TFT_RED, TFT_BLACK);
tft.println(I18n::t(StringId::WIFI_CONNECTION_FAILED));
tft.setTextColor(TFT_WHITE, TFT_BLACK);
tft.setTextColor(TFT_GREEN, TFT_BLACK);
tft.setCursor(10, 30);
tft.println(I18n::t(StringId::WIFI_BACK_TO_LIST_MSG));
delay(1400);
@@ -319,7 +310,7 @@ bool run(TFT_eSPI& tft) {
tft.setCursor(10, 10);
tft.setTextColor(TFT_RED, TFT_BLACK);
tft.println(I18n::t(StringId::WIFI_ALREADY_3));
tft.setTextColor(TFT_WHITE, TFT_BLACK);
tft.setTextColor(TFT_GREEN, TFT_BLACK);
tft.setCursor(10, 30);
tft.println(I18n::t(StringId::WIFI_REMOVE_ONE_FIRST));
delay(1600);
@@ -330,7 +321,7 @@ bool run(TFT_eSPI& tft) {
stage = Stage::Connecting;
tft.fillScreen(TFT_BLACK);
tft.setCursor(10, 10);
tft.setTextColor(TFT_WHITE, TFT_BLACK);
tft.setTextColor(TFT_GREEN, TFT_BLACK);
tft.println(I18n::t(StringId::WIFI_CONNECTING));
} else if (backBtn.contains(tap.x, tap.y)) {
stage = Stage::PickSsid;
@@ -363,16 +354,16 @@ bool run(TFT_eSPI& tft) {
drawCancelButton(tft);
} else if (stage == Stage::EnterPassword) {
tft.fillRect(0, 0, Config::SCREEN_WIDTH, KB_TOP - 4, TFT_BLACK);
tft.setTextColor(TFT_WHITE, TFT_BLACK);
tft.setTextColor(TFT_GREEN, TFT_BLACK);
tft.setCursor(10, 6);
tft.printf("%s%s", I18n::t(StringId::WIFI_LABEL_PREFIX), ssidList[selectedIndex].c_str());
tft.setCursor(10, 22);
tft.setTextColor(TFT_WHITE, TFT_BLACK);
tft.setTextColor(TFT_GREEN, TFT_BLACK);
tft.println(I18n::t(StringId::WIFI_PASSWORD_LABEL));
tft.fillRect(8, 38, Config::SCREEN_WIDTH - 16, 22, TFT_NAVY);
tft.drawRect(8, 38, Config::SCREEN_WIDTH - 16, 22, TFT_DARKGREY);
tft.fillRect(8, 38, Config::SCREEN_WIDTH - 16, 22, TFT_BLACK);
tft.drawRect(8, 38, Config::SCREEN_WIDTH - 16, 22, TFT_GREEN);
tft.setCursor(12, 44);
tft.setTextColor(TFT_WHITE, TFT_NAVY);
tft.setTextColor(TFT_GREEN, TFT_BLACK);
tft.print(passwordBuf);
int16_t rowY0 = KB_TOP;