summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorjacqueline <me@jacqueline.id.au>2024-03-07 10:59:03 +1100
committerjacqueline <me@jacqueline.id.au>2024-03-07 10:59:03 +1100
commitef72b25660912ff247997089abfb93e9f0b52809 (patch)
treed5d9711cb5a4192966d914094e9370c446f145fb
parent53c4ea7805f1af6a4d2969fc8eabbc8ae26ac5ca (diff)
downloadtangara-fw-ef72b25660912ff247997089abfb93e9f0b52809.tar.gz
use prototype inheritance for lua screens, rather than functions
this gives us a way to give each screen nice little hooks, like 'onShown' and 'onHidden'. later we can use these hooks to disable bindings for screens that aren't in-use.
-rw-r--r--lua/browser.lua210
-rw-r--r--lua/licenses.lua29
-rw-r--r--lua/main_menu.lua61
-rw-r--r--lua/playing.lua439
-rw-r--r--lua/settings.lua597
-rw-r--r--src/lua/CMakeLists.txt1
-rw-r--r--src/lua/bridge.cpp2
-rw-r--r--src/lua/include/lua_screen.hpp15
-rw-r--r--src/lua/lua_screen.cpp75
-rw-r--r--src/ui/include/screen.hpp3
-rw-r--r--src/ui/include/screen_lua.hpp3
-rw-r--r--src/ui/screen_lua.cpp36
-rw-r--r--src/ui/ui_fsm.cpp58
13 files changed, 848 insertions, 681 deletions
diff --git a/lua/browser.lua b/lua/browser.lua
index a7f0c336..5577d4df 100644
--- a/lua/browser.lua
+++ b/lua/browser.lua
@@ -6,30 +6,11 @@ local queue = require("queue")
local playing = require("playing")
local theme = require("theme")
local playback = require("playback")
+local screen = require("screen")
-local browser = {}
-
-function browser.create(opts)
- local screen = {}
- screen.root = lvgl.Object(nil, {
- flex = {
- flex_direction = "column",
- flex_wrap = "wrap",
- justify_content = "flex-start",
- align_items = "flex-start",
- align_content = "flex-start",
- },
- w = lvgl.HOR_RES(),
- h = lvgl.VER_RES(),
- })
- screen.root:center()
-
- screen.status_bar = widgets.StatusBar(screen.root, {
- title = opts.title,
- })
-
- if opts.breadcrumb then
- local header = screen.root:Object {
+return screen:new {
+ createUi = function(self)
+ self.root = lvgl.Object(nil, {
flex = {
flex_direction = "column",
flex_wrap = "wrap",
@@ -38,100 +19,113 @@ function browser.create(opts)
align_content = "flex-start",
},
w = lvgl.HOR_RES(),
- h = lvgl.SIZE_CONTENT,
- pad_left = 4,
- pad_right = 4,
- pad_bottom = 2,
- bg_opa = lvgl.OPA(100),
- bg_color = "#fafafa",
- scrollbar_mode = lvgl.SCROLLBAR_MODE.OFF,
- }
-
- header:Label {
- text = opts.breadcrumb,
- text_font = font.fusion_10,
- }
+ h = lvgl.VER_RES(),
+ })
+ self.root:center()
- local buttons = header:Object({
- flex = {
- flex_direction = "row",
- flex_wrap = "wrap",
- justify_content = "flex-end",
- align_items = "center",
- align_content = "center",
- },
- w = lvgl.PCT(100),
- h = lvgl.SIZE_CONTENT,
- pad_column = 4,
+ self.status_bar = widgets.StatusBar(self.root, {
+ title = self.title,
})
- local original_iterator = opts.iterator:clone()
- local enqueue = widgets.IconBtn(buttons, "//lua/img/enqueue.png", "Enqueue")
- enqueue:onClicked(function()
- queue.add(original_iterator)
- playback.playing:set(true)
- end)
- -- enqueue:add_flag(lvgl.FLAG.HIDDEN)
- local play = widgets.IconBtn(buttons, "//lua/img/play_small.png", "Play")
- play:onClicked(function()
- queue.clear()
- queue.add(original_iterator)
- playback.playing:set(true)
- backstack.push(playing)
- end
- )
- end
- screen.list = lvgl.List(screen.root, {
- w = lvgl.PCT(100),
- h = lvgl.PCT(100),
- flex_grow = 1,
- scrollbar_mode = lvgl.SCROLLBAR_MODE.OFF,
- })
+ if self.breadcrumb then
+ local header = self.root:Object {
+ flex = {
+ flex_direction = "column",
+ flex_wrap = "wrap",
+ justify_content = "flex-start",
+ align_items = "flex-start",
+ align_content = "flex-start",
+ },
+ w = lvgl.HOR_RES(),
+ h = lvgl.SIZE_CONTENT,
+ pad_left = 4,
+ pad_right = 4,
+ pad_bottom = 2,
+ bg_opa = lvgl.OPA(100),
+ bg_color = "#fafafa",
+ scrollbar_mode = lvgl.SCROLLBAR_MODE.OFF,
+ }
- local back = screen.list:add_btn(nil, "< Back")
- back:onClicked(backstack.pop)
- back:add_style(theme.list_item)
+ header:Label {
+ text = self.breadcrumb,
+ text_font = font.fusion_10,
+ }
- screen.focused_item = 0
- screen.last_item = 0
- screen.add_item = function(item)
- if not item then return end
- screen.last_item = screen.last_item + 1
- local this_item = screen.last_item
- local btn = screen.list:add_btn(nil, tostring(item))
- btn:onClicked(function()
- local contents = item:contents()
- if type(contents) == "userdata" then
- backstack.push(function()
- return browser.create({
- title = opts.title,
- iterator = contents,
- breadcrumb = tostring(item),
- })
- end)
- else
+ local buttons = header:Object({
+ flex = {
+ flex_direction = "row",
+ flex_wrap = "wrap",
+ justify_content = "flex-end",
+ align_items = "center",
+ align_content = "center",
+ },
+ w = lvgl.PCT(100),
+ h = lvgl.SIZE_CONTENT,
+ pad_column = 4,
+ })
+ local original_iterator = self.iterator:clone()
+ local enqueue = widgets.IconBtn(buttons, "//lua/img/enqueue.png", "Enqueue")
+ enqueue:onClicked(function()
+ queue.add(original_iterator)
+ playback.playing:set(true)
+ end)
+ -- enqueue:add_flag(lvgl.FLAG.HIDDEN)
+ local play = widgets.IconBtn(buttons, "//lua/img/play_small.png", "Play")
+ play:onClicked(function()
queue.clear()
- queue.add(contents)
+ queue.add(original_iterator)
playback.playing:set(true)
backstack.push(playing)
end
- end)
- btn:onevent(lvgl.EVENT.FOCUSED, function()
- screen.focused_item = this_item
- if screen.last_item - 5 < this_item then
- screen.add_item(opts.iterator())
- end
- end)
- btn:add_style(theme.list_item)
- end
+ )
+ end
- for _ = 1, 8 do
- local val = opts.iterator()
- if not val then break end
- screen.add_item(val)
- end
+ self.list = lvgl.List(self.root, {
+ w = lvgl.PCT(100),
+ h = lvgl.PCT(100),
+ flex_grow = 1,
+ scrollbar_mode = lvgl.SCROLLBAR_MODE.OFF,
+ })
- return screen
-end
+ local back = self.list:add_btn(nil, "< Back")
+ back:onClicked(backstack.pop)
+ back:add_style(theme.list_item)
-return browser.create
+ self.focused_item = 0
+ self.last_item = 0
+ self.add_item = function(item)
+ if not item then return end
+ self.last_item = self.last_item + 1
+ local this_item = self.last_item
+ local btn = self.list:add_btn(nil, tostring(item))
+ btn:onClicked(function()
+ local contents = item:contents()
+ if type(contents) == "userdata" then
+ backstack.push(require("browser"):new {
+ title = self.title,
+ iterator = contents,
+ breadcrumb = tostring(item),
+ })
+ else
+ queue.clear()
+ queue.add(contents)
+ playback.playing:set(true)
+ backstack.push(playing:new())
+ end
+ end)
+ btn:onevent(lvgl.EVENT.FOCUSED, function()
+ self.focused_item = this_item
+ if self.last_item - 5 < this_item then
+ self.add_item(self.iterator())
+ end
+ end)
+ btn:add_style(theme.list_item)
+ end
+
+ for _ = 1, 8 do
+ local val = self.iterator()
+ if not val then break end
+ self.add_item(val)
+ end
+ end
+}
diff --git a/lua/licenses.lua b/lua/licenses.lua
index 83437454..fb0e5702 100644
--- a/lua/licenses.lua
+++ b/lua/licenses.lua
@@ -2,20 +2,23 @@ local backstack = require("backstack")
local widgets = require("widgets")
local font = require("font")
local theme = require("theme")
+local screen = require("screen")
local function show_license(text)
- backstack.push(function()
- local screen = widgets.MenuScreen {
- show_back = true,
- title = "Licenses",
- }
- screen.root:Label {
- w = lvgl.PCT(100),
- h = lvgl.SIZE_CONTENT,
- text_font = font.fusion_10,
- text = text,
- }
- end)
+ backstack.push(screen:new {
+ createUi = function(self)
+ self.menu = widgets.MenuScreen {
+ show_back = true,
+ title = "Licenses",
+ }
+ self.menu.root:Label {
+ w = lvgl.PCT(100),
+ h = lvgl.SIZE_CONTENT,
+ text_font = font.fusion_10,
+ text = text,
+ }
+ end
+ })
end
local function gpl(copyright)
@@ -175,4 +178,6 @@ return function()
library("tremor", "bsd", function()
xiphbsd("Copyright (c) 2002, Xiph.org Foundation")
end)
+
+ return menu
end
diff --git a/lua/main_menu.lua b/lua/main_menu.lua
index 1311f8ea..7d47b785 100644
--- a/lua/main_menu.lua
+++ b/lua/main_menu.lua
@@ -5,41 +5,42 @@ local backstack = require("backstack")
local browser = require("browser")
local playing = require("playing")
local theme = require("theme")
+local screen = require("screen")
-return function()
- local menu = widgets.MenuScreen({})
+return screen:new {
+ createUi = function()
+ local menu = widgets.MenuScreen({})
- menu.list = lvgl.List(menu.root, {
- w = lvgl.PCT(100),
- h = lvgl.PCT(100),
- flex_grow = 1,
- })
+ menu.list = lvgl.List(menu.root, {
+ w = lvgl.PCT(100),
+ h = lvgl.PCT(100),
+ flex_grow = 1,
+ })
- local now_playing = menu.list:add_btn(nil, "Now Playing")
- now_playing:onClicked(function()
- backstack.push(playing)
- end)
- now_playing:add_style(theme.list_item)
+ local now_playing = menu.list:add_btn(nil, "Now Playing")
+ now_playing:onClicked(function()
+ backstack.push(playing:new())
+ end)
+ now_playing:add_style(theme.list_item)
- local indexes = database.indexes()
- for _, idx in ipairs(indexes) do
- local btn = menu.list:add_btn(nil, tostring(idx))
- btn:onClicked(function()
- backstack.push(function()
- return browser {
+ local indexes = database.indexes()
+ for _, idx in ipairs(indexes) do
+ local btn = menu.list:add_btn(nil, tostring(idx))
+ btn:onClicked(function()
+ backstack.push(browser:new {
title = tostring(idx),
- iterator = idx:iter()
- }
+ iterator = idx:iter(),
+ })
end)
- end)
- btn:add_style(theme.list_item)
- end
+ btn:add_style(theme.list_item)
+ end
- local settings = menu.list:add_btn(nil, "Settings")
- settings:onClicked(function()
- backstack.push(require("settings").root)
- end)
- settings:add_style(theme.list_item)
+ local settings = menu.list:add_btn(nil, "Settings")
+ settings:onClicked(function()
+ backstack.push(require("settings"):new())
+ end)
+ settings:add_style(theme.list_item)
- return menu
-end
+ return menu
+ end,
+}
diff --git a/lua/playing.lua b/lua/playing.lua
index 4767e42f..947bdec9 100644
--- a/lua/playing.lua
+++ b/lua/playing.lua
@@ -4,6 +4,7 @@ local backstack = require("backstack")
local font = require("font")
local playback = require("playback")
local queue = require("queue")
+local screen = require("screen")
local img = {
play = "//lua/img/play.png",
@@ -18,219 +19,227 @@ local img = {
repeat_disabled = "//lua/img/repeat_disabled.png",
}
-return function(opts)
- local screen = {}
- screen.root = lvgl.Object(nil, {
- flex = {
- flex_direction = "column",
- flex_wrap = "wrap",
- justify_content = "center",
- align_items = "center",
- align_content = "center",
- },
- w = lvgl.HOR_RES(),
- h = lvgl.VER_RES(),
- })
- screen.root:center()
-
- screen.status_bar = widgets.StatusBar(screen.root, {
- back_cb = backstack.pop,
- transparent_bg = true,
- })
-
- local info = screen.root:Object {
- flex = {
- flex_direction = "column",
- flex_wrap = "wrap",
- justify_content = "center",
- align_items = "center",
- align_content = "center",
- },
- w = lvgl.PCT(100),
- h = lvgl.SIZE_CONTENT,
- flex_grow = 1,
- }
-
- local artist = info:Label {
- w = lvgl.PCT(100),
- h = lvgl.SIZE_CONTENT,
- text = "",
- text_font = font.fusion_10,
- text_align = 2,
- }
-
- local title = info:Label {
- w = lvgl.PCT(100),
- h = lvgl.SIZE_CONTENT,
- text = "",
- text_align = 2,
- }
-
- local playlist = screen.root:Object {
- flex = {
- flex_direction = "row",
- justify_content = "center",
- align_items = "center",
- align_content = "center",
- },
- w = lvgl.PCT(100),
- h = lvgl.SIZE_CONTENT,
- }
-
- playlist:Object({ w = 3, h = 1 }) -- spacer
-
- local cur_time = playlist:Label {
- w = lvgl.SIZE_CONTENT,
- h = lvgl.SIZE_CONTENT,
- text = "",
- text_font = font.fusion_10,
- }
-
- playlist:Object({ flex_grow = 1, h = 1 }) -- spacer
-
- local playlist_pos = playlist:Label {
- text = "",
- text_font = font.fusion_10,
- }
- playlist:Label {
- text = "/",
- text_font = font.fusion_10,
- }
- local playlist_total = playlist:Label {
- text = "",
- text_font = font.fusion_10,
- }
-
- playlist:Object({ flex_grow = 1, h = 1 }) -- spacer
-
- local end_time = playlist:Label {
- w = lvgl.SIZE_CONTENT,
- h = lvgl.SIZE_CONTENT,
- align = lvgl.ALIGN.RIGHT_MID,
- text = "",
- text_font = font.fusion_10,
- }
- playlist:Object({ w = 3, h = 1 }) -- spacer
-
- local scrubber = screen.root:Slider {
- w = lvgl.PCT(100),
- h = 5,
- range = { min = 0, max = 100 },
- value = 0,
- }
-
- scrubber:onevent(lvgl.EVENT.RELEASED, function()
- playback.position:set(scrubber:value())
- end)
-
- local controls = screen.root:Object {
- flex = {
- flex_direction = "row",
- justify_content = "center",
- align_items = "center",
- align_content = "center",
- },
- w = lvgl.PCT(100),
- h = lvgl.SIZE_CONTENT,
- pad_column = 8,
- pad_all = 2,
- }
-
-
- controls:Object({ flex_grow = 1, h = 1 }) -- spacer
-
- local repeat_btn = controls:Button {}
- repeat_btn:onClicked(function()
- queue.repeat_track:set(not queue.repeat_track:get())
- end)
- local repeat_img = repeat_btn:Image { src = img.repeat_enabled }
-
- local prev_btn = controls:Button {}
- prev_btn:onClicked(queue.previous)
- local prev_img = prev_btn:Image { src = img.prev_disabled }
-
- local play_pause_btn = controls:Button {}
- play_pause_btn:onClicked(function()
- playback.playing:set(not playback.playing:get())
- end)
- play_pause_btn:focus()
- local play_pause_img = play_pause_btn:Image { src = img.pause }
-
- local next_btn = controls:Button {}
- next_btn:onClicked(queue.next)
- local next_img = next_btn:Image { src = img.next_disabled }
-
- local shuffle_btn = controls:Button {}
- shuffle_btn:onClicked(function()
- queue.random:set(not queue.random:get())
- end)
- local shuffle_img = shuffle_btn:Image { src = img.shuffle }
-
- controls:Object({ flex_grow = 1, h = 1 }) -- spacer
-
-
- local format_time = function(time)
- return string.format("%d:%02d", time // 60, time % 60)
+local is_now_playing_shown = false
+
+return screen:new {
+ createUi = function(self)
+ self.root = lvgl.Object(nil, {
+ flex = {
+ flex_direction = "column",
+ flex_wrap = "wrap",
+ justify_content = "center",
+ align_items = "center",
+ align_content = "center",
+ },
+ w = lvgl.HOR_RES(),
+ h = lvgl.VER_RES(),
+ })
+ self.root:center()
+
+ self.status_bar = widgets.StatusBar(self.root, {
+ back_cb = backstack.pop,
+ transparent_bg = true,
+ })
+
+ local info = self.root:Object {
+ flex = {
+ flex_direction = "column",
+ flex_wrap = "wrap",
+ justify_content = "center",
+ align_items = "center",
+ align_content = "center",
+ },
+ w = lvgl.PCT(100),
+ h = lvgl.SIZE_CONTENT,
+ flex_grow = 1,
+ }
+
+ local artist = info:Label {
+ w = lvgl.PCT(100),
+ h = lvgl.SIZE_CONTENT,
+ text = "",
+ text_font = font.fusion_10,
+ text_align = 2,
+ }
+
+ local title = info:Label {
+ w = lvgl.PCT(100),
+ h = lvgl.SIZE_CONTENT,
+ text = "",
+ text_align = 2,
+ }
+
+ local playlist = self.root:Object {
+ flex = {
+ flex_direction = "row",
+ justify_content = "center",
+ align_items = "center",
+ align_content = "center",
+ },
+ w = lvgl.PCT(100),
+ h = lvgl.SIZE_CONTENT,
+ }
+
+ playlist:Object({ w = 3, h = 1 }) -- spacer
+
+ local cur_time = playlist:Label {
+ w = lvgl.SIZE_CONTENT,
+ h = lvgl.SIZE_CONTENT,
+ text = "",
+ text_font = font.fusion_10,
+ }
+
+ playlist:Object({ flex_grow = 1, h = 1 }) -- spacer
+
+ local playlist_pos = playlist:Label {
+ text = "",
+ text_font = font.fusion_10,
+ }
+ playlist:Label {
+ text = "/",
+ text_font = font.fusion_10,
+ }
+ local playlist_total = playlist:Label {
+ text = "",
+ text_font = font.fusion_10,
+ }
+
+ playlist:Object({ flex_grow = 1, h = 1 }) -- spacer
+
+ local end_time = playlist:Label {
+ w = lvgl.SIZE_CONTENT,
+ h = lvgl.SIZE_CONTENT,
+ align = lvgl.ALIGN.RIGHT_MID,
+ text = "",
+ text_font = font.fusion_10,
+ }
+ playlist:Object({ w = 3, h = 1 }) -- spacer
+
+ local scrubber = self.root:Slider {
+ w = lvgl.PCT(100),
+ h = 5,
+ range = { min = 0, max = 100 },
+ value = 0,
+ }
+
+ scrubber:onevent(lvgl.EVENT.RELEASED, function()
+ playback.position:set(scrubber:value())
+ end)
+
+ local controls = self.root:Object {
+ flex = {
+ flex_direction = "row",
+ justify_content = "center",
+ align_items = "center",
+ align_content = "center",
+ },
+ w = lvgl.PCT(100),
+ h = lvgl.SIZE_CONTENT,
+ pad_column = 8,
+ pad_all = 2,
+ }
+
+
+ controls:Object({ flex_grow = 1, h = 1 }) -- spacer
+
+ local repeat_btn = controls:Button {}
+ repeat_btn:onClicked(function()
+ queue.repeat_track:set(not queue.repeat_track:get())
+ end)
+ local repeat_img = repeat_btn:Image { src = img.repeat_enabled }
+
+ local prev_btn = controls:Button {}
+ prev_btn:onClicked(queue.previous)
+ local prev_img = prev_btn:Image { src = img.prev_disabled }
+
+ local play_pause_btn = controls:Button {}
+ play_pause_btn:onClicked(function()
+ playback.playing:set(not playback.playing:get())
+ end)
+ play_pause_btn:focus()
+ local play_pause_img = play_pause_btn:Image { src = img.pause }
+
+ local next_btn = controls:Button {}
+ next_btn:onClicked(queue.next)
+ local next_img = next_btn:Image { src = img.next_disabled }
+
+ local shuffle_btn = controls:Button {}
+ shuffle_btn:onClicked(function()
+ queue.random:set(not queue.random:get())
+ end)
+ local shuffle_img = shuffle_btn:Image { src = img.shuffle }
+
+ controls:Object({ flex_grow = 1, h = 1 }) -- spacer
+
+
+ local format_time = function(time)
+ return string.format("%d:%02d", time // 60, time % 60)
+ end
+
+ self.bindings = {
+ playback.playing:bind(function(playing)
+ if playing then
+ play_pause_img:set_src(img.pause)
+ else
+ play_pause_img:set_src(img.play)
+ end
+ end),
+ playback.position:bind(function(pos)
+ if not pos then return end
+ cur_time:set {
+ text = format_time(pos)
+ }
+ if not scrubber:is_dragged() then
+ scrubber:set { value = pos }
+ end
+ 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) }
+
+ next_img:set_src(
+ pos < queue.size:get() and img.next or img.next_disabled
+ )
+ prev_img:set_src(
+ pos > 1 and img.prev or img.prev_disabled
+ )
+ end),
+ queue.random:bind(function(shuffling)
+ if shuffling then
+ shuffle_img:set_src(img.shuffle)
+ else
+ shuffle_img:set_src(img.shuffle_disabled)
+ end
+ end),
+ queue.repeat_track:bind(function(en)
+ if en then
+ repeat_img:set_src(img.repeat_enabled)
+ else
+ repeat_img:set_src(img.repeat_disabled)
+ end
+ end),
+ queue.size:bind(function(num)
+ if not num then return end
+ playlist_total:set { text = tostring(num) }
+ end),
+ }
+ end,
+ onShown = function() is_now_playing_shown = true end,
+ onHidden = function() is_now_playing_shown = false end,
+ pushIfNotShown = function(self)
+ if not is_now_playing_shown then
+ backstack.push(self:new())
+ end
end
-
- screen.bindings = {
- playback.playing:bind(function(playing)
- if playing then
- play_pause_img:set_src(img.pause)
- else
- play_pause_img:set_src(img.play)
- end
- end),
- playback.position:bind(function(pos)
- if not pos then return end
- cur_time:set {
- text = format_time(pos)
- }
- if not scrubber:is_dragged() then
- scrubber:set { value = pos }
- end
- 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) }
-
- next_img:set_src(
- pos < queue.size:get() and img.next or img.next_disabled
- )
- prev_img:set_src(
- pos > 1 and img.prev or img.prev_disabled
- )
- end),
- queue.random:bind(function(shuffling)
- if shuffling then
- shuffle_img:set_src(img.shuffle)
- else
- shuffle_img:set_src(img.shuffle_disabled)
- end
- end),
- queue.repeat_track:bind(function(en)
- if en then
- repeat_img:set_src(img.repeat_enabled)
- else
- repeat_img:set_src(img.repeat_disabled)
- end
- end),
- queue.size:bind(function(num)
- if not num then return end
- playlist_total:set { text = tostring(num) }
- end),
- }
-
- return screen
-end
+}
diff --git a/lua/settings.lua b/lua/settings.lua
index 952292e4..9d9ccf2d 100644
--- a/lua/settings.lua
+++ b/lua/settings.lua
@@ -7,8 +7,7 @@ local display = require("display")
local controls = require("controls")
local bluetooth = require("bluetooth")
local database = require("database")
-
-local settings = {}
+local screen = require("screen")
local function SettingsScreen(title)
local menu = widgets.MenuScreen {
@@ -31,323 +30,331 @@ local function SettingsScreen(title)
return menu
end
-function settings.bluetooth()
- local menu = SettingsScreen("Bluetooth")
-
- local enable_container = menu.content:Object {
- flex = {
- flex_direction = "row",
- justify_content = "flex-start",
- align_items = "content",
- align_content = "flex-start",
- },
- w = lvgl.PCT(100),
- h = lvgl.SIZE_CONTENT,
- pad_bottom = 1,
- }
- enable_container:Label { text = "Enable", flex_grow = 1 }
- local enable_sw = enable_container:Switch {}
- enable_sw:onevent(lvgl.EVENT.VALUE_CHANGED, function()
- local enabled = enable_sw:enabled()
- bluetooth.enabled:set(enabled)
- end)
-
- menu.content:Label {
- text = "Paired Device",
- pad_bottom = 1,
- }:add_style(theme.settings_title)
-
- local paired_container = menu.content:Object {
- flex = {
- flex_direction = "row",
- justify_content = "flex-start",
- align_items = "flex-start",
- align_content = "flex-start",
- },
- w = lvgl.PCT(100),
- h = lvgl.SIZE_CONTENT,
- pad_bottom = 2,
- }
-
- local paired_device = paired_container:Label {
- flex_grow = 1,
- }
- local clear_paired = paired_container:Button {}
- clear_paired:Label { text = "x" }
- clear_paired:onClicked(function()
- bluetooth.paired_device:set()
- end)
-
- menu.content:Label {
- text = "Nearby Devices",
- pad_bottom = 1,
- }:add_style(theme.settings_title)
-
- local devices = menu.content:List {
- w = lvgl.PCT(100),
- h = lvgl.SIZE_CONTENT,
- }
-
- menu.bindings = {
- bluetooth.enabled:bind(function(en)
- if en then
- enable_sw:add_state(lvgl.STATE.CHECKED)
- else
- enable_sw:clear_state(lvgl.STATE.CHECKED)
- end
- end),
- bluetooth.paired_device:bind(function(device)
- if device then
- paired_device:set { text = device.name }
- clear_paired:clear_flag(lvgl.FLAG.HIDDEN)
- else
- paired_device:set { text = "None" }
- clear_paired:add_flag(lvgl.FLAG.HIDDEN)
- end
- end),
- bluetooth.devices:bind(function(devs)
- devices:clean()
- for _, dev in pairs(devs) do
- devices:add_btn(nil, dev.name):onClicked(function()
- bluetooth.paired_device:set(dev)
- end)
- end
+local BluetoothSettings = screen:new {
+ createUi = function(self)
+ self.menu = SettingsScreen("Bluetooth")
+
+ local enable_container = self.menu.content:Object {
+ flex = {
+ flex_direction = "row",
+ justify_content = "flex-start",
+ align_items = "content",
+ align_content = "flex-start",
+ },
+ w = lvgl.PCT(100),
+ h = lvgl.SIZE_CONTENT,
+ pad_bottom = 1,
+ }
+ enable_container:Label { text = "Enable", flex_grow = 1 }
+ local enable_sw = enable_container:Switch {}
+ enable_sw:onevent(lvgl.EVENT.VALUE_CHANGED, function()
+ local enabled = enable_sw:enabled()
+ bluetooth.enabled:set(enabled)
end)
- }
-end
-function settings.headphones()
- local menu = SettingsScreen("Headphones")
+ self.menu.content:Label {
+ text = "Paired Device",
+ pad_bottom = 1,
+ }:add_style(theme.settings_title)
+
+ local paired_container = self.menu.content:Object {
+ flex = {
+ flex_direction = "row",
+ justify_content = "flex-start",
+ align_items = "flex-start",
+ align_content = "flex-start",
+ },
+ w = lvgl.PCT(100),
+ h = lvgl.SIZE_CONTENT,
+ pad_bottom = 2,
+ }
+
+ local paired_device = paired_container:Label {
+ flex_grow = 1,
+ }
+ local clear_paired = paired_container:Button {}
+ clear_paired:Label { text = "x" }
+ clear_paired:onClicked(function()
+ bluetooth.paired_device:set()
+ end)
- menu.content:Label {
- text = "Maximum volume limit",
- }:add_style(theme.settings_title)
+ self.menu.content:Label {
+ text = "Nearby Devices",
+ pad_bottom = 1,
+ }:add_style(theme.settings_title)
+
+ local devices = self.menu.content:List {
+ w = lvgl.PCT(100),
+ h = lvgl.SIZE_CONTENT,
+ }
+
+ self.bindings = {
+ bluetooth.enabled:bind(function(en)
+ if en then
+ enable_sw:add_state(lvgl.STATE.CHECKED)
+ else
+ enable_sw:clear_state(lvgl.STATE.CHECKED)
+ end
+ end),
+ bluetooth.paired_device:bind(function(device)
+ if device then
+ paired_device:set { text = device.name }
+ clear_paired:clear_flag(lvgl.FLAG.HIDDEN)
+ else
+ paired_device:set { text = "None" }
+ clear_paired:add_flag(lvgl.FLAG.HIDDEN)
+ end
+ end),
+ bluetooth.devices:bind(function(devs)
+ devices:clean()
+ for _, dev in pairs(devs) do
+ devices:add_btn(nil, dev.name):onClicked(function()
+ bluetooth.paired_device:set(dev)
+ end)
+ end
+ end)
+ }
+ end
+}
+
+local HeadphonesSettings = screen:new {
+ createUi = function(self)
+ self.menu = SettingsScreen("Headphones")
+
+ self.menu.content:Label {
+ text = "Maximum volume limit",
+ }:add_style(theme.settings_title)
+
+ local volume_chooser = self.menu.content:Dropdown {
+ options = "Line Level (-10 dB)\nCD Level (+6 dB)\nMaximum (+10dB)",
+ selected = 1,
+ }
+ local limits = { -10, 6, 10 }
+ volume_chooser:onevent(lvgl.EVENT.VALUE_CHANGED, function()
+ -- luavgl dropdown binding uses 0-based indexing :(
+ local selection = volume_chooser:get('selected') + 1
+ volume.limit_db:set(limits[selection])
+ end)
- local volume_chooser = menu.content:Dropdown {
- options = "Line Level (-10 dB)\nCD Level (+6 dB)\nMaximum (+10dB)",
- selected = 1,
- }
- local limits = { -10, 6, 10 }
- volume_chooser:onevent(lvgl.EVENT.VALUE_CHANGED, function()
- -- luavgl dropdown binding uses 0-based indexing :(
- local selection = volume_chooser:get('selected') + 1
- volume.limit_db:set(limits[selection])
- end)
-
- menu.content:Label {
- text = "Left/Right balance",
- }:add_style(theme.settings_title)
-
- local balance = menu.content:Slider {
- w = lvgl.PCT(100),
- h = 5,
- range = { min = -100, max = 100 },
- value = 0,
- }
- balance:onevent(lvgl.EVENT.VALUE_CHANGED, function()
- volume.left_bias:set(balance:value())
- end)
+ self.menu.content:Label {
+ text = "Left/Right balance",
+ }:add_style(theme.settings_title)
+
+ local balance = self.menu.content:Slider {
+ w = lvgl.PCT(100),
+ h = 5,
+ range = { min = -100, max = 100 },
+ value = 0,
+ }
+ balance:onevent(lvgl.EVENT.VALUE_CHANGED, function()
+ volume.left_bias:set(balance:value())
+ end)
- local balance_label = menu.content:Label {}
+ local balance_label = self.menu.content:Label {}
- menu.bindings = {
- volume.limit_db:bind(function(limit)
- for i = 1, #limits do
- if limits[i] == limit then
- volume_chooser:set { selected = i - 1 }
+ self.bindings = {
+ volume.limit_db:bind(function(limit)
+ for i = 1, #limits do
+ if limits[i] == limit then
+ volume_chooser:set { selected = i - 1 }
+ end
end
- end
- end),
- volume.left_bias:bind(function(bias)
- balance:set {
- value = bias
- }
- if bias < 0 then
- balance_label:set {
- text = string.format("Left %.2fdB", bias / 4)
+ end),
+ volume.left_bias:bind(function(bias)
+ balance:set {
+ value = bias
}
- elseif bias > 0 then
- balance_label:set {
- text = string.format("Right %.2fdB", -bias / 4)
- }
- else
- balance_label:set { text = "Balanced" }
- end
- end),
- }
-
- return menu
-end
-
-function settings.display()
- local menu = SettingsScreen("Display")
-
- local brightness_title = menu.content:Object {
- flex = {
- flex_direction = "row",
- justify_content = "flex-start",
- align_items = "flex-start",
- align_content = "flex-start",
- },
- w = lvgl.PCT(100),
- h = lvgl.SIZE_CONTENT,
- }
- brightness_title:Label { text = "Brightness", flex_grow = 1 }
- local brightness_pct = brightness_title:Label {}
- brightness_pct:add_style(theme.settings_title)
-
- local brightness = menu.content:Slider {
- w = lvgl.PCT(100),
- h = 5,
- range = { min = 0, max = 100 },
- value = display.brightness:get(),
- }
- brightness:onevent(lvgl.EVENT.VALUE_CHANGED, function()
- display.brightness:set(brightness:value())
- end)
-
- menu.bindings = {
- display.brightness:bind(function(b)
- brightness_pct:set { text = tostring(b) .. "%" }
+ if bias < 0 then
+ balance_label:set {
+ text = string.format("Left %.2fdB", bias / 4)
+ }
+ elseif bias > 0 then
+ balance_label:set {
+ text = string.format("Right %.2fdB", -bias / 4)
+ }
+ else
+ balance_label:set { text = "Balanced" }
+ end
+ end),
+ }
+ end
+}
+
+local DisplaySettings = screen:new {
+ createUi = function(self)
+ self.menu = SettingsScreen("Display")
+
+ local brightness_title = self.menu.content:Object {
+ flex = {
+ flex_direction = "row",
+ justify_content = "flex-start",
+ align_items = "flex-start",
+ align_content = "flex-start",
+ },
+ w = lvgl.PCT(100),
+ h = lvgl.SIZE_CONTENT,
+ }
+ brightness_title:Label { text = "Brightness", flex_grow = 1 }
+ local brightness_pct = brightness_title:Label {}
+ brightness_pct:add_style(theme.settings_title)
+
+ local brightness = self.menu.content:Slider {
+ w = lvgl.PCT(100),
+ h = 5,
+ range = { min = 0, max = 100 },
+ value = display.brightness:get(),
+ }
+ brightness:onevent(lvgl.EVENT.VALUE_CHANGED, function()
+ display.brightness:set(brightness:value())
end)
- }
- return menu
-end
+ self.bindings = {
+ display.brightness:bind(function(b)
+ brightness_pct:set { text = tostring(b) .. "%" }
+ end)
+ }
+ end
+}
-function settings.input()
- local menu = SettingsScreen("Input Method")
+local InputSettings = screen:new {
+ createUi = function(self)
+ self.menu = SettingsScreen("Input Method")
- menu.content:Label {
- text = "Control scheme",
- }:add_style(theme.settings_title)
+ self.menu.content:Label {
+ text = "Control scheme",
+ }:add_style(theme.settings_title)
- local schemes = controls.schemes()
- local option_to_scheme = {}
- local scheme_to_option = {}
+ local schemes = controls.schemes()
+ local option_to_scheme = {}
+ local scheme_to_option = {}
- local option_idx = 0
- local options = ""
+ local option_idx = 0
+ local options = ""
- for i, v in pairs(schemes) do
- option_to_scheme[option_idx] = i
- scheme_to_option[i] = option_idx
- if option_idx > 0 then
- options = options .. "\n"
+ for i, v in pairs(schemes) do
+ option_to_scheme[option_idx] = i
+ scheme_to_option[i] = option_idx
+ if option_idx > 0 then
+ options = options .. "\n"
+ end
+ options = options .. v
+ option_idx = option_idx + 1
end
- options = options .. v
- option_idx = option_idx + 1
- end
-
- local controls_chooser = menu.content:Dropdown {
- options = options,
- }
- menu.bindings = {
- controls.scheme:bind(function(s)
- local option = scheme_to_option[s]
- controls_chooser:set({ selected = option })
+ local controls_chooser = self.menu.content:Dropdown {
+ options = options,
+ }
+
+ self.bindings = {
+ controls.scheme:bind(function(s)
+ local option = scheme_to_option[s]
+ controls_chooser:set({ selected = option })
+ end)
+ }
+
+ controls_chooser:onevent(lvgl.EVENT.VALUE_CHANGED, function()
+ local option = controls_chooser:get('selected')
+ local scheme = option_to_scheme[option]
+ controls.scheme:set(scheme)
end)
- }
-
- controls_chooser:onevent(lvgl.EVENT.VALUE_CHANGED, function()
- local option = controls_chooser:get('selected')
- local scheme = option_to_scheme[option]
- controls.scheme:set(scheme)
- end)
-
- menu.content:Label {
- text = "Scroll Sensitivity",
- }:add_style(theme.settings_title)
-
- local slider_scale = 4; -- Power steering
- local sensitivity = menu.content:Slider {
- w = lvgl.PCT(90),
- h = 5,
- range = { min = 0, max = 255/slider_scale },
- value = controls.scroll_sensitivity:get()/slider_scale,
- }
- sensitivity:onevent(lvgl.EVENT.VALUE_CHANGED, function()
- controls.scroll_sensitivity:set(sensitivity:value()*slider_scale)
- end)
-
- return menu
-end
-
-function settings.database()
- local menu = SettingsScreen("Database")
- local db = require("database")
- widgets.Row(menu.content, "Schema version", db.version())
- widgets.Row(menu.content, "Size on disk", string.format("%.1f KiB", db.size() / 1024))
- local actions_container = menu.content:Object {
- w = lvgl.PCT(100),
- h = lvgl.SIZE_CONTENT,
- flex = {
- flex_direction = "row",
- justify_content = "center",
- align_items = "space-evenly",
- align_content = "center",
- },
- pad_top = 4,
- pad_column = 4,
- }
- actions_container:add_style(theme.list_item)
-
- local update = actions_container:Button {}
- update:Label { text = "Update" }
- update:onClicked(function()
- database.update()
- end)
-end
-
-function settings.firmware()
- local menu = SettingsScreen("Firmware")
- local version = require("version")
- widgets.Row(menu.content, "ESP32", version.esp())
- widgets.Row(menu.content, "SAMD21", version.samd())
- widgets.Row(menu.content, "Collator", version.collator())
-end
-
-function settings.root()
- local menu = widgets.MenuScreen {
- show_back = true,
- title = "Settings",
- }
- menu.list = menu.root:List {
- w = lvgl.PCT(100),
- h = lvgl.PCT(100),
- flex_grow = 1,
- }
-
- local function section(name)
- menu.list:add_text(name):add_style(theme.list_heading)
+ self.menu.content:Label {
+ text = "Scroll Sensitivity",
+ }:add_style(theme.settings_title)
+
+ local slider_scale = 4; -- Power steering
+ local sensitivity = self.menu.content:Slider {
+ w = lvgl.PCT(90),
+ h = 5,
+ range = { min = 0, max = 255 / slider_scale },
+ value = controls.scroll_sensitivity:get() / slider_scale,
+ }
+ sensitivity:onevent(lvgl.EVENT.VALUE_CHANGED, function()
+ controls.scroll_sensitivity:set(sensitivity:value() * slider_scale)
+ end)
end
-
- local function submenu(name, fn)
- local item = menu.list:add_btn(nil, name)
- item:onClicked(function()
- backstack.push(fn)
+}
+
+local DatabaseSettings = screen:new {
+ createUi = function(self)
+ self.menu = SettingsScreen("Database")
+ local db = require("database")
+ widgets.Row(self.menu.content, "Schema version", db.version())
+ widgets.Row(self.menu.content, "Size on disk", string.format("%.1f KiB", db.size() / 1024))
+
+ local actions_container = self.menu.content:Object {
+ w = lvgl.PCT(100),
+ h = lvgl.SIZE_CONTENT,
+ flex = {
+ flex_direction = "row",
+ justify_content = "center",
+ align_items = "space-evenly",
+ align_content = "center",
+ },
+ pad_top = 4,
+ pad_column = 4,
+ }
+ actions_container:add_style(theme.list_item)
+
+ local update = actions_container:Button {}
+ update:Label { text = "Update" }
+ update:onClicked(function()
+ database.update()
end)
- item:add_style(theme.list_item)
end
+}
+
+local FirmwareSettings = screen:new {
+ createUi = function(self)
+ self.menu = SettingsScreen("Firmware")
+ local version = require("version")
+ widgets.Row(self.menu.content, "ESP32", version.esp())
+ widgets.Row(self.menu.content, "SAMD21", version.samd())
+ widgets.Row(self.menu.content, "Collator", version.collator())
+ end
+}
- section("Audio")
- submenu("Bluetooth", settings.bluetooth)
- submenu("Headphones", settings.headphones)
+local LicensesScreen = screen:new {
+ createUi = function(self)
+ self.root = require("licenses")()
+ end
+}
+
+return screen:new {
+ createUi = function(self)
+ self.menu = widgets.MenuScreen {
+ show_back = true,
+ title = "Settings",
+ }
+ self.list = self.menu.root:List {
+ w = lvgl.PCT(100),
+ h = lvgl.PCT(100),
+ flex_grow = 1,
+ }
+
+ local function section(name)
+ self.list:add_text(name):add_style(theme.list_heading)
+ end
- section("Interface")
- submenu("Display", settings.display)
- submenu("Input Method", settings.input)
+ local function submenu(name, class)
+ local item = self.list:add_btn(nil, name)
+ item:onClicked(function()
+ backstack.push(class:new())
+ end)
+ item:add_style(theme.list_item)
+ end
- section("System")
- submenu("Database", settings.database)
- submenu("Firmware", settings.firmware)
- submenu("Licenses", function()
- return require("licenses")()
- end)
+ section("Audio")
+ submenu("Bluetooth", BluetoothSettings)
+ submenu("Headphones", HeadphonesSettings)
- return menu
-end
+ section("Interface")
+ submenu("Display", DisplaySettings)
+ submenu("Input Method", InputSettings)
-return settings
+ section("System")
+ submenu("Database", DatabaseSettings)
+ submenu("Firmware", FirmwareSettings)
+ submenu("Licenses", LicensesScreen)
+ end
+}
diff --git a/src/lua/CMakeLists.txt b/src/lua/CMakeLists.txt
index ff0831c9..cee738bd 100644
--- a/src/lua/CMakeLists.txt
+++ b/src/lua/CMakeLists.txt
@@ -5,6 +5,7 @@
idf_component_register(
SRCS "lua_thread.cpp" "bridge.cpp" "property.cpp" "lua_database.cpp"
"lua_queue.cpp" "lua_version.cpp" "lua_controls.cpp" "registry.cpp"
+ "lua_screen.cpp"
INCLUDE_DIRS "include"
REQUIRES "drivers" "lvgl" "tinyfsm" "events" "system_fsm" "database"
"esp_timer" "battery" "esp-idf-lua" "luavgl" "lua-linenoise" "lua-term"
diff --git a/src/lua/bridge.cpp b/src/lua/bridge.cpp
index a26f74bb..2bef1c30 100644
--- a/src/lua/bridge.cpp
+++ b/src/lua/bridge.cpp
@@ -19,6 +19,7 @@
#include "lua_controls.hpp"
#include "lua_database.hpp"
#include "lua_queue.hpp"
+#include "lua_screen.hpp"
#include "lua_version.hpp"
#include "lvgl.h"
@@ -84,6 +85,7 @@ auto Bridge::installBaseModules(lua_State* L) -> void {
RegisterDatabaseModule(L);
RegisterQueueModule(L);
RegisterVersionModule(L);
+ RegisterScreenModule(L);
}
auto Bridge::installLvgl(lua_State* L) -> void {
diff --git a/src/lua/include/lua_screen.hpp b/src/lua/include/lua_screen.hpp
new file mode 100644
index 00000000..1c3bed1a
--- /dev/null
+++ b/src/lua/include/lua_screen.hpp
@@ -0,0 +1,15 @@
+/*
+ * Copyright 2023 jacqueline <me@jacqueline.id.au>
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#pragma once
+
+#include "lua.hpp"
+
+namespace lua {
+
+auto RegisterScreenModule(lua_State*) -> void;
+
+} // namespace lua
diff --git a/src/lua/lua_screen.cpp b/src/lua/lua_screen.cpp
new file mode 100644
index 00000000..27843bc7
--- /dev/null
+++ b/src/lua/lua_screen.cpp
@@ -0,0 +1,75 @@
+/*
+ * Copyright 2023 jacqueline <me@jacqueline.id.au>
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#include "lua_screen.hpp"
+
+#include <memory>
+#include <string>
+
+#include "lua.hpp"
+
+#include "esp_log.h"
+#include "lauxlib.h"
+#include "lua.h"
+#include "lvgl.h"
+
+#include "bridge.hpp"
+#include "database.hpp"
+#include "event_queue.hpp"
+#include "index.hpp"
+#include "property.hpp"
+#include "service_locator.hpp"
+#include "track.hpp"
+#include "track_queue.hpp"
+#include "ui_events.hpp"
+
+namespace lua {
+
+static auto screen_new(lua_State* L) -> int {
+ // o = o or {}
+ if (lua_gettop(L) != 2) {
+ lua_settop(L, 1);
+ lua_newtable(L);
+ }
+ // Swap o and self on the stack.
+ lua_insert(L, 1);
+
+ lua_pushliteral(L, "__index");
+ lua_pushvalue(L, 1);
+ lua_settable(L, 1); // self.__index = self
+
+ lua_setmetatable(L, 1); // setmetatable(o, self)
+
+ return 1; // return o
+}
+
+static auto screen_noop(lua_State* state) -> int {
+ return 0;
+}
+
+static const struct luaL_Reg kScreenFuncs[] = {{"new", screen_new},
+ {"createUi", screen_noop},
+ {"onShown", screen_noop},
+ {"onHidden", screen_noop},
+ {NULL, NULL}};
+
+static auto lua_screen(lua_State* state) -> int {
+ luaL_newlib(state, kScreenFuncs);
+
+ lua_pushliteral(state, "__index");
+ lua_pushvalue(state, -2);
+ lua_rawset(state, -3);
+
+ return 1;
+}
+
+auto RegisterScreenModule(lua_State* s) -> void {
+ luaL_requiref(s, "screen", lua_screen, true);
+
+ lua_pop(s, 1);
+}
+
+} // namespace lua
diff --git a/src/ui/include/screen.hpp b/src/ui/include/screen.hpp
index 60939660..4241c712 100644
--- a/src/ui/include/screen.hpp
+++ b/src/ui/include/screen.hpp
@@ -27,6 +27,9 @@ class Screen {
Screen();
virtual ~Screen();
+ virtual auto onShown() -> void {}
+ virtual auto onHidden() -> void {}
+
auto root() -> lv_obj_t* { return root_; }
auto content() -> lv_obj_t* { return content_; }
auto alert() -> lv_obj_t* { return alert_; }
diff --git a/src/ui/include/screen_lua.hpp b/src/ui/include/screen_lua.hpp
index ee9f6813..0ed3a508 100644
--- a/src/ui/include/screen_lua.hpp
+++ b/src/ui/include/screen_lua.hpp
@@ -18,6 +18,9 @@ class Lua : public Screen {
Lua();
~Lua();
+ auto onShown() -> void override;
+ auto onHidden() -> void override;
+
auto SetObjRef(lua_State*) -> void;
private:
diff --git a/src/ui/screen_lua.cpp b/src/ui/screen_lua.cpp
index 5130b4f7..d6c7a26f 100644
--- a/src/ui/screen_lua.cpp
+++ b/src/ui/screen_lua.cpp
@@ -7,8 +7,10 @@
#include "screen_lua.hpp"
#include "core/lv_obj_tree.h"
+#include "lua.h"
#include "lua.hpp"
+#include "lua_thread.hpp"
#include "luavgl.h"
namespace ui {
@@ -22,6 +24,40 @@ Lua::~Lua() {
}
}
+auto Lua::onShown() -> void {
+ if (!s_ || !obj_ref_) {
+ return;
+ }
+ lua_rawgeti(s_, LUA_REGISTRYINDEX, *obj_ref_);
+ lua_pushliteral(s_, "onShown");
+
+ if (lua_gettable(s_, -2) == LUA_TFUNCTION) {
+ lua_pushvalue(s_, -2);
+ lua::CallProtected(s_, 1, 0);
+ } else {
+ lua_pop(s_, 1);
+ }
+
+ lua_pop(s_, 1);
+}
+
+auto Lua::onHidden() -> void {
+ if (!s_ || !obj_ref_) {
+ return;
+ }
+ lua_rawgeti(s_, LUA_REGISTRYINDEX, *obj_ref_);
+ lua_pushliteral(s_, "onHidden");
+
+ if (lua_gettable(s_, -2) == LUA_TFUNCTION) {
+ lua_pushvalue(s_, -2);
+ lua::CallProtected(s_, 1, 0);
+ } else {
+ lua_pop(s_, 1);
+ }
+
+ lua_pop(s_, 1);
+}
+
auto Lua::SetObjRef(lua_State* s) -> void {
assert(s_ == nullptr);
s_ = s;
diff --git a/src/ui/ui_fsm.cpp b/src/ui/ui_fsm.cpp
index d98e435d..5c22e90e 100644
--- a/src/ui/ui_fsm.cpp
+++ b/src/ui/ui_fsm.cpp
@@ -125,21 +125,24 @@ lua::Property UiState::sPlaybackPlaying{
}};
lua::Property UiState::sPlaybackTrack{};
-lua::Property UiState::sPlaybackPosition{0, [](const lua::LuaValue& val) {
- int current_val = std::get<int>(sPlaybackPosition.Get());
- if (!std::holds_alternative<int>(val)) {
- return false;
- }
- int new_val = std::get<int>(val);
- if (current_val != new_val) {
- auto track = sPlaybackTrack.Get();
- if (!std::holds_alternative<audio::Track>(track)) {
+lua::Property UiState::sPlaybackPosition{
+ 0, [](const lua::LuaValue& val) {
+ int current_val = std::get<int>(sPlaybackPosition.Get());
+ if (!std::holds_alternative<int>(val)) {
return false;
}
- events::Audio().Dispatch(audio::SeekFile{.offset = (uint32_t)new_val, .filename = std::get<audio::Track>(track).filepath});
- }
- return true;
-}};
+ int new_val = std::get<int>(val);
+ if (current_val != new_val) {
+ auto track = sPlaybackTrack.Get();
+ if (!std::holds_alternative<audio::Track>(track)) {
+ return false;
+ }
+ events::Audio().Dispatch(audio::SeekFile{
+ .offset = (uint32_t)new_val,
+ .filename = std::get<audio::Track>(track).filepath});
+ }
+ return true;
+ }};
lua::Property UiState::sQueuePosition{0};
lua::Property UiState::sQueueSize{0};
@@ -294,21 +297,29 @@ auto UiState::InitBootSplash(drivers::IGpios& gpios) -> bool {
}
void UiState::PushScreen(std::shared_ptr<Screen> screen) {
+ lv_obj_set_parent(sAlertContainer, screen->alert());
+
if (sCurrentScreen) {
+ sCurrentScreen->onHidden();
sScreens.push(sCurrentScreen);
}
sCurrentScreen = screen;
- lv_obj_set_parent(sAlertContainer, sCurrentScreen->alert());
+ sCurrentScreen->onShown();
}
int UiState::PopScreen() {
if (sScreens.empty()) {
return 0;
}
- sCurrentScreen = sScreens.top();
- lv_obj_set_parent(sAlertContainer, sCurrentScreen->alert());
+ lv_obj_set_parent(sAlertContainer, sScreens.top()->alert());
+
+ sCurrentScreen->onHidden();
+ sCurrentScreen = sScreens.top();
sScreens.pop();
+
+ sCurrentScreen->onShown();
+
return sScreens.size();
}
@@ -539,7 +550,7 @@ void Lua::entry() {
auto Lua::PushLuaScreen(lua_State* s) -> int {
// Ensure the arg looks right before continuing.
- luaL_checktype(s, 1, LUA_TFUNCTION);
+ luaL_checktype(s, 1, LUA_TTABLE);
// First, create a new plain old Screen object. We will use its root and
// group for the Lua screen. Allocate it in external ram so that arbitrarily
@@ -554,10 +565,15 @@ auto Lua::PushLuaScreen(lua_State* s) -> int {
lv_group_set_default(new_screen->group());
// Call the constructor for this screen.
- lua_settop(s, 1); // Make sure the function is actually at top of stack
- lua::CallProtected(s, 0, 1);
+ // lua_settop(s, 1); // Make sure the screen is actually at top of stack
+ lua_pushliteral(s, "createUi");
+ if (lua_gettable(s, 1) == LUA_TFUNCTION) {
+ lua_pushvalue(s, 1);
+ lua::CallProtected(s, 1, 0);
+ }
- // Store the reference for the table the constructor returned.
+ // Store the reference for this screen's table.
+ lua_settop(s, 1);
new_screen->SetObjRef(s);
// Finally, push the now-initialised screen as if it were a regular C++
@@ -585,7 +601,7 @@ auto Lua::PopLuaScreen(lua_State* s) -> int {
}
auto Lua::Ticks(lua_State* s) -> int {
- lua_pushinteger(s, esp_timer_get_time()/1000);
+ lua_pushinteger(s, esp_timer_get_time() / 1000);
return 1;
}