смена темы дисплея в рантайме: страница THEME, образцы цветов, сохранение в prefs
Build and deploy Docs site to GitHub Pages / github-pages (push) Canceled after 0s
Run Unit Tests / test (push) Canceled after 0s
PR Build Check / build (Heltec_v3_companion_radio_ble) (push) Canceled after 0s
PR Build Check / build (Heltec_v3_repeater) (push) Canceled after 0s
PR Build Check / build (Heltec_v3_room_server) (push) Canceled after 0s
PR Build Check / build (LilyGo_Tlora_C6_repeater_) (push) Canceled after 0s
PR Build Check / build (PicoW_repeater) (push) Canceled after 0s
PR Build Check / build (RAK_4631_companion_radio_ble) (push) Canceled after 0s
PR Build Check / build (RAK_4631_companion_radio_ethernet) (push) Canceled after 0s
PR Build Check / build (RAK_4631_repeater) (push) Canceled after 0s
PR Build Check / build (RAK_4631_repeater_ethernet) (push) Canceled after 0s
PR Build Check / build (RAK_4631_room_server) (push) Canceled after 0s
PR Build Check / build (RAK_4631_room_server_ethernet) (push) Canceled after 0s
PR Build Check / build (Tbeam_SX1276_repeater) (push) Canceled after 0s
PR Build Check / build (wio-e5-mini_repeater) (push) Canceled after 0s
PR Build Check / build (wio_wm1110_repeater) (push) Canceled after 0s

