291f0f20b5
The Lua modules were split between tabs and spaces because nothing pinned a style; .editorconfig is what lua-language-server reads on save, so the editor and the tree now agree. clang-format already had a config and left native/ unchanged. Embedded modules regenerate from the reformatted ui.lua.
275 lines
6.8 KiB
Lua
275 lines
6.8 KiB
Lua
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"
|