mirror of
https://github.com/MarekZegare4/MeshCore-Solo.git
synced 2026-11-01 10:21:59 +00:00
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>
181 lines
8.8 KiB
C++
181 lines
8.8 KiB
C++
#pragma once
|
|
// Design tokens for ui-lvgl -- the one place colours, spacing and type live --
|
|
// and the rules every widget follows (install() below).
|
|
//
|
|
// The accent (amber by default; Settings > Display > Accent colour)
|
|
// on near-black, soft corners. The accent means one of three things:
|
|
// - ACCENT fill, ON_ACCENT text: the primary action of a screen or popup
|
|
// (Download, Install, Save, Go) -- stylePrimary() in UITask.cpp -- and
|
|
// something selected / on (a tab, a chip, a segment, a mode that is
|
|
// running, own bubbles) -- styleSelected();
|
|
// - ACCENT text or icon: a name, a count, a background mode, a value.
|
|
// No accent mixed toward black for "selected": it came out brown.
|
|
// Everything else is BG / SURFACE / SURFACE_2 with TEXT or TEXT_MUTED.
|
|
//
|
|
// 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 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 {
|
|
// Colours (0xRRGGBB), the dark theme's; setLight() sets the light one's.
|
|
static uint32_t BG = 0x0B0B0D; // screen background
|
|
static uint32_t SURFACE = 0x1A1A1F; // cards, rows, bubbles (incoming)
|
|
static uint32_t SURFACE_2 = 0x27272E; // pressed / raised
|
|
static uint32_t ACCENT = 0xFFB000; // primary actions, names, badges, modes (setAccent)
|
|
static uint32_t ON_ACCENT = 0x0B0B0D; // text and icons on an ACCENT fill
|
|
static uint32_t TEXT = 0xF2EEE6;
|
|
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)
|
|
static const int PAD = 8;
|
|
static const int GAP = 6;
|
|
static const int RADIUS = 10; // cards, rows and everything tapped: buttons, tabs, choices, chips
|
|
static const int RADIUS_SM = 6; // small drawn cells inside those (chart backgrounds, captions)
|
|
static const int STATUS_H = 22; // top status bar (Home, the map: screens without a title bar)
|
|
static const int ROW_H = 44; // list row (touch target)
|
|
// A screen's title bar, at the top in place of the status bar: back, the
|
|
// title, the screen's actions, then the time and battery.
|
|
static const int APPBAR_H = 40;
|
|
static const int BAR_BTN_H = 32; // back and the actions in it
|
|
static const int TAB_H = 34; // a screen's tabs (segmented), the first thing under the title bar
|
|
|
|
// Type -- Noto Sans, generated by fonts/generate.sh (declared in lv_conf.h).
|
|
// ui_font_40 holds digits / ':' / '-' / '.' only (clock, timer, stopwatch).
|
|
#define THEME_FONT_SMALL (&ui_font_12)
|
|
#define THEME_FONT_BODY (&ui_font_14)
|
|
#define THEME_FONT_TITLE (&ui_font_16)
|
|
#define THEME_FONT_LARGE (&ui_font_20)
|
|
#define THEME_FONT_CLOCK (&ui_font_40)
|
|
// Status bar icons, refitted to one size (fonts/status_icons.py); any other
|
|
// character falls back to ui_font_12.
|
|
#define THEME_FONT_ICONS (&ui_icons_14)
|
|
|
|
// Extra FontAwesome glyphs baked into ui_font_12..20 beyond LVGL's LV_SYMBOL_*.
|
|
#define UI_SYMBOL_STAR "\xEF\x80\x85" // U+F005, favourite
|
|
#define UI_SYMBOL_MAP "\xEF\x89\xB9" // U+F279
|
|
#define UI_SYMBOL_USERS "\xEF\x83\x80" // U+F0C0, Nearby
|
|
#define UI_SYMBOL_COMPASS "\xEF\x85\x8E" // U+F14E, Navigate
|
|
#define UI_SYMBOL_FLAG "\xEF\x80\xA4" // U+F024, waypoint
|
|
#define UI_SYMBOL_PIN "\xEF\x8F\x85" // U+F3C5, mark here (a place on the map)
|
|
#define UI_SYMBOL_TACK "\xEF\x82\x8D" // U+F08D, thumbtack: pin to the Home dial
|
|
#define UI_SYMBOL_CLOCK "\xEF\x80\x97" // U+F017, Clock tools
|
|
#define UI_SYMBOL_STOPWATCH "\xEF\x8B\xB2" // U+F2F2
|
|
#define UI_SYMBOL_RADIO "\xEF\x94\x99" // U+F519, broadcast tower
|
|
#define UI_SYMBOL_CHART "\xEF\x82\x80" // U+F080, diagnostics
|
|
#define UI_SYMBOL_KEY "\xEF\x82\x84" // U+F084, admin
|
|
#define UI_SYMBOL_ROUTE "\xEF\x93\x97" // U+F4D7, trail
|
|
#define UI_SYMBOL_MUTE "\xEF\x9A\xA9" // U+F6A9, muted (speaker with a cross)
|
|
#define UI_SYMBOL_LOCK "\xEF\x80\xA3" // U+F023, quick panel: lock the screen
|
|
#define UI_SYMBOL_SUN "\xEF\x86\x85" // U+F185, quick panel: brightness
|
|
#define UI_SYMBOL_BULB "\xEF\x83\xAB" // U+F0EB, Flashlight
|
|
|
|
// Accent choices: on the dark theme, and deeper on the light one, to read
|
|
// as text on white and take white text on it.
|
|
struct Accent { const char* name; uint32_t col, light; };
|
|
static const Accent ACCENTS[] = {
|
|
{ "Amber", 0xFFB000, 0xC77800 }, { "Orange", 0xFF7A2E, 0xD9561A }, { "Coral", 0xFF6B7A, 0xD9455A },
|
|
{ "Violet", 0xB18CFF, 0x7A55E0 }, { "Cyan", 0x3CC8E8, 0x0E8AA8 }, { "Lime", 0xB5E04A, 0x5E8A12 },
|
|
};
|
|
static const int ACCENT_COUNT = sizeof(ACCENTS) / sizeof(ACCENTS[0]);
|
|
static int s_accent = 0;
|
|
// `fg` at `pct` % over `bg`, worked out once: an opaque fill in the colour
|
|
// a translucent one would blend to, without blending it every frame.
|
|
static uint32_t mix(uint32_t fg, uint32_t bg, uint32_t pct) {
|
|
uint32_t out = 0;
|
|
for (int sh = 0; sh <= 16; sh += 8) {
|
|
uint32_t c = (fg >> sh) & 0xFF, b = (bg >> sh) & 0xFF;
|
|
out |= ((c * pct + b * (100 - pct)) / 100) << sh;
|
|
}
|
|
return out;
|
|
}
|
|
static void setAccent(int idx) {
|
|
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) {
|
|
// 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" (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
|
|
// default's 5 px bar 7 px in, which ran over right-aligned text.
|
|
static lv_theme_t s_theme;
|
|
static lv_style_t s_btn, s_press, s_checked, s_sbar;
|
|
static void applyTheme(lv_theme_t* th, lv_obj_t* obj) {
|
|
(void)th;
|
|
lv_obj_add_style(obj, &s_sbar, LV_PART_SCROLLBAR);
|
|
if (lv_obj_check_type(obj, &lv_button_class)) {
|
|
lv_obj_add_style(obj, &s_btn, 0);
|
|
lv_obj_add_style(obj, &s_press, LV_STATE_PRESSED);
|
|
lv_obj_add_style(obj, &s_checked, LV_STATE_CHECKED);
|
|
}
|
|
}
|
|
// (Re)builds the default theme in the current accent with this layer over it.
|
|
static void install(lv_display_t* disp) {
|
|
static bool styled = false;
|
|
if (!styled) {
|
|
styled = true;
|
|
lv_style_init(&s_btn);
|
|
lv_style_set_radius(&s_btn, RADIUS);
|
|
lv_style_set_shadow_width(&s_btn, 0);
|
|
lv_style_init(&s_press);
|
|
lv_style_set_transform_width(&s_press, -2);
|
|
lv_style_set_transform_height(&s_press, -2);
|
|
lv_style_init(&s_checked);
|
|
|
|
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
|
|
lv_style_set_pad_bottom(&s_sbar, 2); // horizontal bar likewise
|
|
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(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);
|
|
lv_theme_set_apply_cb(&s_theme, applyTheme);
|
|
lv_display_set_theme(disp, &s_theme);
|
|
lv_obj_report_style_change(&s_checked);
|
|
}
|
|
}
|