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:
@@ -4,7 +4,11 @@ This is a clean contract for repositories under the same owner's control. Choose
|
||||
API without preserving old names, signatures, or behavior; consumers migrate to the contract.
|
||||
|
||||
Every firmware implements all declarations under `lua/api/core/`. Optional hardware contracts
|
||||
live under `lua/api/features/`; `sys.hasFeature(name)` guarantees the complete matching contract.
|
||||
live under `lua/api/features/`, as one file or one directory per feature; `sys.hasFeature(name)`
|
||||
guarantees the complete matching contract. Every namespace belongs to exactly one feature or to
|
||||
core, so a feature is a provider pointer rather than a claim to validate: a panel is the `screen`
|
||||
feature (`screen` and `tree`, including the saved rotation and theme), registered only when the
|
||||
firmware supplies a `GuiProvider`, and calibration is `touch.setCalibration()`.
|
||||
Lua-language sources stay under `lua/`; C/C++ and the vendored interpreter stay under `native/`.
|
||||
Apps are fully trusted; keep permissions and sandboxing out of scope.
|
||||
Firmware commits dirty display content and owns panel refresh policy. Binding annotations under
|
||||
|
||||
@@ -4,7 +4,7 @@ CC ?= cc
|
||||
CXX ?= c++
|
||||
AR ?= ar
|
||||
|
||||
CONTRACTS := $(sort $(wildcard lua/api/core/*.lua lua/api/features/*.lua))
|
||||
CONTRACTS := $(sort $(wildcard lua/api/core/*.lua lua/api/features/*.lua lua/api/features/*/*.lua))
|
||||
LUA_C := $(sort $(wildcard native/src/vendor/lua/*.c))
|
||||
LUA_OBJECTS := $(patsubst native/src/vendor/lua/%.c,_build/lua/%.o,$(LUA_C))
|
||||
LUA_LIBRARY := _build/liblua.a
|
||||
@@ -12,7 +12,7 @@ LUA_SMOKE := _build/lua-smoke
|
||||
RUNTIME_TEST := _build/runtime-test
|
||||
LUAC32 := _build/luac32
|
||||
RUNTIME_CPP := $(sort $(wildcard native/src/runtime/*.cpp native/src/bindings/core/*.cpp \
|
||||
native/src/bindings/features/*.cpp) native/src/embedded_modules.cpp)
|
||||
native/src/bindings/features/*.cpp native/src/bindings/features/*/*.cpp) native/src/embedded_modules.cpp)
|
||||
|
||||
.PHONY: api test embed compiledb format
|
||||
|
||||
|
||||
@@ -26,10 +26,14 @@ native/
|
||||
```
|
||||
|
||||
Every firmware implements all files under `lua/api/core/`. `sys.hasFeature(name)` declares
|
||||
optional features; claiming one guarantees every API and behavior in its matching file. Features
|
||||
compose, so a device may expose both `touch` and `buttons`. Display technology is not a feature:
|
||||
an e-ink `GuiProvider` flattens a gradient the way `gui.color()` quantizes to grayscale, and the
|
||||
firmware owns publication and waveform policy on every panel.
|
||||
optional features; claiming one guarantees every API and behavior in its matching file or
|
||||
directory. Features compose, so a device may expose both `touch` and `buttons`. A panel is the
|
||||
`screen` feature -- the `screen` and `tree` namespaces, including the saved rotation and theme --
|
||||
because a headless firmware supplies no `GuiProvider`. Every namespace belongs to exactly one
|
||||
feature or to core, which is why touch calibration is `touch.setCalibration()` rather than a
|
||||
shared settings namespace three features write to. Display technology is still not a feature:
|
||||
an e-ink `GuiProvider` flattens a gradient the way `screen.color()` quantizes to grayscale, and
|
||||
the firmware owns publication and waveform policy on every panel.
|
||||
|
||||
The contract is the app-facing Lua API, not the provider C++ interface. Shared binding
|
||||
registrations carry LuaLS annotations; `tools/gen_api.py` mirrors them into `lua/api/`. Generated
|
||||
@@ -47,7 +51,7 @@ provider is how `sys.hasFeature()` answers false, and its namespace additions ar
|
||||
registered.
|
||||
|
||||
The declarations are a clean target, not the intersection of today's APIs. Existing apps and
|
||||
firmwares migrate to it without compatibility aliases. Safe filesystem mutation, `node`, app
|
||||
firmwares migrate to it without compatibility aliases. Safe filesystem mutation, app
|
||||
navigation, module loading, and `ble` are core even where a firmware does not implement them yet.
|
||||
Every app may use `require`; modules resolve from its app directory and `/.lua/lib`. This repository
|
||||
owns portable shared modules such as `ui.lua`; firmware-specific modules stay with their firmware.
|
||||
@@ -84,7 +88,7 @@ its provider is a wiring bug and says so.
|
||||
|
||||
The firmware commits dirty display content after callback batches and owns e-ink waveform
|
||||
policy; apps do not refresh the panel manually. Apps are fully trusted with the complete core API.
|
||||
Theme persistence and application belong to shared `ui.lua`, not the firmware `settings` binding.
|
||||
Theme application belongs to shared `ui.lua`; `screen.setTheme()` only stores the name.
|
||||
|
||||
The native library vendors Lua 5.4.8 from GitHub tag `v5.4.8` and compiles it with `LUA_32BITS`.
|
||||
Command-line and upstream test entry points are excluded; `luaconf.h` carries one documented guard
|
||||
@@ -92,7 +96,7 @@ that lets the build flag select 32-bit number mode.
|
||||
|
||||
## Shared UI
|
||||
|
||||
Portable apps normally use the declarative `ui.lua` toolkit; `node` remains the low-level escape
|
||||
Portable apps normally use the declarative `ui.lua` toolkit; `tree` remains the low-level escape
|
||||
hatch. The baseline constructors are `screen`, `box`, `spacer`, `text`, `label`, `button`,
|
||||
`custom`, and `confirm`. A screen accepts both touch and physical-button input.
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
@@ -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
@@ -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)
|
||||
|
||||
+3
-3
@@ -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
@@ -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
|
||||
|
||||
|
||||
@@ -35,19 +35,6 @@ struct MemoryInfo {
|
||||
int32_t largestFreeBlock;
|
||||
};
|
||||
|
||||
class SettingsProvider {
|
||||
public:
|
||||
virtual ~SettingsProvider() = default;
|
||||
virtual int32_t rotation() const = 0;
|
||||
virtual Status setRotation(int32_t degrees) = 0;
|
||||
virtual std::string timezone() const = 0;
|
||||
virtual Status setTimezone(const std::string& timezone) = 0;
|
||||
// Only the name of a palette; the colours themselves live in Lua, so the
|
||||
// firmware can read the saved theme before a lua_State exists.
|
||||
virtual std::string theme() const = 0;
|
||||
virtual Status setTheme(const std::string& theme) = 0;
|
||||
};
|
||||
|
||||
// Only what the firmware alone can answer. App identity, titles, data paths,
|
||||
// feature reporting and navigation are the runtime's, because it owns app
|
||||
// loading and knows which providers exist.
|
||||
@@ -57,6 +44,8 @@ public:
|
||||
virtual int32_t millis() const = 0;
|
||||
virtual MemoryInfo memory() const = 0;
|
||||
virtual bool isClockSynced() const = 0;
|
||||
virtual std::string timezone() const = 0;
|
||||
virtual Status setTimezone(const std::string& timezone) = 0;
|
||||
};
|
||||
|
||||
// Scripts are streamed rather than slurped: a whole module in one buffer needs
|
||||
@@ -115,7 +104,13 @@ public:
|
||||
virtual int32_t width() const = 0;
|
||||
virtual int32_t height() const = 0;
|
||||
virtual int32_t rotation() const = 0;
|
||||
virtual void setRotation(int32_t degrees) = 0;
|
||||
// Applies the rotation and persists it, so the panel comes back the way the
|
||||
// user left it with no second call to forget.
|
||||
virtual Status setRotation(int32_t degrees) = 0;
|
||||
// Only the name of a palette; the colours themselves live in Lua, so the
|
||||
// firmware can read the saved theme before a lua_State exists.
|
||||
virtual std::string theme() const = 0;
|
||||
virtual Status setTheme(const std::string& theme) = 0;
|
||||
virtual int32_t color(int32_t r, int32_t g, int32_t b) const = 0;
|
||||
virtual void clear(int32_t color) = 0;
|
||||
virtual void fillRect(int32_t x, int32_t y, int32_t w, int32_t h,
|
||||
|
||||
@@ -25,15 +25,14 @@ constexpr const char* MAIN_PATH = "/.lua/main.lua";
|
||||
// registered.
|
||||
struct Providers {
|
||||
LogProvider* log = nullptr;
|
||||
SettingsProvider* settings = nullptr;
|
||||
SysProvider* sys = nullptr;
|
||||
FsProvider* fs = nullptr;
|
||||
GuiProvider* gui = nullptr;
|
||||
HttpProvider* http = nullptr;
|
||||
TimerProvider* timer = nullptr;
|
||||
WifiProvider* wifi = nullptr;
|
||||
BleProvider* ble = nullptr;
|
||||
|
||||
GuiProvider* gui = nullptr;
|
||||
TouchProvider* touch = nullptr;
|
||||
ButtonsProvider* buttons = nullptr;
|
||||
};
|
||||
@@ -82,7 +81,6 @@ public:
|
||||
bool applyPendingNavigation();
|
||||
|
||||
LogProvider& log() const { return *providers_.log; }
|
||||
SettingsProvider& settings() const { return *providers_.settings; }
|
||||
SysProvider& sys() const { return *providers_.sys; }
|
||||
FsProvider& fs() const { return *providers_.fs; }
|
||||
GuiProvider& gui() const { return *providers_.gui; }
|
||||
|
||||
@@ -1,101 +0,0 @@
|
||||
// @lua-module settings SettingsLib
|
||||
|
||||
#include <lua/runtime.h>
|
||||
|
||||
extern "C" {
|
||||
#include "lauxlib.h"
|
||||
#include "lua.h"
|
||||
}
|
||||
|
||||
namespace esp32lua {
|
||||
namespace bindings {
|
||||
namespace {
|
||||
|
||||
int pushStatus(lua_State* state, const Status& status) {
|
||||
if (status.ok) {
|
||||
lua_pushboolean(state, true);
|
||||
return 1;
|
||||
}
|
||||
lua_pushnil(state);
|
||||
lua_pushlstring(state, status.error.data(), status.error.size());
|
||||
return 2;
|
||||
}
|
||||
|
||||
int getRotation(lua_State* state) {
|
||||
lua_pushinteger(state, Runtime::from(state)->settings().rotation());
|
||||
return 1;
|
||||
}
|
||||
|
||||
int setRotation(lua_State* state) {
|
||||
const lua_Integer degrees = luaL_checkinteger(state, 1);
|
||||
luaL_argcheck(state, degrees >= 0 && degrees <= 270 && degrees % 90 == 0, 1,
|
||||
"expected 0, 90, 180, or 270");
|
||||
return pushStatus(state,
|
||||
Runtime::from(state)->settings().setRotation(degrees));
|
||||
}
|
||||
|
||||
int getTimezone(lua_State* state) {
|
||||
const std::string timezone = Runtime::from(state)->settings().timezone();
|
||||
lua_pushlstring(state, timezone.data(), timezone.size());
|
||||
return 1;
|
||||
}
|
||||
|
||||
int setTimezone(lua_State* state) {
|
||||
size_t length = 0;
|
||||
const char* value = luaL_checklstring(state, 1, &length);
|
||||
return pushStatus(
|
||||
state, Runtime::from(state)->settings().setTimezone({value, length}));
|
||||
}
|
||||
|
||||
int getTheme(lua_State* state) {
|
||||
const std::string theme = Runtime::from(state)->settings().theme();
|
||||
lua_pushlstring(state, theme.data(), theme.size());
|
||||
return 1;
|
||||
}
|
||||
|
||||
int setTheme(lua_State* state) {
|
||||
size_t length = 0;
|
||||
const char* value = luaL_checklstring(state, 1, &length);
|
||||
return pushStatus(state,
|
||||
Runtime::from(state)->settings().setTheme({value, length}));
|
||||
}
|
||||
|
||||
const luaL_Reg FUNCTIONS[] = {
|
||||
// --- Returns the saved rotation in degrees clockwise.
|
||||
// @return integer
|
||||
{"getRotation", getRotation},
|
||||
// --- Applies and persists the screen rotation.
|
||||
// @param degrees integer 0, 90, 180, or 270 clockwise.
|
||||
// @return true|nil ok
|
||||
// @return string|nil error
|
||||
{"setRotation", setRotation},
|
||||
// --- Returns the active POSIX timezone rule.
|
||||
// @return string
|
||||
{"getTimezone", getTimezone},
|
||||
// --- Applies and persists a POSIX timezone rule.
|
||||
// @param timezone string
|
||||
// @return true|nil ok
|
||||
// @return string|nil error
|
||||
{"setTimezone", setTimezone},
|
||||
// --- 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
|
||||
{"getTheme", getTheme},
|
||||
// --- 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|nil ok
|
||||
// @return string|nil error
|
||||
{"setTheme", setTheme},
|
||||
{nullptr, nullptr},
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
void registerSettings(lua_State* state) {
|
||||
luaL_newlib(state, FUNCTIONS);
|
||||
lua_setglobal(state, "settings");
|
||||
}
|
||||
|
||||
} // namespace bindings
|
||||
} // namespace esp32lua
|
||||
@@ -1,5 +1,5 @@
|
||||
// @lua-module sys SysLib
|
||||
// @lua-preamble ---@alias Feature "touch"|"buttons"
|
||||
// @lua-preamble ---@alias Feature "screen"|"touch"|"buttons"
|
||||
|
||||
#include "../helpers.h"
|
||||
|
||||
@@ -66,6 +66,14 @@ int isClockSynced(lua_State* state) {
|
||||
lua_pushboolean(state, Runtime::from(state)->sys().isClockSynced());
|
||||
return 1;
|
||||
}
|
||||
int getTimezone(lua_State* state) {
|
||||
pushString(state, Runtime::from(state)->sys().timezone());
|
||||
return 1;
|
||||
}
|
||||
int setTimezone(lua_State* state) {
|
||||
const std::string timezone = checkString(state, 1);
|
||||
return pushStatus(state, Runtime::from(state)->sys().setTimezone(timezone));
|
||||
}
|
||||
|
||||
const luaL_Reg FUNCTIONS[] = {
|
||||
// --- Returns the implemented API contract version.
|
||||
@@ -116,6 +124,14 @@ const luaL_Reg FUNCTIONS[] = {
|
||||
// --- Whether network time synchronization has completed.
|
||||
// @return boolean
|
||||
{"isClockSynced", isClockSynced},
|
||||
// --- Returns the active POSIX timezone rule.
|
||||
// @return string
|
||||
{"getTimezone", getTimezone},
|
||||
// --- Applies and persists a POSIX timezone rule.
|
||||
// @param timezone string
|
||||
// @return true|nil ok
|
||||
// @return string|nil error
|
||||
{"setTimezone", setTimezone},
|
||||
{nullptr, nullptr},
|
||||
};
|
||||
|
||||
|
||||
@@ -32,18 +32,18 @@ int wasReleased(lua_State* state) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
// @lua-augment input InputLib
|
||||
// @lua-module buttons ButtonsLib
|
||||
// @lua-preamble ---@alias Button "up"|"down"|"left"|"right"|"confirm"|"back"
|
||||
// @lua-preamble
|
||||
// @lua-preamble -- Roles, not physical buttons: a device maps whatever hardware
|
||||
// it has onto them, and
|
||||
// @lua-preamble -- up/down/left/right are the directions node.moveFocus already
|
||||
// @lua-preamble -- up/down/left/right are the directions tree.moveFocus already
|
||||
// takes.
|
||||
const luaL_Reg INPUT_FUNCTIONS[] = {
|
||||
const luaL_Reg FUNCTIONS[] = {
|
||||
// ---Returns the roles this device reports, so an app can label only the
|
||||
// actions it has.
|
||||
// @return Button[]
|
||||
{"getButtons", getButtons},
|
||||
{"getAll", getButtons},
|
||||
// ---Whether any button is held.
|
||||
// @return boolean
|
||||
{"isAnyPressed", isAnyPressed},
|
||||
@@ -65,7 +65,8 @@ const luaL_Reg INPUT_FUNCTIONS[] = {
|
||||
} // namespace
|
||||
|
||||
void registerButtons(lua_State* state) {
|
||||
augmentGlobal(state, "input", INPUT_FUNCTIONS);
|
||||
luaL_newlib(state, FUNCTIONS);
|
||||
lua_setglobal(state, "buttons");
|
||||
}
|
||||
|
||||
} // namespace bindings
|
||||
|
||||
@@ -1,15 +1,17 @@
|
||||
// @lua-module gui GuiLib
|
||||
// @lua-preamble ---@alias GuiColor integer
|
||||
// @lua-preamble ---@alias GuiFont integer
|
||||
// @lua-preamble ---@alias GuiTextStyle integer
|
||||
// @lua-const FONT_SMALL GuiFont 0 Small auxiliary text.
|
||||
// @lua-const FONT_UI GuiFont 0 Normal controls and labels.
|
||||
// @lua-const FONT_BODY GuiFont 0 Normal reading text.
|
||||
// @lua-const FONT_LARGE GuiFont 0 Headings and prominent values.
|
||||
// @lua-const STYLE_NORMAL GuiTextStyle 0
|
||||
// @lua-const STYLE_BOLD GuiTextStyle 0
|
||||
// @lua-module screen ScreenLib
|
||||
// @lua-preamble -- The panel itself; the widget tree it paints is `tree`, and
|
||||
// @lua-preamble -- sys.hasFeature("screen") covers both.
|
||||
// @lua-preamble ---@alias ScreenColor integer
|
||||
// @lua-preamble ---@alias ScreenFont integer
|
||||
// @lua-preamble ---@alias ScreenTextStyle integer
|
||||
// @lua-const FONT_SMALL ScreenFont 0 Small auxiliary text.
|
||||
// @lua-const FONT_UI ScreenFont 0 Normal controls and labels.
|
||||
// @lua-const FONT_BODY ScreenFont 0 Normal reading text.
|
||||
// @lua-const FONT_LARGE ScreenFont 0 Headings and prominent values.
|
||||
// @lua-const STYLE_NORMAL ScreenTextStyle 0
|
||||
// @lua-const STYLE_BOLD ScreenTextStyle 0
|
||||
|
||||
#include "../helpers.h"
|
||||
#include "../../helpers.h"
|
||||
|
||||
namespace esp32lua {
|
||||
namespace bindings {
|
||||
@@ -36,8 +38,17 @@ int setRotation(lua_State* state) {
|
||||
const int32_t degrees = checkInt(state, 1);
|
||||
luaL_argcheck(state, degrees >= 0 && degrees <= 270 && degrees % 90 == 0, 1,
|
||||
"expected 0, 90, 180, or 270");
|
||||
provider(state).setRotation(degrees);
|
||||
return 0;
|
||||
return pushStatus(state, provider(state).setRotation(degrees));
|
||||
}
|
||||
|
||||
int getTheme(lua_State* state) {
|
||||
pushString(state, provider(state).theme());
|
||||
return 1;
|
||||
}
|
||||
|
||||
int setTheme(lua_State* state) {
|
||||
const std::string theme = checkString(state, 1);
|
||||
return pushStatus(state, provider(state).setTheme(theme));
|
||||
}
|
||||
|
||||
int color(lua_State* state) {
|
||||
@@ -215,62 +226,76 @@ const luaL_Reg FUNCTIONS[] = {
|
||||
// --- Returns the live frame height.
|
||||
// @return integer
|
||||
{"getHeight", getHeight},
|
||||
// --- Rotates the live frame without changing the saved preference.
|
||||
// --- 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|nil ok
|
||||
// @return string|nil error
|
||||
{"setRotation", setRotation},
|
||||
// --- Returns the rotation of the live frame.
|
||||
// @return integer Degrees clockwise for the live frame.
|
||||
// --- Returns the rotation in degrees clockwise.
|
||||
// @return integer
|
||||
{"getRotation", getRotation},
|
||||
// --- 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
|
||||
{"getTheme", getTheme},
|
||||
// --- 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|nil ok
|
||||
// @return string|nil error
|
||||
{"setTheme", setTheme},
|
||||
// --- 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
|
||||
// @return ScreenColor
|
||||
{"color", color},
|
||||
// --- Clears the frame.
|
||||
// @param color GuiColor|nil Defaults to white.
|
||||
// @param color ScreenColor|nil Defaults to white.
|
||||
{"clear", clear},
|
||||
// --- Fills a rectangle.
|
||||
// @param x integer
|
||||
// @param y integer
|
||||
// @param w integer
|
||||
// @param h integer
|
||||
// @param color GuiColor
|
||||
// @param color ScreenColor
|
||||
{"fillRect", fillRect},
|
||||
// --- Outlines a rectangle.
|
||||
// @param x integer
|
||||
// @param y integer
|
||||
// @param w integer
|
||||
// @param h integer
|
||||
// @param color GuiColor
|
||||
// @param color ScreenColor
|
||||
{"drawRect", drawRect},
|
||||
// --- Draws a line.
|
||||
// @param x1 integer
|
||||
// @param y1 integer
|
||||
// @param x2 integer
|
||||
// @param y2 integer
|
||||
// @param color GuiColor
|
||||
// @param color ScreenColor
|
||||
// @param width integer|nil Defaults to one pixel.
|
||||
{"drawLine", drawLine},
|
||||
// --- Draws a single pixel.
|
||||
// @param x integer
|
||||
// @param y integer
|
||||
// @param color GuiColor
|
||||
// @param color ScreenColor
|
||||
{"drawPixel", drawPixel},
|
||||
// --- Outlines a circle.
|
||||
// @param x integer Center.
|
||||
// @param y integer Center.
|
||||
// @param radius integer
|
||||
// @param color GuiColor
|
||||
// @param color ScreenColor
|
||||
// @param width integer|nil Defaults to one pixel.
|
||||
{"drawCircle", drawCircle},
|
||||
// --- Fills a circle.
|
||||
// @param x integer Center.
|
||||
// @param y integer Center.
|
||||
// @param radius integer
|
||||
// @param color GuiColor
|
||||
// @param background GuiColor|nil Surface behind an anti-aliased edge.
|
||||
// @param color ScreenColor
|
||||
// @param background ScreenColor|nil Surface behind an anti-aliased edge.
|
||||
{"fillCircle", fillCircle},
|
||||
// ---Draws an anti-aliased rounded fill, optional gradient, and optional
|
||||
// border in one pass.
|
||||
@@ -279,16 +304,16 @@ const luaL_Reg FUNCTIONS[] = {
|
||||
// @param w integer
|
||||
// @param h integer
|
||||
// @param radius integer
|
||||
// @param background GuiColor Surface behind the anti-aliased edge.
|
||||
// @param top GuiColor|nil Fill, or gradient top; omitted for no fill.
|
||||
// @param bottom GuiColor|nil Gradient bottom; defaults to top. Panels
|
||||
// @param background ScreenColor Surface behind the anti-aliased edge.
|
||||
// @param top ScreenColor|nil Fill, or gradient top; omitted for no fill.
|
||||
// @param bottom ScreenColor|nil Gradient bottom; defaults to top. Panels
|
||||
// without a gradient use top.
|
||||
// @param border GuiColor|nil Omitted for no border.
|
||||
// @param border ScreenColor|nil Omitted for no border.
|
||||
{"roundRect", roundRect},
|
||||
// --- Fills a polygon.
|
||||
// @param xs integer[]
|
||||
// @param ys integer[]
|
||||
// @param color GuiColor
|
||||
// @param color ScreenColor
|
||||
{"fillPolygon", fillPolygon},
|
||||
// --- Draws a bitmap.
|
||||
// @param path string Absolute BMP path.
|
||||
@@ -300,31 +325,31 @@ const luaL_Reg FUNCTIONS[] = {
|
||||
// @return string|nil error
|
||||
{"drawBmp", drawBmp},
|
||||
// --- Measures a text run.
|
||||
// @param font GuiFont Use a named gui.FONT_* role.
|
||||
// @param font ScreenFont Use a named screen.FONT_* role.
|
||||
// @param text string
|
||||
// @param style GuiTextStyle|nil Defaults to gui.STYLE_NORMAL.
|
||||
// @param style ScreenTextStyle|nil Defaults to screen.STYLE_NORMAL.
|
||||
// @return integer
|
||||
{"getTextWidth", getTextWidth},
|
||||
// --- Returns the line height of a font role.
|
||||
// @param font GuiFont Use a named gui.FONT_* role.
|
||||
// @param style GuiTextStyle|nil Defaults to gui.STYLE_NORMAL.
|
||||
// @param font ScreenFont Use a named screen.FONT_* role.
|
||||
// @param style ScreenTextStyle|nil Defaults to screen.STYLE_NORMAL.
|
||||
// @return integer
|
||||
{"getFontHeight", getFontHeight},
|
||||
// --- Draws a text run with its top-left corner at x, y.
|
||||
// @param font GuiFont Use a named gui.FONT_* role.
|
||||
// @param font ScreenFont Use a named screen.FONT_* role.
|
||||
// @param x integer Left edge.
|
||||
// @param y integer Top edge.
|
||||
// @param text string
|
||||
// @param color GuiColor|nil Defaults to black.
|
||||
// @param style GuiTextStyle|nil Defaults to gui.STYLE_NORMAL.
|
||||
// @param background GuiColor|nil Omitted for transparent text.
|
||||
// @param color ScreenColor|nil Defaults to black.
|
||||
// @param style ScreenTextStyle|nil Defaults to screen.STYLE_NORMAL.
|
||||
// @param background ScreenColor|nil Omitted for transparent text.
|
||||
{"drawText", drawText},
|
||||
{nullptr, nullptr},
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
void registerGui(lua_State* state) {
|
||||
void registerScreen(lua_State* state) {
|
||||
luaL_newlib(state, FUNCTIONS);
|
||||
const FontIds fonts = Runtime::from(state)->gui().fonts();
|
||||
setField(state, "FONT_SMALL", fonts.small);
|
||||
@@ -333,7 +358,7 @@ void registerGui(lua_State* state) {
|
||||
setField(state, "FONT_LARGE", fonts.large);
|
||||
setField(state, "STYLE_NORMAL", fonts.styleNormal);
|
||||
setField(state, "STYLE_BOLD", fonts.styleBold);
|
||||
lua_setglobal(state, "gui");
|
||||
lua_setglobal(state, "screen");
|
||||
}
|
||||
|
||||
} // namespace bindings
|
||||
@@ -1,4 +1,4 @@
|
||||
// @lua-module node NodeLib
|
||||
// @lua-module tree TreeLib
|
||||
// @lua-preamble ---@alias NodeId integer
|
||||
// @lua-preamble ---@alias NodeType "box"|"text"|"button"|"custom"
|
||||
// @lua-preamble ---@alias NodeDirection "up"|"down"|"left"|"right"
|
||||
@@ -16,27 +16,27 @@
|
||||
// @lua-preamble ---@field capture? boolean
|
||||
// @lua-preamble ---@field interactive? boolean
|
||||
// @lua-preamble ---@field label? string
|
||||
// @lua-preamble ---@field font? GuiFont
|
||||
// @lua-preamble ---@field font? ScreenFont
|
||||
// @lua-preamble
|
||||
// @lua-preamble ---@class NodeStyle
|
||||
// @lua-preamble ---@field color? GuiColor
|
||||
// @lua-preamble ---@field background? GuiColor Background offered to
|
||||
// @lua-preamble ---@field color? ScreenColor
|
||||
// @lua-preamble ---@field background? ScreenColor Background offered to
|
||||
// descendants.
|
||||
// @lua-preamble ---@field fill? GuiColor Surface painted by a box.
|
||||
// @lua-preamble ---@field border? GuiColor
|
||||
// @lua-preamble ---@field face? GuiColor Default button surface.
|
||||
// @lua-preamble ---@field pressedFace? GuiColor Pressed button surface.
|
||||
// @lua-preamble ---@field pressedColor? GuiColor Pressed button text.
|
||||
// @lua-preamble ---@field focusColor? GuiColor Distinct outline for directional
|
||||
// focus.
|
||||
// @lua-preamble ---@field fill? ScreenColor Surface painted by a box.
|
||||
// @lua-preamble ---@field border? ScreenColor
|
||||
// @lua-preamble ---@field face? ScreenColor Default button surface.
|
||||
// @lua-preamble ---@field pressedFace? ScreenColor Pressed button surface.
|
||||
// @lua-preamble ---@field pressedColor? ScreenColor Pressed button text.
|
||||
// @lua-preamble ---@field focusColor? ScreenColor Distinct outline for
|
||||
// directional focus.
|
||||
// @lua-preamble ---@field radius? integer
|
||||
// @lua-preamble ---@field font? GuiFont
|
||||
// @lua-preamble ---@field textStyle? GuiTextStyle
|
||||
// @lua-preamble ---@field font? ScreenFont
|
||||
// @lua-preamble ---@field textStyle? ScreenTextStyle
|
||||
|
||||
#include <cstdlib>
|
||||
|
||||
#include "../../node/painter.h"
|
||||
#include "../helpers.h"
|
||||
#include "../../../node/painter.h"
|
||||
#include "../../helpers.h"
|
||||
|
||||
namespace esp32lua {
|
||||
namespace bindings {
|
||||
@@ -616,9 +616,9 @@ const luaL_Reg FUNCTIONS[] = {
|
||||
|
||||
} // namespace
|
||||
|
||||
void registerNode(lua_State* state) {
|
||||
void registerTree(lua_State* state) {
|
||||
luaL_newlib(state, FUNCTIONS);
|
||||
lua_setglobal(state, "node");
|
||||
lua_setglobal(state, "tree");
|
||||
}
|
||||
|
||||
} // namespace bindings
|
||||
@@ -40,8 +40,21 @@ int isTouched(lua_State* state) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
// @lua-augment settings SettingsLib
|
||||
const luaL_Reg SETTINGS_FUNCTIONS[] = {
|
||||
// @lua-module touch TouchLib
|
||||
const luaL_Reg FUNCTIONS[] = {
|
||||
// --- Returns the calibrated touch point, or nothing when the panel is not
|
||||
// touched.
|
||||
// @return integer|nil x
|
||||
// @return integer|nil y
|
||||
{"getPoint", getTouch},
|
||||
// --- Returns the uncalibrated touch reading, or nothing when the panel is
|
||||
// not touched.
|
||||
// @return integer|nil x
|
||||
// @return integer|nil y
|
||||
{"getRawPoint", getRawTouch},
|
||||
// --- Whether the panel is currently touched.
|
||||
// @return boolean
|
||||
{"isTouched", isTouched},
|
||||
// --- Persists the panel's touch calibration.
|
||||
// @param x0 integer Raw reading at the left edge.
|
||||
// @param y0 integer Raw reading at the top edge.
|
||||
@@ -53,29 +66,11 @@ const luaL_Reg SETTINGS_FUNCTIONS[] = {
|
||||
{nullptr, nullptr},
|
||||
};
|
||||
|
||||
// @lua-augment input InputLib
|
||||
const luaL_Reg INPUT_FUNCTIONS[] = {
|
||||
// --- Returns the calibrated touch point, or nothing when the panel is not
|
||||
// touched.
|
||||
// @return integer|nil x
|
||||
// @return integer|nil y
|
||||
{"getTouch", getTouch},
|
||||
// --- Returns the uncalibrated touch reading, or nothing when the panel is
|
||||
// not touched.
|
||||
// @return integer|nil x
|
||||
// @return integer|nil y
|
||||
{"getRawTouch", getRawTouch},
|
||||
// --- Whether the panel is currently touched.
|
||||
// @return boolean
|
||||
{"isTouched", isTouched},
|
||||
{nullptr, nullptr},
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
void registerTouch(lua_State* state) {
|
||||
augmentGlobal(state, "settings", SETTINGS_FUNCTIONS);
|
||||
augmentGlobal(state, "input", INPUT_FUNCTIONS);
|
||||
luaL_newlib(state, FUNCTIONS);
|
||||
lua_setglobal(state, "touch");
|
||||
}
|
||||
|
||||
} // namespace bindings
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -11,12 +11,11 @@ namespace bindings {
|
||||
void registerBle(lua_State* state);
|
||||
void registerButtons(lua_State* state);
|
||||
void registerFs(lua_State* state);
|
||||
void registerGui(lua_State* state);
|
||||
void registerHttp(lua_State* state);
|
||||
void registerLog(lua_State* state);
|
||||
void registerNode(lua_State* state);
|
||||
void registerSettings(lua_State* state);
|
||||
void registerScreen(lua_State* state);
|
||||
void registerSys(lua_State* state);
|
||||
void registerTree(lua_State* state);
|
||||
void registerTimer(lua_State* state);
|
||||
void registerTouch(lua_State* state);
|
||||
void registerWifi(lua_State* state);
|
||||
@@ -52,9 +51,9 @@ Runtime::~Runtime() { close(); }
|
||||
bool Runtime::open() {
|
||||
if (state_)
|
||||
return true;
|
||||
if (!providers_.log || !providers_.settings || !providers_.sys ||
|
||||
!providers_.fs || !providers_.gui || !providers_.http ||
|
||||
!providers_.timer || !providers_.wifi || !providers_.ble) {
|
||||
if (!providers_.log || !providers_.sys || !providers_.fs ||
|
||||
!providers_.http || !providers_.timer || !providers_.wifi ||
|
||||
!providers_.ble) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -64,19 +63,20 @@ bool Runtime::open() {
|
||||
*static_cast<Runtime**>(lua_getextraspace(state_)) = this;
|
||||
luaL_openlibs(state_);
|
||||
|
||||
// Primary Namespaces
|
||||
bindings::registerBle(state_);
|
||||
bindings::registerFs(state_);
|
||||
bindings::registerGui(state_);
|
||||
bindings::registerHttp(state_);
|
||||
bindings::registerLog(state_);
|
||||
bindings::registerNode(state_);
|
||||
bindings::registerSettings(state_);
|
||||
bindings::registerSys(state_);
|
||||
bindings::registerTimer(state_);
|
||||
bindings::registerWifi(state_);
|
||||
|
||||
// Feature namespaces extend the tables the core registrations just created,
|
||||
// so they always follow them.
|
||||
// Feature Namespaces
|
||||
if (providers_.gui) {
|
||||
bindings::registerScreen(state_);
|
||||
bindings::registerTree(state_);
|
||||
}
|
||||
if (providers_.touch)
|
||||
bindings::registerTouch(state_);
|
||||
if (providers_.buttons)
|
||||
@@ -90,9 +90,7 @@ void Runtime::close() {
|
||||
cancelAllTimers();
|
||||
lua_close(state_);
|
||||
state_ = nullptr;
|
||||
mainRef_ = 0; // the registry it referenced went with the state
|
||||
// Node handles mean nothing to the next lua_State, so an app that inherited
|
||||
// the previous tree would build onto its nodes.
|
||||
mainRef_ = 0;
|
||||
tree_.reset();
|
||||
appPath_.clear();
|
||||
appTitle_.clear();
|
||||
@@ -103,7 +101,7 @@ Runtime::Batch::Batch(Runtime& runtime) : runtime_(runtime) {
|
||||
}
|
||||
|
||||
Runtime::Batch::~Batch() {
|
||||
if (--runtime_.batchDepth_ == 0)
|
||||
if (--runtime_.batchDepth_ == 0 && runtime_.providers_.gui)
|
||||
runtime_.providers_.gui->commit();
|
||||
}
|
||||
|
||||
@@ -319,6 +317,8 @@ std::string Runtime::appDataPath() const {
|
||||
}
|
||||
|
||||
bool Runtime::hasFeature(const std::string& feature) const {
|
||||
if (feature == "screen")
|
||||
return providers_.gui != nullptr;
|
||||
if (feature == "touch")
|
||||
return providers_.touch != nullptr;
|
||||
if (feature == "buttons")
|
||||
|
||||
@@ -23,34 +23,19 @@ struct Log : LogProvider {
|
||||
}
|
||||
};
|
||||
|
||||
struct Settings : SettingsProvider {
|
||||
int32_t degrees = 0;
|
||||
std::string tz = "UTC0";
|
||||
std::string themeName = "light";
|
||||
int32_t rotation() const override { return degrees; }
|
||||
Status setRotation(int32_t value) override {
|
||||
degrees = value;
|
||||
return Status::success();
|
||||
}
|
||||
std::string timezone() const override { return tz; }
|
||||
Status setTimezone(const std::string& value) override {
|
||||
tz = value;
|
||||
return Status::success();
|
||||
}
|
||||
std::string theme() const override { return themeName; }
|
||||
Status setTheme(const std::string& value) override {
|
||||
themeName = value;
|
||||
return Status::success();
|
||||
}
|
||||
};
|
||||
|
||||
struct Sys : SysProvider {
|
||||
std::string tz = "UTC0";
|
||||
int32_t millis() const override { return 1234; }
|
||||
MemoryInfo memory() const override {
|
||||
const MemoryInfo info = {100, 200, 50};
|
||||
return info;
|
||||
}
|
||||
bool isClockSynced() const override { return true; }
|
||||
std::string timezone() const override { return tz; }
|
||||
Status setTimezone(const std::string& value) override {
|
||||
tz = value;
|
||||
return Status::success();
|
||||
}
|
||||
};
|
||||
|
||||
struct Fs : FsProvider {
|
||||
@@ -154,6 +139,7 @@ struct Fs : FsProvider {
|
||||
struct Gui : GuiProvider {
|
||||
std::string trace;
|
||||
int32_t degrees = 0;
|
||||
std::string themeName = "light";
|
||||
bool gradient = false;
|
||||
|
||||
FontIds fonts() const override {
|
||||
@@ -163,7 +149,15 @@ struct Gui : GuiProvider {
|
||||
int32_t width() const override { return 320; }
|
||||
int32_t height() const override { return 240; }
|
||||
int32_t rotation() const override { return degrees; }
|
||||
void setRotation(int32_t value) override { degrees = value; }
|
||||
Status setRotation(int32_t value) override {
|
||||
degrees = value;
|
||||
return Status::success();
|
||||
}
|
||||
std::string theme() const override { return themeName; }
|
||||
Status setTheme(const std::string& value) override {
|
||||
themeName = value;
|
||||
return Status::success();
|
||||
}
|
||||
int32_t color(int32_t r, int32_t g, int32_t b) const override {
|
||||
return (r << 16) | (g << 8) | b;
|
||||
}
|
||||
@@ -357,7 +351,6 @@ struct Buttons : ButtonsProvider {
|
||||
// Every provider a Runtime needs, so a test names only what it asserts on.
|
||||
struct Bench {
|
||||
Log log;
|
||||
Settings settings;
|
||||
Sys sys;
|
||||
Fs fs;
|
||||
Gui gui;
|
||||
@@ -371,7 +364,6 @@ struct Bench {
|
||||
Providers providers() {
|
||||
Providers providers;
|
||||
providers.log = &log;
|
||||
providers.settings = &settings;
|
||||
providers.sys = &sys;
|
||||
providers.fs = &fs;
|
||||
providers.gui = &gui;
|
||||
|
||||
@@ -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