refactor(api)!: declare callbacks as classes an app composes

The runtime has always called fields on the table main.lua returns, but
@lua-global declared them as loose functions, so the stubs type-checked
something that does not exist and read as "define a global".

Callbacks are now @lua-app blocks that generate a class: App for the core
contract, TouchHandlers and ButtonHandlers beside the namespaces they belong
to. An app composes what it implements:

  ---@class PaintApp : App, TouchHandlers

Names follow the rest of the surface: onTouchDown rather than on_touch_down,
with the field names the runtime looks up renamed to match. @lua-field carries
the plain fields (home, data) that were prose in a preamble before.
This commit is contained in:
2026-08-05 10:42:01 -04:00
parent 75b3a2c490
commit b9f7c9347c
11 changed files with 153 additions and 87 deletions
+9 -5
View File
@@ -72,13 +72,17 @@ void registerButtons(lua_State* state) {
} // namespace bindings
} // namespace esp32lua
// @lua-global
// @lua-app ButtonHandlers
// @lua-preamble -- What an app implements to see buttons, composed into its own
// class:
// @lua-preamble --
// @lua-preamble -- ---@class MenuApp : App, ButtonHandlers
// ---Fired when a button goes down.
// @param button Button
// @lua-fn on_button_down
// @lua-fn onButtonDown?
// ---Fired when a button comes up.
// @param button Button
// @lua-fn on_button_up
// ---Tap alias, fired on release like a click, after on_button_up.
// @lua-fn onButtonUp?
// ---Tap alias, fired on release like a click, after onButtonUp.
// @param button Button
// @lua-fn on_button
// @lua-fn onButton?
+10 -6
View File
@@ -76,21 +76,25 @@ void registerTouch(lua_State* state) {
} // namespace bindings
} // namespace esp32lua
// @lua-global
// @lua-app TouchHandlers
// @lua-preamble -- What an app implements to see raw touch, composed into its
// own class:
// @lua-preamble --
// @lua-preamble -- ---@class PaintApp : App, TouchHandlers
// ---Fired when the finger lands.
// @param x integer
// @param y integer
// @lua-fn on_touch_down
// @lua-fn onTouchDown?
// ---Fired when the finger moves while down, after the firmware's jitter
// filter.
// @param x integer
// @param y integer
// @lua-fn on_touch_move
// @lua-fn onTouchMove?
// ---Fired when the finger lifts.
// @param x integer
// @param y integer
// @lua-fn on_touch_up
// ---Tap alias, fired on release like a click, after on_touch_up.
// @lua-fn onTouchUp?
// ---Tap alias, fired on release like a click, after onTouchUp.
// @param x integer
// @param y integer
// @lua-fn on_touch
// @lua-fn onTouch?
+16 -14
View File
@@ -150,17 +150,16 @@ bool Runtime::finishCall(const char* name, int argc) {
return false;
}
// @lua-global core/runtime
// @lua-app App core/runtime
// @lua-preamble -- The firmware loads /.lua/main.lua into every fresh state and
// calls these on the
// @lua-preamble -- table it returns. Where apps live, what surrounds them and
// which of these an app
// @lua-preamble -- itself sees are all main.lua's to decide.
// @lua-preamble -- itself sees are all main.lua's to decide, which is why an
// app composes the
// @lua-preamble -- classes for the features it handles:
// @lua-preamble --
// @lua-preamble -- Fields the firmware reads: home, the route sys.back() lands
// on once history is
// @lua-preamble -- empty, and data, the sys.getAppDataPath() template whose ?
// is the app id.
// @lua-preamble -- ---@class PaintApp : App, TouchHandlers
// @lua-preamble --
// @lua-preamble -- The firmware does not clear the frame before calling draw(),
// and commits changed
@@ -168,6 +167,9 @@ bool Runtime::finishCall(const char* name, int argc) {
// own refresh policy.
// @lua-preamble -- Timer callbacks are registered directly with
// timer.after/every.
// @lua-field home? string The route sys.back() lands on once history is empty.
// @lua-field data? string The sys.getAppDataPath() template whose ? is the app
// id.
// ---Required. Mounts the route; failing here leaves no app running.
// @param route string The app path sys.launch, sys.back or the boot recorded.
@@ -188,7 +190,7 @@ bool Runtime::callStart(const std::string& route, const std::string& arg) {
// effort.
// @param deltaMs integer Monotonic milliseconds since the previous draw; zero
// on the first.
// @lua-fn draw
// @lua-fn draw?
void Runtime::callDraw(int32_t deltaMs) {
const Batch batch(*this);
if (!beginCall("draw"))
@@ -206,16 +208,16 @@ void Runtime::callTouch(TouchPhase phase, int32_t x, int32_t y) {
const Batch batch(*this);
const char* name =
phase == TouchPhase::Down
? "on_touch_down"
: (phase == TouchPhase::Move ? "on_touch_move" : "on_touch_up");
? "onTouchDown"
: (phase == TouchPhase::Move ? "onTouchMove" : "onTouchUp");
for (int pass = 0; pass < 2; pass++) {
// The tap alias is ordering, not policy: a release always fires on_touch_up
// and then on_touch, so both firmwares agree without either of them
// The tap alias is ordering, not policy: a release always fires onTouchUp
// and then onTouch, so both firmwares agree without either of them
// deciding anything.
if (pass == 1) {
if (phase != TouchPhase::Up)
return;
name = "on_touch";
name = "onTouch";
}
if (!beginCall(name))
continue;
@@ -232,12 +234,12 @@ void Runtime::callButton(const std::string& button, bool pressed) {
return;
}
const Batch batch(*this);
const char* name = pressed ? "on_button_down" : "on_button_up";
const char* name = pressed ? "onButtonDown" : "onButtonUp";
for (int pass = 0; pass < 2; pass++) {
if (pass == 1) {
if (pressed)
return;
name = "on_button";
name = "onButton";
}
if (!beginCall(name))
continue;