Initial flame_lua_runtime package

This commit is contained in:
gem
2026-06-07 22:53:58 +08:00
commit 733b2fb798
262 changed files with 28439 additions and 0 deletions

View File

@@ -0,0 +1,292 @@
---@alias RuntimeLayoutAlign 'start'|'center'|'end'
---@class (exact) RuntimeLayoutItem
---@field node RuntimeNode
---@field marginLeft? number
---@field marginRight? number
---@field marginTop? number
---@field marginBottom? number
---@class RuntimeLayoutItemOpts
---@field margin? number
---@field mx? number
---@field my? number
---@field ml? number
---@field mr? number
---@field mt? number
---@field mb? number
---@field marginLeft? number
---@field marginRight? number
---@field marginTop? number
---@field marginBottom? number
---@class RuntimeLinearLayoutOpts
---@field x? number
---@field y? number
---@field width? number
---@field height? number
---@field gap? number
---@field align? RuntimeLayoutAlign
---@field padding? number
---@field paddingX? number
---@field paddingY? number
---@field px? number
---@field py? number
---@field paddingLeft? number
---@field paddingTop? number
---@class RuntimeBoxLayoutOpts: RuntimeLinearLayoutOpts
---@field rows? integer
---@field columns? integer
---@field cols? integer
---@field cellWidth? number
---@field cellHeight? number
---@field cellW? number
---@field cellH? number
---@field gapX? number
---@field gapY? number
---@field valign? RuntimeLayoutAlign
---@class RuntimeLayout
local layout = {}
---@param value any
---@param fallback number
---@return number
local function read_number(value, fallback)
if type(value) == "number" then
return value
end
return fallback
end
---@param opts? table
---@param key string
---@return number
local function spacing(opts, key)
opts = opts or {}
if key == "left" then
return read_number(opts.paddingLeft, read_number(opts.paddingX, read_number(opts.px, read_number(opts.padding, 0))))
end
if key == "top" then
return read_number(opts.paddingTop, read_number(opts.paddingY, read_number(opts.py, read_number(opts.padding, 0))))
end
return 0
end
---@param node RuntimeNode
---@param parent? string
local function apply_parent(node, parent)
if parent ~= nil and parent ~= "" then
node.parent = parent
elseif parent == "" then
node.parent = nil
end
end
---@param axis_size number
---@param item_size number
---@param align RuntimeLayoutAlign
---@return number
local function align_cross(axis_size, item_size, align)
if align == "center" then
return (axis_size - item_size) / 2
end
if align == "end" then
return axis_size - item_size
end
return 0
end
---@param value any
---@return RuntimeNode
local function node_of(value)
return value.node or value
end
---@param value any
---@param key string
---@return number
local function margin_of(value, key)
local all = read_number(value.margin, 0)
if key == "marginLeft" then
return read_number(value.marginLeft, read_number(value.ml, read_number(value.mx, all)))
end
if key == "marginRight" then
return read_number(value.marginRight, read_number(value.mr, read_number(value.mx, all)))
end
if key == "marginTop" then
return read_number(value.marginTop, read_number(value.mt, read_number(value.my, all)))
end
if key == "marginBottom" then
return read_number(value.marginBottom, read_number(value.mb, read_number(value.my, all)))
end
return 0
end
---@param node RuntimeNode
---@param opts? RuntimeLayoutItemOpts
---@return RuntimeLayoutItem
function layout.item(node, opts)
opts = opts or {}
return {
node = node,
marginLeft = margin_of(opts, "marginLeft"),
marginRight = margin_of(opts, "marginRight"),
marginTop = margin_of(opts, "marginTop"),
marginBottom = margin_of(opts, "marginBottom")
}
end
---@param origin RuntimePoint
---@param position RuntimePoint
---@return RuntimePoint
function layout.local_position(origin, position)
return {
x = position.x - origin.x,
y = position.y - origin.y
}
end
---@param parent? string
---@param items (RuntimeNode|RuntimeLayoutItem)[]
---@param opts? RuntimeLinearLayoutOpts
---@return RuntimeNode[]
function layout.row(parent, items, opts)
opts = opts or {}
local cursor = read_number(opts.x, 0) + spacing(opts, "left")
local base_y = read_number(opts.y, 0) + spacing(opts, "top")
local gap = read_number(opts.gap, 0)
local height = read_number(opts.height, 0)
local align = opts.align or "start"
local nodes = {}
for index, item in ipairs(items) do
local node = node_of(item)
cursor = cursor + margin_of(item, "marginLeft")
node.x = cursor
local item_height = read_number(node.height, 0)
node.y = base_y + align_cross(height, item_height, align)
apply_parent(node, parent)
cursor = cursor + read_number(node.width, 0) + margin_of(item, "marginRight") + gap
nodes[index] = node
end
return nodes
end
---@param parent? string
---@param items (RuntimeNode|RuntimeLayoutItem)[]
---@param opts? RuntimeLinearLayoutOpts
---@return RuntimeNode[]
function layout.column(parent, items, opts)
opts = opts or {}
local cursor = read_number(opts.y, 0) + spacing(opts, "top")
local base_x = read_number(opts.x, 0) + spacing(opts, "left")
local gap = read_number(opts.gap, 0)
local width = read_number(opts.width, 0)
local align = opts.align or "start"
local nodes = {}
for index, item in ipairs(items) do
local node = node_of(item)
cursor = cursor + margin_of(item, "marginTop")
node.y = cursor
local item_width = read_number(node.width, 0)
node.x = base_x + align_cross(width, item_width, align)
apply_parent(node, parent)
cursor = cursor + read_number(node.height, 0) + margin_of(item, "marginBottom") + gap
nodes[index] = node
end
return nodes
end
---@param parent? string
---@param items (RuntimeNode|RuntimeLayoutItem)[]
---@param opts? RuntimeLinearLayoutOpts
---@return RuntimeNode[]
function layout.stack(parent, items, opts)
opts = opts or {}
local offset_x = read_number(opts.x, 0) + spacing(opts, "left")
local offset_y = read_number(opts.y, 0) + spacing(opts, "top")
local nodes = {}
for index, item in ipairs(items) do
local node = node_of(item)
node.x = read_number(node.x, 0) + offset_x
node.y = read_number(node.y, 0) + offset_y
apply_parent(node, parent)
nodes[index] = node
end
return nodes
end
---@param parent? string
---@param items (RuntimeNode|RuntimeLayoutItem)[]
---@param opts? RuntimeBoxLayoutOpts
---@return RuntimeNode[]
function layout.box(parent, items, opts)
opts = opts or {}
local count = #items
local rows = math.floor(read_number(opts.rows, 0))
local columns = math.floor(read_number(opts.columns, read_number(opts.cols, 0)))
if columns <= 0 and rows > 0 then
columns = math.ceil(count / rows)
end
if columns <= 0 then
columns = count > 0 and count or 1
end
if rows <= 0 then
rows = math.ceil(count / columns)
end
local gap_x = read_number(opts.gapX, read_number(opts.gap, 0))
local gap_y = read_number(opts.gapY, read_number(opts.gap, 0))
local origin_x = read_number(opts.x, 0) + spacing(opts, "left")
local origin_y = read_number(opts.y, 0) + spacing(opts, "top")
local cell_w = read_number(opts.cellWidth, read_number(opts.cellW, 0))
local cell_h = read_number(opts.cellHeight, read_number(opts.cellH, 0))
local width = read_number(opts.width, 0)
local height = read_number(opts.height, 0)
if cell_w <= 0 and width > 0 then
cell_w = (width - gap_x * (columns - 1)) / columns
end
if cell_h <= 0 and height > 0 then
cell_h = (height - gap_y * (rows - 1)) / rows
end
local align = opts.align or "start"
local valign = opts.valign or "start"
local nodes = {}
for index, item in ipairs(items) do
local node = node_of(item)
local item_width = read_number(node.width, cell_w)
local item_height = read_number(node.height, cell_h)
local effective_cell_w = cell_w > 0 and cell_w or item_width
local effective_cell_h = cell_h > 0 and cell_h or item_height
local col = (index - 1) % columns
local row = math.floor((index - 1) / columns)
local margin_left = margin_of(item, "marginLeft")
local margin_right = margin_of(item, "marginRight")
local margin_top = margin_of(item, "marginTop")
local margin_bottom = margin_of(item, "marginBottom")
local inner_w = effective_cell_w - margin_left - margin_right
local inner_h = effective_cell_h - margin_top - margin_bottom
node.x = origin_x + col * (effective_cell_w + gap_x) + margin_left + align_cross(inner_w, item_width, align)
node.y = origin_y + row * (effective_cell_h + gap_y) + margin_top + align_cross(inner_h, item_height, valign)
apply_parent(node, parent)
nodes[index] = node
end
return nodes
end
return layout