mirror of
https://github.com/ratspeak/ratdeck.git
synced 2026-08-28 13:34:27 +00:00
ui: light/dark theme toggle
This commit is contained in:
@@ -78,6 +78,7 @@ bool UserConfig::parseJson(const String& json) {
|
||||
if (rawBri > 100) rawBri = rawBri * 100 / 255; // Migrate from PWM to percentage
|
||||
_settings.brightness = constrain(rawBri, 1, 100);
|
||||
_settings.denseFontMode = doc["dense_font"] | false;
|
||||
_settings.themeLight = doc["theme_light"] | false;
|
||||
_settings.keyboardBrightness = constrain(doc["kb_brightness"] | 100, 0, 100);
|
||||
_settings.keyboardAutoOn = doc["kb_auto_on"] | false;
|
||||
_settings.keyboardAutoOff = doc["kb_auto_off"] | false;
|
||||
@@ -150,6 +151,7 @@ String UserConfig::serializeToJson() const {
|
||||
doc["screen_off"] = _settings.screenOffTimeout;
|
||||
doc["brightness"] = _settings.brightness;
|
||||
doc["dense_font"] = _settings.denseFontMode;
|
||||
doc["theme_light"] = _settings.themeLight;
|
||||
doc["kb_brightness"] = _settings.keyboardBrightness;
|
||||
doc["kb_auto_on"] = _settings.keyboardAutoOn;
|
||||
doc["kb_auto_off"] = _settings.keyboardAutoOff;
|
||||
|
||||
@@ -56,6 +56,7 @@ struct UserSettings {
|
||||
uint16_t screenOffTimeout = 60; // seconds
|
||||
uint8_t brightness = 100; // Percentage 1-100
|
||||
bool denseFontMode = false; // T-Deck Plus: adaptive font toggle
|
||||
bool themeLight = false; // false = dark (original palette)
|
||||
|
||||
// Keyboard
|
||||
uint8_t keyboardBrightness = 100; // Percentage 0-100 (0 = off)
|
||||
|
||||
@@ -24,6 +24,7 @@
|
||||
#include "input/InputManager.h"
|
||||
#include "input/HotkeyManager.h"
|
||||
#include "ui/UIManager.h"
|
||||
#include "ui/Theme.h"
|
||||
#include "ui/LvTabBar.h"
|
||||
#include "ui/LvInput.h"
|
||||
#include "ui/screens/LvBootScreen.h"
|
||||
@@ -1106,6 +1107,8 @@ void setup() {
|
||||
} else {
|
||||
Serial.println("[BOOT] Early flash mount failed; using default radio config");
|
||||
}
|
||||
// Select palette before any LVGL styles are built
|
||||
Theme::setScheme(userConfig.settings().themeLight ? Theme::Scheme::LIGHT : Theme::Scheme::DARK);
|
||||
|
||||
// Step 4: Radio + SD init BEFORE display
|
||||
// Radio and SD must init while SPIClass exclusively owns SPI2_HOST.
|
||||
@@ -1250,6 +1253,11 @@ void setup() {
|
||||
|
||||
lvBootScreen.setProgress(0.64f, "Loading config...");
|
||||
userConfig.load(sdStore, flash);
|
||||
// SD config may override the early flash-only load; re-sync palette
|
||||
{
|
||||
Theme::Scheme want = userConfig.settings().themeLight ? Theme::Scheme::LIGHT : Theme::Scheme::DARK;
|
||||
if (want != Theme::scheme()) { Theme::setScheme(want); ui.applyTheme(); }
|
||||
}
|
||||
inputManager.setTrackballSpeed(userConfig.settings().trackballSpeed);
|
||||
applyRadioSettingsToHardware(userConfig.settings(), "BOOT PRE-RNS");
|
||||
|
||||
|
||||
@@ -212,6 +212,12 @@ void init(Keyboard* kb, Trackball* tb, TouchInput* touch) {
|
||||
Serial.println("[LVGL] Input drivers registered (touch enabled)");
|
||||
}
|
||||
|
||||
void applyTheme() {
|
||||
if (!s_cursor) return;
|
||||
lv_obj_set_style_bg_color(s_cursor, lv_color_hex(Theme::BG), 0);
|
||||
lv_obj_set_style_outline_color(s_cursor, lv_color_hex(Theme::PRIMARY), 0);
|
||||
}
|
||||
|
||||
void feedKey(const KeyEvent& evt) {
|
||||
uint32_t key = 0;
|
||||
|
||||
|
||||
@@ -18,4 +18,7 @@ void feedKey(const KeyEvent& evt);
|
||||
// Get the LVGL input group (for focusing widgets)
|
||||
lv_group_t* group();
|
||||
|
||||
// Re-apply palette colors to the touch cursor after a theme switch
|
||||
void applyTheme();
|
||||
|
||||
} // namespace LvInput
|
||||
|
||||
@@ -203,6 +203,13 @@ void LvStatusBar::setUse24Hour(bool use24h) {
|
||||
updateTime();
|
||||
}
|
||||
|
||||
void LvStatusBar::applyTheme() {
|
||||
if (_lblToast) lv_obj_set_style_text_color(_lblToast, lv_color_hex(Theme::BG), 0);
|
||||
refreshIndicators();
|
||||
refreshBattery();
|
||||
refreshTimeColor();
|
||||
}
|
||||
|
||||
void LvStatusBar::refreshIndicators() {
|
||||
if (!_lblLinks) return;
|
||||
|
||||
|
||||
@@ -26,6 +26,9 @@ public:
|
||||
void setUse24Hour(bool use24h);
|
||||
void updateTime(); // Call at 1 Hz to refresh clock
|
||||
|
||||
// Re-apply palette-dependent local colors after a theme switch
|
||||
void applyTheme();
|
||||
|
||||
lv_obj_t* obj() { return _bar; }
|
||||
|
||||
private:
|
||||
|
||||
+3
-1
@@ -29,8 +29,10 @@ public:
|
||||
|
||||
lv_obj_t* obj() { return _bar; }
|
||||
|
||||
private:
|
||||
// Re-apply palette-dependent label colors after a theme switch
|
||||
void refreshTabs();
|
||||
|
||||
private:
|
||||
void refreshTab(int idx);
|
||||
|
||||
lv_obj_t* _bar = nullptr;
|
||||
|
||||
+86
-52
@@ -37,34 +37,105 @@ static lv_style_t s_tabCell;
|
||||
static lv_style_t s_tabCellActive;
|
||||
static lv_style_t s_badge;
|
||||
|
||||
// Palette-dependent props only; re-run on scheme change.
|
||||
static void applyColors() {
|
||||
lv_style_set_bg_color(&s_screen, lv_color_hex(Theme::BG));
|
||||
lv_style_set_text_color(&s_screen, lv_color_hex(Theme::TEXT_PRIMARY));
|
||||
|
||||
lv_style_set_text_color(&s_label, lv_color_hex(Theme::TEXT_PRIMARY));
|
||||
lv_style_set_text_color(&s_labelMuted, lv_color_hex(Theme::TEXT_SECONDARY));
|
||||
lv_style_set_text_color(&s_labelAccent, lv_color_hex(Theme::ACCENT));
|
||||
|
||||
lv_style_set_bg_color(&s_btn, lv_color_hex(Theme::BG_ELEVATED));
|
||||
lv_style_set_border_color(&s_btn, lv_color_hex(Theme::BORDER));
|
||||
lv_style_set_text_color(&s_btn, lv_color_hex(Theme::TEXT_PRIMARY));
|
||||
|
||||
lv_style_set_bg_color(&s_btnPressed, lv_color_hex(Theme::PRIMARY_SUBTLE));
|
||||
lv_style_set_border_color(&s_btnPressed, lv_color_hex(Theme::PRIMARY));
|
||||
lv_style_set_text_color(&s_btnPressed, lv_color_hex(Theme::ACCENT));
|
||||
|
||||
lv_style_set_border_color(&s_btnFocused, lv_color_hex(Theme::PRIMARY));
|
||||
lv_style_set_outline_color(&s_btnFocused, lv_color_hex(Theme::ACCENT));
|
||||
|
||||
lv_style_set_bg_color(&s_bar, lv_color_hex(Theme::BORDER));
|
||||
lv_style_set_bg_color(&s_barIndicator, lv_color_hex(Theme::PRIMARY));
|
||||
|
||||
lv_style_set_bg_color(&s_switch, lv_color_hex(Theme::BORDER));
|
||||
lv_style_set_bg_color(&s_switchChecked, lv_color_hex(Theme::PRIMARY));
|
||||
|
||||
lv_style_set_bg_color(&s_textarea, lv_color_hex(Theme::BG_ELEVATED));
|
||||
lv_style_set_border_color(&s_textarea, lv_color_hex(Theme::BORDER));
|
||||
lv_style_set_text_color(&s_textarea, lv_color_hex(Theme::TEXT_PRIMARY));
|
||||
|
||||
lv_style_set_border_color(&s_textareaFocused, lv_color_hex(Theme::PRIMARY));
|
||||
|
||||
lv_style_set_bg_color(&s_list, lv_color_hex(Theme::BG));
|
||||
|
||||
lv_style_set_bg_color(&s_listBtn, lv_color_hex(Theme::BG));
|
||||
lv_style_set_text_color(&s_listBtn, lv_color_hex(Theme::TEXT_PRIMARY));
|
||||
lv_style_set_border_color(&s_listBtn, lv_color_hex(Theme::DIVIDER));
|
||||
|
||||
lv_style_set_bg_color(&s_listBtnFocused, lv_color_hex(Theme::BG_HOVER));
|
||||
lv_style_set_text_color(&s_listBtnFocused, lv_color_hex(Theme::TEXT_PRIMARY));
|
||||
lv_style_set_border_color(&s_listBtnFocused, lv_color_hex(Theme::PRIMARY));
|
||||
|
||||
lv_style_set_bg_color(&s_dropdown, lv_color_hex(Theme::BG_ELEVATED));
|
||||
lv_style_set_border_color(&s_dropdown, lv_color_hex(Theme::BORDER));
|
||||
lv_style_set_text_color(&s_dropdown, lv_color_hex(Theme::TEXT_PRIMARY));
|
||||
|
||||
lv_style_set_bg_color(&s_slider, lv_color_hex(Theme::BORDER));
|
||||
|
||||
lv_style_set_text_color(&s_sectionHeader, lv_color_hex(Theme::TEXT_SECONDARY));
|
||||
lv_style_set_border_color(&s_sectionHeader, lv_color_hex(Theme::DIVIDER));
|
||||
|
||||
lv_style_set_bg_color(&s_modal, lv_color_hex(Theme::BG_SURFACE));
|
||||
lv_style_set_border_color(&s_modal, lv_color_hex(Theme::BORDER));
|
||||
|
||||
lv_style_set_bg_color(&s_scrollbar, lv_color_hex(Theme::TEXT_MUTED));
|
||||
|
||||
lv_style_set_bg_color(&s_roller, lv_color_hex(Theme::BG));
|
||||
lv_style_set_text_color(&s_roller, lv_color_hex(Theme::TEXT_SECONDARY));
|
||||
|
||||
lv_style_set_bg_color(&s_statusBar, lv_color_hex(Theme::STATUS_BG));
|
||||
lv_style_set_border_color(&s_statusBar, lv_color_hex(Theme::DIVIDER));
|
||||
|
||||
lv_style_set_bg_color(&s_statusToast, lv_color_hex(Theme::TOAST_BG));
|
||||
lv_style_set_text_color(&s_statusToast, lv_color_hex(Theme::BG));
|
||||
|
||||
lv_style_set_bg_color(&s_tabBar, lv_color_hex(Theme::TAB_BG));
|
||||
lv_style_set_border_color(&s_tabBar, lv_color_hex(Theme::DIVIDER));
|
||||
|
||||
lv_style_set_bg_color(&s_tabCell, lv_color_hex(Theme::TAB_BG));
|
||||
lv_style_set_text_color(&s_tabCell, lv_color_hex(Theme::TAB_INACTIVE));
|
||||
|
||||
lv_style_set_bg_color(&s_tabCellActive, lv_color_hex(Theme::TAB_ACTIVE_BG));
|
||||
lv_style_set_border_color(&s_tabCellActive, lv_color_hex(Theme::PRIMARY));
|
||||
lv_style_set_text_color(&s_tabCellActive, lv_color_hex(Theme::TAB_ACTIVE));
|
||||
|
||||
lv_style_set_bg_color(&s_badge, lv_color_hex(Theme::BADGE_BG));
|
||||
lv_style_set_text_color(&s_badge, lv_color_hex(Theme::BG));
|
||||
}
|
||||
|
||||
void init(lv_disp_t* disp) {
|
||||
// Screen background (LV_USE_THEME_DEFAULT is disabled in lv_conf.h).
|
||||
lv_style_init(&s_screen);
|
||||
lv_style_set_bg_color(&s_screen, lv_color_hex(Theme::BG));
|
||||
lv_style_set_bg_opa(&s_screen, LV_OPA_COVER);
|
||||
lv_style_set_text_color(&s_screen, lv_color_hex(Theme::TEXT_PRIMARY));
|
||||
lv_style_set_text_font(&s_screen, &lv_font_ratdeck_14);
|
||||
|
||||
// Labels
|
||||
lv_style_init(&s_label);
|
||||
lv_style_set_text_color(&s_label, lv_color_hex(Theme::TEXT_PRIMARY));
|
||||
lv_style_set_text_font(&s_label, &lv_font_ratdeck_14);
|
||||
|
||||
lv_style_init(&s_labelMuted);
|
||||
lv_style_set_text_color(&s_labelMuted, lv_color_hex(Theme::TEXT_SECONDARY));
|
||||
lv_style_set_text_font(&s_labelMuted, &lv_font_ratdeck_12);
|
||||
|
||||
lv_style_init(&s_labelAccent);
|
||||
lv_style_set_text_color(&s_labelAccent, lv_color_hex(Theme::ACCENT));
|
||||
lv_style_set_text_font(&s_labelAccent, &lv_font_ratdeck_14);
|
||||
|
||||
// Buttons
|
||||
lv_style_init(&s_btn);
|
||||
lv_style_set_bg_color(&s_btn, lv_color_hex(Theme::BG_ELEVATED));
|
||||
lv_style_set_bg_opa(&s_btn, LV_OPA_COVER);
|
||||
lv_style_set_border_color(&s_btn, lv_color_hex(Theme::BORDER));
|
||||
lv_style_set_border_width(&s_btn, 1);
|
||||
lv_style_set_text_color(&s_btn, lv_color_hex(Theme::TEXT_PRIMARY));
|
||||
lv_style_set_text_font(&s_btn, &lv_font_ratdeck_12);
|
||||
lv_style_set_radius(&s_btn, 3);
|
||||
lv_style_set_pad_top(&s_btn, 5);
|
||||
@@ -73,56 +144,42 @@ void init(lv_disp_t* disp) {
|
||||
lv_style_set_pad_right(&s_btn, 8);
|
||||
|
||||
lv_style_init(&s_btnPressed);
|
||||
lv_style_set_bg_color(&s_btnPressed, lv_color_hex(Theme::PRIMARY_SUBTLE));
|
||||
lv_style_set_border_color(&s_btnPressed, lv_color_hex(Theme::PRIMARY));
|
||||
lv_style_set_border_width(&s_btnPressed, 1);
|
||||
lv_style_set_text_color(&s_btnPressed, lv_color_hex(Theme::ACCENT));
|
||||
|
||||
lv_style_init(&s_btnFocused);
|
||||
lv_style_set_border_color(&s_btnFocused, lv_color_hex(Theme::PRIMARY));
|
||||
lv_style_set_border_width(&s_btnFocused, 2);
|
||||
lv_style_set_outline_color(&s_btnFocused, lv_color_hex(Theme::ACCENT));
|
||||
lv_style_set_outline_width(&s_btnFocused, 1);
|
||||
lv_style_set_outline_pad(&s_btnFocused, 0);
|
||||
|
||||
// Bar
|
||||
lv_style_init(&s_bar);
|
||||
lv_style_set_bg_color(&s_bar, lv_color_hex(Theme::BORDER));
|
||||
lv_style_set_bg_opa(&s_bar, LV_OPA_COVER);
|
||||
lv_style_set_radius(&s_bar, 2);
|
||||
|
||||
lv_style_init(&s_barIndicator);
|
||||
lv_style_set_bg_color(&s_barIndicator, lv_color_hex(Theme::PRIMARY));
|
||||
lv_style_set_bg_opa(&s_barIndicator, LV_OPA_COVER);
|
||||
lv_style_set_radius(&s_barIndicator, 2);
|
||||
|
||||
// Switch (LV_USE_SWITCH currently 0; kept for future lv_menu rewrite)
|
||||
lv_style_init(&s_switch);
|
||||
lv_style_set_bg_color(&s_switch, lv_color_hex(Theme::BORDER));
|
||||
lv_style_set_bg_opa(&s_switch, LV_OPA_COVER);
|
||||
lv_style_set_radius(&s_switch, LV_RADIUS_CIRCLE);
|
||||
|
||||
lv_style_init(&s_switchChecked);
|
||||
lv_style_set_bg_color(&s_switchChecked, lv_color_hex(Theme::PRIMARY));
|
||||
|
||||
// Textarea
|
||||
lv_style_init(&s_textarea);
|
||||
lv_style_set_bg_color(&s_textarea, lv_color_hex(Theme::BG_ELEVATED));
|
||||
lv_style_set_bg_opa(&s_textarea, LV_OPA_COVER);
|
||||
lv_style_set_border_color(&s_textarea, lv_color_hex(Theme::BORDER));
|
||||
lv_style_set_border_width(&s_textarea, 1);
|
||||
lv_style_set_text_color(&s_textarea, lv_color_hex(Theme::TEXT_PRIMARY));
|
||||
lv_style_set_text_font(&s_textarea, &lv_font_ratdeck_14);
|
||||
lv_style_set_radius(&s_textarea, 3);
|
||||
lv_style_set_pad_all(&s_textarea, 6);
|
||||
|
||||
lv_style_init(&s_textareaFocused);
|
||||
lv_style_set_border_color(&s_textareaFocused, lv_color_hex(Theme::PRIMARY));
|
||||
lv_style_set_border_width(&s_textareaFocused, 1);
|
||||
|
||||
// List container
|
||||
lv_style_init(&s_list);
|
||||
lv_style_set_bg_color(&s_list, lv_color_hex(Theme::BG));
|
||||
lv_style_set_bg_opa(&s_list, LV_OPA_COVER);
|
||||
lv_style_set_pad_all(&s_list, 0);
|
||||
lv_style_set_pad_row(&s_list, 0);
|
||||
@@ -131,11 +188,8 @@ void init(lv_disp_t* disp) {
|
||||
|
||||
// List items
|
||||
lv_style_init(&s_listBtn);
|
||||
lv_style_set_bg_color(&s_listBtn, lv_color_hex(Theme::BG));
|
||||
lv_style_set_bg_opa(&s_listBtn, LV_OPA_COVER);
|
||||
lv_style_set_text_color(&s_listBtn, lv_color_hex(Theme::TEXT_PRIMARY));
|
||||
lv_style_set_text_font(&s_listBtn, &lv_font_ratdeck_12);
|
||||
lv_style_set_border_color(&s_listBtn, lv_color_hex(Theme::DIVIDER));
|
||||
lv_style_set_border_width(&s_listBtn, 1);
|
||||
lv_style_set_border_side(&s_listBtn, LV_BORDER_SIDE_BOTTOM);
|
||||
lv_style_set_pad_top(&s_listBtn, 5);
|
||||
@@ -146,37 +200,28 @@ void init(lv_disp_t* disp) {
|
||||
|
||||
// List item focused - left-bar indicator + hover background
|
||||
lv_style_init(&s_listBtnFocused);
|
||||
lv_style_set_bg_color(&s_listBtnFocused, lv_color_hex(Theme::BG_HOVER));
|
||||
lv_style_set_bg_opa(&s_listBtnFocused, LV_OPA_COVER);
|
||||
lv_style_set_text_color(&s_listBtnFocused, lv_color_hex(Theme::TEXT_PRIMARY));
|
||||
lv_style_set_border_color(&s_listBtnFocused, lv_color_hex(Theme::PRIMARY));
|
||||
lv_style_set_border_width(&s_listBtnFocused, 3);
|
||||
lv_style_set_border_side(&s_listBtnFocused, LV_BORDER_SIDE_LEFT);
|
||||
|
||||
// Dropdown
|
||||
lv_style_init(&s_dropdown);
|
||||
lv_style_set_bg_color(&s_dropdown, lv_color_hex(Theme::BG_ELEVATED));
|
||||
lv_style_set_bg_opa(&s_dropdown, LV_OPA_COVER);
|
||||
lv_style_set_border_color(&s_dropdown, lv_color_hex(Theme::BORDER));
|
||||
lv_style_set_border_width(&s_dropdown, 1);
|
||||
lv_style_set_text_color(&s_dropdown, lv_color_hex(Theme::TEXT_PRIMARY));
|
||||
lv_style_set_text_font(&s_dropdown, &lv_font_ratdeck_12);
|
||||
lv_style_set_radius(&s_dropdown, 3);
|
||||
lv_style_set_pad_all(&s_dropdown, 4);
|
||||
|
||||
// Slider
|
||||
lv_style_init(&s_slider);
|
||||
lv_style_set_bg_color(&s_slider, lv_color_hex(Theme::BORDER));
|
||||
lv_style_set_bg_opa(&s_slider, LV_OPA_COVER);
|
||||
lv_style_set_radius(&s_slider, 3);
|
||||
|
||||
// Section header (for list section dividers like "Contacts (3)")
|
||||
lv_style_init(&s_sectionHeader);
|
||||
lv_style_set_bg_opa(&s_sectionHeader, LV_OPA_TRANSP);
|
||||
lv_style_set_text_color(&s_sectionHeader, lv_color_hex(Theme::TEXT_SECONDARY));
|
||||
lv_style_set_text_font(&s_sectionHeader, &lv_font_ratdeck_10);
|
||||
lv_style_set_text_letter_space(&s_sectionHeader, 0);
|
||||
lv_style_set_border_color(&s_sectionHeader, lv_color_hex(Theme::DIVIDER));
|
||||
lv_style_set_border_width(&s_sectionHeader, 1);
|
||||
lv_style_set_border_side(&s_sectionHeader, LV_BORDER_SIDE_BOTTOM);
|
||||
lv_style_set_pad_top(&s_sectionHeader, 8);
|
||||
@@ -185,9 +230,7 @@ void init(lv_disp_t* disp) {
|
||||
|
||||
// Modal overlay
|
||||
lv_style_init(&s_modal);
|
||||
lv_style_set_bg_color(&s_modal, lv_color_hex(Theme::BG_SURFACE));
|
||||
lv_style_set_bg_opa(&s_modal, LV_OPA_COVER);
|
||||
lv_style_set_border_color(&s_modal, lv_color_hex(Theme::BORDER));
|
||||
lv_style_set_border_width(&s_modal, 1);
|
||||
lv_style_set_radius(&s_modal, 4);
|
||||
lv_style_set_pad_all(&s_modal, 10);
|
||||
@@ -198,7 +241,6 @@ void init(lv_disp_t* disp) {
|
||||
|
||||
// Scrollbar
|
||||
lv_style_init(&s_scrollbar);
|
||||
lv_style_set_bg_color(&s_scrollbar, lv_color_hex(Theme::TEXT_MUTED));
|
||||
lv_style_set_bg_opa(&s_scrollbar, LV_OPA_40);
|
||||
lv_style_set_radius(&s_scrollbar, LV_RADIUS_CIRCLE);
|
||||
lv_style_set_width(&s_scrollbar, 3);
|
||||
@@ -206,34 +248,26 @@ void init(lv_disp_t* disp) {
|
||||
|
||||
// Roller - no border so selected row highlight extends edge-to-edge
|
||||
lv_style_init(&s_roller);
|
||||
lv_style_set_bg_color(&s_roller, lv_color_hex(Theme::BG));
|
||||
lv_style_set_bg_opa(&s_roller, LV_OPA_COVER);
|
||||
lv_style_set_text_color(&s_roller, lv_color_hex(Theme::TEXT_SECONDARY));
|
||||
lv_style_set_text_font(&s_roller, &lv_font_ratdeck_14);
|
||||
lv_style_set_border_width(&s_roller, 0);
|
||||
|
||||
// Persistent shell bars.
|
||||
lv_style_init(&s_statusBar);
|
||||
lv_style_set_bg_color(&s_statusBar, lv_color_hex(Theme::STATUS_BG));
|
||||
lv_style_set_bg_opa(&s_statusBar, LV_OPA_COVER);
|
||||
lv_style_set_border_color(&s_statusBar, lv_color_hex(Theme::DIVIDER));
|
||||
lv_style_set_border_width(&s_statusBar, 1);
|
||||
lv_style_set_border_side(&s_statusBar, LV_BORDER_SIDE_BOTTOM);
|
||||
lv_style_set_pad_all(&s_statusBar, 0);
|
||||
lv_style_set_radius(&s_statusBar, 0);
|
||||
|
||||
lv_style_init(&s_statusToast);
|
||||
lv_style_set_bg_color(&s_statusToast, lv_color_hex(Theme::TOAST_BG));
|
||||
lv_style_set_bg_opa(&s_statusToast, LV_OPA_COVER);
|
||||
lv_style_set_text_color(&s_statusToast, lv_color_hex(Theme::BG));
|
||||
lv_style_set_text_font(&s_statusToast, &lv_font_ratdeck_12);
|
||||
lv_style_set_pad_all(&s_statusToast, 0);
|
||||
lv_style_set_radius(&s_statusToast, 0);
|
||||
|
||||
lv_style_init(&s_tabBar);
|
||||
lv_style_set_bg_color(&s_tabBar, lv_color_hex(Theme::TAB_BG));
|
||||
lv_style_set_bg_opa(&s_tabBar, LV_OPA_COVER);
|
||||
lv_style_set_border_color(&s_tabBar, lv_color_hex(Theme::DIVIDER));
|
||||
lv_style_set_border_width(&s_tabBar, 1);
|
||||
lv_style_set_border_side(&s_tabBar, LV_BORDER_SIDE_TOP);
|
||||
lv_style_set_pad_all(&s_tabBar, 0);
|
||||
@@ -241,36 +275,36 @@ void init(lv_disp_t* disp) {
|
||||
lv_style_set_radius(&s_tabBar, 0);
|
||||
|
||||
lv_style_init(&s_tabCell);
|
||||
lv_style_set_bg_color(&s_tabCell, lv_color_hex(Theme::TAB_BG));
|
||||
lv_style_set_bg_opa(&s_tabCell, LV_OPA_COVER);
|
||||
lv_style_set_border_width(&s_tabCell, 0);
|
||||
lv_style_set_text_color(&s_tabCell, lv_color_hex(Theme::TAB_INACTIVE));
|
||||
lv_style_set_text_font(&s_tabCell, &lv_font_ratdeck_10);
|
||||
lv_style_set_pad_all(&s_tabCell, 0);
|
||||
lv_style_set_radius(&s_tabCell, 0);
|
||||
|
||||
lv_style_init(&s_tabCellActive);
|
||||
lv_style_set_bg_color(&s_tabCellActive, lv_color_hex(Theme::TAB_ACTIVE_BG));
|
||||
lv_style_set_bg_opa(&s_tabCellActive, LV_OPA_COVER);
|
||||
lv_style_set_border_color(&s_tabCellActive, lv_color_hex(Theme::PRIMARY));
|
||||
lv_style_set_border_width(&s_tabCellActive, 2);
|
||||
lv_style_set_border_side(&s_tabCellActive, LV_BORDER_SIDE_TOP);
|
||||
lv_style_set_text_color(&s_tabCellActive, lv_color_hex(Theme::TAB_ACTIVE));
|
||||
|
||||
lv_style_init(&s_badge);
|
||||
lv_style_set_bg_color(&s_badge, lv_color_hex(Theme::BADGE_BG));
|
||||
lv_style_set_bg_opa(&s_badge, LV_OPA_COVER);
|
||||
lv_style_set_text_color(&s_badge, lv_color_hex(Theme::BG));
|
||||
lv_style_set_text_font(&s_badge, &lv_font_ratdeck_10);
|
||||
lv_style_set_radius(&s_badge, 3);
|
||||
lv_style_set_pad_all(&s_badge, 0);
|
||||
|
||||
applyColors();
|
||||
|
||||
// Apply screen style to default theme
|
||||
lv_obj_add_style(lv_scr_act(), &s_screen, 0);
|
||||
|
||||
Serial.println("[LVGL] Ratdeck field-console theme initialized");
|
||||
}
|
||||
|
||||
void refresh() {
|
||||
applyColors();
|
||||
lv_obj_report_style_change(NULL);
|
||||
}
|
||||
|
||||
// Existing accessors (16)
|
||||
lv_style_t* styleScreen() { return &s_screen; }
|
||||
lv_style_t* styleLabel() { return &s_label; }
|
||||
|
||||
@@ -7,6 +7,9 @@ namespace LvTheme {
|
||||
|
||||
void init(lv_disp_t* disp);
|
||||
|
||||
// Re-apply palette colors to all shared styles after a Theme scheme change.
|
||||
void refresh();
|
||||
|
||||
// Style accessors - existing (16)
|
||||
lv_style_t* styleScreen();
|
||||
lv_style_t* styleLabel();
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
#include "Theme.h"
|
||||
|
||||
// Dark column is the original Ratdeck palette, verbatim. Light column is
|
||||
// derived: same hue family, brand greens/cyan darkened for light-bg contrast.
|
||||
#define THEME_COLORS(X) \
|
||||
X(BG, 0x05080A, 0xF2F6F4) \
|
||||
X(BG_ELEVATED, 0x0C1418, 0xFFFFFF) \
|
||||
X(BG_SURFACE, 0x121D23, 0xE4ECE9) \
|
||||
X(BG_HOVER, 0x183033, 0xD5E5DE) \
|
||||
X(TEXT_PRIMARY, 0xE4F3F0, 0x0E1F1B) \
|
||||
X(TEXT_SECONDARY, 0x91A8A5, 0x47615C) \
|
||||
X(TEXT_MUTED, 0x526866, 0x8CA29D) \
|
||||
X(PRIMARY, 0x00E06D, 0x008F4C) \
|
||||
X(PRIMARY_MUTED, 0x00A853, 0x00713C) \
|
||||
X(PRIMARY_SUBTLE, 0x06251A, 0xD7F0E1) \
|
||||
X(ACCENT, 0x4FD7FF, 0x0077A8) \
|
||||
X(SUCCESS, 0x31E981, 0x168A4D) \
|
||||
X(WARNING_CLR, 0xF0C04F, 0x9A7400) \
|
||||
X(ERROR_CLR, 0xFF5C6C, 0xD02E44) \
|
||||
X(ERROR_SUBTLE, 0x2A0E0E, 0xF6DBDD) \
|
||||
X(BORDER, 0x21383B, 0xC2D3CE) \
|
||||
X(BORDER_ACTIVE, 0x00E06D, 0x008F4C) \
|
||||
X(DIVIDER, 0x102329, 0xDCE6E2) \
|
||||
X(MSG_OUT_BG, 0x082719, 0xD7F0E1) \
|
||||
X(MSG_IN_BG, 0x0B151C, 0xE7EDF1) \
|
||||
X(STATUS_BG, 0x071014, 0xE4ECE9) \
|
||||
X(STATUS_FLASH, 0x12351F, 0xBFE8CE) \
|
||||
X(TAB_BG, 0x081115, 0xE4ECE9) \
|
||||
X(TAB_ACTIVE_BG, 0x0B241B, 0xD7F0E1) \
|
||||
X(TAB_ACTIVE, 0x00E06D, 0x00713C) \
|
||||
X(TAB_INACTIVE, 0x91A8A5, 0x47615C) \
|
||||
X(BADGE_BG, 0xFF5C6C, 0xD02E44) \
|
||||
X(TOAST_BG, 0x00E06D, 0x008F4C)
|
||||
|
||||
namespace Theme {
|
||||
|
||||
namespace {
|
||||
Scheme g_scheme = Scheme::DARK;
|
||||
}
|
||||
|
||||
#define THEME_DEFINE(name, dark, light) uint32_t name = dark;
|
||||
THEME_COLORS(THEME_DEFINE)
|
||||
#undef THEME_DEFINE
|
||||
|
||||
void setScheme(Scheme s) {
|
||||
g_scheme = s;
|
||||
const bool light = (s == Scheme::LIGHT);
|
||||
#define THEME_APPLY(name, d, l) name = light ? (uint32_t)(l) : (uint32_t)(d);
|
||||
THEME_COLORS(THEME_APPLY)
|
||||
#undef THEME_APPLY
|
||||
}
|
||||
|
||||
Scheme scheme() { return g_scheme; }
|
||||
|
||||
} // namespace Theme
|
||||
+38
-28
@@ -4,51 +4,61 @@
|
||||
|
||||
// =============================================================================
|
||||
// Ratdeck design constants
|
||||
// Dark field-console palette tuned for the 320x240 LVGL surface.
|
||||
// Field-console palette tuned for the 320x240 LVGL surface.
|
||||
// Colors are runtime-switchable (Dark = original palette, Light = derived);
|
||||
// the value tables live in Theme.cpp. Identifiers keep their original
|
||||
// spelling so call sites read the active palette transparently.
|
||||
// =============================================================================
|
||||
|
||||
namespace Theme {
|
||||
|
||||
enum class Scheme : uint8_t { DARK = 0, LIGHT = 1 };
|
||||
|
||||
// Switch the active palette (boot default: DARK, the historical palette).
|
||||
void setScheme(Scheme s);
|
||||
Scheme scheme();
|
||||
|
||||
// --- Backgrounds ---
|
||||
constexpr uint32_t BG = 0x05080A; // Screen base
|
||||
constexpr uint32_t BG_ELEVATED = 0x0C1418; // Buttons, inputs, list items
|
||||
constexpr uint32_t BG_SURFACE = 0x121D23; // Modals, dropdowns, shell bars
|
||||
constexpr uint32_t BG_HOVER = 0x183033; // Focus/hover background
|
||||
extern uint32_t BG; // Screen base
|
||||
extern uint32_t BG_ELEVATED; // Buttons, inputs, list items
|
||||
extern uint32_t BG_SURFACE; // Modals, dropdowns, shell bars
|
||||
extern uint32_t BG_HOVER; // Focus/hover background
|
||||
|
||||
// --- Text hierarchy ---
|
||||
constexpr uint32_t TEXT_PRIMARY = 0xE4F3F0; // Primary copy
|
||||
constexpr uint32_t TEXT_SECONDARY = 0x91A8A5; // Metadata and labels
|
||||
constexpr uint32_t TEXT_MUTED = 0x526866; // Disabled, placeholders
|
||||
extern uint32_t TEXT_PRIMARY; // Primary copy
|
||||
extern uint32_t TEXT_SECONDARY; // Metadata and labels
|
||||
extern uint32_t TEXT_MUTED; // Disabled, placeholders
|
||||
|
||||
// --- Brand / interactive ---
|
||||
constexpr uint32_t PRIMARY = 0x00E06D; // Signal green
|
||||
constexpr uint32_t PRIMARY_MUTED = 0x00A853; // Pressed/active state
|
||||
constexpr uint32_t PRIMARY_SUBTLE = 0x06251A; // Selection backgrounds
|
||||
constexpr uint32_t ACCENT = 0x4FD7FF; // Console cyan accent
|
||||
extern uint32_t PRIMARY; // Signal green
|
||||
extern uint32_t PRIMARY_MUTED; // Pressed/active state
|
||||
extern uint32_t PRIMARY_SUBTLE; // Selection backgrounds
|
||||
extern uint32_t ACCENT; // Console cyan accent
|
||||
|
||||
// --- Status ---
|
||||
constexpr uint32_t SUCCESS = 0x31E981; // Online, delivered, connected
|
||||
constexpr uint32_t WARNING_CLR = 0xF0C04F; // Queued, pending, caution
|
||||
constexpr uint32_t ERROR_CLR = 0xFF5C6C; // Failed, error, critical
|
||||
extern uint32_t SUCCESS; // Online, delivered, connected
|
||||
extern uint32_t WARNING_CLR; // Queued, pending, caution
|
||||
extern uint32_t ERROR_CLR; // Failed, error, critical
|
||||
extern uint32_t ERROR_SUBTLE; // Armed destructive backgrounds
|
||||
|
||||
// --- Structural ---
|
||||
constexpr uint32_t BORDER = 0x21383B; // Subtle structural borders
|
||||
constexpr uint32_t BORDER_ACTIVE = 0x00E06D; // Focus borders
|
||||
constexpr uint32_t DIVIDER = 0x102329; // Hairline separators
|
||||
extern uint32_t BORDER; // Subtle structural borders
|
||||
extern uint32_t BORDER_ACTIVE; // Focus borders
|
||||
extern uint32_t DIVIDER; // Hairline separators
|
||||
|
||||
// --- Messages ---
|
||||
constexpr uint32_t MSG_OUT_BG = 0x082719; // Outgoing bubble
|
||||
constexpr uint32_t MSG_IN_BG = 0x0B151C; // Incoming bubble
|
||||
extern uint32_t MSG_OUT_BG; // Outgoing bubble
|
||||
extern uint32_t MSG_IN_BG; // Incoming bubble
|
||||
|
||||
// --- Shell ---
|
||||
constexpr uint32_t STATUS_BG = 0x071014;
|
||||
constexpr uint32_t STATUS_FLASH = 0x12351F;
|
||||
constexpr uint32_t TAB_BG = 0x081115;
|
||||
constexpr uint32_t TAB_ACTIVE_BG = 0x0B241B;
|
||||
constexpr uint32_t TAB_ACTIVE = 0x00E06D;
|
||||
constexpr uint32_t TAB_INACTIVE = 0x91A8A5;
|
||||
constexpr uint32_t BADGE_BG = 0xFF5C6C;
|
||||
constexpr uint32_t TOAST_BG = 0x00E06D;
|
||||
extern uint32_t STATUS_BG;
|
||||
extern uint32_t STATUS_FLASH;
|
||||
extern uint32_t TAB_BG;
|
||||
extern uint32_t TAB_ACTIVE_BG;
|
||||
extern uint32_t TAB_ACTIVE;
|
||||
extern uint32_t TAB_INACTIVE;
|
||||
extern uint32_t BADGE_BG;
|
||||
extern uint32_t TOAST_BG;
|
||||
|
||||
|
||||
// --- Layout Metrics ---
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#include "UIManager.h"
|
||||
#include "Theme.h"
|
||||
#include "LvTheme.h"
|
||||
#include "LvInput.h"
|
||||
|
||||
// --- LvScreen base ---
|
||||
|
||||
@@ -90,6 +91,15 @@ void UIManager::forceRedraw() {
|
||||
lv_obj_invalidate(lv_scr_act());
|
||||
}
|
||||
|
||||
void UIManager::applyTheme() {
|
||||
LvTheme::refresh();
|
||||
if (_lvContent) lv_obj_set_style_bg_color(_lvContent, lv_color_hex(Theme::BG), 0);
|
||||
_lvStatusBar.applyTheme();
|
||||
_lvTabBar.refreshTabs();
|
||||
LvInput::applyTheme();
|
||||
forceRedraw();
|
||||
}
|
||||
|
||||
bool UIManager::handleKey(const KeyEvent& event) {
|
||||
if (_currentLvScreen) {
|
||||
return _currentLvScreen->handleKey(event);
|
||||
|
||||
@@ -42,6 +42,9 @@ public:
|
||||
// Force full redraw
|
||||
void forceRedraw();
|
||||
|
||||
// Re-apply palette to shared styles and persistent shell after a theme switch
|
||||
void applyTheme();
|
||||
|
||||
// Handle key event — routes to current screen
|
||||
bool handleKey(const KeyEvent& event);
|
||||
bool handleLongPress();
|
||||
|
||||
@@ -38,7 +38,7 @@ void styleChoice(lv_obj_t* box, bool selected, bool destructive, bool armed) {
|
||||
if (!box) return;
|
||||
uint32_t border = selected ? (destructive && armed ? Theme::ERROR_CLR : Theme::PRIMARY) : Theme::BORDER;
|
||||
uint32_t bg = selected ? Theme::PRIMARY_SUBTLE : Theme::BG_ELEVATED;
|
||||
if (destructive && armed) bg = 0x2A0E0E;
|
||||
if (destructive && armed) bg = Theme::ERROR_SUBTLE;
|
||||
lv_obj_set_style_bg_color(box, lv_color_hex(bg), 0);
|
||||
lv_obj_set_style_border_color(box, lv_color_hex(border), 0);
|
||||
lv_obj_set_style_border_width(box, selected ? 2 : 1, 0);
|
||||
|
||||
@@ -480,6 +480,17 @@ void LvSettingsScreen::buildItems() {
|
||||
[&s]() { return s.brightness; }, [&s](int v) { s.brightness = v; },
|
||||
[](int v) { return String(v) + "%"; }, 5, 100, 5});
|
||||
idx++;
|
||||
{
|
||||
SettingItem themeItem;
|
||||
themeItem.label = "Theme";
|
||||
themeItem.type = SettingType::ENUM_CHOICE;
|
||||
themeItem.getter = [&s]() { return s.themeLight ? 1 : 0; };
|
||||
themeItem.setter = [&s](int v) { s.themeLight = (v != 0); };
|
||||
themeItem.minVal = 0; themeItem.maxVal = 1; themeItem.step = 1;
|
||||
themeItem.enumLabels = {"Dark", "Light"};
|
||||
_items.push_back(themeItem);
|
||||
idx++;
|
||||
}
|
||||
_items.push_back({"Dim After", SettingType::INTEGER,
|
||||
[&s]() { return s.screenDimTimeout; }, [&s](int v) { s.screenDimTimeout = v; },
|
||||
[](int v) { return String(v) + "s"; }, 5, 300, 5});
|
||||
@@ -2024,6 +2035,15 @@ String LvSettingsScreen::freqFormatWithCursor() const {
|
||||
void LvSettingsScreen::applyAndSave() {
|
||||
if (!_cfg) return;
|
||||
auto& s = _cfg->settings();
|
||||
// Theme switch applies live: palette globals -> shared styles -> shell.
|
||||
// Our own rows re-read Theme:: on the rebuild that follows every commit.
|
||||
Theme::Scheme want = s.themeLight ? Theme::Scheme::LIGHT : Theme::Scheme::DARK;
|
||||
if (want != Theme::scheme()) {
|
||||
Theme::setScheme(want);
|
||||
if (_ui) _ui->applyTheme();
|
||||
if (_screen) lv_obj_set_style_bg_color(_screen, lv_color_hex(Theme::BG), 0);
|
||||
if (_scrollContainer) lv_obj_set_style_bg_color(_scrollContainer, lv_color_hex(Theme::BG), 0);
|
||||
}
|
||||
if (_power) {
|
||||
_power->setBrightness(s.brightness);
|
||||
_power->setDimTimeout(s.screenDimTimeout);
|
||||
|
||||
Reference in New Issue
Block a user