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 <cooljqln@noreply.codeberg.org> Co-authored-by: ailurux <ailuruxx@gmail.com> Co-committed-by: ailurux <ailuruxx@gmail.com>
|
Before Width: | Height: | Size: 4.7 KiB After Width: | Height: | Size: 7.6 KiB |
|
Before Width: | Height: | Size: 4.7 KiB After Width: | Height: | Size: 5.8 KiB |
|
Before Width: | Height: | Size: 4.7 KiB After Width: | Height: | Size: 6.7 KiB |
|
Before Width: | Height: | Size: 4.7 KiB After Width: | Height: | Size: 7.7 KiB |
@@ -14,9 +14,15 @@ local backstack = require("backstack")
|
||||
local main_menu = require("main_menu")
|
||||
|
||||
local function init_ui()
|
||||
-- Load the theme within init_ui because the theme needs fonts to be ready.
|
||||
local theme_dark = require("theme_dark")
|
||||
theme.set(theme_dark)
|
||||
-- Theme is set within init_ui because the theme needs fonts to be ready.
|
||||
-- Set the theme to the saved theme if available
|
||||
local saved_theme = theme.theme_filename()
|
||||
local res = theme.load_theme(saved_theme)
|
||||
if not res then
|
||||
-- Set a default theme (in case the saved theme does not load)
|
||||
local default_theme = require("theme_light")
|
||||
theme.set(default_theme)
|
||||
end
|
||||
|
||||
local lock_time = time.ticks()
|
||||
|
||||
|
||||
@@ -33,9 +33,9 @@ return widgets.MenuScreen:new {
|
||||
margin_all = 2,
|
||||
pad_bottom = 2,
|
||||
pad_column = 4,
|
||||
border_color = "#FFFFFF",
|
||||
border_width = 1,
|
||||
})
|
||||
theme.set_style(now_playing, "now_playing");
|
||||
|
||||
local play_pause = now_playing:Image { src = img.play_small }
|
||||
local title = now_playing:Label {
|
||||
@@ -140,6 +140,7 @@ return widgets.MenuScreen:new {
|
||||
w = lvgl.PCT(100),
|
||||
h = lvgl.SIZE_CONTENT,
|
||||
pad_top = 4,
|
||||
pad_bottom = 2,
|
||||
})
|
||||
|
||||
-- local queue_btn = bottom_bar:Button {}
|
||||
@@ -154,13 +155,13 @@ return widgets.MenuScreen:new {
|
||||
})
|
||||
end)
|
||||
files_btn:Image { src = img.files }
|
||||
theme.set_style(files_btn, "icon_enabled")
|
||||
theme.set_style(files_btn, "menu_icon")
|
||||
|
||||
local settings_btn = bottom_bar:Button {}
|
||||
settings_btn:onClicked(function()
|
||||
backstack.push(require("settings"):new())
|
||||
end)
|
||||
settings_btn:Image { src = img.settings }
|
||||
theme.set_style(settings_btn, "icon_enabled")
|
||||
theme.set_style(settings_btn, "menu_icon")
|
||||
end,
|
||||
}
|
||||
|
||||
@@ -74,7 +74,7 @@ return screen:new {
|
||||
align_items = "center",
|
||||
align_content = "center",
|
||||
},
|
||||
w = lvgl.PCT(100),
|
||||
w = lvgl.PCT(95),
|
||||
h = lvgl.SIZE_CONTENT,
|
||||
}
|
||||
|
||||
@@ -114,11 +114,12 @@ return screen:new {
|
||||
playlist:Object({ w = 3, h = 1 }) -- spacer
|
||||
|
||||
local scrubber = self.root:Slider {
|
||||
w = lvgl.PCT(100),
|
||||
w = lvgl.PCT(90),
|
||||
h = 5,
|
||||
range = { min = 0, max = 100 },
|
||||
value = 0,
|
||||
}
|
||||
theme.set_style(scrubber, "scrubber");
|
||||
local scrubber_desc = widgets.Description(scrubber, "Scrubber")
|
||||
|
||||
scrubber:onevent(lvgl.EVENT.RELEASED, function()
|
||||
@@ -138,6 +139,8 @@ return screen:new {
|
||||
end
|
||||
end)
|
||||
|
||||
self.root:Object({ w = 1, h = 1 }) -- spacer
|
||||
|
||||
local controls = self.root:Object {
|
||||
flex = {
|
||||
flex_direction = "row",
|
||||
@@ -158,7 +161,7 @@ return screen:new {
|
||||
queue.repeat_track:set(not queue.repeat_track:get())
|
||||
end)
|
||||
local repeat_img = repeat_btn:Image { src = img.repeat_src }
|
||||
theme.set_style(repeat_img, icon_enabled_class)
|
||||
theme.set_style(repeat_btn, icon_enabled_class)
|
||||
local repeat_desc = widgets.Description(repeat_btn)
|
||||
|
||||
|
||||
@@ -171,7 +174,7 @@ return screen:new {
|
||||
end
|
||||
end)
|
||||
local prev_img = prev_btn:Image { src = img.prev }
|
||||
theme.set_style(prev_img, icon_enabled_class)
|
||||
theme.set_style(prev_btn, icon_enabled_class)
|
||||
local prev_desc = widgets.Description(prev_btn, "Previous track")
|
||||
|
||||
local play_pause_btn = controls:Button {}
|
||||
@@ -180,13 +183,13 @@ 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)
|
||||
theme.set_style(play_pause_btn, icon_enabled_class)
|
||||
local play_pause_desc = widgets.Description(play_pause_btn, "Play")
|
||||
|
||||
local next_btn = controls:Button {}
|
||||
next_btn:onClicked(queue.next)
|
||||
local next_img = next_btn:Image { src = img.next }
|
||||
theme.set_style(next_img, icon_disabled_class)
|
||||
theme.set_style(next_btn, icon_disabled_class)
|
||||
local next_desc = widgets.Description(next_btn, "Next track")
|
||||
|
||||
local shuffle_btn = controls:Button {}
|
||||
@@ -194,7 +197,7 @@ return screen:new {
|
||||
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)
|
||||
theme.set_style(shuffle_btn, icon_enabled_class)
|
||||
local shuffle_desc = widgets.Description(shuffle_btn)
|
||||
|
||||
controls:Object({ flex_grow = 1, h = 1 }) -- spacer
|
||||
@@ -238,11 +241,11 @@ return screen:new {
|
||||
|
||||
local can_next = pos < queue.size:get() or queue.random:get()
|
||||
theme.set_style(
|
||||
next_img, can_next and icon_enabled_class or icon_disabled_class
|
||||
next_btn, can_next and icon_enabled_class or icon_disabled_class
|
||||
)
|
||||
end),
|
||||
queue.random:bind(function(shuffling)
|
||||
theme.set_style(shuffle_img, shuffling and icon_enabled_class or icon_disabled_class)
|
||||
theme.set_style(shuffle_btn, shuffling and icon_enabled_class or icon_disabled_class)
|
||||
if shuffling then
|
||||
shuffle_desc:set { text = "Disable shuffle" }
|
||||
else
|
||||
@@ -250,7 +253,7 @@ return screen:new {
|
||||
end
|
||||
end),
|
||||
queue.repeat_track:bind(function(en)
|
||||
theme.set_style(repeat_img, en and icon_enabled_class or icon_disabled_class)
|
||||
theme.set_style(repeat_btn, en and icon_enabled_class or icon_disabled_class)
|
||||
if en then
|
||||
repeat_desc:set { text = "Disable track repeat" }
|
||||
else
|
||||
|
||||
@@ -7,9 +7,11 @@ local display = require("display")
|
||||
local controls = require("controls")
|
||||
local bluetooth = require("bluetooth")
|
||||
local theme = require("theme")
|
||||
local filesystem = require("filesystem")
|
||||
local database = require("database")
|
||||
local usb = require("usb")
|
||||
local font = require("font")
|
||||
local main_menu = require("main_menu")
|
||||
|
||||
local SettingsScreen = widgets.MenuScreen:new {
|
||||
show_back = true,
|
||||
@@ -301,6 +303,64 @@ local DisplaySettings = SettingsScreen:new {
|
||||
end
|
||||
}
|
||||
|
||||
local ThemeSettings = SettingsScreen:new {
|
||||
title = "Theme",
|
||||
createUi = function(self)
|
||||
SettingsScreen.createUi(self)
|
||||
|
||||
theme.set_style(self.content:Label {
|
||||
text = "Theme",
|
||||
}, "settings_title")
|
||||
|
||||
local themeOptions = {}
|
||||
themeOptions["Dark"] = "/lua/theme_dark.lua"
|
||||
themeOptions["Light"] = "/lua/theme_light.lua"
|
||||
|
||||
-- Parse theme directory for more themes
|
||||
local theme_dir_iter = filesystem.iterator("/.themes/")
|
||||
for dir in theme_dir_iter do
|
||||
local theme_name = tostring(dir):match("(.+).lua$")
|
||||
themeOptions[theme_name] = "/sdcard/.themes/" .. theme_name .. ".lua"
|
||||
end
|
||||
|
||||
local saved_theme = theme.theme_filename();
|
||||
local saved_theme_name = saved_theme:match(".+/(.*).lua$")
|
||||
|
||||
local options = ""
|
||||
local idx = 0
|
||||
local selected_idx = -1
|
||||
for i, v in pairs(themeOptions) do
|
||||
if (saved_theme == v) then
|
||||
selected_idx = idx
|
||||
end
|
||||
if idx > 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")
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,10 +77,10 @@ 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,
|
||||
@@ -90,28 +92,56 @@ local theme_light = {
|
||||
bg_color = highlight_color,
|
||||
}},
|
||||
},
|
||||
switch = {
|
||||
scrubber = {
|
||||
{lvgl.PART.MAIN, lvgl.Style {
|
||||
bg_opa = lvgl.OPA(100),
|
||||
width = 28,
|
||||
height = 8,
|
||||
radius = 32767, -- LV_RADIUS_CIRCLE = 0x7fff
|
||||
bg_color = background_muted,
|
||||
border_color = highlight_color,
|
||||
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_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,
|
||||
}},
|
||||
},
|
||||
switch = {
|
||||
{lvgl.PART.MAIN, lvgl.Style {
|
||||
bg_opa = lvgl.OPA(100),
|
||||
width = 18,
|
||||
height = 10,
|
||||
radius = 32767, -- LV_RADIUS_CIRCLE = 0x7fff
|
||||
bg_color = background_muted,
|
||||
border_color = text_color,
|
||||
border_width = 1,
|
||||
}},
|
||||
{lvgl.PART.INDICATOR, lvgl.Style {
|
||||
radius = 32767, -- LV_RADIUS_CIRCLE = 0x7fff
|
||||
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
|
||||
bg_opa = lvgl.OPA(100),
|
||||
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
|
||||
|
||||