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:
2026-08-05 10:26:38 -04:00
parent 445a9b2b8f
commit 75b3a2c490
26 changed files with 556 additions and 626 deletions
+14 -17
View File
@@ -18,7 +18,9 @@ fs = {
}
local savedTheme = "light"
settings = {
local frameWidth, frameHeight = 320, 480
screen = {
getTheme = function()
return savedTheme
end,
@@ -26,11 +28,6 @@ settings = {
savedTheme = name
return true
end,
}
local frameWidth, frameHeight = 320, 480
gui = {
FONT_SMALL = 0,
FONT_UI = 1,
FONT_BODY = 2,
@@ -70,7 +67,7 @@ local function interactiveNodes()
return result
end
node = {
tree = {
reset = function()
nodes, focus, buttonCount = {}, nil, 0
end,
@@ -204,13 +201,13 @@ ui.mount(function()
end)
assert(ui.down(10, 10))
assert(node.isPressed(first))
assert(tree.isPressed(first))
assert(ui.move(95, 10))
assert(not node.isPressed(first))
assert(not tree.isPressed(first))
assert(ui.move(10, 10))
assert(node.isPressed(first))
assert(tree.isPressed(first))
assert(ui.up(10, 10))
assert(not node.isPressed(first))
assert(not tree.isPressed(first))
local expectedTouch = { "enter", "exit", "enter", "exit", "click" }
for index, name in ipairs(expectedTouch) do
@@ -223,13 +220,13 @@ end
events = {}
assert(ui.buttonPress("right", true))
assert(ui.buttonPress("right", false))
assert(node.getFocus() == first)
assert(tree.getFocus() == first)
assert(ui.buttonPress("right", true))
assert(node.getFocus() == second)
assert(tree.getFocus() == second)
assert(ui.buttonPress("confirm", true))
assert(node.isPressed(second))
assert(tree.isPressed(second))
assert(ui.buttonPress("confirm", false))
assert(not node.isPressed(second))
assert(not tree.isPressed(second))
assert(ui.buttonPress("back", true) == false)
local expectedButtons = {
@@ -271,7 +268,7 @@ frameWidth, frameHeight = 320, 480
-- still is. Which node was hit is geometry, so the test names the target directly.
ui.reset()
local pressedCalls = {}
node.setPressed = function(_, on)
tree.setPressed = function(_, on)
pressedCalls[#pressedCalls + 1] = on
end
local own, styled
@@ -281,7 +278,7 @@ ui.mount(function()
return ui.box { own, styled }
end)
local target
node.hit = function()
tree.hit = function()
return target
end