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 <noreply@anthropic.com>
This commit is contained in:
Kaj SchittecatandClaude Opus 5.5 committed 2026-10-17 12:15:18 +02:00
1 parent 5198813f99
commit 9eda9b0f26
3 files changed
+36 -5

No files matched your search

+3 -1
View File
@@ -261,17 +261,19 @@ return app</code></pre>
<tr><td>wada.ui.clear()</td><td>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. <b>Any handle you still hold from before the clear is dead</b>: 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 <code>ui.clear</code>: write <code>local clear = ui.clear or function() end</code> if you need to run on both.</td></tr>
<tr><td>wada.ui.list(x, y, w, h)</td><td>A scrollable list of selectable rows, the "pick one of N" widget. Methods: <code>add(text [, fn])</code> returning the row index, <code>set(i, text)</code>, <code>color(i, c)</code>, <code>select(i)</code> (highlights <i>and</i> scrolls it into view), <code>selected()</code>, <code>count()</code>, <code>clear()</code>, <code>pos(x, y)</code>. Rows are real buttons, so keyboard and trackball navigation walks them on touchless boards without you doing anything.</td></tr>
<tr><td>wada.ui.input(title, initial, cb)</td><td>A modal text field. <code>cb(text)</code> on OK, <code>cb(nil)</code> 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.</td></tr>
<tr><td>wada.ui.canvas(w, h)</td><td>A drawing surface. See the canvas methods below.</td></tr>
<tr><td>wada.ui.canvas(w, h)</td><td>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 <code>on_open(w, h)</code> is safe on every board.</td></tr>
<tr><td>wada.ui.chart(points)</td><td>A line chart sized to the page. The Airtime and RF Monitor primitive.</td></tr>
<tr><td>wada.ui.scroll(on)</td><td>Allow the page to scroll when content is taller than the screen.</td></tr>
<tr><td>wada.ui.text_h(size)</td><td>Line height in pixels for a size class. Use it to lay out a canvas.</td></tr>
<tr><td>wada.ui.colors</td><td>Theme table: <code>text</code>, <code>sub</code>, <code>bg</code>, <code>panel</code>, <code>accent</code>, <code>good</code>, <code>bad</code>, and on newer firmware <code>warn</code> (amber) and <code>border</code> (a panel's frame). Write <code>colors.warn or 0xE8A33D</code> to run on both.</td></tr>
</table>
<h3>Label handle</h3>
<p>A button returns the same kind of handle, so all of these work on buttons too; <code>color</code> sets a button's text colour.</p>
<table class="api">
<tr><td>lbl:set(text)</td><td>Replace the text.</td></tr>
<tr><td>lbl:color(c)</td><td>Set the colour, e.g. <code>wada.ui.colors.accent</code>.</td></tr>
<tr><td>lbl:pos(x, y)</td><td>Place it explicitly instead of in flow.</td></tr>
<tr><td>lbl:bg(fill [, border])</td><td>Fill colour behind it: on a button handle, the button's own colour (and its frame when <code>border</code> is given), so a coloured button stays a focus stop for the keys and trackball. New in beta_96: write <code>if b.bg then b:bg(c) end</code> to run on older firmware too.</td></tr>
<tr><td>lbl:width(px, [align])</td><td>Fix the width; longer text wraps instead of running off the panel. Optional <code>"center"</code> / <code>"right"</code> aligns the text inside that width (default left).</td></tr>
</table>
<h3>Canvas handle</h3>
+29 -2
View File
@@ -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);
}
+4 -2
View File
@@ -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;