summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorjacqueline <me@jacqueline.id.au>2023-11-24 15:13:10 +1100
committerjacqueline <me@jacqueline.id.au>2023-11-24 15:13:10 +1100
commit7c6eb2997cbba350e7384151e13659271508e08f (patch)
treeb6f95a4843521e69b24cbf4c126d84442d19fc23
parent230721cd6271f3239b42e1d2471f8db15bebd712 (diff)
downloadtangara-fw-7c6eb2997cbba350e7384151e13659271508e08f.tar.gz
Migrate 'now playing' screen to lua
-rw-r--r--lib/luavgl/src/lvgl.lua38
-rw-r--r--lib/luavgl/src/obj.c276
-rw-r--r--lib/luavgl/src/widgets/bar.c87
-rw-r--r--lib/luavgl/src/widgets/widgets.c12
-rw-r--r--lua/browser.lua5
-rw-r--r--lua/img/next.pngbin0 -> 621 bytes
-rw-r--r--lua/img/pause.pngbin0 -> 581 bytes
-rw-r--r--lua/img/play.pngbin0 -> 617 bytes
-rw-r--r--lua/img/prev.pngbin0 -> 626 bytes
-rw-r--r--lua/main_menu.lua3
-rw-r--r--lua/playing.lua130
-rw-r--r--lua/widgets.lua18
-rw-r--r--src/audio/CMakeLists.txt1
-rw-r--r--src/audio/audio_decoder.cpp26
-rw-r--r--src/audio/audio_fsm.cpp2
-rw-r--r--src/audio/audio_source.cpp41
-rw-r--r--src/audio/fatfs_audio_input.cpp5
-rw-r--r--src/audio/include/audio_decoder.hpp7
-rw-r--r--src/audio/include/audio_events.hpp13
-rw-r--r--src/audio/include/audio_source.hpp27
-rw-r--r--src/audio/include/fatfs_audio_input.hpp4
-rw-r--r--src/audio/include/track_queue.hpp3
-rw-r--r--src/audio/track_queue.cpp10
-rw-r--r--src/codecs/codec.cpp17
-rw-r--r--src/codecs/include/types.hpp2
-rw-r--r--src/database/include/track.hpp2
-rw-r--r--src/database/track.cpp20
-rw-r--r--src/lua/include/property.hpp4
-rw-r--r--src/lua/property.cpp34
-rw-r--r--src/lua/stubs/playback.lua4
-rw-r--r--src/ui/CMakeLists.txt12
-rw-r--r--src/ui/include/modal_add_to_queue.hpp41
-rw-r--r--src/ui/include/screen_onboarding.hpp63
-rw-r--r--src/ui/include/screen_playing.hpp73
-rw-r--r--src/ui/include/screen_track_browser.hpp74
-rw-r--r--src/ui/include/ui_fsm.hpp43
-rw-r--r--src/ui/modal_add_to_queue.cpp182
-rw-r--r--src/ui/screen_lua.cpp4
-rw-r--r--src/ui/screen_onboarding.cpp146
-rw-r--r--src/ui/screen_playing.cpp338
-rw-r--r--src/ui/screen_track_browser.cpp431
-rw-r--r--src/ui/ui_fsm.cpp185
-rw-r--r--src/ui/widget_top_bar.cpp50
43 files changed, 603 insertions, 1830 deletions
diff --git a/lib/luavgl/src/lvgl.lua b/lib/luavgl/src/lvgl.lua
index f30335cb..f4505a67 100644
--- a/lib/luavgl/src/lvgl.lua
+++ b/lib/luavgl/src/lvgl.lua
@@ -335,6 +335,13 @@ end
function lvgl.Object(parent, property)
end
+--- Create Bar widget on parent
+--- @param parent? Object | nil
+--- @param property? BarProp
+--- @return Bar
+function lvgl.Bar(parent, property)
+end
+
--- Create Button widget on parent
--- @param parent? Object | nil
--- @param property? StyleProp
@@ -472,6 +479,13 @@ function obj:Object(property)
end
---
+--- Create bar on object
+--- @param property? BarStyle
+--- @return Bar
+function obj:Bar(property)
+end
+
+---
--- Create button on object
--- @param property? ButtonStyle
--- @return Button
@@ -867,6 +881,18 @@ function calendar:Dropdown(p)
end
---
+--- Bar widget
+---@class Bar:Object
+---
+local bar = {}
+
+--- set method for bar widget
+--- @param p BarStyle
+--- @return nil
+function bar:set(p)
+end
+
+---
--- Button widget
---@class Button:Object
---
@@ -1375,6 +1401,11 @@ end
--- @class LabelStyle :StyleProp
--- @field text string
+--- Bar style
+--- @class BarStyle :StyleProp
+--- @field range BarRangePara
+--- @field value integer
+
--- Button style
--- @class ButtonStyle :StyleProp
@@ -1473,6 +1504,13 @@ end
---
+--- BarRange para
+--- @class BarRangePara
+--- @field min integer
+--- @field max integer
+---
+
+---
--- CalendarToday para
--- @class CalendarDatePara
--- @field year integer
diff --git a/lib/luavgl/src/obj.c b/lib/luavgl/src/obj.c
index a316f59c..eccfd753 100644
--- a/lib/luavgl/src/obj.c
+++ b/lib/luavgl/src/obj.c
@@ -9,8 +9,7 @@
static int luavgl_anim_create(lua_State *L);
static int luavgl_obj_delete(lua_State *L);
-static void _lv_obj_set_align(void *obj, lua_State *L)
-{
+static void _lv_obj_set_align(void *obj, lua_State *L) {
if (lua_isinteger(L, -1)) {
lv_obj_align(obj, lua_tointeger(L, -1), 0, 0);
return;
@@ -42,13 +41,11 @@ static void _lv_obj_set_align(void *obj, lua_State *L)
*
* Internally used.
*/
-static inline void luavgl_setup_obj(lua_State *L, lv_obj_t *obj)
-{
+static inline void luavgl_setup_obj(lua_State *L, lv_obj_t *obj) {
luavgl_iterate(L, -1, luavgl_obj_set_property_kv, obj);
}
-static void obj_delete_cb(lv_event_t *e)
-{
+static void obj_delete_cb(lv_event_t *e) {
lua_State *L = e->user_data;
lua_pushlightuserdata(L, e->current_target);
lua_rawget(L, LUA_REGISTRYINDEX);
@@ -57,8 +54,16 @@ static void obj_delete_cb(lv_event_t *e)
}
luavgl_obj_t *lobj = luavgl_to_lobj(L, -1);
- if (lobj->lua_created)
+ if (lobj->lua_created) {
+ // The underlying object is now gone, so don't keep a reference to it.
+ lobj->obj = NULL;
+ // Ensure there's no dangling reference in the registry either.
+ lua_pushlightuserdata(L, e->current_target);
+ lua_pushnil(L);
+ lua_rawset(L, LUA_REGISTRYINDEX);
+
goto pop_exit;
+ }
luavgl_obj_delete(L);
return;
@@ -73,8 +78,7 @@ pop_exit:
* one. result stack: table(from uservalue)
* return uservalue type: LUA_TTABLE
*/
-LUALIB_API int luavgl_obj_getuserdatauv(lua_State *L, int idx)
-{
+LUALIB_API int luavgl_obj_getuserdatauv(lua_State *L, int idx) {
int type = lua_getuservalue(L, idx);
if (type == LUA_TTABLE)
return type;
@@ -93,13 +97,11 @@ LUALIB_API int luavgl_obj_getuserdatauv(lua_State *L, int idx)
return LUA_TTABLE;
}
-static int luavgl_obj_create(lua_State *L)
-{
+static int luavgl_obj_create(lua_State *L) {
return luavgl_obj_create_helper(L, lv_obj_create);
}
-static int luavgl_obj_delete(lua_State *L)
-{
+static int luavgl_obj_delete(lua_State *L) {
luavgl_obj_t *lobj;
/**
@@ -149,8 +151,7 @@ static int luavgl_obj_delete(lua_State *L)
return 0;
}
-static int luavgl_obj_clean(lua_State *L)
-{
+static int luavgl_obj_clean(lua_State *L) {
luavgl_obj_t *lobj = luavgl_to_lobj(L, -1);
if (lobj == NULL || lobj->obj == NULL)
return 0;
@@ -171,8 +172,7 @@ static int luavgl_obj_clean(lua_State *L)
return 0;
}
-static int luavgl_obj_set(lua_State *L)
-{
+static int luavgl_obj_set(lua_State *L) {
lv_obj_t *obj = luavgl_to_obj(L, 1);
if (!lua_istable(L, -1)) {
@@ -187,8 +187,7 @@ static int luavgl_obj_set(lua_State *L)
/**
* obj:align_to({base=base, type=type, x_ofs=0, y_ofs=0})
*/
-static int luavgl_obj_align_to(lua_State *L)
-{
+static int luavgl_obj_align_to(lua_State *L) {
lv_obj_t *obj = luavgl_to_obj(L, 1);
if (!lua_istable(L, 2)) {
@@ -220,16 +219,14 @@ static int luavgl_obj_align_to(lua_State *L)
return 0;
}
-static int luavgl_obj_set_parent(lua_State *L)
-{
+static int luavgl_obj_set_parent(lua_State *L) {
lv_obj_t *obj = luavgl_to_obj(L, 1);
lv_obj_t *parent = luavgl_to_obj(L, 2);
lv_obj_set_parent(obj, parent);
return 0;
}
-static int luavgl_obj_get_screen(lua_State *L)
-{
+static int luavgl_obj_get_screen(lua_State *L) {
lv_obj_t *obj = luavgl_to_obj(L, 1);
lv_obj_t *screen = lv_obj_get_screen(obj);
@@ -247,8 +244,7 @@ static int luavgl_obj_get_screen(lua_State *L)
return 1;
}
-static int luavgl_obj_get_parent(lua_State *L)
-{
+static int luavgl_obj_get_parent(lua_State *L) {
lv_obj_t *obj = luavgl_to_obj(L, 1);
lv_obj_t *parent = lv_obj_get_parent(obj);
@@ -265,8 +261,7 @@ static int luavgl_obj_get_parent(lua_State *L)
return 1;
}
-static int luavgl_obj_set_get_parent(lua_State *L)
-{
+static int luavgl_obj_set_get_parent(lua_State *L) {
lv_obj_t *obj = luavgl_to_obj(L, 1);
if (!lua_isnoneornil(L, 2)) {
lv_obj_t *parent = luavgl_to_obj(L, 2);
@@ -276,8 +271,7 @@ static int luavgl_obj_set_get_parent(lua_State *L)
return luavgl_obj_get_parent(L);
}
-static int luavgl_obj_get_child(lua_State *L)
-{
+static int luavgl_obj_get_child(lua_State *L) {
lv_obj_t *obj = luavgl_to_obj(L, 1);
int id = luavgl_tointeger(L, 2);
@@ -297,15 +291,13 @@ static int luavgl_obj_get_child(lua_State *L)
return 1;
}
-static int luavgl_obj_get_child_cnt(lua_State *L)
-{
+static int luavgl_obj_get_child_cnt(lua_State *L) {
lv_obj_t *obj = luavgl_to_obj(L, 1);
lua_pushinteger(L, lv_obj_get_child_cnt(obj));
return 1;
}
-static int luavgl_obj_get_state(lua_State *L)
-{
+static int luavgl_obj_get_state(lua_State *L) {
lv_obj_t *obj = luavgl_to_obj(L, 1);
lv_state_t state = lv_obj_get_state(obj);
lua_pushinteger(L, state);
@@ -318,8 +310,7 @@ static int luavgl_obj_get_state(lua_State *L)
* obj:scroll_to({x=10, anim=true})
* obj:scroll_to({x=10, y=100, anim=false})
*/
-static int luavgl_obj_scroll_to(lua_State *L)
-{
+static int luavgl_obj_scroll_to(lua_State *L) {
lv_obj_t *obj = luavgl_to_obj(L, 1);
if (!lua_istable(L, -1)) {
@@ -348,16 +339,14 @@ static int luavgl_obj_scroll_to(lua_State *L)
return 0;
}
-static int luavgl_obj_is_visible(lua_State *L)
-{
+static int luavgl_obj_is_visible(lua_State *L) {
lv_obj_t *obj = luavgl_to_obj(L, 1);
lua_pushboolean(L, lv_obj_is_visible(obj));
return 1;
}
-static int luavgl_obj_add_flag(lua_State *L)
-{
+static int luavgl_obj_add_flag(lua_State *L) {
lv_obj_t *obj = luavgl_to_obj(L, 1);
lv_obj_flag_t flag = lua_tointeger(L, 2);
lv_obj_add_flag(obj, flag);
@@ -365,8 +354,7 @@ static int luavgl_obj_add_flag(lua_State *L)
return 0;
}
-static int luavgl_obj_clear_flag(lua_State *L)
-{
+static int luavgl_obj_clear_flag(lua_State *L) {
lv_obj_t *obj = luavgl_to_obj(L, 1);
lv_obj_flag_t flag = lua_tointeger(L, 2);
lv_obj_clear_flag(obj, flag);
@@ -374,16 +362,14 @@ static int luavgl_obj_clear_flag(lua_State *L)
return 0;
}
-static int luavgl_obj_add_state(lua_State *L)
-{
+static int luavgl_obj_add_state(lua_State *L) {
lv_obj_t *obj = luavgl_to_obj(L, 1);
lv_state_t state = lua_tointeger(L, 2);
lv_obj_add_state(obj, state);
return 0;
}
-static int luavgl_obj_clear_state(lua_State *L)
-{
+static int luavgl_obj_clear_state(lua_State *L) {
lv_obj_t *obj = luavgl_to_obj(L, 1);
lv_state_t state = lua_tointeger(L, 2);
lv_obj_clear_state(obj, state);
@@ -393,8 +379,7 @@ static int luavgl_obj_clear_state(lua_State *L)
/**
* obj:scroll_by(x, y, anim_en)
*/
-static int luavgl_obj_scroll_by(lua_State *L)
-{
+static int luavgl_obj_scroll_by(lua_State *L) {
lv_obj_t *obj = luavgl_to_obj(L, 1);
int x = luavgl_tointeger(L, 2);
int y = luavgl_tointeger(L, 3);
@@ -404,8 +389,7 @@ static int luavgl_obj_scroll_by(lua_State *L)
return 0;
}
-static int luavgl_obj_scroll_by_bounded(lua_State *L)
-{
+static int luavgl_obj_scroll_by_bounded(lua_State *L) {
lv_obj_t *obj = luavgl_to_obj(L, 1);
int dx = luavgl_tointeger(L, 2);
int dy = luavgl_tointeger(L, 3);
@@ -415,8 +399,7 @@ static int luavgl_obj_scroll_by_bounded(lua_State *L)
return 0;
}
-static int luavgl_obj_scroll_to_view(lua_State *L)
-{
+static int luavgl_obj_scroll_to_view(lua_State *L) {
lv_obj_t *obj = luavgl_to_obj(L, 1);
int anim_en = luavgl_tointeger(L, 2);
@@ -424,8 +407,7 @@ static int luavgl_obj_scroll_to_view(lua_State *L)
return 0;
}
-static int luavgl_obj_scroll_to_view_recursive(lua_State *L)
-{
+static int luavgl_obj_scroll_to_view_recursive(lua_State *L) {
lv_obj_t *obj = luavgl_to_obj(L, 1);
int anim_en = luavgl_tointeger(L, 2);
@@ -433,8 +415,7 @@ static int luavgl_obj_scroll_to_view_recursive(lua_State *L)
return 0;
}
-static int luavgl_obj_scroll_by_raw(lua_State *L)
-{
+static int luavgl_obj_scroll_by_raw(lua_State *L) {
lv_obj_t *obj = luavgl_to_obj(L, 1);
int x = luavgl_tointeger(L, 2);
int y = luavgl_tointeger(L, 3);
@@ -443,72 +424,62 @@ static int luavgl_obj_scroll_by_raw(lua_State *L)
return 0;
}
-static int luavgl_obj_is_scrolling(lua_State *L)
-{
+static int luavgl_obj_is_scrolling(lua_State *L) {
lv_obj_t *obj = luavgl_to_obj(L, 1);
lua_pushboolean(L, lv_obj_is_scrolling(obj));
return 1;
}
-static int luavgl_obj_scrollbar_invalidate(lua_State *L)
-{
+static int luavgl_obj_scrollbar_invalidate(lua_State *L) {
lv_obj_t *obj = luavgl_to_obj(L, 1);
lv_obj_scrollbar_invalidate(obj);
return 0;
}
-static int luavgl_obj_readjust_scroll(lua_State *L)
-{
+static int luavgl_obj_readjust_scroll(lua_State *L) {
lv_obj_t *obj = luavgl_to_obj(L, 1);
int anim_en = luavgl_tointeger(L, 2);
lv_obj_readjust_scroll(obj, anim_en);
return 0;
}
-static int luavgl_obj_is_editable(lua_State *L)
-{
+static int luavgl_obj_is_editable(lua_State *L) {
lv_obj_t *obj = luavgl_to_obj(L, 1);
lua_pushboolean(L, lv_obj_is_editable(obj));
return 1;
}
-static int luavgl_obj_is_group_def(lua_State *L)
-{
+static int luavgl_obj_is_group_def(lua_State *L) {
lv_obj_t *obj = luavgl_to_obj(L, 1);
lua_pushboolean(L, lv_obj_is_group_def(obj));
return 1;
}
-static int luavgl_obj_is_layout_positioned(lua_State *L)
-{
+static int luavgl_obj_is_layout_positioned(lua_State *L) {
lv_obj_t *obj = luavgl_to_obj(L, 1);
lua_pushboolean(L, lv_obj_is_layout_positioned(obj));
return 1;
}
-static int luavgl_obj_mark_layout_as_dirty(lua_State *L)
-{
+static int luavgl_obj_mark_layout_as_dirty(lua_State *L) {
lv_obj_t *obj = luavgl_to_obj(L, 1);
lv_obj_mark_layout_as_dirty(obj);
return 0;
}
-static int luavgl_obj_center(lua_State *L)
-{
+static int luavgl_obj_center(lua_State *L) {
lv_obj_t *obj = luavgl_to_obj(L, 1);
lv_obj_center(obj);
return 0;
}
-static int luavgl_obj_invalidate(lua_State *L)
-{
+static int luavgl_obj_invalidate(lua_State *L) {
lv_obj_t *obj = luavgl_to_obj(L, 1);
lv_obj_invalidate(obj);
return 0;
}
-static int luavgl_obj_set_flex_flow(lua_State *L)
-{
+static int luavgl_obj_set_flex_flow(lua_State *L) {
lv_obj_t *obj = luavgl_to_obj(L, 1);
lv_flex_flow_t flow = luavgl_tointeger(L, 2);
@@ -516,8 +487,7 @@ static int luavgl_obj_set_flex_flow(lua_State *L)
return 0;
}
-static int luavgl_obj_set_flex_align(lua_State *L)
-{
+static int luavgl_obj_set_flex_align(lua_State *L) {
lv_obj_t *obj = luavgl_to_obj(L, 1);
lv_flex_align_t m = luavgl_tointeger(L, 2);
lv_flex_align_t c = luavgl_tointeger(L, 3);
@@ -527,8 +497,7 @@ static int luavgl_obj_set_flex_align(lua_State *L)
return 0;
}
-static int luavgl_obj_set_flex_grow(lua_State *L)
-{
+static int luavgl_obj_set_flex_grow(lua_State *L) {
lv_obj_t *obj = luavgl_to_obj(L, 1);
uint8_t grow = luavgl_tointeger(L, 2);
@@ -536,8 +505,7 @@ static int luavgl_obj_set_flex_grow(lua_State *L)
return 0;
}
-static int luavgl_obj_indev_search(lua_State *L)
-{
+static int luavgl_obj_indev_search(lua_State *L) {
lv_obj_t *obj = luavgl_to_obj(L, 1);
lv_point_t point;
if (lua_istable(L, 2)) {
@@ -567,8 +535,7 @@ static int luavgl_obj_indev_search(lua_State *L)
return 1;
}
-static int luavgl_obj_get_coords(lua_State *L)
-{
+static int luavgl_obj_get_coords(lua_State *L) {
lv_area_t area;
lv_obj_t *obj = luavgl_to_obj(L, 1);
lv_obj_get_coords(obj, &area);
@@ -592,8 +559,7 @@ static int luavgl_obj_get_coords(lua_State *L)
/**
* get object real position using lv_obj_get_x/x2/y/y2
*/
-static int luavgl_obj_get_pos(lua_State *L)
-{
+static int luavgl_obj_get_pos(lua_State *L) {
lv_obj_t *obj = luavgl_to_obj(L, 1);
lua_newtable(L);
@@ -615,15 +581,13 @@ static int luavgl_obj_get_pos(lua_State *L)
/**
* Remove all animations associates to this object
*/
-static int luavgl_obj_remove_anim_all(lua_State *L)
-{
+static int luavgl_obj_remove_anim_all(lua_State *L) {
lv_obj_t *obj = luavgl_to_obj(L, 1);
lv_anim_del(obj, NULL);
return 1;
}
-static int luavgl_obj_gc(lua_State *L)
-{
+static int luavgl_obj_gc(lua_State *L) {
if (lua_type(L, 1) != LUA_TUSERDATA) {
/* If t = setmetatable({}, obj_meta_table), this will happen when t is
* gc;ed. Currently all metatables for classes based on obj, that has no own
@@ -647,62 +611,61 @@ static int luavgl_obj_gc(lua_State *L)
}
static const luaL_Reg luavgl_obj_methods[] = {
- {"set", luavgl_obj_set },
- {"set_style", luavgl_obj_set_style },
- {"align_to", luavgl_obj_align_to },
- {"delete", luavgl_obj_delete },
- {"clean", luavgl_obj_clean },
-
- /* misc. functions */
- {"parent", luavgl_obj_set_get_parent },
- {"set_parent", luavgl_obj_set_parent },
- {"get_parent", luavgl_obj_get_parent },
- {"get_child", luavgl_obj_get_child },
- {"get_child_cnt", luavgl_obj_get_child_cnt },
- {"get_screen", luavgl_obj_get_screen },
- {"get_state", luavgl_obj_get_state },
- {"scroll_to", luavgl_obj_scroll_to },
- {"is_scrolling", luavgl_obj_is_scrolling },
- {"is_visible", luavgl_obj_is_visible },
- {"add_flag", luavgl_obj_add_flag },
- {"clear_flag", luavgl_obj_clear_flag },
- {"add_state", luavgl_obj_add_state },
- {"clear_state", luavgl_obj_clear_state },
- {"add_style", luavgl_obj_add_style },
- {"remove_style", luavgl_obj_remove_style },
- {"remove_style_all", luavgl_obj_remove_style_all },
- {"scroll_by", luavgl_obj_scroll_by },
- {"scroll_by_bounded", luavgl_obj_scroll_by_bounded },
- {"scroll_to_view", luavgl_obj_scroll_to_view },
+ {"set", luavgl_obj_set},
+ {"set_style", luavgl_obj_set_style},
+ {"align_to", luavgl_obj_align_to},
+ {"delete", luavgl_obj_delete},
+ {"clean", luavgl_obj_clean},
+
+ /* misc. functions */
+ {"parent", luavgl_obj_set_get_parent},
+ {"set_parent", luavgl_obj_set_parent},
+ {"get_parent", luavgl_obj_get_parent},
+ {"get_child", luavgl_obj_get_child},
+ {"get_child_cnt", luavgl_obj_get_child_cnt},
+ {"get_screen", luavgl_obj_get_screen},
+ {"get_state", luavgl_obj_get_state},
+ {"scroll_to", luavgl_obj_scroll_to},
+ {"is_scrolling", luavgl_obj_is_scrolling},
+ {"is_visible", luavgl_obj_is_visible},
+ {"add_flag", luavgl_obj_add_flag},
+ {"clear_flag", luavgl_obj_clear_flag},
+ {"add_state", luavgl_obj_add_state},
+ {"clear_state", luavgl_obj_clear_state},
+ {"add_style", luavgl_obj_add_style},
+ {"remove_style", luavgl_obj_remove_style},
+ {"remove_style_all", luavgl_obj_remove_style_all},
+ {"scroll_by", luavgl_obj_scroll_by},
+ {"scroll_by_bounded", luavgl_obj_scroll_by_bounded},
+ {"scroll_to_view", luavgl_obj_scroll_to_view},
{"scroll_to_view_recursive", luavgl_obj_scroll_to_view_recursive},
- {"scroll_by_raw", luavgl_obj_scroll_by_raw },
- {"scrollbar_invalidate", luavgl_obj_scrollbar_invalidate },
- {"readjust_scroll", luavgl_obj_readjust_scroll },
- {"is_editable", luavgl_obj_is_editable },
- {"is_group_def", luavgl_obj_is_group_def },
- {"is_layout_positioned", luavgl_obj_is_layout_positioned },
- {"mark_layout_as_dirty", luavgl_obj_mark_layout_as_dirty },
- {"center", luavgl_obj_center },
- {"invalidate", luavgl_obj_invalidate },
- {"set_flex_flow", luavgl_obj_set_flex_flow },
- {"set_flex_align", luavgl_obj_set_flex_align },
- {"set_flex_grow", luavgl_obj_set_flex_grow },
- {"indev_search", luavgl_obj_indev_search },
- {"get_coords", luavgl_obj_get_coords },
- {"get_pos", luavgl_obj_get_pos },
-
- {"onevent", luavgl_obj_on_event },
- {"onPressed", luavgl_obj_on_pressed },
- {"onClicked", luavgl_obj_on_clicked },
- {"onShortClicked", luavgl_obj_on_short_clicked },
- {"anim", luavgl_anim_create },
- {"Anim", luavgl_anim_create },
- {"remove_all_anim", luavgl_obj_remove_anim_all }, /* remove all */
- {NULL, NULL },
+ {"scroll_by_raw", luavgl_obj_scroll_by_raw},
+ {"scrollbar_invalidate", luavgl_obj_scrollbar_invalidate},
+ {"readjust_scroll", luavgl_obj_readjust_scroll},
+ {"is_editable", luavgl_obj_is_editable},
+ {"is_group_def", luavgl_obj_is_group_def},
+ {"is_layout_positioned", luavgl_obj_is_layout_positioned},
+ {"mark_layout_as_dirty", luavgl_obj_mark_layout_as_dirty},
+ {"center", luavgl_obj_center},
+ {"invalidate", luavgl_obj_invalidate},
+ {"set_flex_flow", luavgl_obj_set_flex_flow},
+ {"set_flex_align", luavgl_obj_set_flex_align},
+ {"set_flex_grow", luavgl_obj_set_flex_grow},
+ {"indev_search", luavgl_obj_indev_search},
+ {"get_coords", luavgl_obj_get_coords},
+ {"get_pos", luavgl_obj_get_pos},
+
+ {"onevent", luavgl_obj_on_event},
+ {"onPressed", luavgl_obj_on_pressed},
+ {"onClicked", luavgl_obj_on_clicked},
+ {"onShortClicked", luavgl_obj_on_short_clicked},
+ {"anim", luavgl_anim_create},
+ {"Anim", luavgl_anim_create},
+ {"remove_all_anim", luavgl_obj_remove_anim_all}, /* remove all */
+ {NULL, NULL},
};
-static void luavgl_obj_init(lua_State *L)
-{
+static void luavgl_obj_init(lua_State *L) {
/* base lv_obj */
luavgl_obj_newmetatable(L, &lv_obj_class, "lv_obj", luavgl_obj_methods);
lua_pushcfunction(L, luavgl_obj_gc);
@@ -728,16 +691,16 @@ static void luavgl_obj_init(lua_State *L)
}
static const luavgl_value_setter_t obj_property_table[] = {
- {"x", 0, {.setter = (setter_int_t)lv_obj_set_x} },
- {"y", 0, {.setter = (setter_int_t)lv_obj_set_y} },
- {"w", 0, {.setter = (setter_int_t)lv_obj_set_width} },
- {"h", 0, {.setter = (setter_int_t)lv_obj_set_height} },
- {"align", SETTER_TYPE_STACK, {.setter_stack = _lv_obj_set_align} },
-
- {"scrollbar_mode", 0, {.setter = (setter_int_t)lv_obj_set_scrollbar_mode}},
- {"scroll_dir", 0, {.setter = (setter_int_t)lv_obj_set_scroll_dir} },
- {"scroll_snap_x", 0, {.setter = (setter_int_t)lv_obj_set_scroll_snap_x} },
- {"scroll_snap_y", 0, {.setter = (setter_int_t)lv_obj_set_scroll_snap_y} },
+ {"x", 0, {.setter = (setter_int_t)lv_obj_set_x}},
+ {"y", 0, {.setter = (setter_int_t)lv_obj_set_y}},
+ {"w", 0, {.setter = (setter_int_t)lv_obj_set_width}},
+ {"h", 0, {.setter = (setter_int_t)lv_obj_set_height}},
+ {"align", SETTER_TYPE_STACK, {.setter_stack = _lv_obj_set_align}},
+
+ {"scrollbar_mode", 0, {.setter = (setter_int_t)lv_obj_set_scrollbar_mode}},
+ {"scroll_dir", 0, {.setter = (setter_int_t)lv_obj_set_scroll_dir}},
+ {"scroll_snap_x", 0, {.setter = (setter_int_t)lv_obj_set_scroll_snap_x}},
+ {"scroll_snap_y", 0, {.setter = (setter_int_t)lv_obj_set_scroll_snap_y}},
};
/**
@@ -751,8 +714,7 @@ static const luavgl_value_setter_t obj_property_table[] = {
* stack[-2]: key(property name)
* stack[-1]: value(could be any lua data)
*/
-LUALIB_API int luavgl_obj_set_property_kv(lua_State *L, void *data)
-{
+LUALIB_API int luavgl_obj_set_property_kv(lua_State *L, void *data) {
lv_obj_t *obj = data;
int ret = luavgl_set_property(L, obj, obj_property_table);
@@ -764,8 +726,7 @@ LUALIB_API int luavgl_obj_set_property_kv(lua_State *L, void *data)
}
LUALIB_API int luavgl_obj_create_helper(lua_State *L,
- lv_obj_t *(*create)(lv_obj_t *parent))
-{
+ lv_obj_t *(*create)(lv_obj_t *parent)) {
luavgl_ctx_t *ctx = luavgl_context(L);
lv_obj_t *parent;
@@ -811,8 +772,7 @@ LUALIB_API int luavgl_obj_create_helper(lua_State *L,
* If no metatable not found for this obj class, then lv_obj_class metatable is
* used
*/
-LUALIB_API luavgl_obj_t *luavgl_add_lobj(lua_State *L, lv_obj_t *obj)
-{
+LUALIB_API luavgl_obj_t *luavgl_add_lobj(lua_State *L, lv_obj_t *obj) {
luavgl_obj_t *lobj;
/* In rare case, obj may be deleted but not gc'ed in lua, and lvgl quickly
diff --git a/lib/luavgl/src/widgets/bar.c b/lib/luavgl/src/widgets/bar.c
new file mode 100644
index 00000000..bab38aae
--- /dev/null
+++ b/lib/luavgl/src/widgets/bar.c
@@ -0,0 +1,87 @@
+#include "luavgl.h"
+#include "private.h"
+#include <src/misc/lv_anim.h>
+#include <src/widgets/lv_bar.h>
+
+static int luavgl_bar_create(lua_State *L)
+{
+ return luavgl_obj_create_helper(L, lv_bar_create);
+}
+
+static void _lv_bar_set_range(void *obj, lua_State *L)
+{
+ int min=0, max=100;
+
+ int type = lua_type(L, -1);
+ if (type == LUA_TTABLE) {
+ lua_getfield(L, -1, "min");
+ min = lua_tointeger(L, -1);
+ lua_pop(L, 1);
+ lua_getfield(L, -1, "max");
+ max = luavgl_tointeger(L, -1);
+ lua_pop(L, 1);
+ }
+
+ lv_bar_set_range(obj, min, max);
+}
+
+static void _lv_bar_set_value(void *obj, int value)
+{
+ lv_bar_set_value(obj, value, LV_ANIM_OFF);
+}
+
+static const luavgl_value_setter_t bar_property_table[] = {
+ {"range", SETTER_TYPE_STACK, {.setter_stack = _lv_bar_set_range}},
+ {"value", SETTER_TYPE_INT, {.setter = (setter_int_t)_lv_bar_set_value}},
+};
+
+LUALIB_API int luavgl_bar_set_property_kv(lua_State *L, void *data)
+{
+ lv_obj_t *obj = data;
+ int ret = luavgl_set_property(L, obj, bar_property_table);
+
+ if (ret == 0) {
+ return 0;
+ }
+ /* a base obj property? */
+ ret = luavgl_obj_set_property_kv(L, obj);
+ if (ret != 0) {
+ debug("unkown property for bar.\n");
+ }
+
+ return ret;
+}
+
+static int luavgl_bar_set(lua_State *L)
+{
+ lv_obj_t *obj = luavgl_to_obj(L, 1);
+
+ if (!lua_istable(L, -1)) {
+ luaL_error(L, "expect a table on 2nd para.");
+ return 0;
+ }
+
+ luavgl_iterate(L, -1, luavgl_bar_set_property_kv, obj);
+
+ return 0;
+}
+
+static int luavgl_bar_tostring(lua_State *L)
+{
+ lv_obj_t *obj = luavgl_to_obj(L, 1);
+ lua_pushfstring(L, "lv_bar:%p", obj);
+ return 1;
+}
+
+static const luaL_Reg luavgl_bar_methods[] = {
+ {"set", luavgl_bar_set },
+ {NULL, NULL },
+};
+
+static void luavgl_bar_init(lua_State *L)
+{
+ luavgl_obj_newmetatable(L, &lv_bar_class, "lv_bar", luavgl_bar_methods);
+ lua_pushcfunction(L, luavgl_bar_tostring);
+ lua_setfield(L, -2, "__tostring");
+ lua_pop(L, 1);
+}
diff --git a/lib/luavgl/src/widgets/widgets.c b/lib/luavgl/src/widgets/widgets.c
index 19b789a7..e5f64f23 100644
--- a/lib/luavgl/src/widgets/widgets.c
+++ b/lib/luavgl/src/widgets/widgets.c
@@ -1,6 +1,10 @@
#include "luavgl.h"
#include "private.h"
+#if LV_USE_BAR
+#include "bar.c"
+#endif
+
#if LV_USE_BTN
#include "btn.c"
#endif
@@ -50,6 +54,10 @@ static int luavgl_obj_create(lua_State *L);
static const luaL_Reg widget_create_methods[] = {
{"Object", luavgl_obj_create },
+#if LV_USE_BAR
+ {"Bar", luavgl_bar_create},
+#endif
+
#if LV_USE_BTN
{"Button", luavgl_btn_create},
#endif
@@ -142,4 +150,8 @@ static void luavgl_widgets_init(lua_State *L)
luavgl_btn_init(L);
#endif
+#if LV_USE_BAR
+ luavgl_bar_init(L);
+#endif
+
}
diff --git a/lua/browser.lua b/lua/browser.lua
index f07d80bc..415e5dbb 100644
--- a/lua/browser.lua
+++ b/lua/browser.lua
@@ -5,6 +5,7 @@ local database = require("database")
local backstack = require("backstack")
local font = require("font")
local queue = require("queue")
+local playing = require("playing")
local browser = {}
@@ -73,6 +74,7 @@ function browser.create(opts)
play:onClicked(function()
queue.clear()
queue.add(original_iterator)
+ backstack.push(playing)
end
)
end
@@ -107,8 +109,7 @@ function browser.create(opts)
else
queue.clear()
queue.add(contents)
- legacy_ui.open_now_playing()
- -- backstack.push(playing)
+ backstack.push(playing)
end
end)
btn:onevent(lvgl.EVENT.FOCUSED, function()
diff --git a/lua/img/next.png b/lua/img/next.png
new file mode 100644
index 00000000..1b22a509
--- /dev/null
+++ b/lua/img/next.png
Binary files differ
diff --git a/lua/img/pause.png b/lua/img/pause.png
new file mode 100644
index 00000000..29fa4b90
--- /dev/null
+++ b/lua/img/pause.png
Binary files differ
diff --git a/lua/img/play.png b/lua/img/play.png
new file mode 100644
index 00000000..cc10cab5
--- /dev/null
+++ b/lua/img/play.png
Binary files differ
diff --git a/lua/img/prev.png b/lua/img/prev.png
new file mode 100644
index 00000000..f17e6162
--- /dev/null
+++ b/lua/img/prev.png
Binary files differ
diff --git a/lua/main_menu.lua b/lua/main_menu.lua
index c0b9b1d1..c2d052a3 100644
--- a/lua/main_menu.lua
+++ b/lua/main_menu.lua
@@ -4,6 +4,7 @@ local legacy_ui = require("legacy_ui")
local database = require("database")
local backstack = require("backstack")
local browser = require("browser")
+local playing = require("playing")
return function()
local menu = {}
@@ -29,7 +30,7 @@ return function()
})
menu.list:add_btn(nil, "Now Playing"):onClicked(function()
- legacy_ui.open_now_playing();
+ backstack.push(playing)
end)
local indexes = database.indexes()
diff --git a/lua/playing.lua b/lua/playing.lua
index 89bb27f7..a183e1ab 100644
--- a/lua/playing.lua
+++ b/lua/playing.lua
@@ -2,6 +2,8 @@ local lvgl = require("lvgl")
local widgets = require("widgets")
local backstack = require("backstack")
local font = require("font")
+local playback = require("playback")
+local queue = require("queue")
return function(opts)
local screen = {}
@@ -23,72 +25,142 @@ return function(opts)
transparent_bg = true,
})
- local track_info = screen.root:Object {
+ local info = screen.root:Object {
flex = {
flex_direction = "column",
+ flex_wrap = "wrap",
justify_content = "center",
align_items = "center",
align_content = "center",
},
- w = lvgl.SIZE_CONTENT,
+ w = lvgl.HOR_RES(),
+ h = lvgl.SIZE_CONTENT,
flex_grow = 1,
}
- local artist = track_info:Label {
- text = "Cool Artist",
- text_font = font.fusion_10,
- }
-
- local artist = track_info:Label {
- text = "Good Album",
+ local artist = info:Label {
+ w = lvgl.SIZE_CONTENT,
+ h = lvgl.SIZE_CONTENT,
+ text = "",
text_font = font.fusion_10,
}
- local title = track_info:Label {
- text = "A really good song",
+ local title = info:Label {
+ w = lvgl.SIZE_CONTENT,
+ h = lvgl.SIZE_CONTENT,
+ text = "",
}
- local scrubber = screen.root:Object {}
-
- local times = screen.root:Object {
+ local playlist = screen.root:Object {
flex = {
flex_direction = "row",
justify_content = "center",
- align_items = "space-between",
+ align_items = "center",
align_content = "center",
},
- w = lvgl.PCT(100),
+ w = lvgl.SIZE_CONTENT,
h = lvgl.SIZE_CONTENT,
}
- local cur_time = track_info:Label {
- text = "1:09",
+
+ local playlist_pos = playlist:Label {
+ text = "",
+ text_font = font.fusion_10,
}
- local end_time = track_info:Label {
- text = "4:20",
+ playlist:Label {
+ text = "/",
+ text_font = font.fusion_10,
+ }
+ local playlist_total = playlist:Label {
+ text = "",
+ text_font = font.fusion_10,
}
+ local scrubber = screen.root:Bar {
+ w = lvgl.PCT(100),
+ h = 5,
+ range = { min = 0, max = 100 },
+ value = 0,
+ }
local controls = screen.root:Object {
flex = {
flex_direction = "row",
justify_content = "center",
- align_items = "space-evenly",
+ align_items = "center",
align_content = "center",
},
w = lvgl.PCT(100),
h = lvgl.SIZE_CONTENT,
+ pad_column = 8,
+ pad_all = 2,
+ }
+
+ local cur_time = controls:Label {
+ w = lvgl.SIZE_CONTENT,
+ h = lvgl.SIZE_CONTENT,
+ text = "",
+ text_font = font.fusion_10,
}
- controls:Label {
- text = ">",
+
+ controls:Object({ flex_grow = 1, h = 1 }) -- spacer
+
+ controls:Image {
+ src = "//lua/img/prev.png",
}
- controls:Label {
- text = ">",
+ local play_pause_img = controls:Image {
+ src = "//lua/img/pause.png",
}
- controls:Label {
- text = ">",
+ controls:Image {
+ src = "//lua/img/next.png",
}
- controls:Label {
- text = ">",
+ controls:Object({ flex_grow = 1, h = 1 }) -- spacer
+
+ local end_time = controls:Label {
+ w = lvgl.SIZE_CONTENT,
+ h = lvgl.SIZE_CONTENT,
+ align = lvgl.ALIGN.RIGHT_MID,
+ text = "",
+ text_font = font.fusion_10,
+ }
+
+ local format_time = function(time)
+ return string.format("%d:%02d", time // 60, time % 60)
+ end
+
+ screen.bindings = {
+ playback.playing:bind(function(playing)
+ if playing then
+ play_pause_img:set_src("//lua/img/pause.png")
+ else
+ play_pause_img:set_src("//lua/img/play.png")
+ end
+ end),
+ playback.position:bind(function(pos)
+ if not pos then return end
+ cur_time:set {
+ text = format_time(pos)
+ }
+ scrubber:set { value = pos }
+ end),
+ playback.track:bind(function(track)
+ if not track then return end
+ end_time:set {
+ text = format_time(track.duration)
+ }
+ title:set { text = track.title }
+ artist:set { text = track.artist }
+ scrubber:set {
+ range = { min = 0, max = track.duration }
+ }
+ end),
+ queue.position:bind(function(pos)
+ if not pos then return end
+ playlist_pos:set { text = tostring(pos) }
+ end),
+ queue.size:bind(function(num)
+ if not num then return end
+ playlist_total:set { text = tostring(num) }
+ end),
}
return screen
diff --git a/lua/widgets.lua b/lua/widgets.lua
index b601326b..bd0b2405 100644
--- a/lua/widgets.lua
+++ b/lua/widgets.lua
@@ -1,7 +1,6 @@
local lvgl = require("lvgl")
local power = require("power")
local bluetooth = require("bluetooth")
-local playback = require("playback")
local font = require("font")
local widgets = {}
@@ -53,7 +52,6 @@ function widgets.StatusBar(parent, opts)
status_bar.title:set { text = opts.title }
end
- status_bar.playing = status_bar.root:Image {}
status_bar.bluetooth = status_bar.root:Image {}
status_bar.battery = status_bar.root:Image {}
status_bar.chg = status_bar.battery:Image {
@@ -64,7 +62,7 @@ function widgets.StatusBar(parent, opts)
local is_charging = nil
local percent = nil
- function update_battery_icon()
+ local function update_battery_icon()
if is_charging == nil or percent == nil then return end
local src
if percent >= 95 then
@@ -101,20 +99,6 @@ function widgets.StatusBar(parent, opts)
is_charging = p
update_battery_icon()
end),
- playback.playing:bind(function(playing)
- if playing then
- status_bar.playing:set_src("//lua/assets/play.png")
- else
- status_bar.playing:set_src("//lua/assets/pause.png")
- end
- end),
- playback.track:bind(function(track)
- if track then
- status_bar.playing:clear_flag(lvgl.FLAG.HIDDEN)
- else
- status_bar.playing:add_flag(lvgl.FLAG.HIDDEN)
- end
- end),
bluetooth.enabled:bind(function(en)
if en then
status_bar.bluetooth:clear_flag(lvgl.FLAG.HIDDEN)
diff --git a/src/audio/CMakeLists.txt b/src/audio/CMakeLists.txt
index 0cf8eacd..95bab4c2 100644
--- a/src/audio/CMakeLists.txt
+++ b/src/audio/CMakeLists.txt
@@ -6,6 +6,7 @@ idf_component_register(
SRCS "audio_decoder.cpp" "fatfs_audio_input.cpp" "i2s_audio_output.cpp"
"track_queue.cpp" "audio_fsm.cpp" "audio_converter.cpp" "resample.cpp"
"fatfs_source.cpp" "bt_audio_output.cpp" "readahead_source.cpp"
+ "audio_source.cpp"
INCLUDE_DIRS "include"
REQUIRES "codecs" "drivers" "cbor" "result" "tasks" "span" "memory" "tinyfsm"
"database" "system_fsm" "playlist" "speexdsp")
diff --git a/src/audio/audio_decoder.cpp b/src/audio/audio_decoder.cpp
index a58268b0..fd011c51 100644
--- a/src/audio/audio_decoder.cpp
+++ b/src/audio/audio_decoder.cpp
@@ -50,12 +50,16 @@ namespace audio {
static constexpr std::size_t kCodecBufferLength =
drivers::kI2SBufferLengthFrames * sizeof(sample::Sample) * 2;
-Timer::Timer(const codecs::ICodec::OutputFormat& format)
- : current_seconds_(0),
+Timer::Timer(std::shared_ptr<Track> t,
+ const codecs::ICodec::OutputFormat& format)
+ : track_(t),
+ current_seconds_(0),
current_sample_in_second_(0),
samples_per_second_(format.sample_rate_hz * format.num_channels),
total_duration_seconds_(format.total_samples.value_or(0) /
- format.num_channels / format.sample_rate_hz) {}
+ format.num_channels / format.sample_rate_hz) {
+ track_->duration = total_duration_seconds_;
+}
auto Timer::AddSamples(std::size_t samples) -> void {
bool incremented = false;
@@ -69,10 +73,10 @@ auto Timer::AddSamples(std::size_t samples) -> void {
if (incremented) {
if (total_duration_seconds_ < current_seconds_) {
total_duration_seconds_ = current_seconds_;
+ track_->duration = total_duration_seconds_;
}
- PlaybackUpdate ev{.seconds_elapsed = current_seconds_,
- .seconds_total = total_duration_seconds_};
+ PlaybackUpdate ev{.seconds_elapsed = current_seconds_, .track = track_};
events::Audio().Dispatch(ev);
events::Ui().Dispatch(ev);
}
@@ -102,7 +106,7 @@ Decoder::Decoder(std::shared_ptr<IAudioSource> source,
void Decoder::Main() {
for (;;) {
if (source_->HasNewStream() || !stream_) {
- std::shared_ptr<codecs::IStream> new_stream = source_->NextStream();
+ std::shared_ptr<TaggedStream> new_stream = source_->NextStream();
ESP_LOGI(kTag, "decoder has new stream");
if (new_stream && BeginDecoding(new_stream)) {
stream_ = new_stream;
@@ -118,7 +122,7 @@ void Decoder::Main() {
}
}
-auto Decoder::BeginDecoding(std::shared_ptr<codecs::IStream> stream) -> bool {
+auto Decoder::BeginDecoding(std::shared_ptr<TaggedStream> stream) -> bool {
// Ensure any previous codec is freed before creating a new one.
codec_.reset();
codec_.reset(codecs::CreateCodecForType(stream->type()).value_or(nullptr));
@@ -136,7 +140,13 @@ auto Decoder::BeginDecoding(std::shared_ptr<codecs::IStream> stream) -> bool {
stream->SetPreambleFinished();
if (open_res->total_samples) {
- timer_.reset(new Timer(open_res.value()));
+ timer_.reset(new Timer(std::shared_ptr<Track>{new Track{
+ .tags = stream->tags(),
+ .db_info = {},
+ .bitrate_kbps = 0,
+ .encoding = stream->type(),
+ }},
+ open_res.value()));
} else {
timer_.reset();
}
diff --git a/src/audio/audio_fsm.cpp b/src/audio/audio_fsm.cpp
index f43d0ce2..e33a2cab 100644
--- a/src/audio/audio_fsm.cpp
+++ b/src/audio/audio_fsm.cpp
@@ -211,7 +211,7 @@ void Playback::react(const TogglePlayPause& ev) {
void Playback::react(const PlaybackUpdate& ev) {
ESP_LOGI(kTag, "elapsed: %lu, total: %lu", ev.seconds_elapsed,
- ev.seconds_total);
+ ev.track->duration);
}
void Playback::react(const internal::InputFileOpened& ev) {}
diff --git a/src/audio/audio_source.cpp b/src/audio/audio_source.cpp
new file mode 100644
index 00000000..b9262b45
--- /dev/null
+++ b/src/audio/audio_source.cpp
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2023 jacqueline <me@jacqueline.id.au>
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#include "audio_source.hpp"
+#include "codec.hpp"
+#include "types.hpp"
+
+namespace audio {
+
+TaggedStream::TaggedStream(std::shared_ptr<database::TrackTags> t,
+ std::unique_ptr<codecs::IStream> w)
+ : codecs::IStream(w->type()), tags_(t), wrapped_(std::move(w)) {}
+
+auto TaggedStream::tags() -> std::shared_ptr<database::TrackTags> {
+ return tags_;
+}
+
+auto TaggedStream::Read(cpp::span<std::byte> dest) -> ssize_t {
+ return wrapped_->Read(dest);
+}
+
+auto TaggedStream::CanSeek() -> bool {
+ return wrapped_->CanSeek();
+}
+
+auto TaggedStream::SeekTo(int64_t destination, SeekFrom from) -> void {
+ wrapped_->SeekTo(destination, from);
+}
+
+auto TaggedStream::CurrentPosition() -> int64_t {
+ return wrapped_->CurrentPosition();
+}
+
+auto TaggedStream::SetPreambleFinished() -> void {
+ wrapped_->SetPreambleFinished();
+}
+
+} // namespace audio
diff --git a/src/audio/fatfs_audio_input.cpp b/src/audio/fatfs_audio_input.cpp
index 6580f301..5594718f 100644
--- a/src/audio/fatfs_audio_input.cpp
+++ b/src/audio/fatfs_audio_input.cpp
@@ -85,7 +85,7 @@ auto FatfsAudioInput::HasNewStream() -> bool {
return has_new_stream_;
}
-auto FatfsAudioInput::NextStream() -> std::shared_ptr<codecs::IStream> {
+auto FatfsAudioInput::NextStream() -> std::shared_ptr<TaggedStream> {
while (true) {
has_new_stream_.wait(false);
@@ -147,8 +147,7 @@ auto FatfsAudioInput::OpenFile(const std::pmr::string& path) -> bool {
auto source =
std::make_unique<FatfsSource>(stream_type.value(), std::move(file));
- // new_stream_.reset(new ReadaheadSource(bg_worker_, std::move(source)));
- new_stream_ = std::move(source);
+ new_stream_.reset(new TaggedStream(tags, std::move(source)));
return true;
}
diff --git a/src/audio/include/audio_decoder.hpp b/src/audio/include/audio_decoder.hpp
index 1759f6e4..318e6fd4 100644
--- a/src/audio/include/audio_decoder.hpp
+++ b/src/audio/include/audio_decoder.hpp
@@ -10,6 +10,7 @@
#include <memory>
#include "audio_converter.hpp"
+#include "audio_events.hpp"
#include "audio_sink.hpp"
#include "audio_source.hpp"
#include "codec.hpp"
@@ -23,11 +24,13 @@ namespace audio {
*/
class Timer {
public:
- Timer(const codecs::ICodec::OutputFormat& format);
+ Timer(std::shared_ptr<Track>, const codecs::ICodec::OutputFormat& format);
auto AddSamples(std::size_t) -> void;
private:
+ std::shared_ptr<Track> track_;
+
uint32_t current_seconds_;
uint32_t current_sample_in_second_;
uint32_t samples_per_second_;
@@ -54,7 +57,7 @@ class Decoder {
Decoder(std::shared_ptr<IAudioSource> source,
std::shared_ptr<SampleConverter> converter);
- auto BeginDecoding(std::shared_ptr<codecs::IStream>) -> bool;
+ auto BeginDecoding(std::shared_ptr<TaggedStream>) -> bool;
auto ContinueDecoding() -> bool;
std::shared_ptr<IAudioSource> source_;
diff --git a/src/audio/include/audio_events.hpp b/src/audio/include/audio_events.hpp
index b130938c..9994a9f6 100644
--- a/src/audio/include/audio_events.hpp
+++ b/src/audio/include/audio_events.hpp
@@ -8,20 +8,31 @@
#include <stdint.h>
#include <cstdint>
+#include <memory>
#include <string>
#include "tinyfsm.hpp"
#include "track.hpp"
#include "track_queue.hpp"
+#include "types.hpp"
namespace audio {
+struct Track {
+ std::shared_ptr<database::TrackTags> tags;
+ std::shared_ptr<database::TrackData> db_info;
+
+ uint32_t duration;
+ uint32_t bitrate_kbps;
+ codecs::StreamType encoding;
+};
+
struct PlaybackStarted : tinyfsm::Event {};
struct PlaybackUpdate : tinyfsm::Event {
uint32_t seconds_elapsed;
- uint32_t seconds_total;
+ std::shared_ptr<Track> track;
};
struct PlaybackFinished : tinyfsm::Event {};
diff --git a/src/audio/include/audio_source.hpp b/src/audio/include/audio_source.hpp
index a0d690a6..a54cb260 100644
--- a/src/audio/include/audio_source.hpp
+++ b/src/audio/include/audio_source.hpp
@@ -6,16 +6,41 @@
#pragma once
+#include <memory>
#include "codec.hpp"
+#include "track.hpp"
+#include "types.hpp"
namespace audio {
+class TaggedStream : public codecs::IStream {
+ public:
+ TaggedStream(std::shared_ptr<database::TrackTags>,
+ std::unique_ptr<codecs::IStream> wrapped);
+
+ auto tags() -> std::shared_ptr<database::TrackTags>;
+
+ auto Read(cpp::span<std::byte> dest) -> ssize_t override;
+
+ auto CanSeek() -> bool override;
+
+ auto SeekTo(int64_t destination, SeekFrom from) -> void override;
+
+ auto CurrentPosition() -> int64_t override;
+
+ auto SetPreambleFinished() -> void override;
+
+ private:
+ std::shared_ptr<database::TrackTags> tags_;
+ std::unique_ptr<codecs::IStream> wrapped_;
+};
+
class IAudioSource {
public:
virtual ~IAudioSource() {}
virtual auto HasNewStream() -> bool = 0;
- virtual auto NextStream() -> std::shared_ptr<codecs::IStream> = 0;
+ virtual auto NextStream() -> std::shared_ptr<TaggedStream> = 0;
};
} // namespace audio
diff --git a/src/audio/include/fatfs_audio_input.hpp b/src/audio/include/fatfs_audio_input.hpp
index 9b516478..c7d52ca3 100644
--- a/src/audio/include/fatfs_audio_input.hpp
+++ b/src/audio/include/fatfs_audio_input.hpp
@@ -43,7 +43,7 @@ class FatfsAudioInput : public IAudioSource {
auto SetPath() -> void;
auto HasNewStream() -> bool override;
- auto NextStream() -> std::shared_ptr<codecs::IStream> override;
+ auto NextStream() -> std::shared_ptr<TaggedStream> override;
FatfsAudioInput(const FatfsAudioInput&) = delete;
FatfsAudioInput& operator=(const FatfsAudioInput&) = delete;
@@ -58,7 +58,7 @@ class FatfsAudioInput : public IAudioSource {
tasks::Worker& bg_worker_;
std::mutex new_stream_mutex_;
- std::shared_ptr<codecs::IStream> new_stream_;
+ std::shared_ptr<TaggedStream> new_stream_;
std::atomic<bool> has_new_stream_;
diff --git a/src/audio/include/track_queue.hpp b/src/audio/include/track_queue.hpp
index 49c0d61b..0be2384a 100644
--- a/src/audio/include/track_queue.hpp
+++ b/src/audio/include/track_queue.hpp
@@ -72,6 +72,9 @@ class TrackQueue {
*/
auto Clear() -> void;
+ auto Position() -> size_t;
+ auto Size() -> size_t;
+
TrackQueue(const TrackQueue&) = delete;
TrackQueue& operator=(const TrackQueue&) = delete;
diff --git a/src/audio/track_queue.cpp b/src/audio/track_queue.cpp
index 86f6e034..c400e66a 100644
--- a/src/audio/track_queue.cpp
+++ b/src/audio/track_queue.cpp
@@ -8,10 +8,12 @@
#include <algorithm>
#include <mutex>
+#include <optional>
#include <variant>
#include "audio_events.hpp"
#include "audio_fsm.hpp"
+#include "database.hpp"
#include "event_queue.hpp"
#include "source.hpp"
#include "track.hpp"
@@ -217,4 +219,12 @@ auto TrackQueue::Clear() -> void {
events::Ui().Dispatch(ev);
}
+auto TrackQueue::Position() -> size_t {
+ return played_.size() + (enqueued_.empty() ? 0 : 1);
+}
+
+auto TrackQueue::Size() -> size_t {
+ return played_.size() + enqueued_.size();
+}
+
} // namespace audio
diff --git a/src/codecs/codec.cpp b/src/codecs/codec.cpp
index a4c1a5cf..3610dea8 100644
--- a/src/codecs/codec.cpp
+++ b/src/codecs/codec.cpp
@@ -17,6 +17,23 @@
namespace codecs {
+auto StreamTypeToString(StreamType t) -> std::string {
+ switch (t) {
+ case StreamType::kMp3:
+ return "Mp3";
+ case StreamType::kPcm:
+ return "Wav";
+ case StreamType::kVorbis:
+ return "Vorbis";
+ case StreamType::kFlac:
+ return "Flac";
+ case StreamType::kOpus:
+ return "Opus";
+ default:
+ return "";
+ }
+}
+
auto CreateCodecForType(StreamType type) -> std::optional<ICodec*> {
switch (type) {
case StreamType::kMp3:
diff --git a/src/codecs/include/types.hpp b/src/codecs/include/types.hpp
index e0bba47d..c9eefe45 100644
--- a/src/codecs/include/types.hpp
+++ b/src/codecs/include/types.hpp
@@ -18,4 +18,6 @@ enum class StreamType {
kOpus,
};
+auto StreamTypeToString(StreamType t) -> std::string;
+
} // namespace codecs
diff --git a/src/database/include/track.hpp b/src/database/include/track.hpp
index 72296e8d..8a24024f 100644
--- a/src/database/include/track.hpp
+++ b/src/database/include/track.hpp
@@ -56,6 +56,8 @@ enum class Tag {
kDuration = 5,
};
+auto TagToString(Tag t) -> std::string;
+
/*
* Owning container for tag-related track metadata that was extracted from a
* file.
diff --git a/src/database/track.cpp b/src/database/track.cpp
index d30264cd..871e3087 100644
--- a/src/database/track.cpp
+++ b/src/database/track.cpp
@@ -13,6 +13,25 @@
namespace database {
+auto TagToString(Tag t) -> std::string {
+ switch (t) {
+ case Tag::kTitle:
+ return "title";
+ case Tag::kArtist:
+ return "artist";
+ case Tag::kAlbum:
+ return "album";
+ case Tag::kAlbumTrack:
+ return "album_track";
+ case Tag::kGenre:
+ return "genre";
+ case Tag::kDuration:
+ return "duration";
+ default:
+ return "";
+ }
+}
+
auto TrackTags::set(const Tag& key, const std::pmr::string& val) -> void {
tags_[key] = val;
}
@@ -64,5 +83,4 @@ auto Track::TitleOrFilename() const -> std::pmr::string {
}
return data().filepath.substr(start);
}
-
} // namespace database
diff --git a/src/lua/include/property.hpp b/src/lua/include/property.hpp
index 60f9906a..207696bd 100644
--- a/src/lua/include/property.hpp
+++ b/src/lua/include/property.hpp
@@ -9,13 +9,15 @@
#include <memory>
#include <string>
+#include "audio_events.hpp"
#include "lua.hpp"
#include "lvgl.h"
#include "service_locator.hpp"
namespace lua {
-using LuaValue = std::variant<std::monostate, int, float, bool, std::string>;
+using LuaValue =
+ std::variant<std::monostate, int, float, bool, std::string, audio::Track>;
using LuaFunction = std::function<int(lua_State*)>;
class Property {
diff --git a/src/lua/property.cpp b/src/lua/property.cpp
index c63d243f..3e492237 100644
--- a/src/lua/property.cpp
+++ b/src/lua/property.cpp
@@ -9,10 +9,13 @@
#include <memory>
#include <string>
+#include "lua.h"
#include "lua.hpp"
#include "lua_thread.hpp"
#include "lvgl.h"
#include "service_locator.hpp"
+#include "track.hpp"
+#include "types.hpp"
namespace lua {
@@ -51,7 +54,7 @@ static auto property_bind(lua_State* state) -> int {
lua_pushvalue(state, 2);
p->PushValue(*state);
- CallProtected(state, 1, 0); // Invoke the initial binding.
+ CallProtected(state, 1, 0); // Invoke the initial binding.
lua_pushstring(state, kBindingsTable);
lua_gettable(state, LUA_REGISTRYINDEX); // REGISTRY[kBindingsTable]
@@ -171,6 +174,31 @@ auto Property::PushValue(lua_State& s) -> int {
lua_pushboolean(&s, arg);
} else if constexpr (std::is_same_v<T, std::string>) {
lua_pushstring(&s, arg.c_str());
+ } else if constexpr (std::is_same_v<T, audio::Track>) {
+ lua_newtable(&s);
+ int table = lua_gettop(&s);
+ for (const auto& [key, val] : arg.tags->tags()) {
+ lua_pushstring(&s, database::TagToString(key).c_str());
+ lua_pushstring(&s, val.c_str());
+ lua_settable(&s, table);
+ }
+ if (arg.db_info) {
+ lua_pushliteral(&s, "id");
+ lua_pushinteger(&s, arg.db_info->id);
+ lua_settable(&s, table);
+ }
+
+ lua_pushliteral(&s, "duration");
+ lua_pushinteger(&s, arg.duration);
+ lua_settable(&s, table);
+
+ lua_pushliteral(&s, "bitrate_kbps");
+ lua_pushinteger(&s, arg.bitrate_kbps);
+ lua_settable(&s, table);
+
+ lua_pushliteral(&s, "encoding");
+ lua_pushstring(&s, codecs::StreamTypeToString(arg.encoding).c_str());
+ lua_settable(&s, table);
} else {
static_assert(always_false_v<T>, "PushValue missing type");
}
@@ -228,8 +256,8 @@ auto Property::Update(const LuaValue& v) -> void {
continue;
}
- PushValue(*b.first); // push the argument
- CallProtected(b.first, 1, 0); // invoke the closure
+ PushValue(*b.first); // push the argument
+ CallProtected(b.first, 1, 0); // invoke the closure
}
}
diff --git a/src/lua/stubs/playback.lua b/src/lua/stubs/playback.lua
index d32febae..340da37d 100644
--- a/src/lua/stubs/playback.lua
+++ b/src/lua/stubs/playback.lua
@@ -8,4 +8,8 @@ local playback = {}
-- @treturn types.Property a boolean property
function playback.playing() end
+function playback:track() end
+
+function playback:position() end
+
return playback
diff --git a/src/ui/CMakeLists.txt b/src/ui/CMakeLists.txt
index d37f3fdf..a869053d 100644
--- a/src/ui/CMakeLists.txt
+++ b/src/ui/CMakeLists.txt
@@ -3,15 +3,11 @@
# SPDX-License-Identifier: GPL-3.0-only
idf_component_register(
- SRCS "lvgl_task.cpp" "ui_fsm.cpp" "screen_splash.cpp"
- "encoder_input.cpp" "screen_track_browser.cpp" "screen_playing.cpp"
- "themes.cpp" "widget_top_bar.cpp" "screen.cpp" "screen_onboarding.cpp"
- "modal_progress.cpp" "modal.cpp" "modal_confirm.cpp" "screen_settings.cpp"
- "event_binding.cpp" "modal_add_to_queue.cpp" "screen_lua.cpp"
+ SRCS "lvgl_task.cpp" "ui_fsm.cpp" "screen_splash.cpp" "encoder_input.cpp"
+ "themes.cpp" "widget_top_bar.cpp" "screen.cpp" "modal_progress.cpp"
+ "modal.cpp" "modal_confirm.cpp" "screen_settings.cpp" "event_binding.cpp"
+ "screen_lua.cpp"
"splash.c" "font_fusion_12.c" "font_fusion_10.c"
- "icons/battery_empty.c" "icons/battery_full.c" "icons/battery_20.c"
- "icons/battery_40.c" "icons/battery_60.c" "icons/battery_80.c" "icons/play.c"
- "icons/pause.c" "icons/bluetooth.c"
INCLUDE_DIRS "include"
REQUIRES "drivers" "lvgl" "tinyfsm" "events" "system_fsm" "database" "esp_timer" "battery" "bindey" "lua" "luavgl")
target_compile_options(${COMPONENT_LIB} PRIVATE ${EXTRA_WARNINGS})
diff --git a/src/ui/include/modal_add_to_queue.hpp b/src/ui/include/modal_add_to_queue.hpp
deleted file mode 100644
index e6417cd4..00000000
--- a/src/ui/include/modal_add_to_queue.hpp
+++ /dev/null
@@ -1,41 +0,0 @@
-/*
- * Copyright 2023 jacqueline <me@jacqueline.id.au>
- *
- * SPDX-License-Identifier: GPL-3.0-only
- */
-
-#pragma once
-
-#include <memory>
-#include <vector>
-
-#include "database.hpp"
-#include "index.hpp"
-#include "lvgl.h"
-
-#include "modal.hpp"
-#include "source.hpp"
-#include "track_queue.hpp"
-
-namespace ui {
-namespace modals {
-
-class AddToQueue : public Modal {
- public:
- AddToQueue(Screen*,
- audio::TrackQueue&,
- std::shared_ptr<playlist::IResetableSource>,
- bool all_tracks_only = false);
-
- private:
- audio::TrackQueue& queue_;
- std::shared_ptr<playlist::IResetableSource> item_;
- lv_obj_t* container_;
-
- lv_obj_t* selected_track_btn_;
- lv_obj_t* all_tracks_btn_;
- bool all_tracks_;
-};
-
-} // namespace modals
-} // namespace ui
diff --git a/src/ui/include/screen_onboarding.hpp b/src/ui/include/screen_onboarding.hpp
deleted file mode 100644
index 0c3c61fb..00000000
--- a/src/ui/include/screen_onboarding.hpp
+++ /dev/null
@@ -1,63 +0,0 @@
-/*
- * Copyright 2023 jacqueline <me@jacqueline.id.au>
- *
- * SPDX-License-Identifier: GPL-3.0-only
- */
-
-#pragma once
-
-#include <memory>
-#include <vector>
-
-#include "lvgl.h"
-
-#include "screen.hpp"
-
-namespace ui {
-namespace screens {
-
-class Onboarding : public Screen {
- public:
- Onboarding(const std::pmr::string& title, bool show_prev, bool show_next);
-
- private:
- lv_obj_t* window_;
- lv_obj_t* title_;
- lv_obj_t* next_button_;
- lv_obj_t* prev_button_;
-
- protected:
- lv_obj_t* content_;
-};
-
-namespace onboarding {
-
-class LinkToManual : public Onboarding {
- public:
- LinkToManual();
-};
-
-class Controls : public Onboarding {
- public:
- Controls();
-};
-
-class MissingSdCard : public Onboarding {
- public:
- MissingSdCard();
-};
-
-class FormatSdCard : public Onboarding {
- public:
- FormatSdCard();
-};
-
-class InitDatabase : public Onboarding {
- public:
- InitDatabase();
-};
-
-} // namespace onboarding
-
-} // namespace screens
-} // namespace ui
diff --git a/src/ui/include/screen_playing.hpp b/src/ui/include/screen_playing.hpp
deleted file mode 100644
index 185c55cc..00000000
--- a/src/ui/include/screen_playing.hpp
+++ /dev/null
@@ -1,73 +0,0 @@
-/*
- * Copyright 2023 jacqueline <me@jacqueline.id.au>
- *
- * SPDX-License-Identifier: GPL-3.0-only
- */
-
-#pragma once
-
-#include <stdint.h>
-#include <sys/_stdint.h>
-#include <memory>
-#include <vector>
-
-#include "bindey/property.h"
-#include "esp_log.h"
-#include "lvgl.h"
-
-#include "database.hpp"
-#include "future_fetcher.hpp"
-#include "model_playback.hpp"
-#include "model_top_bar.hpp"
-#include "screen.hpp"
-#include "track.hpp"
-#include "track_queue.hpp"
-
-namespace ui {
-namespace screens {
-
-/*
- * The 'Now Playing' / 'Currently Playing' screen that contains information
- * about the current track, as well as playback controls.
- */
-class Playing : public Screen {
- public:
- explicit Playing(models::TopBar&,
- models::Playback& playback_model,
- std::weak_ptr<database::Database> db,
- audio::TrackQueue& queue);
- ~Playing();
-
- auto Tick() -> void override;
-
- auto OnFocusAboveFold() -> void;
- auto OnFocusBelowFold() -> void;
-
- Playing(const Playing&) = delete;
- Playing& operator=(const Playing&) = delete;
-
- private:
- auto control_button(lv_obj_t* parent, char* icon) -> lv_obj_t*;
- auto next_up_label(lv_obj_t* parent, const std::pmr::string& text)
- -> lv_obj_t*;
-
- std::weak_ptr<database::Database> db_;
- audio::TrackQueue& queue_;
-
- bindey::property<std::shared_ptr<database::Track>> current_track_;
- bindey::property<std::vector<std::shared_ptr<database::Track>>> next_tracks_;
-
- std::unique_ptr<database::FutureFetcher<std::shared_ptr<database::Track>>>
- new_track_;
- std::unique_ptr<
- database::FutureFetcher<std::vector<std::shared_ptr<database::Track>>>>
- new_next_tracks_;
-
- lv_obj_t* next_up_header_;
- lv_obj_t* next_up_label_;
- lv_obj_t* next_up_hint_;
- lv_obj_t* next_up_container_;
-};
-
-} // namespace screens
-} // namespace ui
diff --git a/src/ui/include/screen_track_browser.hpp b/src/ui/include/screen_track_browser.hpp
deleted file mode 100644
index 0b2d6fc3..00000000
--- a/src/ui/include/screen_track_browser.hpp
+++ /dev/null
@@ -1,74 +0,0 @@
-/*
- * Copyright 2023 jacqueline <me@jacqueline.id.au>
- *
- * SPDX-License-Identifier: GPL-3.0-only
- */
-
-#pragma once
-
-#include <deque>
-#include <memory>
-#include <string>
-#include <vector>
-
-#include "lvgl.h"
-
-#include "database.hpp"
-#include "model_top_bar.hpp"
-#include "screen.hpp"
-#include "track_queue.hpp"
-
-namespace ui {
-namespace screens {
-
-class TrackBrowser : public Screen {
- public:
- TrackBrowser(
- models::TopBar& top_bar,
- audio::TrackQueue& queue,
- std::weak_ptr<database::Database> db,
- const std::pmr::vector<std::pmr::string>& breadcrumbs,
- std::future<database::Result<database::IndexRecord>*>&& initial_page);
- ~TrackBrowser() {}
-
- auto Tick() -> void override;
-
- auto OnItemSelected(lv_event_t* ev) -> void;
- auto OnItemClicked(lv_event_t* ev) -> void;
-
- private:
- enum Position {
- START = 0,
- END = 1,
- };
- auto AddLoadingIndictor(Position pos) -> void;
- auto AddResults(Position pos,
- std::shared_ptr<database::Result<database::IndexRecord>>)
- -> void;
- auto DropPage(Position pos) -> void;
- auto FetchNewPage(Position pos) -> void;
-
- auto GetNumRecords() -> std::size_t;
- auto GetItemIndex(lv_obj_t* obj) -> std::optional<std::size_t>;
-
- audio::TrackQueue& queue_;
- std::weak_ptr<database::Database> db_;
- lv_obj_t* back_button_;
- lv_obj_t* play_button_;
- lv_obj_t* enqueue_button_;
- lv_obj_t* list_;
- lv_obj_t* loading_indicator_;
-
- std::pmr::vector<std::pmr::string> breadcrumbs_;
-
- std::optional<Position> loading_pos_;
- std::optional<std::future<database::Result<database::IndexRecord>*>>
- loading_page_;
-
- std::shared_ptr<database::Result<database::IndexRecord>> initial_page_;
- std::deque<std::shared_ptr<database::Result<database::IndexRecord>>>
- current_pages_;
-};
-
-} // namespace screens
-} // namespace ui
diff --git a/src/ui/include/ui_fsm.hpp b/src/ui/include/ui_fsm.hpp
index 9e81259a..9f530d71 100644
--- a/src/ui/include/ui_fsm.hpp
+++ b/src/ui/include/ui_fsm.hpp
@@ -6,8 +6,7 @@
#pragma once
-#include <stdint.h>
-#include <sys/_stdint.h>
+#include <cstdint>
#include <memory>
#include <stack>
@@ -23,7 +22,6 @@
#include "nvs.hpp"
#include "property.hpp"
#include "relative_wheel.hpp"
-#include "screen_playing.hpp"
#include "screen_settings.hpp"
#include "service_locator.hpp"
#include "tinyfsm.hpp"
@@ -60,16 +58,14 @@ class UiState : public tinyfsm::Fsm<UiState> {
virtual void react(const system_fsm::BatteryStateChanged&);
virtual void react(const audio::PlaybackStarted&);
virtual void react(const audio::PlaybackFinished&);
- void react(const audio::PlaybackUpdate&);
- void react(const audio::QueueUpdate&);
+ virtual void react(const audio::PlaybackUpdate&);
+ virtual void react(const audio::QueueUpdate&);
virtual void react(const system_fsm::KeyLockChanged&);
virtual void react(const OnLuaError&) {}
virtual void react(const internal::RecordSelected&) {}
- virtual void react(const internal::IndexSelected&) {}
virtual void react(const internal::BackPressed&) {}
- virtual void react(const internal::ShowNowPlaying&){};
virtual void react(const internal::ShowSettingsPage&){};
virtual void react(const internal::ModalCancelPressed&) {
sCurrentModal.reset();
@@ -127,12 +123,12 @@ class Lua : public UiState {
void react(const OnLuaError&) override;
- void react(const internal::IndexSelected&) override;
- void react(const internal::ShowNowPlaying&) override;
void react(const internal::ShowSettingsPage&) override;
void react(const system_fsm::BatteryStateChanged&) override;
+ void react(const audio::QueueUpdate&) override;
void react(const audio::PlaybackStarted&) override;
+ void react(const audio::PlaybackUpdate&) override;
void react(const audio::PlaybackFinished&) override;
using UiState::react;
@@ -144,32 +140,23 @@ class Lua : public UiState {
std::shared_ptr<lua::Property> battery_pct_;
std::shared_ptr<lua::Property> battery_mv_;
std::shared_ptr<lua::Property> battery_charging_;
+
std::shared_ptr<lua::Property> bluetooth_en_;
+
std::shared_ptr<lua::Property> playback_playing_;
std::shared_ptr<lua::Property> playback_track_;
-};
-
-class Onboarding : public UiState {
- public:
- void entry() override;
-
- void react(const internal::OnboardingNavigate&) override;
+ std::shared_ptr<lua::Property> playback_position_;
- using UiState::react;
-
- private:
- uint8_t progress_;
- bool has_formatted_;
+ std::shared_ptr<lua::Property> queue_position_;
+ std::shared_ptr<lua::Property> queue_size_;
};
class Browse : public UiState {
public:
void entry() override;
- void react(const internal::RecordSelected&) override;
void react(const internal::BackPressed&) override;
- void react(const internal::ShowNowPlaying&) override;
void react(const internal::ShowSettingsPage&) override;
void react(const internal::ReindexDatabase&) override;
@@ -178,16 +165,6 @@ class Browse : public UiState {
using UiState::react;
};
-class Playing : public UiState {
- public:
- void entry() override;
- void exit() override;
-
- void react(const internal::BackPressed&) override;
-
- using UiState::react;
-};
-
class Indexing : public UiState {
public:
void entry() override;
diff --git a/src/ui/modal_add_to_queue.cpp b/src/ui/modal_add_to_queue.cpp
deleted file mode 100644
index e102fae8..00000000
--- a/src/ui/modal_add_to_queue.cpp
+++ /dev/null
@@ -1,182 +0,0 @@
-/*
- * Copyright 2023 jacqueline <me@jacqueline.id.au>
- *
- * SPDX-License-Identifier: GPL-3.0-only
- */
-
-#include "modal_add_to_queue.hpp"
-
-#include "core/lv_event.h"
-#include "core/lv_obj.h"
-#include "core/lv_obj_tree.h"
-#include "esp_log.h"
-
-#include "core/lv_group.h"
-#include "core/lv_obj_pos.h"
-#include "event_queue.hpp"
-#include "extra/layouts/flex/lv_flex.h"
-#include "extra/widgets/list/lv_list.h"
-#include "extra/widgets/menu/lv_menu.h"
-#include "extra/widgets/spinner/lv_spinner.h"
-#include "extra/widgets/tabview/lv_tabview.h"
-#include "hal/lv_hal_disp.h"
-#include "index.hpp"
-#include "misc/lv_area.h"
-#include "misc/lv_color.h"
-#include "source.hpp"
-#include "themes.hpp"
-#include "track_queue.hpp"
-#include "ui_events.hpp"
-#include "ui_fsm.hpp"
-#include "widget_top_bar.hpp"
-#include "widgets/lv_btn.h"
-#include "widgets/lv_label.h"
-
-namespace ui {
-namespace modals {
-
-AddToQueue::AddToQueue(Screen* host,
- audio::TrackQueue& queue,
- std::shared_ptr<playlist::IResetableSource> item,
- bool all_tracks_only)
- : Modal(host), queue_(queue), item_(item), all_tracks_(0) {
- lv_obj_set_layout(root_, LV_LAYOUT_FLEX);
- lv_obj_set_flex_flow(root_, LV_FLEX_FLOW_COLUMN);
- lv_obj_set_flex_align(root_, LV_FLEX_ALIGN_SPACE_EVENLY, LV_FLEX_ALIGN_START,
- LV_FLEX_ALIGN_CENTER);
-
- if (all_tracks_only) {
- all_tracks_ = true;
- } else {
- lv_obj_t* button_container = lv_obj_create(root_);
- lv_obj_set_size(button_container, lv_pct(100), LV_SIZE_CONTENT);
- lv_obj_set_layout(button_container, LV_LAYOUT_FLEX);
- lv_obj_set_flex_flow(button_container, LV_FLEX_FLOW_ROW);
- lv_obj_set_flex_align(button_container, LV_FLEX_ALIGN_SPACE_EVENLY,
- LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER);
-
- selected_track_btn_ = lv_btn_create(button_container);
- lv_obj_t* label = lv_label_create(selected_track_btn_);
- lv_label_set_text(label, "Selected");
- lv_group_add_obj(group_, selected_track_btn_);
- lv_obj_add_state(selected_track_btn_, LV_STATE_CHECKED);
- themes::Theme::instance()->ApplyStyle(selected_track_btn_,
- themes::Style::kTab);
-
- lv_bind(selected_track_btn_, LV_EVENT_CLICKED, [this](lv_obj_t*) {
- lv_obj_add_state(selected_track_btn_, LV_STATE_CHECKED);
- lv_obj_clear_state(all_tracks_btn_, LV_STATE_CHECKED);
- all_tracks_ = false;
- });
-
- all_tracks_btn_ = lv_btn_create(button_container);
- label = lv_label_create(all_tracks_btn_);
- lv_label_set_text(label, "From here");
- lv_group_add_obj(group_, all_tracks_btn_);
- themes::Theme::instance()->ApplyStyle(all_tracks_btn_, themes::Style::kTab);
-
- lv_bind(all_tracks_btn_, LV_EVENT_CLICKED, [this](lv_obj_t*) {
- lv_obj_clear_state(selected_track_btn_, LV_STATE_CHECKED);
- lv_obj_add_state(all_tracks_btn_, LV_STATE_CHECKED);
- all_tracks_ = true;
- });
-
- lv_obj_t* spacer = lv_obj_create(root_);
- lv_obj_set_size(spacer, 1, 4);
- }
-
- lv_obj_t* button_container = lv_obj_create(root_);
- lv_obj_set_size(button_container, lv_pct(100), LV_SIZE_CONTENT);
- lv_obj_set_layout(button_container, LV_LAYOUT_FLEX);
- lv_obj_set_flex_flow(button_container, LV_FLEX_FLOW_ROW);
- lv_obj_set_flex_align(button_container, LV_FLEX_ALIGN_SPACE_EVENLY,
- LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER);
-
- lv_obj_t* btn = lv_btn_create(button_container);
- lv_obj_t* label = lv_label_create(btn);
- lv_label_set_text(label, "Play now");
- lv_group_add_obj(group_, btn);
-
- lv_bind(btn, LV_EVENT_CLICKED, [this](lv_obj_t*) {
- queue_.Clear();
- if (all_tracks_) {
- queue_.IncludeNext(item_);
- } else {
- auto track = item_->Current();
- if (track) {
- queue_.AddNext(*track);
- }
- }
- events::Ui().Dispatch(internal::ModalCancelPressed{});
- events::Ui().Dispatch(internal::ShowNowPlaying{});
- });
-
- bool has_queue = queue.GetCurrent().has_value();
-
- if (has_queue) {
- label = lv_label_create(root_);
- lv_label_set_text(label, "Enqueue");
-
- lv_obj_t* spacer = lv_obj_create(root_);
- lv_obj_set_size(spacer, 1, 4);
-
- button_container = lv_obj_create(root_);
- lv_obj_set_size(button_container, lv_pct(100), LV_SIZE_CONTENT);
- lv_obj_set_layout(button_container, LV_LAYOUT_FLEX);
- lv_obj_set_flex_flow(button_container, LV_FLEX_FLOW_ROW);
- lv_obj_set_flex_align(button_container, LV_FLEX_ALIGN_SPACE_EVENLY,
- LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER);
-
- btn = lv_btn_create(button_container);
- label = lv_label_create(btn);
- lv_label_set_text(label, "Next");
- lv_group_add_obj(group_, btn);
-
- lv_bind(btn, LV_EVENT_CLICKED, [this](lv_obj_t*) {
- if (all_tracks_) {
- queue_.IncludeNext(item_);
- } else {
- queue_.AddNext(item_->Current().value());
- }
- events::Ui().Dispatch(internal::ModalCancelPressed{});
- });
-
- btn = lv_btn_create(button_container);
- label = lv_label_create(btn);
- lv_label_set_text(label, "Last");
- lv_group_add_obj(group_, btn);
-
- lv_bind(btn, LV_EVENT_CLICKED, [this](lv_obj_t*) {
- if (all_tracks_) {
- queue_.IncludeLast(item_);
- } else {
- queue_.AddLast(item_->Current().value());
- }
- events::Ui().Dispatch(internal::ModalCancelPressed{});
- });
- }
-
- lv_obj_t* spacer = lv_obj_create(root_);
- lv_obj_set_size(spacer, 1, 4);
-
- button_container = lv_obj_create(root_);
- lv_obj_set_size(button_container, lv_pct(100), LV_SIZE_CONTENT);
- lv_obj_set_layout(button_container, LV_LAYOUT_FLEX);
- lv_obj_set_flex_flow(button_container, LV_FLEX_FLOW_ROW);
- lv_obj_set_flex_align(button_container, LV_FLEX_ALIGN_END,
- LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER);
-
- btn = lv_btn_create(button_container);
- label = lv_label_create(btn);
- lv_label_set_text(label, "Cancel");
- lv_group_add_obj(group_, btn);
- lv_obj_set_style_text_color(label, lv_palette_main(LV_PALETTE_RED),
- LV_PART_MAIN);
-
- lv_bind(btn, LV_EVENT_CLICKED, [](lv_obj_t*) {
- events::Ui().Dispatch(internal::ModalCancelPressed{});
- });
-}
-
-} // namespace modals
-} // namespace ui
diff --git a/src/ui/screen_lua.cpp b/src/ui/screen_lua.cpp
index ae49ffd5..5130b4f7 100644
--- a/src/ui/screen_lua.cpp
+++ b/src/ui/screen_lua.cpp
@@ -6,9 +6,9 @@
#include "screen_lua.hpp"
-#include "lauxlib.h"
-#include "lua.h"
+#include "core/lv_obj_tree.h"
#include "lua.hpp"
+
#include "luavgl.h"
namespace ui {
diff --git a/src/ui/screen_onboarding.cpp b/src/ui/screen_onboarding.cpp
deleted file mode 100644
index f5ce004f..00000000
--- a/src/ui/screen_onboarding.cpp
+++ /dev/null
@@ -1,146 +0,0 @@
-/*
- * Copyright 2023 jacqueline <me@jacqueline.id.au>
- *
- * SPDX-License-Identifier: GPL-3.0-only
- */
-
-#include "screen_onboarding.hpp"
-
-#include "core/lv_event.h"
-#include "core/lv_obj_pos.h"
-#include "draw/lv_draw_rect.h"
-#include "event_queue.hpp"
-#include "extra/libs/qrcode/lv_qrcode.h"
-#include "extra/widgets/win/lv_win.h"
-#include "font/lv_symbol_def.h"
-#include "misc/lv_color.h"
-#include "ui_events.hpp"
-#include "widgets/lv_btn.h"
-#include "widgets/lv_label.h"
-#include "widgets/lv_switch.h"
-
-static const char kManualUrl[] = "https://tangara.gay/onboarding";
-
-namespace ui {
-namespace screens {
-
-static void next_btn_cb(lv_event_t* ev) {
- events::Ui().Dispatch(internal::OnboardingNavigate{.forwards = true});
-}
-
-static void prev_btn_cb(lv_event_t* ev) {
- events::Ui().Dispatch(internal::OnboardingNavigate{.forwards = false});
-}
-
-Onboarding::Onboarding(const std::pmr::string& title,
- bool show_prev,
- bool show_next) {
- window_ = lv_win_create(root_, 18);
- if (show_prev) {
- prev_button_ = lv_win_add_btn(window_, LV_SYMBOL_LEFT, 20);
- lv_obj_add_event_cb(prev_button_, prev_btn_cb, LV_EVENT_CLICKED, NULL);
- lv_group_add_obj(group_, prev_button_);
- }
- title_ = lv_win_add_title(window_, title.c_str());
- if (show_next) {
- next_button_ = lv_win_add_btn(window_, LV_SYMBOL_RIGHT, 20);
- lv_obj_add_event_cb(next_button_, next_btn_cb, LV_EVENT_CLICKED, NULL);
- lv_group_add_obj(group_, next_button_);
- }
-
- content_ = lv_win_get_content(window_);
- lv_obj_set_layout(content_, LV_LAYOUT_FLEX);
- lv_obj_set_flex_flow(content_, LV_FLEX_FLOW_COLUMN);
- lv_obj_set_flex_align(content_, LV_FLEX_ALIGN_START, LV_FLEX_ALIGN_CENTER,
- LV_FLEX_ALIGN_CENTER);
-}
-
-namespace onboarding {
-
-LinkToManual::LinkToManual() : Onboarding("Welcome!", false, true) {
- lv_obj_t* intro = lv_label_create(content_);
- lv_label_set_text(intro, "For full instructions, see the manual:");
- lv_label_set_long_mode(intro, LV_LABEL_LONG_WRAP);
- lv_obj_set_size(intro, lv_pct(100), LV_SIZE_CONTENT);
-
- lv_obj_t* qr =
- lv_qrcode_create(content_, 80, lv_color_black(), lv_color_white());
- lv_qrcode_update(qr, kManualUrl, sizeof(kManualUrl));
-}
-
-static void create_radio_button(lv_obj_t* parent,
- const std::pmr::string& text) {
- lv_obj_t* obj = lv_checkbox_create(parent);
- lv_checkbox_set_text(obj, text.c_str());
- // TODO: radio styling
-}
-
-Controls::Controls() : Onboarding("Controls", true, true) {
- lv_obj_t* label = lv_label_create(content_);
- lv_label_set_text(label, "this screen changes your control scheme.");
-
- label = lv_label_create(content_);
- lv_label_set_text(label, "how does the touch wheel behave?");
-
- create_radio_button(content_, "iPod-style");
- create_radio_button(content_, "Directional");
- create_radio_button(content_, "One Big Button");
-
- label = lv_label_create(content_);
- lv_label_set_text(label, "how do the side buttons behave?");
-
- create_radio_button(content_, "Adjust volume");
- create_radio_button(content_, "Scroll");
-}
-
-MissingSdCard::MissingSdCard() : Onboarding("SD Card", true, false) {
- lv_obj_t* label = lv_label_create(content_);
- lv_label_set_text(label,
- "It looks like there isn't an SD card present. Please "
- "insert one to continue.");
- lv_label_set_long_mode(label, LV_LABEL_LONG_WRAP);
- lv_obj_set_size(label, lv_pct(100), LV_SIZE_CONTENT);
-}
-
-FormatSdCard::FormatSdCard() : Onboarding("SD Card", true, false) {
- lv_obj_t* label = lv_label_create(content_);
- lv_label_set_text(label,
- "It looks like there is an SD card present, but it has not "
- "been formatted. Would you like to format it?");
- lv_label_set_long_mode(label, LV_LABEL_LONG_WRAP);
- lv_obj_set_size(label, lv_pct(100), LV_SIZE_CONTENT);
-
- lv_obj_t* button = lv_btn_create(content_);
- label = lv_label_create(button);
- lv_label_set_text(label, "Format");
-
- lv_obj_t* exfat_con = lv_obj_create(content_);
- lv_obj_set_layout(exfat_con, LV_LAYOUT_FLEX);
- lv_obj_set_size(exfat_con, lv_pct(100), LV_SIZE_CONTENT);
- lv_obj_set_flex_flow(exfat_con, LV_FLEX_FLOW_ROW);
- lv_obj_set_flex_align(exfat_con, LV_FLEX_ALIGN_SPACE_EVENLY,
- LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_START);
-
- label = lv_label_create(exfat_con);
- lv_label_set_text(label, "Use exFAT");
- lv_switch_create(exfat_con);
-}
-
-InitDatabase::InitDatabase() : Onboarding("Database", true, true) {
- lv_obj_t* label = lv_label_create(content_);
- lv_label_set_text(label,
- "Many of Tangara's browsing features rely building an "
- "index of your music. Would you like to do this now? It "
- "will take some time if you have a large collection.");
- lv_label_set_long_mode(label, LV_LABEL_LONG_WRAP);
- lv_obj_set_size(label, lv_pct(100), LV_SIZE_CONTENT);
-
- lv_obj_t* button = lv_btn_create(content_);
- label = lv_label_create(button);
- lv_label_set_text(label, "Index");
-}
-
-} // namespace onboarding
-
-} // namespace screens
-} // namespace ui
diff --git a/src/ui/screen_playing.cpp b/src/ui/screen_playing.cpp
deleted file mode 100644
index c29d342e..00000000
--- a/src/ui/screen_playing.cpp
+++ /dev/null
@@ -1,338 +0,0 @@
-/*
- * Copyright 2023 jacqueline <me@jacqueline.id.au>
- *
- * SPDX-License-Identifier: GPL-3.0-only
- */
-
-#include "screen_playing.hpp"
-#include <sys/_stdint.h>
-#include <memory>
-
-#include "audio_events.hpp"
-#include "bindey/binding.h"
-#include "core/lv_event.h"
-#include "core/lv_obj.h"
-#include "core/lv_obj_scroll.h"
-#include "core/lv_obj_tree.h"
-#include "database.hpp"
-#include "esp_log.h"
-#include "extra/layouts/flex/lv_flex.h"
-#include "extra/layouts/grid/lv_grid.h"
-#include "font/lv_symbol_def.h"
-#include "future_fetcher.hpp"
-#include "lvgl.h"
-
-#include "core/lv_group.h"
-#include "core/lv_obj_pos.h"
-#include "event_queue.hpp"
-#include "extra/widgets/list/lv_list.h"
-#include "extra/widgets/menu/lv_menu.h"
-#include "extra/widgets/spinner/lv_spinner.h"
-#include "future_fetcher.hpp"
-#include "hal/lv_hal_disp.h"
-#include "index.hpp"
-#include "misc/lv_anim.h"
-#include "misc/lv_area.h"
-#include "misc/lv_color.h"
-#include "misc/lv_txt.h"
-#include "model_playback.hpp"
-#include "model_top_bar.hpp"
-#include "track.hpp"
-#include "ui_events.hpp"
-#include "ui_fsm.hpp"
-#include "widget_top_bar.hpp"
-#include "widgets/lv_btn.h"
-#include "widgets/lv_img.h"
-#include "widgets/lv_label.h"
-#include "widgets/lv_slider.h"
-
-namespace ui {
-namespace screens {
-
-static void above_fold_focus_cb(lv_event_t* ev) {
- if (ev->user_data == NULL) {
- return;
- }
- Playing* instance = reinterpret_cast<Playing*>(ev->user_data);
- instance->OnFocusAboveFold();
-}
-
-static void below_fold_focus_cb(lv_event_t* ev) {
- if (ev->user_data == NULL) {
- return;
- }
- Playing* instance = reinterpret_cast<Playing*>(ev->user_data);
- instance->OnFocusBelowFold();
-}
-
-static lv_style_t scrubber_style;
-
-auto info_label(lv_obj_t* parent) -> lv_obj_t* {
- lv_obj_t* label = lv_label_create(parent);
- lv_obj_set_size(label, lv_pct(100), LV_SIZE_CONTENT);
- lv_label_set_text(label, "");
- lv_label_set_long_mode(label, LV_LABEL_LONG_DOT);
- lv_obj_set_style_text_align(label, LV_TEXT_ALIGN_CENTER, 0);
- lv_obj_center(label);
-
- lv_obj_set_style_bg_color(label, lv_color_black(), LV_STATE_FOCUSED);
- return label;
-}
-
-auto Playing::control_button(lv_obj_t* parent, char* icon) -> lv_obj_t* {
- lv_obj_t* button = lv_btn_create(parent);
- lv_obj_set_size(button, LV_SIZE_CONTENT, LV_SIZE_CONTENT);
-
- lv_obj_clear_flag(button, LV_OBJ_FLAG_SCROLL_ON_FOCUS);
- lv_obj_add_event_cb(button, above_fold_focus_cb, LV_EVENT_FOCUSED, this);
-
- lv_obj_t* icon_obj = lv_img_create(button);
- lv_img_set_src(icon_obj, icon);
-
- return button;
-}
-
-auto Playing::next_up_label(lv_obj_t* parent, const std::pmr::string& text)
- -> lv_obj_t* {
- lv_obj_t* button = lv_list_add_btn(parent, NULL, text.c_str());
- lv_label_set_long_mode(lv_obj_get_child(button, -1), LV_LABEL_LONG_DOT);
- lv_obj_add_event_cb(button, below_fold_focus_cb, LV_EVENT_FOCUSED, this);
- lv_group_add_obj(group_, button);
- return button;
-}
-
-Playing::Playing(models::TopBar& top_bar_model,
- models::Playback& playback_model,
- std::weak_ptr<database::Database> db,
- audio::TrackQueue& queue)
- : db_(db),
- queue_(queue),
- current_track_(),
- next_tracks_(),
- new_track_(),
- new_next_tracks_() {
- lv_obj_set_layout(content_, LV_LAYOUT_FLEX);
- lv_group_set_wrap(group_, false);
-
- lv_obj_set_size(content_, lv_pct(100), lv_disp_get_ver_res(NULL));
- lv_obj_set_flex_flow(content_, LV_FLEX_FLOW_COLUMN);
- lv_obj_set_flex_align(content_, LV_FLEX_ALIGN_START, LV_FLEX_ALIGN_START,
- LV_FLEX_ALIGN_START);
-
- lv_obj_set_scrollbar_mode(content_, LV_SCROLLBAR_MODE_OFF);
-
- lv_obj_t* above_fold_container = lv_obj_create(content_);
- lv_obj_set_layout(above_fold_container, LV_LAYOUT_FLEX);
- lv_obj_set_size(above_fold_container, lv_pct(100), lv_disp_get_ver_res(NULL));
- lv_obj_set_flex_flow(above_fold_container, LV_FLEX_FLOW_COLUMN);
- lv_obj_set_flex_align(above_fold_container, LV_FLEX_ALIGN_START,
- LV_FLEX_ALIGN_START, LV_FLEX_ALIGN_START);
-
- widgets::TopBar::Configuration config{
- .show_back_button = true,
- .title = "Now Playing",
- };
- CreateTopBar(above_fold_container, config, top_bar_model);
-
- lv_obj_t* now_playing_container = lv_obj_create(above_fold_container);
- lv_obj_set_layout(now_playing_container, LV_LAYOUT_FLEX);
- lv_obj_set_width(now_playing_container, lv_pct(100));
- lv_obj_set_flex_grow(now_playing_container, 1);
- lv_obj_set_flex_flow(now_playing_container, LV_FLEX_FLOW_COLUMN);
- lv_obj_set_flex_align(now_playing_container, LV_FLEX_ALIGN_SPACE_BETWEEN,
- LV_FLEX_ALIGN_START, LV_FLEX_ALIGN_START);
-
- lv_obj_set_style_pad_left(now_playing_container, 4, LV_PART_MAIN);
- lv_obj_set_style_pad_right(now_playing_container, 4, LV_PART_MAIN);
-
- lv_obj_t* info_container = lv_obj_create(now_playing_container);
- lv_obj_set_layout(info_container, LV_LAYOUT_FLEX);
- lv_obj_set_width(info_container, lv_pct(100));
- lv_obj_set_flex_grow(info_container, 1);
- lv_obj_set_flex_flow(info_container, LV_FLEX_FLOW_COLUMN);
- lv_obj_set_flex_align(info_container, LV_FLEX_ALIGN_CENTER,
- LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER);
-
- lv_obj_t* artist_label = info_label(info_container);
- lv_obj_t* album_label = info_label(info_container);
- lv_obj_t* title_label = info_label(info_container);
-
- lv_obj_t* scrubber = lv_slider_create(now_playing_container);
- lv_obj_set_size(scrubber, lv_pct(100), 5);
-
- lv_style_init(&scrubber_style);
- lv_style_set_bg_color(&scrubber_style, lv_color_black());
- lv_obj_add_style(scrubber, &scrubber_style, LV_PART_INDICATOR);
-
- lv_group_add_obj(group_, scrubber);
-
- data_bindings_.emplace_back(playback_model.current_track.onChangedAndNow(
- [=, this](const std::optional<database::TrackId>& id) {
- if (!id) {
- return;
- }
- if (current_track_.get() && current_track_.get()->data().id == *id) {
- return;
- }
- auto db = db_.lock();
- if (!db) {
- return;
- }
- // Clear the playback progress whilst we're waiting for the next
- // track's data to load.
- lv_slider_set_value(scrubber, 0, LV_ANIM_OFF);
- new_track_.reset(
- new database::FutureFetcher<std::shared_ptr<database::Track>>(
- db->GetTrack(*id)));
- }));
-
- data_bindings_.emplace_back(current_track_.onChangedAndNow(
- [=](const std::shared_ptr<database::Track>& t) {
- if (!t) {
- return;
- }
- lv_label_set_text(
- artist_label,
- t->tags().at(database::Tag::kArtist).value_or("").c_str());
- lv_label_set_text(
- album_label,
- t->tags().at(database::Tag::kAlbum).value_or("").c_str());
- lv_label_set_text(title_label, t->TitleOrFilename().c_str());
- }));
-
- data_bindings_.emplace_back(
- playback_model.current_track_duration.onChangedAndNow([=](uint32_t d) {
- lv_slider_set_range(scrubber, 0, std::max<uint32_t>(1, d));
- }));
- data_bindings_.emplace_back(
- playback_model.current_track_position.onChangedAndNow(
- [=](uint32_t p) { lv_slider_set_value(scrubber, p, LV_ANIM_OFF); }));
-
- lv_obj_t* spacer = lv_obj_create(now_playing_container);
- lv_obj_set_size(spacer, 1, 4);
-
- lv_obj_t* controls_container = lv_obj_create(now_playing_container);
- lv_obj_set_size(controls_container, lv_pct(100), 20);
- lv_obj_set_flex_flow(controls_container, LV_FLEX_FLOW_ROW);
- lv_obj_set_flex_align(controls_container, LV_FLEX_ALIGN_SPACE_EVENLY,
- LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER);
-
- lv_obj_t* play_pause_control =
- control_button(controls_container, LV_SYMBOL_PLAY);
- lv_group_add_obj(group_, play_pause_control);
- lv_bind(play_pause_control, LV_EVENT_CLICKED, [=](lv_obj_t*) {
- events::Audio().Dispatch(audio::TogglePlayPause{});
- });
-
- lv_obj_t* track_prev = control_button(controls_container, LV_SYMBOL_PREV);
- lv_group_add_obj(group_, track_prev);
- lv_bind(track_prev, LV_EVENT_CLICKED, [=](lv_obj_t*) { queue_.Previous(); });
-
- lv_obj_t* track_next = control_button(controls_container, LV_SYMBOL_NEXT);
- lv_group_add_obj(group_, track_next);
- lv_bind(track_next, LV_EVENT_CLICKED, [=](lv_obj_t*) { queue_.Next(); });
-
- lv_obj_t* shuffle = control_button(controls_container, LV_SYMBOL_SHUFFLE);
- lv_group_add_obj(group_, shuffle);
- // lv_bind(shuffle, LV_EVENT_CLICKED, [=](lv_obj_t*) { queue_ });
-
- lv_group_add_obj(group_, control_button(controls_container, LV_SYMBOL_LOOP));
-
- next_up_header_ = lv_obj_create(now_playing_container);
- lv_obj_set_size(next_up_header_, lv_pct(100), 15);
- lv_obj_set_flex_flow(next_up_header_, LV_FLEX_FLOW_ROW);
- lv_obj_set_flex_align(next_up_header_, LV_FLEX_ALIGN_START, LV_FLEX_ALIGN_END,
- LV_FLEX_ALIGN_END);
-
- next_up_label_ = lv_label_create(next_up_header_);
- lv_label_set_text(next_up_label_, "");
- lv_obj_set_height(next_up_label_, lv_pct(100));
- lv_obj_set_flex_grow(next_up_label_, 1);
-
- next_up_hint_ = lv_label_create(next_up_header_);
- lv_label_set_text(next_up_hint_, "");
- lv_obj_set_size(next_up_hint_, LV_SIZE_CONTENT, lv_pct(100));
-
- next_up_container_ = lv_list_create(content_);
- lv_obj_set_layout(next_up_container_, LV_LAYOUT_FLEX);
- lv_obj_set_size(next_up_container_, lv_pct(100), lv_disp_get_ver_res(NULL));
- lv_obj_set_flex_flow(next_up_container_, LV_FLEX_FLOW_COLUMN);
- lv_obj_set_flex_align(next_up_container_, LV_FLEX_ALIGN_START,
- LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER);
-
- data_bindings_.emplace_back(playback_model.upcoming_tracks.onChangedAndNow(
- [=, this](const std::vector<database::TrackId>& ids) {
- auto db = db_.lock();
- if (!db) {
- return;
- }
- lv_label_set_text(next_up_label_, "Next up");
-
- new_next_tracks_.reset(new database::FutureFetcher<
- std::vector<std::shared_ptr<database::Track>>>(
- db->GetBulkTracks(ids)));
- }));
-
- data_bindings_.emplace_back(next_tracks_.onChangedAndNow(
- [=](const std::vector<std::shared_ptr<database::Track>>& tracks) {
- // TODO(jacqueline): Do a proper diff to maintain selection.
- int children = lv_obj_get_child_cnt(next_up_container_);
- while (children > 0) {
- lv_obj_del(lv_obj_get_child(next_up_container_, 0));
- children--;
- }
-
- if (tracks.empty()) {
- lv_label_set_text(next_up_label_, "Nothing queued");
- lv_label_set_text(next_up_hint_, "");
- return;
- } else {
- lv_label_set_text(next_up_label_, "Next up");
- lv_label_set_text(next_up_hint_, "");
- }
-
- for (const auto& track : tracks) {
- lv_group_add_obj(group_, next_up_label(next_up_container_,
- track->TitleOrFilename()));
- }
- }));
-}
-
-Playing::~Playing() {}
-
-auto Playing::Tick() -> void {
- if (new_track_ && new_track_->Finished()) {
- auto res = new_track_->Result();
- new_track_.reset();
- if (res) {
- current_track_(*res);
- }
- }
- if (new_next_tracks_ && new_next_tracks_->Finished()) {
- auto res = new_next_tracks_->Result();
- new_next_tracks_.reset();
- if (res) {
- std::vector<std::shared_ptr<database::Track>> filtered;
- for (const auto& t : *res) {
- if (t) {
- filtered.push_back(t);
- }
- }
- next_tracks_.set(filtered);
- }
- }
-}
-
-auto Playing::OnFocusAboveFold() -> void {
- lv_obj_scroll_to_y(content_, 0, LV_ANIM_ON);
-}
-
-auto Playing::OnFocusBelowFold() -> void {
- if (lv_obj_get_scroll_y(content_) < lv_obj_get_y(next_up_header_) + 20) {
- lv_obj_scroll_to_y(content_, lv_obj_get_y(next_up_header_) + 20,
- LV_ANIM_ON);
- }
-}
-
-} // namespace screens
-} // namespace ui
diff --git a/src/ui/screen_track_browser.cpp b/src/ui/screen_track_browser.cpp
deleted file mode 100644
index c7b035ad..00000000
--- a/src/ui/screen_track_browser.cpp
+++ /dev/null
@@ -1,431 +0,0 @@
-/*
- * Copyright 2023 jacqueline <me@jacqueline.id.au>
- *
- * SPDX-License-Identifier: GPL-3.0-only
- */
-
-#include <algorithm>
-#include <memory>
-
-#include "core/lv_obj.h"
-#include "core/lv_obj_scroll.h"
-#include "core/lv_obj_tree.h"
-#include "database.hpp"
-#include "event_queue.hpp"
-#include "extra/layouts/flex/lv_flex.h"
-#include "font/lv_symbol_def.h"
-#include "lvgl.h"
-#include "misc/lv_anim.h"
-#include "misc/lv_color.h"
-#include "model_top_bar.hpp"
-
-#include "core/lv_event.h"
-#include "esp_log.h"
-
-#include "core/lv_group.h"
-#include "core/lv_obj_pos.h"
-#include "extra/widgets/list/lv_list.h"
-#include "extra/widgets/menu/lv_menu.h"
-#include "extra/widgets/spinner/lv_spinner.h"
-#include "hal/lv_hal_disp.h"
-#include "misc/lv_area.h"
-#include "screen_track_browser.hpp"
-#include "source.hpp"
-#include "themes.hpp"
-#include "track_queue.hpp"
-#include "ui_events.hpp"
-#include "ui_fsm.hpp"
-#include "widget_top_bar.hpp"
-#include "widgets/lv_label.h"
-
-[[maybe_unused]] static constexpr char kTag[] = "browser";
-
-static constexpr int kMaxPages = 4;
-static constexpr int kPageBuffer = 6;
-
-namespace ui {
-namespace screens {
-
-static void item_click_cb(lv_event_t* ev) {
- if (ev->user_data == NULL) {
- return;
- }
- TrackBrowser* instance = reinterpret_cast<TrackBrowser*>(ev->user_data);
- instance->OnItemClicked(ev);
-}
-
-static void item_select_cb(lv_event_t* ev) {
- if (ev->user_data == NULL) {
- return;
- }
- TrackBrowser* instance = reinterpret_cast<TrackBrowser*>(ev->user_data);
- instance->OnItemSelected(ev);
-}
-
-TrackBrowser::TrackBrowser(
- models::TopBar& top_bar_model,
- audio::TrackQueue& queue,
- std::weak_ptr<database::Database> db,
- const std::pmr::vector<std::pmr::string>& crumbs,
- std::future<database::Result<database::IndexRecord>*>&& initial_page)
- : queue_(queue),
- db_(db),
- play_button_(nullptr),
- enqueue_button_(nullptr),
- list_(nullptr),
- loading_indicator_(nullptr),
- breadcrumbs_(crumbs),
- loading_pos_(END),
- loading_page_(move(initial_page)),
- initial_page_(),
- current_pages_() {
- lv_obj_set_layout(content_, LV_LAYOUT_FLEX);
- lv_obj_set_flex_flow(content_, LV_FLEX_FLOW_COLUMN);
- lv_obj_set_flex_align(content_, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER,
- LV_FLEX_ALIGN_CENTER);
-
- widgets::TopBar::Configuration config{
- .show_back_button = true,
- .title = breadcrumbs_[0],
- };
- auto top_bar = CreateTopBar(content_, config, top_bar_model);
- back_button_ = top_bar->button();
-
- lv_obj_t* scrollable = lv_obj_create(content_);
- lv_obj_set_width(scrollable, lv_pct(100));
- lv_obj_set_flex_grow(scrollable, 1);
- lv_obj_set_layout(scrollable, LV_LAYOUT_FLEX);
- lv_obj_set_flex_flow(scrollable, LV_FLEX_FLOW_COLUMN);
- lv_obj_set_flex_align(scrollable, LV_FLEX_ALIGN_START, LV_FLEX_ALIGN_START,
- LV_FLEX_ALIGN_START);
-
- if (crumbs.size() > 1) {
- lv_obj_t* header = lv_obj_create(scrollable);
- lv_obj_set_size(header, lv_pct(100), LV_SIZE_CONTENT);
- lv_obj_set_layout(header, LV_LAYOUT_FLEX);
- lv_obj_set_flex_flow(header, LV_FLEX_FLOW_COLUMN);
- lv_obj_set_flex_align(header, LV_FLEX_ALIGN_START, LV_FLEX_ALIGN_START,
- LV_FLEX_ALIGN_START);
-
- lv_obj_set_style_pad_left(header, 4, LV_PART_MAIN);
- lv_obj_set_style_pad_right(header, 4, LV_PART_MAIN);
-
- lv_obj_t* spacer = lv_obj_create(header);
- lv_obj_set_size(spacer, 1, 2);
-
- for (size_t i = 1; i < crumbs.size(); i++) {
- lv_obj_t* crumb = lv_label_create(header);
- lv_label_set_text(crumb, crumbs[i].c_str());
-
- spacer = lv_obj_create(header);
- lv_obj_set_size(spacer, 1, 2);
- }
-
- spacer = lv_obj_create(header);
- lv_obj_set_size(spacer, 1, 2);
-
- lv_obj_t* buttons_container = lv_obj_create(header);
- lv_obj_set_width(buttons_container, lv_pct(100));
- lv_obj_set_height(buttons_container, LV_SIZE_CONTENT);
- lv_obj_set_layout(buttons_container, LV_LAYOUT_FLEX);
- lv_obj_set_flex_flow(buttons_container, LV_FLEX_FLOW_ROW);
- lv_obj_set_flex_align(buttons_container, LV_FLEX_ALIGN_END,
- LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER);
-
- lv_obj_t* label;
-
- play_button_ = lv_btn_create(buttons_container);
- label = lv_label_create(play_button_);
- lv_label_set_text(label, "Play all");
- lv_group_add_obj(group_, play_button_);
- themes::Theme::instance()->ApplyStyle(play_button_,
- themes::Style::kButtonPrimary);
-
- lv_bind(play_button_, LV_EVENT_CLICKED, [&](lv_obj_t*) {
- if (!initial_page_) {
- return;
- }
- queue_.Clear();
- queue_.IncludeNext(playlist::CreateSourceFromResults(db_, initial_page_));
- events::Ui().Dispatch(internal::ShowNowPlaying{});
- });
-
- if (queue_.GetCurrent()) {
- spacer = lv_obj_create(buttons_container);
- lv_obj_set_size(spacer, 4, 1);
-
- enqueue_button_ = lv_btn_create(buttons_container);
- label = lv_label_create(enqueue_button_);
- lv_label_set_text(label, "Enqueue");
- lv_group_add_obj(group_, enqueue_button_);
- themes::Theme::instance()->ApplyStyle(enqueue_button_,
- themes::Style::kButtonPrimary);
-
- lv_bind(enqueue_button_, LV_EVENT_CLICKED, [&](lv_obj_t*) {
- if (!initial_page_) {
- return;
- }
- queue_.IncludeNext(
- playlist::CreateSourceFromResults(db_, initial_page_));
- });
- }
-
- lv_obj_set_style_border_width(header, 1, LV_PART_MAIN);
- lv_obj_set_style_border_color(header, lv_color_black(), LV_PART_MAIN);
- lv_obj_set_style_border_side(header, LV_BORDER_SIDE_BOTTOM, LV_PART_MAIN);
-
- spacer = lv_obj_create(header);
- lv_obj_set_size(spacer, 1, 4);
-
- lv_obj_set_style_border_width(header, 1, LV_PART_MAIN);
- lv_obj_set_style_border_color(
- header, lv_palette_lighten(LV_PALETTE_GREY, 3), LV_PART_MAIN);
- }
-
- list_ = lv_list_create(scrollable);
- lv_obj_set_size(list_, lv_pct(100), LV_SIZE_CONTENT);
-
- // The default scrollbar is deceptive because we load in items progressively.
- // TODO/FIXME: this doesn't actually turn off the scrollbar, it seems.
- lv_obj_set_scrollbar_mode(content_, LV_SCROLLBAR_MODE_OFF);
- // Wrapping behaves in surprising ways, again due to progressing loading.
- lv_group_set_wrap(group_, false);
-}
-
-auto TrackBrowser::Tick() -> void {
- if (!loading_page_) {
- return;
- }
- if (!loading_page_->valid()) {
- // TODO(jacqueline): error case.
- return;
- }
- if (loading_page_->wait_for(std::chrono::seconds(0)) ==
- std::future_status::ready) {
- std::shared_ptr<database::Result<database::IndexRecord>> result{
- loading_page_->get()};
- AddResults(loading_pos_.value_or(END), result);
-
- loading_page_.reset();
- loading_pos_.reset();
- }
-}
-
-auto TrackBrowser::OnItemSelected(lv_event_t* ev) -> void {
- auto index = GetItemIndex(lv_event_get_target(ev));
- if (!index) {
- return;
- }
- if (index < kPageBuffer) {
- FetchNewPage(START);
- return;
- }
- if (index > GetNumRecords() - kPageBuffer) {
- FetchNewPage(END);
- return;
- }
-}
-
-auto TrackBrowser::OnItemClicked(lv_event_t* ev) -> void {
- auto res = GetItemIndex(lv_event_get_target(ev));
- if (!res) {
- return;
- }
-
- auto index = *res;
- for (const auto& page : current_pages_) {
- for (std::size_t i = 0; i < page->values().size(); i++) {
- if (index == 0) {
- auto text = page->values()[i]->text();
- auto crumbs = breadcrumbs_;
- crumbs.push_back(text.value());
- events::Ui().Dispatch(internal::RecordSelected{
- .show_menu = ev->code == LV_EVENT_LONG_PRESSED,
- .new_crumbs = crumbs,
- .initial_page = initial_page_,
- .page = page,
- .record = i,
- });
- return;
- }
- index--;
- }
- }
-}
-
-auto TrackBrowser::AddLoadingIndictor(Position pos) -> void {
- if (loading_indicator_) {
- return;
- }
- loading_indicator_ = lv_list_add_text(list_, "Loading...");
- if (pos == START) {
- lv_obj_move_to_index(loading_indicator_, 0);
- }
-}
-
-auto TrackBrowser::AddResults(
- Position pos,
- std::shared_ptr<database::Result<database::IndexRecord>> results) -> void {
- if (loading_indicator_ != nullptr) {
- lv_obj_del(loading_indicator_);
- loading_indicator_ = nullptr;
- }
-
- if (initial_page_ == nullptr) {
- initial_page_ = results;
- }
-
- auto fn = [&](const std::shared_ptr<database::IndexRecord>& record) {
- auto text = record->text();
- if (!text) {
- // TODO(jacqueline): Display category-specific text.
- text = "[ no data ]";
- }
- lv_obj_t* item = lv_list_add_btn(list_, NULL, text->c_str());
- lv_label_set_long_mode(lv_obj_get_child(item, -1), LV_LABEL_LONG_DOT);
- lv_obj_add_event_cb(item, item_click_cb, LV_EVENT_CLICKED, this);
- lv_obj_add_event_cb(item, item_click_cb, LV_EVENT_LONG_PRESSED, this);
- lv_obj_add_event_cb(item, item_select_cb, LV_EVENT_FOCUSED, this);
-
- if (pos == START) {
- lv_obj_move_to_index(item, 0);
- }
- };
-
- lv_obj_t* focused = lv_group_get_focused(group_);
-
- // Adding objects at the start of the list will artificially scroll the list
- // up. Scroll it down by the height we're adding so that the user doesn't
- // notice any jank.
- if (pos == START) {
- int num_to_add = results->values().size();
- // Assuming that all items are the same height, this item's y pos should be
- // exactly the height of the new items.
- lv_obj_t* representative_item = lv_obj_get_child(list_, num_to_add);
- if (representative_item != nullptr) {
- int scroll_adjustment = lv_obj_get_y(representative_item);
- lv_obj_scroll_by(list_, 0, -scroll_adjustment, LV_ANIM_OFF);
- }
- }
-
- switch (pos) {
- case START:
- std::for_each(results->values().rbegin(), results->values().rend(), fn);
- current_pages_.push_front(results);
- break;
- case END:
- std::for_each(results->values().begin(), results->values().end(), fn);
- current_pages_.push_back(results);
- break;
- }
-
- lv_group_remove_all_objs(group_);
- lv_group_add_obj(group_, back_button_);
- if (play_button_) {
- lv_group_add_obj(group_, play_button_);
- }
- if (enqueue_button_) {
- lv_group_add_obj(group_, enqueue_button_);
- }
- int num_children = lv_obj_get_child_cnt(list_);
- for (int i = 0; i < num_children; i++) {
- lv_group_add_obj(group_, lv_obj_get_child(list_, i));
- }
- lv_group_focus_obj(focused);
-}
-
-auto TrackBrowser::DropPage(Position pos) -> void {
- if (pos == START) {
- // Removing objects from the start of the list will artificially scroll the
- // list down. Scroll it up by the height we're removing so that the user
- // doesn't notice any jank.
- int num_to_remove = current_pages_.front()->values().size();
- lv_obj_t* new_top_obj = lv_obj_get_child(list_, num_to_remove);
- if (new_top_obj != nullptr) {
- int scroll_adjustment = lv_obj_get_y(new_top_obj);
- lv_obj_scroll_by(list_, 0, scroll_adjustment, LV_ANIM_OFF);
- }
-
- for (int i = 0; i < current_pages_.front()->values().size(); i++) {
- lv_obj_t* item = lv_obj_get_child(list_, 0);
- if (item == NULL) {
- continue;
- }
- lv_obj_del(item);
- }
- current_pages_.pop_front();
- } else if (pos == END) {
- for (int i = 0; i < current_pages_.back()->values().size(); i++) {
- lv_obj_t* item = lv_obj_get_child(list_, lv_obj_get_child_cnt(list_) - 1);
- if (item == NULL) {
- continue;
- }
- lv_group_remove_obj(item);
- lv_obj_del(item);
- }
- current_pages_.pop_back();
- }
-}
-
-auto TrackBrowser::FetchNewPage(Position pos) -> void {
- if (loading_page_) {
- return;
- }
-
- std::optional<database::Continuation> cont;
- switch (pos) {
- case START:
- cont = current_pages_.front()->prev_page();
- break;
- case END:
- cont = current_pages_.back()->next_page();
- break;
- }
- if (!cont) {
- return;
- }
-
- auto db = db_.lock();
- if (!db) {
- return;
- }
-
- // If we already have a complete set of pages, drop the page that's furthest
- // away.
- if (current_pages_.size() >= kMaxPages) {
- switch (pos) {
- case START:
- DropPage(END);
- break;
- case END:
- DropPage(START);
- break;
- }
- }
-
- loading_pos_ = pos;
- loading_page_ = db->GetPage<database::IndexRecord>(&cont.value());
-}
-
-auto TrackBrowser::GetNumRecords() -> std::size_t {
- return lv_obj_get_child_cnt(list_) - (loading_indicator_ != nullptr ? 1 : 0);
-}
-
-auto TrackBrowser::GetItemIndex(lv_obj_t* obj) -> std::optional<std::size_t> {
- std::size_t child_count = lv_obj_get_child_cnt(list_);
- std::size_t index = 0;
- for (int i = 0; i < child_count; i++) {
- lv_obj_t* child = lv_obj_get_child(list_, i);
- if (child == loading_indicator_) {
- continue;
- }
- if (child == obj) {
- return index;
- }
- index++;
- }
- return {};
-}
-
-} // namespace screens
-} // namespace ui
diff --git a/src/ui/ui_fsm.cpp b/src/ui/ui_fsm.cpp
index 5b4ea2a7..557dc1a3 100644
--- a/src/ui/ui_fsm.cpp
+++ b/src/ui/ui_fsm.cpp
@@ -30,7 +30,6 @@
#include "event_queue.hpp"
#include "gpios.hpp"
#include "lvgl_task.hpp"
-#include "modal_add_to_queue.hpp"
#include "modal_confirm.hpp"
#include "modal_progress.hpp"
#include "model_playback.hpp"
@@ -39,11 +38,8 @@
#include "relative_wheel.hpp"
#include "screen.hpp"
#include "screen_lua.hpp"
-#include "screen_onboarding.hpp"
-#include "screen_playing.hpp"
#include "screen_settings.hpp"
#include "screen_splash.hpp"
-#include "screen_track_browser.hpp"
#include "source.hpp"
#include "spiffs.hpp"
#include "storage.hpp"
@@ -51,15 +47,12 @@
#include "touchwheel.hpp"
#include "track_queue.hpp"
#include "ui_events.hpp"
-#include "widget_top_bar.hpp"
#include "widgets/lv_label.h"
namespace ui {
[[maybe_unused]] static constexpr char kTag[] = "ui_fsm";
-static const std::size_t kRecordsPerPage = 15;
-
std::unique_ptr<UiTask> UiState::sTask;
std::shared_ptr<system_fsm::ServiceLocator> UiState::sServices;
std::unique_ptr<drivers::Display> UiState::sDisplay;
@@ -116,27 +109,17 @@ void UiState::react(const system_fsm::BatteryStateChanged& ev) {
sTopBarModel.battery_state.set(ev.new_state);
}
-void UiState::react(const audio::PlaybackStarted&) {
- sPlaybackModel.is_playing.set(true);
-}
+void UiState::react(const audio::PlaybackStarted&) {}
-void UiState::react(const audio::PlaybackFinished&) {
- sPlaybackModel.is_playing.set(false);
-}
+void UiState::react(const audio::PlaybackFinished&) {}
-void UiState::react(const audio::PlaybackUpdate& ev) {
- sPlaybackModel.current_track_duration.set(ev.seconds_total);
- sPlaybackModel.current_track_position.set(ev.seconds_elapsed);
-}
+void UiState::react(const audio::PlaybackUpdate& ev) {}
void UiState::react(const audio::QueueUpdate&) {
auto& queue = sServices->track_queue();
bool had_queue = sPlaybackModel.current_track.get().has_value();
sPlaybackModel.current_track.set(queue.GetCurrent());
sPlaybackModel.upcoming_tracks.set(queue.GetUpcoming(10));
- if (!had_queue) {
- transit<states::Playing>();
- }
}
void UiState::react(const internal::ControlSchemeChanged&) {
@@ -192,8 +175,13 @@ void Lua::entry() {
battery_charging_ = std::make_shared<lua::Property>(bat.is_charging);
bluetooth_en_ = std::make_shared<lua::Property>(false);
+
+ queue_position_ = std::make_shared<lua::Property>(0);
+ queue_size_ = std::make_shared<lua::Property>(0);
+
playback_playing_ = std::make_shared<lua::Property>(false);
playback_track_ = std::make_shared<lua::Property>();
+ playback_position_ = std::make_shared<lua::Property>();
sLua.reset(lua::LuaThread::Start(*sServices, sCurrentScreen->content()));
sLua->bridge().AddPropertyModule("power",
@@ -211,7 +199,12 @@ void Lua::entry() {
{
{"playing", playback_playing_},
{"track", playback_track_},
+ {"position", playback_position_},
});
+ sLua->bridge().AddPropertyModule("queue", {
+ {"position", queue_position_},
+ {"size", queue_size_},
+ });
sLua->bridge().AddPropertyModule(
"backstack",
{
@@ -233,7 +226,7 @@ auto Lua::PushLuaScreen(lua_State* s) -> int {
auto new_screen = std::make_shared<screens::Lua>();
// Tell lvgl about the new roots.
- luavgl_set_root(s, new_screen->root());
+ luavgl_set_root(s, new_screen->content());
lv_group_set_default(new_screen->group());
// Call the constructor for this screen.
@@ -252,7 +245,7 @@ auto Lua::PushLuaScreen(lua_State* s) -> int {
auto Lua::PopLuaScreen(lua_State* s) -> int {
PopScreen();
- luavgl_set_root(s, sCurrentScreen->root());
+ luavgl_set_root(s, sCurrentScreen->content());
lv_group_set_default(sCurrentScreen->group());
return 0;
}
@@ -265,25 +258,6 @@ void Lua::react(const OnLuaError& err) {
ESP_LOGE("lua", "%s", err.message.c_str());
}
-void Lua::react(const internal::IndexSelected& ev) {
- auto db = sServices->database().lock();
- if (!db) {
- return;
- }
-
- ESP_LOGI(kTag, "selected index %u", ev.id);
- auto query = db->GetTracksByIndex(ev.id, kRecordsPerPage);
- std::pmr::vector<std::pmr::string> crumbs = {""};
- PushScreen(std::make_shared<screens::TrackBrowser>(
- sTopBarModel, sServices->track_queue(), sServices->database(), crumbs,
- std::move(query)));
- transit<Browse>();
-}
-
-void Lua::react(const internal::ShowNowPlaying&) {
- transit<Playing>();
-}
-
void Lua::react(const internal::ShowSettingsPage& ev) {
PushScreen(std::shared_ptr<Screen>(new screens::Settings(sTopBarModel)));
transit<Browse>();
@@ -294,71 +268,27 @@ void Lua::react(const system_fsm::BatteryStateChanged& ev) {
battery_mv_->Update(static_cast<int>(ev.new_state.millivolts));
}
-void Lua::react(const audio::PlaybackStarted&) {
- playback_playing_->Update(true);
+void Lua::react(const audio::QueueUpdate&) {
+ auto& queue = sServices->track_queue();
+ queue_size_->Update(static_cast<int>(queue.Size()));
+ queue_position_->Update(static_cast<int>(queue.Position()));
}
-void Lua::react(const audio::PlaybackFinished&) {
- playback_playing_->Update(false);
+void Lua::react(const audio::PlaybackStarted& ev) {
+ playback_playing_->Update(true);
}
-void Onboarding::entry() {
- progress_ = 0;
- has_formatted_ = false;
- sCurrentScreen.reset(new screens::onboarding::LinkToManual());
+void Lua::react(const audio::PlaybackUpdate& ev) {
+ playback_track_->Update(*ev.track);
+ playback_position_->Update(static_cast<int>(ev.seconds_elapsed));
}
-void Onboarding::react(const internal::OnboardingNavigate& ev) {
- int dir = ev.forwards ? 1 : -1;
- progress_ += dir;
-
- for (;;) {
- if (progress_ == 0) {
- sCurrentScreen.reset(new screens::onboarding::LinkToManual());
- return;
- } else if (progress_ == 1) {
- sCurrentScreen.reset(new screens::onboarding::Controls());
- return;
- } else if (progress_ == 2) {
- if (sServices->sd() == drivers::SdState::kNotPresent) {
- sCurrentScreen.reset(new screens::onboarding::MissingSdCard());
- return;
- } else {
- progress_ += dir;
- }
- } else if (progress_ == 3) {
- if (sServices->sd() == drivers::SdState::kNotFormatted) {
- has_formatted_ = true;
- sCurrentScreen.reset(new screens::onboarding::FormatSdCard());
- return;
- } else {
- progress_ += dir;
- }
- } else if (progress_ == 4) {
- if (has_formatted_) {
- // If we formatted the SD card during this onboarding flow, then there
- // is no music that needs indexing.
- progress_ += dir;
- } else {
- sCurrentScreen.reset(new screens::onboarding::InitDatabase());
- return;
- }
- } else {
- // We finished onboarding! Ensure this flow doesn't appear again.
- sServices->nvs().HasShownOnboarding(true);
-
- transit<Browse>();
- return;
- }
- }
+void Lua::react(const audio::PlaybackFinished&) {
+ playback_playing_->Update(false);
}
void Browse::entry() {}
-void Browse::react(const internal::ShowNowPlaying& ev) {
- transit<Playing>();
-}
-
void Browse::react(const internal::ShowSettingsPage& ev) {
std::shared_ptr<Screen> screen;
std::shared_ptr<screens::Bluetooth> bt_screen;
@@ -397,47 +327,6 @@ void Browse::react(const internal::ShowSettingsPage& ev) {
}
}
-void Browse::react(const internal::RecordSelected& ev) {
- auto db = sServices->database().lock();
- if (!db) {
- return;
- }
-
- auto& queue = sServices->track_queue();
- auto record = ev.page->values().at(ev.record);
- if (record->track()) {
- ESP_LOGI(kTag, "selected track '%s'", record->text()->c_str());
- auto source = std::make_shared<playlist::IndexRecordSource>(
- sServices->database(), ev.initial_page, 0, ev.page, ev.record);
- if (ev.show_menu) {
- sCurrentModal.reset(
- new modals::AddToQueue(sCurrentScreen.get(), queue, source));
- } else {
- queue.Clear();
- queue.AddNext(source);
- transit<Playing>();
- }
- } else {
- ESP_LOGI(kTag, "selected record '%s'", record->text()->c_str());
- auto cont = record->Expand(kRecordsPerPage);
- if (!cont) {
- return;
- }
- auto query = db->GetPage<database::IndexRecord>(&cont.value());
- if (ev.show_menu) {
- std::shared_ptr<database::Result<database::IndexRecord>> res{query.get()};
- auto source = playlist::CreateSourceFromResults(db, res);
- sCurrentModal.reset(
- new modals::AddToQueue(sCurrentScreen.get(), queue, source, true));
- } else {
- std::pmr::string title = record->text().value_or("");
- PushScreen(std::make_shared<screens::TrackBrowser>(
- sTopBarModel, sServices->track_queue(), sServices->database(),
- ev.new_crumbs, std::move(query)));
- }
- }
-}
-
void Browse::react(const internal::BackPressed& ev) {
if (PopScreen() == 0) {
transit<Lua>();
@@ -455,28 +344,6 @@ void Browse::react(const internal::ReindexDatabase& ev) {
transit<Indexing>();
}
-static std::shared_ptr<screens::Playing> sPlayingScreen;
-
-void Playing::entry() {
- ESP_LOGI(kTag, "push playing screen");
- sPlayingScreen.reset(new screens::Playing(sTopBarModel, sPlaybackModel,
- sServices->database(),
- sServices->track_queue()));
- PushScreen(sPlayingScreen);
-}
-
-void Playing::exit() {
- sPlayingScreen.reset();
-}
-
-void Playing::react(const internal::BackPressed& ev) {
- if (PopScreen() == 0) {
- transit<Lua>();
- } else {
- transit<Browse>();
- }
-}
-
static std::shared_ptr<modals::Progress> sIndexProgress;
void Indexing::entry() {
diff --git a/src/ui/widget_top_bar.cpp b/src/ui/widget_top_bar.cpp
index 348ffb6b..fbad5548 100644
--- a/src/ui/widget_top_bar.cpp
+++ b/src/ui/widget_top_bar.cpp
@@ -19,16 +19,6 @@
#include "widgets/lv_img.h"
#include "widgets/lv_label.h"
-LV_IMG_DECLARE(kIconBluetooth);
-LV_IMG_DECLARE(kIconPlay);
-LV_IMG_DECLARE(kIconPause);
-LV_IMG_DECLARE(kIconBatteryEmpty);
-LV_IMG_DECLARE(kIconBattery20);
-LV_IMG_DECLARE(kIconBattery40);
-LV_IMG_DECLARE(kIconBattery60);
-LV_IMG_DECLARE(kIconBattery80);
-LV_IMG_DECLARE(kIconBatteryFull);
-
namespace ui {
namespace widgets {
@@ -64,46 +54,6 @@ TopBar::TopBar(lv_obj_t* parent,
lv_label_set_text(title_, config.title.c_str());
lv_label_set_long_mode(title_, LV_LABEL_LONG_DOT);
- lv_obj_t* playback = lv_img_create(container_);
-
- bindings_.push_back(model.is_playing.onChangedAndNow([=](bool is_playing) {
- lv_img_set_src(playback, is_playing ? &kIconPlay : &kIconPause);
- }));
- bindings_.push_back(model.current_track.onChangedAndNow(
- [=](const std::optional<database::TrackId>& id) {
- if (id) {
- lv_obj_clear_flag(playback, LV_OBJ_FLAG_HIDDEN);
- } else {
- lv_obj_add_flag(playback, LV_OBJ_FLAG_HIDDEN);
- }
- }));
-
- lv_obj_t* battery = lv_img_create(container_);
- lv_obj_t* charging = lv_label_create(container_);
-
- bindings_.push_back(model.battery_state.onChangedAndNow(
- [=](const battery::Battery::BatteryState& state) {
- if (state.is_charging) {
- lv_label_set_text(charging, "+");
- } else {
- lv_label_set_text(charging, "");
- }
-
- if (state.percent >= 95) {
- lv_img_set_src(battery, &kIconBatteryFull);
- } else if (state.percent >= 75) {
- lv_img_set_src(battery, &kIconBattery80);
- } else if (state.percent >= 55) {
- lv_img_set_src(battery, &kIconBattery60);
- } else if (state.percent >= 35) {
- lv_img_set_src(battery, &kIconBattery40);
- } else if (state.percent >= 15) {
- lv_img_set_src(battery, &kIconBattery20);
- } else {
- lv_img_set_src(battery, &kIconBatteryEmpty);
- }
- }));
-
themes::Theme::instance()->ApplyStyle(container_, themes::Style::kTopBar);
}