summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--lib/luavgl/src/font.c44
-rw-r--r--lib/luavgl/src/fs.c11
-rw-r--r--lib/luavgl/src/luavgl.h2
-rw-r--r--lua/font.lua36
-rw-r--r--lua/main.lua115
-rw-r--r--src/tangara/lua/bridge.cpp62
-rw-r--r--src/tangara/lua/lua_font.cpp115
-rw-r--r--src/tangara/lua/lua_font.hpp15
8 files changed, 252 insertions, 148 deletions
diff --git a/lib/luavgl/src/font.c b/lib/luavgl/src/font.c
index 87e0bbef..7751e0c7 100644
--- a/lib/luavgl/src/font.c
+++ b/lib/luavgl/src/font.c
@@ -1,49 +1,23 @@
#include "luavgl.h"
#include "private.h"
-static char *to_lower(char *str)
-{
- for (char *s = str; *s; ++s)
- *s = *s >= 'A' && *s <= 'Z' ? *s | 0x60 : *s;
- return str;
-}
-
-static char *luavgl_strchr(const char *s, char c)
-{
- while (*s) {
- if (c == *s) {
- return (char *)s;
- }
- s++;
- }
- return NULL;
-}
-
-/**
- * Dynamic font family fallback is not supported.
- * The fallback only happen when font creation fails and continue to try next
- * one. Fallback logic in lvgl is supposed to be system wide.
- *
- * lvgl.Font("MiSansW medium, montserrat", 24, "normal")
- */
static int luavgl_font_create(lua_State *L)
{
-
if (!lua_isstring(L, 1)) {
return luaL_argerror(L, 1, "expect string");
}
- const char *name = lua_tostring(L, 1);
- const lv_font_t *font = NULL;
+ if (!lua_isfunction(L, 2)) {
+ return luaL_argerror(L, 1, "expect function");
+ }
luavgl_ctx_t *ctx = luavgl_context(L);
- if (ctx->make_font) {
- font = ctx->make_font(name);
+ if (!ctx->make_font) {
+ return luaL_error(L, "cannot create font");
}
- if (font) {
- lua_pushlightuserdata(L, (void *)font);
- return 1;
- }
+ const char *name = lua_tostring(L, 1);
+ int cb_ref = luaL_ref(L, LUA_REGISTRYINDEX);
+ ctx->make_font(L, name, cb_ref);
- return luaL_error(L, "cannot create font");
+ return 0;
}
diff --git a/lib/luavgl/src/fs.c b/lib/luavgl/src/fs.c
index 4deec5e5..4918dcb6 100644
--- a/lib/luavgl/src/fs.c
+++ b/lib/luavgl/src/fs.c
@@ -1,6 +1,17 @@
#include "luavgl.h"
#include "private.h"
+static char *luavgl_strchr(const char *s, char c)
+{
+ while (*s) {
+ if (c == *s) {
+ return (char *)s;
+ }
+ s++;
+ }
+ return NULL;
+}
+
typedef struct luavgl_fs_s {
lv_fs_file_t file;
bool closed; /* userdata exists but lv_fs has been closed */
diff --git a/lib/luavgl/src/luavgl.h b/lib/luavgl/src/luavgl.h
index 6c5f8e98..8b7b92aa 100644
--- a/lib/luavgl/src/luavgl.h
+++ b/lib/luavgl/src/luavgl.h
@@ -12,7 +12,7 @@
extern "C" {
#endif
-typedef const lv_font_t *(*make_font_cb)(const char *);
+typedef void (*make_font_cb)(lua_State *L, const char *, int cb);
typedef void (*delete_font_cb)(const lv_font_t *);
typedef int (*luavgl_pcall_t)(lua_State *L, int nargs, int nresults);
diff --git a/lua/font.lua b/lua/font.lua
index 7afa1e01..c76602ec 100644
--- a/lua/font.lua
+++ b/lua/font.lua
@@ -1,6 +1,36 @@
local lvgl = require("lvgl")
-return {
- fusion_12 = lvgl.Font("//lua/fonts/fusion12"),
- fusion_10 = lvgl.Font("//lua/fonts/fusion10"),
+local fonts = {}
+local fonts_priv = {
+ has_invoked_cb = false,
+ cb = nil,
}
+
+function fonts_priv.has_loaded_all()
+ return fonts.fusion_12 and fonts.fusion_10
+end
+
+function fonts_priv.invoke_cb()
+ if fonts_priv.has_invoked_cb or not fonts_priv.cb then return end
+ if not fonts_priv.has_loaded_all() then return end
+ fonts_priv.has_invoked_cb = true
+ fonts_priv.cb()
+end
+
+lvgl.Font("//lua/fonts/fusion12", function(font)
+ fonts.fusion_12 = font
+ fonts_priv.invoke_cb()
+end)
+
+lvgl.Font("//lua/fonts/fusion10", function(font)
+ fonts.fusion_10 = font
+ fonts_priv.invoke_cb()
+end)
+
+function fonts.on_loaded(cb)
+ fonts_priv.cb = cb
+ fonts_priv.has_invoked_cb = false
+ fonts_priv.invoke_cb()
+end
+
+return fonts
diff --git a/lua/main.lua b/lua/main.lua
index 1540004d..4cd754b6 100644
--- a/lua/main.lua
+++ b/lua/main.lua
@@ -1,4 +1,10 @@
+-- Load fonts first, since they're parsed asynchronously and we can do much of
+-- the other UI setup in parallel.
local font = require("font")
+
+-- require() everything else needed for the main menu + global bindings. Do
+-- this now instead of in init_ui because loading and parsing the scripts can
+-- take a while.
local vol = require("volume")
local theme = require("theme")
local controls = require("controls")
@@ -7,55 +13,66 @@ local sd_card = require("sd_card")
local backstack = require("backstack")
local main_menu = require("main_menu")
-local theme_dark = require("theme_dark")
-theme.set(theme_dark)
+local function init_ui()
+ -- Load the theme within init_ui because the theme needs fonts to be ready.
+ local theme_dark = require("theme_dark")
+ theme.set(theme_dark)
-local lock_time = time.ticks()
+ local lock_time = time.ticks()
--- Set up property bindings that are used across every screen.
-GLOBAL_BINDINGS = {
- -- Show an alert with the current volume whenever the volume changes
- vol.current_pct:bind(function(pct)
- require("alerts").show(function()
- local container = lvgl.Object(nil, {
- w = lvgl.PCT(80),
- h = lvgl.SIZE_CONTENT,
- flex = {
- flex_direction = "column",
- justify_content = "center",
- align_items = "center",
- align_content = "center",
- },
- radius = 8,
- pad_all = 2,
- })
- theme.set_style(container, "pop_up")
- container:Label {
- text = string.format("Volume %i%%", pct),
- text_font = font.fusion_10
- }
- container:Bar {
- w = lvgl.PCT(100),
- h = 8,
- range = { min = 0, max = 100 },
- value = pct,
- }
- container:center()
- end)
- end),
- -- When the device has been locked for a while, default to showing the now
- -- playing screen after unlocking.
- controls.lock_switch:bind(function(locked)
- if locked then
- lock_time = time.ticks()
- elseif time.ticks() - lock_time > 8000 then
- local queue = require("queue")
- if queue.size:get() > 0 then
- require("playing"):pushIfNotShown()
+ -- Set up property bindings that are used across every screen.
+ GLOBAL_BINDINGS = {
+ -- Show an alert with the current volume whenever the volume changes
+ vol.current_pct:bind(function(pct)
+ require("alerts").show(function()
+ local container = lvgl.Object(nil, {
+ w = lvgl.PCT(80),
+ h = lvgl.SIZE_CONTENT,
+ flex = {
+ flex_direction = "column",
+ justify_content = "center",
+ align_items = "center",
+ align_content = "center",
+ },
+ radius = 8,
+ pad_all = 2,
+ })
+ theme.set_style(container, "pop_up")
+ container:Label {
+ text = string.format("Volume %i%%", pct),
+ text_font = font.fusion_10
+ }
+ container:Bar {
+ w = lvgl.PCT(100),
+ h = 8,
+ range = { min = 0, max = 100 },
+ value = pct,
+ }
+ container:center()
+ end)
+ end),
+ -- When the device has been locked for a while, default to showing the now
+ -- playing screen after unlocking.
+ controls.lock_switch:bind(function(locked)
+ if locked then
+ lock_time = time.ticks()
+ elseif time.ticks() - lock_time > 8000 then
+ local queue = require("queue")
+ if queue.size:get() > 0 then
+ require("playing"):pushIfNotShown()
+ end
end
- end
- end),
- sd_card.mounted:bind(function(mounted)
- backstack.reset(main_menu:new())
- end),
-}
+ end),
+ sd_card.mounted:bind(function(mounted)
+ backstack.reset(main_menu:new())
+ end),
+ }
+end
+
+-- Wait for fonts to finish, then show the main menu.
+-- We could show an intermediary Lua-controlled splash/loading UI whilst we
+-- wait, but in practice loading the fonts takes only a few hundred ms longer
+-- than all the other UI init.
+font.on_loaded(function()
+ init_ui()
+end)
diff --git a/src/tangara/lua/bridge.cpp b/src/tangara/lua/bridge.cpp
index 0f1e65ac..1c757a22 100644
--- a/src/tangara/lua/bridge.cpp
+++ b/src/tangara/lua/bridge.cpp
@@ -24,6 +24,7 @@
#include "lua/lua_controls.hpp"
#include "lua/lua_database.hpp"
#include "lua/lua_filesystem.hpp"
+#include "lua/lua_font.hpp"
#include "lua/lua_queue.hpp"
#include "lua/lua_screen.hpp"
#include "lua/lua_testing.hpp"
@@ -50,65 +51,6 @@ namespace lua {
static constexpr char kBridgeKey[] = "bridge";
-static auto make_font_cb(const char* name) -> const lv_font_t* {
- // Most Lua file paths start with "//" in order to deal with LVGL's Windows-y
- // approach to paths. Try to handle such paths correctly so that paths in Lua
- // code look a bit more consistent.
- {
- std::string name_str = name;
- if (name_str.starts_with("//")) {
- name++;
- }
- }
-
- // This following is a bit C-brained. Sorry.
-
- ESP_LOGI(kTag, "load font '%s'", name);
- FILE* f = fopen(name, "r");
- if (!f) {
- return NULL;
- }
-
- uint8_t* data = NULL;
- long len = 0;
- lv_font_t* font = NULL;
-
- if (fseek(f, 0, SEEK_END)) {
- goto fail_with_file;
- }
-
- len = ftell(f);
- if (len <= 0) {
- goto fail_with_file;
- }
-
- if (fseek(f, 0, SEEK_SET)) {
- goto fail_with_file;
- }
-
- data = reinterpret_cast<uint8_t*>(heap_caps_malloc(len, MALLOC_CAP_SPIRAM));
- if (!data) {
- goto fail_with_buffer;
- }
-
- if (fread(data, 1, len, f) < len) {
- goto fail_with_buffer;
- }
-
- // We can finally start parsing the font!
- font = lv_binfont_create_from_buffer(data, len);
-
-fail_with_buffer:
- // LVGL copies the font data out of the buffer, so we don't need to big raw
- // data alloc after this function returns.
- heap_caps_free(data);
-
-fail_with_file:
- fclose(f);
-
- return font;
-}
-
static auto delete_font_cb(const lv_font_t* font) -> void {
// FIXME: luavgl never actually calls this?
}
@@ -146,7 +88,7 @@ auto Bridge::installBaseModules(lua_State* L) -> void {
auto Bridge::installLvgl(lua_State* L) -> void {
luavgl_set_pcall(L, CallProtected);
- luavgl_set_font_extension(L, make_font_cb, delete_font_cb);
+ luavgl_set_font_extension(L, loadFont, delete_font_cb);
luaL_requiref(L, "lvgl", luaopen_lvgl, true);
lua_pop(L, 1);
}
diff --git a/src/tangara/lua/lua_font.cpp b/src/tangara/lua/lua_font.cpp
new file mode 100644
index 00000000..e5913492
--- /dev/null
+++ b/src/tangara/lua/lua_font.cpp
@@ -0,0 +1,115 @@
+/*
+ * Copyright 2024 jacqueline <me@jacqueline.id.au>
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#include "lua_font.hpp"
+
+#include <cstdint>
+#include <string>
+
+#include "lauxlib.h"
+#include "lua.h"
+#include "lvgl.h"
+
+#include "events/event_queue.hpp"
+#include "lua/bridge.hpp"
+#include "lua/lua_registry.hpp"
+#include "lua/lua_thread.hpp"
+
+namespace lua {
+
+[[maybe_unused]] static constexpr char kTag[] = "lua_font";
+
+/* Reads the given file completely into PSRAM. */
+static auto readFont(std::string path) -> std::span<uint8_t> {
+ // This following is a bit C-brained. Sorry.
+ FILE* f = fopen(path.c_str(), "r");
+ if (!f) {
+ return {};
+ }
+
+ uint8_t* data = NULL;
+ long len = 0;
+
+ if (fseek(f, 0, SEEK_END)) {
+ goto fail;
+ }
+
+ len = ftell(f);
+ if (len <= 0) {
+ goto fail;
+ }
+
+ if (fseek(f, 0, SEEK_SET)) {
+ len = 0;
+ goto fail;
+ }
+
+ data = reinterpret_cast<uint8_t*>(heap_caps_malloc(len, MALLOC_CAP_SPIRAM));
+ if (!data) {
+ len = 0;
+ goto fail;
+ }
+
+ if (fread(data, 1, len, f) < len) {
+ heap_caps_free(data);
+ len = 0;
+ }
+
+fail:
+ fclose(f);
+
+ return {data, static_cast<size_t>(len)};
+}
+
+static auto parseFont(std::span<uint8_t> data) -> lv_font_t* {
+ if (data.empty()) {
+ return nullptr;
+ }
+
+ lv_font_t* font = lv_binfont_create_from_buffer(data.data(), data.size());
+ heap_caps_free(data.data());
+
+ return font;
+}
+
+auto loadFont(lua_State* L, const char* path, int cb_ref) -> void {
+ // Most Lua file paths start with "//" in order to deal with LVGL's Windows-y
+ // approach to paths. Try to handle such paths correctly so that paths in Lua
+ // code look a bit more consistent.
+ std::string path_str = path;
+ if (path_str.starts_with("//")) {
+ path++;
+ path_str = path;
+ }
+
+ // Do the file read from the current thread, since the path might be for a
+ // file in flash, and we can't read from flash in a background task.
+ auto font_data = readFont(path_str);
+
+ Bridge* bridge = Bridge::Get(L);
+ bridge->services().bg_worker().Dispatch<void>([=]() {
+ // Do the parsing now that we're in the background.
+ lv_font_t* font = parseFont(font_data);
+
+ // Hop back to the UI task to invoke the Lua callback.
+ events::Ui().RunOnTask([=] {
+ // Retrieve the callback by ref, and release the ref.
+ lua_rawgeti(L, LUA_REGISTRYINDEX, cb_ref);
+ luaL_unref(L, LUA_REGISTRYINDEX, cb_ref);
+
+ // We always invoke the callback, but we don't always have a result.
+ if (font) {
+ lua_pushlightuserdata(L, (void*)font);
+ } else {
+ lua_pushnil(L);
+ }
+
+ CallProtected(L, 1, 0);
+ });
+ });
+}
+
+} // namespace lua
diff --git a/src/tangara/lua/lua_font.hpp b/src/tangara/lua/lua_font.hpp
new file mode 100644
index 00000000..dfec4eb0
--- /dev/null
+++ b/src/tangara/lua/lua_font.hpp
@@ -0,0 +1,15 @@
+/*
+ * Copyright 2024 jacqueline <me@jacqueline.id.au>
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#pragma once
+
+#include "lua.hpp"
+
+namespace lua {
+
+auto loadFont(lua_State* L, const char* name, int cb_ref) -> void;
+
+}