mirror of
https://github.com/MarekZegare4/MeshCore-Solo.git
synced 2026-11-01 10:21:59 +00:00
feat(l2): light theme reworked - a tint for selected, grey values, quiet edges
The light theme read as the dark one inverted. Now something selected (a tab, a segment, a tile, Fav) is a tint of the accent with the accent's text (SEL_*), the full accent fill stays for the primary action; setting values read in grey (VALUE); popups, the banner and the toast have a quiet grey edge (EDGE), the toast is a dark pill; neutral greys the RGB565 panel keeps neutral; the big clock graphite. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -100,9 +100,9 @@ void UITask::channelMenu(int idx) {
|
||||
};
|
||||
for (auto& b : btns) {
|
||||
if (b.act == A_READ && !unread) continue;
|
||||
lv_obj_t* bt = barButton(acts, b.text, onChanAction, b.act, b.act == A_FAV && chanctl::favourite(_prefs, idx));
|
||||
lv_obj_t* bt = barButton(acts, b.text, onChanAction, b.act);
|
||||
if (b.act == A_DELETE) s_del_lbl = lv_obj_get_child(bt, 0);
|
||||
if (b.act == A_FAV) s_fav_btn = bt;
|
||||
if (b.act == A_FAV) { s_fav_btn = bt; styleSelected(bt, chanctl::favourite(_prefs, idx)); }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -124,7 +124,7 @@ void UITask::channelAction(uint8_t act) {
|
||||
bool on = !chanctl::favourite(_prefs, idx);
|
||||
chanctl::setFavourite(_prefs, idx, on);
|
||||
prefsSave();
|
||||
if (s_fav_btn) barButtonOn(s_fav_btn, on);
|
||||
if (s_fav_btn) styleSelected(s_fav_btn, on);
|
||||
showToast(on ? "Added to favourites" : "Removed from favourites", 1200);
|
||||
if (_screen == SCR_CHATS) { buildChats(); channelMenu(idx); } // list order / star
|
||||
break;
|
||||
|
||||
@@ -185,7 +185,8 @@ void UITask::conversationMenu(const uint8_t* pub_key) {
|
||||
}
|
||||
|
||||
lv_obj_t* acts = buttonBar(panel);
|
||||
s_fav_btn = barButton(acts, UI_SYMBOL_STAR " Fav", onConvAction, A_FAV, contactctl::favourite(ci));
|
||||
s_fav_btn = barButton(acts, UI_SYMBOL_STAR " Fav", onConvAction, A_FAV);
|
||||
styleSelected(s_fav_btn, contactctl::favourite(ci));
|
||||
if (!room && _core->dmUnread(ci.id.pub_key) > 0) barButton(acts, LV_SYMBOL_OK " Read", onConvAction, A_READ, false);
|
||||
barButton(acts, UI_SYMBOL_TACK, onConvAction, A_PIN, favslots::findContact(_prefs, ci.id.pub_key) >= 0);
|
||||
if (room) {
|
||||
@@ -208,7 +209,7 @@ void UITask::conversationAction(uint8_t act) {
|
||||
case A_FAV: {
|
||||
bool on = !contactctl::favourite(ci);
|
||||
if (!contactctl::setFavourite(ci.id.pub_key, on)) break;
|
||||
if (s_fav_btn) barButtonOn(s_fav_btn, on);
|
||||
if (s_fav_btn) styleSelected(s_fav_btn, on);
|
||||
showToast(on ? "Added to favourites" : "Removed from favourites", 1200);
|
||||
if (_screen == SCR_CHATS) { buildChats(); conversationMenu(s_key); } // star / filter
|
||||
break;
|
||||
|
||||
@@ -419,7 +419,7 @@ void UITask::setAccent(int idx) {
|
||||
theme::setAccent(idx);
|
||||
lvport::saveAccent(theme::s_accent);
|
||||
theme::install(lv_display_get_default());
|
||||
if (_toast) lv_obj_set_style_border_color(_toast, lv_color_hex(theme::ACCENT), 0);
|
||||
if (_toast) lv_obj_set_style_border_color(_toast, lv_color_hex(theme::EDGE), 0);
|
||||
_status_sig[0] = '\0'; // icons in the accent rebuilt
|
||||
refreshStatusBar();
|
||||
pinRowRefresh(); // the page, kept at its bottom where this row is
|
||||
|
||||
@@ -99,15 +99,15 @@ static void onPagesScrolled(lv_event_t* e) {
|
||||
dotsSet(lv_obj_get_scroll_x(s_pages) > lv_obj_get_width(s_pages) / 2);
|
||||
}
|
||||
|
||||
// A tile's look: on in the accent with dark text, off on the surface;
|
||||
// A tile's look: on in SEL_BG / SEL_FG (Theme.h), off on the surface;
|
||||
// `dim`: nothing to switch on this device.
|
||||
static void tileSet(int t, const char* icon, const char* cap, bool on, bool dim = false) {
|
||||
const uint32_t fg = on ? theme::ON_ACCENT : dim ? theme::TEXT_MUTED : theme::TEXT;
|
||||
const uint32_t fg = on ? theme::SEL_FG : dim ? theme::TEXT_MUTED : theme::TEXT;
|
||||
const uint8_t look = 1 | (on ? 2 : 0);
|
||||
if (s_look[t] != look) { // LVGL restyles, and redraws, on every set (the refresh is each second)
|
||||
s_look[t] = look;
|
||||
lv_obj_set_style_bg_color(s_tile[t], lv_color_hex(on ? theme::ACCENT : theme::SURFACE), 0);
|
||||
lv_obj_set_style_bg_color(s_tile[t], lv_color_hex(on ? theme::mix(theme::ACCENT, theme::BG, 75) : theme::SURFACE_2), LV_STATE_PRESSED);
|
||||
lv_obj_set_style_bg_color(s_tile[t], lv_color_hex(on ? theme::SEL_BG : theme::SURFACE), 0);
|
||||
lv_obj_set_style_bg_color(s_tile[t], lv_color_hex(on ? theme::SEL_PRESS : theme::SURFACE_2), LV_STATE_PRESSED);
|
||||
}
|
||||
setText(s_icon[t], icon);
|
||||
setText(s_cap[t], cap);
|
||||
|
||||
@@ -14,7 +14,10 @@
|
||||
//
|
||||
// Two themes, dark (the default) and light (Settings > Display > Theme). The
|
||||
// colours are set once, before anything is built (setLight()), so changing
|
||||
// the theme restarts the device.
|
||||
// the theme restarts the device. The light one isn't the dark one inverted:
|
||||
// something selected is a tint of the accent with the accent's text (SEL_*),
|
||||
// not a full accent fill -- that stays for the primary action and own
|
||||
// bubbles -- values read in grey, and popups and banners have a quiet edge.
|
||||
#include <src/themes/lv_theme_private.h> // lv_theme_t's fields, to layer a theme
|
||||
|
||||
namespace theme {
|
||||
@@ -28,6 +31,12 @@ namespace theme {
|
||||
static uint32_t TEXT_MUTED= 0x9A958C;
|
||||
static uint32_t OK = 0x6FCF6F; // delivered / relayed
|
||||
static uint32_t FAIL = 0xE5534B; // not delivered
|
||||
// Set from those by setAccent():
|
||||
static uint32_t SEL_BG = 0xFFB000; // something selected / on: a tab, a segment, a tile
|
||||
static uint32_t SEL_FG = 0x0B0B0D; // its text and icons
|
||||
static uint32_t SEL_PRESS = 0xC08400; // it pressed
|
||||
static uint32_t VALUE = 0xFFB000; // a setting's value (a choice, a typed value)
|
||||
static uint32_t EDGE = 0xFFB000; // the edge of a popup, the banner, the toast
|
||||
static bool s_light = false;
|
||||
|
||||
// Spacing (px)
|
||||
@@ -96,20 +105,34 @@ namespace theme {
|
||||
if (idx < 0 || idx >= ACCENT_COUNT) idx = 0;
|
||||
s_accent = idx;
|
||||
ACCENT = s_light ? ACCENTS[idx].light : ACCENTS[idx].col;
|
||||
if (s_light) {
|
||||
SEL_BG = mix(ACCENT, SURFACE, 20);
|
||||
SEL_FG = mix(ACCENT, 0x000000, 78);
|
||||
SEL_PRESS = mix(ACCENT, SURFACE, 32);
|
||||
VALUE = TEXT_MUTED;
|
||||
EDGE = SURFACE_2;
|
||||
} else {
|
||||
SEL_BG = ACCENT;
|
||||
SEL_FG = ON_ACCENT;
|
||||
SEL_PRESS = mix(ACCENT, BG, 75);
|
||||
VALUE = ACCENT;
|
||||
EDGE = ACCENT;
|
||||
}
|
||||
}
|
||||
// Before anything is built (the colours are copied into what is).
|
||||
static void setLight(bool on) {
|
||||
s_light = on;
|
||||
if (on) {
|
||||
BG = 0xF2F0EC; SURFACE = 0xFFFFFF; SURFACE_2 = 0xE2DFD9;
|
||||
ON_ACCENT = 0xFFFFFF; TEXT = 0x1C1B19; TEXT_MUTED = 0x6E6A62;
|
||||
// Neutral greys the panel's RGB565 keeps neutral (0xF3F3F1 came out pink).
|
||||
BG = 0xEFEFEF; SURFACE = 0xFFFFFF; SURFACE_2 = 0xDEDEDE;
|
||||
ON_ACCENT = 0xFFFFFF; TEXT = 0x1C1C1C; TEXT_MUTED = 0x6B6D6B;
|
||||
OK = 0x2E9A3E; FAIL = 0xD23A32;
|
||||
}
|
||||
setAccent(s_accent);
|
||||
}
|
||||
|
||||
// Over LVGL's default theme, for every widget: a checked button is
|
||||
// "selected" (the accent, ON_ACCENT text), buttons
|
||||
// "selected" (SEL_BG, SEL_FG text), buttons
|
||||
// have the card radius and give a little under the finger (the default
|
||||
// theme's transitions animate it both ways). Scrollbars are thin and hug
|
||||
// the edge, inside the padding every panel and list has, instead of the
|
||||
@@ -137,7 +160,7 @@ namespace theme {
|
||||
lv_style_set_transform_width(&s_press, -2);
|
||||
lv_style_set_transform_height(&s_press, -2);
|
||||
lv_style_init(&s_checked);
|
||||
lv_style_set_text_color(&s_checked, lv_color_hex(ON_ACCENT));
|
||||
|
||||
lv_style_init(&s_sbar);
|
||||
lv_style_set_width(&s_sbar, 3);
|
||||
lv_style_set_pad_right(&s_sbar, 2); // vertical bar: 2 px off the edge
|
||||
@@ -145,7 +168,8 @@ namespace theme {
|
||||
lv_style_set_pad_top(&s_sbar, RADIUS / 2);
|
||||
lv_style_set_pad_left(&s_sbar, RADIUS / 2);
|
||||
}
|
||||
lv_style_set_bg_color(&s_checked, lv_color_hex(ACCENT));
|
||||
lv_style_set_bg_color(&s_checked, lv_color_hex(SEL_BG));
|
||||
lv_style_set_text_color(&s_checked, lv_color_hex(SEL_FG));
|
||||
lv_theme_t* base = lv_theme_default_init(disp, lv_color_hex(ACCENT), lv_color_hex(ACCENT), !s_light, THEME_FONT_BODY);
|
||||
s_theme = *base;
|
||||
lv_theme_set_parent(&s_theme, base);
|
||||
|
||||
@@ -524,14 +524,15 @@ static void bannerBuild() {
|
||||
lv_obj_remove_flag(s_banner, LV_OBJ_FLAG_GESTURE_BUBBLE);
|
||||
lv_obj_set_size(s_banner, w - 12, LV_SIZE_CONTENT);
|
||||
lv_obj_align(s_banner, LV_ALIGN_TOP_MID, 0, theme::STATUS_H + 2);
|
||||
lv_obj_set_style_bg_color(s_banner, lv_color_hex(theme::SURFACE_2), 0);
|
||||
lv_obj_set_style_bg_color(s_banner, lv_color_hex(theme::SURFACE), LV_STATE_PRESSED);
|
||||
lv_obj_set_style_border_color(s_banner, lv_color_hex(theme::ACCENT), 0);
|
||||
// Raised off the page: lighter than it on the dark theme, white on the light.
|
||||
lv_obj_set_style_bg_color(s_banner, lv_color_hex(theme::s_light ? theme::SURFACE : theme::SURFACE_2), 0);
|
||||
lv_obj_set_style_bg_color(s_banner, lv_color_hex(theme::s_light ? theme::SURFACE_2 : theme::SURFACE), LV_STATE_PRESSED);
|
||||
lv_obj_set_style_border_color(s_banner, lv_color_hex(theme::EDGE), 0);
|
||||
lv_obj_set_style_border_width(s_banner, 1, 0);
|
||||
lv_obj_set_style_radius(s_banner, theme::RADIUS, 0);
|
||||
lv_obj_set_style_shadow_width(s_banner, 12, 0);
|
||||
lv_obj_set_style_shadow_color(s_banner, lv_color_hex(0x000000), 0);
|
||||
lv_obj_set_style_shadow_opa(s_banner, LV_OPA_60, 0);
|
||||
lv_obj_set_style_shadow_opa(s_banner, theme::s_light ? LV_OPA_20 : LV_OPA_60, 0);
|
||||
lv_obj_set_style_pad_all(s_banner, 8, 0);
|
||||
lv_obj_set_style_pad_column(s_banner, 10, 0);
|
||||
lv_obj_set_flex_flow(s_banner, LV_FLEX_FLOW_ROW);
|
||||
@@ -644,14 +645,15 @@ void UITask::buildStatusBar() {
|
||||
lv_obj_remove_flag(_toast, LV_OBJ_FLAG_SCROLLABLE);
|
||||
lv_obj_remove_flag(_toast, LV_OBJ_FLAG_CLICKABLE);
|
||||
lv_obj_set_size(_toast, LV_SIZE_CONTENT, LV_SIZE_CONTENT);
|
||||
lv_obj_set_style_bg_color(_toast, lv_color_hex(theme::SURFACE_2), 0);
|
||||
lv_obj_set_style_border_color(_toast, lv_color_hex(theme::ACCENT), 0);
|
||||
lv_obj_set_style_border_width(_toast, 1, 0);
|
||||
// On the light theme a dark pill, as a phone's: a light one was lost on the page.
|
||||
lv_obj_set_style_bg_color(_toast, lv_color_hex(theme::s_light ? theme::TEXT : theme::SURFACE_2), 0);
|
||||
lv_obj_set_style_border_color(_toast, lv_color_hex(theme::EDGE), 0);
|
||||
lv_obj_set_style_border_width(_toast, theme::s_light ? 0 : 1, 0);
|
||||
lv_obj_set_style_radius(_toast, theme::RADIUS, 0);
|
||||
lv_obj_set_style_pad_hor(_toast, 12, 0);
|
||||
lv_obj_set_style_pad_ver(_toast, 6, 0);
|
||||
lv_obj_align(_toast, LV_ALIGN_TOP_MID, 0, theme::STATUS_H + 4); // slides down from under the status bar
|
||||
lv_obj_t* tl = label(_toast, "", THEME_FONT_BODY, theme::TEXT);
|
||||
lv_obj_t* tl = label(_toast, "", THEME_FONT_BODY, theme::s_light ? theme::SURFACE : theme::TEXT);
|
||||
lv_label_set_long_mode(tl, LV_LABEL_LONG_WRAP); // long texts wrap instead of running off the screen
|
||||
lv_obj_set_style_max_width(tl, lv_display_get_horizontal_resolution(NULL) - 40, 0);
|
||||
lv_obj_set_style_text_align(tl, LV_TEXT_ALIGN_CENTER, 0);
|
||||
|
||||
@@ -346,7 +346,7 @@ static lv_obj_t* popupOpen(lv_obj_t* parent, PopFit fit, lv_obj_t*& overlay, int
|
||||
}
|
||||
if (fit != POP_FIT) lv_obj_remove_flag(panel, LV_OBJ_FLAG_SCROLLABLE);
|
||||
lv_obj_set_style_bg_color(panel, lv_color_hex(theme::BG), 0);
|
||||
lv_obj_set_style_border_color(panel, lv_color_hex(theme::ACCENT), 0);
|
||||
lv_obj_set_style_border_color(panel, lv_color_hex(theme::EDGE), 0);
|
||||
lv_obj_set_style_border_width(panel, 1, 0);
|
||||
lv_obj_set_style_radius(panel, theme::RADIUS, 0);
|
||||
lv_obj_set_style_pad_all(panel, theme::PAD, 0);
|
||||
@@ -855,7 +855,7 @@ static lv_obj_t* choiceCreate(lv_obj_t* parent, const char* opts, int sel, const
|
||||
Choice* ch = (Choice*)lv_malloc(sizeof(Choice));
|
||||
memset(ch, 0, sizeof(*ch));
|
||||
snprintf(ch->title, sizeof(ch->title), "%s", title ? title : "");
|
||||
ch->value = label(c, "", THEME_FONT_BODY, theme::ACCENT);
|
||||
ch->value = label(c, "", THEME_FONT_BODY, theme::VALUE);
|
||||
lv_obj_set_style_max_width(ch->value, 140, 0); // a long value wraps (LONG_DOT mis-sizes with SIZE_CONTENT)
|
||||
lv_obj_set_style_text_align(ch->value, LV_TEXT_ALIGN_RIGHT, 0);
|
||||
label(c, LV_SYMBOL_DOWN, THEME_FONT_SMALL, theme::TEXT_MUTED);
|
||||
@@ -917,7 +917,7 @@ static lv_obj_t* rowValue(lv_obj_t* row, const char* text, lv_event_cb_t cb, voi
|
||||
lv_obj_add_flag(c, LV_OBJ_FLAG_CLICKABLE);
|
||||
lv_obj_set_ext_click_area(c, 10);
|
||||
lv_obj_set_style_opa(c, LV_OPA_60, LV_STATE_PRESSED);
|
||||
label(c, text, THEME_FONT_BODY, theme::ACCENT);
|
||||
label(c, text, THEME_FONT_BODY, theme::VALUE);
|
||||
label(c, LV_SYMBOL_EDIT, THEME_FONT_SMALL, theme::TEXT_MUTED);
|
||||
lv_obj_add_event_cb(c, cb, LV_EVENT_CLICKED, user);
|
||||
return c;
|
||||
@@ -970,9 +970,9 @@ static lv_obj_t* segmented(lv_obj_t* parent, const char** map, int sel, int w, i
|
||||
lv_obj_set_style_bg_opa(m, LV_OPA_TRANSP, 0);
|
||||
lv_obj_set_style_border_width(m, 0, 0);
|
||||
lv_obj_set_style_bg_color(m, lv_color_hex(item), LV_PART_ITEMS);
|
||||
lv_obj_set_style_bg_color(m, lv_color_hex(theme::ACCENT), LV_PART_ITEMS | LV_STATE_CHECKED);
|
||||
lv_obj_set_style_bg_color(m, lv_color_hex(theme::SEL_BG), LV_PART_ITEMS | LV_STATE_CHECKED);
|
||||
lv_obj_set_style_text_color(m, lv_color_hex(theme::TEXT), LV_PART_ITEMS);
|
||||
lv_obj_set_style_text_color(m, lv_color_hex(theme::ON_ACCENT), LV_PART_ITEMS | LV_STATE_CHECKED);
|
||||
lv_obj_set_style_text_color(m, lv_color_hex(theme::SEL_FG), LV_PART_ITEMS | LV_STATE_CHECKED);
|
||||
lv_obj_set_style_text_font(m, THEME_FONT_SMALL, LV_PART_ITEMS);
|
||||
lv_obj_set_style_shadow_width(m, 0, LV_PART_ITEMS);
|
||||
lv_obj_set_style_radius(m, theme::RADIUS, LV_PART_ITEMS);
|
||||
@@ -1024,8 +1024,8 @@ static void stylePrimary(lv_obj_t* b) {
|
||||
lv_obj_set_style_text_color(lv_obj_get_child(b, i), lv_color_hex(theme::ON_ACCENT), 0);
|
||||
}
|
||||
|
||||
// Something selected / on (a tab, a chip, a row): the accent with ON_ACCENT
|
||||
// text, or back to the surface with TEXT. Every label under it follows.
|
||||
// Something selected / on (a tab, a chip, a row): SEL_BG with SEL_FG text
|
||||
// (Theme.h), or back to the surface with TEXT. Every label under it follows.
|
||||
static void textUnder(lv_obj_t* o, uint32_t col) {
|
||||
for (uint32_t i = 0; i < lv_obj_get_child_count(o); i++) {
|
||||
lv_obj_t* c = lv_obj_get_child(o, i);
|
||||
@@ -1034,14 +1034,15 @@ static void textUnder(lv_obj_t* o, uint32_t col) {
|
||||
}
|
||||
}
|
||||
static void styleSelected(lv_obj_t* b, bool on) {
|
||||
lv_obj_set_style_bg_color(b, lv_color_hex(on ? theme::ACCENT : theme::SURFACE), 0);
|
||||
lv_obj_set_style_bg_color(b, lv_color_hex(on ? theme::mix(theme::ACCENT, theme::BG, 75) : theme::SURFACE_2), LV_STATE_PRESSED);
|
||||
textUnder(b, on ? theme::ON_ACCENT : theme::TEXT);
|
||||
lv_obj_set_style_bg_color(b, lv_color_hex(on ? theme::SEL_BG : theme::SURFACE), 0);
|
||||
lv_obj_set_style_bg_color(b, lv_color_hex(on ? theme::SEL_PRESS : theme::SURFACE_2), LV_STATE_PRESSED);
|
||||
textUnder(b, on ? theme::SEL_FG : theme::TEXT);
|
||||
}
|
||||
|
||||
// A row of equal buttons at the foot of a popup or screen: buttonBar(), then
|
||||
// barButton() for each -- `accent` for the main action or one that's on
|
||||
// (barButtonOn() flips it later). Returns the button; its label is child 0.
|
||||
// barButton() for each -- `accent` for the main action, in the full accent
|
||||
// (barButtonOn() flips it later; a button that is on, as Fav, takes
|
||||
// styleSelected() instead). Returns the button; its label is child 0.
|
||||
static lv_obj_t* buttonBar(lv_obj_t* parent) {
|
||||
lv_obj_t* bar = lv_obj_create(parent);
|
||||
styleSurface(bar, theme::BG);
|
||||
@@ -1051,7 +1052,11 @@ static lv_obj_t* buttonBar(lv_obj_t* parent) {
|
||||
lv_obj_set_style_pad_column(bar, theme::GAP, 0);
|
||||
return bar;
|
||||
}
|
||||
static void barButtonOn(lv_obj_t* b, bool on) { styleSelected(b, on); }
|
||||
static void barButtonOn(lv_obj_t* b, bool on) {
|
||||
lv_obj_set_style_bg_color(b, lv_color_hex(on ? theme::ACCENT : theme::SURFACE), 0);
|
||||
lv_obj_set_style_bg_color(b, lv_color_hex(on ? theme::mix(theme::ACCENT, theme::BG, 75) : theme::SURFACE_2), LV_STATE_PRESSED);
|
||||
textUnder(b, on ? theme::ON_ACCENT : theme::TEXT);
|
||||
}
|
||||
static lv_obj_t* barButton(lv_obj_t* bar, const char* text, lv_event_cb_t cb, uintptr_t user, bool accent = false) {
|
||||
lv_obj_t* b = lv_button_create(bar);
|
||||
lv_obj_set_height(b, 40);
|
||||
@@ -1264,7 +1269,8 @@ static lv_obj_t* create(lv_obj_t* parent) {
|
||||
lv_obj_t* o = lv_obj_create(parent);
|
||||
lv_obj_remove_style_all(o);
|
||||
lv_obj_remove_flag(o, LV_OBJ_FLAG_CLICKABLE);
|
||||
lv_obj_set_style_text_color(o, lv_color_hex(theme::TEXT), 0);
|
||||
// Graphite on the light theme: that much black, that big, was heavy.
|
||||
lv_obj_set_style_text_color(o, lv_color_hex(theme::s_light ? theme::mix(theme::TEXT, theme::BG, 80) : theme::TEXT), 0);
|
||||
Text* t = (Text*)lv_malloc(sizeof(Text));
|
||||
if (t) { snprintf(t->hm, sizeof(t->hm), "--:--"); t->sec[0] = 0; }
|
||||
lv_obj_set_user_data(o, t);
|
||||
|
||||
Reference in New Issue
Block a user