f7c5cc09ba
A node was a Lua table of ~625 bytes, of which 21 keys pushed it over a
power-of-two hash boundary and eight were style copies inheritance had
splattered down from its parent. A 400 node screen cost ~250 KB and could not
coexist with wifi's buffers.
The tree now lives in src/ui/layout.h as a 16 byte struct in a flat arena, and
splits by lifetime: Node holds what hit testing and repainting need forever,
Spec holds what only measure/place read and is dropped when layout ends. Style
is sparse and resolved by walking parents, so a node naming no colours costs
nothing. Re-layout rebuilds from Lua rather than retaining the inputs.
401 nodes: 8218 B steady, 21050 B peak
Lua: ~250000 B steady
sdcard/lib/ui.lua stays the toolkit and keeps every constructor signature, but
returns integer handles: 627 lines to 374. Composition, the palette and custom
painters are still Lua on the SD card; only primitives now need a reflash.
BREAKING CHANGE: ui constructors return handles, not tables. Use
ui.setText(id, text) and keep per-node app data in a table keyed by id.
58 lines
2.5 KiB
Lua
58 lines
2.5 KiB
Lua
-- Run: lua test/ui_theme.lua
|
|
-- Asserts the palette ui.lua derives from a theme's three seed colors. The fake packs
|
|
-- channels into one integer, so contrast between roles stays checkable.
|
|
package.path = "sdcard/lib/?.lua;test/?.lua;" .. package.path
|
|
|
|
local device = require("fake_device").install()
|
|
local ui = require("ui")
|
|
|
|
local BLACK, WHITE = gui.color(0, 0, 0), gui.color(255, 255, 255)
|
|
|
|
local function reload(name)
|
|
device.theme = name
|
|
return ui.reloadTheme()
|
|
end
|
|
|
|
-- Seeds pass through untouched.
|
|
local light = reload("light")
|
|
assert(light.bg == WHITE, "light bg")
|
|
assert(light.fg == BLACK, "light fg")
|
|
assert(light.accent == gui.color(0, 120, 255), "light accent")
|
|
|
|
-- Muted sits between the text and the surface, so it reads as secondary on both.
|
|
assert(light.muted > BLACK and light.muted < WHITE, "muted between fg and bg")
|
|
|
|
-- Contrast is derived, never configured: text on a dark accent must be light.
|
|
assert(light.accent_fg == WHITE, "white text on the blue accent")
|
|
assert(reload("mono").accent_fg == WHITE, "white text on the black accent")
|
|
|
|
-- A pale accent has to flip the label to black; no theme file can get this wrong.
|
|
local pale = ui.themeNames() -- force the module to be loaded before patching it
|
|
assert(#pale >= 3, "expected the canned themes")
|
|
package.loaded["theme"].pale = {bg = {255, 255, 255}, fg = {0, 0, 0}, accent = {255, 235, 120}}
|
|
assert(reload("pale").accent_fg == BLACK, "black text on a pale accent")
|
|
|
|
-- Dark inverts the surface without any per-component configuration.
|
|
local dark = reload("dark")
|
|
assert(dark.bg < dark.fg, "dark theme keeps light text on a dark surface")
|
|
assert(#dark.face == 2 and #dark.face_pressed == 2, "button gradients are pairs")
|
|
|
|
-- Pinned roles win over derived ones, which is the deviation hatch.
|
|
assert(reload("mono").radius == 0, "mono pins radius")
|
|
assert(light.radius == 6, "light keeps the derived radius")
|
|
|
|
-- An unknown name must not leave an app without colors.
|
|
local missing = reload("does-not-exist")
|
|
assert(missing.bg == WHITE and missing.fg == BLACK, "unknown theme falls back to light")
|
|
|
|
-- Dimming re-derives every role rather than blending pixels, because the panel has no
|
|
-- alpha to composite a scrim with.
|
|
reload("light")
|
|
assert(ui.theme.dim.bg ~= ui.theme.bg, "the dim palette matches the lit one")
|
|
assert(ui.theme.dim.muted ~= ui.theme.muted, "dimming did not reach derived roles")
|
|
|
|
-- Which node ends up wearing which role is inheritance, and inheritance is the firmware's
|
|
-- half: test/ui_layout_test.cpp asserts it against the same code the panel runs.
|
|
|
|
print("ok")
|