Add basic lua browser screen

custom
jacqueline 1 year ago
parent cd46d7bd20
commit 06aca259cb
  1. 2
      lib/lvgl/src/extra/widgets/list/lv_list.c
  2. BIN
      lua/assets/audio.png
  3. BIN
      lua/assets/battery_20.png
  4. BIN
      lua/assets/battery_40.png
  5. BIN
      lua/assets/battery_60.png
  6. BIN
      lua/assets/battery_80.png
  7. BIN
      lua/assets/battery_empty.png
  8. BIN
      lua/assets/battery_full.png
  9. BIN
      lua/assets/bt.png
  10. BIN
      lua/assets/bt_conn.png
  11. BIN
      lua/assets/pause.png
  12. BIN
      lua/assets/play.png
  13. 113
      lua/browser.lua
  14. 8
      lua/main_menu.lua
  15. 13
      lua/widgets.lua
  16. 12
      src/database/database.cpp
  17. 1
      src/database/include/database.hpp
  18. 77
      src/lua/lua_database.cpp
  19. 15
      src/ui/ui_fsm.cpp

@ -91,7 +91,7 @@ lv_obj_t * lv_list_add_btn(lv_obj_t * list, const void * icon, const char * txt)
if(txt) {
lv_obj_t * label = lv_label_create(obj);
lv_label_set_text(label, txt);
lv_label_set_long_mode(label, LV_LABEL_LONG_SCROLL_CIRCULAR);
lv_label_set_long_mode(label, LV_LABEL_LONG_DOT);
lv_obj_set_flex_grow(label, 1);
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 623 B

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 617 B

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 617 B

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 618 B

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 622 B

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 614 B

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 618 B

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.3 KiB

After

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 654 B

After

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 608 B

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 616 B

After

Width:  |  Height:  |  Size: 4.2 KiB

@ -0,0 +1,113 @@
local lvgl = require("lvgl")
local widgets = require("widgets")
local legacy_ui = require("legacy_ui")
local database = require("database")
local backstack = require("backstack")
local browser = {}
function browser.create(opts)
local screen = {}
screen.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(),
})
screen.root:center()
screen.status_bar = widgets.StatusBar(screen.root, {
back_cb = backstack.pop,
title = opts.title,
})
if opts.breadcrumb then
local header = screen.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_top = 2,
pad_bottom = 2,
bg_opa = lvgl.OPA(100),
bg_color = "#f3e5f5",
scrollbar_mode = lvgl.SCROLLBAR_MODE.OFF,
}
header:Label { text = opts.breadcrumb }
local buttons = header:Object({
flex = {
flex_direction = "row",
flex_wrap = "wrap",
justify_content = "flex-end",
align_items = "center",
align_content = "center",
},
w = lvgl.PCT(100),
h = lvgl.SIZE_CONTENT,
pad_column = 4,
})
local enqueue = buttons:Button {}
enqueue:Label { text = "Enqueue" }
enqueue:add_flag(lvgl.FLAG.HIDDEN)
local play = buttons:Button {}
play:Label { text = "Play all" }
end
screen.list = lvgl.List(screen.root, {
w = lvgl.PCT(100),
h = lvgl.PCT(100),
flex_grow = 1,
scrollbar_mode = lvgl.SCROLLBAR_MODE.OFF,
})
screen.focused_item = 0
screen.last_item = 0
screen.add_item = function(item)
if not item then return end
screen.last_item = screen.last_item + 1
local this_item = screen.last_item
local btn = screen.list:add_btn(nil, tostring(item))
btn:onClicked(function()
local contents = item:contents()
if type(contents) == "function" then
backstack.push(function()
return browser.create({
title = opts.title,
iterator = contents,
breadcrumb = tostring(item),
})
end)
else
print("selected track", contents)
end
end)
btn:onevent(lvgl.EVENT.FOCUSED, function()
screen.focused_item = this_item
if screen.last_item - 5 < this_item then
opts.iterator(screen.add_item)
end
end)
end
for _ = 1, 8 do
opts.iterator(screen.add_item)
end
return screen
end
return browser.create

@ -2,6 +2,8 @@ local lvgl = require("lvgl")
local widgets = require("widgets")
local legacy_ui = require("legacy_ui")
local database = require("database")
local backstack = require("backstack")
local browser = require("browser")
return function()
local menu = {}
@ -35,6 +37,12 @@ return function()
local btn = menu.list:add_btn(nil, tostring(idx))
btn:onClicked(function()
legacy_ui.open_browse(id);
-- backstack.push(function()
-- return browser {
-- title = tostring(idx),
-- iterator = idx:iter()
-- }
-- end)
end)
end

