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:
+11
-11
@@ -8,8 +8,8 @@ local ORDER = { "back", "left", "up", "down", "right", "confirm" }
|
||||
|
||||
local function available()
|
||||
local roles = {}
|
||||
if input and input.getButtons then
|
||||
for _, role in ipairs(input.getButtons()) do
|
||||
if buttons and buttons.getAll then
|
||||
for _, role in ipairs(buttons.getAll()) do
|
||||
roles[role] = true
|
||||
end
|
||||
end
|
||||
@@ -21,11 +21,11 @@ end
|
||||
---@param options table|nil `y`, `font`, `color`, and `background` overrides.
|
||||
function hints.draw(actions, options)
|
||||
options = options or {}
|
||||
local font = options.font or gui.FONT_SMALL
|
||||
local color = options.color or gui.color(0, 0, 0)
|
||||
local background = options.background or gui.color(255, 255, 255)
|
||||
local height = gui.getFontHeight(font) + 6
|
||||
local y = options.y or (gui.getHeight() - height)
|
||||
local font = options.font or screen.FONT_SMALL
|
||||
local color = options.color or screen.color(0, 0, 0)
|
||||
local background = options.background or screen.color(255, 255, 255)
|
||||
local height = screen.getFontHeight(font) + 6
|
||||
local y = options.y or (screen.getHeight() - height)
|
||||
|
||||
local roles = available()
|
||||
local labels = {}
|
||||
@@ -35,15 +35,15 @@ function hints.draw(actions, options)
|
||||
end
|
||||
end
|
||||
|
||||
gui.fillRect(0, y, gui.getWidth(), height, background)
|
||||
screen.fillRect(0, y, screen.getWidth(), height, background)
|
||||
if #labels == 0 then
|
||||
return height
|
||||
end
|
||||
|
||||
local slot = gui.getWidth() // #labels
|
||||
local slot = screen.getWidth() // #labels
|
||||
for index, label in ipairs(labels) do
|
||||
local left = slot * (index - 1) + (slot - gui.getTextWidth(font, label)) // 2
|
||||
gui.drawText(font, left, y + 3, label, color, gui.STYLE_NORMAL, background)
|
||||
local left = slot * (index - 1) + (slot - screen.getTextWidth(font, label)) // 2
|
||||
screen.drawText(font, left, y + 3, label, color, screen.STYLE_NORMAL, background)
|
||||
end
|
||||
return height
|
||||
end
|
||||
|
||||
+41
-41
@@ -69,7 +69,7 @@ local function mix(a, b, amount)
|
||||
end
|
||||
|
||||
local function color(rgb)
|
||||
return gui.color(rgb[1], rgb[2], rgb[3])
|
||||
return screen.color(rgb[1], rgb[2], rgb[3])
|
||||
end
|
||||
|
||||
local function palette(seed)
|
||||
@@ -115,27 +115,27 @@ function ui.setTheme(name)
|
||||
if not THEMES[name] then
|
||||
return nil, "Unknown theme"
|
||||
end
|
||||
local ok, err = settings.setTheme(name)
|
||||
local ok, err = screen.setTheme(name)
|
||||
if not ok then
|
||||
return nil, err
|
||||
end
|
||||
loadTheme(name)
|
||||
if root then
|
||||
applyPalette(root)
|
||||
gui.clear(ui.theme.background)
|
||||
node.invalidate(root)
|
||||
screen.clear(ui.theme.background)
|
||||
tree.invalidate(root)
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
loadTheme(settings.getTheme())
|
||||
loadTheme(screen.getTheme())
|
||||
|
||||
local function clearState()
|
||||
enterHandlers, exitHandlers, clickHandlers, painters = {}, {}, {}, {}
|
||||
pressStyles = {}
|
||||
end
|
||||
|
||||
node.setPainter(function(id, x, y, w, h)
|
||||
tree.setPainter(function(id, x, y, w, h)
|
||||
local painter = painters[id]
|
||||
if painter then
|
||||
painter(id, x, y, w, h)
|
||||
@@ -166,7 +166,7 @@ local function applyStyle(id, spec)
|
||||
style.background, style.fill, hasStyle = spec.background, spec.background, true
|
||||
end
|
||||
if hasStyle then
|
||||
node.setStyle(id, style)
|
||||
tree.setStyle(id, style)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -186,9 +186,9 @@ local function build(spec, kind)
|
||||
|
||||
spec.type = kind
|
||||
spec.interactive = spec.on_enter ~= nil or spec.on_exit ~= nil or spec.on_click ~= nil
|
||||
local id = node.create(nil, spec)
|
||||
local id = tree.create(nil, spec)
|
||||
for _, child in ipairs(children) do
|
||||
node.attach(id, child)
|
||||
tree.attach(id, child)
|
||||
end
|
||||
|
||||
applyStyle(id, spec)
|
||||
@@ -234,7 +234,7 @@ end
|
||||
---@return integer w
|
||||
---@return integer h
|
||||
function ui.frame()
|
||||
return gui.getWidth(), gui.getHeight() - inset
|
||||
return screen.getWidth(), screen.getHeight() - inset
|
||||
end
|
||||
|
||||
---@param spec UiSpec
|
||||
@@ -259,15 +259,15 @@ end
|
||||
---@return NodeId
|
||||
function ui.label(text, spec)
|
||||
spec = spec or {}
|
||||
local font, style = spec.font or gui.FONT_UI, spec.style or gui.STYLE_NORMAL
|
||||
if spec.fit and gui.getTextWidth(font, text, style) > spec.fit then
|
||||
while #text > 1 and gui.getTextWidth(font, text .. "~", style) > spec.fit do
|
||||
local font, style = spec.font or screen.FONT_UI, spec.style or screen.STYLE_NORMAL
|
||||
if spec.fit and screen.getTextWidth(font, text, style) > spec.fit then
|
||||
while #text > 1 and screen.getTextWidth(font, text .. "~", style) > spec.fit do
|
||||
text = text:sub(1, -2)
|
||||
end
|
||||
text = text .. "~"
|
||||
end
|
||||
spec.w = gui.getTextWidth(font, text, style)
|
||||
spec.h = gui.getFontHeight(font, style)
|
||||
spec.w = screen.getTextWidth(font, text, style)
|
||||
spec.h = screen.getFontHeight(font, style)
|
||||
spec.font, spec.fit = font, nil
|
||||
return ui.text(text, spec)
|
||||
end
|
||||
@@ -282,7 +282,7 @@ function ui.button(spec)
|
||||
spec.label = nil
|
||||
local id = build(spec, "button")
|
||||
if label then
|
||||
node.create(id, { type = "text", label = label, font = font or gui.FONT_UI })
|
||||
tree.create(id, { type = "text", label = label, font = font or screen.FONT_UI })
|
||||
end
|
||||
return id
|
||||
end
|
||||
@@ -296,16 +296,16 @@ end
|
||||
---@param id NodeId
|
||||
---@param text string
|
||||
function ui.setText(id, text)
|
||||
if node.getLabel(id) == text then
|
||||
if tree.getLabel(id) == text then
|
||||
return
|
||||
end
|
||||
node.setLabel(id, text)
|
||||
node.invalidate(id)
|
||||
tree.setLabel(id, text)
|
||||
tree.invalidate(id)
|
||||
end
|
||||
|
||||
---@param id NodeId
|
||||
function ui.invalidate(id)
|
||||
node.invalidate(id)
|
||||
tree.invalidate(id)
|
||||
end
|
||||
|
||||
---@param spec UiConfirmSpec
|
||||
@@ -343,7 +343,7 @@ function ui.confirm(spec)
|
||||
end
|
||||
|
||||
function ui.reset()
|
||||
node.reset()
|
||||
tree.reset()
|
||||
clearState()
|
||||
laidOut = false
|
||||
root, captured, insideCaptured, confirming = nil, nil, nil, nil
|
||||
@@ -352,7 +352,7 @@ end
|
||||
applyPalette = function(root)
|
||||
-- The root is the panel background, not a card: no border, so it takes the fast fillRect
|
||||
-- path rather than the per-pixel roundRect one. Radius stays so cards inherit it.
|
||||
node.setStyle(root, {
|
||||
tree.setStyle(root, {
|
||||
color = ui.theme.color,
|
||||
background = ui.theme.background,
|
||||
face = ui.theme.face,
|
||||
@@ -360,7 +360,7 @@ applyPalette = function(root)
|
||||
pressedColor = ui.theme.pressedColor,
|
||||
focusColor = ui.theme.focusColor,
|
||||
radius = ui.theme.radius,
|
||||
font = gui.FONT_UI,
|
||||
font = screen.FONT_UI,
|
||||
})
|
||||
end
|
||||
|
||||
@@ -376,16 +376,16 @@ end
|
||||
function ui.rebuild()
|
||||
ui.reset()
|
||||
root = builder()
|
||||
node.setSize(root, "fill", "fill")
|
||||
tree.setSize(root, "fill", "fill")
|
||||
applyPalette(root)
|
||||
local ok, err = node.layout(root, 0, 0, gui.getWidth(), gui.getHeight())
|
||||
local ok, err = tree.layout(root, 0, 0, screen.getWidth(), screen.getHeight())
|
||||
if not ok then
|
||||
error(err, 2)
|
||||
end
|
||||
node.dropScratch()
|
||||
tree.dropScratch()
|
||||
laidOut = true
|
||||
gui.clear(ui.theme.background)
|
||||
node.draw(root)
|
||||
screen.clear(ui.theme.background)
|
||||
tree.draw(root)
|
||||
-- A build allocates a spec table per node and drops them all here, and the next thing an
|
||||
-- app does may be the one that needs a contiguous WiFi buffer. Collecting now costs a few
|
||||
-- milliseconds on a screen change nobody can see, and leaves the heap in a known state
|
||||
@@ -395,18 +395,18 @@ end
|
||||
|
||||
function ui.draw()
|
||||
if root then
|
||||
node.draw(root)
|
||||
tree.draw(root)
|
||||
end
|
||||
end
|
||||
|
||||
local function inside(id, x, y)
|
||||
local rx, ry, rw, rh = node.getRect(id)
|
||||
local rx, ry, rw, rh = tree.getRect(id)
|
||||
return x >= rx and x < rx + rw and y >= ry and y < ry + rh
|
||||
end
|
||||
|
||||
local function enter(id, x, y)
|
||||
if pressStyles[id] then
|
||||
node.setPressed(id, true)
|
||||
tree.setPressed(id, true)
|
||||
end
|
||||
local handler = enterHandlers[id]
|
||||
if handler then
|
||||
@@ -416,7 +416,7 @@ end
|
||||
|
||||
local function exit(id, x, y)
|
||||
if pressStyles[id] then
|
||||
node.setPressed(id, false)
|
||||
tree.setPressed(id, false)
|
||||
end
|
||||
local handler = exitHandlers[id]
|
||||
if handler then
|
||||
@@ -428,16 +428,16 @@ end
|
||||
---@param y integer
|
||||
---@return boolean handled
|
||||
function ui.down(x, y)
|
||||
local focused = node.getFocus()
|
||||
local focused = tree.getFocus()
|
||||
if focused then
|
||||
node.setFocus(nil)
|
||||
tree.setFocus(nil)
|
||||
local handler = exitHandlers[focused]
|
||||
if handler then
|
||||
handler(focused)
|
||||
end
|
||||
end
|
||||
|
||||
local target = root and node.hit(root, x, y)
|
||||
local target = root and tree.hit(root, x, y)
|
||||
if not target then
|
||||
return false
|
||||
end
|
||||
@@ -489,7 +489,7 @@ end
|
||||
local DIRECTIONS = { up = true, down = true, left = true, right = true }
|
||||
|
||||
local function focusFirst()
|
||||
local focused = node.focusFirst(root)
|
||||
local focused = tree.focusFirst(root)
|
||||
if focused then
|
||||
local handler = enterHandlers[focused]
|
||||
if handler then
|
||||
@@ -511,12 +511,12 @@ function ui.buttonPress(name, pressed)
|
||||
if not pressed then
|
||||
return true
|
||||
end
|
||||
local previous = node.getFocus()
|
||||
local previous = tree.getFocus()
|
||||
if not previous then
|
||||
focusFirst()
|
||||
return true
|
||||
end
|
||||
local focused = node.moveFocus(root, name)
|
||||
local focused = tree.moveFocus(root, name)
|
||||
if focused ~= previous then
|
||||
local leave = exitHandlers[previous]
|
||||
if leave then
|
||||
@@ -533,18 +533,18 @@ function ui.buttonPress(name, pressed)
|
||||
if name ~= "confirm" then
|
||||
return false
|
||||
end
|
||||
local focused = node.getFocus() or focusFirst()
|
||||
local focused = tree.getFocus() or focusFirst()
|
||||
if not focused then
|
||||
return false
|
||||
end
|
||||
if pressed then
|
||||
node.setPressed(focused, true)
|
||||
tree.setPressed(focused, true)
|
||||
confirming = focused
|
||||
else
|
||||
local target = confirming
|
||||
confirming = nil
|
||||
if target then
|
||||
node.setPressed(target, false)
|
||||
tree.setPressed(target, false)
|
||||
local handler = clickHandlers[target]
|
||||
if handler then
|
||||
handler(target)
|
||||
|
||||
Reference in New Issue
Block a user