summaryrefslogtreecommitdiff
path: root/lua
diff options
context:
space:
mode:
authorrdsh <rdsh@noreply.codeberg.org>2025-02-05 21:57:45 +0000
committerrdsh <rdsh@noreply.codeberg.org>2025-02-05 21:57:45 +0000
commit2ec84a1393331f57f8402bce66d337c0dd255f64 (patch)
tree30f271bf10988a9717abdf266d135e3ec0ae2d31 /lua
parentddcd06dbca61fc55a7c2cd68f82f8cfe7b4c5cbf (diff)
parent9ecb79a264daa7896ce7d5a65592c05631213d5a (diff)
downloadtangara-fw-2ec84a1393331f57f8402bce66d337c0dd255f64.tar.gz
Merge pull request 'main' (#1) from cool-tech-zone/tangara-fw:main into main
Reviewed-on: https://codeberg.org/rdsh/tangara-fw/pulls/1
Diffstat (limited to 'lua')
-rw-r--r--lua/browser.lua1
-rw-r--r--lua/file_browser.lua6
-rw-r--r--lua/fonts/fusion12bin615260 -> 655932 bytes
-rw-r--r--lua/images.lua2
-rw-r--r--lua/img/file_icons/directory.pngbin0 -> 7848 bytes
-rw-r--r--lua/img/file_icons/playlist.pngbin0 -> 6832 bytes
-rw-r--r--lua/main_menu.lua31
-rw-r--r--lua/playing.lua2
-rw-r--r--lua/playlist_browser.lua94
-rw-r--r--lua/playlist_iterator.lua35
-rw-r--r--lua/settings.lua92
-rw-r--r--lua/theme_hicon.lua2
-rw-r--r--lua/theme_light.lua5
-rw-r--r--lua/widgets.lua10
14 files changed, 237 insertions, 43 deletions
diff --git a/lua/browser.lua b/lua/browser.lua
index 2a024fc5..264db0c5 100644
--- a/lua/browser.lua
+++ b/lua/browser.lua
@@ -118,6 +118,7 @@ return screen:new {
end
widgets.InfiniteList(self.root, self.iterator, {
+ focus_first_item = true,
get_icon = get_icon_func,
callback = function(item)
return function()
diff --git a/lua/file_browser.lua b/lua/file_browser.lua
index a85c2ba2..6289828f 100644
--- a/lua/file_browser.lua
+++ b/lua/file_browser.lua
@@ -8,11 +8,11 @@ local backstack = require("backstack")
local font = require("font")
local queue = require("queue")
local playing = require("playing")
-local styles = require("styles")
local playback = require("playback")
local theme = require("theme")
local screen = require("screen")
local filesystem = require("filesystem")
+local playlist_iterator = require("playlist_iterator")
return screen:new {
create_ui = function(self)
@@ -59,6 +59,7 @@ return screen:new {
end
widgets.InfiniteList(self.root, self.iterator, {
+ focus_first_item = true,
callback = function(item)
return function()
local is_dir = item:is_directory()
@@ -69,8 +70,7 @@ return screen:new {
breadcrumb = item:filepath()
})
elseif
- item:filepath():match("%.playlist$") or
- item:filepath():match("%.m3u8?$") then
+ playlist_iterator:is_playlist(item) then
queue.open_playlist(item:filepath())
playback.playing:set(true)
backstack.push(playing:new())
diff --git a/lua/fonts/fusion12 b/lua/fonts/fusion12
index 3745c17d..c5380a47 100644
--- a/lua/fonts/fusion12
+++ b/lua/fonts/fusion12
Binary files differ
diff --git a/lua/images.lua b/lua/images.lua
index b10d0f74..69bd8e4b 100644
--- a/lua/images.lua
+++ b/lua/images.lua
@@ -29,6 +29,8 @@ local img = {
unlistened = lvgl.ImgData("//lua/img/unlistened.png"),
info = lvgl.ImgData("//lua/img/info.png"),
menu = lvgl.ImgData("//lua/img/menu.png"),
+ file_directory = lvgl.ImgData("//lua/img/file_icons/directory.png"),
+ file_playlist = lvgl.ImgData("//lua/img/file_icons/playlist.png"),
}
return img
diff --git a/lua/img/file_icons/directory.png b/lua/img/file_icons/directory.png
new file mode 100644
index 00000000..32624415
--- /dev/null
+++ b/lua/img/file_icons/directory.png
Binary files differ
diff --git a/lua/img/file_icons/playlist.png b/lua/img/file_icons/playlist.png
new file mode 100644
index 00000000..4787d59c
--- /dev/null
+++ b/lua/img/file_icons/playlist.png
Binary files differ
diff --git a/lua/main_menu.lua b/lua/main_menu.lua
index cc7874d7..8754df85 100644
--- a/lua/main_menu.lua
+++ b/lua/main_menu.lua
@@ -11,7 +11,6 @@ local browser = require("browser")
local playing = require("playing")
local styles = require("styles")
local filesystem = require("filesystem")
-local screen = require("screen")
local font = require("font")
local theme = require("theme")
local img = require("images")
@@ -56,7 +55,6 @@ return widgets.MenuScreen:new {
now_playing:onClicked(function() backstack.push(playing:new()) end)
- local has_focus = false
local track_duration = nil
self.bindings = self.bindings + {
@@ -72,7 +70,6 @@ return widgets.MenuScreen:new {
now_playing:add_flag(lvgl.FLAG.HIDDEN)
return
else
- has_focus = true
now_playing:clear_flag(lvgl.FLAG.HIDDEN)
end
title:set { text = track.title }
@@ -131,6 +128,15 @@ return widgets.MenuScreen:new {
})
end
+ local playlist_btn = indexes_list:add_btn(nil, "Playlists")
+ playlist_btn:onClicked(function()
+ backstack.push(require("playlist_browser"):new {
+ title = "Playlists",
+ iterator = filesystem.iterator("/Playlists")
+ })
+ end)
+ playlist_btn:add_style(styles.list_item)
+
local function show_no_indexes(msg)
indexes_list:add_flag(lvgl.FLAG.HIDDEN)
no_indexes_container:clear_flag(lvgl.FLAG.HIDDEN)
@@ -140,6 +146,18 @@ return widgets.MenuScreen:new {
local function hide_no_indexes()
no_indexes_container:add_flag(lvgl.FLAG.HIDDEN)
indexes_list:clear_flag(lvgl.FLAG.HIDDEN)
+
+ if indexes[1] then
+ indexes[1].object:focus()
+ end
+ end
+
+ local function hide_playlist_listing()
+ playlist_btn:add_flag(lvgl.FLAG.HIDDEN)
+ end
+
+ local function show_playlist_listing()
+ playlist_btn:clear_flag(lvgl.FLAG.HIDDEN)
end
local function update_visible_indexes()
@@ -155,6 +173,13 @@ return widgets.MenuScreen:new {
end
if has_valid_index then
hide_no_indexes()
+
+ -- If we have valid indexes, then also check for playlists
+ if filesystem.iterator("/Playlists/"):next() == nil then
+ hide_playlist_listing()
+ else
+ show_playlist_listing()
+ end
else
if require("database").updating:get() then
show_no_indexes("The database is updating for the first time. Please wait.")
diff --git a/lua/playing.lua b/lua/playing.lua
index 08cdaaa2..9391a85c 100644
--- a/lua/playing.lua
+++ b/lua/playing.lua
@@ -276,7 +276,7 @@ return screen:new {
if queue.loading:get() then
title:set { text = "Loading..." }
else
- title:set{text=""}
+ title:set{ text = "Not Playing" }
end
album:set{text=""}
artist:set{text=""}
diff --git a/lua/playlist_browser.lua b/lua/playlist_browser.lua
new file mode 100644
index 00000000..8854a19f
--- /dev/null
+++ b/lua/playlist_browser.lua
@@ -0,0 +1,94 @@
+-- SPDX-FileCopyrightText: 2025 Sam Lord <code@samlord.co.uk>
+--
+-- SPDX-License-Identifier: GPL-3.0-only
+
+local lvgl = require("lvgl")
+local widgets = require("widgets")
+local backstack = require("backstack")
+local playing = require("playing")
+local filesystem = require("filesystem")
+local screen = require("screen")
+local font = require("font")
+local theme = require("theme")
+local playback = require("playback")
+local queue = require("queue")
+local playlist_iterator = require("playlist_iterator")
+local img = require("images")
+
+
+return screen:new {
+ create_ui = function(self)
+ self.root = lvgl.Object(nil, {
+ flex = {
+ flex_direction = "column",
+ flex_wrap = "wrap",
+ justify_content = "flex-start",
+ align_items = "flex-start",
+ align_content = "flex-start"
+ },
+ w = lvgl.HOR_RES(),
+ h = lvgl.VER_RES()
+ })
+ self.root:center()
+
+ self.status_bar = widgets.StatusBar(self, {
+ back_cb = backstack.pop,
+ title = self.title
+ })
+
+ local header = self.root:Object {
+ flex = {
+ flex_direction = "column",
+ flex_wrap = "wrap",
+ justify_content = "flex-start",
+ align_items = "flex-start",
+ align_content = "flex-start"
+ },
+ w = lvgl.HOR_RES(),
+ h = lvgl.SIZE_CONTENT,
+ pad_left = 4,
+ pad_right = 4,
+ pad_bottom = 2,
+ scrollbar_mode = lvgl.SCROLLBAR_MODE.OFF
+ }
+ theme.set_subject(header, "header")
+
+ if self.breadcrumb then
+ header:Label {
+ text = self.breadcrumb,
+ text_font = font.fusion_10
+ }
+ end
+
+ local get_icon_func = function(item)
+ if item:is_directory() then
+ return img.file_directory
+ else
+ return img.file_playlist
+ end
+ end
+
+ widgets.InfiniteList(self.root, playlist_iterator:create(self.iterator), {
+ focus_first_item = true,
+ get_icon = get_icon_func,
+ callback = function(item)
+ return function()
+ if item:is_directory() then
+ backstack.push(
+ require("playlist_browser"):new {
+ title = self.title,
+ iterator = filesystem.iterator(item:filepath()),
+ breadcrumb = item:filepath()
+ })
+ elseif
+ playlist_iterator:is_playlist(item) then
+ -- TODO: playlist viewer
+ queue.open_playlist(item:filepath())
+ playback.playing:set(true)
+ backstack.push(playing:new())
+ end
+ end
+ end
+ })
+ end
+}
diff --git a/lua/playlist_iterator.lua b/lua/playlist_iterator.lua
new file mode 100644
index 00000000..06e80ad2
--- /dev/null
+++ b/lua/playlist_iterator.lua
@@ -0,0 +1,35 @@
+local PlaylistIterator = {}
+
+function PlaylistIterator:is_playlist(item)
+ return item:filepath():match("%.playlist$")
+ or item:filepath():match("%.m3u8?$")
+end
+
+function PlaylistIterator:create(fs_iterator)
+ local iterator = fs_iterator:clone()
+ local obj = {};
+
+ local find_matching = function(iterate_fn)
+ local next = iterate_fn(iterator);
+ while next and (not PlaylistIterator:is_playlist(next) and not next:is_directory()) do
+ next = iterate_fn();
+ end
+ return next;
+ end
+
+ function obj:clone()
+ return PlaylistIterator:create(iterator)
+ end
+
+ function obj:next()
+ return find_matching(iterator.next)
+ end
+
+ function obj:prev()
+ return find_matching(iterator.prev)
+ end
+
+ return obj
+end
+
+return PlaylistIterator
diff --git a/lua/settings.lua b/lua/settings.lua
index c0e7c23e..aae6db99 100644
--- a/lua/settings.lua
+++ b/lua/settings.lua
@@ -93,6 +93,7 @@ settings.BluetoothSettings = SettingsScreen:new {
local enabled = enable_sw:enabled()
bluetooth.enabled:set(enabled)
end)
+ enable_sw:focus()
self.bindings = self.bindings + {
bluetooth.enabled:bind(function(en)
@@ -232,6 +233,7 @@ settings.HeadphonesSettings = SettingsScreen:new {
local selection = volume_chooser:get('selected') + 1
volume.limit_db:set(limits[selection])
end)
+ volume_chooser:focus()
theme.set_subject(self.content:Label {
text = "Left/Right balance",
@@ -304,6 +306,7 @@ settings.DisplaySettings = SettingsScreen:new {
brightness:onevent(lvgl.EVENT.VALUE_CHANGED, function()
display.brightness:set(brightness:value())
end)
+ brightness:focus()
self.bindings = self.bindings + {
display.brightness:bind(function(b)
@@ -372,6 +375,8 @@ settings.ThemeSettings = SettingsScreen:new {
backstack.reset(main_menu:new())
end
end)
+
+ theme_chooser:focus()
end
}
@@ -380,44 +385,62 @@ settings.InputSettings = SettingsScreen:new {
create_ui = function(self)
SettingsScreen.create_ui(self)
- theme.set_subject(self.content:Label {
- text = "Control scheme",
- }, "settings_title")
+ -- Use the control scheme enum lists to generate the relevant dropdowns
+ local make_scheme_control = function(self, scheme_list, control_scheme)
+ local option_to_scheme = {}
+ local scheme_to_option = {}
+ local option_idx = 0
+ local options = ""
+
+ -- Sort the keys to order the dropdowns the same as the enums
+ keys = {}
+ for i in pairs(scheme_list) do table.insert(keys, i) end
+ table.sort(keys)
+
+ for i, k in pairs(keys) do
+ v = scheme_list[k]
+
+ option_to_scheme[option_idx] = k
+ scheme_to_option[k] = option_idx
+ if option_idx > 0 then
+ options = options .. "\n"
+ end
+ options = options .. v
+ option_idx = option_idx + 1
+ end
- local schemes = controls.schemes()
- local option_to_scheme = {}
- local scheme_to_option = {}
+ local controls_chooser = self.content:Dropdown {
+ options = options,
+ symbol = img.chevron,
+ }
- local option_idx = 0
- local options = ""
+ self.bindings = self.bindings + {
+ control_scheme:bind(function(s)
+ local option = scheme_to_option[s]
+ controls_chooser:set({ selected = option })
+ end)
+ }
- for i, v in pairs(schemes) do
- option_to_scheme[option_idx] = i
- scheme_to_option[i] = option_idx
- if option_idx > 0 then
- options = options .. "\n"
- end
- options = options .. v
- option_idx = option_idx + 1
+ controls_chooser:onevent(lvgl.EVENT.VALUE_CHANGED, function()
+ local option = controls_chooser:get('selected')
+ local scheme = option_to_scheme[option]
+ control_scheme:set(scheme)
+ end)
+
+ return controls_chooser
end
- local controls_chooser = self.content:Dropdown {
- options = options,
- symbol = img.chevron,
- }
+ theme.set_subject(self.content:Label {
+ text = "Control scheme",
+ }, "settings_title")
+ local controls_chooser = make_scheme_control(self, controls.schemes(), controls.scheme)
- self.bindings = self.bindings + {
- controls.scheme:bind(function(s)
- local option = scheme_to_option[s]
- controls_chooser:set({ selected = option })
- end)
- }
+ theme.set_subject(self.content:Label {
+ text = "Control scheme when locked",
+ }, "settings_title")
+ make_scheme_control(self, controls.locked_schemes(), controls.locked_scheme)
- controls_chooser:onevent(lvgl.EVENT.VALUE_CHANGED, function()
- local option = controls_chooser:get('selected')
- local scheme = option_to_scheme[option]
- controls.scheme:set(scheme)
- end)
+ controls_chooser:focus()
theme.set_subject(self.content:Label {
text = "Scroll Sensitivity",
@@ -483,6 +506,7 @@ settings.MassStorageSettings = SettingsScreen:new {
end
bind_switch()
end)
+ enable_sw:focus()
self.bindings = self.bindings + {
usb.msc_enabled:bind(bind_switch),
@@ -560,6 +584,7 @@ settings.DatabaseSettings = SettingsScreen:new {
update:onClicked(function()
database.update()
end)
+ update:focus()
self.bindings = self.bindings + {
database.auto_update:bind(function(en)
@@ -841,10 +866,11 @@ settings.Root = widgets.MenuScreen:new {
end)
end
item:add_style(styles.list_item)
+ return item
end
local audio_section = section("Audio")
- submenu("Bluetooth", settings.BluetoothSettings, audio_section)
+ local first_item = submenu("Bluetooth", settings.BluetoothSettings, audio_section)
submenu("Headphones", settings.HeadphonesSettings)
section("Interface")
@@ -863,6 +889,8 @@ settings.Root = widgets.MenuScreen:new {
submenu("Firmware", settings.FirmwareSettings)
submenu("Licenses", settings.LicensesScreen)
submenu("Regulatory", settings.RegulatoryScreen)
+
+ first_item:focus()
end
}
diff --git a/lua/theme_hicon.lua b/lua/theme_hicon.lua
index 80f78a10..f712e0a6 100644
--- a/lua/theme_hicon.lua
+++ b/lua/theme_hicon.lua
@@ -179,6 +179,8 @@ local theme_hicon = {
bg_color = background_color,
border_color = text_color,
border_width = 1,
+ outline_color = background_color,
+ outline_width = 1,
}},
{lvgl.PART.KNOB | lvgl.STATE.FOCUSED, lvgl.Style {
bg_color = text_color,
diff --git a/lua/theme_light.lua b/lua/theme_light.lua
index f4039766..2addf546 100644
--- a/lua/theme_light.lua
+++ b/lua/theme_light.lua
@@ -186,6 +186,7 @@ local theme_light = {
},
dropdown = {
{lvgl.PART.MAIN, lvgl.Style{
+ bg_opa = lvgl.OPA(100),
radius = 2,
pad_all = 2,
bg_color = background_color,
@@ -196,7 +197,8 @@ local theme_light = {
outline_width = 1,
}},
{lvgl.PART.MAIN | lvgl.STATE.FOCUSED, lvgl.Style {
- border_color = highlight_color,
+ text_color = "#ffffff",
+ bg_color = highlight_color,
}},
{lvgl.PART.INDICATOR, lvgl.Style {
image_recolor_opa = 255,
@@ -213,6 +215,7 @@ local theme_light = {
bg_color = background_color
}},
{lvgl.PART.SELECTED | lvgl.STATE.CHECKED, lvgl.Style {
+ text_color = "#ffffff",
bg_color = highlight_color,
}},
},
diff --git a/lua/widgets.lua b/lua/widgets.lua
index 5e18809b..20f0cd2a 100644
--- a/lua/widgets.lua
+++ b/lua/widgets.lua
@@ -306,10 +306,11 @@ function widgets.InfiniteList(parent, iterator, opts)
fwd_iterator:prev()
end
- local function add_item(item, index)
+ local function add_item(item, index, item_opts)
if not item then
return
end
+ item_opts = item_opts or {}
local this_item = index
local add_to_top = false
if this_item < first_index then
@@ -325,6 +326,9 @@ function widgets.InfiniteList(parent, iterator, opts)
if add_to_top then
btn:move_to_index(0)
end
+ if item_opts.focus then
+ btn:focus()
+ end
-- opts.callback should take an item and return a function matching the arg of onClicked
if opts.callback then
btn:onClicked(opts.callback(item))
@@ -357,11 +361,11 @@ function widgets.InfiniteList(parent, iterator, opts)
end
for idx = 0, 8 do
- local val = fwd_iterator()
+ local val = fwd_iterator:next()
if not val then
break
end
- add_item(val, idx)
+ add_item(val, idx, { focus = (opts.focus_first_item and idx == 0) })
end
return infinite_list