From d719f9c5017ad8006c21b6d546a5d70e846e9502 Mon Sep 17 00:00:00 2001 From: ailurux Date: Mon, 12 Aug 2024 03:19:03 +0000 Subject: [PATCH] daniel/theme-setting (#87) - Themes can be loaded from disk and built-in - Themes can be selected in a new themes menu of the settings screen - Some touch-ups to existing themes - The saved theme is persisted in nvs Reviewed-on: https://codeberg.org/cool-tech-zone/tangara-fw/pulls/87 Reviewed-by: cooljqln Co-authored-by: ailurux Co-committed-by: ailurux --- lua/img/next.png | Bin 4811 -> 7828 bytes lua/img/pause.png | Bin 4771 -> 5963 bytes lua/img/play.png | Bin 4813 -> 6828 bytes lua/img/prev.png | Bin 4810 -> 7839 bytes lua/main.lua | 12 +++-- lua/main_menu.lua | 7 +-- lua/playing.lua | 23 ++++---- lua/settings.lua | 61 ++++++++++++++++++++++ lua/theme_dark.lua | 29 ++++++++--- lua/theme_light.lua | 78 +++++++++++++++++++++++----- luals-stubs/theme.lua | 28 ++++++++++ src/drivers/include/drivers/nvs.hpp | 5 ++ src/drivers/nvs.cpp | 39 ++++++++++++++ src/tangara/lua/lua_theme.cpp | 33 ++++++++++++ src/tangara/ui/themes.cpp | 5 ++ src/tangara/ui/themes.hpp | 4 ++ 16 files changed, 289 insertions(+), 35 deletions(-) create mode 100644 luals-stubs/theme.lua diff --git a/lua/img/next.png b/lua/img/next.png index 1f6f044b3caef94da41bb73f69a89b7cd49c7586..929c5f36ad2f2045073a783dcd760e2832f5d08c 100644 GIT binary patch delta 4295 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 delta 1467 zcmV;s1w{IkJaB^>EX>4U6ba`-PAZ2)IW&i+q+U=K1vg{@d zhUcszOF$ArVmTO0Rd$f&=Lgg7(|sRxbCW>^ZH{@!AcQ{2?i0?x{v6>CTpSaJq~^Kg z9C4+R3RiSIUe~c|ifPr?b?%m4<>7w75D8kjhK28iglCcVxPR9S&H;>=q-4RA5$4N@hKz`HA}jzlzAtqXazNk1x%zRQZdv?XgNLtB}{;& zWp7mD?aZ&fd4Kxo2iLD0Ylq1SgPYo|`z=21-dlKB-Ez6)Peu7|U$8#pq;GP$x8WCy z{JArKwaDb&BEC}{qx%@=njBJ18E4M>P>-Xm9wr{sTWeWHG2Dv2tx`}t*4Pj_J;a8L zIxr~2U_2;Mr%Hp))i!D{G4aWUmbtMknS({)CKq#*K!1Zxiga;ALqn_x#C+RAYu~cA zYvhRgPMFCAWrP)X310{QrhKDljF~e;(QjYDE}mHkGbXuNV-|qW*ggf(uv|>HqrYAg ztbm{{H#-hk<8i4tl%BCAlk?1eMM?3>ZnC}=z(v?wVGIcf#7v@;DH^j8!H*6;6_z9E z_@K~;RDZd{N=_mfaF7Mq8*7Z#TvoosczXhbN=8;X09C+BNfADlJRpXuiY8UfYU*0F zX2~BD^^`Rxq5bU_u{p1k$~5<7UW~`QcA7Zv_f;m>WUe)9(>4= z4n6W=hacsr4dK&L)0UexZ>81FT|!49_uQ>}FMqwBbRv|_bn2N;JN+zY9SF4{BMlvS z*zi$Cy{T=guhibj{hS(YYP>*6KxH>Ih|L}!f zWq)B2UkH5e1JGRI4QbFiRUJT7O`G9+>ig> z|L5Fu0ijl6y4^7j=!Rn@<1s0nS(QSs2tuUlMZdyqb552MXuH1d5#ak>lxMZS_vh-- zvNi((67eiEEr)o6czV+@IqwsTte|Mb=fqN z*htcOtc`!j^ULH?$yEd+#{$aGp?H4qKlnXcJ2yGuCxv4`=Zoupi~ylspk8&|?_<}k zp8&yU;L2$F%Qax;lk{p+iyQ%c+e^U3byHLKfXf|V@X1sx#gl?mLM{iqpV2pEfPq_} zd(EF);~b|CK#FFSxB(6hfzdo=ueW)3SL@vVz0;WA4**MYc~!O0T>t`My~lAZmPqB)OaG2L1Qd)3yO7sKXQiKm)zE VG!C!UnEn6&002ovPDHLkV1hQUqXPf{ diff --git a/lua/img/pause.png b/lua/img/pause.png index e7011821ff4f268e706307163f9ea45f61aab537..32c5a2b4ca85dbe7e46c1b939f3c78d5ca42ad62 100644 GIT binary patch delta 2418 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 delta 1384 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 diff --git a/lua/img/prev.png b/lua/img/prev.png index b445c75ab7cf5c387a719b97c47c1a70927aecc6..714c7c204c3068e9a91dcd860bc2abbfc9ada997 100644 GIT binary patch delta 4308 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 0 then + options = options .. "\n" + end + options = options .. i + idx = idx + 1 + end + + if (selected_idx == -1) then + options = options .. "\n" .. saved_theme_name + selected_idx = idx + end + + local theme_chooser = self.content:Dropdown { + options = options, + } + theme_chooser:set({selected = selected_idx}) + + theme_chooser:onevent(lvgl.EVENT.VALUE_CHANGED, function() + local option = theme_chooser:get('selected_str') + local selectedTheme = themeOptions[option] + if (selectedTheme) then + theme.load_theme(tostring(selectedTheme)) + backstack.reset(main_menu:new()) + end + end) + end +} + local InputSettings = SettingsScreen:new { title = "Input Method", createUi = function(self) @@ -742,6 +802,7 @@ return widgets.MenuScreen:new { section("Interface") submenu("Display", DisplaySettings) + submenu("Theme", ThemeSettings) submenu("Input Method", InputSettings) section("USB") diff --git a/lua/theme_dark.lua b/lua/theme_dark.lua index 6508f642..b9bcece2 100644 --- a/lua/theme_dark.lua +++ b/lua/theme_dark.lua @@ -95,24 +95,27 @@ local theme_dark = { switch = { {lvgl.PART.MAIN, lvgl.Style { bg_opa = lvgl.OPA(100), - width = 28, - height = 8, + width = 18, + height = 10, radius = 32767, -- LV_RADIUS_CIRCLE = 0x7fff bg_color = background_muted, - border_color = highlight_color, + border_color = text_color, + border_width = 1, }}, {lvgl.PART.INDICATOR, lvgl.Style { radius = 32767, -- LV_RADIUS_CIRCLE = 0x7fff - bg_color = background_muted, + bg_color = background_color, }}, {lvgl.PART.INDICATOR | lvgl.STATE.CHECKED, lvgl.Style { bg_color = highlight_color, + bg_opa = lvgl.OPA(100), }}, {lvgl.PART.KNOB, lvgl.Style { radius = 32767, -- LV_RADIUS_CIRCLE = 0x7fff - pad_all = 2, bg_opa = lvgl.OPA(100), bg_color = background_muted, + border_color = text_color, + border_width = 1, }}, {lvgl.PART.KNOB | lvgl.STATE.FOCUSED, lvgl.Style { bg_color = highlight_color, @@ -170,7 +173,21 @@ local theme_dark = { image_recolor = icon_enabled_color, }}, }, - + now_playing = { + {lvgl.PART.MAIN, lvgl.Style { + bg_opa = lvgl.OPA(100), + radius = 32767, -- LV_RADIUS_CIRCLE = 0x7fff + border_width = 1, + border_color = highlight_color, + border_side = 15, -- LV_BORDER_SIDE_FULL + }}, + }, + menu_icon = { + {lvgl.PART.MAIN, lvgl.Style { + pad_all = 4, + radius = 4 + }}, + }, } return theme_dark diff --git a/lua/theme_light.lua b/lua/theme_light.lua index 05b7d291..96403de3 100644 --- a/lua/theme_light.lua +++ b/lua/theme_light.lua @@ -2,10 +2,10 @@ local lvgl = require("lvgl") local font = require("font") local background_color = "#ffffff" -local background_muted = "#fafafa" -local text_color = "#000000" -local highlight_color = "#ce93d8" -local icon_enabled_color = "#2c2c2c" +local background_muted = "#f2f2f2" +local text_color = "#2c2c2c" +local highlight_color = "#ff82bc" +local icon_enabled_color = "#ff82bc" local icon_disabled_color = "#999999" local theme_light = { @@ -47,6 +47,7 @@ local theme_light = { }}, {lvgl.PART.MAIN | lvgl.STATE.FOCUSED, lvgl.Style { bg_opa = lvgl.OPA(100), + text_color = "#ffffff", bg_color = highlight_color, image_recolor_opa = 0, }}, @@ -55,6 +56,7 @@ local theme_light = { {lvgl.PART.MAIN | lvgl.STATE.FOCUSED, lvgl.Style { bg_opa = lvgl.OPA(100), bg_color = highlight_color, + text_color = "#ffffff" }}, }, bar = { @@ -75,16 +77,41 @@ local theme_light = { }}, {lvgl.PART.KNOB, lvgl.Style { radius = 32767, -- LV_RADIUS_CIRCLE = 0x7fff - pad_all = 2, bg_color = background_muted, shadow_width = 5, - shadow_opa = lvgl.OPA(100) + shadow_opa = lvgl.OPA(100), + pad_all = 2, + }}, + {lvgl.PART.MAIN | lvgl.STATE.FOCUSED, lvgl.Style { + bg_color = background_muted, + }}, + {lvgl.PART.KNOB | lvgl.STATE.FOCUSED, lvgl.Style { + bg_color = highlight_color, + }}, + {lvgl.PART.INDICATOR | lvgl.STATE.CHECKED, lvgl.Style { + bg_color = highlight_color, + }}, + }, + scrubber = { + {lvgl.PART.MAIN, lvgl.Style { + bg_opa = lvgl.OPA(100), + bg_color = background_muted, + radius = 32767, -- LV_RADIUS_CIRCLE = 0x7fff + }}, + {lvgl.PART.INDICATOR, lvgl.Style { + radius = 32767, -- LV_RADIUS_CIRCLE = 0x7fff + bg_color = highlight_color, + }}, + {lvgl.PART.KNOB, lvgl.Style { + radius = 32767, -- LV_RADIUS_CIRCLE = 0x7fff + bg_color = background_muted, }}, {lvgl.PART.MAIN | lvgl.STATE.FOCUSED, lvgl.Style { bg_color = background_muted, }}, {lvgl.PART.KNOB | lvgl.STATE.FOCUSED, lvgl.Style { bg_color = highlight_color, + pad_all = 1, }}, {lvgl.PART.INDICATOR | lvgl.STATE.CHECKED, lvgl.Style { bg_color = highlight_color, @@ -93,24 +120,27 @@ local theme_light = { switch = { {lvgl.PART.MAIN, lvgl.Style { bg_opa = lvgl.OPA(100), - width = 28, - height = 8, + width = 18, + height = 10, radius = 32767, -- LV_RADIUS_CIRCLE = 0x7fff bg_color = background_muted, - border_color = highlight_color, + border_color = text_color, + border_width = 1, }}, {lvgl.PART.INDICATOR, lvgl.Style { radius = 32767, -- LV_RADIUS_CIRCLE = 0x7fff - bg_color = background_muted, + bg_color = background_color, }}, {lvgl.PART.INDICATOR | lvgl.STATE.CHECKED, lvgl.Style { + bg_opa = lvgl.OPA(100), bg_color = highlight_color, }}, {lvgl.PART.KNOB, lvgl.Style { radius = 32767, -- LV_RADIUS_CIRCLE = 0x7fff - pad_all = 2, bg_opa = lvgl.OPA(100), - bg_color = background_muted, + bg_color = background_color, + border_color = text_color, + border_width = 1, }}, {lvgl.PART.KNOB | lvgl.STATE.FOCUSED, lvgl.Style { bg_color = highlight_color, @@ -161,14 +191,36 @@ local theme_light = { image_recolor_opa = 180, image_recolor = icon_disabled_color, }}, + {lvgl.PART.MAIN | lvgl.STATE.FOCUSED, lvgl.Style { + image_recolor_opa = 0, + image_recolor = "#ffffff", + }}, }, icon_enabled = { {lvgl.PART.MAIN, lvgl.Style { image_recolor_opa = 180, image_recolor = icon_enabled_color, }}, + {lvgl.PART.MAIN | lvgl.STATE.FOCUSED, lvgl.Style { + image_recolor_opa = 0, + image_recolor = "#ffffff", + }}, + }, + now_playing = { + {lvgl.PART.MAIN, lvgl.Style { + bg_opa = lvgl.OPA(100), + radius = 32767, -- LV_RADIUS_CIRCLE = 0x7fff + border_width = 1, + border_color = highlight_color, + border_side = 15, -- LV_BORDER_SIDE_FULL + }}, + }, + menu_icon = { + {lvgl.PART.MAIN, lvgl.Style { + pad_all = 4, + radius = 4 + }}, }, - } return theme_light diff --git a/luals-stubs/theme.lua b/luals-stubs/theme.lua new file mode 100644 index 00000000..4a945cb3 --- /dev/null +++ b/luals-stubs/theme.lua @@ -0,0 +1,28 @@ +--- @meta + +--- @class theme +local theme = {} + +--- Loads a theme from a filename, this can be either builtin (ie, located in +--- "/lua/") or on the sdcard (in, '/sdcard/.themes/') +--- If successful, the filename will be saved to non-volatile storage. +--- Returns whether the theme was successfully loaded +--- @param filename string +--- @return boolean +function theme.load_theme(filename) end + +--- Sets a theme directly from a table. Does not persist between restarts. +--- @param theme +function theme.set(theme) end + +--- Set the style name (similar in concept to a css selector) for an object +--- This will set any styles associated with that style name on the object +--- @param obj Object The object to set a particular style on +--- @param style string The name of the style to apply to this object +function theme.set_style(obj, style) end + +--- Returns the filename of the saved theme +--- @return string +function theme.theme_filename() end + +return theme diff --git a/src/drivers/include/drivers/nvs.hpp b/src/drivers/include/drivers/nvs.hpp index e298ffc3..e147c8c7 100644 --- a/src/drivers/include/drivers/nvs.hpp +++ b/src/drivers/include/drivers/nvs.hpp @@ -113,6 +113,9 @@ class NvsStorage { auto ScreenBrightness() -> uint_fast8_t; auto ScreenBrightness(uint_fast8_t) -> void; + auto InterfaceTheme() -> std::optional; + auto InterfaceTheme(std::string) -> void; + auto ScrollSensitivity() -> uint_fast8_t; auto ScrollSensitivity(uint_fast8_t) -> void; @@ -163,6 +166,8 @@ class NvsStorage { Setting input_mode_; Setting output_mode_; + Setting theme_; + Setting bt_preferred_; Setting> bt_names_; diff --git a/src/drivers/nvs.cpp b/src/drivers/nvs.cpp index 6fac8c61..d004201b 100644 --- a/src/drivers/nvs.cpp +++ b/src/drivers/nvs.cpp @@ -29,6 +29,7 @@ static constexpr char kKeyBluetoothVolumes[] = "bt_vols"; static constexpr char kKeyBluetoothNames[] = "bt_names"; static constexpr char kKeyOutput[] = "out"; static constexpr char kKeyBrightness[] = "bright"; +static constexpr char kKeyInterfaceTheme[] = "ui_theme"; static constexpr char kKeyAmpMaxVolume[] = "hp_vol_max"; static constexpr char kKeyAmpCurrentVolume[] = "hp_vol"; static constexpr char kKeyAmpLeftBias[] = "hp_bias"; @@ -169,6 +170,31 @@ auto Setting>::store( nvs_set_blob(nvs, name_, encoded.data(), encoded.size()); } +template <> +auto Setting::store( + nvs_handle_t nvs, + std::string v) -> void { + cppbor::Tstr cbor{v}; + auto encoded = cbor.encode(); + nvs_set_blob(nvs, name_, encoded.data(), encoded.size()); +} + +template <> +auto Setting::load(nvs_handle_t nvs) + -> std::optional { + auto raw = nvs_get_string(nvs, name_); + if (!raw) { + return {}; + } + auto [parsed, unused, err] = cppbor::parseWithViews( + reinterpret_cast(raw->data()), raw->size()); + if (parsed->type() != cppbor::TSTR) { + return {}; + } + auto v = parsed->asViewTstr()->view(); + return std::string{v.begin(), v.end()}; +} + template <> auto Setting::load(nvs_handle_t nvs) -> std::optional { @@ -248,6 +274,7 @@ NvsStorage::NvsStorage(nvs_handle_t handle) amp_left_bias_(kKeyAmpLeftBias), input_mode_(kKeyPrimaryInput), output_mode_(kKeyOutput), + theme_{kKeyInterfaceTheme}, bt_preferred_(kKeyBluetoothPreferred), bt_names_(kKeyBluetoothNames), db_auto_index_(kKeyDbAutoIndex), @@ -273,6 +300,7 @@ auto NvsStorage::Read() -> void { amp_left_bias_.read(handle_); input_mode_.read(handle_); output_mode_.read(handle_); + theme_.read(handle_); bt_preferred_.read(handle_); bt_names_.read(handle_); db_auto_index_.read(handle_); @@ -293,6 +321,7 @@ auto NvsStorage::Write() -> bool { amp_left_bias_.write(handle_); input_mode_.write(handle_); output_mode_.write(handle_); + theme_.write(handle_); bt_preferred_.write(handle_); bt_names_.write(handle_); db_auto_index_.write(handle_); @@ -466,6 +495,16 @@ auto NvsStorage::ScreenBrightness(uint_fast8_t val) -> void { brightness_.set(val); } +auto NvsStorage::InterfaceTheme() -> std::optional { + std::lock_guard lock{mutex_}; + return theme_.get(); +} + +auto NvsStorage::InterfaceTheme(std::string themeFile) -> void { + std::lock_guard lock{mutex_}; + theme_.set(themeFile); +} + auto NvsStorage::ScrollSensitivity() -> uint_fast8_t { std::lock_guard lock{mutex_}; return std::clamp(sensitivity_.get().value_or(128), 0, 255); diff --git a/src/tangara/lua/lua_theme.cpp b/src/tangara/lua/lua_theme.cpp index 5edde104..03578778 100644 --- a/src/tangara/lua/lua_theme.cpp +++ b/src/tangara/lua/lua_theme.cpp @@ -75,8 +75,41 @@ static auto set_theme(lua_State* L) -> int { return 0; } + +static auto load_theme(lua_State* L) -> int { + std::string filename = luaL_checkstring(L, -1); + // Set the theme filename in non-volatile storage + Bridge* instance = Bridge::Get(L); + // Load the theme using lua + auto status = luaL_loadfile(L, filename.c_str()); + if (status != LUA_OK) { + lua_pushboolean(L, false); + return 1; + } + status = lua::CallProtected(L, 0, 1); + if (status == LUA_OK) { + ui::themes::Theme::instance()->Reset(); + set_theme(L); + instance->services().nvs().InterfaceTheme(filename); + lua_pushboolean(L, true); + } else { + lua_pushboolean(L, false); + } + + return 1; +} + +static auto theme_filename(lua_State* L) -> int { + Bridge* instance = Bridge::Get(L); + auto file = instance->services().nvs().InterfaceTheme().value_or("/lua/theme_light.lua"); + lua_pushstring(L, file.c_str()); + return 1; +} + static const struct luaL_Reg kThemeFuncs[] = {{"set", set_theme}, {"set_style", set_style}, + {"load_theme", load_theme}, + {"theme_filename", theme_filename}, {NULL, NULL}}; static auto lua_theme(lua_State* L) -> int { diff --git a/src/tangara/ui/themes.cpp b/src/tangara/ui/themes.cpp index 726bd5f0..3d532d10 100644 --- a/src/tangara/ui/themes.cpp +++ b/src/tangara/ui/themes.cpp @@ -16,6 +16,7 @@ #include "widgets/bar/lv_bar.h" #include "widgets/button/lv_button.h" #include "widgets/slider/lv_slider.h" +#include "themes.hpp" namespace ui { namespace themes { @@ -81,6 +82,10 @@ void Theme::ApplyStyle(lv_obj_t* obj, std::string style_key) { } } +void Theme::Reset() { + style_map.clear(); +} + auto Theme::instance() -> Theme* { static Theme sTheme{}; return &sTheme; diff --git a/src/tangara/ui/themes.hpp b/src/tangara/ui/themes.hpp index fd576478..4826859e 100644 --- a/src/tangara/ui/themes.hpp +++ b/src/tangara/ui/themes.hpp @@ -1,6 +1,7 @@ #pragma once #include +#include #include #include #include "lvgl.h" @@ -26,12 +27,15 @@ class Theme { void AddStyle(std::string key, int selector, lv_style_t* style); + void Reset(); + static auto instance() -> Theme*; private: Theme(); std::map>> style_map; lv_theme_t theme_; + std::optional filename_; }; } // namespace themes } // namespace ui