Commit Graph

18 Commits

Author SHA1 Message Date
evan 7bc9afae33 refactor(lua)!: migrate to the per-feature namespaces
Follows lib/esp32-lua-api: gui -> screen, node -> tree, settings and input
split into screen/sys/touch/buttons. Settings is one provider lighter, with
timezone on Sys and rotation and theme on Gui, which now applies and persists a
rotation in one call. The calibration screen stashes the rotation it borrows
rather than relying on a transient setter.
2026-08-05 10:26:43 -04:00
evan 1aa3a59ca9 feat: give /.lua/main.lua the whole screen and the app contract
The firmware knew where apps, data and modules lived, called four globals,
and painted a status bar into a strip it clipped every app out of. None of
that was its business, and the viewport made the bar something an app could
neither compose with nor replace.

It now loads one file. main.lua mounts the route inside its own node tree,
so the bar is a sibling of the app rather than chrome painted over it: one
layout, one hit test, no inset arithmetic and no invalidation flags on the
C++ side. Apps and main.lua are tables -- they share a lua_State, so globals
would collide -- and a screen changes by rebuilding from node().
2026-08-04 21:15:21 -04:00
evan 9e58e72bf6 build: add a format target and one shared editor config
clang-format had no config here, so adopting the submodule's puts both repos
on the same pointer alignment. .editorconfig is what lua-language-server reads
on save, which is why the Lua tree had drifted between tabs and two widths.
2026-08-04 13:20:08 -04:00
evan fce0dfb70a feat(ble): add a NimBLE provider and fold its controls into Settings
Bluedroid cannot initialize beside the Lua runtime on this heap, so the
provider is NimBLE-Arduino. Scans keep the six strongest devices in a fixed
buffer rather than growing one. The standalone BLE app is gone: it was a
screen of toggles that belongs next to the other device settings.
2026-08-04 13:11:17 -04:00
evan 7510bb38a5 feat(sdcard)!: move the card to /.lua and the shared Lua API
Apps live under /.lua/apps with data in /.lua/data, matching the runtime's
paths. ui.lua and hints.lua ship from the submodule through `make sdcard`
rather than a copy that drifts, so theme.lua and the firmware's own ui.lua
are gone.

App-visible changes: text takes a font role rather than a text size, the
theme palette names roles (color/background) instead of fg/bg, ticks are
timer.every, on_press is on_click, and centring is layout alignment now
that textAlign left the node contract.
2026-08-03 18:32:58 -04:00
evan f7c5cc09ba feat(ui)!: move the widget tree into C++
A node was a Lua table of ~625 bytes, of which 21 keys pushed it over a
power-of-two hash boundary and eight were style copies inheritance had
splattered down from its parent. A 400 node screen cost ~250 KB and could not
coexist with wifi's buffers.

The tree now lives in src/ui/layout.h as a 16 byte struct in a flat arena, and
splits by lifetime: Node holds what hit testing and repainting need forever,
Spec holds what only measure/place read and is dropped when layout ends. Style
is sparse and resolved by walking parents, so a node naming no colours costs
nothing. Re-layout rebuilds from Lua rather than retaining the inputs.

    401 nodes:  8218 B steady, 21050 B peak
           Lua: ~250000 B steady

sdcard/lib/ui.lua stays the toolkit and keeps every constructor signature, but
returns integer handles: 627 lines to 374. Composition, the palette and custom
painters are still Lua on the SD card; only primitives now need a reflash.

BREAKING CHANGE: ui constructors return handles, not tables. Use
ui.setText(id, text) and keep per-node app data in a table keyed by id.
2026-08-02 18:48:48 -04:00
evan 6b6280b72a feat(ui): add reusable on-screen keyboard 2026-08-02 16:07:10 -04:00
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 01dfc4b458 refactor: capitalize app directory names
The directory name is what the status bar and the launcher cards display, so Home,
Hello and Settings read as titles without a lookup table. Updates the firmware's home
path, the launcher's self-exclusion and the host tests that load the settings app.

Also brings the bar's next repaint forward when an app renames itself or the frame
rotates. The bar still decides what changed; this only stops the answer waiting most of
a second for the next tick.
2026-08-02 13:20:19 -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 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 fea0e9c291 fix(settings): reduce keyboard memory usage 2026-08-01 21:43:17 -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 2cdb7b3702 feat: timezone setting and main-axis justify in the ui toolkit
The launcher clock read UTC and sat next to the title because the toolkit could only
stack children from the start of an axis. justify adds the CSS main-axis modes that
had a caller -- start, end, center, between -- so a header keeps its title left and
its clock right without any app doing arithmetic.

Timezones are stored as POSIX TZ rules rather than offsets, so newlib applies DST
changeovers and os.date() in Lua reports local time with no binding of its own.
/lib/timezones.lua is only the picker list: a zone missing from it still works if its
rule is written into settings, the same split themes already use.

settings_calibration.lua now finds rows by label. Adding the timezone row shifted
every hardcoded y coordinate in it, which is the failure that had been predicted and
would have silently retargeted taps at the wrong control.
2026-08-01 17:03:55 -04:00
evan 393ce3c0ed refactor: split the Lua runtime, share test stubs, add a Makefile
lua_app.cpp had grown to 701 lines holding every binding, the module loader and the
app lifecycle, so new bindings landed wherever the cursor was. Each Lua table now has
its own file under bindings/, and the app is recovered from the lua_State's extra
space instead of a file-static, so a second state cannot reach the wrong app.

Three tests each redeclared the binding surface, which broke twice this session when
a binding changed; test/fake_device.lua is now the single stub. `make test` runs all
four suites and pins Lua 5.4, matching the vendored interpreter rather than the 5.2
the tests had silently been using.
2026-08-01 11:17:42 -04:00
evan eec9b692c6 feat(wifi): scan, connect and persist networks from the settings app
Adds a wifi binding over the Arduino API and a settings flow that scans, picks
the strongest AP per SSID, takes a password from an on-screen keyboard, and
reports connection state. Credentials join /settings.lua and reconnect at boot.

Settings are now written through a temp file and rename, and strings are
Lua-escaped, so a password cannot corrupt the file the firmware parses at boot.
2026-08-01 06:29:38 -04:00
evan 4e5796fd51 feat(ui): layout toolkit, Lua launcher and touch press events
Apps describe nesting instead of coordinates: /lib/ui.lua borrows CSS block flow,
the box model and auto sizing, and owns hit testing, press capture and the pressed
repaint. The runtime gains require backed by the SD card, on_touch_down/on_touch_up,
text metrics and rounded gradient fills, so the launcher becomes an ordinary Lua app
and the firmware keeps only a fallback screen for an unreadable card.
2026-07-31 22:27:34 -04:00
evan ad396d3f01 feat: Lua app firmware for the E32R40T display module
Boots to a launcher that lists /apps/<name>/main.lua on the SD card and runs
the selected app in a vendored Lua 5.4 with gui, input, fs, sys and log
bindings. Settings persist as a Lua table in /settings.lua, covering touch
calibration and screen rotation, with a settings app to edit both. Rotation is
applied after mapping raw touch into the panel's rotation-0 frame, so turning
the UI never invalidates a calibration.
2026-07-31 21:28:11 -04:00