Commit Graph

10 Commits

Author SHA1 Message Date
evan 1cb52997a9 feat(gui): render user-selectable TTF fonts
Embed a Latin-1 Meslo LG S default and use OpenFontRender for anti-aliased text with an explicit pixel size on each draw and measurement call. Apps may replace the one retained face with an SD-backed TTF and restore Meslo with nil. Refresh UI sizing, status telemetry, Settings typography, tests, documentation, and font license notices.
2026-08-02 14:54:49 -04:00
evan 0ca1fd9842 feat(lua): add app navigation history
Make sys.launch push the current path and argument, sys.replace switch without pushing, and sys.back restore the previous route. The status-bar chevron uses the same back action, failed child apps return to their caller, and history is capped at eight routes. Remove the redundant Settings back card.
2026-08-02 13:30:12 -04:00
evan b5146b8642 refactor(lua): give the status bar its own dirty tracking and split settings out of sys
The bar repainted itself whole every second. It now compares each field against what
it last painted, adds seconds and a memory percentage, and keys the cache on
gui.getRotation() and ui.themeName so rotation and theme changes still repaint it.
Invalidation lives entirely in Lua; the firmware's push flag and gfx/statusbar.h are gone.

Bindings follow getName/setName/isName, persisted preferences move from sys to a settings
table, and gui.setRotation takes degrees like settings does. A bar that dies mid-run now
keeps its rows reserved rather than silently resizing the running app.
2026-08-02 10:49:01 -04:00
evan b9b620ad2b feat(ui): add a home button to the status bar, rename launcher to home
Leaving an app was the app's own responsibility, so one that shipped without
an exit could only be escaped with a reset. The bar now paints a back button
into its leading square and the firmware treats that rect as home, acting on
release so a press sliding into the app cancels. The app it returns to is
/apps/home, which is what it is to the user.

Card grids in home and settings centre left to right as a unit.
2026-08-01 23:13:20 -04:00
evan 114c8c5f72 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.
2026-08-01 22:50:19 -04:00
evan 7925eace57 chore: rename the project to slate32
esp32-lcd named the substrate rather than the thing, and both halves would age: the
chip is swappable and the panel technology is incidental. What the project actually is
is a Lua app platform that happens to run on a cheap touchscreen, so the name is now a
model number rather than a parts list. slate32 is also unclaimed, where slate alone
collides with several well known projects.

Board identifiers stay as they were -- the e32r40t QEMU machine, the esp32-32e build
env and the test skill name all refer to real hardware, which did not get renamed.
2026-08-01 18:58:16 -04:00
evan f36292734a docs: wifi.status() and the tick interval are no longer deviations
Both firmwares now return {state, ssid, ip, rssi} from wifi.status() and set the tick
interval through app.setTickInterval(), so the drift list is down to the filesystem
gaps and timers.
2026-08-01 18:02:32 -04:00
evan 8045faddb4 refactor: replace the TICK_MS global with app.setTickInterval()
A magic global that the runtime reads once at startup could not be changed later, gave
no feedback when misspelled, and was a second spelling of a mechanism the sibling
firmware already had. app.setTickInterval(ms) clamps to 33..3600000, takes 0 to stop,
and errors when on_tick() is not defined -- by the time init() runs the chunk body has
finished, so a missing callback is a typo rather than a race.

Also drops the code comments pointing at the other repo. Where the two APIs agree or
differ belongs in docs/lua-api-parity.md; a comment beside a constant explaining that
another firmware picked the same number is noise a reader here cannot act on.
2026-08-01 17:58:11 -04:00
evan d338dfe3e8 refactor: rename the app entry point to init() and require it
crosspoint-reader calls it init() and requires it; this called it setup() and treated
it as optional. Same concept, two spellings, so an app could not move between the two
firmwares for no reason worth defending. init() wins because it is also the stricter
contract: a misspelled entry point is now an error instead of an app that starts,
draws nothing, and explains nothing.

Requiring it exposed that error screens were unreadable. fail() painted the message
and the host relaunched the launcher over it on the very next frame, so every Lua
error was serial-only -- which would have made "Missing init()" useless to anyone
holding the device rather than a console.
2026-08-01 17:52:32 -04:00
evan 6f2d618b8d feat: http bindings matching crosspoint-reader, plus generated Lua stubs
The http table copies crosspoint-reader's signatures exactly -- get/head/delete/post/
patch returning (body|nil, status), download taking maxBytes/expectedSize/sha256, the
same 50000 byte body cap and the same -1 for a request that never left the device --
so a script that talks to a server runs on either firmware. docs/lua-api-parity.md
records that, and every other place the two APIs agree, differ for a reason, or differ
because nobody noticed.

Two crosspoint behaviours are deliberately not copied. It reinterprets a string in
argument 2 of a GET as a request body, which turns a mistyped headers table into a
silent protocol error. More seriously it calls setInsecure() on every request, so TLS
is encrypted but unauthenticated on the very path a firmware update would use; this
verifies against the root bundle already sitting in the framework, and the emulator
confirms expired.badssl.com is refused while a wrong sha256 deletes the file.

Downloading exposed two failures worth naming. A 2KB read buffer on the stack tripped
the loop task's canary because a TLS handshake had already spent it, and the
hand-rolled read loop spun forever on a stream that stopped producing -- HTTPClient's
own writeToStream handles both, so the loop is gone and the loop task gets 16KB.

scripts/gen_lua_stubs.py generates stubs/esp32lcd.lua in the same LuaLS format
crosspoint uses, reading annotations off the luaL_Reg tables so a module's docs sit
with its registration. make test runs --check, which crosspoint's copy never wired up.
2026-08-01 17:33:47 -04:00