feat(ui): add persistent status bar and card grid launcher

The bar is host-owned chrome: the firmware clips apps into a viewport below
it, so no app can paint over it, while /lib/statusbar.lua owns the height,
repaint interval and painting. gui.fullscreen() lets touch calibration take
the physical panel back.

Launcher and settings become grids of square cards (3 across landscape, 2
portrait) via the new gui.setTextSize, ui.label and ui.cardSide. The one
function on the `app` table moves to sys.setTickInterval, alongside the new
sys.appName the bar needs.
This commit is contained in:
2026-08-01 22:50:19 -04:00
parent f5e44885db
commit 114c8c5f72
17 changed files with 356 additions and 96 deletions
+4
View File
@@ -4,6 +4,7 @@
#include <WiFi.h>
#include <XPT2046_Touchscreen.h>
#include "gfx/statusbar.h"
#include "lua/lua_app.h"
#include "net.h"
#include "settings.h"
@@ -35,6 +36,7 @@ String nextApp;
// Last resort only: the UI lives in Lua, so this exists purely to explain why
// nothing else could run.
void fallbackScreen(const char* message) {
tft.resetViewport(); // no app, no status bar: this message owns the panel
tft.setRotation(0);
tft.fillScreen(TFT_WHITE);
tft.setTextColor(TFT_RED, TFT_WHITE);
@@ -46,7 +48,9 @@ void fallbackScreen(const char* message) {
}
void startApp(const String& path) {
// Full panel until the app's own status bar module reports its height in load().
tft.setRotation(settings.rotationIndex()); // apps may have rotated the frame
statusbar::apply(tft, 0);
Serial.printf("launching %s\n", path.c_str());
if (app.load(path.c_str())) return;
if (path == LAUNCHER) fallbackScreen("launcher failed to start");