Initial flame_lua_runtime package
This commit is contained in:
292
assets/runtime/lua/layout.lua
Normal file
292
assets/runtime/lua/layout.lua
Normal 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
|
||||
Reference in New Issue
Block a user