From 9eda9b0f268b90fa37d60846cd6fc0dbe807bb9c Mon Sep 17 00:00:00 2001 From: Kaj Schittecat Date: Sat, 17 Oct 2026 12:15:18 +0200 Subject: [PATCH] apps: Store lists up to 40 apps, canvases fit the big screens, button:bg - The catalog parse stopped at 16 rows and the catalog has 16, so a 17th app (Blackjack, #681) was silently never listed; installed apps had the same cap. Both are 40 now, and the catalog buffer grows from 8 KB to 24 KB so the JSON is not cut off. PSRAM only. - wada.ui.canvas raised a Lua error above 480 px, which killed apps that size a canvas from on_open(w, h) on the T-Display P4 (body ~575 tall), Tanmatsu and CrowPanel 5 (800 wide) (Discord). Sides are now clamped to the screen's long side instead. - button:bg(fill [, border]) colours a button (or boxes a label), so apps no longer fake coloured buttons from panels that keys cannot focus (Discord). Co-Authored-By: Claude Opus 5.5 --- deploy/site/sdk.html | 4 +++- src/ui-touch/LuaAppHost.cpp | 31 +++++++++++++++++++++++++++++-- src/ui-touch/UITask.cpp | 6 ++++-- 3 files changed, 36 insertions(+), 5 deletions(-) diff --git a/deploy/site/sdk.html b/deploy/site/sdk.html index 5c42c67..6c3d7c3 100644 --- a/deploy/site/sdk.html +++ b/deploy/site/sdk.html @@ -261,17 +261,19 @@ return app wada.ui.clear()Remove every widget from the app's page, leaving the page itself. This is how you build an app with more than one screen: clear, then create the next screen's widgets in the same place you built the first. Any handle you still hold from before the clear is dead: calling a method on it does nothing rather than crashing, but it will not come back, so re-create and re-assign. Older firmware has no ui.clear: write local clear = ui.clear or function() end if you need to run on both. wada.ui.list(x, y, w, h)A scrollable list of selectable rows, the "pick one of N" widget. Methods: add(text [, fn]) returning the row index, set(i, text), color(i, c), select(i) (highlights and scrolls it into view), selected(), count(), clear(), pos(x, y). Rows are real buttons, so keyboard and trackball navigation walks them on touchless boards without you doing anything. wada.ui.input(title, initial, cb)A modal text field. cb(text) on OK, cb(nil) on cancel or on the dialog being closed any other way: exactly one call, always, so an app that disabled itself while waiting always gets to re-enable. Uses the firmware's own dialog and keyboard, so it behaves identically on a touchscreen, on the Tanmatsu's physical keyboard and on a trackball board. One prompt at a time. - wada.ui.canvas(w, h)A drawing surface. See the canvas methods below. + wada.ui.canvas(w, h)A drawing surface. See the canvas methods below. Each side is clamped to the screen's long side (at least 480), so sizing it from on_open(w, h) is safe on every board. wada.ui.chart(points)A line chart sized to the page. The Airtime and RF Monitor primitive. wada.ui.scroll(on)Allow the page to scroll when content is taller than the screen. wada.ui.text_h(size)Line height in pixels for a size class. Use it to lay out a canvas. wada.ui.colorsTheme table: text, sub, bg, panel, accent, good, bad, and on newer firmware warn (amber) and border (a panel's frame). Write colors.warn or 0xE8A33D to run on both.

Label handle

+

A button returns the same kind of handle, so all of these work on buttons too; color sets a button's text colour.

+
lbl:set(text)Replace the text.
lbl:color(c)Set the colour, e.g. wada.ui.colors.accent.
lbl:pos(x, y)Place it explicitly instead of in flow.
lbl:bg(fill [, border])Fill colour behind it: on a button handle, the button's own colour (and its frame when border is given), so a coloured button stays a focus stop for the keys and trackball. New in beta_96: write if b.bg then b:bg(c) end to run on older firmware too.
lbl:width(px, [align])Fix the width; longer text wraps instead of running off the panel. Optional "center" / "right" aligns the text inside that width (default left).

Canvas handle

diff --git a/src/ui-touch/LuaAppHost.cpp b/src/ui-touch/LuaAppHost.cpp index 39a2690..3cd1bdb 100644 --- a/src/ui-touch/LuaAppHost.cpp +++ b/src/ui-touch/LuaAppHost.cpp @@ -437,8 +437,17 @@ int cvGc(lua_State* L) { int uiCanvas(lua_State* L) { if (!s_h || !s_h->body) return luaL_error(L, "no app body"); int w = (int)luaL_checkinteger(L, 1), h = (int)luaL_checkinteger(L, 2); - luaL_argcheck(L, w > 0 && w <= 480, 1, "width 1..480"); - luaL_argcheck(L, h > 0 && h <= 480, 2, "height 1..480"); + luaL_argcheck(L, w > 0, 1, "width must be positive"); + luaL_argcheck(L, h > 0, 2, "height must be positive"); + // Up to the screen's long side (at least 480). A hard 480 made apps that size + // a canvas from on_open(w, h) die with a Lua error on the larger screens: + // the T-Display P4's body is ~575 tall, the Tanmatsu and CrowPanel 5 are 800 + // wide (Discord, Blackjack on the P4). Larger requests are clamped, not fatal. + int lim = (int)lv_disp_get_hor_res(nullptr); + if ((int)lv_disp_get_ver_res(nullptr) > lim) lim = (int)lv_disp_get_ver_res(nullptr); + if (lim < 480) lim = 480; + if (w > lim) w = lim; + if (h > lim) h = lim; size_t bytes = (size_t)w * h * sizeof(lv_color_t); lv_color_t* buf = (lv_color_t*)heap_caps_malloc(bytes, MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT); if (!buf) return luaL_error(L, "canvas alloc failed"); @@ -498,6 +507,23 @@ int lbColor(lua_State* L) { return 0; } +// button:bg(fill [, border]) -- the fill colour of a button (or a label, which +// then gets a solid box behind its text). Apps asked for coloured buttons and +// were drawing their own from panels, which are no focus stop for keypad +// navigation. Older firmware has no bg: `if b.bg then b:bg(c) end`. +int lbBg(lua_State* L) { + WidgetUd* u = checkLabel(L); + if (!u->obj) return 0; + lv_obj_set_style_bg_color(u->obj, lv_color_hex(argColor(L, 2)), LV_PART_MAIN); + lv_obj_set_style_bg_opa(u->obj, LV_OPA_COVER, LV_PART_MAIN); + if (!lua_isnoneornil(L, 3)) { + lv_obj_set_style_border_color(u->obj, lv_color_hex(argColor(L, 3)), LV_PART_MAIN); + if (lv_obj_get_style_border_width(u->obj, LV_PART_MAIN) == 0) + lv_obj_set_style_border_width(u->obj, 1, LV_PART_MAIN); + } + return 0; +} + int lbWidth(lua_State* L) { // label:width(px [, "left"|"center"|"right"]) — wraps instead of overflowing WidgetUd* u = checkLabel(L); if (u->obj) { @@ -3068,6 +3094,7 @@ void openWada(lua_State* L) { lua_pushcfunction(L, lbPos); lua_setfield(L, -2, "pos"); lua_pushcfunction(L, lbColor); lua_setfield(L, -2, "color"); lua_pushcfunction(L, lbWidth); lua_setfield(L, -2, "width"); + lua_pushcfunction(L, lbBg); lua_setfield(L, -2, "bg"); lua_setfield(L, -2, "__index"); lua_pop(L, 1); } diff --git a/src/ui-touch/UITask.cpp b/src/ui-touch/UITask.cpp index 28b444e..ffa6201 100644 --- a/src/ui-touch/UITask.cpp +++ b/src/ui-touch/UITask.cpp @@ -33609,14 +33609,16 @@ struct LuaCatApp { char id[20]; char name[28]; char ver[12]; char desc[72]; cha // submission where a raw private-use codepoint would not be. Mapped to a glyph // by luaAppIconGlyph(); empty = the generic app symbol. struct LuaInstApp { char id[20]; char name[28]; char ver[12]; char icon[12]; }; -static const int kLuaCatMax = 16, kLuaInstMax = 16; +// The catalog reached 16 apps in beta_95, and the parse loop stops at the cap, +// so a 17th row (or install) was silently never listed. PSRAM only. +static const int kLuaCatMax = 40, kLuaInstMax = 40; static const int kLangCatMax = LANG_COUNT + 2, kLangInstMax = LANG_COUNT; static LuaCatApp* s_lua_cat = nullptr; // PSRAM, allocated on first store use static int s_lua_cat_n = 0; // -1 = last fetch failed static LuaInstApp* s_lua_inst = nullptr; // PSRAM (see luaStoreTablesReady) static int s_lua_inst_n = 0; static char* s_luacat_buf = nullptr; // catalog JSON (PSRAM, worker-filled) -static const size_t kLuaCatBufMax = 8192; +static const size_t kLuaCatBufMax = 24576; // ~280 B per catalog row; PSRAM static volatile bool s_luacat_request = false, s_luacat_done = false; static volatile bool s_luainst_request = false, s_luainst_done = false; static bool s_luainst_ok = false;