From 490b067b765a05192118306e8796bf042ca31b94 Mon Sep 17 00:00:00 2001 From: ailurux Date: Thu, 7 Mar 2024 12:11:20 +1100 Subject: [PATCH 01/18] Add luavgl_to_style method --- lib/luavgl/src/luavgl.h | 5 +++++ lib/luavgl/src/util.c | 11 +++++++++++ 2 files changed, 16 insertions(+) diff --git a/lib/luavgl/src/luavgl.h b/lib/luavgl/src/luavgl.h index b26bb5c7..c76a6493 100644 --- a/lib/luavgl/src/luavgl.h +++ b/lib/luavgl/src/luavgl.h @@ -157,6 +157,11 @@ LUALIB_API int luavgl_obj_getuserdatauv(lua_State *L, int idx); */ LUALIB_API lv_obj_t *luavgl_to_obj(lua_State *L, int idx); +/** + * @brief Get lvgl style from stack + */ +LUALIB_API lv_style_t *luavgl_to_style(lua_State *L, int idx); + /** * @brief Convert value to integer * diff --git a/lib/luavgl/src/util.c b/lib/luavgl/src/util.c index 2042a6d9..7fb86906 100644 --- a/lib/luavgl/src/util.c +++ b/lib/luavgl/src/util.c @@ -272,6 +272,17 @@ LUALIB_API lv_obj_t *luavgl_to_obj(lua_State *L, int idx) return lobj->obj; } +LUALIB_API lv_style_t *luavgl_to_style(lua_State *L, int idx) +{ + luavgl_style_t *lsty = luavgl_check_style(L, idx); + if (lsty == NULL) { + luaL_argerror(L, idx, "expect lua lvgl style, got null"); + return NULL; + } + + return &lsty->style; +} + LUALIB_API int luavgl_tointeger(lua_State *L, int idx) { int v = 0; From a78614a5806c9800956f10f993e1c70b74fbf323 Mon Sep 17 00:00:00 2001 From: ailurux Date: Thu, 7 Mar 2024 12:12:32 +1100 Subject: [PATCH 02/18] WIP: Getting styles from lua --- lua/browser.lua | 6 +-- lua/licenses.lua | 4 +- lua/main.lua | 26 ++++++++++ lua/main_menu.lua | 8 ++-- lua/settings.lua | 22 ++++----- lua/{theme.lua => styles.lua} | 4 +- lua/widgets.lua | 4 +- src/lua/CMakeLists.txt | 4 +- src/lua/bridge.cpp | 2 + src/lua/include/lua_theme.hpp | 15 ++++++ src/lua/lua_theme.cpp | 89 +++++++++++++++++++++++++++++++++++ src/ui/CMakeLists.txt | 2 +- 12 files changed, 159 insertions(+), 27 deletions(-) rename lua/{theme.lua => styles.lua} (92%) create mode 100644 src/lua/include/lua_theme.hpp create mode 100644 src/lua/lua_theme.cpp diff --git a/lua/browser.lua b/lua/browser.lua index a7f0c336..e174a05d 100644 --- a/lua/browser.lua +++ b/lua/browser.lua @@ -4,7 +4,7 @@ local backstack = require("backstack") local font = require("font") local queue = require("queue") local playing = require("playing") -local theme = require("theme") +local styles = require("styles") local playback = require("playback") local browser = {} @@ -90,7 +90,7 @@ function browser.create(opts) local back = screen.list:add_btn(nil, "< Back") back:onClicked(backstack.pop) - back:add_style(theme.list_item) + back:add_style(styles.list_item) screen.focused_item = 0 screen.last_item = 0 @@ -122,7 +122,7 @@ function browser.create(opts) screen.add_item(opts.iterator()) end end) - btn:add_style(theme.list_item) + btn:add_style(styles.list_item) end for _ = 1, 8 do diff --git a/lua/licenses.lua b/lua/licenses.lua index 83437454..b5d1ae88 100644 --- a/lua/licenses.lua +++ b/lua/licenses.lua @@ -1,7 +1,7 @@ local backstack = require("backstack") local widgets = require("widgets") local font = require("font") -local theme = require("theme") +local styles = require("styles") local function show_license(text) backstack.push(function() @@ -100,7 +100,7 @@ return function() w = lvgl.PCT(100), h = lvgl.SIZE_CONTENT, } - row:add_style(theme.list_item) + row:add_style(styles.list_item) row:Label { text = name, flex_grow = 1 } local button = row:Button {} button:Label { text = license, text_font = font.fusion_10 } diff --git a/lua/main.lua b/lua/main.lua index 5cbbf0a6..5cfba47b 100644 --- a/lua/main.lua +++ b/lua/main.lua @@ -1,5 +1,6 @@ local font = require("font") local vol = require("volume") +local theme = require("theme") -- Set up property bindings that are used across every screen. GLOBAL_BINDINGS = { @@ -34,6 +35,31 @@ GLOBAL_BINDINGS = { end), } +local lvgl = require("lvgl") +local my_theme = { + base = { + {lvgl.PART.MAIN, lvgl.Style { + bg_opa = lvgl.OPA(0), + text_font = font.fusion_12, + text_color = "#ff0000", -- Red to check it applies + }}, + {lvgl.STATE.FOCUSED, lvgl.Style { + bg_opa = lvgl.OPA(100), + bg_color = "#0000ff", -- ew + text_color = "#ff0000", -- Red to check it applies + }}, + }, + button = { + {lvgl.STATE.FOCUSED, lvgl.Style { + bg_color = "#00ff00", + }}, + {lvgl.PART.MAIN, lvgl.Style { + bg_color = "#00ff00", + }}, + }, +} +theme.set(my_theme) + local backstack = require("backstack") local main_menu = require("main_menu") diff --git a/lua/main_menu.lua b/lua/main_menu.lua index 1311f8ea..1a9d9975 100644 --- a/lua/main_menu.lua +++ b/lua/main_menu.lua @@ -4,7 +4,7 @@ local database = require("database") local backstack = require("backstack") local browser = require("browser") local playing = require("playing") -local theme = require("theme") +local styles = require("styles") return function() local menu = widgets.MenuScreen({}) @@ -19,7 +19,7 @@ return function() now_playing:onClicked(function() backstack.push(playing) end) - now_playing:add_style(theme.list_item) + now_playing:add_style(styles.list_item) local indexes = database.indexes() for _, idx in ipairs(indexes) do @@ -32,14 +32,14 @@ return function() } end) end) - btn:add_style(theme.list_item) + btn:add_style(styles.list_item) end local settings = menu.list:add_btn(nil, "Settings") settings:onClicked(function() backstack.push(require("settings").root) end) - settings:add_style(theme.list_item) + settings:add_style(styles.list_item) return menu end diff --git a/lua/settings.lua b/lua/settings.lua index 952292e4..cb726a2a 100644 --- a/lua/settings.lua +++ b/lua/settings.lua @@ -1,7 +1,7 @@ local lvgl = require("lvgl") local backstack = require("backstack") local widgets = require("widgets") -local theme = require("theme") +local styles = require("styles") local volume = require("volume") local display = require("display") local controls = require("controls") @@ -55,7 +55,7 @@ function settings.bluetooth() menu.content:Label { text = "Paired Device", pad_bottom = 1, - }:add_style(theme.settings_title) + }:add_style(styles.settings_title) local paired_container = menu.content:Object { flex = { @@ -81,7 +81,7 @@ function settings.bluetooth() menu.content:Label { text = "Nearby Devices", pad_bottom = 1, - }:add_style(theme.settings_title) + }:add_style(styles.settings_title) local devices = menu.content:List { w = lvgl.PCT(100), @@ -121,7 +121,7 @@ function settings.headphones() menu.content:Label { text = "Maximum volume limit", - }:add_style(theme.settings_title) + }:add_style(styles.settings_title) local volume_chooser = menu.content:Dropdown { options = "Line Level (-10 dB)\nCD Level (+6 dB)\nMaximum (+10dB)", @@ -136,7 +136,7 @@ function settings.headphones() menu.content:Label { text = "Left/Right balance", - }:add_style(theme.settings_title) + }:add_style(styles.settings_title) local balance = menu.content:Slider { w = lvgl.PCT(100), @@ -194,7 +194,7 @@ function settings.display() } brightness_title:Label { text = "Brightness", flex_grow = 1 } local brightness_pct = brightness_title:Label {} - brightness_pct:add_style(theme.settings_title) + brightness_pct:add_style(styles.settings_title) local brightness = menu.content:Slider { w = lvgl.PCT(100), @@ -220,7 +220,7 @@ function settings.input() menu.content:Label { text = "Control scheme", - }:add_style(theme.settings_title) + }:add_style(styles.settings_title) local schemes = controls.schemes() local option_to_scheme = {} @@ -258,7 +258,7 @@ function settings.input() menu.content:Label { text = "Scroll Sensitivity", - }:add_style(theme.settings_title) + }:add_style(styles.settings_title) local slider_scale = 4; -- Power steering local sensitivity = menu.content:Slider { @@ -292,7 +292,7 @@ function settings.database() pad_top = 4, pad_column = 4, } - actions_container:add_style(theme.list_item) + actions_container:add_style(styles.list_item) local update = actions_container:Button {} update:Label { text = "Update" } @@ -321,7 +321,7 @@ function settings.root() } local function section(name) - menu.list:add_text(name):add_style(theme.list_heading) + menu.list:add_text(name):add_style(styles.list_heading) end local function submenu(name, fn) @@ -329,7 +329,7 @@ function settings.root() item:onClicked(function() backstack.push(fn) end) - item:add_style(theme.list_item) + item:add_style(styles.list_item) end section("Audio") diff --git a/lua/theme.lua b/lua/styles.lua similarity index 92% rename from lua/theme.lua rename to lua/styles.lua index 9c808946..76ecad2a 100644 --- a/lua/theme.lua +++ b/lua/styles.lua @@ -1,7 +1,7 @@ local lvgl = require("lvgl") local font = require("font") -local theme = { +local styles = { list_item = lvgl.Style { pad_left = 4, pad_right = 4, @@ -20,4 +20,4 @@ local theme = { } } -return theme +return styles diff --git a/lua/widgets.lua b/lua/widgets.lua index 8905fa43..8253041b 100644 --- a/lua/widgets.lua +++ b/lua/widgets.lua @@ -3,7 +3,7 @@ local power = require("power") local bluetooth = require("bluetooth") local font = require("font") local backstack = require("backstack") -local theme = require("theme") +local styles = require("styles") local database = require("database") local widgets = {} @@ -41,7 +41,7 @@ function widgets.Row(parent, left, right) w = lvgl.PCT(100), h = lvgl.SIZE_CONTENT, } - container:add_style(theme.list_item) + container:add_style(styles.list_item) container:Label { text = left, flex_grow = 1 } container:Label { text = right } end diff --git a/src/lua/CMakeLists.txt b/src/lua/CMakeLists.txt index ff0831c9..72e48aa0 100644 --- a/src/lua/CMakeLists.txt +++ b/src/lua/CMakeLists.txt @@ -3,8 +3,8 @@ # SPDX-License-Identifier: GPL-3.0-only idf_component_register( - SRCS "lua_thread.cpp" "bridge.cpp" "property.cpp" "lua_database.cpp" - "lua_queue.cpp" "lua_version.cpp" "lua_controls.cpp" "registry.cpp" + SRCS "lua_theme.cpp" "lua_thread.cpp" "bridge.cpp" "property.cpp" "lua_database.cpp" + "lua_queue.cpp" "lua_version.cpp" "lua_theme.cpp" "lua_controls.cpp" "registry.cpp" INCLUDE_DIRS "include" REQUIRES "drivers" "lvgl" "tinyfsm" "events" "system_fsm" "database" "esp_timer" "battery" "esp-idf-lua" "luavgl" "lua-linenoise" "lua-term" diff --git a/src/lua/bridge.cpp b/src/lua/bridge.cpp index a26f74bb..44be06f8 100644 --- a/src/lua/bridge.cpp +++ b/src/lua/bridge.cpp @@ -20,6 +20,7 @@ #include "lua_database.hpp" #include "lua_queue.hpp" #include "lua_version.hpp" +#include "lua_theme.hpp" #include "lvgl.h" #include "font/lv_font_loader.h" @@ -84,6 +85,7 @@ auto Bridge::installBaseModules(lua_State* L) -> void { RegisterDatabaseModule(L); RegisterQueueModule(L); RegisterVersionModule(L); + RegisterThemeModule(L); } auto Bridge::installLvgl(lua_State* L) -> void { diff --git a/src/lua/include/lua_theme.hpp b/src/lua/include/lua_theme.hpp new file mode 100644 index 00000000..fed710e0 --- /dev/null +++ b/src/lua/include/lua_theme.hpp @@ -0,0 +1,15 @@ +/* + * Copyright 2024 ailurux + * + * SPDX-License-Identifier: GPL-3.0-only + */ + +#pragma once + +#include "lua.hpp" + +namespace lua { + +auto RegisterThemeModule(lua_State*) -> void; + +} // namespace lua diff --git a/src/lua/lua_theme.cpp b/src/lua/lua_theme.cpp new file mode 100644 index 00000000..a95e634b --- /dev/null +++ b/src/lua/lua_theme.cpp @@ -0,0 +1,89 @@ + +/* + * Copyright 2023 ailurux + * + * SPDX-License-Identifier: GPL-3.0-only + */ + +#include "lua_version.hpp" + +#include + +#include "bridge.hpp" +#include "lua.hpp" + +#include "esp_app_desc.h" +#include "esp_log.h" +#include "lauxlib.h" +#include "lua.h" +#include "lua_thread.hpp" +#include "luavgl.h" +#include "themes.hpp" + +namespace lua { + +static auto set_theme(lua_State* L) -> int { + // lv_style_t* style = luavgl_to_style(L, -1); + // if (style == NULL) { + // ESP_LOGI("DANIEL", "Style was null or malformed??"); + // return 0; + // } + + // ESP_LOGI("DANIEL", "GOT ONE!"); + // themes::Theme::instance()->...; + + /* table is in the stack at index 't' */ + std::string class_name; + lua_pushnil(L); /* first key */ + while (lua_next(L, -2) != 0) { + /* uses 'key' (at index -2) and 'value' (at index -1) */ + if (lua_type(L, -2) == LUA_TSTRING) { + class_name = lua_tostring(L, -2); + } + if (lua_type(L, -1) == LUA_TTABLE) { + // Nesting + lua_pushnil(L); // First key + while (lua_next(L, -2) != 0) { + // Nesting the second + int selector = -1; + lv_style_t* style = NULL; + lua_pushnil(L); // First key + while (lua_next(L, -2) != 0) { + int idx = lua_tointeger(L, -2); + if (idx == 1) { + // Selector + selector = lua_tointeger(L, -1); + } else if (idx == 2) { + // Style + lv_style_t* style = luavgl_to_style(L, -1); + if (style == NULL) { + ESP_LOGI("DANIEL", "Style was null or malformed??"); + return 0; + } else { + ESP_LOGI("DANIEL", "Got style for class %s with selector %d", class_name.c_str(), selector); + } + } + lua_pop(L, 1); + } + lua_pop(L, 1); + } + } + /* removes 'value'; keeps 'key' for next iteration */ + lua_pop(L, 1); + } + return 0; +} + +static const struct luaL_Reg kThemeFuncs[] = {{"set", set_theme}, {NULL, NULL}}; + +static auto lua_theme(lua_State* L) -> int { + luaL_newlib(L, kThemeFuncs); + return 1; +} + +auto RegisterThemeModule(lua_State* L) -> void { + luaL_requiref(L, "theme", lua_theme, true); + lua_pop(L, 1); +} + +} // namespace lua diff --git a/src/ui/CMakeLists.txt b/src/ui/CMakeLists.txt index 6d45fc9f..81bd983b 100644 --- a/src/ui/CMakeLists.txt +++ b/src/ui/CMakeLists.txt @@ -3,7 +3,7 @@ # SPDX-License-Identifier: GPL-3.0-only idf_component_register( - SRCS "lvgl_task.cpp" "ui_fsm.cpp" "screen_splash.cpp" "encoder_input.cpp" + SRCS "themes copy.cpp" "lvgl_task.cpp" "ui_fsm.cpp" "screen_splash.cpp" "encoder_input.cpp" "themes.cpp" "screen.cpp" "modal.cpp" "screen_lua.cpp" "splash.c" "font_fusion_12.c" "font_fusion_10.c" INCLUDE_DIRS "include" From 312b70f9f6a2e3d7d387dfe3502f12f091e8fe37 Mon Sep 17 00:00:00 2001 From: ailurux Date: Thu, 7 Mar 2024 14:20:06 +1100 Subject: [PATCH 03/18] WIP: Base styles are applied --- src/lua/lua_theme.cpp | 1 + src/ui/CMakeLists.txt | 2 +- src/ui/include/themes.hpp | 25 ++---- src/ui/themes.cpp | 179 ++++---------------------------------- 4 files changed, 27 insertions(+), 180 deletions(-) diff --git a/src/lua/lua_theme.cpp b/src/lua/lua_theme.cpp index a95e634b..7b007f4d 100644 --- a/src/lua/lua_theme.cpp +++ b/src/lua/lua_theme.cpp @@ -60,6 +60,7 @@ static auto set_theme(lua_State* L) -> int { ESP_LOGI("DANIEL", "Style was null or malformed??"); return 0; } else { + ui::themes::Theme::instance()->AddStyle(class_name, selector, style); ESP_LOGI("DANIEL", "Got style for class %s with selector %d", class_name.c_str(), selector); } } diff --git a/src/ui/CMakeLists.txt b/src/ui/CMakeLists.txt index 81bd983b..6d45fc9f 100644 --- a/src/ui/CMakeLists.txt +++ b/src/ui/CMakeLists.txt @@ -3,7 +3,7 @@ # SPDX-License-Identifier: GPL-3.0-only idf_component_register( - SRCS "themes copy.cpp" "lvgl_task.cpp" "ui_fsm.cpp" "screen_splash.cpp" "encoder_input.cpp" + SRCS "lvgl_task.cpp" "ui_fsm.cpp" "screen_splash.cpp" "encoder_input.cpp" "themes.cpp" "screen.cpp" "modal.cpp" "screen_lua.cpp" "splash.c" "font_fusion_12.c" "font_fusion_10.c" INCLUDE_DIRS "include" diff --git a/src/ui/include/themes.hpp b/src/ui/include/themes.hpp index 11680c0d..65462f65 100644 --- a/src/ui/include/themes.hpp +++ b/src/ui/include/themes.hpp @@ -1,5 +1,8 @@ #pragma once +#include +#include +#include #include "lvgl.h" namespace ui { @@ -21,29 +24,15 @@ class Theme { void Callback(lv_obj_t* obj); void ApplyStyle(lv_obj_t* obj, Style style); + void AddStyle(std::string key, int selector, lv_style_t* style); + static auto instance() -> Theme*; private: Theme(); - - lv_style_t base_style_; - lv_style_t base_focused_style_; - - lv_style_t button_style_; - lv_style_t bar_style_; - lv_style_t dropdown_style_; - lv_style_t dropdown_list_style_; - - lv_style_t slider_indicator_style_; - lv_style_t slider_knob_style_; - lv_style_t slider_knob_focused_style_; - - lv_style_t switch_style_; - lv_style_t switch_indicator_style_; - lv_style_t switch_indicator_checked_style_; - lv_style_t switch_knob_style_; - + std::map>> style_map; lv_theme_t theme_; + }; } // namespace themes } // namespace ui diff --git a/src/ui/themes.cpp b/src/ui/themes.cpp index f8390570..87b1f92b 100644 --- a/src/ui/themes.cpp +++ b/src/ui/themes.cpp @@ -19,84 +19,6 @@ static void theme_apply_cb(lv_theme_t* th, lv_obj_t* obj) { } Theme::Theme() { - lv_style_init(&base_style_); - lv_style_set_bg_opa(&base_style_, LV_OPA_TRANSP); - lv_style_set_text_font(&base_style_, &font_fusion_12); - lv_style_set_text_color(&base_style_, lv_color_black()); - - lv_style_init(&base_focused_style_); - lv_style_set_bg_opa(&base_focused_style_, LV_OPA_COVER); - lv_style_set_bg_color(&base_focused_style_, - lv_palette_lighten(LV_PALETTE_BLUE, 5)); - - lv_style_init(&button_style_); - lv_style_set_pad_left(&button_style_, 2); - lv_style_set_pad_right(&button_style_, 2); - lv_style_set_pad_top(&button_style_, 1); - lv_style_set_pad_bottom(&button_style_, 1); - lv_style_set_bg_color(&button_style_, lv_color_white()); - lv_style_set_radius(&button_style_, 5); - - lv_style_init(&bar_style_); - lv_style_set_bg_opa(&bar_style_, LV_OPA_COVER); - lv_style_set_radius(&bar_style_, LV_RADIUS_CIRCLE); - - lv_style_init(&slider_indicator_style_); - lv_style_set_radius(&slider_indicator_style_, LV_RADIUS_CIRCLE); - lv_style_set_bg_color(&slider_indicator_style_, - lv_palette_main(LV_PALETTE_BLUE)); - - lv_style_init(&slider_knob_style_); - lv_style_set_radius(&slider_knob_style_, LV_RADIUS_CIRCLE); - lv_style_set_pad_all(&slider_knob_style_, 2); - lv_style_set_bg_color(&slider_knob_style_, lv_color_white()); - lv_style_set_shadow_width(&slider_knob_style_, 5); - lv_style_set_shadow_opa(&slider_knob_style_, LV_OPA_COVER); - - lv_style_init(&slider_knob_focused_style_); - lv_style_set_bg_color(&slider_knob_focused_style_, - lv_palette_lighten(LV_PALETTE_BLUE, 4)); - - lv_style_init(&switch_style_); - lv_style_set_width(&switch_style_, 28); - lv_style_set_height(&switch_style_, 18); - lv_style_set_radius(&switch_style_, LV_RADIUS_CIRCLE); - - lv_style_init(&switch_knob_style_); - lv_style_set_pad_all(&switch_knob_style_, -2); - lv_style_set_radius(&switch_knob_style_, LV_RADIUS_CIRCLE); - lv_style_set_bg_opa(&switch_knob_style_, LV_OPA_COVER); - lv_style_set_bg_color(&switch_knob_style_, lv_color_white()); - - lv_style_init(&slider_knob_focused_style_); - lv_style_set_bg_color(&slider_knob_focused_style_, - lv_palette_lighten(LV_PALETTE_BLUE, 4)); - - lv_style_init(&switch_indicator_style_); - lv_style_set_radius(&switch_indicator_style_, LV_RADIUS_CIRCLE); - lv_style_set_bg_opa(&switch_indicator_style_, LV_OPA_COVER); - lv_style_set_bg_color(&switch_indicator_style_, - lv_palette_main(LV_PALETTE_GREY)); - - lv_style_init(&switch_indicator_checked_style_); - lv_style_set_bg_color(&switch_indicator_checked_style_, - lv_palette_main(LV_PALETTE_BLUE)); - - lv_style_init(&dropdown_style_); - lv_style_set_radius(&dropdown_style_, 2); - lv_style_set_pad_all(&dropdown_style_, 2); - lv_style_set_border_width(&dropdown_style_, 1); - lv_style_set_border_color(&dropdown_style_, lv_palette_main(LV_PALETTE_BLUE)); - lv_style_set_border_side(&dropdown_style_, LV_BORDER_SIDE_FULL); - - lv_style_init(&dropdown_list_style_); - lv_style_set_radius(&dropdown_list_style_, 2); - lv_style_set_border_width(&dropdown_list_style_, 1); - lv_style_set_border_color(&dropdown_list_style_, lv_palette_main(LV_PALETTE_BLUE_GREY)); - lv_style_set_bg_opa(&dropdown_list_style_, LV_OPA_COVER); - lv_style_set_bg_color(&dropdown_list_style_, lv_color_white()); - lv_style_set_pad_all(&dropdown_list_style_, 2); - lv_theme_t* parent_theme = lv_disp_get_theme(NULL); theme_ = *parent_theme; theme_.user_data = this; @@ -111,98 +33,33 @@ void Theme::Apply(void) { } void Theme::Callback(lv_obj_t* obj) { - lv_obj_add_style(obj, &base_style_, LV_PART_MAIN); - lv_obj_add_style(obj, &base_focused_style_, LV_PART_SELECTED); - lv_obj_add_style(obj, &base_focused_style_, LV_STATE_FOCUSED); - - if (lv_obj_check_type(obj, &lv_btn_class)) { - lv_obj_add_style(obj, &button_style_, LV_PART_MAIN); - } else if (lv_obj_check_type(obj, &lv_bar_class)) { - lv_obj_add_style(obj, &bar_style_, LV_PART_MAIN); - } else if (lv_obj_check_type(obj, &lv_slider_class)) { - lv_obj_add_style(obj, &bar_style_, LV_PART_MAIN); - lv_obj_add_style(obj, &slider_indicator_style_, LV_PART_INDICATOR); - lv_obj_add_style(obj, &slider_knob_style_, LV_PART_KNOB); - lv_obj_add_style(obj, &slider_knob_focused_style_, LV_STATE_FOCUSED); - } else if (lv_obj_check_type(obj, &lv_switch_class)) { - lv_obj_add_style(obj, &switch_style_, LV_PART_MAIN); - lv_obj_add_style(obj, &switch_indicator_style_, LV_PART_INDICATOR); - lv_obj_add_style(obj, &switch_indicator_checked_style_, - LV_PART_INDICATOR | LV_STATE_CHECKED); - lv_obj_add_style(obj, &switch_knob_style_, LV_PART_KNOB); - } else if (lv_obj_check_type(obj, &lv_dropdown_class)) { - lv_obj_add_style(obj, &dropdown_style_, LV_PART_MAIN); - } else if (lv_obj_check_type(obj, &lv_dropdownlist_class)) { - lv_obj_add_style(obj, &dropdown_list_style_, LV_PART_MAIN); + // Find and apply base styles + if (auto search = style_map.find("base"); search != style_map.end()) { + for (const auto& pair : search->second) { + lv_obj_add_style(obj, pair.second, pair.first); + } } -} - -void Theme::ApplyStyle(lv_obj_t* obj, Style style) { - switch (style) { - case Style::kTopBar: - lv_obj_set_style_pad_bottom(obj, 1, LV_PART_MAIN); - - lv_obj_set_style_shadow_width(obj, 6, LV_PART_MAIN); - lv_obj_set_style_shadow_opa(obj, LV_OPA_COVER, LV_PART_MAIN); - lv_obj_set_style_shadow_ofs_x(obj, 0, LV_PART_MAIN); - break; - case Style::kPopup: - lv_obj_set_style_shadow_width(obj, 6, LV_PART_MAIN); - lv_obj_set_style_shadow_opa(obj, LV_OPA_COVER, LV_PART_MAIN); - lv_obj_set_style_shadow_ofs_x(obj, 0, LV_PART_MAIN); - lv_obj_set_style_shadow_ofs_y(obj, 0, LV_PART_MAIN); - - lv_obj_set_style_radius(obj, 5, LV_PART_MAIN); - - lv_obj_set_style_bg_opa(obj, LV_OPA_COVER, LV_PART_MAIN); - lv_obj_set_style_bg_color(obj, lv_color_white(), LV_PART_MAIN); - - lv_obj_set_style_pad_top(obj, 2, LV_PART_MAIN); - lv_obj_set_style_pad_bottom(obj, 2, LV_PART_MAIN); - lv_obj_set_style_pad_left(obj, 2, LV_PART_MAIN); - lv_obj_set_style_pad_right(obj, 2, LV_PART_MAIN); - break; - case Style::kTab: - lv_obj_set_style_radius(obj, 0, LV_PART_MAIN); - lv_obj_set_style_border_width(obj, 1, LV_STATE_CHECKED); - lv_obj_set_style_border_color(obj, lv_palette_main(LV_PALETTE_BLUE), - LV_STATE_CHECKED); - lv_obj_set_style_border_side(obj, LV_BORDER_SIDE_BOTTOM, - LV_STATE_CHECKED); - break; - case Style::kButtonPrimary: - lv_obj_set_style_border_width(obj, 1, LV_PART_MAIN); - lv_obj_set_style_border_color(obj, lv_palette_main(LV_PALETTE_BLUE), - LV_PART_MAIN); - lv_obj_set_style_border_side(obj, LV_BORDER_SIDE_FULL, LV_PART_MAIN); - break; - case Style::kMenuSubheadFirst: - case Style::kMenuSubhead: - lv_obj_set_style_text_color(obj, lv_palette_darken(LV_PALETTE_GREY, 3), - LV_PART_MAIN); - lv_obj_set_style_text_align(obj, LV_TEXT_ALIGN_CENTER, LV_PART_MAIN); + // TODO: Apply widget style - lv_obj_set_style_border_width(obj, 1, LV_PART_MAIN); - lv_obj_set_style_border_color(obj, lv_palette_lighten(LV_PALETTE_GREY, 3), - LV_PART_MAIN); - - if (style == Style::kMenuSubhead) { - lv_obj_set_style_border_side( - obj, LV_BORDER_SIDE_TOP | LV_BORDER_SIDE_BOTTOM, LV_PART_MAIN); - } else { - lv_obj_set_style_border_side(obj, LV_BORDER_SIDE_BOTTOM, LV_PART_MAIN); - } - break; - default: - break; - } } +void Theme::ApplyStyle(lv_obj_t* obj, Style style) {} + auto Theme::instance() -> Theme* { static Theme sTheme{}; return &sTheme; } +void Theme::AddStyle(std::string key, int selector, lv_style_t* style) { + style_map.try_emplace(key, std::vector>{}); + if (auto search = style_map.find(key); search != style_map.end()) { + // Key exists + auto &vec = search->second; + // Add it to the list + vec.push_back(std::make_pair(selector, style)); + } +} + } // namespace themes } // namespace ui From dc74bc1de9dd56c4146232622140b56e90dcc43d Mon Sep 17 00:00:00 2001 From: ailurux Date: Thu, 7 Mar 2024 15:46:42 +1100 Subject: [PATCH 04/18] Add other styles to lua theme --- lua/main.lua | 77 +++++++++++++++++++++++++++++++++++++++--- src/ui/themes.cpp | 85 +++++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 155 insertions(+), 7 deletions(-) diff --git a/lua/main.lua b/lua/main.lua index 5cfba47b..6cdef17d 100644 --- a/lua/main.lua +++ b/lua/main.lua @@ -41,22 +41,89 @@ local my_theme = { {lvgl.PART.MAIN, lvgl.Style { bg_opa = lvgl.OPA(0), text_font = font.fusion_12, - text_color = "#ff0000", -- Red to check it applies + text_color = "#000000", }}, {lvgl.STATE.FOCUSED, lvgl.Style { bg_opa = lvgl.OPA(100), - bg_color = "#0000ff", -- ew - text_color = "#ff0000", -- Red to check it applies + bg_color = "#E3F2FD", }}, }, button = { + {lvgl.PART.MAIN, lvgl.Style { + pad_left = 2, + pad_right = 2, + pad_top = 1, + pad_bottom = 1, + bg_color = "#ffffff", + radius = 5, + }}, + }, + bar = { + {lvgl.PART.MAIN, lvgl.Style { + bg_opa = lvgl.OPA(100), + radius = 32767, -- LV_RADIUS_CIRCLE = 0x7fff + }}, + }, + slider = { + {lvgl.PART.MAIN, lvgl.Style { + bg_opa = lvgl.OPA(100), + radius = 32767, -- LV_RADIUS_CIRCLE = 0x7fff + }}, + {lvgl.PART.INDICATOR, lvgl.Style { + radius = 32767, -- LV_RADIUS_CIRCLE = 0x7fff + bg_color = "#2196F3", + }}, + {lvgl.PART.KNOB, lvgl.Style { + radius = 32767, -- LV_RADIUS_CIRCLE = 0x7fff + pad_all = 2, + bg_color = "#ffffff", + shadow_width = 5, + shadow_opa = lvgl.OPA(100) + }}, {lvgl.STATE.FOCUSED, lvgl.Style { - bg_color = "#00ff00", + bg_color = "#BBDEFB", }}, + }, + switch = { {lvgl.PART.MAIN, lvgl.Style { - bg_color = "#00ff00", + bg_opa = lvgl.OPA(100), + width = 28, + height = 18, + radius = 32767, -- LV_RADIUS_CIRCLE = 0x7fff + }}, + {lvgl.PART.INDICATOR, lvgl.Style { + radius = 32767, -- LV_RADIUS_CIRCLE = 0x7fff + bg_color = "#9E9E9E", + }}, + {lvgl.PART.INDICATOR | lvgl.STATE.CHECKED, lvgl.Style { + bg_color = "#2196F3", + }}, + {lvgl.PART.KNOB, lvgl.Style { + radius = 32767, -- LV_RADIUS_CIRCLE = 0x7fff + pad_all = 2, + bg_opa = lvgl.OPA(100), + bg_color = "#ffffff", }}, }, + dropdown = { + {lvgl.PART.MAIN, lvgl.Style{ + radius = 2, + pad_all = 2, + border_width = 1, + border_color = "#2196F3", + border_side = 15, -- LV_BORDER_SIDE_FULL + }} + }, + dropdownlist = { + {lvgl.PART.MAIN, lvgl.Style{ + radius = 2, + pad_all = 2, + border_width = 1, + border_color = "#607D8B", + bg_opa = lvgl.OPA(100), + bg_color = "#ffffff" + }} + } } theme.set(my_theme) diff --git a/src/ui/themes.cpp b/src/ui/themes.cpp index 87b1f92b..4fd477ab 100644 --- a/src/ui/themes.cpp +++ b/src/ui/themes.cpp @@ -40,11 +40,92 @@ void Theme::Callback(lv_obj_t* obj) { } } - // TODO: Apply widget style + // Determine class name + std::string class_name; + if (lv_obj_check_type(obj, &lv_btn_class)) { + class_name = "button"; + } else if (lv_obj_check_type(obj, &lv_bar_class)) { + class_name = "bar"; + } else if (lv_obj_check_type(obj, &lv_slider_class)) { + class_name = "slider"; + } else if (lv_obj_check_type(obj, &lv_switch_class)) { + class_name = "switch"; + } else if (lv_obj_check_type(obj, &lv_dropdown_class)) { + class_name = "dropdown"; + } else if (lv_obj_check_type(obj, &lv_dropdownlist_class)) { + class_name = "dropdownlist"; + } + + // Apply all styles from class + if (auto search = style_map.find(class_name); search != style_map.end()) { + for (const auto& pair : search->second) { + lv_obj_add_style(obj, pair.second, pair.first); + } + } } -void Theme::ApplyStyle(lv_obj_t* obj, Style style) {} +void Theme::ApplyStyle(lv_obj_t* obj, Style style) { + switch (style) { + case Style::kTopBar: + lv_obj_set_style_pad_bottom(obj, 1, LV_PART_MAIN); + + lv_obj_set_style_shadow_width(obj, 6, LV_PART_MAIN); + lv_obj_set_style_shadow_opa(obj, LV_OPA_COVER, LV_PART_MAIN); + lv_obj_set_style_shadow_ofs_x(obj, 0, LV_PART_MAIN); + break; + case Style::kPopup: + lv_obj_set_style_shadow_width(obj, 6, LV_PART_MAIN); + lv_obj_set_style_shadow_opa(obj, LV_OPA_COVER, LV_PART_MAIN); + lv_obj_set_style_shadow_ofs_x(obj, 0, LV_PART_MAIN); + lv_obj_set_style_shadow_ofs_y(obj, 0, LV_PART_MAIN); + + lv_obj_set_style_radius(obj, 5, LV_PART_MAIN); + + lv_obj_set_style_bg_opa(obj, LV_OPA_COVER, LV_PART_MAIN); + lv_obj_set_style_bg_color(obj, lv_color_white(), LV_PART_MAIN); + + lv_obj_set_style_pad_top(obj, 2, LV_PART_MAIN); + lv_obj_set_style_pad_bottom(obj, 2, LV_PART_MAIN); + lv_obj_set_style_pad_left(obj, 2, LV_PART_MAIN); + lv_obj_set_style_pad_right(obj, 2, LV_PART_MAIN); + break; + case Style::kTab: + lv_obj_set_style_radius(obj, 0, LV_PART_MAIN); + + lv_obj_set_style_border_width(obj, 1, LV_STATE_CHECKED); + lv_obj_set_style_border_color(obj, lv_palette_main(LV_PALETTE_BLUE), + LV_STATE_CHECKED); + lv_obj_set_style_border_side(obj, LV_BORDER_SIDE_BOTTOM, + LV_STATE_CHECKED); + break; + case Style::kButtonPrimary: + lv_obj_set_style_border_width(obj, 1, LV_PART_MAIN); + lv_obj_set_style_border_color(obj, lv_palette_main(LV_PALETTE_BLUE), + LV_PART_MAIN); + lv_obj_set_style_border_side(obj, LV_BORDER_SIDE_FULL, LV_PART_MAIN); + break; + case Style::kMenuSubheadFirst: + case Style::kMenuSubhead: + lv_obj_set_style_text_color(obj, lv_palette_darken(LV_PALETTE_GREY, 3), + LV_PART_MAIN); + lv_obj_set_style_text_align(obj, LV_TEXT_ALIGN_CENTER, LV_PART_MAIN); + + lv_obj_set_style_border_width(obj, 1, LV_PART_MAIN); + lv_obj_set_style_border_color(obj, lv_palette_lighten(LV_PALETTE_GREY, 3), + LV_PART_MAIN); + + if (style == Style::kMenuSubhead) { + lv_obj_set_style_border_side( + obj, LV_BORDER_SIDE_TOP | LV_BORDER_SIDE_BOTTOM, LV_PART_MAIN); + } else { + lv_obj_set_style_border_side(obj, LV_BORDER_SIDE_BOTTOM, LV_PART_MAIN); + } + break; + default: + break; + } +} auto Theme::instance() -> Theme* { static Theme sTheme{}; From 20c2816a7b2497c2ab0d07a65fb640050a929371 Mon Sep 17 00:00:00 2001 From: ailurux Date: Thu, 7 Mar 2024 17:52:39 +1100 Subject: [PATCH 05/18] Remove the White Square --- src/ui/screen.cpp | 2 +- src/ui/ui_fsm.cpp | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/ui/screen.cpp b/src/ui/screen.cpp index 3e4f8e42..bacce3f9 100644 --- a/src/ui/screen.cpp +++ b/src/ui/screen.cpp @@ -33,7 +33,7 @@ Screen::Screen() lv_obj_center(alert_); lv_obj_set_style_bg_opa(modal_content_, LV_OPA_TRANSP, 0); - lv_obj_set_style_bg_color(modal_content_, lv_color_black(), 0); + lv_obj_set_style_bg_opa(alert_, LV_OPA_TRANSP, 0); // Disable wrapping by default, since it's confusing and generally makes it // harder to navigate quickly. diff --git a/src/ui/ui_fsm.cpp b/src/ui/ui_fsm.cpp index d98e435d..25ae9817 100644 --- a/src/ui/ui_fsm.cpp +++ b/src/ui/ui_fsm.cpp @@ -457,6 +457,7 @@ void Lua::entry() { sAlertTimer = xTimerCreate("ui_alerts", pdMS_TO_TICKS(1000), false, NULL, alert_timer_callback); sAlertContainer = lv_obj_create(sCurrentScreen->alert()); + lv_obj_set_style_bg_opa(sAlertContainer, LV_OPA_TRANSP, 0); auto& registry = lua::Registry::instance(*sServices); sLua = registry.uiThread(); From 1133d4621508b7ec6bac4ab8731f3493066ceeee Mon Sep 17 00:00:00 2001 From: ailurux Date: Sun, 10 Mar 2024 13:20:17 +1100 Subject: [PATCH 06/18] WIP Lua Theming- style classes --- lua/browser.lua | 4 +- lua/main.lua | 93 +--------------------------- lua/theme_dark.lua | 127 ++++++++++++++++++++++++++++++++++++++ lua/theme_light.lua | 106 +++++++++++++++++++++++++++++++ lua/widgets.lua | 6 +- src/lua/lua_theme.cpp | 25 ++++---- src/ui/include/themes.hpp | 2 +- src/ui/modal.cpp | 2 - src/ui/screen_lua.cpp | 5 +- src/ui/themes.cpp | 64 ++----------------- 10 files changed, 264 insertions(+), 170 deletions(-) create mode 100644 lua/theme_dark.lua create mode 100644 lua/theme_light.lua diff --git a/lua/browser.lua b/lua/browser.lua index e174a05d..055c8641 100644 --- a/lua/browser.lua +++ b/lua/browser.lua @@ -6,6 +6,7 @@ local queue = require("queue") local playing = require("playing") local styles = require("styles") local playback = require("playback") +local theme = require("theme") local browser = {} @@ -43,9 +44,10 @@ function browser.create(opts) pad_right = 4, pad_bottom = 2, bg_opa = lvgl.OPA(100), - bg_color = "#fafafa", scrollbar_mode = lvgl.SCROLLBAR_MODE.OFF, } + theme.set_style(header, "header") + header:Label { text = opts.breadcrumb, diff --git a/lua/main.lua b/lua/main.lua index 6cdef17d..f2533387 100644 --- a/lua/main.lua +++ b/lua/main.lua @@ -35,97 +35,8 @@ GLOBAL_BINDINGS = { end), } -local lvgl = require("lvgl") -local my_theme = { - base = { - {lvgl.PART.MAIN, lvgl.Style { - bg_opa = lvgl.OPA(0), - text_font = font.fusion_12, - text_color = "#000000", - }}, - {lvgl.STATE.FOCUSED, lvgl.Style { - bg_opa = lvgl.OPA(100), - bg_color = "#E3F2FD", - }}, - }, - button = { - {lvgl.PART.MAIN, lvgl.Style { - pad_left = 2, - pad_right = 2, - pad_top = 1, - pad_bottom = 1, - bg_color = "#ffffff", - radius = 5, - }}, - }, - bar = { - {lvgl.PART.MAIN, lvgl.Style { - bg_opa = lvgl.OPA(100), - radius = 32767, -- LV_RADIUS_CIRCLE = 0x7fff - }}, - }, - slider = { - {lvgl.PART.MAIN, lvgl.Style { - bg_opa = lvgl.OPA(100), - radius = 32767, -- LV_RADIUS_CIRCLE = 0x7fff - }}, - {lvgl.PART.INDICATOR, lvgl.Style { - radius = 32767, -- LV_RADIUS_CIRCLE = 0x7fff - bg_color = "#2196F3", - }}, - {lvgl.PART.KNOB, lvgl.Style { - radius = 32767, -- LV_RADIUS_CIRCLE = 0x7fff - pad_all = 2, - bg_color = "#ffffff", - shadow_width = 5, - shadow_opa = lvgl.OPA(100) - }}, - {lvgl.STATE.FOCUSED, lvgl.Style { - bg_color = "#BBDEFB", - }}, - }, - switch = { - {lvgl.PART.MAIN, lvgl.Style { - bg_opa = lvgl.OPA(100), - width = 28, - height = 18, - radius = 32767, -- LV_RADIUS_CIRCLE = 0x7fff - }}, - {lvgl.PART.INDICATOR, lvgl.Style { - radius = 32767, -- LV_RADIUS_CIRCLE = 0x7fff - bg_color = "#9E9E9E", - }}, - {lvgl.PART.INDICATOR | lvgl.STATE.CHECKED, lvgl.Style { - bg_color = "#2196F3", - }}, - {lvgl.PART.KNOB, lvgl.Style { - radius = 32767, -- LV_RADIUS_CIRCLE = 0x7fff - pad_all = 2, - bg_opa = lvgl.OPA(100), - bg_color = "#ffffff", - }}, - }, - dropdown = { - {lvgl.PART.MAIN, lvgl.Style{ - radius = 2, - pad_all = 2, - border_width = 1, - border_color = "#2196F3", - border_side = 15, -- LV_BORDER_SIDE_FULL - }} - }, - dropdownlist = { - {lvgl.PART.MAIN, lvgl.Style{ - radius = 2, - pad_all = 2, - border_width = 1, - border_color = "#607D8B", - bg_opa = lvgl.OPA(100), - bg_color = "#ffffff" - }} - } -} -theme.set(my_theme) +local theme_light = require("theme_dark") +theme.set(theme_light) local backstack = require("backstack") local main_menu = require("main_menu") diff --git a/lua/theme_dark.lua b/lua/theme_dark.lua new file mode 100644 index 00000000..5c91ea1e --- /dev/null +++ b/lua/theme_dark.lua @@ -0,0 +1,127 @@ +local lvgl = require("lvgl") +local font = require("font") + +local background_color = "#242933" +local background_muted = "#353c4b" +local text_color = "#fefefe" +local highlight_color = "#ff0077" + +local theme_dark = { + base = { + {lvgl.PART.MAIN, lvgl.Style { + bg_opa = lvgl.OPA(0), + text_font = font.fusion_12, + text_color = text_color, + }}, + }, + root = { + {lvgl.PART.MAIN, lvgl.Style { + bg_opa = lvgl.OPA(100), + bg_color = background_color, -- Root background color + }}, + }, + header = { + {lvgl.PART.MAIN, lvgl.Style { + bg_opa = lvgl.OPA(100), + bg_color = background_muted, + }}, + }, + button = { + {lvgl.PART.MAIN, lvgl.Style { + pad_left = 2, + pad_right = 2, + pad_top = 1, + pad_bottom = 1, + bg_color = background_color, + radius = 5, + }}, + {lvgl.PART.MAIN | lvgl.STATE.FOCUSED, lvgl.Style { + bg_opa = lvgl.OPA(100), + bg_color = highlight_color, + }}, + }, + listbutton = { + {lvgl.PART.MAIN | lvgl.STATE.FOCUSED, lvgl.Style { + bg_opa = lvgl.OPA(100), + bg_color = highlight_color, + }}, + }, + bar = { + {lvgl.PART.MAIN, lvgl.Style { + bg_opa = lvgl.OPA(100), + radius = 32767, -- LV_RADIUS_CIRCLE = 0x7fff + }}, + }, + slider = { + {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 + pad_all = 2, + bg_color = background_color, + shadow_width = 5, + shadow_opa = lvgl.OPA(100) + }}, + {lvgl.PART.MAIN | lvgl.STATE.FOCUSED, lvgl.Style { + bg_color = background_muted, + }}, + {lvgl.PART.KNOB | lvgl.STATE.FOCUSED, lvgl.Style { + bg_color = highlight_color, + }}, + }, + switch = { + {lvgl.PART.MAIN, lvgl.Style { + bg_opa = lvgl.OPA(100), + width = 28, + height = 12, + radius = 32767, -- LV_RADIUS_CIRCLE = 0x7fff + }}, + {lvgl.PART.MAIN | lvgl.STATE.FOCUSED, lvgl.Style { + bg_color = background_muted, + }}, + {lvgl.PART.INDICATOR, lvgl.Style { + radius = 32767, -- LV_RADIUS_CIRCLE = 0x7fff + bg_color = background_muted, + }}, + {lvgl.PART.INDICATOR | lvgl.STATE.CHECKED, lvgl.Style { + 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 = text_color, + }}, + }, + dropdown = { + {lvgl.PART.MAIN, lvgl.Style{ + radius = 2, + pad_all = 2, + border_width = 1, + border_color = text_color, + border_side = 15, -- LV_BORDER_SIDE_FULL + }}, + {lvgl.PART.MAIN | lvgl.STATE.FOCUSED, lvgl.Style { + border_color = highlight_color, + }}, + }, + dropdownlist = { + {lvgl.PART.MAIN, lvgl.Style{ + radius = 2, + pad_all = 2, + border_width = 1, + border_color = text_color, + bg_opa = lvgl.OPA(100), + bg_color = background_color + }}, + } +} + +return theme_dark diff --git a/lua/theme_light.lua b/lua/theme_light.lua new file mode 100644 index 00000000..637861d9 --- /dev/null +++ b/lua/theme_light.lua @@ -0,0 +1,106 @@ +local lvgl = require("lvgl") +local font = require("font") + +local background_color = "#FFFFFF" +local background_muted = "#FFFFFF" +local text_color = "#000000" +local highlight_color = "#E3F2FD" + +local theme_light = { + base = { + {lvgl.PART.MAIN, lvgl.Style { + bg_opa = lvgl.OPA(0), + bg_color = background_color, -- Root background color + text_font = font.fusion_12, + text_color = text_color, + }}, + {lvgl.STATE.FOCUSED, lvgl.Style { + bg_opa = lvgl.OPA(100), + bg_color = highlight_color, + }}, + }, + root = { + {lvgl.PART.MAIN, lvgl.Style { + bg_opa = lvgl.OPA(100), + bg_color = background_color, -- Root background color + }}, + }, + button = { + {lvgl.PART.MAIN, lvgl.Style { + pad_left = 2, + pad_right = 2, + pad_top = 1, + pad_bottom = 1, + bg_color = background_color, + radius = 5, + }}, + }, + bar = { + {lvgl.PART.MAIN, lvgl.Style { + bg_opa = lvgl.OPA(100), + radius = 32767, -- LV_RADIUS_CIRCLE = 0x7fff + }}, + }, + slider = { + {lvgl.PART.MAIN, lvgl.Style { + bg_opa = lvgl.OPA(100), + 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 + pad_all = 2, + bg_color = background_color, + shadow_width = 5, + shadow_opa = lvgl.OPA(100) + }}, + {lvgl.STATE.FOCUSED, lvgl.Style { + bg_color = highlight_color, + }}, + }, + switch = { + {lvgl.PART.MAIN, lvgl.Style { + bg_opa = lvgl.OPA(100), + width = 28, + height = 18, + radius = 32767, -- LV_RADIUS_CIRCLE = 0x7fff + }}, + {lvgl.PART.INDICATOR, lvgl.Style { + radius = 32767, -- LV_RADIUS_CIRCLE = 0x7fff + bg_color = background_muted, + }}, + {lvgl.PART.INDICATOR | lvgl.STATE.CHECKED, lvgl.Style { + 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_color, + }}, + }, + dropdown = { + {lvgl.PART.MAIN, lvgl.Style{ + radius = 2, + pad_all = 2, + border_width = 1, + border_color = "#2196F3", + border_side = 15, -- LV_BORDER_SIDE_FULL + }} + }, + dropdownlist = { + {lvgl.PART.MAIN, lvgl.Style{ + radius = 2, + pad_all = 2, + border_width = 1, + border_color = "#607D8B", + bg_opa = lvgl.OPA(100), + bg_color = background_color + }} + } +} + +return theme_light diff --git a/lua/widgets.lua b/lua/widgets.lua index 8253041b..15212ded 100644 --- a/lua/widgets.lua +++ b/lua/widgets.lua @@ -5,6 +5,7 @@ local font = require("font") local backstack = require("backstack") local styles = require("styles") local database = require("database") +local theme = require("theme") local widgets = {} @@ -66,10 +67,7 @@ function widgets.StatusBar(parent, opts) } if not opts.transparent_bg then - status_bar.root:set { - bg_opa = lvgl.OPA(100), - bg_color = "#fafafa", - } + theme.set_style(status_bar.root, "header"); end if opts.back_cb then diff --git a/src/lua/lua_theme.cpp b/src/lua/lua_theme.cpp index 7b007f4d..2fcd71e5 100644 --- a/src/lua/lua_theme.cpp +++ b/src/lua/lua_theme.cpp @@ -22,17 +22,19 @@ namespace lua { -static auto set_theme(lua_State* L) -> int { - // lv_style_t* style = luavgl_to_style(L, -1); - // if (style == NULL) { - // ESP_LOGI("DANIEL", "Style was null or malformed??"); - // return 0; - // } - - // ESP_LOGI("DANIEL", "GOT ONE!"); - // themes::Theme::instance()->...; +static auto set_style(lua_State* L) -> int { + // Get the object and class name from the stack + if (lua_type(L, -1) == LUA_TSTRING) { + std::string class_name = lua_tostring(L, -1); + lv_obj_t* obj = luavgl_to_obj(L, -2); + if (obj != NULL) { + ui::themes::Theme::instance()->ApplyStyle(obj, class_name); + } + } + return 0; +} - /* table is in the stack at index 't' */ +static auto set_theme(lua_State* L) -> int { std::string class_name; lua_pushnil(L); /* first key */ while (lua_next(L, -2) != 0) { @@ -61,7 +63,6 @@ static auto set_theme(lua_State* L) -> int { return 0; } else { ui::themes::Theme::instance()->AddStyle(class_name, selector, style); - ESP_LOGI("DANIEL", "Got style for class %s with selector %d", class_name.c_str(), selector); } } lua_pop(L, 1); @@ -75,7 +76,7 @@ static auto set_theme(lua_State* L) -> int { return 0; } -static const struct luaL_Reg kThemeFuncs[] = {{"set", set_theme}, {NULL, NULL}}; +static const struct luaL_Reg kThemeFuncs[] = {{"set", set_theme}, {"set_style", set_style}, {NULL, NULL}}; static auto lua_theme(lua_State* L) -> int { luaL_newlib(L, kThemeFuncs); diff --git a/src/ui/include/themes.hpp b/src/ui/include/themes.hpp index 65462f65..09b9cdce 100644 --- a/src/ui/include/themes.hpp +++ b/src/ui/include/themes.hpp @@ -22,7 +22,7 @@ class Theme { public: void Apply(void); void Callback(lv_obj_t* obj); - void ApplyStyle(lv_obj_t* obj, Style style); + void ApplyStyle(lv_obj_t* obj, std::string style_key); void AddStyle(std::string key, int selector, lv_style_t* style); diff --git a/src/ui/modal.cpp b/src/ui/modal.cpp index 88f6d3ef..ec541914 100644 --- a/src/ui/modal.cpp +++ b/src/ui/modal.cpp @@ -41,8 +41,6 @@ Modal::Modal(Screen* host) lv_obj_set_style_bg_opa(root_, LV_OPA_COVER, 0); lv_obj_set_style_bg_color(root_, lv_color_white(), 0); - themes::Theme::instance()->ApplyStyle(root_, themes::Style::kPopup); - host_->modal_group(group_); } diff --git a/src/ui/screen_lua.cpp b/src/ui/screen_lua.cpp index 5130b4f7..b3554241 100644 --- a/src/ui/screen_lua.cpp +++ b/src/ui/screen_lua.cpp @@ -8,13 +8,16 @@ #include "core/lv_obj_tree.h" #include "lua.hpp" +#include "themes.hpp" #include "luavgl.h" namespace ui { namespace screens { -Lua::Lua() : s_(nullptr), obj_ref_() {} +Lua::Lua() : s_(nullptr), obj_ref_() { + themes::Theme::instance()->ApplyStyle(root_, "root"); +} Lua::~Lua() { if (s_ && obj_ref_) { diff --git a/src/ui/themes.cpp b/src/ui/themes.cpp index 4fd477ab..88f45b1b 100644 --- a/src/ui/themes.cpp +++ b/src/ui/themes.cpp @@ -44,6 +44,8 @@ void Theme::Callback(lv_obj_t* obj) { std::string class_name; if (lv_obj_check_type(obj, &lv_btn_class)) { class_name = "button"; + } else if (lv_obj_check_type(obj, &lv_list_btn_class)) { + class_name = "listbutton"; } else if (lv_obj_check_type(obj, &lv_bar_class)) { class_name = "bar"; } else if (lv_obj_check_type(obj, &lv_slider_class)) { @@ -65,65 +67,11 @@ void Theme::Callback(lv_obj_t* obj) { } -void Theme::ApplyStyle(lv_obj_t* obj, Style style) { - switch (style) { - case Style::kTopBar: - lv_obj_set_style_pad_bottom(obj, 1, LV_PART_MAIN); - - lv_obj_set_style_shadow_width(obj, 6, LV_PART_MAIN); - lv_obj_set_style_shadow_opa(obj, LV_OPA_COVER, LV_PART_MAIN); - lv_obj_set_style_shadow_ofs_x(obj, 0, LV_PART_MAIN); - break; - case Style::kPopup: - lv_obj_set_style_shadow_width(obj, 6, LV_PART_MAIN); - lv_obj_set_style_shadow_opa(obj, LV_OPA_COVER, LV_PART_MAIN); - lv_obj_set_style_shadow_ofs_x(obj, 0, LV_PART_MAIN); - lv_obj_set_style_shadow_ofs_y(obj, 0, LV_PART_MAIN); - - lv_obj_set_style_radius(obj, 5, LV_PART_MAIN); - - lv_obj_set_style_bg_opa(obj, LV_OPA_COVER, LV_PART_MAIN); - lv_obj_set_style_bg_color(obj, lv_color_white(), LV_PART_MAIN); - - lv_obj_set_style_pad_top(obj, 2, LV_PART_MAIN); - lv_obj_set_style_pad_bottom(obj, 2, LV_PART_MAIN); - lv_obj_set_style_pad_left(obj, 2, LV_PART_MAIN); - lv_obj_set_style_pad_right(obj, 2, LV_PART_MAIN); - break; - case Style::kTab: - lv_obj_set_style_radius(obj, 0, LV_PART_MAIN); - - lv_obj_set_style_border_width(obj, 1, LV_STATE_CHECKED); - lv_obj_set_style_border_color(obj, lv_palette_main(LV_PALETTE_BLUE), - LV_STATE_CHECKED); - lv_obj_set_style_border_side(obj, LV_BORDER_SIDE_BOTTOM, - LV_STATE_CHECKED); - break; - case Style::kButtonPrimary: - lv_obj_set_style_border_width(obj, 1, LV_PART_MAIN); - lv_obj_set_style_border_color(obj, lv_palette_main(LV_PALETTE_BLUE), - LV_PART_MAIN); - lv_obj_set_style_border_side(obj, LV_BORDER_SIDE_FULL, LV_PART_MAIN); - break; - case Style::kMenuSubheadFirst: - case Style::kMenuSubhead: - lv_obj_set_style_text_color(obj, lv_palette_darken(LV_PALETTE_GREY, 3), - LV_PART_MAIN); - lv_obj_set_style_text_align(obj, LV_TEXT_ALIGN_CENTER, LV_PART_MAIN); - - lv_obj_set_style_border_width(obj, 1, LV_PART_MAIN); - lv_obj_set_style_border_color(obj, lv_palette_lighten(LV_PALETTE_GREY, 3), - LV_PART_MAIN); - - if (style == Style::kMenuSubhead) { - lv_obj_set_style_border_side( - obj, LV_BORDER_SIDE_TOP | LV_BORDER_SIDE_BOTTOM, LV_PART_MAIN); - } else { - lv_obj_set_style_border_side(obj, LV_BORDER_SIDE_BOTTOM, LV_PART_MAIN); +void Theme::ApplyStyle(lv_obj_t* obj, std::string style_key) { + if (auto search = style_map.find(style_key); search != style_map.end()) { + for (const auto& pair : search->second) { + lv_obj_add_style(obj, pair.second, pair.first); } - break; - default: - break; } } From f1599c237c36f08e96dd5d1ab98bc04e35e1ade1 Mon Sep 17 00:00:00 2001 From: ailurux Date: Mon, 18 Mar 2024 13:11:13 +1100 Subject: [PATCH 07/18] Better styling for settings pages + dropdown menus --- lua/main.lua | 2 +- lua/settings.lua | 38 ++++++++++++++---------- lua/styles.lua | 5 ---- lua/theme_dark.lua | 19 +++++++++--- lua/theme_light.lua | 72 +++++++++++++++++++++++++++++++++------------ 5 files changed, 92 insertions(+), 44 deletions(-) diff --git a/lua/main.lua b/lua/main.lua index f2533387..f7dfb2c9 100644 --- a/lua/main.lua +++ b/lua/main.lua @@ -35,7 +35,7 @@ GLOBAL_BINDINGS = { end), } -local theme_light = require("theme_dark") +local theme_light = require("theme_light") theme.set(theme_light) local backstack = require("backstack") diff --git a/lua/settings.lua b/lua/settings.lua index cb726a2a..aac5ce9b 100644 --- a/lua/settings.lua +++ b/lua/settings.lua @@ -6,6 +6,7 @@ local volume = require("volume") local display = require("display") local controls = require("controls") local bluetooth = require("bluetooth") +local theme = require("theme") local database = require("database") local settings = {} @@ -23,7 +24,7 @@ local function SettingsScreen(title) align_items = "flex-start", align_content = "flex-start", }, - w = lvgl.PCT(100), + w = lvgl.PCT(90), flex_grow = 1, pad_left = 4, pad_right = 4, @@ -52,10 +53,10 @@ function settings.bluetooth() bluetooth.enabled:set(enabled) end) - menu.content:Label { + theme.set_style(menu.content:Label { text = "Paired Device", pad_bottom = 1, - }:add_style(styles.settings_title) + }, "settings_title") local paired_container = menu.content:Object { flex = { @@ -78,10 +79,10 @@ function settings.bluetooth() bluetooth.paired_device:set() end) - menu.content:Label { + theme.set_style(menu.content:Label { text = "Nearby Devices", pad_bottom = 1, - }:add_style(styles.settings_title) + }, "settings_title") local devices = menu.content:List { w = lvgl.PCT(100), @@ -119,9 +120,9 @@ end function settings.headphones() local menu = SettingsScreen("Headphones") - menu.content:Label { - text = "Maximum volume limit", - }:add_style(styles.settings_title) + theme.set_style(menu.content:Label { + text = "Maxiumum volume limit", + }, "settings_title") local volume_chooser = menu.content:Dropdown { options = "Line Level (-10 dB)\nCD Level (+6 dB)\nMaximum (+10dB)", @@ -134,9 +135,9 @@ function settings.headphones() volume.limit_db:set(limits[selection]) end) - menu.content:Label { + theme.set_style(menu.content:Label { text = "Left/Right balance", - }:add_style(styles.settings_title) + }, "settings_title") local balance = menu.content:Slider { w = lvgl.PCT(100), @@ -191,10 +192,11 @@ function settings.display() }, w = lvgl.PCT(100), h = lvgl.SIZE_CONTENT, + } brightness_title:Label { text = "Brightness", flex_grow = 1 } local brightness_pct = brightness_title:Label {} - brightness_pct:add_style(styles.settings_title) + theme.set_style(brightness_pct, "settings_title") local brightness = menu.content:Slider { w = lvgl.PCT(100), @@ -218,9 +220,9 @@ end function settings.input() local menu = SettingsScreen("Input Method") - menu.content:Label { + theme.set_style(menu.content:Label { text = "Control scheme", - }:add_style(styles.settings_title) + }, "settings_title") local schemes = controls.schemes() local option_to_scheme = {} @@ -256,9 +258,9 @@ function settings.input() controls.scheme:set(scheme) end) - menu.content:Label { + theme.set_style(menu.content:Label { text = "Scroll Sensitivity", - }:add_style(styles.settings_title) + }, "settings_title") local slider_scale = 4; -- Power steering local sensitivity = menu.content:Slider { @@ -321,7 +323,11 @@ function settings.root() } local function section(name) - menu.list:add_text(name):add_style(styles.list_heading) + local elem = menu.list:Label { + text = name, + pad_left = 4, + } + theme.set_style(elem, "settings_title") end local function submenu(name, fn) diff --git a/lua/styles.lua b/lua/styles.lua index 76ecad2a..fd45263e 100644 --- a/lua/styles.lua +++ b/lua/styles.lua @@ -13,11 +13,6 @@ local styles = { text_font = font.fusion_10, text_align = lvgl.ALIGN.CENTER, }, - settings_title = lvgl.Style { - pad_top = 2, - pad_bottom = 4, - text_font = font.fusion_10, - } } return styles diff --git a/lua/theme_dark.lua b/lua/theme_dark.lua index 5c91ea1e..e5082ff1 100644 --- a/lua/theme_dark.lua +++ b/lua/theme_dark.lua @@ -1,9 +1,9 @@ local lvgl = require("lvgl") local font = require("font") -local background_color = "#242933" +local background_color = "#1c1c1c" local background_muted = "#353c4b" -local text_color = "#fefefe" +local text_color = "#eeeeee" local highlight_color = "#ff0077" local theme_dark = { @@ -11,13 +11,13 @@ local theme_dark = { {lvgl.PART.MAIN, lvgl.Style { bg_opa = lvgl.OPA(0), text_font = font.fusion_12, - text_color = text_color, }}, }, root = { {lvgl.PART.MAIN, lvgl.Style { bg_opa = lvgl.OPA(100), bg_color = background_color, -- Root background color + text_color = text_color }}, }, header = { @@ -121,7 +121,18 @@ local theme_dark = { bg_opa = lvgl.OPA(100), bg_color = background_color }}, - } + {lvgl.PART.SELECTED | lvgl.STATE.CHECKED, lvgl.Style { + bg_color = highlight_color, + }}, + }, + settings_title = { + {lvgl.PART.MAIN, lvgl.Style { + pad_top = 2, + pad_bottom = 4, + text_font = font.fusion_10, + text_color = highlight_color, + }}, + }, } return theme_dark diff --git a/lua/theme_light.lua b/lua/theme_light.lua index 637861d9..82abd368 100644 --- a/lua/theme_light.lua +++ b/lua/theme_light.lua @@ -1,28 +1,29 @@ local lvgl = require("lvgl") local font = require("font") -local background_color = "#FFFFFF" -local background_muted = "#FFFFFF" +local background_color = "#ffffff" +local background_muted = "#fafafa" local text_color = "#000000" -local highlight_color = "#E3F2FD" +local highlight_color = "#CE93D8" local theme_light = { base = { {lvgl.PART.MAIN, lvgl.Style { bg_opa = lvgl.OPA(0), - bg_color = background_color, -- Root background color text_font = font.fusion_12, - text_color = text_color, - }}, - {lvgl.STATE.FOCUSED, lvgl.Style { - bg_opa = lvgl.OPA(100), - bg_color = highlight_color, }}, }, root = { {lvgl.PART.MAIN, lvgl.Style { bg_opa = lvgl.OPA(100), bg_color = background_color, -- Root background color + text_color = text_color + }}, + }, + header = { + {lvgl.PART.MAIN, lvgl.Style { + bg_opa = lvgl.OPA(100), + bg_color = background_muted, }}, }, button = { @@ -34,6 +35,16 @@ local theme_light = { bg_color = background_color, radius = 5, }}, + {lvgl.PART.MAIN | lvgl.STATE.FOCUSED, lvgl.Style { + bg_opa = lvgl.OPA(100), + bg_color = highlight_color, + }}, + }, + listbutton = { + {lvgl.PART.MAIN | lvgl.STATE.FOCUSED, lvgl.Style { + bg_opa = lvgl.OPA(100), + bg_color = highlight_color, + }}, }, bar = { {lvgl.PART.MAIN, lvgl.Style { @@ -44,6 +55,7 @@ local theme_light = { slider = { {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 { @@ -57,7 +69,13 @@ local theme_light = { shadow_width = 5, shadow_opa = lvgl.OPA(100) }}, - {lvgl.STATE.FOCUSED, lvgl.Style { + {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, }}, }, @@ -65,21 +83,24 @@ local theme_light = { {lvgl.PART.MAIN, lvgl.Style { bg_opa = lvgl.OPA(100), width = 28, - height = 18, + height = 8, radius = 32767, -- LV_RADIUS_CIRCLE = 0x7fff }}, {lvgl.PART.INDICATOR, lvgl.Style { radius = 32767, -- LV_RADIUS_CIRCLE = 0x7fff bg_color = background_muted, }}, - {lvgl.PART.INDICATOR | lvgl.STATE.CHECKED, lvgl.Style { + {lvgl.PART.MAIN | lvgl.STATE.CHECKED, lvgl.Style { 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_color, + bg_color = background_muted, + }}, + {lvgl.PART.KNOB | lvgl.STATE.FOCUSED, lvgl.Style { + bg_color = highlight_color, }}, }, dropdown = { @@ -87,20 +108,35 @@ local theme_light = { radius = 2, pad_all = 2, border_width = 1, - border_color = "#2196F3", + border_color = background_muted, border_side = 15, -- LV_BORDER_SIDE_FULL - }} + bg_color = background_color, + }}, + {lvgl.PART.MAIN | lvgl.STATE.FOCUSED, lvgl.Style { + border_color = highlight_color, + }}, }, dropdownlist = { {lvgl.PART.MAIN, lvgl.Style{ radius = 2, pad_all = 2, border_width = 1, - border_color = "#607D8B", + border_color = highlight_color, bg_opa = lvgl.OPA(100), bg_color = background_color - }} - } + }}, + {lvgl.PART.SELECTED | lvgl.STATE.CHECKED, lvgl.Style { + bg_color = highlight_color, + }}, + }, + settings_title = { + {lvgl.PART.MAIN, lvgl.Style { + pad_top = 2, + pad_bottom = 4, + text_font = font.fusion_10, + text_color = highlight_color, + }}, + }, } return theme_light From 170c23b832eed6dad2b118e50164464cc93e5c4c Mon Sep 17 00:00:00 2001 From: ailurux Date: Wed, 20 Mar 2024 11:47:58 +1100 Subject: [PATCH 08/18] Fairyfloss dark theme palette test --- lua/main.lua | 4 ++-- lua/theme_dark.lua | 26 ++++++++++++++++---------- 2 files changed, 18 insertions(+), 12 deletions(-) diff --git a/lua/main.lua b/lua/main.lua index f7dfb2c9..291f524e 100644 --- a/lua/main.lua +++ b/lua/main.lua @@ -35,8 +35,8 @@ GLOBAL_BINDINGS = { end), } -local theme_light = require("theme_light") -theme.set(theme_light) +local theme_dark = require("theme_dark") +theme.set(theme_dark) local backstack = require("backstack") local main_menu = require("main_menu") diff --git a/lua/theme_dark.lua b/lua/theme_dark.lua index e5082ff1..21cfe76c 100644 --- a/lua/theme_dark.lua +++ b/lua/theme_dark.lua @@ -1,10 +1,10 @@ local lvgl = require("lvgl") local font = require("font") -local background_color = "#1c1c1c" -local background_muted = "#353c4b" +local background_color = "#5a5474" +local background_muted = "#464258" local text_color = "#eeeeee" -local highlight_color = "#ff0077" +local highlight_color = "#9773d3" local theme_dark = { base = { @@ -65,7 +65,7 @@ local theme_dark = { {lvgl.PART.KNOB, lvgl.Style { radius = 32767, -- LV_RADIUS_CIRCLE = 0x7fff pad_all = 2, - bg_color = background_color, + bg_color = background_muted, shadow_width = 5, shadow_opa = lvgl.OPA(100) }}, @@ -75,16 +75,18 @@ local theme_dark = { {lvgl.PART.KNOB | lvgl.STATE.FOCUSED, lvgl.Style { bg_color = highlight_color, }}, + {lvgl.PART.INDICATOR | lvgl.STATE.CHECKED, lvgl.Style { + bg_color = highlight_color, + }}, }, switch = { {lvgl.PART.MAIN, lvgl.Style { bg_opa = lvgl.OPA(100), width = 28, - height = 12, + height = 8, radius = 32767, -- LV_RADIUS_CIRCLE = 0x7fff - }}, - {lvgl.PART.MAIN | lvgl.STATE.FOCUSED, lvgl.Style { bg_color = background_muted, + border_color = highlight_color, }}, {lvgl.PART.INDICATOR, lvgl.Style { radius = 32767, -- LV_RADIUS_CIRCLE = 0x7fff @@ -97,7 +99,10 @@ local theme_dark = { radius = 32767, -- LV_RADIUS_CIRCLE = 0x7fff pad_all = 2, bg_opa = lvgl.OPA(100), - bg_color = text_color, + bg_color = background_muted, + }}, + {lvgl.PART.KNOB | lvgl.STATE.FOCUSED, lvgl.Style { + bg_color = highlight_color, }}, }, dropdown = { @@ -105,8 +110,9 @@ local theme_dark = { radius = 2, pad_all = 2, border_width = 1, - border_color = text_color, + border_color = background_muted, border_side = 15, -- LV_BORDER_SIDE_FULL + bg_color = background_color, }}, {lvgl.PART.MAIN | lvgl.STATE.FOCUSED, lvgl.Style { border_color = highlight_color, @@ -117,7 +123,7 @@ local theme_dark = { radius = 2, pad_all = 2, border_width = 1, - border_color = text_color, + border_color = highlight_color, bg_opa = lvgl.OPA(100), bg_color = background_color }}, From 4293c488364171b4abf5d1dccac1a1fb55a42a31 Mon Sep 17 00:00:00 2001 From: ailurux Date: Wed, 20 Mar 2024 14:08:11 +1100 Subject: [PATCH 09/18] Fix bad merge --- lua/main.lua | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/lua/main.lua b/lua/main.lua index d84e2417..1a605dab 100644 --- a/lua/main.lua +++ b/lua/main.lua @@ -1,9 +1,14 @@ local font = require("font") local vol = require("volume") local theme = require("theme") +local controls = require("controls") +local time = require("time") + +local lock_time = time.ticks() -- Set up property bindings that are used across every screen. GLOBAL_BINDINGS = { + -- Show an alert with the current volume whenever the volume changes vol.current_pct:bind(function(pct) require("alerts").show(function() local container = lvgl.Object(nil, { From f29d31d01c26ee0cb175e44ac4096e5904c282cd Mon Sep 17 00:00:00 2001 From: ailurux Date: Wed, 20 Mar 2024 15:30:32 +1100 Subject: [PATCH 10/18] Image recolouring for database indicator --- lua/img/db.png | Bin 4557 -> 5361 bytes lua/theme_dark.lua | 10 ++++++++++ lua/widgets.lua | 2 ++ 3 files changed, 12 insertions(+) diff --git a/lua/img/db.png b/lua/img/db.png index 3952ded29a8857e43767f4697cf59cc5c1dccddd..6245fab2a6a6da42109d057fcab104d26012f86a 100644 GIT binary patch delta 1539 zcmV+e2K@QWBk?JaBYy)TdQ@0+Qek%>aB^>EX>4U6ba`-PAZ2)IW&i+q+U=KJ*5f7& zhVNNLmw+UM#Bwm0bGm~rzb}|LUot;uI_*U-0x@6(gwP||NjU%dbA&%oI3^BB&2z~) zqNI`vR}4H}STIuAO7w3$`NRZBg1M+ZN^G_HwkokA{ht$e&jd3Z) zT!TORG;}mBOOajl<&r-Y<-32u`jC^p$>rXLUo7(H z&ivIPlY5K!PIZj#W1MSpNHt}gIqyR~jI6Via^Y_EwuJ6Yr95{ zxbK9SOi)Hxao6zm=HJwB6pb-+hA8^&E7-*|3t`42H(SgC5E|R3AR3m7>2~zjGQkQ6 z>T{rwjuk0r4TLD~zy%olgfI!S7N|~ZD8xj2I;8S5al8z4w zjYyR{tbgPrk^u)(V8XC z49nT_d<<6;OQx31%&k~;@#N~+&E1RF!bJie(^`;^#Y-u*V$%xE6{{;|)OzqCM>_P# zhaG;Dqc((3OHEsD*1VNgJ9h~kiQIFy?!EMS(tn9iI@76VKJE0goOK}7hKw|HNslHNsC--w|w5jm|B>|1y)F3u{sG#{c(a8+NI1z~3B!GbC$t*gh#FN~CtOJF1L{Ux47{;xiHcF7v#c3_eSnpZr`ES>w1050)#9K z%QmS|kbam{+3~?^_Mi9RcNcyXeH48ZeH48ZeH48Z{cjb?z>f#~qYQro7?i+El8r*k z0004mlOF*Ue>+ISP!xv$rbP5f8Y;#VQ6KK1>?h)YoU6g0FzxU_rf6=lw0|FB9EHf>Kc!PL)(=j>k z6N{{%XvF8lV-__?{K$3H=QqxIj|HA7TB+nbu}CcCTUc#jRD z(4lyKfABx}JzG0BIpHUTV?gJN>wb&?piX>DawbYX39Jt8qT zF)%qWG&DCgHaRdgF_RAl83HmivnmFI0+Y%ITqHMRH)1t5WMVBaGh{O@G&E*mEn;Ob zFfBAWWjHf5WjAIvF=UfU2q-R1VPhaqQz;?}ARr(hARu#eMRs&LcW7m9Jt8tNGBhnP zGc7VOR53IyuC}dAbL_t(2&$W+1 z4!|G?1PAoe|9`g)UfiZ3Hga(<;y}m%r(*yS(SZAfX5x_aB^>EX>4U6ba`-PAZ2)IW&i+q+Ko|JZp9!B z{O1%sg0}!Z4tdFwdV?On1DK?#RH=^eGC20wr1R_Zoj$rqm1J9~*>fg{xw$krm8R@N zGuhBnUSau(r|UuhDXh-WCGZnCe2&a z@k10+2}Tx^1*7b>JVra#XiMsSOpD|#b1`TWDkVGoDr5!wJUY?$OMDdC=0Y!E#Vb|7 zNls-YXw^JfYCooJ*seJtk?}Ra9bzqG@r0)Y1u*w$)aY8~&)wWR`a}LRR{93oVs{C! z{A|Gh00Dz(Lw`e1Nkc;*aB^>EX>4Tx0C=2zkv&MmP!xqvQ>7vm2Rn#3WT*wfE-KE=X9p)m7b)?+q|hS92aor0 z-aUu=?gNBom1$N_0?>5Z%%qZHF25>q@-S%1cyB&FfIzV6*x+{Jm8f8C#J zKrL7d2#CZ9W|%hd2Jy_MZE)TvjkWpZUY8P07HxkO0z*}Mkq$s zgd)a-WD-$;g$OnOk!*t7fDd4G5;L(*VnU;d_QPLn?!_ZULjV9-j}97`wi(X=0000< KMNUMnLSTY4zIbo| diff --git a/lua/theme_dark.lua b/lua/theme_dark.lua index 21cfe76c..8c1da65c 100644 --- a/lua/theme_dark.lua +++ b/lua/theme_dark.lua @@ -17,6 +17,8 @@ local theme_dark = { {lvgl.PART.MAIN, lvgl.Style { bg_opa = lvgl.OPA(100), bg_color = background_color, -- Root background color + bg_grad_dir = 1, + bg_grad_color = "#1d0e38", text_color = text_color }}, }, @@ -33,11 +35,13 @@ local theme_dark = { pad_top = 1, pad_bottom = 1, bg_color = background_color, + img_opa = 180, radius = 5, }}, {lvgl.PART.MAIN | lvgl.STATE.FOCUSED, lvgl.Style { bg_opa = lvgl.OPA(100), bg_color = highlight_color, + img_opa = 255, }}, }, listbutton = { @@ -131,6 +135,12 @@ local theme_dark = { bg_color = highlight_color, }}, }, + database_indicator = { + {lvgl.PART.MAIN, lvgl.Style { + img_recolor_opa = 180, + img_recolor = highlight_color, + }}, + }, settings_title = { {lvgl.PART.MAIN, lvgl.Style { pad_top = 2, diff --git a/lua/widgets.lua b/lua/widgets.lua index 15212ded..b9588edb 100644 --- a/lua/widgets.lua +++ b/lua/widgets.lua @@ -94,6 +94,8 @@ function widgets.StatusBar(parent, opts) status_bar.db_updating = status_bar.root:Image { src = "//lua/img/db.png" } + theme.set_style(status_bar.db_updating, "database_indicator") + status_bar.bluetooth = status_bar.root:Image {} status_bar.battery = status_bar.root:Image {} status_bar.chg = status_bar.battery:Image { From 223c4cbbc91ca6727dd1ceb076640e59bb6ec25b Mon Sep 17 00:00:00 2001 From: ailurux Date: Wed, 27 Mar 2024 15:41:22 +1100 Subject: [PATCH 11/18] Allow image recoloring properties to be inherited by children --- lib/lvgl/src/misc/lv_style.c | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/lib/lvgl/src/misc/lv_style.c b/lib/lvgl/src/misc/lv_style.c index 419c29e4..baf135ad 100644 --- a/lib/lvgl/src/misc/lv_style.c +++ b/lib/lvgl/src/misc/lv_style.c @@ -92,9 +92,11 @@ const uint8_t _lv_style_builtin_prop_flag_lookup_table[_LV_STYLE_NUM_BUILT_IN_PR [LV_STYLE_SHADOW_COLOR] = 0, [LV_STYLE_SHADOW_OPA] = LV_STYLE_PROP_EXT_DRAW, - [LV_STYLE_IMG_OPA] = 0, - [LV_STYLE_IMG_RECOLOR] = 0, - [LV_STYLE_IMG_RECOLOR_OPA] = 0, + // Image style inheritance + // https://github.com/lvgl/lvgl/pull/4664 + [LV_STYLE_IMG_OPA] = LV_STYLE_PROP_INHERIT, + [LV_STYLE_IMG_RECOLOR] = LV_STYLE_PROP_INHERIT, + [LV_STYLE_IMG_RECOLOR_OPA] = LV_STYLE_PROP_INHERIT, [LV_STYLE_LINE_WIDTH] = LV_STYLE_PROP_EXT_DRAW, [LV_STYLE_LINE_DASH_WIDTH] = 0, From 489fbceb2b5a623ea502ab647f023d2e2e566121 Mon Sep 17 00:00:00 2001 From: ailurux Date: Wed, 27 Mar 2024 16:07:22 +1100 Subject: [PATCH 12/18] Update icons and volume dialogue to use themes --- lua/img/enqueue.png | Bin 590 -> 4782 bytes lua/img/next.png | Bin 621 -> 4811 bytes lua/img/next_disabled.png | Bin 1539 -> 0 bytes lua/img/pause.png | Bin 581 -> 4771 bytes lua/img/play.png | Bin 617 -> 4813 bytes lua/img/play_small.png | Bin 593 -> 4780 bytes lua/img/prev.png | Bin 626 -> 4810 bytes lua/img/prev_disabled.png | Bin 1533 -> 0 bytes lua/img/repeat.png | Bin 4786 -> 5023 bytes lua/img/repeat_disabled.png | Bin 7287 -> 0 bytes lua/img/shuffle.png | Bin 4809 -> 5055 bytes lua/img/shuffle_disabled.png | Bin 8706 -> 0 bytes lua/main.lua | 9 ++++---- lua/playing.lua | 43 +++++++++++++++++------------------ lua/theme_dark.lua | 26 +++++++++++++++++++-- lua/theme_light.lua | 38 ++++++++++++++++++++++++++++--- 16 files changed, 84 insertions(+), 32 deletions(-) delete mode 100644 lua/img/next_disabled.png delete mode 100644 lua/img/prev_disabled.png delete mode 100644 lua/img/repeat_disabled.png delete mode 100644 lua/img/shuffle_disabled.png diff --git a/lua/img/enqueue.png b/lua/img/enqueue.png index 9f720969227166aeaeb1e6902fcb7073fb4238e1..b5136a7733e4b9fd0d9131f0b12cd0420d47add6 100644 GIT binary patch literal 4782 zcmeHKc~leU77rk6D{i17$Qy!7K_|(Cge(wIAVP{sDT|;K9+SxgB4jZcNI;>exPnqd zal-=cs8q3{qOxdR06|d{eJU!bfV);LE-3FyK!x-AkLSGpN9LSl<}SZ`@9(?c%{R*f zMSg>=##oU^q`?Az-yra-zqhh51K%s@7s^Q_lX1x*;Y1Lsg;Z*#R2GjzM3M@JaGgv_ zBI&L_Sr>8W(S&c8+_vK{jTTreZ&4v}31(wcf2Pe%R8tMioEa5Ou%hW17?OI(4)3)}JHPdu6xV z<>`*^mHc_rOedwJ1|Blnz3(D@RJ{F71XSz8;2YY zSLcV>+}*Z(vN&`8`t6ye&PSH${(8_6o41Z!Zk+s$V`o^2TkjT^q*cPU~&thM7#@Ey*IRg)G^-&Q*|N^K)ZTjZK1&qHRja%=(Y!{PwEY1#Jr(O+CIZ`f2-_-OSKEQ5tyggGO;K$EUD?|j{1X(qx)!h;*Orm?_iSG%1+cL+vLK|pX`#fl3o+xLu#hILN^XA9D-m5qV z`*y>ME)iu0(QR5ryI7w6KKF)p*Xy@lzUMzQ zse@X7Ddf%?YCHF`t4*QzF5B4&w6X`^Y-Egcp{!aMcjClZ@s$&&XN8%EO&dBtjqSm9 zIqJW>eZu0AB~k2~S>3m~+iz-&3TKLsHY3&@OR({G5tq%g)_U)=@ZR>2UwJZKBg!2S zd#vK=usiP~*Tj@ocD6sfc);Dnc=(ISZtzmwB3*JL!g)3#V{bw1srFOYNTz>Rb;6Z) z*7>9?(_dX?7v1wL@t=G@B3zEn+)uQ+v@Y9WzlORkrHQSa+Z15i)R9w3%VW;WEfX;_ z*&|B^*$oc~3G}tJ-g3}>+cp17NqblRT>bLgy`Y-VLC$B5Ya0r6sn5OM@FF}|oR*1; zv8nADk5V&9g^>kO3C+*2tHt@G6ESycW8a-)Mz`g>niHJiV3cyB>U2VIaA(@NvC+?K zLMETiSs3C?F+ZMvk%gx%h$xs>pC578Vds#kE2L|W7+G5{xaf)RJDj8qdvx<-dE@%q zLr4MYq|nlaNHEtNWMF=U3ukaKrJRCFlwzEsldHgdBauA4bSe~!!wE=?$H){s^0Q-= zWJo6Ak;9lmno#9~$IASZ)p&5SCq657C3+i)%2oOhw3)3P_KMij@fjk4y%7 z=u>`jl~DK@UZLq_0q{Z9p(-jup;6^>YF`fx!A}Gry$SuThb9F44N!w{jWR)v;rv8g zK{)q?kYJzvRSD{NLpc(RipS$}V5$L8k$xfl1j4}29(oF5WO9|k3y|HZ-$a1>XWV|)pK3Q411q7B>#M{P^yvwFd1QTju0)B+BwWKSo6eSSa5ofYu`wFV zpkWw{GU#lWNux`c2+Iw@-Q4>?2^1OvRbaRt3V>5&0EdOK+)yz>hcUK01~U*g3+8Z` z?y$R*BX)C>O6eS&(FfuOwG4D68s9f7J(L7M0XI4Wq0?YDoQA^;l;IAeh}0bhSxCef z4QI01ECZAT<4#wqgoN_HA*R=Mb-GU7*Hvw26T|28tA(oE?xSYYh!UeQ8YROrn$j%RtUo2GFe0OL}Fam%c z2=Z0@4$w6~*H|up?Naa#TmQ|nTZ4Z zd`aE{}!OsMrj*X-3?Ch4i nXUA6guX+DV@s2SC6<0`aKaT2p`Ufu^c#;Hsk?(Kb(F^|zZq^d; delta 487 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 K{3U4d>i+@;fF98R delta 504 zcmV4D6Mra?eUUv#!$2IxUsI)`6%i|nIAo|!7DPoHwF*V35Nd^1 z9ZW9$f+h_~ii@M*T5#}VvFhOBtgC~oAP9bdxVktgx=4xtOA0MwJvi>iyL*qjcYshW zGRj3 zJj)EzCf*>P-n0$Q`@|uZmsR3(;xUshNc_lk#p5^5IhO^V88VZJdEyYUm}_FCiCNy% zh^L6ds-{!Ekn&jNyv127mRbFt{Dr}czOu}9n!|`;5lfIDLO~e?6k#Jmt4@lA1noy# z_=jA-L@tF~1u$|fpnn7nvg-%`gWug+*~tknDHsLXUmWLS1PJT`wTk0>A3IL%1n@rt zS9-%=ssb~gq*oeR_z39P1}?4}nz9F6?f`>NhHT2N@Ho diff --git a/lua/img/next_disabled.png b/lua/img/next_disabled.png deleted file mode 100644 index c8ff06b245a277ece8deb1b858b80978e8f61b59..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1539 zcmeAS@N?(olHy`uVBq!ia0vp^d?3uh1|;P@bT2Y6Fs{#Zb`J1#c2)=|%1_J8No8Qr zm{>c})+0JlRkTs}~PKG&or87g$(ZY~A9`x&HaHNFnX22L<(i?*E)$eQ&3Lg~?PcmgImf zvFesW=4om1(z@X$7mo$+;+Pc{$*U^#*yE3N{_*$0E2sSsySjJZnf>>F{#azRZKue( zl6gCCm%q+;wV10JeIbQ|Kk8Ir>wPYDvDl@R{y%mqJ9ElspZi&}&hqK!(zR(?mOPAi zFK_si78UWmC!%t>#+v8v{KPLuh1=bpb=`Ei)SjJedSYU0W?O!h-1Afg+MX+_HcP zu9|NFGlC72V{i<1_VfhO&PAz-C8;S2<(VZJ3hti10SxOyoK68HI14-?iy0X7ltGxW zVyS%@0|V2Y%#etZ2wxwoMOCsh%%@d$ZF8_ zgDsB4WigTjl17j}tb$UD^K(i;;TW2iSz>2oqmNA(T~{PRo1L+N0aO#RD7uaatU8cI z(RKJ2WdcJZ5#%&GL#PeNVo>!q`k?qnN)M341{MXTHajjGeR#^X<1+HT4$2i7o-U3d z8lrR02l5?I;9w588)_+@ewXQ_&s3)?wKJ!+uUq-*_KEPP*KE^PDQ>!~+-_cRAlT;M zn@Ao0g75X!W+(Cv>fGr_EnODiV&y!8y+iepDZfDb>d!3K*V?E2Y*=G5<0;=}qtm$$ Y*&_-K=49IKtOeyDPgg&ebxsLQ06|_AWB>pF diff --git a/lua/img/pause.png b/lua/img/pause.png index 29fa4b904c67ba753273413d039e7f1c0dcdee7c..e7011821ff4f268e706307163f9ea45f61aab537 100644 GIT binary patch literal 4771 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 Rk^yj`0KXvLJ>HRX{sb(WBkuqJ delta 514 zcmV+d0{#8XCFul^BYy#eX+uL$Nkc;*aB^>EX>4Tx04R}tkv&MmKpe$iQ>CI65i5!~ zWT;LSL`5963Pq?8YK2xEOfLO`CJjl7i=*ILaPVWX>fqw6tAnc`2!4RLx;QDiNQwVT z3N2ziIPS;0dyl(!fKV+m&1xG5G~G56v8b3zuZn?JbYTGf=zoPEGs~Ehq&QlRuY36T zdKckYtDu$r-;Lu1(#aSzsS^b{;g~5!zvdndw!-!!KOOPN! zK^X-UVIxATPJfDp1noy#_=jA-L@tF~1u$|fpac!F>j(dX-`!f-$q6qh7zNs29Oq*M z2I1v=Nf-s<}}eE^cwRs04xI0Qy>l)c{K-4Pwlz5RQp-ro-^E^>}EGl-dPj3YsRGRyF<6bR3g~E<$_=B$vqSc>n+a07*qoM6N<$ Ef`vxqHUIzs diff --git a/lua/img/play_small.png b/lua/img/play_small.png index 3fc7032e1cf9f8edb170bcc9964f3fc6ca38a751..ac29aa98720bcb02ca307238bc9747b7f1ab62d5 100644 GIT binary patch literal 4780 zcmeHKc~}$I77wC;D2O7yqTtI5E{{Tz$sQt+H417lK-k2E$z%eNY|ac2P*w#*P^!2+ zL<9>`D;BY!;)YnQsQ4;%eJYBIg5uWNDopywjdvOzxcJch33Ub8qhC z$%1CvSWmR3P$)Km0sbN2mmFJLS%CXm_7BG>6!R%5@<<{CPNEtxy-KY?sYJ2?rJ_c) zib65oc)BI}K=-uKId>c-xiNuOd3O!0_yh~*v~*}$BF5Ai#@BIb{1Ea{FpfbZ&|qS#r>rTYl&05#e1LpYw?4-?=t=|t_5pT-Tv7gt5)CX z)UKNRRxerZGr}!3O?JTS>zbbrJM3?CyD`~n(dNSAV>FVJH@`Ht9ef&mPRA^~dh>wO z!R8$iuPF~zwfeq8ZkM(>g=4A+ivWAOZR$qB|0VY zKUhP@e95EICHrP}ROqpW3or6#zpHCmB4#&8ou13jbj|#}fu9?xILWE{T~;_d%1u+v zSL}Di!hp=Tuc}+%HMNqcI_ITapc11^#4F^Y}*p~S%QpUrs zOp9&GNB5d@au$wV{ohyFo7;}I@UV{5=}(kd?2~y%a%%*=yVAdmD~RIw=iaH!TH?9K zPB#z3PcbOvwTSgjID6|e?{uwwbw=4Y(2U9ZoPG@n-c;H!wdiGanxybdSer7{&3koB z^xe@Tt(@}wU0dOnS*wJip7F+YJMYgaAG;gN-Qpw}*Is5FGby&<>f-R@r;N*ot(xN- zCZaf;SQET{<^(Y{cg>nvVbZ0Ki|eNZetR-xKX=J@r%&zNJ(Yu>4`1kcI0eIBKbeYe z&bU^mx{SOk!Cj#RWex2_)4Gt)BX-v=m0tP!+QPYhZ|bhsRjMvT>niar7lWI{3Ej7G z=Jf3Wm7LO)U6+EqUPf7jXq#92&7R0#(Gjd$y6v%BeG=wXHEY{MrfeK^U23EFHrKki z#(sN?cY#ahA?dJyxY8Ees?U-)?D?uV@y{Gv1PQ0gW<=-U(uij`0 z`^9~;-r$@@vu>9Y3tVpeP(q=Yty25>$pZcS-me$1N=j1K&j|QVHa@3!*Br@X^A_s8 zFUmzxb`Hxfe?Gq4_bZ152~fkMQN`RTu8ehA@l8!<6jz!~M@3jh_}Hz;5Y7;~9uCOs zoR&SIcCqkQVejqU&Re)ydAQU7hQ2wX3KRD&d81ZV2KP2q)AI zcN`~|%lt>$?yQ?saXsM2xr^n-v=?4I;^-LykGs>e5%13Y z?z93*`J%GL32o1jYkPN4nqu3}#l1Pti+Nb|%hJ$%C$rR>Cr&4XhQ7`??;P{utla%{ zQI_17VR>{%s{qZ&j4oS#VMp|Rr>ZgDYgC&Knc3QAwtAv92a}T`x^KNZc4_OKF_gJj za#;PvMPOfZQiJ^~QaVqB=(P-3saK#3qt*cSHwwkm%V>a+c$Am3m`27IDfNh2DKZUFKBVICID9&+;6ik+3g**= z3Y1M33Kcw-l7|SDFna)0pbjTs9fFcj0Gy!)ID8?81t}B=U7I))S$a$hgXvUj;aHSu(8Zb*B;g_-S)iE4WTjxN->d|vabS^5wtRonhoZ~+p8D2+Mr~42siwXa!AM^a98N@) zra-{2Pld$6x>yt}@cxc^FIWG`Y_U};2nB1Nu2djwIv3)r=rF|R1D;hR!3qTn;S5H{ z^(rC>#!#PFP${SebdaeU>OcFqbRBG-6o-;TL2ND^;?UUwIg2ae2}C@h8;d1^AR4p3 zVkWt+-aPDAUDh98c~BiV1|@+Pnu0J-J$MWfiW~}r|1VT$9P%5$PGM{AbA@Z!pWkBReNFoV8y_P&VQ%vVMR7p)z z6LD1XJQtB^`!`_Z9_a5Q|LhVHQ|8z+X2hWm$Mp_710XhgT&3M2bNkvLr0LN#?w%+s c*D_}crQOcX=2rd#ci=|}lmz*I;~SIp2SW7-Y5)KL delta 490 zcmVEX>4Tx04R}tkv&MmKpe$iTcx5E2djuU z1gTCIL`B3&t5Adrp;l+0Yt2!bCVt}afBE>hzE zl0u6Z503ls?%w0>9pJAPnQFF-0;*;i$#_giXI6yZD>~4N9)ApCT4JU?D~bttj<0+8 z_<9%RS>EUV9GyzmWPnc~o?*IS5w8e@rXeeB!1+&?D8Auyu*H; z88%YMIpQ#}kZ)qSiCMu=i6@97imFk*Kkc%@d5g1JEVKGO`3pl?ZF!07GzSpJ0u~`c zfQ&LWP=tjjt$!LRCX%!t^YHgOevw={dNRA);4}N!R=Eld|q)-fKeX;G2 zAt1O5)GD_9eQevc6Cm&mTxkt|sR~Sel3r5?HiQh=sE zmjm9<=$kS?_bt%A>h@OO$LRx*qOKA*z`-FfoTuzHj}mvcHTU-KnR7akN9^Mr;Hm#R5$(dt8QUVt*f^Cf+s(v)OA0|xUK#6@M&#iZ_|NwTVEbD(<9ex zGCSYdk>Qz3e{@b6!W|j=iEawF6z}=DfMepFHG!{o8E(F*^b?>==AY|7=#W{x(Kb-&^bB zT2WILol+^SyK6jnyKnksQ_P2%8E1Y!epEhqYd*4v_xnN@#`!_7^4umwh$ro?iHwT~ zKyEH8E6DsdlT|j9EP39u|5T*%WVH9F(0sr7d#-xTWFyTLi`EwJ_l&SlKP%JSAGNZ} zM%BaA3zwb8*ds$CV;^t4p18uLp{$o09p4|T@OaWxU$;izdj(=zRGEKg%x@K(?%1he z{J69B&I4}bLyc?G9}3d}2=jK3gl^3}BAvGYQ@haUg!U)JZCzw026>PSyE_ z<_*`D&pzv6o$llXU!15P&j&on1{qX$S`zOa&ZE1hB z6fpX~ewNxY>Q>#%eaDpClU9sIvs<9?_Yal&`+r<6V3FjcWQ>)Z2<^S7<>!fkjSgp_ zhShnZ2xr$>S4Q{F^V{M2T>`uKVfPK(VIIttX>sM{rwv=lE{j6)juGb!99cAb6rmZaw;Vl>R z+25(Zi$W0^;7H$6IB?r%JPuo{r%(KjC_IWLi9LpEp9=QnVP{U^A^eroJlnZ8b0Ye%6bb^7KJ=DL0N)B*^8jQmY3?H=j{> zR%E3q{Fq&T-F%5pq|S@X{o&l^$UB4bzwuqB&OU5Mr_a0OP3%3CWDaY(`JuFCecd;- zU}MtMqKi?WtqszE_7yIhB*OJtCZ^IW38qDB0PT%N^Y*bAFg%VRAte!`(TN#Pj~rz{ z8kLw4#*?vS27e+}BS|(A^5hT&o*ah@RSchTPTm#~0MHU723fT6I+MsEX4r5=;GPn* z7?90`j1x1$WucJ2-bg@PCYQ;E11*|Flrhc;@;0i}qA3AEZ4lr|%!nmPgNVg4o6Sr! zhp9Klun?h8$YP@`6or8WY)aCRm<86E+$o4wi~z!f8#M+}qt`(cCZ^OUkYWY{=%J7E z(;8&5Pw+ZZ8w-FBmIX7g5GI?Y)w0@qn8?6H0Ma(0zx6OFz*~Sdg)r$8j5raPNa#rS z_7E!klfNOs7;g(lg|mowLJLeyU{vIbAty*>p`Sb`3Su-`gUt(&{e>l|QGX`ui@8x6 zTR80}0^C30eqsGFcAGJizWuByC>4Mra0EdK9sv_P+zaL^F+MC%f-(^xJQabk2@Y3) z+n`jqC`fPAVj!IwEfzzt47wPbf+AcrK2$1ZpiK5>NoYJqs)2);A;WNJvi!3~q0tg@ z5~Juu_z1#75FsM;;;=cW;Iq**!e|1eNP!}3Cd##GsJw_kG=N!*DpUYqlY?MH{zd{L z^+ttWA1`K5bAl*OTYW>`tyz(1Ou!-uBJ-mL#2#oK1kt`PvgRvBn8pkJ{*HLNz$C?5a8Ut2Q%!4Uv-#~@$DZwFl+ zbbS>AUuE1;T^)3N6$4*o+)-WsH@ciYfASDIunjVU51JF1wWZ)=sEaaqLIACWI&w?% zvw+0WAc-*1XiRtNw6kpYNd&^Kq*NBz^?}1c2hm7!)1-+&)SnCtC;jzWYUfO|?dJ#; zWY&B;}xSW6s#7y(4Zdn^IBRW26j)6UYU`JEnJeYz?bzsyUMMm zlT&$P{8(Pr2rbRGua39UH*>lD`LN-=-^`s;RI}@R)FFMoy17e~nM>#8zF+}ZnlvyZ KV2@vP+J6D1Wgi0o delta 509 zcmV1C6Mra?eUUv#!$2IxUsI)`6%i|nIAo|!7DPoHwF*V35Nd^1 z9ZW9$f+h_~ii@M*T5#}VvFhOBtgC~oAP9bdxVktgx=4xtOA0MwJvi>iyL*qjcYshW zGRj3 zJj)EzCf*>P-n0$Q`@|uZmsR3(;xUshNc_lk#p5^5IhO^V88VZJdEyYUm}_FCiCNy% zh^L6ds-{!Ekn&jNyv127mRbFt{Dr}czOu}9n!|`;5lfIDLO~e?6k#Jmt4@lA1noy# z_=jA-L@tF~1u$|fpnn7nvg-%`gWug+*~tknDHsLXUmWLS1PJT`wTk0>A3IL%1n@rt zS9-%=ssb~gq*oeR_z39P1}?4}nz9F6?f`>NhHT2NjQgrfz3b?PPAPqgf^oInaJ#dcAx za^eOMX1L`z0z=G1#jy;3`)b)WJ9piE7uCWW<2(%)zJuPI00000NkvXXu0mjfAn@sw diff --git a/lua/img/prev_disabled.png b/lua/img/prev_disabled.png deleted file mode 100644 index accebe233d845830489d26a6da799c363f484c9b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1533 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 delta 1353 zcmV-P1-AO1C$c4wBmpInB_DrVlI$i7hTmC5mVhLL#Bwm0s_Y=k&kv^E(|s;=a*~HU zG*okuK?r@4JrmBq{v6>C6po2QQuADLjwq?5!W9FLS2u*JSKoui>KB#rek5~Dz6|%o8+7<|1C&#`wY(;;<+oE*M+qNhd zx0kE+wogu53Z)T!TORG;}mBOOajMNddz(NwirjzjCb|CMyhXYPasU__#-J z;bC>l<&r-Y<-32u`jCHi5=gIulB9r@w_)c|xfk`0-<3Wi!RT^}zzEOjTiBC54%#CHq94rbq znatCyfd-ot>EZ~0hFB4Z`L>1Dxn&*K$PxFQu#ySN5IxTtzD|GsP5nmE7&B*xqTjxP zT|Bc8W=wLk#VP=yv3&}nVY!%YM}I97tbm{{H#;s^<8i4tl%BCAlk?1eMNRR_ZnC}= zz(qJ)VGIcf#7v@;DH^j8!H*6;6_z8n_@K~;RJp@SP9hm_kOep!dyLjvR=&h|djfsa`o)y?!{~2 zA_1>yEy%~>rIcE6XocpA-4!cpJ@}9#9eU)$4nN9K8^WihrY$#X-b$;TyM&HJ?zvm{ zUV1(0L@1r<)H9!U`dQ985Nbn48anc@;iHUtQ`=Nusl9)b`#Ck*)OdlCfW~fW5Su+z z(0rZfWCmiK2*hm?KtS_k7M)V!N$x<_1blap70R$uC!9``7zn15SO?wgp5(sEEhPUf zZv0LzOmzPRxiHbak^7e0cc}IGF=&g}%EGXm8U^Wx1*cc`IzOZTxDUU(@uTRY=%eVP z=%eVP=%W!!Nplu2UkH5`~h)xa8h)U67Ne2En>Xz zcpvB8b9nDQK&Y3QW_64Mnr@q^L|n{dSH;jP1cVVl9|mM*8FP}9gl~P_QzzA3jA!}x z{aHPKYR+OnKqQ`JhG`RT5KnK~2Iqa^2rJ4e@j3CBNf#u3C`-N zgjg)JvC_t@Xlle$#8Fk#DPPEVta9Gstd*;*c~Abra86%Y<~prmB(R7jND!f*iW17O z5u;rv#X^eC<39c&*DsMvAy)~E91EyGgY5c$!T;d*Y_0s{gqIYK1D!99^DzR1c7aCC zalVfor*Q%VpMfjA<*(F%nNQMdEiHNk^lk$e*DX!i11@)f!6!pDWmgK)67qTA{fxdT z3-sRt-D}?5n&&uu0Maz8VUF~!Gx2HM3ANY%McBA91-2ea$9BEXu z=LCoXBxN``WiT;0Gc92>V=yf=G-hQjIWROaEiq+cV=*>2VPrTmVv~~xrw%YTH!w6d zIX5ygH8C(Tvq%VC0keq;Ug0amue00000 LNkvXXu0mjf??Qbf diff --git a/lua/img/repeat_disabled.png b/lua/img/repeat_disabled.png deleted file mode 100644 index 20b6ab59f2f734c9cf4631185e4efbe7ff8972cf..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 7287 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 diff --git a/lua/img/shuffle.png b/lua/img/shuffle.png index b54e359d2faaf46947f4d004a5e8dcd4be981984..4a65635b5938ecadee4f4d35a08e924ad8a6d3e8 100644 GIT binary patch delta 1452 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 Hr?CG6`9FZO diff --git a/lua/main.lua b/lua/main.lua index 1a605dab..dc73c964 100644 --- a/lua/main.lua +++ b/lua/main.lua @@ -6,6 +6,9 @@ local time = require("time") local lock_time = time.ticks() +local theme_dark = require("theme_dark") +theme.set(theme_dark) + -- Set up property bindings that are used across every screen. GLOBAL_BINDINGS = { -- Show an alert with the current volume whenever the volume changes @@ -20,11 +23,10 @@ GLOBAL_BINDINGS = { align_items = "center", align_content = "center", }, - bg_opa = lvgl.OPA(100), - bg_color = "#fafafa", radius = 8, pad_all = 2, }) + theme.set_style(container, "pop_up") container:Label { text = string.format("Volume %i%%", pct), text_font = font.fusion_10 @@ -52,9 +54,6 @@ GLOBAL_BINDINGS = { end), } -local theme_dark = require("theme_dark") -theme.set(theme_dark) - local backstack = require("backstack") local main_menu = require("main_menu") diff --git a/lua/playing.lua b/lua/playing.lua index 947bdec9..a1ba2cc1 100644 --- a/lua/playing.lua +++ b/lua/playing.lua @@ -5,22 +5,22 @@ local font = require("font") local playback = require("playback") local queue = require("queue") local screen = require("screen") +local theme = require("theme") local img = { play = "//lua/img/play.png", pause = "//lua/img/pause.png", next = "//lua/img/next.png", - next_disabled = "//lua/img/next_disabled.png", prev = "//lua/img/prev.png", - prev_disabled = "//lua/img/prev_disabled.png", shuffle = "//lua/img/shuffle.png", - shuffle_disabled = "//lua/img/shuffle_disabled.png", - repeat_enabled = "//lua/img/repeat.png", - repeat_disabled = "//lua/img/repeat_disabled.png", + repeat_src = "//lua/img/repeat.png", -- repeat is a reserved word } local is_now_playing_shown = false +local icon_enabled_class = "icon_enabled" +local icon_disabled_class = "icon_disabled" + return screen:new { createUi = function(self) self.root = lvgl.Object(nil, { @@ -146,11 +146,14 @@ return screen:new { repeat_btn:onClicked(function() queue.repeat_track:set(not queue.repeat_track:get()) end) - local repeat_img = repeat_btn:Image { src = img.repeat_enabled } + local repeat_img = repeat_btn:Image { src = img.repeat_src } + theme.set_style(repeat_img, icon_enabled_class) + local prev_btn = controls:Button {} prev_btn:onClicked(queue.previous) - local prev_img = prev_btn:Image { src = img.prev_disabled } + local prev_img = prev_btn:Image { src = img.prev } + theme.set_style(prev_img, icon_disabled_class) local play_pause_btn = controls:Button {} play_pause_btn:onClicked(function() @@ -158,16 +161,19 @@ return screen:new { end) play_pause_btn:focus() local play_pause_img = play_pause_btn:Image { src = img.pause } + theme.set_style(play_pause_img, icon_enabled_class) local next_btn = controls:Button {} next_btn:onClicked(queue.next) - local next_img = next_btn:Image { src = img.next_disabled } + local next_img = next_btn:Image { src = img.next } + theme.set_style(next_img, icon_disabled_class) local shuffle_btn = controls:Button {} shuffle_btn:onClicked(function() queue.random:set(not queue.random:get()) end) local shuffle_img = shuffle_btn:Image { src = img.shuffle } + theme.set_style(shuffle_img, icon_enabled_class) controls:Object({ flex_grow = 1, h = 1 }) -- spacer @@ -208,26 +214,19 @@ return screen:new { if not pos then return end playlist_pos:set { text = tostring(pos) } - next_img:set_src( - pos < queue.size:get() and img.next or img.next_disabled + theme.set_style( + next_img, pos < queue.size:get() and icon_enabled_class or icon_disabled_class ) - prev_img:set_src( - pos > 1 and img.prev or img.prev_disabled + + theme.set_style( + prev_img, pos > 1 and icon_enabled_class or icon_disabled_class ) end), queue.random:bind(function(shuffling) - if shuffling then - shuffle_img:set_src(img.shuffle) - else - shuffle_img:set_src(img.shuffle_disabled) - end + theme.set_style(shuffle_img, shuffling and icon_enabled_class or icon_disabled_class) end), queue.repeat_track:bind(function(en) - if en then - repeat_img:set_src(img.repeat_enabled) - else - repeat_img:set_src(img.repeat_disabled) - end + theme.set_style(repeat_img, en and icon_enabled_class or icon_disabled_class) end), queue.size:bind(function(num) if not num then return end diff --git a/lua/theme_dark.lua b/lua/theme_dark.lua index 8c1da65c..be5feeaa 100644 --- a/lua/theme_dark.lua +++ b/lua/theme_dark.lua @@ -5,6 +5,8 @@ local background_color = "#5a5474" local background_muted = "#464258" local text_color = "#eeeeee" local highlight_color = "#9773d3" +local icon_enabled_color = "#eeeeee" +local icon_disabled_color = "#6d6d69" local theme_dark = { base = { @@ -28,6 +30,12 @@ local theme_dark = { bg_color = background_muted, }}, }, + pop_up = { + {lvgl.PART.MAIN, lvgl.Style { + bg_opa = lvgl.OPA(100), + bg_color = background_muted, + }}, + }, button = { {lvgl.PART.MAIN, lvgl.Style { pad_left = 2, @@ -35,13 +43,14 @@ local theme_dark = { pad_top = 1, pad_bottom = 1, bg_color = background_color, - img_opa = 180, + img_recolor_opa = 180, + img_recolor = highlight_color, radius = 5, }}, {lvgl.PART.MAIN | lvgl.STATE.FOCUSED, lvgl.Style { bg_opa = lvgl.OPA(100), bg_color = highlight_color, - img_opa = 255, + img_recolor_opa = 0, }}, }, listbutton = { @@ -149,6 +158,19 @@ local theme_dark = { text_color = highlight_color, }}, }, + icon_disabled = { + {lvgl.PART.MAIN, lvgl.Style { + img_recolor_opa = 180, + img_recolor = icon_disabled_color, + }}, + }, + icon_enabled = { + {lvgl.PART.MAIN, lvgl.Style { + img_recolor_opa = 180, + img_recolor = icon_enabled_color, + }}, + }, + } return theme_dark diff --git a/lua/theme_light.lua b/lua/theme_light.lua index 82abd368..e0a4468f 100644 --- a/lua/theme_light.lua +++ b/lua/theme_light.lua @@ -4,7 +4,9 @@ local font = require("font") local background_color = "#ffffff" local background_muted = "#fafafa" local text_color = "#000000" -local highlight_color = "#CE93D8" +local highlight_color = "#ce93d8" +local icon_enabled_color = "#2c2c2c" +local icon_disabled_color = "#999999" local theme_light = { base = { @@ -26,6 +28,12 @@ local theme_light = { bg_color = background_muted, }}, }, + pop_up = { + {lvgl.PART.MAIN, lvgl.Style { + bg_opa = lvgl.OPA(100), + bg_color = background_muted, + }}, + }, button = { {lvgl.PART.MAIN, lvgl.Style { pad_left = 2, @@ -33,11 +41,14 @@ local theme_light = { pad_top = 1, pad_bottom = 1, bg_color = background_color, + img_recolor_opa = 180, + img_recolor = highlight_color, radius = 5, }}, {lvgl.PART.MAIN | lvgl.STATE.FOCUSED, lvgl.Style { bg_opa = lvgl.OPA(100), bg_color = highlight_color, + img_recolor_opa = 0, }}, }, listbutton = { @@ -65,7 +76,7 @@ local theme_light = { {lvgl.PART.KNOB, lvgl.Style { radius = 32767, -- LV_RADIUS_CIRCLE = 0x7fff pad_all = 2, - bg_color = background_color, + bg_color = background_muted, shadow_width = 5, shadow_opa = lvgl.OPA(100) }}, @@ -85,12 +96,14 @@ local theme_light = { width = 28, height = 8, radius = 32767, -- LV_RADIUS_CIRCLE = 0x7fff + bg_color = background_muted, + border_color = highlight_color, }}, {lvgl.PART.INDICATOR, lvgl.Style { radius = 32767, -- LV_RADIUS_CIRCLE = 0x7fff bg_color = background_muted, }}, - {lvgl.PART.MAIN | lvgl.STATE.CHECKED, lvgl.Style { + {lvgl.PART.INDICATOR | lvgl.STATE.CHECKED, lvgl.Style { bg_color = highlight_color, }}, {lvgl.PART.KNOB, lvgl.Style { @@ -129,6 +142,12 @@ local theme_light = { bg_color = highlight_color, }}, }, + database_indicator = { + {lvgl.PART.MAIN, lvgl.Style { + img_recolor_opa = 180, + img_recolor = highlight_color, + }}, + }, settings_title = { {lvgl.PART.MAIN, lvgl.Style { pad_top = 2, @@ -137,6 +156,19 @@ local theme_light = { text_color = highlight_color, }}, }, + icon_disabled = { + {lvgl.PART.MAIN, lvgl.Style { + img_recolor_opa = 180, + img_recolor = icon_disabled_color, + }}, + }, + icon_enabled = { + {lvgl.PART.MAIN, lvgl.Style { + img_recolor_opa = 180, + img_recolor = icon_enabled_color, + }}, + }, + } return theme_light From bf58cb7acf402420158f3ac2530f62ddc3057914 Mon Sep 17 00:00:00 2001 From: ailurux Date: Wed, 27 Mar 2024 16:11:36 +1100 Subject: [PATCH 13/18] Minor fixes --- lua/browser.lua | 2 +- src/lua/lua_theme.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lua/browser.lua b/lua/browser.lua index fa412021..924381ea 100644 --- a/lua/browser.lua +++ b/lua/browser.lua @@ -43,7 +43,7 @@ return screen:new { pad_right = 4, pad_bottom = 2, bg_opa = lvgl.OPA(100), - scrollbar_mode = lvgl.SCROLLBAR_MODE.OFF, + scrollbar_mode = lvgl.SCROLLBAR_MODE.OFF, } theme.set_style(header, "header") diff --git a/src/lua/lua_theme.cpp b/src/lua/lua_theme.cpp index 2fcd71e5..32d6f660 100644 --- a/src/lua/lua_theme.cpp +++ b/src/lua/lua_theme.cpp @@ -59,7 +59,7 @@ static auto set_theme(lua_State* L) -> int { // Style lv_style_t* style = luavgl_to_style(L, -1); if (style == NULL) { - ESP_LOGI("DANIEL", "Style was null or malformed??"); + ESP_LOGI(kTag, "Style was null or malformed"); return 0; } else { ui::themes::Theme::instance()->AddStyle(class_name, selector, style); From 10441162c4323d6e41f54425a8d7641fda18f711 Mon Sep 17 00:00:00 2001 From: ailurux Date: Thu, 28 Mar 2024 15:36:16 +1100 Subject: [PATCH 14/18] Fix for adding multiple styles with the same key --- src/ui/themes.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/ui/themes.cpp b/src/ui/themes.cpp index 88f45b1b..b13f226a 100644 --- a/src/ui/themes.cpp +++ b/src/ui/themes.cpp @@ -70,6 +70,7 @@ void Theme::Callback(lv_obj_t* obj) { void Theme::ApplyStyle(lv_obj_t* obj, std::string style_key) { if (auto search = style_map.find(style_key); search != style_map.end()) { for (const auto& pair : search->second) { + lv_obj_remove_style(obj, pair.second, pair.first); lv_obj_add_style(obj, pair.second, pair.first); } } From 78c708e9390047ef24ccd8fbe0cd2d38ff758654 Mon Sep 17 00:00:00 2001 From: ailurux Date: Thu, 28 Mar 2024 15:36:27 +1100 Subject: [PATCH 15/18] Fix log message --- src/lua/lua_theme.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lua/lua_theme.cpp b/src/lua/lua_theme.cpp index 32d6f660..fe69aa6a 100644 --- a/src/lua/lua_theme.cpp +++ b/src/lua/lua_theme.cpp @@ -59,7 +59,7 @@ static auto set_theme(lua_State* L) -> int { // Style lv_style_t* style = luavgl_to_style(L, -1); if (style == NULL) { - ESP_LOGI(kTag, "Style was null or malformed"); + ESP_LOGI("lua_theme", "Style was null or malformed"); return 0; } else { ui::themes::Theme::instance()->AddStyle(class_name, selector, style); From f1c8866b815a92aeda3133fd27051ce7c873cc57 Mon Sep 17 00:00:00 2001 From: ailurux Date: Thu, 28 Mar 2024 16:03:37 +1100 Subject: [PATCH 16/18] Check type is actually a table --- src/lua/lua_theme.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/lua/lua_theme.cpp b/src/lua/lua_theme.cpp index fe69aa6a..4eb46499 100644 --- a/src/lua/lua_theme.cpp +++ b/src/lua/lua_theme.cpp @@ -36,6 +36,7 @@ static auto set_style(lua_State* L) -> int { static auto set_theme(lua_State* L) -> int { std::string class_name; + luaL_checktype(L, -1, LUA_TTABLE); lua_pushnil(L); /* first key */ while (lua_next(L, -2) != 0) { /* uses 'key' (at index -2) and 'value' (at index -1) */ From 79b6c3b393a1ff351b437ef59b2a4e472da0c38c Mon Sep 17 00:00:00 2001 From: ailurux Date: Thu, 28 Mar 2024 16:29:23 +1100 Subject: [PATCH 17/18] Use luaL_checkstring in set_style --- src/lua/lua_theme.cpp | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/src/lua/lua_theme.cpp b/src/lua/lua_theme.cpp index 4eb46499..d7f099cc 100644 --- a/src/lua/lua_theme.cpp +++ b/src/lua/lua_theme.cpp @@ -24,12 +24,10 @@ namespace lua { static auto set_style(lua_State* L) -> int { // Get the object and class name from the stack - if (lua_type(L, -1) == LUA_TSTRING) { - std::string class_name = lua_tostring(L, -1); - lv_obj_t* obj = luavgl_to_obj(L, -2); - if (obj != NULL) { - ui::themes::Theme::instance()->ApplyStyle(obj, class_name); - } + std::string class_name = luaL_checkstring(L, -1); + lv_obj_t* obj = luavgl_to_obj(L, -2); + if (obj != NULL) { + ui::themes::Theme::instance()->ApplyStyle(obj, class_name); } return 0; } From 7c5dae84175aa750ca1b8beeb066f5607ca73181 Mon Sep 17 00:00:00 2001 From: ailurux Date: Thu, 28 Mar 2024 16:34:04 +1100 Subject: [PATCH 18/18] Remove unused variable --- src/lua/lua_theme.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/lua/lua_theme.cpp b/src/lua/lua_theme.cpp index d7f099cc..72434d97 100644 --- a/src/lua/lua_theme.cpp +++ b/src/lua/lua_theme.cpp @@ -47,7 +47,6 @@ static auto set_theme(lua_State* L) -> int { while (lua_next(L, -2) != 0) { // Nesting the second int selector = -1; - lv_style_t* style = NULL; lua_pushnil(L); // First key while (lua_next(L, -2) != 0) { int idx = lua_tointeger(L, -2);