From 7f630cebddcf6d0b8a31632af7ed617f4173a6e1 Mon Sep 17 00:00:00 2001 From: ailurux Date: Fri, 19 Apr 2024 02:06:37 +0000 Subject: [PATCH] daniel/recycler-list (#66) @cooljqln should be good to merge to main, give it a look over though please? :) Reviewed-on: https://codeberg.org/cool-tech-zone/tangara-fw/pulls/66 Co-authored-by: ailurux Co-committed-by: ailurux --- lib/luavgl/src/obj.c | 9 ++ lua/browser.lua | 71 ++++--------- lua/widgets.lua | 217 +++++++++++++++++++++++++++++++-------- src/lua/lua_database.cpp | 14 +++ 4 files changed, 215 insertions(+), 96 deletions(-) diff --git a/lib/luavgl/src/obj.c b/lib/luavgl/src/obj.c index 4b108891..fb89e3f9 100644 --- a/lib/luavgl/src/obj.c +++ b/lib/luavgl/src/obj.c @@ -595,6 +595,14 @@ static int luavgl_obj_focus(lua_State *L) { return 0; } +static int luavgl_obj_move_to_index(lua_State *L) { + lv_obj_t *obj = luavgl_to_obj(L, 1); + int idx = luavgl_tointeger(L, 2); + + lv_obj_move_to_index(obj, idx); + return 0; +} + /** * Remove all animations associates to this object */ @@ -672,6 +680,7 @@ static const luaL_Reg luavgl_obj_methods[] = { {"get_coords", luavgl_obj_get_coords}, {"get_pos", luavgl_obj_get_pos}, {"focus", luavgl_obj_focus}, + {"move_to_index", luavgl_obj_move_to_index}, {"onevent", luavgl_obj_on_event}, {"onPressed", luavgl_obj_on_pressed}, diff --git a/lua/browser.lua b/lua/browser.lua index 926fac64..96ebbcab 100644 --- a/lua/browser.lua +++ b/lua/browser.lua @@ -78,62 +78,27 @@ return screen:new{ queue.clear() queue.add(original_iterator) playback.playing:set(true) - backstack.push(playing) + backstack.push(playing:new()) end) - self.list = lvgl.List(self.root, { - w = lvgl.PCT(100), - h = lvgl.PCT(100), - flex_grow = 1, - scrollbar_mode = lvgl.SCROLLBAR_MODE.OFF - }) - - -- local back = self.list:add_btn(nil, "< Back") - -- back:onClicked(backstack.pop) - -- back:add_style(styles.list_item) - - self.focused_item = 0 - self.last_item = 0 - self.add_item = function(item) - if not item then - return - end - self.last_item = self.last_item + 1 - local this_item = self.last_item - local btn = self.list:add_btn(nil, tostring(item)) - btn:onClicked(function() - local contents = item:contents() - if type(contents) == "userdata" then - backstack.push(require("browser"):new{ - title = self.title, - iterator = contents, - breadcrumb = tostring(item) - }) - else - queue.clear() - queue.add(contents) - playback.playing:set(true) - backstack.push(playing:new()) + local recycle_list = widgets.RecyclerList(self.root, self.iterator, { + callback = function(item) + return function() + local contents = item:contents() + if type(contents) == "userdata" then + backstack.push(require("browser"):new{ + title = self.title, + iterator = contents, + breadcrumb = tostring(item) + }) + else + queue.clear() + queue.add(contents) + playback.playing:set(true) + backstack.push(playing:new()) + end end - end) - btn:onevent(lvgl.EVENT.FOCUSED, function() - self.focused_item = this_item - if self.last_item - 5 < this_item then - self.add_item(self.iterator()) - end - end) - btn:add_style(styles.list_item) - if this_item == 1 then - btn:focus() - end - end - - for _ = 1, 8 do - local val = self.iterator() - if not val then - break end - self.add_item(val) - end + }) end } diff --git a/lua/widgets.lua b/lua/widgets.lua index be018e19..4d7ff077 100644 --- a/lua/widgets.lua +++ b/lua/widgets.lua @@ -1,4 +1,5 @@ local lvgl = require("lvgl") + local power = require("power") local bluetooth = require("bluetooth") local font = require("font") @@ -9,17 +10,17 @@ local theme = require("theme") local screen = require("screen") local img = { - db = lvgl.ImgData("//lua/img/db.png"), - chg = lvgl.ImgData("//lua/img/bat/chg.png"), - bat_100 = lvgl.ImgData("//lua/img/bat/100.png"), - bat_80 = lvgl.ImgData("//lua/img/bat/80.png"), - bat_60 = lvgl.ImgData("//lua/img/bat/60.png"), - bat_40 = lvgl.ImgData("//lua/img/bat/40.png"), - bat_20 = lvgl.ImgData("//lua/img/bat/20.png"), - bat_0 = lvgl.ImgData("//lua/img/bat/0.png"), - bat_0chg = lvgl.ImgData("//lua/img/bat/0chg.png"), - bt_conn = lvgl.ImgData("//lua/assets/bt_conn.png"), - bt = lvgl.ImgData("//lua/assets/bt.png") + db = lvgl.ImgData("//lua/img/db.png"), + chg = lvgl.ImgData("//lua/img/bat/chg.png"), + bat_100 = lvgl.ImgData("//lua/img/bat/100.png"), + bat_80 = lvgl.ImgData("//lua/img/bat/80.png"), + bat_60 = lvgl.ImgData("//lua/img/bat/60.png"), + bat_40 = lvgl.ImgData("//lua/img/bat/40.png"), + bat_20 = lvgl.ImgData("//lua/img/bat/20.png"), + bat_0 = lvgl.ImgData("//lua/img/bat/0.png"), + bat_0chg = lvgl.ImgData("//lua/img/bat/0chg.png"), + bt_conn = lvgl.ImgData("//lua/assets/bt_conn.png"), + bt = lvgl.ImgData("//lua/assets/bt.png") } local widgets = {} @@ -49,19 +50,24 @@ widgets.MenuScreen = screen:new { } function widgets.Row(parent, left, right) - local container = parent:Object { - flex = { - flex_direction = "row", - justify_content = "flex-start", - align_items = "flex-start", - align_content = "flex-start", - }, - w = lvgl.PCT(100), - h = lvgl.SIZE_CONTENT, - } - container:add_style(styles.list_item) - container:Label { text = left, flex_grow = 1 } - container:Label { text = right } + local container = parent:Object{ + flex = { + flex_direction = "row", + justify_content = "flex-start", + align_items = "flex-start", + align_content = "flex-start" + }, + w = lvgl.PCT(100), + h = lvgl.SIZE_CONTENT + } + container:add_style(styles.list_item) + container:Label{ + text = left, + flex_grow = 1 + } + container:Label{ + text = right + } end local bindings_meta = { @@ -119,8 +125,8 @@ function widgets.StatusBar(parent, opts) local charge_icon = battery_icon:Image { src = img.chg } charge_icon:center() - local is_charging = nil - local percent = nil + local is_charging = nil + local percent = nil local function update_battery_icon() if is_charging == nil or percent == nil then return end @@ -185,23 +191,148 @@ function widgets.StatusBar(parent, opts) end function widgets.IconBtn(parent, icon, text) - local btn = parent:Button { - flex = { - flex_direction = "row", - justify_content = "flex-start", - align_items = "center", - align_content = "center", - }, - w = lvgl.SIZE_CONTENT, - h = lvgl.SIZE_CONTENT, - pad_top = 1, - pad_bottom = 1, - pad_left = 1, - pad_column = 1, - } - btn:Image { src = icon } - btn:Label { text = text, text_font = font.fusion_10 } - return btn + local btn = parent:Button{ + flex = { + flex_direction = "row", + justify_content = "flex-start", + align_items = "center", + align_content = "center" + }, + w = lvgl.SIZE_CONTENT, + h = lvgl.SIZE_CONTENT, + pad_top = 1, + pad_bottom = 1, + pad_left = 1, + pad_column = 1 + } + btn:Image{ + src = icon + } + btn:Label{ + text = text, + text_font = font.fusion_10 + } + return btn +end + +function widgets.RecyclerList(parent, iterator, opts) + local recycler_list = {} + + recycler_list.root = lvgl.List(parent, { + w = lvgl.PCT(100), + h = lvgl.PCT(100), + flex_grow = 1, + scrollbar_mode = lvgl.SCROLLBAR_MODE.OFF + }) + + local refreshing = false -- Used so that we can ignore focus events during this phase + local function refresh_group() + refreshing = true + local group = lvgl.group.get_default() + local focused_obj = group:get_focused() + local num_children = recycler_list.root:get_child_cnt() + -- remove all children from the group and re-add them + for i = 0, num_children-1 do + lvgl.group.remove_obj(recycler_list.root:get_child(i)) + end + for i = 0, num_children-1 do + group:add_obj(recycler_list.root:get_child(i)) + end + if (focused_obj) then + lvgl.group.focus_obj(focused_obj) + end + refreshing = false + end + + local fwd_iterator = iterator:clone() + local bck_iterator = iterator:clone() + + local last_selected = 0 + local last_index = 0 + local first_index = 0 + + local function remove_top() + local obj = recycler_list.root:get_child(0) + obj:delete() + first_index = first_index + 1 + bck_iterator:next() + end + + local function remove_last() + local obj = recycler_list.root:get_child(-1) + obj:delete() + last_index = last_index - 1 + fwd_iterator:prev() + end + + local moving_back = false + + local function add_item(item, index) + if not item then + return + end + local this_item = index + local add_to_top = false + if this_item < first_index then + first_index = this_item + add_to_top = true + end + if this_item > last_index then last_index = index end + local btn = recycler_list.root:add_btn(nil, tostring(item)) + if add_to_top then + btn:move_to_index(0) + 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)) + end + btn:onevent(lvgl.EVENT.FOCUSED, function() + if refreshing then return end + selected = this_item + if this_item > last_selected and this_item > 3 then + -- moving forward + if moving_back == true then + fwd_iterator:next() + moving_back = false + end + local to_add = fwd_iterator:next() + if to_add then + remove_top() + add_item(to_add, last_index+1) + end + end + if this_item < last_selected then + -- moving backward + if last_index - this_item > 3 then + if moving_back == false then + -- Special case for the element we switch on + bck_iterator:prev() + moving_back = true + end + if (last_index > 10) then + remove_last() + end + if (first_index > 0) then + add_item(bck_iterator:prev(), first_index-1) + end + end + end + last_selected = this_item + refresh_group() + end) + btn:add_style(styles.list_item) + return btn + end + + for idx = 0, 8 do + local val = fwd_iterator() + if not val then + break + end + add_item(val, idx) + end + + return recycler_list end return widgets diff --git a/src/lua/lua_database.cpp b/src/lua/lua_database.cpp index 57cefbbc..d0612fdd 100644 --- a/src/lua/lua_database.cpp +++ b/src/lua/lua_database.cpp @@ -160,6 +160,19 @@ static auto push_iterator(lua_State* state, const database::Iterator& it) luaL_setmetatable(state, kDbIteratorMetatable); } +static auto db_iterate_prev(lua_State* state) -> int { + database::Iterator* it = db_check_iterator(state, 1); + std::optional res = (*it)--; + + if (res) { + push_lua_record(state, *res); + } else { + lua_pushnil(state); + } + + return 1; +} + static auto db_iterate(lua_State* state) -> int { database::Iterator* it = db_check_iterator(state, 1); std::optional res = (*it)++; @@ -186,6 +199,7 @@ static auto db_iterator_gc(lua_State* state) -> int { } static const struct luaL_Reg kDbIteratorFuncs[] = {{"next", db_iterate}, + {"prev", db_iterate_prev}, {"clone", db_iterator_clone}, {"__call", db_iterate}, {"__gc", db_iterator_gc},