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.colors | Theme 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;