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.
custom
jacqueline 1 year ago
parent 53c4ea7805
commit ef72b25660
  1. 64
      lua/browser.lua
  2. 13
      lua/licenses.lua
  3. 19
      lua/main_menu.lua
  4. 35
      lua/playing.lua
  5. 159
      lua/settings.lua
  6. 1
      src/lua/CMakeLists.txt
  7. 2
      src/lua/bridge.cpp
  8. 15
      src/lua/include/lua_screen.hpp
  9. 75
      src/lua/lua_screen.cpp
  10. 3
      src/ui/include/screen.hpp
  11. 3
      src/ui/include/screen_lua.hpp
  12. 36
      src/ui/screen_lua.cpp
  13. 38
      src/ui/ui_fsm.cpp

@ -6,12 +6,11 @@ local queue = require("queue")
local playing = require("playing") local playing = require("playing")
local theme = require("theme") local theme = require("theme")
local playback = require("playback") local playback = require("playback")
local screen = require("screen")
local browser = {} return screen:new {
createUi = function(self)
function browser.create(opts) self.root = lvgl.Object(nil, {
local screen = {}
screen.root = lvgl.Object(nil, {
flex = { flex = {
flex_direction = "column", flex_direction = "column",
flex_wrap = "wrap", flex_wrap = "wrap",
@ -22,14 +21,14 @@ function browser.create(opts)
w = lvgl.HOR_RES(), w = lvgl.HOR_RES(),
h = lvgl.VER_RES(), h = lvgl.VER_RES(),
}) })
screen.root:center() self.root:center()
screen.status_bar = widgets.StatusBar(screen.root, { self.status_bar = widgets.StatusBar(self.root, {
title = opts.title, title = self.title,
}) })
if opts.breadcrumb then if self.breadcrumb then
local header = screen.root:Object { local header = self.root:Object {
flex = { flex = {
flex_direction = "column", flex_direction = "column",
flex_wrap = "wrap", flex_wrap = "wrap",
@ -48,7 +47,7 @@ function browser.create(opts)
} }
header:Label { header:Label {
text = opts.breadcrumb, text = self.breadcrumb,
text_font = font.fusion_10, text_font = font.fusion_10,
} }
@ -64,7 +63,7 @@ function browser.create(opts)
h = lvgl.SIZE_CONTENT, h = lvgl.SIZE_CONTENT,
pad_column = 4, pad_column = 4,
}) })
local original_iterator = opts.iterator:clone() local original_iterator = self.iterator:clone()
local enqueue = widgets.IconBtn(buttons, "//lua/img/enqueue.png", "Enqueue") local enqueue = widgets.IconBtn(buttons, "//lua/img/enqueue.png", "Enqueue")
enqueue:onClicked(function() enqueue:onClicked(function()
queue.add(original_iterator) queue.add(original_iterator)
@ -81,57 +80,52 @@ function browser.create(opts)
) )
end end
screen.list = lvgl.List(screen.root, { self.list = lvgl.List(self.root, {
w = lvgl.PCT(100), w = lvgl.PCT(100),
h = lvgl.PCT(100), h = lvgl.PCT(100),
flex_grow = 1, flex_grow = 1,
scrollbar_mode = lvgl.SCROLLBAR_MODE.OFF, scrollbar_mode = lvgl.SCROLLBAR_MODE.OFF,
}) })
local back = screen.list:add_btn(nil, "< Back") local back = self.list:add_btn(nil, "< Back")
back:onClicked(backstack.pop) back:onClicked(backstack.pop)
back:add_style(theme.list_item) back:add_style(theme.list_item)
screen.focused_item = 0 self.focused_item = 0
screen.last_item = 0 self.last_item = 0
screen.add_item = function(item) self.add_item = function(item)
if not item then return end if not item then return end
screen.last_item = screen.last_item + 1 self.last_item = self.last_item + 1
local this_item = screen.last_item local this_item = self.last_item
local btn = screen.list:add_btn(nil, tostring(item)) local btn = self.list:add_btn(nil, tostring(item))
btn:onClicked(function() btn:onClicked(function()
local contents = item:contents() local contents = item:contents()
if type(contents) == "userdata" then if type(contents) == "userdata" then
backstack.push(function() backstack.push(require("browser"):new {
return browser.create({ title = self.title,
title = opts.title,
iterator = contents, iterator = contents,
breadcrumb = tostring(item), breadcrumb = tostring(item),
}) })
end)
else else
queue.clear() queue.clear()
queue.add(contents) queue.add(contents)
playback.playing:set(true) playback.playing:set(true)
backstack.push(playing) backstack.push(playing:new())
end end
end) end)
btn:onevent(lvgl.EVENT.FOCUSED, function() btn:onevent(lvgl.EVENT.FOCUSED, function()
screen.focused_item = this_item self.focused_item = this_item
if screen.last_item - 5 < this_item then if self.last_item - 5 < this_item then
screen.add_item(opts.iterator()) self.add_item(self.iterator())
end end
end) end)
btn:add_style(theme.list_item) btn:add_style(theme.list_item)
end end
for _ = 1, 8 do for _ = 1, 8 do
local val = opts.iterator() local val = self.iterator()
if not val then break end if not val then break end
screen.add_item(val) self.add_item(val)
end end
end
return screen }
end
return browser.create

