Add screen-timeout slider screen; move screensaver toggle into it
Replaces the repeated-tap timeout cycling (0-10, one tap per minute) with a dedicated screen and slider (1-15 minutes, then "Never"). The screensaver toggle moves from its own System-menu row into this screen, directly below the slider, since it only determines what happens when the timeout fires. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -15,6 +15,14 @@ namespace Config {
|
|||||||
constexpr uint8_t BRIGHTNESS_MAX_PERCENT = 100;
|
constexpr uint8_t BRIGHTNESS_MAX_PERCENT = 100;
|
||||||
constexpr uint8_t BRIGHTNESS_STEP_PERCENT = 10;
|
constexpr uint8_t BRIGHTNESS_STEP_PERCENT = 10;
|
||||||
|
|
||||||
|
// Bildschirm-Timeout (Menue > System > Bildschirm-Timeout), in Minuten,
|
||||||
|
// per Schieberegler einstellbar (siehe timeout_screen.cpp) - danach
|
||||||
|
// folgt "Nie" (kein Timeout) als eigene Endposition. Vorher nur per
|
||||||
|
// wiederholtem Antippen 0-10 durchklickbar (0 = Nie), was bei z.B. 10
|
||||||
|
// Minuten zehn einzelne Tipps brauchte.
|
||||||
|
constexpr uint8_t SCREEN_TIMEOUT_MIN_MINUTES = 1;
|
||||||
|
constexpr uint8_t SCREEN_TIMEOUT_MAX_MINUTES = 15;
|
||||||
|
|
||||||
// Nachtmodus (22-6 Uhr) dimmt relativ zur jeweils eingestellten normalen
|
// Nachtmodus (22-6 Uhr) dimmt relativ zur jeweils eingestellten normalen
|
||||||
// Helligkeit, nicht auf einen festen Absolutwert - sonst waere der
|
// Helligkeit, nicht auf einen festen Absolutwert - sonst waere der
|
||||||
// Dimm-Effekt bei niedrig eingestellter Normalhelligkeit wirkungslos
|
// Dimm-Effekt bei niedrig eingestellter Normalhelligkeit wirkungslos
|
||||||
|
|||||||
10
src/i18n.h
10
src/i18n.h
@@ -344,6 +344,16 @@ enum class StringId : uint8_t {
|
|||||||
OTA_RESTART_BUTTON,
|
OTA_RESTART_BUTTON,
|
||||||
OTA_FAILED_BODY,
|
OTA_FAILED_BODY,
|
||||||
|
|
||||||
|
// Eigener Bildschirm-Timeout-Screen (Menue > System > Bildschirm-
|
||||||
|
// Timeout, siehe timeout_screen.cpp) mit Schieberegler statt des
|
||||||
|
// vorherigen Durchklickens per wiederholtem Antippen - der
|
||||||
|
// Ruhebildschirm-Umschalter (vorher eigene Zeile im System-Menue,
|
||||||
|
// MENU_SCREENSAVER) zieht mit auf diesen Screen um, da er inhaltlich
|
||||||
|
// eng mit dem Timeout zusammenhaengt und hier genug Platz fuer eine
|
||||||
|
// kurze Erklaerung ist.
|
||||||
|
TIMEOUT_SCREEN_TITLE,
|
||||||
|
TIMEOUT_SCREENSAVER_DESC,
|
||||||
|
|
||||||
COUNT
|
COUNT
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -253,6 +253,8 @@ static const char* const I18N_DE[] = {
|
|||||||
"Die neue Firmware wurde erfolgreich installiert. Bitte jetzt neu starten, um sie zu verwenden.",
|
"Die neue Firmware wurde erfolgreich installiert. Bitte jetzt neu starten, um sie zu verwenden.",
|
||||||
"Jetzt neu starten",
|
"Jetzt neu starten",
|
||||||
"Das Update konnte nicht heruntergeladen oder installiert werden. Das Gerät läuft unverändert mit der bisherigen Firmware weiter. Bitte WLAN-Verbindung prüfen und es später erneut versuchen.",
|
"Das Update konnte nicht heruntergeladen oder installiert werden. Das Gerät läuft unverändert mit der bisherigen Firmware weiter. Bitte WLAN-Verbindung prüfen und es später erneut versuchen.",
|
||||||
|
"Bildschirm-Timeout",
|
||||||
|
"Nach dieser Zeit ohne Berührung schaltet sich das Display ab, um Strom zu sparen. Ist der Ruhebildschirm aktiviert, zeigt es stattdessen einen gedimmten Sternenhimmel mit Uhrzeit.",
|
||||||
};
|
};
|
||||||
|
|
||||||
static_assert(sizeof(I18N_DE) / sizeof(I18N_DE[0]) == (size_t)StringId::COUNT,
|
static_assert(sizeof(I18N_DE) / sizeof(I18N_DE[0]) == (size_t)StringId::COUNT,
|
||||||
|
|||||||
@@ -254,6 +254,8 @@ static const char* const I18N_EN[] = {
|
|||||||
"The new firmware was installed successfully. Please restart now to start using it.",
|
"The new firmware was installed successfully. Please restart now to start using it.",
|
||||||
"Restart now",
|
"Restart now",
|
||||||
"The update could not be downloaded or installed. The device is still running the previous firmware, unchanged. Please check your WiFi connection and try again later.",
|
"The update could not be downloaded or installed. The device is still running the previous firmware, unchanged. Please check your WiFi connection and try again later.",
|
||||||
|
"Screen Timeout",
|
||||||
|
"After this time without touch, the display turns off to save power. With the screensaver on, it dims to a starfield with a clock instead.",
|
||||||
};
|
};
|
||||||
|
|
||||||
static_assert(sizeof(I18N_EN) / sizeof(I18N_EN[0]) == (size_t)StringId::COUNT,
|
static_assert(sizeof(I18N_EN) / sizeof(I18N_EN[0]) == (size_t)StringId::COUNT,
|
||||||
|
|||||||
@@ -253,6 +253,8 @@ static const char* const I18N_ES[] = {
|
|||||||
"El nuevo firmware se instaló correctamente. Reinicia ahora para empezar a usarlo.",
|
"El nuevo firmware se instaló correctamente. Reinicia ahora para empezar a usarlo.",
|
||||||
"Reiniciar ahora",
|
"Reiniciar ahora",
|
||||||
"No se pudo descargar o instalar la actualización. El dispositivo sigue funcionando sin cambios con el firmware anterior. Comprueba tu conexión WiFi e inténtalo de nuevo más tarde.",
|
"No se pudo descargar o instalar la actualización. El dispositivo sigue funcionando sin cambios con el firmware anterior. Comprueba tu conexión WiFi e inténtalo de nuevo más tarde.",
|
||||||
|
"Tiempo de espera de pantalla",
|
||||||
|
"Tras este tiempo sin toques, la pantalla se apaga para ahorrar energía. Con el salvapantallas activado, muestra en su lugar un cielo estrellado atenuado con la hora.",
|
||||||
};
|
};
|
||||||
|
|
||||||
static_assert(sizeof(I18N_ES) / sizeof(I18N_ES[0]) == (size_t)StringId::COUNT,
|
static_assert(sizeof(I18N_ES) / sizeof(I18N_ES[0]) == (size_t)StringId::COUNT,
|
||||||
|
|||||||
@@ -253,6 +253,8 @@ static const char* const I18N_FR[] = {
|
|||||||
"Le nouveau micrologiciel a été installé avec succès. Veuillez redémarrer maintenant pour l'utiliser.",
|
"Le nouveau micrologiciel a été installé avec succès. Veuillez redémarrer maintenant pour l'utiliser.",
|
||||||
"Redémarrer maintenant",
|
"Redémarrer maintenant",
|
||||||
"La mise à jour n'a pas pu être téléchargée ou installée. L'appareil continue de fonctionner normalement avec le micrologiciel précédent. Merci de vérifier votre connexion WiFi et de réessayer plus tard.",
|
"La mise à jour n'a pas pu être téléchargée ou installée. L'appareil continue de fonctionner normalement avec le micrologiciel précédent. Merci de vérifier votre connexion WiFi et de réessayer plus tard.",
|
||||||
|
"Délai d'inactivité",
|
||||||
|
"Après ce délai sans contact, l'écran s'éteint pour économiser l'énergie. Avec l'écran de veille activé, il affiche à la place un ciel étoilé tamisé avec l'heure.",
|
||||||
};
|
};
|
||||||
|
|
||||||
static_assert(sizeof(I18N_FR) / sizeof(I18N_FR[0]) == (size_t)StringId::COUNT,
|
static_assert(sizeof(I18N_FR) / sizeof(I18N_FR[0]) == (size_t)StringId::COUNT,
|
||||||
|
|||||||
@@ -253,6 +253,8 @@ static const char* const I18N_IT[] = {
|
|||||||
"Il nuovo firmware è stato installato correttamente. Riavvia ora per iniziare a usarlo.",
|
"Il nuovo firmware è stato installato correttamente. Riavvia ora per iniziare a usarlo.",
|
||||||
"Riavvia ora",
|
"Riavvia ora",
|
||||||
"Non è stato possibile scaricare o installare l'aggiornamento. Il dispositivo continua a funzionare senza modifiche con il firmware precedente. Controlla la connessione WiFi e riprova più tardi.",
|
"Non è stato possibile scaricare o installare l'aggiornamento. Il dispositivo continua a funzionare senza modifiche con il firmware precedente. Controlla la connessione WiFi e riprova più tardi.",
|
||||||
|
"Timeout schermo",
|
||||||
|
"Dopo questo tempo senza tocchi, lo schermo si spegne per risparmiare energia. Con il salvaschermo attivo, mostra invece un cielo stellato attenuato con l'orario.",
|
||||||
};
|
};
|
||||||
|
|
||||||
static_assert(sizeof(I18N_IT) / sizeof(I18N_IT[0]) == (size_t)StringId::COUNT,
|
static_assert(sizeof(I18N_IT) / sizeof(I18N_IT[0]) == (size_t)StringId::COUNT,
|
||||||
|
|||||||
@@ -253,6 +253,8 @@ static const char* const I18N_TR[] = {
|
|||||||
"Yeni donanım yazılımı başarıyla yüklendi. Kullanmaya başlamak için lütfen şimdi yeniden başlatın.",
|
"Yeni donanım yazılımı başarıyla yüklendi. Kullanmaya başlamak için lütfen şimdi yeniden başlatın.",
|
||||||
"Şimdi yeniden başlat",
|
"Şimdi yeniden başlat",
|
||||||
"Güncelleme indirilemedi veya yüklenemedi. Cihaz değişmeden önceki donanım yazılımıyla çalışmaya devam ediyor. Lütfen WiFi bağlantınızı kontrol edip daha sonra tekrar deneyin.",
|
"Güncelleme indirilemedi veya yüklenemedi. Cihaz değişmeden önceki donanım yazılımıyla çalışmaya devam ediyor. Lütfen WiFi bağlantınızı kontrol edip daha sonra tekrar deneyin.",
|
||||||
|
"Ekran Zaman Aşımı",
|
||||||
|
"Bu süre boyunca dokunulmazsa ekran güç tasarrufu için kapanır. Ekran koruyucu açıksa, bunun yerine saatli, sönük bir yıldızlı gökyüzü gösterir.",
|
||||||
};
|
};
|
||||||
|
|
||||||
static_assert(sizeof(I18N_TR) / sizeof(I18N_TR[0]) == (size_t)StringId::COUNT,
|
static_assert(sizeof(I18N_TR) / sizeof(I18N_TR[0]) == (size_t)StringId::COUNT,
|
||||||
|
|||||||
@@ -13,6 +13,7 @@
|
|||||||
#include "aircraft_list_screen.h"
|
#include "aircraft_list_screen.h"
|
||||||
#include "about_screen.h"
|
#include "about_screen.h"
|
||||||
#include "brightness_screen.h"
|
#include "brightness_screen.h"
|
||||||
|
#include "timeout_screen.h"
|
||||||
#include "language_screen.h"
|
#include "language_screen.h"
|
||||||
#include "units_screen.h"
|
#include "units_screen.h"
|
||||||
#include "settings_backup.h"
|
#include "settings_backup.h"
|
||||||
@@ -86,8 +87,11 @@ namespace {
|
|||||||
// BACKUP_RESET_ROW_COUNT unten) und dort durch einen einzelnen
|
// BACKUP_RESET_ROW_COUNT unten) und dort durch einen einzelnen
|
||||||
// Ordner-Button ersetzt wurden: -2 (Backup/Restore raus) +1 (neuer
|
// Ordner-Button ersetzt wurden: -2 (Backup/Restore raus) +1 (neuer
|
||||||
// Ordner-Button) = 9. Danach +1 fuer den neuen "Nach Update
|
// Ordner-Button) = 9. Danach +1 fuer den neuen "Nach Update
|
||||||
// suchen"-Punkt (OTA-Update, siehe ota_update.h) = 10.
|
// suchen"-Punkt (OTA-Update, siehe ota_update.h) = 10. Der
|
||||||
constexpr uint8_t SYSTEM_ROW_COUNT = 11;
|
// Ruhebildschirm-Umschalter, der zwischenzeitlich hier eine eigene
|
||||||
|
// Zeile hatte, ist in den neuen Bildschirm-Timeout-Screen umgezogen
|
||||||
|
// (siehe timeout_screen.cpp) - Zeilenzahl bleibt dadurch bei 10.
|
||||||
|
constexpr uint8_t SYSTEM_ROW_COUNT = 10;
|
||||||
constexpr int16_t SYSTEM_ROW_GAP = 4;
|
constexpr int16_t SYSTEM_ROW_GAP = 4;
|
||||||
constexpr int16_t SYSTEM_START_Y = 18;
|
constexpr int16_t SYSTEM_START_Y = 18;
|
||||||
constexpr int16_t SYSTEM_END_Y = Config::SCREEN_HEIGHT - 10;
|
constexpr int16_t SYSTEM_END_Y = Config::SCREEN_HEIGHT - 10;
|
||||||
@@ -590,18 +594,17 @@ void run(TFT_eSPI& tft) {
|
|||||||
Rect brightnessBtn = systemRowRect(2);
|
Rect brightnessBtn = systemRowRect(2);
|
||||||
Rect timeoutBtn = systemRowRect(3);
|
Rect timeoutBtn = systemRowRect(3);
|
||||||
Rect nightDimBtn = systemRowRect(4);
|
Rect nightDimBtn = systemRowRect(4);
|
||||||
Rect screensaverBtn = systemRowRect(5);
|
Rect webuiBtn = systemRowRect(5);
|
||||||
Rect webuiBtn = systemRowRect(6);
|
|
||||||
// "Sicherung & Reset" (Backup/Restore/Werksreset, siehe
|
// "Sicherung & Reset" (Backup/Restore/Werksreset, siehe
|
||||||
// Page::BackupReset unten) und "Nach Update suchen" stehen
|
// Page::BackupReset unten) und "Nach Update suchen" stehen
|
||||||
// bewusst direkt ueber "Info", das seinerseits als letzter
|
// bewusst direkt ueber "Info", das seinerseits als letzter
|
||||||
// Punkt direkt ueber dem Zurueck-Button steht - so bleiben
|
// Punkt direkt ueber dem Zurueck-Button steht - so bleiben
|
||||||
// beide Positionen stabil, egal wie viele weitere Punkte davor
|
// beide Positionen stabil, egal wie viele weitere Punkte davor
|
||||||
// noch dazukommen.
|
// noch dazukommen.
|
||||||
Rect backupResetBtn = systemRowRect(7);
|
Rect backupResetBtn = systemRowRect(6);
|
||||||
Rect checkUpdateBtn = systemRowRect(8);
|
Rect checkUpdateBtn = systemRowRect(7);
|
||||||
Rect aboutBtn = systemRowRect(9);
|
Rect aboutBtn = systemRowRect(8);
|
||||||
Rect backBtn = systemRowRect(10);
|
Rect backBtn = systemRowRect(9);
|
||||||
|
|
||||||
drawButton(tft, calibBtn, I18n::t(StringId::MENU_CALIBRATE));
|
drawButton(tft, calibBtn, I18n::t(StringId::MENU_CALIBRATE));
|
||||||
|
|
||||||
@@ -613,7 +616,6 @@ void run(TFT_eSPI& tft) {
|
|||||||
drawButton(tft, brightnessBtn, brightnessLabel(SettingsStore::brightnessPercent()));
|
drawButton(tft, brightnessBtn, brightnessLabel(SettingsStore::brightnessPercent()));
|
||||||
drawButton(tft, timeoutBtn, screenTimeoutLabel(SettingsStore::screenTimeoutMinutes()));
|
drawButton(tft, timeoutBtn, screenTimeoutLabel(SettingsStore::screenTimeoutMinutes()));
|
||||||
drawButton(tft, nightDimBtn, I18n::t(StringId::MENU_NIGHT_DIMMING) + onOff(SettingsStore::nightDimmingEnabled()));
|
drawButton(tft, nightDimBtn, I18n::t(StringId::MENU_NIGHT_DIMMING) + onOff(SettingsStore::nightDimmingEnabled()));
|
||||||
drawButton(tft, screensaverBtn, I18n::t(StringId::MENU_SCREENSAVER) + onOff(SettingsStore::screensaverEnabled()));
|
|
||||||
drawButton(tft, webuiBtn, I18n::t(StringId::MENU_LOGBOOK_WEBUI));
|
drawButton(tft, webuiBtn, I18n::t(StringId::MENU_LOGBOOK_WEBUI));
|
||||||
drawButton(tft, backupResetBtn, I18n::t(StringId::MENU_BACKUP_RESET));
|
drawButton(tft, backupResetBtn, I18n::t(StringId::MENU_BACKUP_RESET));
|
||||||
drawButton(tft, checkUpdateBtn, I18n::t(StringId::MENU_CHECK_UPDATE));
|
drawButton(tft, checkUpdateBtn, I18n::t(StringId::MENU_CHECK_UPDATE));
|
||||||
@@ -636,13 +638,15 @@ void run(TFT_eSPI& tft) {
|
|||||||
} else if (brightnessBtn.contains(tap.x, tap.y)) {
|
} else if (brightnessBtn.contains(tap.x, tap.y)) {
|
||||||
BrightnessScreen::run(tft);
|
BrightnessScreen::run(tft);
|
||||||
} else if (timeoutBtn.contains(tap.x, tap.y)) {
|
} else if (timeoutBtn.contains(tap.x, tap.y)) {
|
||||||
uint8_t current = SettingsStore::screenTimeoutMinutes();
|
// Vorher: Durchklicken per wiederholtem Antippen (0-10, ein
|
||||||
uint8_t next = (current >= 10) ? 0 : (current + 1);
|
// Tipp pro Minute - bei z.B. 10 Minuten also zehn Tipps).
|
||||||
SettingsStore::setScreenTimeoutMinutes(next);
|
// Jetzt: eigener Screen mit Schieberegler, siehe
|
||||||
|
// timeout_screen.cpp - dort lebt jetzt auch der
|
||||||
|
// Ruhebildschirm-Umschalter (inhaltlich eng verwandt, und
|
||||||
|
// dort ist Platz fuer eine kurze Erklaerung).
|
||||||
|
TimeoutScreen::run(tft);
|
||||||
} else if (nightDimBtn.contains(tap.x, tap.y)) {
|
} else if (nightDimBtn.contains(tap.x, tap.y)) {
|
||||||
SettingsStore::setNightDimmingEnabled(!SettingsStore::nightDimmingEnabled());
|
SettingsStore::setNightDimmingEnabled(!SettingsStore::nightDimmingEnabled());
|
||||||
} else if (screensaverBtn.contains(tap.x, tap.y)) {
|
|
||||||
SettingsStore::setScreensaverEnabled(!SettingsStore::screensaverEnabled());
|
|
||||||
} else if (backupResetBtn.contains(tap.x, tap.y)) {
|
} else if (backupResetBtn.contains(tap.x, tap.y)) {
|
||||||
page = Page::BackupReset;
|
page = Page::BackupReset;
|
||||||
} else if (checkUpdateBtn.contains(tap.x, tap.y)) {
|
} else if (checkUpdateBtn.contains(tap.x, tap.y)) {
|
||||||
|
|||||||
@@ -57,7 +57,7 @@ namespace {
|
|||||||
ledHeartbeatOn = (value.toInt() != 0);
|
ledHeartbeatOn = (value.toInt() != 0);
|
||||||
} else if (key == "screen_timeout_min") {
|
} else if (key == "screen_timeout_min") {
|
||||||
int v = value.toInt();
|
int v = value.toInt();
|
||||||
if (v >= 0 && v <= 10) screenTimeoutMin = (uint8_t)v;
|
if (v >= 0 && v <= Config::SCREEN_TIMEOUT_MAX_MINUTES) screenTimeoutMin = (uint8_t)v;
|
||||||
} else if (key == "night_dimming") {
|
} else if (key == "night_dimming") {
|
||||||
nightDimmingOn = (value.toInt() != 0);
|
nightDimmingOn = (value.toInt() != 0);
|
||||||
} else if (key == "screensaver") {
|
} else if (key == "screensaver") {
|
||||||
@@ -210,7 +210,7 @@ void setLedHeartbeatEnabled(bool on) {
|
|||||||
uint8_t screenTimeoutMinutes() { return screenTimeoutMin; }
|
uint8_t screenTimeoutMinutes() { return screenTimeoutMin; }
|
||||||
|
|
||||||
void setScreenTimeoutMinutes(uint8_t minutes) {
|
void setScreenTimeoutMinutes(uint8_t minutes) {
|
||||||
if (minutes <= 10) {
|
if (minutes <= Config::SCREEN_TIMEOUT_MAX_MINUTES) {
|
||||||
screenTimeoutMin = minutes;
|
screenTimeoutMin = minutes;
|
||||||
save();
|
save();
|
||||||
}
|
}
|
||||||
|
|||||||
206
src/timeout_screen.cpp
Normal file
206
src/timeout_screen.cpp
Normal file
@@ -0,0 +1,206 @@
|
|||||||
|
#include "timeout_screen.h"
|
||||||
|
#include "settings_store.h"
|
||||||
|
#include "touch_input.h"
|
||||||
|
#include "menu_stars.h"
|
||||||
|
#include "config.h"
|
||||||
|
#include "i18n.h"
|
||||||
|
|
||||||
|
namespace TimeoutScreen {
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
struct Rect {
|
||||||
|
int16_t x, y, w, h;
|
||||||
|
bool contains(int16_t px, int16_t py) const {
|
||||||
|
return px >= x && px < x + w && py >= y && py < y + h;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
void drawButton(TFT_eSPI& tft, const Rect& r, const String& label) {
|
||||||
|
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_GREEN, TFT_BLACK);
|
||||||
|
tft.drawString(label, r.x + r.w / 2, r.y + r.h / 2);
|
||||||
|
tft.setTextDatum(TL_DATUM);
|
||||||
|
}
|
||||||
|
|
||||||
|
String onOff(bool on) { return I18n::t(on ? StringId::ON : StringId::OFF); }
|
||||||
|
|
||||||
|
// Einfacher Zeilenumbruch-Helfer, gleiches Prinzip wie in
|
||||||
|
// menu_screen.cpp (dort mit zusaetzlicher Scroll-Unterstuetzung, hier
|
||||||
|
// bewusst ohne - der Beschreibungstext ist kurz genug, dass Scrollen
|
||||||
|
// nicht noetig ist). draw=false liefert nur die Gesamthoehe, ohne etwas
|
||||||
|
// zu zeichnen (fuer die vorab-Platzberechnung des Zurueck-Buttons).
|
||||||
|
int16_t layoutWrapped(TFT_eSPI& tft, int16_t x, int16_t startY, int16_t maxWidth,
|
||||||
|
int16_t lineHeight, const String& text, bool draw) {
|
||||||
|
int16_t y = startY;
|
||||||
|
int32_t start = 0;
|
||||||
|
int32_t len = text.length();
|
||||||
|
while (start < len) {
|
||||||
|
while (start < len && text[start] == ' ') start++;
|
||||||
|
if (start >= len) break;
|
||||||
|
|
||||||
|
String line = text.substring(start, len);
|
||||||
|
while (tft.textWidth(line) > maxWidth) {
|
||||||
|
int32_t lastSpace = line.lastIndexOf(' ');
|
||||||
|
if (lastSpace <= 0) break;
|
||||||
|
line = line.substring(0, lastSpace);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (draw) {
|
||||||
|
tft.setCursor(x, y);
|
||||||
|
tft.print(line);
|
||||||
|
}
|
||||||
|
y += lineHeight;
|
||||||
|
start += line.length();
|
||||||
|
}
|
||||||
|
return (int16_t)(y - startY);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Schieberegler-Positionen: 1..Config::SCREEN_TIMEOUT_MAX_MINUTES
|
||||||
|
// Minuten (je eine Position pro Minute) plus eine zusaetzliche
|
||||||
|
// Endposition ganz rechts fuer "Nie" (kein Timeout) - intern weiterhin
|
||||||
|
// als 0 gespeichert, wie schon vor diesem Screen.
|
||||||
|
constexpr uint8_t STEP_COUNT = Config::SCREEN_TIMEOUT_MAX_MINUTES + 1;
|
||||||
|
|
||||||
|
uint8_t indexFromMinutes(uint8_t minutes) {
|
||||||
|
if (minutes == 0 || minutes > Config::SCREEN_TIMEOUT_MAX_MINUTES) return STEP_COUNT - 1;
|
||||||
|
return minutes - 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t minutesFromIndex(uint8_t index) {
|
||||||
|
if (index >= STEP_COUNT - 1) return 0;
|
||||||
|
return index + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
String valueLabel(uint8_t minutes) {
|
||||||
|
if (minutes == 0) return I18n::t(StringId::NEVER);
|
||||||
|
return String(minutes) + " min";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void run(TFT_eSPI& tft) {
|
||||||
|
bool done = false;
|
||||||
|
MenuStars::reset();
|
||||||
|
|
||||||
|
uint8_t index = indexFromMinutes(SettingsStore::screenTimeoutMinutes());
|
||||||
|
bool dragging = false;
|
||||||
|
|
||||||
|
constexpr int16_t TRACK_X = 20;
|
||||||
|
constexpr int16_t TRACK_Y = 95;
|
||||||
|
constexpr int16_t TRACK_W = Config::SCREEN_WIDTH - 2 * TRACK_X;
|
||||||
|
constexpr int16_t TRACK_H = 6;
|
||||||
|
constexpr int16_t THUMB_R = 12;
|
||||||
|
// Grosszuegige vertikale Trefferzone rund um den duennen Regler-Strich -
|
||||||
|
// sonst muesste man beim Ziehen extrem praezise genau auf die paar
|
||||||
|
// Pixel der Linie treffen.
|
||||||
|
constexpr int16_t TRACK_HIT_Y_MIN = TRACK_Y - 24;
|
||||||
|
constexpr int16_t TRACK_HIT_Y_MAX = TRACK_Y + 24;
|
||||||
|
|
||||||
|
Rect screensaverBtn = {10, 135, (int16_t)(Config::SCREEN_WIDTH - 20), 36};
|
||||||
|
|
||||||
|
// Beschreibungstext-Hoehe (haengt von der jeweils eingestellten Sprache
|
||||||
|
// ab) einmalig vorab messen, damit der Zurueck-Button IMMER direkt
|
||||||
|
// darunter landet - nie mit dem Text ueberlappend und nie unnoetig viel
|
||||||
|
// Leerraum lassend. Gleiches Prinzip wie bei confirmWarningScreen()/
|
||||||
|
// infoScreen() in menu_screen.cpp.
|
||||||
|
constexpr int16_t DESC_Y = 185;
|
||||||
|
constexpr int16_t DESC_LINE_H = 14;
|
||||||
|
constexpr int16_t DESC_MAX_WIDTH = Config::SCREEN_WIDTH - 20;
|
||||||
|
int16_t descH = layoutWrapped(tft, 10, DESC_Y, DESC_MAX_WIDTH, DESC_LINE_H,
|
||||||
|
I18n::t(StringId::TIMEOUT_SCREENSAVER_DESC), false);
|
||||||
|
int16_t backY = (int16_t)(DESC_Y + descH + 10);
|
||||||
|
// Sicherheitsnetz, falls eine Uebersetzung doch mal laenger ausfaellt,
|
||||||
|
// als der verfuegbare Platz hergibt - der Zurueck-Button bleibt so
|
||||||
|
// IMMER erreichbar, statt vom Bildschirmrand abgeschnitten zu werden.
|
||||||
|
constexpr int16_t BACK_Y_MAX = Config::SCREEN_HEIGHT - 46;
|
||||||
|
if (backY > BACK_Y_MAX) backY = BACK_Y_MAX;
|
||||||
|
Rect backBtn = {10, backY, (int16_t)(Config::SCREEN_WIDTH - 20), 36};
|
||||||
|
|
||||||
|
auto redraw = [&]() {
|
||||||
|
tft.fillScreen(TFT_BLACK);
|
||||||
|
tft.setTextColor(TFT_GREEN, TFT_BLACK);
|
||||||
|
tft.setCursor(10, 14);
|
||||||
|
tft.println(I18n::t(StringId::TIMEOUT_SCREEN_TITLE));
|
||||||
|
|
||||||
|
uint8_t minutes = minutesFromIndex(index);
|
||||||
|
tft.setTextDatum(MC_DATUM);
|
||||||
|
tft.setTextSize(3);
|
||||||
|
tft.drawString(valueLabel(minutes), Config::SCREEN_WIDTH / 2, 55);
|
||||||
|
tft.setTextSize(1);
|
||||||
|
tft.setTextDatum(TL_DATUM);
|
||||||
|
|
||||||
|
int16_t thumbX = (int16_t)(TRACK_X + (int32_t)index * TRACK_W / (STEP_COUNT - 1));
|
||||||
|
tft.fillRoundRect(TRACK_X, (int16_t)(TRACK_Y - TRACK_H / 2), TRACK_W, TRACK_H,
|
||||||
|
(int16_t)(TRACK_H / 2), TFT_DARKGREY);
|
||||||
|
if (thumbX > TRACK_X) {
|
||||||
|
tft.fillRoundRect(TRACK_X, (int16_t)(TRACK_Y - TRACK_H / 2), (int16_t)(thumbX - TRACK_X),
|
||||||
|
TRACK_H, (int16_t)(TRACK_H / 2), TFT_GREEN);
|
||||||
|
}
|
||||||
|
tft.fillCircle(thumbX, TRACK_Y, THUMB_R, TFT_GREEN);
|
||||||
|
tft.drawCircle(thumbX, TRACK_Y, THUMB_R, TFT_BLACK);
|
||||||
|
|
||||||
|
tft.setTextColor(TFT_GREEN, TFT_BLACK);
|
||||||
|
tft.setTextDatum(TL_DATUM);
|
||||||
|
tft.drawString(String(Config::SCREEN_TIMEOUT_MIN_MINUTES) + " min", TRACK_X,
|
||||||
|
(int16_t)(TRACK_Y + THUMB_R + 6));
|
||||||
|
tft.setTextDatum(TR_DATUM);
|
||||||
|
tft.drawString(I18n::t(StringId::NEVER), (int16_t)(TRACK_X + TRACK_W),
|
||||||
|
(int16_t)(TRACK_Y + THUMB_R + 6));
|
||||||
|
tft.setTextDatum(TL_DATUM);
|
||||||
|
|
||||||
|
drawButton(tft, screensaverBtn,
|
||||||
|
String(I18n::t(StringId::MENU_SCREENSAVER)) + onOff(SettingsStore::screensaverEnabled()));
|
||||||
|
|
||||||
|
tft.setTextColor(TFT_GREEN, TFT_BLACK);
|
||||||
|
layoutWrapped(tft, 10, DESC_Y, DESC_MAX_WIDTH, DESC_LINE_H,
|
||||||
|
I18n::t(StringId::TIMEOUT_SCREENSAVER_DESC), true);
|
||||||
|
|
||||||
|
drawButton(tft, backBtn, I18n::t(StringId::BACK));
|
||||||
|
};
|
||||||
|
|
||||||
|
redraw();
|
||||||
|
|
||||||
|
while (!done) {
|
||||||
|
TouchInput::Point live = TouchInput::mappedPoint();
|
||||||
|
if (live.touched && live.y >= TRACK_HIT_Y_MIN && live.y <= TRACK_HIT_Y_MAX) {
|
||||||
|
int16_t clampedX = live.x;
|
||||||
|
if (clampedX < TRACK_X) clampedX = TRACK_X;
|
||||||
|
if (clampedX > TRACK_X + TRACK_W) clampedX = (int16_t)(TRACK_X + TRACK_W);
|
||||||
|
uint8_t newIndex = (uint8_t)(((int32_t)(clampedX - TRACK_X) * (STEP_COUNT - 1) + TRACK_W / 2) / TRACK_W);
|
||||||
|
if (newIndex != index) {
|
||||||
|
index = newIndex;
|
||||||
|
redraw();
|
||||||
|
}
|
||||||
|
dragging = true;
|
||||||
|
} else if (dragging && !live.touched) {
|
||||||
|
// Loslassen: jetzt erst dauerhaft speichern (nicht bei jeder
|
||||||
|
// Zwischenposition waehrend des Ziehens selbst, um unnoetig
|
||||||
|
// viele SD-Kartenschreibvorgaenge zu vermeiden).
|
||||||
|
SettingsStore::setScreenTimeoutMinutes(minutesFromIndex(index));
|
||||||
|
dragging = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// wasTapped() IMMER aufrufen (auch waehrend des Ziehens), damit ihr
|
||||||
|
// interner Loslassen-Erkennungszustand synchron bleibt - nur die
|
||||||
|
// AUSWERTUNG des Ergebnisses wird waehrend des Ziehens unterdrueckt.
|
||||||
|
// Wuerde man den Aufruf selbst waehrend des Ziehens auslassen,
|
||||||
|
// koennte beim Loslassen faelschlich ein "Tap" mit einer veralteten
|
||||||
|
// Position (von VOR Beginn des Ziehens) gemeldet werden.
|
||||||
|
TouchInput::Point tap;
|
||||||
|
bool tapped = TouchInput::wasTapped(tap);
|
||||||
|
if (!dragging && tapped) {
|
||||||
|
if (screensaverBtn.contains(tap.x, tap.y)) {
|
||||||
|
SettingsStore::setScreensaverEnabled(!SettingsStore::screensaverEnabled());
|
||||||
|
redraw();
|
||||||
|
} else if (backBtn.contains(tap.x, tap.y)) {
|
||||||
|
done = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
MenuStars::update(tft);
|
||||||
|
delay(20);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
13
src/timeout_screen.h
Normal file
13
src/timeout_screen.h
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
#pragma once
|
||||||
|
#include <Arduino.h>
|
||||||
|
#include <TFT_eSPI.h>
|
||||||
|
|
||||||
|
// Bildschirm-Timeout einstellen (Menue > System > Bildschirm-Timeout) -
|
||||||
|
// Schieberegler von Config::SCREEN_TIMEOUT_MIN_MINUTES bis
|
||||||
|
// Config::SCREEN_TIMEOUT_MAX_MINUTES, danach "Nie" als eigene Endposition.
|
||||||
|
// Der Ruhebildschirm-Umschalter (siehe SettingsStore::screensaverEnabled())
|
||||||
|
// lebt ebenfalls hier, direkt unter dem Regler, da er inhaltlich eng mit
|
||||||
|
// dem Timeout zusammenhaengt (bestimmt nur, WAS beim Timeout passiert).
|
||||||
|
namespace TimeoutScreen {
|
||||||
|
void run(TFT_eSPI& tft);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user