diff --git a/lua/browser.lua b/lua/browser.lua index ccb2dae8..f07d80bc 100644 --- a/lua/browser.lua +++ b/lua/browser.lua @@ -4,7 +4,7 @@ local legacy_ui = require("legacy_ui") local database = require("database") local backstack = require("backstack") local font = require("font") -local playing = require("playing") +local queue = require("queue") local browser = {} @@ -40,7 +40,6 @@ function browser.create(opts) h = lvgl.SIZE_CONTENT, pad_left = 4, pad_right = 4, - pad_top = 2, pad_bottom = 2, bg_opa = lvgl.OPA(100), bg_color = "#fafafa", @@ -64,9 +63,18 @@ function browser.create(opts) h = lvgl.SIZE_CONTENT, pad_column = 4, }) + local original_iterator = opts.iterator:clone() local enqueue = widgets.IconBtn(buttons, "//lua/img/enqueue.png", "Enqueue") + enqueue:onClicked(function() + queue.add(original_iterator) + end) -- enqueue:add_flag(lvgl.FLAG.HIDDEN) local play = widgets.IconBtn(buttons, "//lua/img/play_small.png", "Play") + play:onClicked(function() + queue.clear() + queue.add(original_iterator) + end + ) end screen.list = lvgl.List(screen.root, { @@ -88,7 +96,7 @@ function browser.create(opts) local btn = screen.list:add_btn(nil, tostring(item)) btn:onClicked(function() local contents = item:contents() - if type(contents) == "function" then + if type(contents) == "userdata" then backstack.push(function() return browser.create({ title = opts.title, @@ -97,7 +105,8 @@ function browser.create(opts) }) end) else - print("add", contents) + queue.clear() + queue.add(contents) legacy_ui.open_now_playing() -- backstack.push(playing) end @@ -105,13 +114,13 @@ function browser.create(opts) btn:onevent(lvgl.EVENT.FOCUSED, function() screen.focused_item = this_item if screen.last_item - 5 < this_item then - opts.iterator(screen.add_item) + opts.iterator:next(screen.add_item) end end) end for _ = 1, 8 do - opts.iterator(screen.add_item) + opts.iterator:next(screen.add_item) end return screen diff --git a/lua/img/bat/chg.png b/lua/img/bat/chg.png index af90f02b..d604bb76 100644 Binary files a/lua/img/bat/chg.png and b/lua/img/bat/chg.png differ diff --git a/lua/img/play_small.png b/lua/img/play_small.png index a7361be4..3fc7032e 100644 Binary files a/lua/img/play_small.png and b/lua/img/play_small.png differ diff --git a/lua/main_menu.lua b/lua/main_menu.lua index 50e930fb..c0b9b1d1 100644 --- a/lua/main_menu.lua +++ b/lua/main_menu.lua @@ -36,13 +36,12 @@ return function() for id, idx in ipairs(indexes) do 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) + backstack.push(function() + return browser { + title = tostring(idx), + iterator = idx:iter() + } + end) end) end diff --git a/lua/widgets.lua b/lua/widgets.lua index 95615a8c..b601326b 100644 --- a/lua/widgets.lua +++ b/lua/widgets.lua @@ -151,6 +151,7 @@ function widgets.IconBtn(parent, icon, text) } btn:Image { src = icon } btn:Label { text = text, text_font = font.fusion_10 } + return btn end return widgets diff --git a/src/database/database.cpp b/src/database/database.cpp index dad983d0..76d3a2ab 100644 --- a/src/database/database.cpp +++ b/src/database/database.cpp @@ -874,6 +874,12 @@ Iterator::Iterator(std::weak_ptr db, const IndexInfo& idx) Iterator::Iterator(std::weak_ptr db, const Continuation& c) : db_(db), pos_mutex_(), current_pos_(c), prev_pos_() {} +Iterator::Iterator(const Iterator& other) + : db_(other.db_), + pos_mutex_(), + current_pos_(other.current_pos_), + prev_pos_(other.prev_pos_) {} + auto Iterator::Next(Callback cb) -> void { auto db = db_.lock(); if (!db) { @@ -899,6 +905,31 @@ auto Iterator::Next(Callback cb) -> void { }); } +auto Iterator::NextSync() -> std::optional { + auto db = db_.lock(); + if (!db) { + return {}; + } + return db->worker_task_ + ->Dispatch>( + [=]() -> std::optional { + std::lock_guard lock{pos_mutex_}; + if (!current_pos_) { + return {}; + } + std::unique_ptr> res{ + db->dbGetPage(*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"); + return {}; + } + return *res->values()[0]; + }) + .get(); +} + auto Iterator::Prev(Callback cb) -> void { auto db = db_.lock(); if (!db) { diff --git a/src/database/include/database.hpp b/src/database/include/database.hpp index e18701eb..36f734b8 100644 --- a/src/database/include/database.hpp +++ b/src/database/include/database.hpp @@ -193,12 +193,18 @@ class Iterator { public: Iterator(std::weak_ptr, const IndexInfo&); Iterator(std::weak_ptr, const Continuation&); + Iterator(const Iterator &); + + auto database() const { return db_; } using Callback = std::function)>; auto Next(Callback) -> void; + auto NextSync() -> std::optional; + auto Prev(Callback) -> void; + private: auto InvokeNull(Callback) -> void; diff --git a/src/lua/include/lua_database.hpp b/src/lua/include/lua_database.hpp index e47ace08..b0d2acbd 100644 --- a/src/lua/include/lua_database.hpp +++ b/src/lua/include/lua_database.hpp @@ -8,8 +8,12 @@ #include "lua.hpp" +#include "database.hpp" + namespace lua { +auto db_check_iterator(lua_State*, int stack_pos) -> database::Iterator*; + auto RegisterDatabaseModule(lua_State*) -> void; } // namespace lua diff --git a/src/lua/lua_database.cpp b/src/lua/lua_database.cpp index 4a7c82a5..d41f4794 100644 --- a/src/lua/lua_database.cpp +++ b/src/lua/lua_database.cpp @@ -32,7 +32,7 @@ namespace lua { static constexpr char kDbIndexMetatable[] = "db_index"; static constexpr char kDbRecordMetatable[] = "db_record"; -static constexpr char kDbIteratorMetatable[] = "db_record"; +static constexpr char kDbIteratorMetatable[] = "db_iterator"; static auto indexes(lua_State* state) -> int { Bridge* instance = Bridge::Get(state); @@ -94,13 +94,37 @@ static auto push_lua_record(lua_State* L, const database::IndexRecord& r) std::memcpy(record->text, text.data(), text.size()); } +auto db_check_iterator(lua_State* L, int stack_pos) -> database::Iterator* { + database::Iterator* it = *reinterpret_cast( + luaL_checkudata(L, stack_pos, kDbIteratorMetatable)); + return it; +} + +static auto push_iterator(lua_State* state, + std::variant val) -> void { + Bridge* instance = Bridge::Get(state); + database::Iterator** data = reinterpret_cast( + lua_newuserdata(state, sizeof(uintptr_t))); + std::visit( + [&](auto&& arg) { + using T = std::decay_t; + if constexpr (std::is_same_v) { + *data = new database::Iterator(*arg); + } else { + *data = new database::Iterator(instance->services().database(), arg); + } + }, + val); + luaL_setmetatable(state, kDbIteratorMetatable); +} + static auto db_iterate(lua_State* state) -> int { - luaL_checktype(state, 1, LUA_TFUNCTION); + database::Iterator* it = db_check_iterator(state, 1); + luaL_checktype(state, 2, LUA_TFUNCTION); int callback_ref = luaL_ref(state, LUA_REGISTRYINDEX); - database::Iterator* it = *reinterpret_cast( - lua_touserdata(state, lua_upvalueindex(1))); - it->Next([=](std::optional res) { events::Ui().RunOnTask([=]() { lua_rawgeti(state, LUA_REGISTRYINDEX, callback_ref); @@ -116,29 +140,22 @@ static auto db_iterate(lua_State* state) -> int { return 0; } +static auto db_iterator_clone(lua_State* state) -> int { + database::Iterator* it = db_check_iterator(state, 1); + push_iterator(state, it); + return 1; +} + static auto db_iterator_gc(lua_State* state) -> int { - database::Iterator** it = reinterpret_cast( - luaL_checkudata(state, 1, kDbIteratorMetatable)); - if (it != NULL) { - delete *it; - } + database::Iterator* it = db_check_iterator(state, 1); + delete it; return 0; } -static auto push_iterator( - lua_State* state, - std::variant val) -> void { - Bridge* instance = Bridge::Get(state); - database::Iterator** data = reinterpret_cast( - lua_newuserdata(state, sizeof(uintptr_t))); - std::visit( - [&](auto&& arg) { - *data = new database::Iterator(instance->services().database(), arg); - }, - val); - luaL_setmetatable(state, kDbIteratorMetatable); - lua_pushcclosure(state, db_iterate, 1); -} +static const struct luaL_Reg kDbIteratorFuncs[] = {{"next", db_iterate}, + {"clone", db_iterator_clone}, + {"__gc", db_iterator_gc}, + {NULL, NULL}}; static auto record_text(lua_State* state) -> int { LuaRecord* data = reinterpret_cast( @@ -219,9 +236,10 @@ static auto lua_database(lua_State* state) -> int { luaL_setfuncs(state, kDbIndexFuncs, 0); luaL_newmetatable(state, kDbIteratorMetatable); - lua_pushliteral(state, "__gc"); - lua_pushcfunction(state, db_iterator_gc); - lua_settable(state, -3); + lua_pushliteral(state, "__index"); + lua_pushvalue(state, -2); + lua_settable(state, -3); // metatable.__index = metatable + luaL_setfuncs(state, kDbIteratorFuncs, 0); luaL_newmetatable(state, kDbRecordMetatable); lua_pushliteral(state, "__index"); diff --git a/src/lua/lua_queue.cpp b/src/lua/lua_queue.cpp index 500940a2..929a7159 100644 --- a/src/lua/lua_queue.cpp +++ b/src/lua/lua_queue.cpp @@ -21,6 +21,7 @@ #include "index.hpp" #include "property.hpp" #include "service_locator.hpp" +#include "source.hpp" #include "ui_events.hpp" namespace lua { @@ -28,10 +29,28 @@ namespace lua { [[maybe_unused]] static constexpr char kTag[] = "lua_queue"; static auto queue_add(lua_State* state) -> int { + Bridge* instance = Bridge::Get(state); + + if (lua_isinteger(state, 1)) { + instance->services().track_queue().AddLast(luaL_checkinteger(state, 1)); + } else { + database::Iterator* it = db_check_iterator(state, 1); + instance->services().track_queue().IncludeLast( + std::make_shared(*it)); + } + + return 0; +} + +static auto queue_clear(lua_State* state) -> int { + Bridge* instance = Bridge::Get(state); + instance->services().track_queue().Clear(); return 0; } -static const struct luaL_Reg kQueueFuncs[] = {{"add", queue_add}, {NULL, NULL}}; +static const struct luaL_Reg kQueueFuncs[] = {{"add", queue_add}, + {"clear", queue_clear}, + {NULL, NULL}}; static auto lua_queue(lua_State* state) -> int { luaL_newlib(state, kQueueFuncs); diff --git a/src/playlist/include/source.hpp b/src/playlist/include/source.hpp index aa15e7df..ce12faf3 100644 --- a/src/playlist/include/source.hpp +++ b/src/playlist/include/source.hpp @@ -9,6 +9,7 @@ #include #include #include +#include #include #include @@ -73,6 +74,24 @@ class IResetableSource : public ISource { virtual auto Reset() -> void = 0; }; +class IteratorSource : public IResetableSource { + public: + IteratorSource(const database::Iterator&); + + auto Current() -> std::optional override; + auto Advance() -> std::optional override; + auto Peek(std::size_t n, std::vector*) + -> std::size_t override; + + auto Previous() -> std::optional override; + auto Reset() -> void override; + + private: + const database::Iterator& start_; + std::optional current_; + std::stack> next_; +}; + auto CreateSourceFromResults( std::weak_ptr, std::shared_ptr>) diff --git a/src/playlist/source.cpp b/src/playlist/source.cpp index 7a062bc7..2540c3fb 100644 --- a/src/playlist/source.cpp +++ b/src/playlist/source.cpp @@ -22,6 +22,55 @@ namespace playlist { +[[maybe_unused]] static constexpr char kTag[] = "queue_src"; + +IteratorSource::IteratorSource(const database::Iterator& it) + : start_(it), current_(), next_() { + Reset(); + Advance(); +} + +auto IteratorSource::Current() -> std::optional { + return current_; +} + +auto IteratorSource::Advance() -> std::optional { + ESP_LOGI(kTag, "advancing"); + while (!next_.empty()) { + auto next = next_.top().NextSync(); + if (!next) { + ESP_LOGI(kTag, "top was empty"); + next_.pop(); + continue; + } + if (next->track()) { + ESP_LOGI(kTag, "top held track %lu", next->track().value_or(0)); + current_ = next->track(); + return current_; + } + ESP_LOGI(kTag, "top held records"); + next_.push(database::Iterator(start_.database(), next->Expand(1).value())); + } + ESP_LOGI(kTag, "exhausted"); + return {}; +} + +auto IteratorSource::Peek(std::size_t n, std::vector*) + -> std::size_t { + return 0; +} + +auto IteratorSource::Previous() -> std::optional { + return {}; +} + +auto IteratorSource::Reset() -> void { + while (!next_.empty()) { + next_.pop(); + } + next_.push(start_); +} + auto CreateSourceFromResults( std::weak_ptr db, std::shared_ptr> results)