-- Run: lua test/ui_theme.lua -- Asserts the palette ui.lua derives from a theme's three seed colors. package.path = "sdcard/lib/?.lua;" .. package.path local themeName = "light" -- Packing the channels keeps them recoverable, so contrast is checkable. gui = { color = function(r, g, b) return r * 65536 + g * 256 + b end, width = function() return 320 end, height = function() return 480 end, clear = function() end, fillRect = function() end, drawText = function() end, roundRect = function() end, fontHeight = function() return 8 end, textWidth = function(text) return #text * 6 end, } sys = {millis = function() return 0 end, getTheme = function() return themeName end} local ui = require("ui") local BLACK, WHITE = gui.color(0, 0, 0), gui.color(255, 255, 255) local function reload(name) themeName = 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") -- The root seeds the tree, so an app naming no colors still gets themed. reload("dark") local button = ui.button{label = "go", on_press = function() end} local screen = ui.screen(ui.box{pad = 12, button}) assert(screen.root.bg == ui.theme.bg, "root takes the theme surface") assert(screen.root.color == ui.theme.fg, "root takes the theme text") screen:draw() assert(button.color == ui.theme.fg, "the button inherited the theme") assert(button.press_bg == ui.theme.accent, "the button inherited the accent") -- An explicit value still wins over the theme. local custom = ui.button{label = "delete", press_bg = 123, on_press = function() end} ui.screen(ui.box{pad = 12, custom}):draw() assert(custom.press_bg == 123, "explicit styling overrides the theme") print("ok")