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

@@ -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;
}
}
}