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:
Eiswolf-BG
2026-08-13 02:44:35 +02:00
parent 0bf8518ac5
commit 6effdb4eaa
12 changed files with 269 additions and 16 deletions

View File

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

View File

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

View File

@@ -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,

View File

@@ -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,

View File

@@ -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,

View File

@@ -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,

View File

@@ -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,

View File

@@ -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,

View File

@@ -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)) {

View File

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