package.path = "./lua/lib/?.lua;" .. package.path local files = {} local cleared, invalidated = nil, {} fs = { readFile = function(path, maxBytes) local value = files[path] if value and #value > maxBytes then return nil, "too large" end return value, value and nil or "missing" end, writeFile = function(path, value) files[path] = value return true end, } local frameWidth, frameHeight = 320, 480 gui = { FONT_SMALL = 0, FONT_UI = 1, FONT_BODY = 2, FONT_LARGE = 3, STYLE_NORMAL = 0, STYLE_BOLD = 1, color = function(r, g, b) return r * 65536 + g * 256 + b end, getWidth = function() return frameWidth end, getHeight = function() return frameHeight end, getTextWidth = function(_, text) return #text * 6 end, getFontHeight = function() return 8 end, clear = function(color) cleared = color end, } local nodes, focus, painter = {}, nil, nil local buttonCount = 0 local function interactiveNodes() local result = {} for id, entry in ipairs(nodes) do if entry.interactive then result[#result + 1] = id end end return result end node = { reset = function() nodes, focus, buttonCount = {}, nil, 0 end, create = function(parent, spec) local id = #nodes + 1 local x = 0 if spec.type == "button" then buttonCount = buttonCount + 1 x = (buttonCount - 1) * 100 end nodes[id] = { parent = parent, type = spec.type, label = spec.label, interactive = spec.interactive, rect = { x, 0, 90, 50 }, pressed = false, style = {}, } return id end, attach = function(parent, child) nodes[child].parent = parent end, setSize = function() end, setStyle = function(id, style) for key, value in pairs(style) do nodes[id].style[key] = value end end, layout = function() return true end, dropScratch = function() end, hit = function(_, x, y) for _, id in ipairs(interactiveNodes()) do local rect = nodes[id].rect if x >= rect[1] and x < rect[1] + rect[3] and y >= rect[2] and y < rect[2] + rect[4] then return id end end end, getRect = function(id) return table.unpack(nodes[id].rect) end, setLabel = function(id, text) nodes[id].label = text end, getLabel = function(id) return nodes[id].label end, invalidate = function(id) invalidated[#invalidated + 1] = id end, setPressed = function(id, pressed) nodes[id].pressed = pressed end, isPressed = function(id) return nodes[id].pressed end, setPainter = function(callback) painter = callback end, draw = function() if painter then for id, entry in ipairs(nodes) do if entry.type == "custom" then painter(id, table.unpack(entry.rect)) end end end end, focusFirst = function() focus = interactiveNodes()[1] return focus end, setFocus = function(id) focus = id end, getFocus = function() return focus end, moveFocus = function(_, direction) local items, current = interactiveNodes(), nil for index, id in ipairs(items) do if id == focus then current = index break end end local step = (direction == "right" or direction == "down") and 1 or -1 local nextIndex = current and current + step or 1 if items[nextIndex] then focus = items[nextIndex] end return focus end, } local ui = require "ui" assert(ui.getTheme() == "light") assert(table.concat(ui.themeNames(), ",") == "dark,light,mono") local ok, err = ui.setTheme "missing" assert(ok == nil and err == "Unknown theme") assert(ui.setTheme "dark" == true) assert(files["/.lua/theme"] == "dark" and ui.getTheme() == "dark") local events = {} local function handler(name) return function(id, x, y) events[#events + 1] = { name, id, x, y } end end local first = ui.button { label = "one", on_enter = handler "enter", on_exit = handler "exit", on_click = handler "click", } local second = ui.button { label = "two", on_enter = handler "enter", on_exit = handler "exit", on_click = handler "click", } local screen = ui.screen(ui.box { row = true, first, second }) assert(screen:down(10, 10)) assert(node.isPressed(first)) assert(screen:move(95, 10)) assert(not node.isPressed(first)) assert(screen:move(10, 10)) assert(node.isPressed(first)) assert(screen:up(10, 10)) assert(not node.isPressed(first)) local expectedTouch = { "enter", "exit", "enter", "exit", "click" } for index, name in ipairs(expectedTouch) do local event = events[index] assert(event and event[1] == name and event[2] == first) assert(event[3] == 10 or event[3] == 95) assert(event[4] == 10) end events = {} assert(screen:button("right", true)) assert(screen:button("right", false)) assert(node.getFocus() == first) assert(screen:button("right", true)) assert(node.getFocus() == second) assert(screen:button("confirm", true)) assert(node.isPressed(second)) assert(screen:button("confirm", false)) assert(not node.isPressed(second)) assert(screen:button("back", true) == false) local expectedButtons = { { "enter", first }, { "exit", first }, { "enter", second }, { "click", second }, } for index, expected in ipairs(expectedButtons) do local event = events[index] assert(event and event[1] == expected[1] and event[2] == expected[2]) assert(event[3] == nil and event[4] == nil) end local before = #invalidated assert(ui.setTheme "mono" == true) assert(ui.getTheme() == "mono" and #invalidated == before + 1) assert(cleared == ui.theme.background) screen:draw() -- 320x480 portrait: two columns, and the reserve comes off the height budget. local side, columns = ui.cardSide(5, 12, 8) assert(columns == 2 and side == 144, "width is the binding constraint in portrait") assert(select(1, ui.cardSide(5, 12, 8, 300)) == 46, "height binds once the reserve is large") frameWidth, frameHeight = 480, 320 side, columns = ui.cardSide(5, 12, 8) assert(columns == 3 and side == 144, "landscape uses three columns") frameWidth, frameHeight = 320, 480 -- A widget painting its own feedback is never given a pressed style, while an ordinary one -- still is. Which node was hit is geometry, so the test names the target directly. ui.reset() local pressedCalls = {} node.setPressed = function(_, on) pressedCalls[#pressedCalls + 1] = on end local own = ui.custom { h = 20, press_style = false, on_click = function() end } local styled = ui.button { h = 20, label = "ok", on_click = function() end } local board = ui.screen(ui.box { own, styled }) local target node.hit = function() return target end target = own board:down(0, 0) board:up(0, 0) assert(#pressedCalls == 0, "a self-painting widget is not styled on press") target = styled board:down(0, 0) assert(pressedCalls[1] == true, "an ordinary widget still gets its pressed style") print "ok"