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
-147
View File
@@ -1,147 +0,0 @@
---@meta
-- Generated from native/src/bindings/core/gui.cpp. Do not edit.
---@alias GuiColor integer
---@alias GuiFont integer
---@alias GuiTextStyle integer
---@class GuiLib
---@field FONT_SMALL GuiFont Small auxiliary text.
---@field FONT_UI GuiFont Normal controls and labels.
---@field FONT_BODY GuiFont Normal reading text.
---@field FONT_LARGE GuiFont Headings and prominent values.
---@field STYLE_NORMAL GuiTextStyle
---@field STYLE_BOLD GuiTextStyle
gui = {}
gui.FONT_SMALL = 0
gui.FONT_UI = 0
gui.FONT_BODY = 0
gui.FONT_LARGE = 0
gui.STYLE_NORMAL = 0
gui.STYLE_BOLD = 0
---Returns the live frame width.
---@return integer
function gui.getWidth() end
---Returns the live frame height.
---@return integer
function gui.getHeight() end
---Rotates the live frame without changing the saved preference.
---@param degrees integer 0, 90, 180, or 270 clockwise.
function gui.setRotation(degrees) end
---Returns the rotation of the live frame.
---@return integer Degrees clockwise for the live frame.
function gui.getRotation() end
---Returns an opaque native color. E-ink implementations quantize RGB to available grayscale.
---@param r integer 0 through 255.
---@param g integer 0 through 255.
---@param b integer 0 through 255.
---@return GuiColor
function gui.color(r, g, b) end
---Clears the frame.
---@param color? GuiColor Defaults to white.
function gui.clear(color) end
---Fills a rectangle.
---@param x integer
---@param y integer
---@param w integer
---@param h integer
---@param color GuiColor
function gui.fillRect(x, y, w, h, color) end
---Outlines a rectangle.
---@param x integer
---@param y integer
---@param w integer
---@param h integer
---@param color GuiColor
function gui.drawRect(x, y, w, h, color) end
---Draws a line.
---@param x1 integer
---@param y1 integer
---@param x2 integer
---@param y2 integer
---@param color GuiColor
---@param width? integer Defaults to one pixel.
function gui.drawLine(x1, y1, x2, y2, color, width) end
---Draws a single pixel.
---@param x integer
---@param y integer
---@param color GuiColor
function gui.drawPixel(x, y, color) end
---Outlines a circle.
---@param x integer Center.
---@param y integer Center.
---@param radius integer
---@param color GuiColor
---@param width? integer Defaults to one pixel.
function gui.drawCircle(x, y, radius, color, width) end
---Fills a circle.
---@param x integer Center.
---@param y integer Center.
---@param radius integer
---@param color GuiColor
---@param background? GuiColor Surface behind an anti-aliased edge.
function gui.fillCircle(x, y, radius, color, background) end
---Draws an anti-aliased rounded fill, optional gradient, and optional border in one pass.
---@param x integer
---@param y integer
---@param w integer
---@param h integer
---@param radius integer
---@param background GuiColor Surface behind the anti-aliased edge.
---@param top? GuiColor Fill, or gradient top; omitted for no fill.
---@param bottom? GuiColor Gradient bottom; defaults to top. Panels without a gradient use top.
---@param border? GuiColor Omitted for no border.
function gui.roundRect(x, y, w, h, radius, background, top, bottom, border) end
---Fills a polygon.
---@param xs integer[]
---@param ys integer[]
---@param color GuiColor
function gui.fillPolygon(xs, ys, color) end
---Draws a bitmap.
---@param path string Absolute BMP path.
---@param x? integer Left edge; defaults to centered.
---@param y? integer Top edge; defaults to centered.
---@param maxWidth? integer Defaults to panel width.
---@param maxHeight? integer Defaults to panel height.
---@return true? ok
---@return string? error
function gui.drawBmp(path, x, y, maxWidth, maxHeight) end
---Measures a text run.
---@param font GuiFont Use a named gui.FONT_* role.
---@param text string
---@param style? GuiTextStyle Defaults to gui.STYLE_NORMAL.
---@return integer
function gui.getTextWidth(font, text, style) end
---Returns the line height of a font role.
---@param font GuiFont Use a named gui.FONT_* role.
---@param style? GuiTextStyle Defaults to gui.STYLE_NORMAL.
---@return integer
function gui.getFontHeight(font, style) end
---Draws a text run with its top-left corner at x, y.
---@param font GuiFont Use a named gui.FONT_* role.
---@param x integer Left edge.
---@param y integer Top edge.
---@param text string
---@param color? GuiColor Defaults to black.
---@param style? GuiTextStyle Defaults to gui.STYLE_NORMAL.
---@param background? GuiColor Omitted for transparent text.
function gui.drawText(font, x, y, text, color, style, background) end
-38
View File
@@ -1,38 +0,0 @@
---@meta
-- Generated from native/src/bindings/core/settings.cpp. Do not edit.
---@class SettingsLib
settings = {}
---Returns the saved rotation in degrees clockwise.
---@return integer
function settings.getRotation() end
---Applies and persists the screen rotation.
---@param degrees integer 0, 90, 180, or 270 clockwise.
---@return true? ok
---@return string? error
function settings.setRotation(degrees) end
---Returns the active POSIX timezone rule.
---@return string
function settings.getTimezone() end
---Applies and persists a POSIX timezone rule.
---@param timezone string
---@return true? ok
---@return string? error
function settings.setTimezone(timezone) end
---Returns the saved palette name. Apps read ui.getTheme() instead; this
---is the stored value, which only ui.setTheme() knows how to apply.
---@return string
function settings.getTheme() end
---Persists a palette name without applying it. Call ui.setTheme(), which
---writes through here and then rebuilds the palette and repaints.
---@param theme string
---@return true? ok
---@return string? error
function settings.setTheme(theme) end
+11 -1
View File
@@ -2,7 +2,7 @@
-- Generated from native/src/bindings/core/sys.cpp. Do not edit.
---@alias Feature "touch"|"buttons"
---@alias Feature "screen"|"touch"|"buttons"
---@class SysLib
sys = {}
@@ -62,3 +62,13 @@ function sys.getMemory() end
---Whether network time synchronization has completed.
---@return boolean
function sys.isClockSynced() end
---Returns the active POSIX timezone rule.
---@return string
function sys.getTimezone() end
---Applies and persists a POSIX timezone rule.
---@param timezone string
---@return true? ok
---@return string? error
function sys.setTimezone(timezone) end
+8 -8
View File
@@ -5,33 +5,33 @@
---@alias Button "up"|"down"|"left"|"right"|"confirm"|"back"
-- Roles, not physical buttons: a device maps whatever hardware it has onto them, and
-- up/down/left/right are the directions node.moveFocus already takes.
-- up/down/left/right are the directions tree.moveFocus already takes.
---@class InputLib
input = input or {}
---@class ButtonsLib
buttons = {}
---Returns the roles this device reports, so an app can label only the actions it has.
---@return Button[]
function input.getButtons() end
function buttons.getAll() end
---Whether any button is held.
---@return boolean
function input.isAnyPressed() end
function buttons.isAnyPressed() end
---Whether a button is held.
---@param button Button
---@return boolean
function input.isPressed(button) end
function buttons.isPressed(button) end
---Whether a button went down since the last poll.
---@param button Button
---@return boolean
function input.wasPressed(button) end
function buttons.wasPressed(button) end
---Whether a button came up since the last poll.
---@param button Button
---@return boolean
function input.wasReleased(button) end
function buttons.wasReleased(button) end
---Fired when a button goes down.
---@param button Button
+164
View File
@@ -0,0 +1,164 @@
---@meta
-- Generated from native/src/bindings/features/screen/screen.cpp. Do not edit.
-- The panel itself; the widget tree it paints is `tree`, and
-- sys.hasFeature("screen") covers both.
---@alias ScreenColor integer
---@alias ScreenFont integer
---@alias ScreenTextStyle integer
---@class ScreenLib
---@field FONT_SMALL ScreenFont Small auxiliary text.
---@field FONT_UI ScreenFont Normal controls and labels.
---@field FONT_BODY ScreenFont Normal reading text.
---@field FONT_LARGE ScreenFont Headings and prominent values.
---@field STYLE_NORMAL ScreenTextStyle
---@field STYLE_BOLD ScreenTextStyle
screen = {}
screen.FONT_SMALL = 0
screen.FONT_UI = 0
screen.FONT_BODY = 0
screen.FONT_LARGE = 0
screen.STYLE_NORMAL = 0
screen.STYLE_BOLD = 0
---Returns the live frame width.
---@return integer
function screen.getWidth() end
---Returns the live frame height.
---@return integer
function screen.getHeight() end
---Rotates the panel and persists the choice, so there is one rotation
---rather than a live one and a saved one to reconcile.
---@param degrees integer 0, 90, 180, or 270 clockwise.
---@return true? ok
---@return string? error
function screen.setRotation(degrees) end
---Returns the rotation in degrees clockwise.
---@return integer
function screen.getRotation() end
---Returns the saved palette name. Apps read ui.getTheme() instead; this
---is the stored value, which only ui.setTheme() knows how to apply.
---@return string
function screen.getTheme() end
---Persists a palette name without applying it. Call ui.setTheme(), which
---writes through here and then rebuilds the palette and repaints.
---@param theme string
---@return true? ok
---@return string? error
function screen.setTheme(theme) end
---Returns an opaque native color. E-ink implementations quantize RGB to available grayscale.
---@param r integer 0 through 255.
---@param g integer 0 through 255.
---@param b integer 0 through 255.
---@return ScreenColor
function screen.color(r, g, b) end
---Clears the frame.
---@param color? ScreenColor Defaults to white.
function screen.clear(color) end
---Fills a rectangle.
---@param x integer
---@param y integer
---@param w integer
---@param h integer
---@param color ScreenColor
function screen.fillRect(x, y, w, h, color) end
---Outlines a rectangle.
---@param x integer
---@param y integer
---@param w integer
---@param h integer
---@param color ScreenColor
function screen.drawRect(x, y, w, h, color) end
---Draws a line.
---@param x1 integer
---@param y1 integer
---@param x2 integer
---@param y2 integer
---@param color ScreenColor
---@param width? integer Defaults to one pixel.
function screen.drawLine(x1, y1, x2, y2, color, width) end
---Draws a single pixel.
---@param x integer
---@param y integer
---@param color ScreenColor
function screen.drawPixel(x, y, color) end
---Outlines a circle.
---@param x integer Center.
---@param y integer Center.
---@param radius integer
---@param color ScreenColor
---@param width? integer Defaults to one pixel.
function screen.drawCircle(x, y, radius, color, width) end
---Fills a circle.
---@param x integer Center.
---@param y integer Center.
---@param radius integer
---@param color ScreenColor
---@param background? ScreenColor Surface behind an anti-aliased edge.
function screen.fillCircle(x, y, radius, color, background) end
---Draws an anti-aliased rounded fill, optional gradient, and optional border in one pass.
---@param x integer
---@param y integer
---@param w integer
---@param h integer
---@param radius integer
---@param background ScreenColor Surface behind the anti-aliased edge.
---@param top? ScreenColor Fill, or gradient top; omitted for no fill.
---@param bottom? ScreenColor Gradient bottom; defaults to top. Panels without a gradient use top.
---@param border? ScreenColor Omitted for no border.
function screen.roundRect(x, y, w, h, radius, background, top, bottom, border) end
---Fills a polygon.
---@param xs integer[]
---@param ys integer[]
---@param color ScreenColor
function screen.fillPolygon(xs, ys, color) end
---Draws a bitmap.
---@param path string Absolute BMP path.
---@param x? integer Left edge; defaults to centered.
---@param y? integer Top edge; defaults to centered.
---@param maxWidth? integer Defaults to panel width.
---@param maxHeight? integer Defaults to panel height.
---@return true? ok
---@return string? error
function screen.drawBmp(path, x, y, maxWidth, maxHeight) end
---Measures a text run.
---@param font ScreenFont Use a named screen.FONT_* role.
---@param text string
---@param style? ScreenTextStyle Defaults to screen.STYLE_NORMAL.
---@return integer
function screen.getTextWidth(font, text, style) end
---Returns the line height of a font role.
---@param font ScreenFont Use a named screen.FONT_* role.
---@param style? ScreenTextStyle Defaults to screen.STYLE_NORMAL.
---@return integer
function screen.getFontHeight(font, style) end
---Draws a text run with its top-left corner at x, y.
---@param font ScreenFont Use a named screen.FONT_* role.
---@param x integer Left edge.
---@param y integer Top edge.
---@param text string
---@param color? ScreenColor Defaults to black.
---@param style? ScreenTextStyle Defaults to screen.STYLE_NORMAL.
---@param background? ScreenColor Omitted for transparent text.
function screen.drawText(font, x, y, text, color, style, background) end
@@ -1,6 +1,6 @@
---@meta
-- Generated from native/src/bindings/core/node.cpp. Do not edit.
-- Generated from native/src/bindings/features/screen/tree.cpp. Do not edit.
---@alias NodeId integer
---@alias NodeType "box"|"text"|"button"|"custom"
@@ -19,43 +19,43 @@
---@field capture? boolean
---@field interactive? boolean
---@field label? string
---@field font? GuiFont
---@field font? ScreenFont
---@class NodeStyle
---@field color? GuiColor
---@field background? GuiColor Background offered to descendants.
---@field fill? GuiColor Surface painted by a box.
---@field border? GuiColor
---@field face? GuiColor Default button surface.
---@field pressedFace? GuiColor Pressed button surface.
---@field pressedColor? GuiColor Pressed button text.
---@field focusColor? GuiColor Distinct outline for directional focus.
---@field color? ScreenColor
---@field background? ScreenColor Background offered to descendants.
---@field fill? ScreenColor Surface painted by a box.
---@field border? ScreenColor
---@field face? ScreenColor Default button surface.
---@field pressedFace? ScreenColor Pressed button surface.
---@field pressedColor? ScreenColor Pressed button text.
---@field focusColor? ScreenColor Distinct outline for directional focus.
---@field radius? integer
---@field font? GuiFont
---@field textStyle? GuiTextStyle
---@field font? ScreenFont
---@field textStyle? ScreenTextStyle
---@class NodeLib
node = {}
---@class TreeLib
tree = {}
---Drops the current tree; all existing IDs become invalid.
function node.reset() end
function tree.reset() end
---Creates a node, optionally as a child of an existing one.
---@param parent? NodeId Nil creates a root.
---@param spec NodeSpec
---@return NodeId
function node.create(parent, spec) end
function tree.create(parent, spec) end
---Adopts an existing root as a child.
---@param parent NodeId
---@param child NodeId Existing root without a parent.
function node.attach(parent, child) end
function tree.attach(parent, child) end
---Changes a node's requested size before layout.
---@param id NodeId
---@param w? number|"fill"|"auto"
---@param h? number|"fill"|"auto"
function node.setSize(id, w, h) end
function tree.setSize(id, w, h) end
---Measures and places a subtree.
---@param root NodeId
@@ -65,17 +65,17 @@ function node.setSize(id, w, h) end
---@param h integer
---@return true? ok
---@return string? error
function node.layout(root, x, y, w, h) end
function tree.layout(root, x, y, w, h) end
---Releases temporary measurement and placement inputs after layout.
function node.dropScratch() end
function tree.dropScratch() end
---Returns the deepest interactive node under a point.
---@param root NodeId
---@param x integer
---@param y integer
---@return NodeId?
function node.hit(root, x, y) end
function tree.hit(root, x, y) end
---Returns a node's placed rectangle.
---@param id NodeId
@@ -83,73 +83,73 @@ function node.hit(root, x, y) end
---@return integer y
---@return integer w
---@return integer h
function node.getRect(id) end
function tree.getRect(id) end
---Replaces a node's text and marks it for repaint.
---@param id NodeId
---@param text string
function node.setLabel(id, text) end
function tree.setLabel(id, text) end
---Returns a node's text.
---@param id NodeId
---@return string?
function node.getLabel(id) end
function tree.getLabel(id) end
---Returns a node's parent.
---@param id NodeId
---@return NodeId?
function node.getParent(id) end
function tree.getParent(id) end
---Sets the style roles a subtree inherits.
---@param id NodeId
---@param style NodeStyle
function node.setStyle(id, style) end
function tree.setStyle(id, style) end
---Marks a node for repaint.
---@param id NodeId
function node.invalidate(id) end
function tree.invalidate(id) end
---Sets a node's pressed state.
---@param id NodeId
---@param pressed boolean
function node.setPressed(id, pressed) end
function tree.setPressed(id, pressed) end
---Whether a node is pressed.
---@param id NodeId
---@return boolean
function node.isPressed(id) end
function tree.isPressed(id) end
---Focuses the first interactive node in layout order.
---@param root NodeId
---@return NodeId? focused
function node.focusFirst(root) end
function tree.focusFirst(root) end
---Changes focus and invalidates the previously and newly focused nodes.
---@param id? NodeId Nil clears focus.
function node.setFocus(id) end
function tree.setFocus(id) end
---Returns the focused node.
---@return NodeId?
function node.getFocus() end
function tree.getFocus() end
---Moves to the nearest interactive node in the requested direction without wrapping.
---@param root NodeId
---@param direction NodeDirection
---@return NodeId? focused Current focus when no candidate exists.
function node.moveFocus(root, direction) end
function tree.moveFocus(root, direction) end
---Registers the painter every custom node calls.
---@param painter fun(id: NodeId, x: integer, y: integer, w: integer, h: integer)
function node.setPainter(painter) end
function tree.setPainter(painter) end
---Paints dirty nodes; the firmware owns publication to the physical display.
---@param root NodeId
function node.draw(root) end
function tree.draw(root) end
---Returns the number of nodes in the tree.
---@return integer
function node.getCount() end
function tree.getCount() end
---Returns the tree's memory use.
---@return integer bytes
function node.getFootprint() end
function tree.getFootprint() end
+17 -20
View File
@@ -2,8 +2,22 @@
-- Generated from native/src/bindings/features/touch.cpp. Do not edit.
---@class SettingsLib
settings = settings or {}
---@class TouchLib
touch = {}
---Returns the calibrated touch point, or nothing when the panel is not touched.
---@return integer? x
---@return integer? y
function touch.getPoint() end
---Returns the uncalibrated touch reading, or nothing when the panel is not touched.
---@return integer? x
---@return integer? y
function touch.getRawPoint() end
---Whether the panel is currently touched.
---@return boolean
function touch.isTouched() end
---Persists the panel's touch calibration.
---@param x0 integer Raw reading at the left edge.
@@ -12,24 +26,7 @@ settings = settings or {}
---@param y1 integer Raw reading at the bottom edge.
---@return true? ok
---@return string? error
function settings.setCalibration(x0, y0, x1, y1) end
---@class InputLib
input = input or {}
---Returns the calibrated touch point, or nothing when the panel is not touched.
---@return integer? x
---@return integer? y
function input.getTouch() end
---Returns the uncalibrated touch reading, or nothing when the panel is not touched.
---@return integer? x
---@return integer? y
function input.getRawTouch() end
---Whether the panel is currently touched.
---@return boolean
function input.isTouched() end
function touch.setCalibration(x0, y0, x1, y1) end
---Fired when the finger lands.
---@param x integer
+11 -11
View File
@@ -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
View File
@@ -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)
+3 -3
View File
@@ -3,7 +3,7 @@ package.path = "./lua/lib/?.lua;" .. package.path
local drawn = {}
local roles = { "confirm", "back", "right" }
gui = {
screen = {
FONT_SMALL = 0,
STYLE_NORMAL = 0,
color = function(r, g, b)
@@ -29,8 +29,8 @@ gui = {
end,
}
input = {
getButtons = function()
buttons = {
getAll = function()
return roles
end,
}
+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