This commit is contained in:
2026-10-08 20:00:56 +03:00
parent 3c10296b90
commit 76e754f7ad
6 changed files with 280 additions and 81 deletions
+150 -5
View File
@@ -121,6 +121,7 @@ class HomeScreen : public UIScreen {
#if UI_SENSORS_PAGE == 1
SENSORS,
#endif
THEME, // выбор темы дисплея (только если драйвер поддерживает темы)
SHUTDOWN,
Count // keep as last
};
@@ -162,6 +163,46 @@ class HomeScreen : public UIScreen {
const char* radioRowName(uint8_t row);
void renderRadioEditor(DisplayDriver& display);
// выбор темы дисплея (страница THEME)
bool _theme_edit;
uint8_t _theme_sel;
unsigned long _theme_last_activity;
void themeEditEnter() {
DisplayDriver* d = _task->getDisplay();
if (d == NULL || d->getThemeCount() == 0) return;
_task->clearAlert();
_theme_edit = true;
_theme_sel = d->getTheme(); // начинаем с активной темы
_theme_last_activity = millis();
}
void themeStore() { // запомнить текущую (предпросмотренную) тему в prefs
DisplayDriver* d = _task->getDisplay();
if (d == NULL) return;
const char* name = d->getThemeName(d->getTheme());
strncpy(_node_prefs->ui_theme, name, sizeof(_node_prefs->ui_theme) - 1);
_node_prefs->ui_theme[sizeof(_node_prefs->ui_theme) - 1] = 0;
the_mesh.savePrefs();
}
void themeEditLeave(bool save) {
_theme_edit = false;
if (save) {
themeStore();
_task->showAlert("Saved", 1000);
}
_task->showPendingMessages();
}
void themeRowStep(int8_t dir) {
DisplayDriver* d = _task->getDisplay();
if (d == NULL) return;
uint8_t rows = d->getThemeCount() + 1; // темы + "Save & exit"
_theme_sel = (_theme_sel + rows + dir) % rows;
if (_theme_sel < d->getThemeCount()) d->setTheme(_theme_sel); // живой предпросмотр
}
void renderThemePage(DisplayDriver& display);
void renderThemeEditor(DisplayDriver& display);
uint8_t pageStep(int8_t dir);
void renderBatteryIndicator(DisplayDriver& display, uint16_t batteryMilliVolts) {
// Convert millivolts to percentage
@@ -234,10 +275,11 @@ public:
HomeScreen(UITask* task, mesh::RTCClock* rtc, SensorManager* sensors, NodePrefs* node_prefs)
: _task(task), _rtc(rtc), _sensors(sensors), _node_prefs(node_prefs), _page(0),
_shutdown_init(false), _radio_edit(false), _radio_val_edit(false), _radio_sel(0),
_radio_last_activity(0), sensors_lpp(200) { }
_radio_last_activity(0), _theme_edit(false), _theme_sel(0), _theme_last_activity(0),
sensors_lpp(200) { }
// в меню настройки радио входящие сообщения не всплывают поверх экрана
bool suppressAlerts() override { return _radio_edit; }
// в меню настройки (радио или темы) входящие сообщения не всплывают поверх экрана
bool suppressAlerts() override { return _radio_edit || _theme_edit; }
void poll() override {
if (_radio_edit) {
@@ -248,6 +290,14 @@ public:
_task->keepDisplayOn(); // пока работаем — экран не гасим по таймауту
}
}
if (_theme_edit) {
if (millis() - _theme_last_activity >= UI_RADIO_MENU_TIMEOUT_MILLIS) {
themeEditLeave(false);
_task->blankDisplay();
} else {
_task->keepDisplayOn();
}
}
if (_shutdown_init && !_task->isButtonPressed()) { // must wait for USR button to be released
_task->shutdown();
}
@@ -260,6 +310,10 @@ public:
renderRadioEditor(display);
return 5000;
}
if (_theme_edit) {
renderThemeEditor(display);
return 5000;
}
display.setColor(UIColor::title_bkg);
display.fillRect(0, 0, display.width(), 12);
char tmp[80];
@@ -493,6 +547,8 @@ public:
if (sensors_scroll) sensors_scroll_offset = (sensors_scroll_offset+1)%sensors_nb;
else sensors_scroll_offset = 0;
#endif
} else if (_page == HomePage::THEME) {
renderThemePage(display);
} else if (_page == HomePage::SHUTDOWN) {
display.setColor(UIColor::corp_blue);
display.setTextSize(1);
@@ -531,12 +587,38 @@ public:
}
return true; // остальные события в этом режиме игнорируем
}
if (_theme_edit) { // меню выбора темы
_theme_last_activity = millis();
if (c == KEY_NEXT || c == KEY_RIGHT) {
themeRowStep(+1);
return true;
}
if (c == KEY_PREV || c == KEY_LEFT) {
themeRowStep(-1);
return true;
}
if (c == KEY_ENTER) {
DisplayDriver* d = _task->getDisplay();
if (d != NULL) {
if (_theme_sel >= d->getThemeCount()) {
themeEditLeave(true); // строка "Save & exit" — сохранить и выйти
} else {
themeStore(); // длинное нажатие на теме — запомнить её сейчас
char msg[28];
snprintf(msg, sizeof(msg), "theme %s OK", d->getThemeName(d->getTheme()));
_task->showAlert(msg, 1200);
}
}
return true;
}
return true;
}
if (c == KEY_LEFT || c == KEY_PREV) {
_page = (_page + HomePage::Count - 1) % HomePage::Count;
_page = pageStep(-1);
return true;
}
if (c == KEY_NEXT || c == KEY_RIGHT) {
_page = (_page + 1) % HomePage::Count;
_page = pageStep(+1);
if (_page == HomePage::RECENT) {
_task->showAlert("Recent adverts", 800);
}
@@ -548,6 +630,10 @@ public:
_task->showAlert("Nodes must match", 1500);
return true;
}
if (c == KEY_ENTER && _page == HomePage::THEME) {
themeEditEnter();
return true;
}
if (c == KEY_ENTER && _page == HomePage::BLUETOOTH) {
if (_task->isBluetoothEnabled()) { // toggle Bluetooth on/off
_task->disableBluetooth();
@@ -705,6 +791,60 @@ void HomeScreen::renderRadioEditor(DisplayDriver& display) {
}
}
uint8_t HomeScreen::pageStep(int8_t dir) {
uint8_t p = _page;
for (uint8_t i = 0; i < HomePage::Count; i++) {
p = (p + HomePage::Count + dir) % HomePage::Count;
// страница тем есть только у драйверов, которые поддерживают палитры
if (p != HomePage::THEME || _task->displayHasThemes()) return p;
}
return _page;
}
void HomeScreen::renderThemePage(DisplayDriver& display) {
char tmp[32];
uint8_t n = display.getThemeCount();
display.setTextSize(1);
display.setColor(UIColor::primary_txt);
snprintf(tmp, sizeof(tmp), "Theme: %s", n > 0 ? display.getThemeName(display.getTheme()) : "-");
display.setCursor(0, 22);
display.print(tmp);
display.setColor(UIColor::secondary_txt);
display.drawTextCentered(display.width() / 2, 56, "setup: " PRESS_LABEL);
}
void HomeScreen::renderThemeEditor(DisplayDriver& display) {
uint8_t n = display.getThemeCount();
uint8_t rows = n + 1; // темы + "Save & exit"
const uint8_t VISIBLE = 6; // 6 строк по 10 px влезают в 64
uint8_t first = _theme_sel >= VISIBLE ? _theme_sel - VISIBLE + 1 : 0;
display.setTextSize(1);
int y = 2;
for (uint8_t r = first; r < rows && y <= 52; r++, y += 10) {
const char* label = (r < n) ? display.getThemeName(r) : "Save & exit";
if (r == _theme_sel) {
display.setColor(UIColor::primary_txt); // выбранная строка — инверсия
display.fillRect(0, y - 1, display.width(), 10);
display.setColor(UIColor::window_bkg);
} else {
display.setColor(UIColor::primary_txt);
}
display.drawTextLeftAlign(1, y, label);
if (r < n) {
// образцы цветов темы: полоса заголовка, заливка попапа, акцент
int sx = display.width() - 32;
display.setColor(display.getThemeColor(r, DisplayDriver::THEME_COLOR_TITLE));
display.fillRect(sx, y, 12, 8);
display.setColor(display.getThemeColor(r, DisplayDriver::THEME_COLOR_POPUP));
display.fillRect(sx + 13, y, 8, 8);
display.setColor(display.getThemeColor(r, DisplayDriver::THEME_COLOR_ACCENT));
display.fillRect(sx + 22, y, 10, 8);
}
}
}
class MsgPreviewScreen : public UIScreen {
UITask* _task;
mesh::RTCClock* _rtc;
@@ -811,6 +951,11 @@ void UITask::begin(DisplayDriver* display, SensorManager* sensors, NodePrefs* no
_node_prefs = node_prefs;
if (_display != NULL) {
// применить сохранённую тему дисплея (если драйвер их поддерживает)
if (_display->getThemeCount() > 0) {
uint8_t idx = _display->findTheme(_node_prefs->ui_theme);
if (idx != 0xFF) _display->setTheme(idx);
}
_display->turnOn();
}