@ -2,20 +2,23 @@ local backstack = require("backstack")
local widgets = require("widgets") local widgets = require("widgets")
local font = require("font") local font = require("font")
local theme = require("theme") local theme = require("theme")
local screen = require("screen")
local function show_license(text) local function show_license(text)
backstack.push(function() backstack.push(screen:new {
local screen = widgets.MenuScreen { createUi = function(self)
self.menu = widgets.MenuScreen {
show_back = true, show_back = true,
title = "Licenses", title = "Licenses",
} }
screen.root:Label { self.menu.root:Label {
w = lvgl.PCT(100), w = lvgl.PCT(100),
h = lvgl.SIZE_CONTENT, h = lvgl.SIZE_CONTENT,
text_font = font.fusion_10, text_font = font.fusion_10,
text = text, text = text,
} }
end) end
})
end end
local function gpl(copyright) local function gpl(copyright)
@ -175,4 +178,6 @@ return function()
library("tremor", "bsd", function() library("tremor", "bsd", function()
xiphbsd("Copyright (c) 2002, Xiph.org Foundation") xiphbsd("Copyright (c) 2002, Xiph.org Foundation")
end) end)
return menu
end end

@ -5,8 +5,10 @@ local backstack = require("backstack")
local browser = require("browser") local browser = require("browser")
local playing = require("playing") local playing = require("playing")
local theme = require("theme") local theme = require("theme")
local screen = require("screen")
return function() return screen:new {
createUi = function()
local menu = widgets.MenuScreen({}) local menu = widgets.MenuScreen({})
menu.list = lvgl.List(menu.root, { menu.list = lvgl.List(menu.root, {
@ -17,7 +19,7 @@ return function()
local now_playing = menu.list:add_btn(nil, "Now Playing") local now_playing = menu.list:add_btn(nil, "Now Playing")
now_playing:onClicked(function() now_playing:onClicked(function()
backstack.push(playing) backstack.push(playing:new())
end) end)
now_playing:add_style(theme.list_item) now_playing:add_style(theme.list_item)
@ -25,21 +27,20 @@ return function()
for _, idx in ipairs(indexes) do for _, idx in ipairs(indexes) do
local btn = menu.list:add_btn(nil, tostring(idx)) local btn = menu.list:add_btn(nil, tostring(idx))
btn:onClicked(function() btn:onClicked(function()
backstack.push(function() backstack.push(browser:new {
return browser {
title = tostring(idx), title = tostring(idx),
iterator = idx:iter() iterator = idx:iter(),
} })
end)
end) end)
btn:add_style(theme.list_item) btn:add_style(theme.list_item)
end end
local settings = menu.list:add_btn(nil, "Settings") local settings = menu.list:add_btn(nil, "Settings")
settings:onClicked(function() settings:onClicked(function()
backstack.push(require("settings").root) backstack.push(require("settings"):new())
end) end)
settings:add_style(theme.list_item) settings:add_style(theme.list_item)
return menu return menu
end end,
}

@ -4,6 +4,7 @@ local backstack = require("backstack")
local font = require("font") local font = require("font")
local playback = require("playback") local playback = require("playback")
local queue = require("queue") local queue = require("queue")
local screen = require("screen")
local img = { local img = {
play = "//lua/img/play.png", play = "//lua/img/play.png",
@ -18,9 +19,11 @@ local img = {
repeat_disabled = "//lua/img/repeat_disabled.png", repeat_disabled = "//lua/img/repeat_disabled.png",
} }
return function(opts) local is_now_playing_shown = false
local screen = {}
screen.root = lvgl.Object(nil, { return screen:new {
createUi = function(self)
self.root = lvgl.Object(nil, {
flex = { flex = {
flex_direction = "column", flex_direction = "column",
flex_wrap = "wrap", flex_wrap = "wrap",
@ -31,14 +34,14 @@ return function(opts)
w = lvgl.HOR_RES(), w = lvgl.HOR_RES(),
h = lvgl.VER_RES(), h = lvgl.VER_RES(),
}) })
screen.root:center() self.root:center()
screen.status_bar = widgets.StatusBar(screen.root, { self.status_bar = widgets.StatusBar(self.root, {
back_cb = backstack.pop, back_cb = backstack.pop,
transparent_bg = true, transparent_bg = true,
}) })
local info = screen.root:Object { local info = self.root:Object {
flex = { flex = {
flex_direction = "column", flex_direction = "column",
flex_wrap = "wrap", flex_wrap = "wrap",
@ -66,7 +69,7 @@ return function(opts)
text_align = 2, text_align = 2,
} }
local playlist = screen.root:Object { local playlist = self.root:Object {
flex = { flex = {
flex_direction = "row", flex_direction = "row",
justify_content = "center", justify_content = "center",
@ -112,7 +115,7 @@ return function(opts)
} }
playlist:Object({ w = 3, h = 1 }) -- spacer playlist:Object({ w = 3, h = 1 }) -- spacer
local scrubber = screen.root:Slider { local scrubber = self.root:Slider {
w = lvgl.PCT(100), w = lvgl.PCT(100),
h = 5, h = 5,
range = { min = 0, max = 100 }, range = { min = 0, max = 100 },
@ -123,7 +126,7 @@ return function(opts)
playback.position:set(scrubber:value()) playback.position:set(scrubber:value())
end) end)
local controls = screen.root:Object { local controls = self.root:Object {
flex = { flex = {
flex_direction = "row", flex_direction = "row",
justify_content = "center", justify_content = "center",
@ -173,7 +176,7 @@ return function(opts)
return string.format("%d:%02d", time // 60, time % 60) return string.format("%d:%02d", time // 60, time % 60)
end end
screen.bindings = { self.bindings = {
playback.playing:bind(function(playing) playback.playing:bind(function(playing)
if playing then if playing then
play_pause_img:set_src(img.pause) play_pause_img:set_src(img.pause)
@ -231,6 +234,12 @@ return function(opts)
playlist_total:set { text = tostring(num) } playlist_total:set { text = tostring(num) }
end), end),
} }
end,
return screen onShown = function() is_now_playing_shown = true end,
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
}

@ -7,8 +7,7 @@ local display = require("display")
local controls = require("controls") local controls = require("controls")
local bluetooth = require("bluetooth") local bluetooth = require("bluetooth")
local database = require("database") local database = require("database")
local screen = require("screen")
local settings = {}
local function SettingsScreen(title) local function SettingsScreen(title)
local menu = widgets.MenuScreen { local menu = widgets.MenuScreen {
@ -31,10 +30,11 @@ local function SettingsScreen(title)
return menu return menu
end end
function settings.bluetooth() local BluetoothSettings = screen:new {
local menu = SettingsScreen("Bluetooth") createUi = function(self)
self.menu = SettingsScreen("Bluetooth")
local enable_container = menu.content:Object { local enable_container = self.menu.content:Object {
flex = { flex = {
flex_direction = "row", flex_direction = "row",
justify_content = "flex-start", justify_content = "flex-start",
@ -52,12 +52,12 @@ function settings.bluetooth()
bluetooth.enabled:set(enabled) bluetooth.enabled:set(enabled)
end) end)
menu.content:Label { self.menu.content:Label {
text = "Paired Device", text = "Paired Device",
pad_bottom = 1, pad_bottom = 1,
}:add_style(theme.settings_title) }:add_style(theme.settings_title)
local paired_container = menu.content:Object { local paired_container = self.menu.content:Object {
flex = { flex = {
flex_direction = "row", flex_direction = "row",
justify_content = "flex-start", justify_content = "flex-start",
@ -78,17 +78,17 @@ function settings.bluetooth()
bluetooth.paired_device:set() bluetooth.paired_device:set()
end) end)
menu.content:Label { self.menu.content:Label {
text = "Nearby Devices", text = "Nearby Devices",
pad_bottom = 1, pad_bottom = 1,
}:add_style(theme.settings_title) }:add_style(theme.settings_title)
local devices = menu.content:List { local devices = self.menu.content:List {
w = lvgl.PCT(100), w = lvgl.PCT(100),
h = lvgl.SIZE_CONTENT, h = lvgl.SIZE_CONTENT,
} }
menu.bindings = { self.bindings = {
bluetooth.enabled:bind(function(en) bluetooth.enabled:bind(function(en)
if en then if en then
enable_sw:add_state(lvgl.STATE.CHECKED) enable_sw:add_state(lvgl.STATE.CHECKED)
@ -114,16 +114,18 @@ function settings.bluetooth()
end end
end) end)
} }
end end
}
function settings.headphones() local HeadphonesSettings = screen:new {
local menu = SettingsScreen("Headphones") createUi = function(self)
self.menu = SettingsScreen("Headphones")
menu.content:Label { self.menu.content:Label {
text = "Maximum volume limit", text = "Maximum volume limit",
}:add_style(theme.settings_title) }:add_style(theme.settings_title)
local volume_chooser = menu.content:Dropdown { local volume_chooser = self.menu.content:Dropdown {
options = "Line Level (-10 dB)\nCD Level (+6 dB)\nMaximum (+10dB)", options = "Line Level (-10 dB)\nCD Level (+6 dB)\nMaximum (+10dB)",
selected = 1, selected = 1,
} }
@ -134,11 +136,11 @@ function settings.headphones()
volume.limit_db:set(limits[selection]) volume.limit_db:set(limits[selection])
end) end)
menu.content:Label { self.menu.content:Label {
text = "Left/Right balance", text = "Left/Right balance",
}:add_style(theme.settings_title) }:add_style(theme.settings_title)
local balance = menu.content:Slider { local balance = self.menu.content:Slider {
w = lvgl.PCT(100), w = lvgl.PCT(100),
h = 5, h = 5,
range = { min = -100, max = 100 }, range = { min = -100, max = 100 },
@ -148,9 +150,9 @@ function settings.headphones()
volume.left_bias:set(balance:value()) volume.left_bias:set(balance:value())
end) end)
local balance_label = menu.content:Label {} local balance_label = self.menu.content:Label {}
menu.bindings = { self.bindings = {
volume.limit_db:bind(function(limit) volume.limit_db:bind(function(limit)
for i = 1, #limits do for i = 1, #limits do
if limits[i] == limit then if limits[i] == limit then
@ -175,14 +177,14 @@ function settings.headphones()
end end
end), end),
} }
end
}
return menu local DisplaySettings = screen:new {
end createUi = function(self)
self.menu = SettingsScreen("Display")
function settings.display()
local menu = SettingsScreen("Display")
local brightness_title = menu.content:Object { local brightness_title = self.menu.content:Object {
flex = { flex = {
flex_direction = "row", flex_direction = "row",
justify_content = "flex-start", justify_content = "flex-start",
@ -196,7 +198,7 @@ function settings.display()
local brightness_pct = brightness_title:Label {} local brightness_pct = brightness_title:Label {}
brightness_pct:add_style(theme.settings_title) brightness_pct:add_style(theme.settings_title)
local brightness = menu.content:Slider { local brightness = self.menu.content:Slider {
w = lvgl.PCT(100), w = lvgl.PCT(100),
h = 5, h = 5,
range = { min = 0, max = 100 }, range = { min = 0, max = 100 },
@ -206,19 +208,19 @@ function settings.display()
display.brightness:set(brightness:value()) display.brightness:set(brightness:value())
end) end)
menu.bindings = { self.bindings = {
display.brightness:bind(function(b) display.brightness:bind(function(b)
brightness_pct:set { text = tostring(b) .. "%" } brightness_pct:set { text = tostring(b) .. "%" }
end) end)
} }
end
}
return menu local InputSettings = screen:new {
end createUi = function(self)
self.menu = SettingsScreen("Input Method")
function settings.input()
local menu = SettingsScreen("Input Method")
menu.content:Label { self.menu.content:Label {
text = "Control scheme", text = "Control scheme",
}:add_style(theme.settings_title) }:add_style(theme.settings_title)
@ -239,11 +241,11 @@ function settings.input()
option_idx = option_idx + 1 option_idx = option_idx + 1
end end
local controls_chooser = menu.content:Dropdown { local controls_chooser = self.menu.content:Dropdown {
options = options, options = options,
} }
menu.bindings = { self.bindings = {
controls.scheme:bind(function(s) controls.scheme:bind(function(s)
local option = scheme_to_option[s] local option = scheme_to_option[s]
controls_chooser:set({ selected = option }) controls_chooser:set({ selected = option })
@ -256,31 +258,31 @@ function settings.input()
controls.scheme:set(scheme) controls.scheme:set(scheme)
end) end)
menu.content:Label { self.menu.content:Label {
text = "Scroll Sensitivity", text = "Scroll Sensitivity",
}:add_style(theme.settings_title) }:add_style(theme.settings_title)
local slider_scale = 4; -- Power steering local slider_scale = 4; -- Power steering
local sensitivity = menu.content:Slider { local sensitivity = self.menu.content:Slider {
w = lvgl.PCT(90), w = lvgl.PCT(90),
h = 5, h = 5,
range = { min = 0, max = 255/slider_scale }, range = { min = 0, max = 255 / slider_scale },
value = controls.scroll_sensitivity:get()/slider_scale, value = controls.scroll_sensitivity:get() / slider_scale,
} }
sensitivity:onevent(lvgl.EVENT.VALUE_CHANGED, function() sensitivity:onevent(lvgl.EVENT.VALUE_CHANGED, function()
controls.scroll_sensitivity:set(sensitivity:value()*slider_scale) controls.scroll_sensitivity:set(sensitivity:value() * slider_scale)
end) end)
end
}
return menu local DatabaseSettings = screen:new {
end createUi = function(self)
self.menu = SettingsScreen("Database")
function settings.database()
local menu = SettingsScreen("Database")
local db = require("database") local db = require("database")
widgets.Row(menu.content, "Schema version", db.version()) widgets.Row(self.menu.content, "Schema version", db.version())
widgets.Row(menu.content, "Size on disk", string.format("%.1f KiB", db.size() / 1024)) widgets.Row(self.menu.content, "Size on disk", string.format("%.1f KiB", db.size() / 1024))
local actions_container = menu.content:Object { local actions_container = self.menu.content:Object {
w = lvgl.PCT(100), w = lvgl.PCT(100),
h = lvgl.SIZE_CONTENT, h = lvgl.SIZE_CONTENT,
flex = { flex = {
@ -299,55 +301,60 @@ function settings.database()
update:onClicked(function() update:onClicked(function()
database.update() database.update()
end) end)
end end
}
function settings.firmware() local FirmwareSettings = screen:new {
local menu = SettingsScreen("Firmware") createUi = function(self)
self.menu = SettingsScreen("Firmware")
local version = require("version") local version = require("version")
widgets.Row(menu.content, "ESP32", version.esp()) widgets.Row(self.menu.content, "ESP32", version.esp())
widgets.Row(menu.content, "SAMD21", version.samd()) widgets.Row(self.menu.content, "SAMD21", version.samd())
widgets.Row(menu.content, "Collator", version.collator()) widgets.Row(self.menu.content, "Collator", version.collator())
end end
}
function settings.root() local LicensesScreen = screen:new {
local menu = widgets.MenuScreen { createUi = function(self)
self.root = require("licenses")()
end
}
return screen:new {
createUi = function(self)
self.menu = widgets.MenuScreen {
show_back = true, show_back = true,
title = "Settings", title = "Settings",
} }
menu.list = menu.root:List { self.list = self.menu.root:List {
w = lvgl.PCT(100), w = lvgl.PCT(100),
h = lvgl.PCT(100), h = lvgl.PCT(100),
flex_grow = 1, flex_grow = 1,
} }
local function section(name) local function section(name)
menu.list:add_text(name):add_style(theme.list_heading) self.list:add_text(name):add_style(theme.list_heading)
end end
local function submenu(name, fn) local function submenu(name, class)
local item = menu.list:add_btn(nil, name) local item = self.list:add_btn(nil, name)
item:onClicked(function() item:onClicked(function()
backstack.push(fn) backstack.push(class:new())
end) end)
item:add_style(theme.list_item) item:add_style(theme.list_item)
end end
section("Audio") section("Audio")
submenu("Bluetooth", settings.bluetooth) submenu("Bluetooth", BluetoothSettings)
submenu("Headphones", settings.headphones) submenu("Headphones", HeadphonesSettings)
section("Interface") section("Interface")
submenu("Display", settings.display) submenu("Display", DisplaySettings)
submenu("Input Method", settings.input) submenu("Input Method", InputSettings)
section("System") section("System")
submenu("Database", settings.database) submenu("Database", DatabaseSettings)
submenu("Firmware", settings.firmware) submenu("Firmware", FirmwareSettings)
submenu("Licenses", function() submenu("Licenses", LicensesScreen)
return require("licenses")() end
end) }
return menu
end
return settings

@ -5,6 +5,7 @@
idf_component_register( idf_component_register(
SRCS "lua_thread.cpp" "bridge.cpp" "property.cpp" "lua_database.cpp" SRCS "lua_thread.cpp" "bridge.cpp" "property.cpp" "lua_database.cpp"
"lua_queue.cpp" "lua_version.cpp" "lua_controls.cpp" "registry.cpp" "lua_queue.cpp" "lua_version.cpp" "lua_controls.cpp" "registry.cpp"
"lua_screen.cpp"
INCLUDE_DIRS "include" INCLUDE_DIRS "include"
REQUIRES "drivers" "lvgl" "tinyfsm" "events" "system_fsm" "database" REQUIRES "drivers" "lvgl" "tinyfsm" "events" "system_fsm" "database"
"esp_timer" "battery" "esp-idf-lua" "luavgl" "lua-linenoise" "lua-term" "esp_timer" "battery" "esp-idf-lua" "luavgl" "lua-linenoise" "lua-term"

@ -19,6 +19,7 @@
#include "lua_controls.hpp" #include "lua_controls.hpp"
#include "lua_database.hpp" #include "lua_database.hpp"
#include "lua_queue.hpp" #include "lua_queue.hpp"
#include "lua_screen.hpp"
#include "lua_version.hpp" #include "lua_version.hpp"
#include "lvgl.h" #include "lvgl.h"
@ -84,6 +85,7 @@ auto Bridge::installBaseModules(lua_State* L) -> void {
RegisterDatabaseModule(L); RegisterDatabaseModule(L);
RegisterQueueModule(L); RegisterQueueModule(L);
RegisterVersionModule(L); RegisterVersionModule(L);
RegisterScreenModule(L);
} }
auto Bridge::installLvgl(lua_State* L) -> void { auto Bridge::installLvgl(lua_State* L) -> void {

@ -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

@ -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

@ -27,6 +27,9 @@ class Screen {
Screen(); Screen();
virtual ~Screen(); virtual ~Screen();
virtual auto onShown() -> void {}
virtual auto onHidden() -> void {}
auto root() -> lv_obj_t* { return root_; } auto root() -> lv_obj_t* { return root_; }
auto content() -> lv_obj_t* { return content_; } auto content() -> lv_obj_t* { return content_; }
auto alert() -> lv_obj_t* { return alert_; } auto alert() -> lv_obj_t* { return alert_; }

@ -18,6 +18,9 @@ class Lua : public Screen {
Lua(); Lua();
~Lua(); ~Lua();
auto onShown() -> void override;
auto onHidden() -> void override;
auto SetObjRef(lua_State*) -> void; auto SetObjRef(lua_State*) -> void;
private: private:

@ -7,8 +7,10 @@
#include "screen_lua.hpp" #include "screen_lua.hpp"
#include "core/lv_obj_tree.h" #include "core/lv_obj_tree.h"
#include "lua.h"
#include "lua.hpp" #include "lua.hpp"
#include "lua_thread.hpp"
#include "luavgl.h" #include "luavgl.h"
namespace ui { 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 { auto Lua::SetObjRef(lua_State* s) -> void {
assert(s_ == nullptr); assert(s_ == nullptr);
s_ = s; s_ = s;

@ -125,7 +125,8 @@ lua::Property UiState::sPlaybackPlaying{
}}; }};
lua::Property UiState::sPlaybackTrack{}; lua::Property UiState::sPlaybackTrack{};
lua::Property UiState::sPlaybackPosition{0, [](const lua::LuaValue& val) { lua::Property UiState::sPlaybackPosition{
0, [](const lua::LuaValue& val) {
int current_val = std::get<int>(sPlaybackPosition.Get()); int current_val = std::get<int>(sPlaybackPosition.Get());
if (!std::holds_alternative<int>(val)) { if (!std::holds_alternative<int>(val)) {
return false; return false;
@ -136,10 +137,12 @@ lua::Property UiState::sPlaybackPosition{0, [](const lua::LuaValue& val) {
if (!std::holds_alternative<audio::Track>(track)) { if (!std::holds_alternative<audio::Track>(track)) {
return false; return false;
} }
events::Audio().Dispatch(audio::SeekFile{.offset = (uint32_t)new_val, .filename = std::get<audio::Track>(track).filepath}); events::Audio().Dispatch(audio::SeekFile{
.offset = (uint32_t)new_val,
.filename = std::get<audio::Track>(track).filepath});
} }
return true; return true;
}}; }};
lua::Property UiState::sQueuePosition{0}; lua::Property UiState::sQueuePosition{0};
lua::Property UiState::sQueueSize{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) { void UiState::PushScreen(std::shared_ptr<Screen> screen) {
lv_obj_set_parent(sAlertContainer, screen->alert());
if (sCurrentScreen) { if (sCurrentScreen) {
sCurrentScreen->onHidden();
sScreens.push(sCurrentScreen); sScreens.push(sCurrentScreen);
} }
sCurrentScreen = screen; sCurrentScreen = screen;
lv_obj_set_parent(sAlertContainer, sCurrentScreen->alert()); sCurrentScreen->onShown();
} }
int UiState::PopScreen() { int UiState::PopScreen() {
if (sScreens.empty()) { if (sScreens.empty()) {
return 0; return 0;
} }
sCurrentScreen = sScreens.top(); lv_obj_set_parent(sAlertContainer, sScreens.top()->alert());
lv_obj_set_parent(sAlertContainer, sCurrentScreen->alert());
sCurrentScreen->onHidden();
sCurrentScreen = sScreens.top();
sScreens.pop(); sScreens.pop();
sCurrentScreen->onShown();
return sScreens.size(); return sScreens.size();
} }
@ -539,7 +550,7 @@ void Lua::entry() {
auto Lua::PushLuaScreen(lua_State* s) -> int { auto Lua::PushLuaScreen(lua_State* s) -> int {
// Ensure the arg looks right before continuing. // 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 // 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 // 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()); lv_group_set_default(new_screen->group());
// Call the constructor for this screen. // Call the constructor for this screen.
lua_settop(s, 1); // Make sure the function is actually at top of stack // lua_settop(s, 1); // Make sure the screen is actually at top of stack
lua::CallProtected(s, 0, 1); 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); new_screen->SetObjRef(s);
// Finally, push the now-initialised screen as if it were a regular C++ // 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 { 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; return 1;
} }

Loading…
Cancel
Save