refactor(api)!: one namespace per feature, screen split out
Namespaces were shared across features: `settings` was written by core, the
panel and touch, and `input` by touch and buttons. That made "does this
firmware implement the whole feature?" a question no pointer could answer.
Each namespace now belongs to exactly one feature or to core, so a feature is
a provider pointer and the compiler validates completeness:
gui, node -> screen, tree, under the screen feature
settings -> screen (rotation, theme), sys (timezone),
touch (calibration)
input -> touch, buttons
Runtime::open() no longer requires a GuiProvider; a firmware without one runs
with no screen/tree globals and reports sys.hasFeature("screen") false.
Rotation is one value again: GuiProvider::setRotation applies and persists, so
an app rotating the panel transiently puts the old value back itself.
This commit is contained in:
@@ -37,13 +37,17 @@ int main() {
|
||||
assert(bench.log.level == esp32lua::LogLevel::Info);
|
||||
assert(bench.log.message == "shared runtime");
|
||||
|
||||
run(state, "assert(settings.getRotation() == 0)\n"
|
||||
"assert(settings.setRotation(90))\n"
|
||||
"assert(settings.getTimezone() == 'UTC0')\n"
|
||||
"assert(settings.setTimezone('EST5EDT'))");
|
||||
assert(bench.settings.degrees == 90);
|
||||
assert(bench.settings.tz == "EST5EDT");
|
||||
expectError(state, "settings.setRotation(45)");
|
||||
run(state, "assert(sys.hasFeature('screen'))\n"
|
||||
"assert(screen.getRotation() == 0)\n"
|
||||
"assert(screen.setRotation(90))\n"
|
||||
"assert(screen.getTheme() == 'light')\n"
|
||||
"assert(screen.setTheme('dark'))\n"
|
||||
"assert(sys.getTimezone() == 'UTC0')\n"
|
||||
"assert(sys.setTimezone('EST5EDT'))");
|
||||
assert(bench.gui.degrees == 90);
|
||||
assert(bench.gui.themeName == "dark");
|
||||
assert(bench.sys.tz == "EST5EDT");
|
||||
expectError(state, "screen.setRotation(45)");
|
||||
|
||||
run(state, "assert(sys.getAPIVersion() == 1)\n"
|
||||
"assert(sys.hasFeature('touch') and sys.hasFeature('buttons'))\n"
|
||||
@@ -66,17 +70,17 @@ int main() {
|
||||
assert(bench.fs.written.size() == 3);
|
||||
expectError(state, "fs.readFile('/notes.txt', 999999)");
|
||||
|
||||
run(state, "assert(gui.getWidth() == 320 and gui.getHeight() == 240)\n"
|
||||
"assert(gui.FONT_UI == 2 and gui.STYLE_BOLD == 1)\n"
|
||||
"assert(gui.color(255, 0, 0) == 0xFF0000)\n"
|
||||
"gui.setRotation(180)\n"
|
||||
"gui.clear()\n"
|
||||
"gui.fillPolygon({1, 2, 3}, {4, 5, 6}, 0)\n"
|
||||
"gui.drawText(gui.FONT_UI, 0, 0, 'hi')");
|
||||
run(state, "assert(screen.getWidth() == 320 and screen.getHeight() == 240)\n"
|
||||
"assert(screen.FONT_UI == 2 and screen.STYLE_BOLD == 1)\n"
|
||||
"assert(screen.color(255, 0, 0) == 0xFF0000)\n"
|
||||
"assert(screen.setRotation(180))\n"
|
||||
"screen.clear()\n"
|
||||
"screen.fillPolygon({1, 2, 3}, {4, 5, 6}, 0)\n"
|
||||
"screen.drawText(screen.FONT_UI, 0, 0, 'hi')");
|
||||
assert(bench.gui.degrees == 180);
|
||||
assert(bench.gui.trace == "clear;fillPolygon3;drawText(hi);");
|
||||
expectError(state, "gui.fillPolygon({1, 2}, {3}, 0)");
|
||||
expectError(state, "gui.color(300, 0, 0)");
|
||||
expectError(state, "screen.fillPolygon({1, 2}, {3}, 0)");
|
||||
expectError(state, "screen.color(300, 0, 0)");
|
||||
|
||||
run(state, "local response = http.get('https://example.test', {maxBytes = "
|
||||
"16, headers = {Accept = 'text/plain'}})\n"
|
||||
@@ -120,60 +124,60 @@ int main() {
|
||||
expectError(state, "timer.after(0, function() end)");
|
||||
|
||||
run(state,
|
||||
"assert(settings.setCalibration(100, 200, 300, 400))\n"
|
||||
"local x, y = input.getTouch()\n"
|
||||
"assert(touch.setCalibration(100, 200, 300, 400))\n"
|
||||
"local x, y = touch.getPoint()\n"
|
||||
"assert(x == 10 and y == 20)\n"
|
||||
"assert(input.isTouched())\n"
|
||||
"assert(input.isPressed('confirm') and not input.isPressed('back'))\n"
|
||||
"assert(#input.getButtons() == 4 and input.getButtons()[3] == "
|
||||
"assert(touch.isTouched())\n"
|
||||
"assert(buttons.isPressed('confirm') and not buttons.isPressed('back'))\n"
|
||||
"assert(#buttons.getAll() == 4 and buttons.getAll()[3] == "
|
||||
"'confirm')");
|
||||
assert(bench.touch.calibration[3] == 400);
|
||||
|
||||
// Gradients are core: an e-ink provider flattens what it cannot show.
|
||||
run(state, "gui.roundRect(0, 0, 10, 10, 4, 0, 0xFF, 0x00)\n"
|
||||
"gui.roundRect(0, 0, 10, 10, 4, 0, nil, nil, 0xFF)");
|
||||
run(state, "screen.roundRect(0, 0, 10, 10, 4, 0, 0xFF, 0x00)\n"
|
||||
"screen.roundRect(0, 0, 10, 10, 4, 0, nil, nil, 0xFF)");
|
||||
assert(bench.gui.gradient);
|
||||
assert(bench.gui.trace.find("roundRect(-,border);") != std::string::npos);
|
||||
|
||||
bench.gui.trace.clear();
|
||||
run(state,
|
||||
"node.reset()\n"
|
||||
"local root = node.create(nil, {type = 'box', w = 'fill', h = 'fill', "
|
||||
"tree.reset()\n"
|
||||
"local root = tree.create(nil, {type = 'box', w = 'fill', h = 'fill', "
|
||||
"pad = 4, gap = 2})\n"
|
||||
"local label = node.create(root, {type = 'text', label = 'Hello'})\n"
|
||||
"local button = node.create(root, {type = 'button', h = 40, interactive "
|
||||
"local label = tree.create(root, {type = 'text', label = 'Hello'})\n"
|
||||
"local button = tree.create(root, {type = 'button', h = 40, interactive "
|
||||
"= true})\n"
|
||||
"node.setStyle(root, {background = 0xFFFFFF, fill = 0xFFFFFF, color = 0, "
|
||||
"tree.setStyle(root, {background = 0xFFFFFF, fill = 0xFFFFFF, color = 0, "
|
||||
"face = 0xEEEEEE,\n"
|
||||
" border = 0x333333, focusColor = 0xFF0000})\n"
|
||||
"assert(node.layout(root, 0, 0, 320, 240))\n"
|
||||
"local x, y, w, h = node.getRect(label)\n"
|
||||
"assert(tree.layout(root, 0, 0, 320, 240))\n"
|
||||
"local x, y, w, h = tree.getRect(label)\n"
|
||||
"assert(x == 4 and y == 4 and w == 312 and h == 16)\n"
|
||||
"assert(node.getLabel(label) == 'Hello')\n"
|
||||
"assert(node.hit(root, 10, 40) == button)\n"
|
||||
"assert(node.hit(root, 10, 200) == nil)\n"
|
||||
"assert(node.focusFirst(root) == button)\n"
|
||||
"assert(node.getFocus() == button)\n"
|
||||
"assert(node.moveFocus(root, 'up') == button)\n"
|
||||
"node.setPressed(button, true)\n"
|
||||
"assert(node.isPressed(button))\n"
|
||||
"assert(node.getCount() == 3 and node.getFootprint() > 0)\n"
|
||||
"node.draw(root)\n"
|
||||
"node.dropScratch()");
|
||||
"assert(tree.getLabel(label) == 'Hello')\n"
|
||||
"assert(tree.hit(root, 10, 40) == button)\n"
|
||||
"assert(tree.hit(root, 10, 200) == nil)\n"
|
||||
"assert(tree.focusFirst(root) == button)\n"
|
||||
"assert(tree.getFocus() == button)\n"
|
||||
"assert(tree.moveFocus(root, 'up') == button)\n"
|
||||
"tree.setPressed(button, true)\n"
|
||||
"assert(tree.isPressed(button))\n"
|
||||
"assert(tree.getCount() == 3 and tree.getFootprint() > 0)\n"
|
||||
"tree.draw(root)\n"
|
||||
"tree.dropScratch()");
|
||||
assert(bench.gui.trace.find("drawText(Hello);") != std::string::npos);
|
||||
// The bordered box rounds its corners; the button fills without one.
|
||||
assert(bench.gui.trace.find("roundRect(fill,border);") != std::string::npos);
|
||||
assert(bench.gui.trace.find("roundRect(fill,-);") != std::string::npos);
|
||||
expectError(state, "node.create(nil, {type = 'nope'})");
|
||||
expectError(state, "tree.create(nil, {type = 'nope'})");
|
||||
|
||||
run(state,
|
||||
"node.reset()\n"
|
||||
"local root = node.create(nil, {type = 'custom', w = 'fill', h = "
|
||||
"tree.reset()\n"
|
||||
"local root = tree.create(nil, {type = 'custom', w = 'fill', h = "
|
||||
"'fill'})\n"
|
||||
"painted = 0\n"
|
||||
"node.setPainter(function(id, x, y, w, h) painted = painted + w end)\n"
|
||||
"assert(node.layout(root, 0, 0, 320, 240))\n"
|
||||
"node.draw(root)\n"
|
||||
"tree.setPainter(function(id, x, y, w, h) painted = painted + w end)\n"
|
||||
"assert(tree.layout(root, 0, 0, 320, 240))\n"
|
||||
"tree.draw(root)\n"
|
||||
"assert(painted == 320)");
|
||||
|
||||
// A timer firing inside draw is still one batch.
|
||||
@@ -245,8 +249,22 @@ int main() {
|
||||
assert(noTouch.open());
|
||||
noTouch.callTouch(esp32lua::TouchPhase::Down, 1, 1);
|
||||
assert(headless.log.message == "callTouch without a touch provider");
|
||||
run(noTouch.state(),
|
||||
"assert(input.getTouch == nil and input.isPressed ~= nil)");
|
||||
run(noTouch.state(), "assert(touch == nil and buttons.isPressed ~= nil)");
|
||||
}
|
||||
|
||||
// A screenless firmware still runs: no gui provider, no screen/tree
|
||||
// namespaces, but a callback batch still completes.
|
||||
{
|
||||
fake::Bench screenless;
|
||||
esp32lua::Providers providers = screenless.providers();
|
||||
providers.gui = nullptr;
|
||||
esp32lua::Runtime noScreen(providers);
|
||||
assert(noScreen.open());
|
||||
assert(!noScreen.hasFeature("screen"));
|
||||
run(noScreen.state(), "assert(not sys.hasFeature('screen'))\n"
|
||||
"assert(screen == nil and tree == nil)\n"
|
||||
"assert(sys.getTimezone() == 'UTC0')");
|
||||
noScreen.callDraw(0);
|
||||
}
|
||||
|
||||
// App loading: a fresh state per app, main.lua deciding where apps and
|
||||
|
||||
Reference in New Issue
Block a user