@ -17,23 +17,20 @@ function widgets.StatusBar(parent, opts)
},
w = lvgl.HOR_RES(),
h = lvgl.SIZE_CONTENT,
bg_opa = lvgl.OPA(100),
bg_color = "#fff",
pad_top = 1,
pad_bottom = 1,
pad_column = 1,
shadow_width = 6,
shadow_opa = lvgl.OPA(50),
shaddow_ofs_x = 0,
bg_opa = lvgl.OPA(100),
bg_color = "#e1bee7",
scrollbar_mode = lvgl.SCROLLBAR_MODE.OFF,
}
if opts.back_cb then
status_bar.back = status_bar.root:Label {
status_bar.back = status_bar.root:Button {
w = lvgl.SIZE_CONTENT,
h = 12,
text = "<",
}
status_bar.back:Label({ text = "<", align = lvgl.ALIGN.CENTER })
status_bar.back:onClicked(opts.back_cb)
end
@ -44,7 +41,7 @@ function widgets.StatusBar(parent, opts)
flex_grow = 1,
}
if opts.title then
status_bar.title.set { text = opts.title }
status_bar.title:set { text = opts.title }
end
status_bar.playing = status_bar.root:Image {}

@ -846,8 +846,7 @@ auto IndexRecord::Expand(std::size_t page_size) const
if (track_) {
return {};
}
IndexKey::Header new_header = ExpandHeader(key_.header, key_.item);
std::string new_prefix = EncodeIndexPrefix(new_header);
std::string new_prefix = EncodeIndexPrefix(ExpandHeader());
return Continuation{
.prefix = {new_prefix.data(), new_prefix.size()},
.start_key = {new_prefix.data(), new_prefix.size()},
@ -857,6 +856,10 @@ auto IndexRecord::Expand(std::size_t page_size) const
};
}
auto IndexRecord::ExpandHeader() const -> IndexKey::Header {
return ::database::ExpandHeader(key_.header, key_.item);
}
Iterator::Iterator(std::weak_ptr<Database> db, const IndexInfo& idx)
: db_(db), pos_mutex_(), current_pos_(), prev_pos_() {
std::string prefix = EncodeIndexPrefix(
@ -887,6 +890,11 @@ auto Iterator::Next(Callback cb) -> void {
db->dbGetPage<IndexRecord>(*current_pos_)};
prev_pos_ = current_pos_;
current_pos_ = res->next_page();
if (!res || res->values().empty() || !res->values()[0]) {
ESP_LOGI(kTag, "dropping empty result");
InvokeNull(cb);
return;
}
std::invoke(cb, *res->values()[0]);
});
}

