Selectable UI themes and a Popular source

Themes (Phosphor, Pink, Amber, Ice, Terminal) restyle the menu, boot
page and toasts live from the menu; all but Terminal use readable
Montserrat text. Source gains Popular: the top-rated listings walked in
rank order from page 1, alternating providers when mixed.
This commit is contained in:
Juli committed 2026-09-17 03:11:25 +02:00
1 parent 1ea7b13bc7
commit 3bdfcba6fb
9 files changed
+171 -47

No files matched your search

+6 -1
View File
@@ -63,10 +63,15 @@ Entries:
saving asks for one of the 50 slots (it jumps to the first empty one; picking a used slot overwrites it).
* **Favourites** — the saved cameras; click one to play it. A saved camera shows a
yellow *FAV n* marker while it plays.
* **Source** — *Random* draws from the sites, *Favourites* cycles the saved slots.
* **Source** — *Random* draws random listing pages, *Popular* walks the sites'
top-rated listings in rank order from the top, *Favourites* cycles the saved
slots.
* **Provider** — *insecam*, *opentopia* or *Mixed* (a random site per batch).
* **Time per camera**, **Listing** (mixed / top rated / newest), **Screen
brightness**, **Ring brightness**, **Caption**.
* **Theme** — the look of the menu, boot page and pop-ups, applied as you
cycle: *Phosphor* (green), *Pink*, *Amber*, *Ice*, and *Terminal* (green with
an 8×16 pixel font). The camera caption keeps its own neutral style.
* **Forget a favourite**, **Wi-Fi setup** (reboots into the hotspot), **Power
off**, the board name with its IP, and the power source.
+2 -1
View File
@@ -38,6 +38,7 @@ void app_main(void)
display_init();
ui_init();
ui_set_theme(settings.theme);
ui_status("insecam widget", "starting");
display_set_backlight(settings.lcd_brightness);
@@ -58,7 +59,7 @@ void app_main(void)
if (!net_wait_connected(20000)) run_portal();
}
ESP_LOGI(TAG, "online at %s", net_ip_str());
ui_status("insecam widget", "online " "\xE2\x80\xA2" " finding cameras\n\nknob: next / previous\nclick: pause hold: menu");
ui_status("insecam widget", "online - finding cameras\n\nturn: next / previous\nclick: pause\nhold: menu");
leds_set_mode(LED_SPIN, 0x2060ff);
slideshow_start();
}
+12 -4
View File
@@ -20,7 +20,7 @@ static const char *TAG = "menu";
typedef enum { VIEW_TOP, VIEW_FAV_PLAY, VIEW_FAV_SAVE, VIEW_FAV_FORGET } view_t;
enum { ROW_FAV_TOGGLE, ROW_FAVS, ROW_SOURCE, ROW_PROVIDER, ROW_INTERVAL, ROW_LISTING, ROW_LCD, ROW_RING,
ROW_CAPTION, ROW_FORGET, ROW_WIFI, ROW_POWER, ROW_IP, ROW_BATTERY, ROW_CLOSE, TOP_ROWS };
ROW_CAPTION, ROW_THEME, ROW_FORGET, ROW_WIFI, ROW_POWER, ROW_IP, ROW_BATTERY, ROW_CLOSE, TOP_ROWS };
static ui_menu_row_t rows[TOP_ROWS > FAV_SLOTS + 1 ? TOP_ROWS : FAV_SLOTS + 1];
static char values[TOP_ROWS][20];
@@ -43,15 +43,17 @@ static int build_top(const cam_info_t *current, int fav_slot)
rows[ROW_FAV_TOGGLE] = (ui_menu_row_t){fav_slot >= 0 ? "Remove from favourites" : "Save as favourite",
values[ROW_FAV_TOGGLE], current == NULL};
rows[ROW_FAVS] = (ui_menu_row_t){"Favourites", values[ROW_FAVS], false};
rows[ROW_SOURCE] = (ui_menu_row_t){"Source", settings.source == SOURCE_FAVORITES ? "Favourites" : "Random", false};
rows[ROW_SOURCE] = (ui_menu_row_t){"Source", settings.source == SOURCE_FAVORITES ? "Favourites" :
settings.source == SOURCE_POPULAR ? "Popular" : "Random", false};
rows[ROW_PROVIDER] = (ui_menu_row_t){"Provider",
settings.provider == PROVIDER_PREF_INSECAM ? "insecam" : settings.provider == PROVIDER_PREF_OPENTOPIA ? "opentopia" : "Mixed",
settings.source == SOURCE_FAVORITES};
rows[ROW_INTERVAL] = (ui_menu_row_t){"Time per camera", values[ROW_INTERVAL], false};
rows[ROW_LISTING] = (ui_menu_row_t){"Listing", listing_name(settings.listing), false};
rows[ROW_LISTING] = (ui_menu_row_t){"Listing", listing_name(settings.listing), settings.source == SOURCE_POPULAR};
rows[ROW_LCD] = (ui_menu_row_t){"Screen brightness", values[ROW_LCD], false};
rows[ROW_RING] = (ui_menu_row_t){"Ring brightness", values[ROW_RING], false};
rows[ROW_CAPTION] = (ui_menu_row_t){"Caption", settings.caption ? "Shown" : "Hidden", false};
rows[ROW_THEME] = (ui_menu_row_t){"Theme", ui_theme_name(settings.theme), false};
rows[ROW_FORGET] = (ui_menu_row_t){"Forget a favourite", NULL, fav_count() == 0};
rows[ROW_WIFI] = (ui_menu_row_t){"Wi-Fi setup", NULL, false};
rows[ROW_POWER] = (ui_menu_row_t){"Power off", "knob wakes", false};
@@ -179,10 +181,16 @@ menu_result_t menu_run(const cam_info_t *current, int *fav_slot_out)
case ROW_FAVS: view = VIEW_FAV_PLAY; sel = top = 0; break;
case ROW_FORGET: view = VIEW_FAV_FORGET; sel = top = 0; break;
case ROW_SOURCE:
settings.source = settings.source == SOURCE_RANDOM ? SOURCE_FAVORITES : SOURCE_RANDOM;
settings.source = settings.source == SOURCE_RANDOM ? SOURCE_POPULAR :
settings.source == SOURCE_POPULAR ? SOURCE_FAVORITES : SOURCE_RANDOM;
if (settings.source == SOURCE_FAVORITES && fav_count() == 0) ui_toast("No favourites saved yet", 1500);
settings_save();
break;
case ROW_THEME:
settings.theme = (settings.theme + 1) % ui_theme_count();
ui_set_theme(settings.theme);
settings_save();
break;
case ROW_LISTING:
settings.listing = (settings.listing + 1) % 3;
settings_save();
+5 -2
View File
@@ -1,10 +1,12 @@
#include "settings.h"
#include "ui.h"
#include "sdkconfig.h"
#include "esp_log.h"
#include "nvs.h"
#include "nvs_flash.h"
#include "esp_heap_caps.h"
#include <assert.h>
#include <stddef.h>
#include <stdio.h>
#include <string.h>
@@ -43,12 +45,13 @@ void settings_init(void)
size_t len = sizeof stored;
if (nvs_get_blob(nvs, "settings", &stored, &len) == ESP_OK && len >= 7 && len <= sizeof stored) {
memcpy(&settings, &stored, len);
if (len < sizeof settings) settings.provider = PROVIDER_PREF_MIXED; // pre-provider blob: padding, not a choice
if (len <= offsetof(settings_t, provider)) settings.provider = PROVIDER_PREF_MIXED; // pre-provider blob
settings.version = SETTINGS_VERSION;
if (settings.interval_s < 5 || settings.interval_s > 300) settings.interval_s = CONFIG_WIDGET_INTERVAL_S;
if (settings.lcd_brightness > 100) settings.lcd_brightness = 100;
if (settings.listing > LISTING_NEW) settings.listing = LISTING_MIXED;
if (settings.source > SOURCE_FAVORITES) settings.source = SOURCE_RANDOM;
if (settings.source > SOURCE_POPULAR) settings.source = SOURCE_RANDOM;
if (settings.theme >= ui_theme_count()) settings.theme = 0;
if (settings.provider > PROVIDER_PREF_MIXED) settings.provider = PROVIDER_PREF_MIXED;
}
favs = heap_caps_calloc(FAV_SLOTS, sizeof *favs, MALLOC_CAP_SPIRAM);
+2 -1
View File
@@ -5,7 +5,7 @@
// Persistent settings (NVS namespace "widget"); Kconfig supplies first-boot defaults.
typedef enum { LISTING_MIXED = 0, LISTING_RATING, LISTING_NEW } listing_pref_t;
typedef enum { SOURCE_RANDOM = 0, SOURCE_FAVORITES } source_pref_t;
typedef enum { SOURCE_RANDOM = 0, SOURCE_FAVORITES, SOURCE_POPULAR } source_pref_t;
typedef enum { PROVIDER_PREF_INSECAM = 0, PROVIDER_PREF_OPENTOPIA, PROVIDER_PREF_MIXED } provider_pref_t;
typedef struct {
@@ -17,6 +17,7 @@ typedef struct {
uint8_t caption; // 1 = caption shown
uint8_t provider; // provider_pref_t (appended; older blobs load with the default)
uint8_t version; // SETTINGS_VERSION; lets us tell a pre-provider blob apart
uint8_t theme; // ui theme index
} settings_t;
#define SETTINGS_VERSION 2
+14 -4
View File
@@ -46,6 +46,8 @@ typedef enum { LEAVE_NEXT, LEAVE_PREV, LEAVE_ERROR, LEAVE_MENU } leave_t;
static cam_info_t page_cams[PAGE_ITEMS];
static int page_count, page_next;
static int total_pages[2][2] = {{287, 287}, {162, 162}}; // [provider][listing]
static int popular_page[2] = {1, 1}; // next rated page per provider in Popular mode
static int popular_turn;
static cam_info_t history[HISTORY_LEN];
static int hist_len, hist_pos = -1;
@@ -88,18 +90,26 @@ static void refill_queue(void)
{
int failures = 0;
while (1) {
insecam_listing_t listing = pick_listing();
bool popular = settings.source == SOURCE_POPULAR;
insecam_listing_t listing = popular ? INSECAM_BY_RATING : pick_listing();
provider_t provider = settings.provider == PROVIDER_PREF_INSECAM ? PROVIDER_INSECAM :
settings.provider == PROVIDER_PREF_OPENTOPIA ? PROVIDER_OPENTOPIA :
popular ? ((popular_turn++ & 1) ? PROVIDER_OPENTOPIA : PROVIDER_INSECAM) :
(esp_random() & 1) ? PROVIDER_OPENTOPIA : PROVIDER_INSECAM;
int page = 1 + esp_random() % total_pages[provider][listing];
int page;
if (popular) { // walk the top-rated listing from the top, in rank order
if (popular_page[provider] > total_pages[provider][listing]) popular_page[provider] = 1;
page = popular_page[provider]++;
} else {
page = 1 + esp_random() % total_pages[provider][listing];
}
int n = 0;
esp_err_t err = provider == PROVIDER_OPENTOPIA
? opentopia_fetch_page(listing, page, page_cams, PAGE_ITEMS, &n, &total_pages[provider][listing])
: insecam_fetch_page(listing, page, page_cams, PAGE_ITEMS, &n, &total_pages[provider][listing]);
if (err == ESP_OK && n > 0) {
// shuffle so the six don't always play in site order
for (int i = n - 1; i > 0; i--) {
// shuffle so the six don't always play in site order (Popular keeps rank order)
for (int i = popular ? 0 : n - 1; i > 0; i--) {
int j = esp_random() % (i + 1);
cam_info_t t = page_cams[i];
page_cams[i] = page_cams[j];
+121 -34
View File
@@ -4,10 +4,41 @@
#include "lvgl.h"
#include <string.h>
static lv_obj_t *scr_status, *lbl_title, *lbl_msg;
// Themes for the "system" pages (menu, status, toast). The camera page keeps its own look.
typedef struct {
const char *name;
uint32_t bg, text, dim, sel_bg, sel_text, edit;
const lv_font_t *title_font, *row_font, *hint_font;
} theme_t;
static const theme_t themes[] = {
{"Phosphor", 0x000000, 0x3ddc84, 0x1f7a4a, 0x3ddc84, 0x04140b, 0xffb000,
&lv_font_montserrat_18, &lv_font_montserrat_14, &lv_font_montserrat_12},
{"Pink", 0x140812, 0xff6ab8, 0x8c3a6c, 0xff6ab8, 0x1e0716, 0xfff06a,
&lv_font_montserrat_18, &lv_font_montserrat_14, &lv_font_montserrat_12},
{"Amber", 0x000000, 0xffb000, 0x7a5200, 0xffb000, 0x1a1000, 0x3ddc84,
&lv_font_montserrat_18, &lv_font_montserrat_14, &lv_font_montserrat_12},
{"Ice", 0x03070d, 0xa6dcff, 0x3a6e91, 0x2fb8ff, 0x03070d, 0xffb000,
&lv_font_montserrat_18, &lv_font_montserrat_14, &lv_font_montserrat_12},
{"Terminal", 0x000000, 0x3ddc84, 0x1f7a4a, 0x3ddc84, 0x04140b, 0xffb000,
&lv_font_unscii_16, &lv_font_unscii_16, &lv_font_unscii_8},
};
static const theme_t *th = &themes[0];
static lv_obj_t *scr_status, *status_hdr, *lbl_title, *lbl_msg, *status_cursor;
static lv_obj_t *scr_cam, *canvas, *overlay, *lbl_place, *lbl_meta, *lbl_batt, *badge, *marker, *bar, *toast;
static lv_obj_t *scr_menu, *menu_title, *menu_row[UI_MENU_ROWS], *menu_label[UI_MENU_ROWS], *menu_value[UI_MENU_ROWS];
static lv_obj_t *scr_menu, *menu_title, *menu_cursor, *menu_rule, *menu_hint, *menu_row[UI_MENU_ROWS], *menu_label[UI_MENU_ROWS], *menu_value[UI_MENU_ROWS];
static lv_obj_t *prev_screen;
static lv_timer_t *cursor_timer;
static char menu_line[UI_MENU_ROWS][64];
static void cursor_blink_cb(lv_timer_t *t)
{
static bool on;
on = !on;
lv_obj_set_hidden(menu_cursor, !on);
lv_obj_set_hidden(status_cursor, !on);
}
static lv_timer_t *toast_timer;
static bool overlay_on = true;
@@ -41,15 +72,19 @@ void ui_init(void)
// --- status page ---
scr_status = plain_screen();
lbl_title = label(scr_status, &lv_font_montserrat_24, 0xffffff);
status_hdr = label(scr_status, th->hint_font, th->dim);
lv_label_set_text(status_hdr, "T-EMBED // ESP32-S3 // insecam widget");
lv_obj_set_pos(status_hdr, 12, 10);
lbl_title = label(scr_status, th->title_font, th->text);
lv_obj_set_width(lbl_title, LCD_H_RES - 24);
lv_obj_set_style_text_align(lbl_title, LV_TEXT_ALIGN_CENTER, 0);
lv_obj_align(lbl_title, LV_ALIGN_TOP_MID, 0, 34);
lbl_msg = label(scr_status, &lv_font_montserrat_14, 0xb8c0cc);
lv_obj_set_pos(lbl_title, 12, 30);
lbl_msg = label(scr_status, th->row_font, th->text);
lv_obj_set_width(lbl_msg, LCD_H_RES - 24);
lv_obj_set_style_text_align(lbl_msg, LV_TEXT_ALIGN_CENTER, 0);
lv_label_set_long_mode(lbl_msg, LV_LABEL_LONG_WRAP);
lv_obj_align(lbl_msg, LV_ALIGN_TOP_MID, 0, 80);
lv_obj_set_pos(lbl_msg, 12, 62);
status_cursor = label(scr_status, th->row_font, th->text);
lv_label_set_text(status_cursor, "_");
lv_obj_align(status_cursor, LV_ALIGN_BOTTOM_LEFT, 12, -10);
// --- camera page ---
scr_cam = plain_screen();
@@ -102,36 +137,44 @@ void ui_init(void)
lv_obj_set_hidden(marker, true);
// toast lives on the top layer so it shows over every page
toast = label(lv_layer_top(), &lv_font_montserrat_14, 0xffffff);
lv_obj_set_style_bg_color(toast, lv_color_hex(0x202830), 0);
toast = label(lv_layer_top(), th->row_font, th->text);
lv_obj_set_style_bg_color(toast, lv_color_hex(th->bg), 0);
lv_obj_set_style_bg_opa(toast, LV_OPA_90, 0);
lv_obj_set_style_radius(toast, 6, 0);
lv_obj_set_style_border_color(toast, lv_color_hex(th->text), 0);
lv_obj_set_style_border_width(toast, 1, 0);
lv_obj_set_style_radius(toast, 0, 0);
lv_obj_set_style_pad_all(toast, 8, 0);
lv_obj_align(toast, LV_ALIGN_CENTER, 0, -10);
lv_obj_set_hidden(toast, true);
// --- menu page ---
// --- menu page: green phosphor terminal ---
scr_menu = plain_screen();
lv_obj_t *titlebar = lv_obj_create(scr_menu);
lv_obj_remove_style_all(titlebar);
lv_obj_set_size(titlebar, LCD_H_RES, 22);
lv_obj_set_style_bg_color(titlebar, lv_color_hex(0x1c2430), 0);
lv_obj_set_style_bg_opa(titlebar, LV_OPA_COVER, 0);
menu_title = label(titlebar, &lv_font_montserrat_14, 0x9fb3c8);
lv_obj_align(menu_title, LV_ALIGN_LEFT_MID, 8, 0);
menu_title = label(scr_menu, th->title_font, th->text);
lv_obj_set_pos(menu_title, 6, 2);
menu_cursor = label(scr_menu, th->title_font, th->text);
lv_label_set_text(menu_cursor, "_");
menu_rule = lv_obj_create(scr_menu);
lv_obj_remove_style_all(menu_rule);
lv_obj_set_size(menu_rule, LCD_H_RES - 12, 1);
lv_obj_set_pos(menu_rule, 6, 23);
lv_obj_set_style_bg_opa(menu_rule, LV_OPA_COVER, 0);
for (int i = 0; i < UI_MENU_ROWS; i++) {
menu_row[i] = lv_obj_create(scr_menu);
lv_obj_remove_style_all(menu_row[i]);
lv_obj_set_size(menu_row[i], LCD_H_RES, 24);
lv_obj_set_pos(menu_row[i], 0, 24 + i * 24);
lv_obj_set_size(menu_row[i], LCD_H_RES - 12, 20);
lv_obj_set_pos(menu_row[i], 6, 26 + i * 21);
lv_obj_set_style_bg_opa(menu_row[i], LV_OPA_COVER, 0);
menu_label[i] = label(menu_row[i], &lv_font_montserrat_14, 0xffffff);
lv_obj_set_width(menu_label[i], 190);
lv_label_set_long_mode(menu_label[i], LV_LABEL_LONG_DOT);
lv_obj_align(menu_label[i], LV_ALIGN_LEFT_MID, 10, 0);
menu_value[i] = label(menu_row[i], &lv_font_montserrat_14, 0xc4ccd8);
lv_obj_align(menu_value[i], LV_ALIGN_RIGHT_MID, -10, 0);
menu_label[i] = label(menu_row[i], th->row_font, th->text);
lv_obj_set_width(menu_label[i], 200);
lv_label_set_long_mode(menu_label[i], LV_LABEL_LONG_CLIP);
lv_obj_align(menu_label[i], LV_ALIGN_LEFT_MID, 4, 0);
menu_value[i] = label(menu_row[i], th->row_font, th->text);
lv_obj_align(menu_value[i], LV_ALIGN_RIGHT_MID, -4, 0);
}
menu_hint = label(scr_menu, th->hint_font, th->dim);
lv_obj_set_pos(menu_hint, 6, 154);
cursor_timer = lv_timer_create(cursor_blink_cb, 500, NULL);
ui_set_theme(0);
lv_screen_load(scr_status);
lvgl_port_unlock();
@@ -140,7 +183,9 @@ void ui_init(void)
void ui_status(const char *title, const char *msg)
{
lvgl_port_lock(0);
lv_label_set_text(lbl_title, title ? title : "");
char buf[200];
snprintf(buf, sizeof buf, "> %s", title ? title : "");
lv_label_set_text(lbl_title, buf);
lv_label_set_text(lbl_msg, msg ? msg : "");
if (lv_screen_active() != scr_status) lv_screen_load(scr_status);
lvgl_port_unlock();
@@ -237,20 +282,28 @@ void ui_set_marker(const char *text)
void ui_menu_show(const char *title, const ui_menu_row_t *rows, int count, int top, int selected, bool editing)
{
lvgl_port_lock(0);
lv_label_set_text(menu_title, title);
char buf[64];
snprintf(buf, sizeof buf, "T-EMBED://%s", title);
for (char *p = buf; *p; p++) *p = (*p >= 'a' && *p <= 'z') ? *p - 32 : *p;
lv_label_set_text(menu_title, buf);
lv_obj_update_layout(menu_title);
lv_obj_set_pos(menu_cursor, 6 + lv_obj_get_width(menu_title), 2);
for (int i = 0; i < UI_MENU_ROWS; i++) {
int idx = top + i;
bool present = idx < count;
lv_obj_set_hidden(menu_row[i], !present);
if (!present) continue;
bool sel = idx == selected;
lv_obj_set_style_bg_color(menu_row[i], lv_color_hex(sel ? 0x2f81f7 : 0x000000), 0);
lv_label_set_text(menu_label[i], rows[idx].label);
lv_obj_set_style_text_color(menu_label[i], lv_color_hex(rows[idx].dim ? 0x6c7684 : 0xffffff), 0);
uint32_t fg = sel ? th->sel_text : rows[idx].dim ? th->dim : th->text;
lv_obj_set_style_bg_color(menu_row[i], lv_color_hex(sel ? th->sel_bg : th->bg), 0);
snprintf(menu_line[i], sizeof menu_line[i], "%02d%c%s", idx + 1, sel ? '>' : ' ', rows[idx].label);
lv_label_set_text(menu_label[i], menu_line[i]);
lv_obj_set_style_text_color(menu_label[i], lv_color_hex(fg), 0);
lv_label_set_text(menu_value[i], rows[idx].value ? rows[idx].value : "");
lv_obj_set_style_text_color(menu_value[i],
lv_color_hex(sel && editing ? 0xffd040 : sel ? 0xffffff : 0xc4ccd8), 0);
lv_obj_set_style_text_color(menu_value[i], lv_color_hex(sel && editing ? th->edit : fg), 0);
}
snprintf(buf, sizeof buf, "[%02d/%02d] turn:move click:%s hold:back", selected + 1, count, editing ? "set" : "ok");
lv_label_set_text(menu_hint, buf);
if (lv_screen_active() != scr_menu) {
prev_screen = lv_screen_active();
lv_screen_load(scr_menu);
@@ -272,3 +325,37 @@ void ui_set_battery(const char *text)
else { lv_label_set_text(lbl_batt, text); lv_obj_set_hidden(lbl_batt, false); }
lvgl_port_unlock();
}
int ui_theme_count(void) { return sizeof themes / sizeof themes[0]; }
const char *ui_theme_name(int idx) { return themes[idx < 0 || idx >= ui_theme_count() ? 0 : idx].name; }
static void apply(lv_obj_t *l, const lv_font_t *f, uint32_t rgb)
{
lv_obj_set_style_text_font(l, f, 0);
lv_obj_set_style_text_color(l, lv_color_hex(rgb), 0);
}
void ui_set_theme(int idx)
{
if (idx < 0 || idx >= ui_theme_count()) idx = 0;
lvgl_port_lock(0);
th = &themes[idx];
lv_obj_set_style_bg_color(scr_status, lv_color_hex(th->bg), 0);
lv_obj_set_style_bg_color(scr_menu, lv_color_hex(th->bg), 0);
apply(status_hdr, th->hint_font, th->dim);
apply(lbl_title, th->title_font, th->text);
apply(lbl_msg, th->row_font, th->text);
apply(status_cursor, th->row_font, th->text);
apply(menu_title, th->title_font, th->text);
apply(menu_cursor, th->title_font, th->text);
lv_obj_set_style_bg_color(menu_rule, lv_color_hex(th->dim), 0);
for (int i = 0; i < UI_MENU_ROWS; i++) {
lv_obj_set_style_text_font(menu_label[i], th->row_font, 0);
lv_obj_set_style_text_font(menu_value[i], th->row_font, 0);
}
apply(menu_hint, th->hint_font, th->dim);
apply(toast, th->row_font, th->text);
lv_obj_set_style_bg_color(toast, lv_color_hex(th->bg), 0);
lv_obj_set_style_border_color(toast, lv_color_hex(th->text), 0);
lvgl_port_unlock();
}
+5
View File
@@ -5,6 +5,11 @@
// All calls take the LVGL lock themselves; safe from any task.
void ui_init(void);
// Colour/font themes for the system pages (menu, status, toast).
int ui_theme_count(void);
const char *ui_theme_name(int idx);
void ui_set_theme(int idx);
// Full-screen text page (boot, Wi-Fi setup, fatal errors).
void ui_status(const char *title, const char *msg);
+4
View File
@@ -48,3 +48,7 @@ CONFIG_LV_USE_BAR=y
CONFIG_LV_USE_LABEL=y
CONFIG_LV_USE_PERF_MONITOR=n
# terminal-style UI fonts
CONFIG_LV_FONT_UNSCII_8=y
CONFIG_LV_FONT_UNSCII_16=y