Merge PR #375: Day and Night themes (#296)

oumike. A firmware-wide appearance setting: Night stays the default and renders
exactly as before, Day is a low-glare light palette with its own semantic roles
for text, panels, fields, borders, controls, focus, charts, status colours and
chat surfaces. Selecting a mode restarts, so every LVGL object is rebuilt from
one coherent palette rather than half-repainted. There is a Night/Day selector
in Settings, Display, the Control Center Theme chip is now a direct toggle whose
sun/moon icon shows the active mode, and the standalone console UI has a
matching Day palette.

Main already carried the palette scaffolding for this, pinned to Night behind a
comment saying it was waiting on this branch, so the merge mostly replaces those
stubs with the real thing.

The prefs schema needed care. The branch appended theme_mode as v54, but v54 and
v55 were taken by boot_wifi_time/boot_wifi_open and loud_alerts before this
merged, so the field moves to the tail behind them and the version becomes v56,
with the trailing-field assert and the migration step moved to match. A packed
struct read back at the wrong offsets is the failure this schema's asserts exist
to prevent, so the invariants from both sides are kept rather than one replacing
the other.

That also surfaced a stale test: the v53 case asserted that v53 plus exactly two
bytes was the whole struct, which stopped being true when loud_alerts landed and
was not caught because the host test was not run then. It now counts every byte
appended since and asserts the new fields come back at their defaults. The
schema host tests pass. While there, the size comment said "about 500 bytes";
the struct is 114.

Built on all nine S3 envs and both ESP32-P4 targets.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Kaj SchittecatandClaude Opus 5 committed 2026-09-04 09:11:54 +02:00
commit 2a8b62c6a7
33 files changed
+371 -56

No files matched your search

+24 -4
View File
@@ -164,11 +164,28 @@ uint8_t ringGetSpl2(int i) {
// and the greens were unreadable. Owning both ends removes the dependency on
// which definition happens to win at link time.
#define RGB565(r, g, b) ((uint16_t)((((r) & 0xF8) << 8) | (((g) & 0xFC) << 3) | ((b) >> 3)))
constexpr uint16_t kBg = RGB565(0x0E, 0x12, 0x16); // the firmware's near-black
bool s_day_theme = false;
uint16_t consoleBg() {
return s_day_theme ? RGB565(0xF1, 0xF4, 0xF6) : RGB565(0x0E, 0x12, 0x16);
}
uint16_t colourFor(uint8_t c) {
// e-ink has one ink colour; anything else is invisible or dithered.
if (s_disp && s_disp->isEink()) return UIColor::primary_txt;
if (s_day_theme) {
switch (c) {
case CC_DIM: return RGB565(0x56, 0x60, 0x6C);
case CC_ECHO:
case CC_OK: return RGB565(0x27, 0x67, 0x38);
case CC_WARN: return RGB565(0x80, 0x60, 0x00);
case CC_ERR: return RGB565(0xA5, 0x2B, 0x26);
case CC_CHAN: return RGB565(0x1F, 0x5F, 0x9E);
case CC_SENDER: return RGB565(0x8A, 0x5A, 0x00);
case CC_DM: return RGB565(0x68, 0x42, 0xA0);
case CC_HEAD: return RGB565(0x08, 0x77, 0x6D);
default: return RGB565(0x17, 0x20, 0x26);
}
}
switch (c) {
case CC_DIM: return RGB565(0x7A, 0x7F, 0x87); // grey, the theme's "sub"
case CC_ECHO: return RGB565(0x53, 0xC0, 0x6B); // green, the shell-echo convention
@@ -321,7 +338,7 @@ void measure() {
// ---- render -----------------------------------------------------------------
void render() {
if (!s_disp || !s_active) return;
s_disp->startFrame(kBg);
s_disp->startFrame(consoleBg());
s_disp->setTextSize(1);
// Oldest-first from the scroll position, newest at the bottom.
@@ -410,7 +427,7 @@ void drawInputLine(bool cursor_on) {
const int iy = s_disp->height() - s_line_h;
#endif
// Clear only this row. fillScreen would take the scrollback and keypad with it.
s_disp->setColor(kBg);
s_disp->setColor(consoleBg());
s_disp->fillRect(0, iy, s_disp->width(), s_line_h);
s_disp->setTextSize(1);
@@ -452,7 +469,7 @@ void drawInputLine(bool cursor_on) {
// Toggle just the cursor cell. Two fillRects, no text, no clear.
void drawCursorOnly(bool on) {
if (!s_disp) return;
s_disp->setColor(on ? colourFor(CC_TEXT) : kBg);
s_disp->setColor(on ? colourFor(CC_TEXT) : consoleBg());
s_disp->fillRect(s_cur_x, s_cur_y, s_char_w, s_line_h);
}
@@ -705,6 +722,9 @@ void submit() {
void consoleBegin(DisplayDriver* d) {
s_disp = d;
if (!s_disp) return;
#if defined(ESP32)
s_day_theme = touchPrefsGetThemeMode() == TOUCH_THEME_DAY;
#endif
if (!s_ring) {
const size_t bytes = (size_t)kMaxLines * (kLineCap + 1);
#if defined(ESP32)