@ -80,6 +80,7 @@ class IndexRecord {
auto track() const -> std::optional<TrackId>;
auto Expand(std::size_t) const -> std::optional<Continuation>;
auto ExpandHeader() const -> IndexKey::Header;
private:
IndexKey key_;

@ -20,7 +20,9 @@
#include "event_queue.hpp"
#include "index.hpp"
#include "property.hpp"
#include "records.hpp"
#include "service_locator.hpp"
#include "track.hpp"
#include "ui_events.hpp"
namespace lua {
@ -55,6 +57,42 @@ static auto indexes(lua_State* state) -> int {
static const struct luaL_Reg kDatabaseFuncs[] = {{"indexes", indexes},
{NULL, NULL}};
/*
* Struct to be used as userdata for the Lua representation of database records.
* In order to push these large values into PSRAM as much as possible, memory
* for these is allocated and managed by Lua. This struct must therefore be
* trivially copyable.
*/
struct LuaRecord {
database::TrackId id_or_zero;
database::IndexKey::Header header_at_next_depth;
size_t text_size;
char text[];
};
static_assert(std::is_trivially_copyable_v<LuaRecord> == true);
static auto push_lua_record(lua_State* L, const database::IndexRecord& r)
-> void {
// Bake out the text into something concrete.
auto text = r.text().value_or("");
// Create and init the userdata.
LuaRecord* record = reinterpret_cast<LuaRecord*>(
lua_newuserdata(L, sizeof(LuaRecord) + text.size()));
luaL_setmetatable(L, kDbRecordMetatable);
// Init all the fields
*record = {
.id_or_zero = r.track().value_or(0),
.header_at_next_depth = r.ExpandHeader(),
.text_size = text.size(),
};
// Copy the string data across.
std::memcpy(record->text, text.data(), text.size());
}
static auto db_iterate(lua_State* state) -> int {
luaL_checktype(state, 1, LUA_TFUNCTION);
int callback_ref = luaL_ref(state, LUA_REGISTRYINDEX);
@ -66,11 +104,7 @@ static auto db_iterate(lua_State* state) -> int {
events::Ui().RunOnTask([=]() {
lua_rawgeti(state, LUA_REGISTRYINDEX, callback_ref);
if (res) {
database::IndexRecord** record =
reinterpret_cast<database::IndexRecord**>(
lua_newuserdata(state, sizeof(uintptr_t)));
*record = new database::IndexRecord(*res);
luaL_setmetatable(state, kDbRecordMetatable);
push_lua_record(state, *res);
} else {
lua_pushnil(state);
}
@ -105,40 +139,37 @@ static auto push_iterator(
lua_pushcclosure(state, db_iterate, 1);
}
static auto record_text(lua_State* state) -> int {
database::IndexRecord* data = *reinterpret_cast<database::IndexRecord**>(
LuaRecord* data = reinterpret_cast<LuaRecord*>(
luaL_checkudata(state, 1, kDbRecordMetatable));
lua_pushstring(state,
data->text().value_or("[tell jacqueline u saw this]").c_str());
lua_pushlstring(state, data->text, data->text_size);
return 1;
}
static auto record_contents(lua_State* state) -> int {
database::IndexRecord* data = *reinterpret_cast<database::IndexRecord**>(
LuaRecord* data = reinterpret_cast<LuaRecord*>(
luaL_checkudata(state, 1, kDbRecordMetatable));
if (data->track()) {
lua_pushinteger(state, *data->track());
if (data->id_or_zero) {
lua_pushinteger(state, data->id_or_zero);
} else {
push_iterator(state, data->Expand(1).value());
std::string p = database::EncodeIndexPrefix(data->header_at_next_depth);
push_iterator(state, database::Continuation{
.prefix = {p.data(), p.size()},
.start_key = {p.data(), p.size()},
.forward = true,
.was_prev_forward = true,
.page_size = 1,
});
}
return 1;
}
static auto record_gc(lua_State* state) -> int {
database::IndexRecord** data = reinterpret_cast<database::IndexRecord**>(
luaL_checkudata(state, 1, kDbRecordMetatable));
if (data != NULL) {
delete *data;
}
return 0;
}
static const struct luaL_Reg kDbRecordFuncs[] = {{"title", record_text},
{"contents", record_contents},
{"__tostring", record_text},
{"__gc", record_gc},
{NULL, NULL}};
static auto index_name(lua_State* state) -> int {
@ -207,4 +238,4 @@ auto RegisterDatabaseModule(lua_State* s) -> void {
lua_pop(s, 1);
}
} // namespace lua
} // namespace lua

@ -219,6 +219,7 @@ void Lua::entry() {
{"pop", [&](lua_State* s) { return PopLuaScreen(s); }},
});
sCurrentScreen.reset();
sLua->RunScript("/lua/main.lua");
}
}
@ -243,12 +244,6 @@ auto Lua::PushLuaScreen(lua_State* s) -> int {
// Store the reference for the table the constructor returned.
new_screen->SetObjRef(s);
// Ensure that we don't pollute the new screen's group. We leave the luavgl
// root alone.
// FIXME: maybe we should set the luavgl root to some catch-all that throws
// when anything is added to it? this may help catch bugs!
lv_group_set_default(NULL);
// Finally, push the now-initialised screen as if it were a regular C++
// screen.
PushScreen(new_screen);
@ -256,12 +251,16 @@ auto Lua::PushLuaScreen(lua_State* s) -> int {
return 0;
}
auto Lua::PopLuaScreen(lua_State*) -> int {
auto Lua::PopLuaScreen(lua_State* s) -> int {
PopScreen();
luavgl_set_root(s, sCurrentScreen->root());
lv_group_set_default(sCurrentScreen->group());
return 0;
}
void Lua::exit() {}
void Lua::exit() {
lv_group_set_default(NULL);
}
void Lua::react(const internal::IndexSelected& ev) {
auto db = sServices->database().lock();

Loading…
Cancel
Save