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
|
||||
261
assets/runtime/lua/runtime_commands.lua
Normal file
261
assets/runtime/lua/runtime_commands.lua
Normal file
@@ -0,0 +1,261 @@
|
||||
---@class (exact) RuntimeCommandOpts
|
||||
---@field id? string
|
||||
---@field group? string
|
||||
---@field commandGroup? string
|
||||
---@field scope? string
|
||||
---@field onComplete? string
|
||||
---@field duration? number
|
||||
|
||||
---@class (exact) RuntimeAudioCommandOpts: RuntimeCommandOpts
|
||||
---@field volume? number
|
||||
|
||||
---@class (exact) RuntimeBgmCommandOpts: RuntimeAudioCommandOpts
|
||||
---@field channel? string
|
||||
---@field loop? boolean
|
||||
|
||||
---@class (exact) RuntimeSpineCommandOpts: RuntimeCommandOpts
|
||||
---@field track? integer
|
||||
---@field loop? boolean
|
||||
---@field queue? boolean
|
||||
---@field delay? number
|
||||
|
||||
---@class (exact) RuntimeResourceCommandOpts: RuntimeCommandOpts
|
||||
---@field failOnError? boolean
|
||||
|
||||
---@class RuntimeCommands
|
||||
local commands = {}
|
||||
|
||||
---@param opts? table
|
||||
---@return table
|
||||
local function copy_opts(opts)
|
||||
local result = {}
|
||||
if opts ~= nil then
|
||||
for key, value in pairs(opts) do
|
||||
result[key] = value
|
||||
end
|
||||
end
|
||||
return result
|
||||
end
|
||||
|
||||
---@param command_type RuntimeCommandType
|
||||
---@param opts? table
|
||||
---@return RuntimeCommand
|
||||
local function with_type(command_type, opts)
|
||||
local command = copy_opts(opts)
|
||||
command.type = command_type
|
||||
return command
|
||||
end
|
||||
|
||||
---@param text string
|
||||
---@param opts? RuntimeCommandOpts
|
||||
---@return RuntimeCommand
|
||||
function commands.toast(text, opts)
|
||||
local command = with_type("toast", opts)
|
||||
command.text = text
|
||||
return command
|
||||
end
|
||||
|
||||
---@param text string
|
||||
---@param opts? RuntimeCommandOpts
|
||||
---@return RuntimeCommand
|
||||
function commands.copy_text(text, opts)
|
||||
local command = with_type("copy_text", opts)
|
||||
command.text = text
|
||||
return command
|
||||
end
|
||||
|
||||
---@param duration number
|
||||
---@param opts? RuntimeCommandOpts
|
||||
---@return RuntimeCommand
|
||||
function commands.delay(duration, opts)
|
||||
local command = with_type("delay", opts)
|
||||
command.duration = duration
|
||||
return command
|
||||
end
|
||||
|
||||
---@param items RuntimeCommand[]
|
||||
---@param opts? RuntimeCommandOpts
|
||||
---@return RuntimeCommand
|
||||
function commands.sequence(items, opts)
|
||||
local command = with_type("sequence", opts)
|
||||
command.commands = items or {}
|
||||
return command
|
||||
end
|
||||
|
||||
---@param items RuntimeCommand[]
|
||||
---@param opts? RuntimeCommandOpts
|
||||
---@return RuntimeCommand
|
||||
function commands.parallel(items, opts)
|
||||
local command = with_type("parallel", opts)
|
||||
command.commands = items or {}
|
||||
return command
|
||||
end
|
||||
|
||||
---@param target string
|
||||
---@param path RuntimePoint[]
|
||||
---@param opts? RuntimeCommandOpts
|
||||
---@return RuntimeCommand
|
||||
function commands.move_path(target, path, opts)
|
||||
local command = with_type("move_path", opts)
|
||||
command.target = target
|
||||
command.path = path or {}
|
||||
return command
|
||||
end
|
||||
|
||||
---@param target string
|
||||
---@param x number
|
||||
---@param y number
|
||||
---@param opts? RuntimeCommandOpts
|
||||
---@return RuntimeCommand
|
||||
function commands.move_to(target, x, y, opts)
|
||||
local command = with_type("move_to", opts)
|
||||
command.target = target
|
||||
command.x = x
|
||||
command.y = y
|
||||
return command
|
||||
end
|
||||
|
||||
---@param target string
|
||||
---@param alpha number
|
||||
---@param opts? RuntimeCommandOpts
|
||||
---@return RuntimeCommand
|
||||
function commands.fade_to(target, alpha, opts)
|
||||
local command = with_type("fade_to", opts)
|
||||
command.target = target
|
||||
command.alpha = alpha
|
||||
return command
|
||||
end
|
||||
|
||||
---@param target string
|
||||
---@param scale number
|
||||
---@param opts? RuntimeCommandOpts
|
||||
---@return RuntimeCommand
|
||||
function commands.scale_to(target, scale, opts)
|
||||
local command = with_type("scale_to", opts)
|
||||
command.target = target
|
||||
command.scale = scale
|
||||
return command
|
||||
end
|
||||
|
||||
---@param target string
|
||||
---@param angle number
|
||||
---@param opts? RuntimeCommandOpts
|
||||
---@return RuntimeCommand
|
||||
function commands.rotate_to(target, angle, opts)
|
||||
local command = with_type("rotate_to", opts)
|
||||
command.target = target
|
||||
command.angle = angle
|
||||
return command
|
||||
end
|
||||
|
||||
---@param target string
|
||||
---@param opts? RuntimeCommandOpts
|
||||
---@return RuntimeCommand
|
||||
function commands.remove_node(target, opts)
|
||||
local command = with_type("remove_node", opts)
|
||||
command.target = target
|
||||
return command
|
||||
end
|
||||
|
||||
---@param target string
|
||||
---@param animation string
|
||||
---@param opts? RuntimeSpineCommandOpts
|
||||
---@return RuntimeCommand
|
||||
function commands.play_spine_animation(target, animation, opts)
|
||||
local command = with_type("play_spine_animation", opts)
|
||||
command.target = target
|
||||
command.animation = animation
|
||||
if command.loop == nil then
|
||||
command.loop = true
|
||||
end
|
||||
return command
|
||||
end
|
||||
|
||||
---@param asset string
|
||||
---@param opts? RuntimeAudioCommandOpts
|
||||
---@return RuntimeCommand
|
||||
function commands.play_sound(asset, opts)
|
||||
local command = with_type("play_sound", opts)
|
||||
command.asset = asset
|
||||
return command
|
||||
end
|
||||
|
||||
---@param asset string
|
||||
---@param opts? RuntimeBgmCommandOpts
|
||||
---@return RuntimeCommand
|
||||
function commands.play_bgm(asset, opts)
|
||||
local command = with_type("play_bgm", opts)
|
||||
command.asset = asset
|
||||
if command.channel == nil then
|
||||
command.channel = "bgm"
|
||||
end
|
||||
if command.loop == nil then
|
||||
command.loop = true
|
||||
end
|
||||
return command
|
||||
end
|
||||
|
||||
---@param channel? string
|
||||
---@param opts? RuntimeCommandOpts
|
||||
---@return RuntimeCommand
|
||||
function commands.pause_bgm(channel, opts)
|
||||
local command = with_type("pause_bgm", opts)
|
||||
command.channel = channel or "bgm"
|
||||
return command
|
||||
end
|
||||
|
||||
---@param channel? string
|
||||
---@param opts? RuntimeCommandOpts
|
||||
---@return RuntimeCommand
|
||||
function commands.resume_bgm(channel, opts)
|
||||
local command = with_type("resume_bgm", opts)
|
||||
command.channel = channel or "bgm"
|
||||
return command
|
||||
end
|
||||
|
||||
---@param channel? string
|
||||
---@param opts? RuntimeCommandOpts
|
||||
---@return RuntimeCommand
|
||||
function commands.stop_bgm(channel, opts)
|
||||
local command = with_type("stop_bgm", opts)
|
||||
command.channel = channel or "bgm"
|
||||
return command
|
||||
end
|
||||
|
||||
---@param group string
|
||||
---@param opts? RuntimeResourceCommandOpts
|
||||
---@return RuntimeCommand
|
||||
function commands.preload_group(group, opts)
|
||||
local command = with_type("preload_resources", opts)
|
||||
command.group = group
|
||||
return command
|
||||
end
|
||||
|
||||
---@param group string
|
||||
---@param opts? RuntimeCommandOpts
|
||||
---@return RuntimeCommand
|
||||
function commands.evict_group(group, opts)
|
||||
local command = with_type("evict_resources", opts)
|
||||
command.group = group
|
||||
return command
|
||||
end
|
||||
|
||||
---@param id string
|
||||
---@return RuntimeCommand
|
||||
function commands.cancel_id(id)
|
||||
return { type = "cancel_commands", id = id }
|
||||
end
|
||||
|
||||
---@param group string
|
||||
---@return RuntimeCommand
|
||||
function commands.cancel_group(group)
|
||||
return { type = "cancel_commands", group = group }
|
||||
end
|
||||
|
||||
---@param scope string
|
||||
---@return RuntimeCommand
|
||||
function commands.cancel_scope(scope)
|
||||
return { type = "cancel_commands", scope = scope }
|
||||
end
|
||||
|
||||
return commands
|
||||
427
assets/runtime/lua/runtime_ui.lua
Normal file
427
assets/runtime/lua/runtime_ui.lua
Normal file
@@ -0,0 +1,427 @@
|
||||
---@class RuntimeUi
|
||||
local runtime_ui = {}
|
||||
|
||||
---@generic T: table
|
||||
---@param base? T
|
||||
---@param opts? table
|
||||
---@return T
|
||||
local function merge(base, opts)
|
||||
local result = {}
|
||||
for key, value in pairs(base or {}) do
|
||||
result[key] = value
|
||||
end
|
||||
|
||||
if opts ~= nil then
|
||||
for key, value in pairs(opts) do
|
||||
result[key] = value
|
||||
end
|
||||
end
|
||||
|
||||
return result
|
||||
end
|
||||
|
||||
---@param value any
|
||||
---@return boolean
|
||||
local function is_table(value)
|
||||
return type(value) == "table"
|
||||
end
|
||||
|
||||
---@param opts? table
|
||||
---@return table
|
||||
local function normalize_opts(opts)
|
||||
local source = opts or {}
|
||||
local result = {}
|
||||
for key, value in pairs(source) do
|
||||
if key ~= "w" and key ~= "h" and key ~= "size" and key ~= "handler" and key ~= "onClick" then
|
||||
result[key] = value
|
||||
end
|
||||
end
|
||||
if result.width == nil then
|
||||
result.width = source.w or source.size
|
||||
end
|
||||
if result.height == nil then
|
||||
result.height = source.h or source.size
|
||||
end
|
||||
if result.onTap == nil then
|
||||
result.onTap = source.handler or source.onClick
|
||||
end
|
||||
return result
|
||||
end
|
||||
|
||||
---@param base table
|
||||
---@param opts? table
|
||||
---@return table
|
||||
local function node_opts(base, opts)
|
||||
return merge(base, normalize_opts(opts))
|
||||
end
|
||||
|
||||
---@param base? RuntimeNodeProps
|
||||
---@param opts? RuntimeNodeProps
|
||||
---@return RuntimeNodeProps
|
||||
function runtime_ui.style(base, opts)
|
||||
return merge(base or {}, opts)
|
||||
end
|
||||
|
||||
---@param parent string
|
||||
---@param opts? RuntimeNodeProps
|
||||
---@return RuntimeNodeProps
|
||||
function runtime_ui.with_parent(parent, opts)
|
||||
return merge(opts or {}, { parent = parent })
|
||||
end
|
||||
|
||||
---@param node_type RuntimeNodeType
|
||||
---@param id string
|
||||
---@param opts? RuntimeNodeProps
|
||||
---@return RuntimeNode
|
||||
function runtime_ui.node(node_type, id, opts)
|
||||
return node_opts({ id = id, type = node_type }, opts)
|
||||
end
|
||||
|
||||
---@param id string
|
||||
---@param x number|RuntimeNodeInit
|
||||
---@param y? number
|
||||
---@param width? number
|
||||
---@param height? number
|
||||
---@param opts? RuntimeNodeInit
|
||||
---@return RuntimeNode
|
||||
function runtime_ui.panel(id, x, y, width, height, opts)
|
||||
if is_table(x) then
|
||||
return runtime_ui.node("panel", id, node_opts({ x = 0, y = 0, width = 0, height = 0 }, x))
|
||||
end
|
||||
return runtime_ui.node("panel", id, node_opts({
|
||||
x = x,
|
||||
y = y,
|
||||
width = width,
|
||||
height = height
|
||||
}, opts))
|
||||
end
|
||||
|
||||
---@param id string
|
||||
---@param x number|RuntimeNodeInit
|
||||
---@param y? number
|
||||
---@param width? number
|
||||
---@param height? number
|
||||
---@param opts? RuntimeNodeInit
|
||||
---@return RuntimeNode
|
||||
function runtime_ui.rect(id, x, y, width, height, opts)
|
||||
if is_table(x) then
|
||||
return runtime_ui.node("rect", id, node_opts({ x = 0, y = 0, width = 0, height = 0 }, x))
|
||||
end
|
||||
return runtime_ui.node("rect", id, node_opts({
|
||||
x = x,
|
||||
y = y,
|
||||
width = width,
|
||||
height = height
|
||||
}, opts))
|
||||
end
|
||||
|
||||
---@param id string
|
||||
---@param x number|RuntimeNodeInit
|
||||
---@param y? number
|
||||
---@param size? number
|
||||
---@param opts? RuntimeNodeInit
|
||||
---@return RuntimeNode
|
||||
function runtime_ui.circle(id, x, y, size, opts)
|
||||
if is_table(x) then
|
||||
return runtime_ui.node("circle", id, node_opts({ x = 0, y = 0, width = 0, height = 0 }, x))
|
||||
end
|
||||
return runtime_ui.node("circle", id, node_opts({
|
||||
x = x,
|
||||
y = y,
|
||||
width = size,
|
||||
height = size
|
||||
}, opts))
|
||||
end
|
||||
|
||||
---@param id string
|
||||
---@param x number|RuntimeNodeInit
|
||||
---@param y? number
|
||||
---@param width? number
|
||||
---@param height? number
|
||||
---@param opts? RuntimeNodeInit
|
||||
---@return RuntimeNode
|
||||
function runtime_ui.line(id, x, y, width, height, opts)
|
||||
if is_table(x) then
|
||||
return runtime_ui.node("line", id, node_opts({ x = 0, y = 0, width = 0, height = 0 }, x))
|
||||
end
|
||||
return runtime_ui.node("line", id, node_opts({
|
||||
x = x,
|
||||
y = y,
|
||||
width = width,
|
||||
height = height
|
||||
}, opts))
|
||||
end
|
||||
|
||||
---@param id string
|
||||
---@param x number|RuntimeNodeInit
|
||||
---@param y? number
|
||||
---@param width? number
|
||||
---@param height? number
|
||||
---@param value? number
|
||||
---@param opts? RuntimeNodeInit
|
||||
---@return RuntimeNode
|
||||
function runtime_ui.progress(id, x, y, width, height, value, opts)
|
||||
if is_table(x) then
|
||||
return runtime_ui.node("progress", id, node_opts({ x = 0, y = 0, width = 0, height = 0, value = 0 }, x))
|
||||
end
|
||||
return runtime_ui.node("progress", id, node_opts({
|
||||
x = x,
|
||||
y = y,
|
||||
width = width,
|
||||
height = height,
|
||||
value = value
|
||||
}, opts))
|
||||
end
|
||||
|
||||
---@param id string
|
||||
---@param x number|RuntimeNodeInit
|
||||
---@param y? number
|
||||
---@param width? number
|
||||
---@param height? number
|
||||
---@param opts? RuntimeNodeInit
|
||||
---@return RuntimeNode
|
||||
function runtime_ui.particle(id, x, y, width, height, opts)
|
||||
if is_table(x) then
|
||||
return runtime_ui.node("particle", id, node_opts({ x = 0, y = 0, width = 0, height = 0 }, x))
|
||||
end
|
||||
return runtime_ui.node("particle", id, node_opts({
|
||||
x = x,
|
||||
y = y,
|
||||
width = width,
|
||||
height = height
|
||||
}, opts))
|
||||
end
|
||||
|
||||
---@param id string
|
||||
---@param text string|RuntimeNodeInit
|
||||
---@param x? number
|
||||
---@param y? number
|
||||
---@param width? number
|
||||
---@param height? number
|
||||
---@param opts? RuntimeNodeInit
|
||||
---@return RuntimeNode
|
||||
function runtime_ui.text(id, text, x, y, width, height, opts)
|
||||
if is_table(text) then
|
||||
return runtime_ui.node("text", id, node_opts({ text = "", x = 0, y = 0, width = 0, height = 0 }, text))
|
||||
end
|
||||
return runtime_ui.node("text", id, node_opts({
|
||||
text = text,
|
||||
x = x,
|
||||
y = y,
|
||||
width = width,
|
||||
height = height
|
||||
}, opts))
|
||||
end
|
||||
|
||||
---@param id string
|
||||
---@param text string|RuntimeNodeInit
|
||||
---@param x? number
|
||||
---@param y? number
|
||||
---@param width? number
|
||||
---@param height? number
|
||||
---@param handler? string
|
||||
---@param opts? RuntimeNodeInit
|
||||
---@return RuntimeNode
|
||||
function runtime_ui.button(id, text, x, y, width, height, handler, opts)
|
||||
if is_table(text) then
|
||||
return runtime_ui.node("button", id, node_opts({
|
||||
text = "",
|
||||
x = 0,
|
||||
y = 0,
|
||||
width = 0,
|
||||
height = 0,
|
||||
interactive = true
|
||||
}, text))
|
||||
end
|
||||
return runtime_ui.node("button", id, node_opts({
|
||||
text = text,
|
||||
x = x,
|
||||
y = y,
|
||||
width = width,
|
||||
height = height,
|
||||
interactive = true,
|
||||
onTap = handler
|
||||
}, opts))
|
||||
end
|
||||
|
||||
---@param id string
|
||||
---@param x number|RuntimeNodeInit
|
||||
---@param y? number
|
||||
---@param width? number
|
||||
---@param height? number
|
||||
---@param opts? RuntimeNodeInit
|
||||
---@return RuntimeNode
|
||||
function runtime_ui.list_view(id, x, y, width, height, opts)
|
||||
if is_table(x) then
|
||||
return runtime_ui.node("listView", id, node_opts({ x = 0, y = 0, width = 0, height = 0 }, x))
|
||||
end
|
||||
return runtime_ui.node("listView", id, node_opts({
|
||||
x = x,
|
||||
y = y,
|
||||
width = width,
|
||||
height = height
|
||||
}, opts))
|
||||
end
|
||||
|
||||
---@param id string
|
||||
---@param asset string|RuntimeNodeInit
|
||||
---@param x? number
|
||||
---@param y? number
|
||||
---@param width? number
|
||||
---@param height? number
|
||||
---@param opts? RuntimeNodeInit
|
||||
---@return RuntimeNode
|
||||
function runtime_ui.image(id, asset, x, y, width, height, opts)
|
||||
if is_table(asset) then
|
||||
return runtime_ui.node("image", id, node_opts({ asset = "", x = 0, y = 0, width = 0, height = 0 }, asset))
|
||||
end
|
||||
return runtime_ui.node("image", id, node_opts({
|
||||
asset = asset,
|
||||
x = x,
|
||||
y = y,
|
||||
width = width,
|
||||
height = height
|
||||
}, opts))
|
||||
end
|
||||
|
||||
---@param id string
|
||||
---@param asset string|RuntimeNodeInit
|
||||
---@param x? number
|
||||
---@param y? number
|
||||
---@param width? number
|
||||
---@param height? number
|
||||
---@param opts? RuntimeNodeInit
|
||||
---@return RuntimeNode
|
||||
function runtime_ui.sprite(id, asset, x, y, width, height, opts)
|
||||
if is_table(asset) then
|
||||
return runtime_ui.node("sprite", id, node_opts({ asset = "", x = 0, y = 0, width = 0, height = 0 }, asset))
|
||||
end
|
||||
return runtime_ui.node("sprite", id, node_opts({
|
||||
asset = asset,
|
||||
x = x,
|
||||
y = y,
|
||||
width = width,
|
||||
height = height
|
||||
}, opts))
|
||||
end
|
||||
|
||||
---@param id string
|
||||
---@param asset string|RuntimeNodeInit
|
||||
---@param x? number
|
||||
---@param y? number
|
||||
---@param width? number
|
||||
---@param height? number
|
||||
---@param animation? string
|
||||
---@param opts? RuntimeNodeInit
|
||||
---@return RuntimeNode
|
||||
function runtime_ui.spine(id, asset, x, y, width, height, animation, opts)
|
||||
if is_table(asset) then
|
||||
return runtime_ui.node("spine", id, node_opts({ asset = "", x = 0, y = 0, width = 0, height = 0, loop = true }, asset))
|
||||
end
|
||||
return runtime_ui.node("spine", id, node_opts({
|
||||
asset = asset,
|
||||
x = x,
|
||||
y = y,
|
||||
width = width,
|
||||
height = height,
|
||||
animation = animation,
|
||||
loop = true
|
||||
}, opts))
|
||||
end
|
||||
|
||||
---@param id string
|
||||
---@param props RuntimeNodeInit
|
||||
---@return RuntimeNodeUpdate
|
||||
function runtime_ui.update(id, props)
|
||||
return { id = id, props = normalize_opts(props or {}) }
|
||||
end
|
||||
|
||||
---@param id string
|
||||
---@param text string
|
||||
---@return RuntimeNodeUpdate
|
||||
function runtime_ui.text_update(id, text)
|
||||
return runtime_ui.update(id, { text = text })
|
||||
end
|
||||
|
||||
---@param id string
|
||||
---@param visible boolean
|
||||
---@return RuntimeNodeUpdate
|
||||
function runtime_ui.visible_update(id, visible)
|
||||
return runtime_ui.update(id, { visible = visible })
|
||||
end
|
||||
|
||||
---@param id string
|
||||
---@param alpha number
|
||||
---@return RuntimeNodeUpdate
|
||||
function runtime_ui.alpha_update(id, alpha)
|
||||
return runtime_ui.update(id, { alpha = alpha })
|
||||
end
|
||||
|
||||
---@param id string
|
||||
---@param scale number
|
||||
---@return RuntimeNodeUpdate
|
||||
function runtime_ui.scale_update(id, scale)
|
||||
return runtime_ui.update(id, { scale = scale })
|
||||
end
|
||||
|
||||
---@param id string
|
||||
---@param x number
|
||||
---@param y number
|
||||
---@return RuntimeNodeUpdate
|
||||
function runtime_ui.position_update(id, x, y)
|
||||
return runtime_ui.update(id, { x = x, y = y })
|
||||
end
|
||||
|
||||
---@param id string
|
||||
---@param width number
|
||||
---@param height number
|
||||
---@return RuntimeNodeUpdate
|
||||
function runtime_ui.size_update(id, width, height)
|
||||
return runtime_ui.update(id, { width = width, height = height })
|
||||
end
|
||||
|
||||
---@param id string
|
||||
---@param x number
|
||||
---@param y number
|
||||
---@param scale number
|
||||
---@param rotation number
|
||||
---@return RuntimeNodeUpdate
|
||||
function runtime_ui.transform_update(id, x, y, scale, rotation)
|
||||
return runtime_ui.update(id, {
|
||||
x = x,
|
||||
y = y,
|
||||
scale = scale,
|
||||
rotation = rotation
|
||||
})
|
||||
end
|
||||
|
||||
---@param ids string[]
|
||||
---@param props RuntimeNodeInit
|
||||
---@return RuntimeNodeUpdate[]
|
||||
function runtime_ui.batch_update(ids, props)
|
||||
local updates = {}
|
||||
for _, id in ipairs(ids) do
|
||||
table.insert(updates, runtime_ui.update(id, props))
|
||||
end
|
||||
return updates
|
||||
end
|
||||
|
||||
---@param nodes RuntimeNode[]
|
||||
---@param node RuntimeNode
|
||||
---@return RuntimeNode[]
|
||||
function runtime_ui.append(nodes, node)
|
||||
table.insert(nodes, node)
|
||||
return nodes
|
||||
end
|
||||
|
||||
---@param nodes RuntimeNode[]
|
||||
---@param extra_nodes RuntimeNode[]
|
||||
---@return RuntimeNode[]
|
||||
function runtime_ui.append_all(nodes, extra_nodes)
|
||||
for _, node in ipairs(extra_nodes) do
|
||||
table.insert(nodes, node)
|
||||
end
|
||||
return nodes
|
||||
end
|
||||
|
||||
return runtime_ui
|
||||
773
assets/runtime/lua/runtime_widgets.lua
Normal file
773
assets/runtime/lua/runtime_widgets.lua
Normal file
@@ -0,0 +1,773 @@
|
||||
local runtime_ui = runtime.import("runtime_ui")
|
||||
|
||||
---@class (exact) RuntimeDialogButton
|
||||
---@field id? string
|
||||
---@field text string
|
||||
---@field handler string
|
||||
---@field color? string
|
||||
|
||||
---@class (exact) RuntimeDialogOpts
|
||||
---@field screenWidth? number
|
||||
---@field screenHeight? number
|
||||
---@field overlay? boolean
|
||||
---@field overlayColor? string
|
||||
---@field blockInput? boolean
|
||||
---@field layer? integer
|
||||
---@field color? string
|
||||
---@field radius? number
|
||||
---@field panelStyle? RuntimeNodeProps
|
||||
---@field titleColor? string
|
||||
---@field titleSize? number
|
||||
---@field titleStyle? RuntimeNodeProps
|
||||
---@field messageColor? string
|
||||
---@field messageSize? number
|
||||
---@field messageStyle? RuntimeNodeProps
|
||||
---@field buttons? RuntimeDialogButton[]
|
||||
---@field buttonGap? number
|
||||
---@field buttonStyle? RuntimeNodeProps
|
||||
|
||||
---@class (exact) RuntimeLabeledProgressOpts: RuntimeNodeProps
|
||||
---@field labelHeight? number
|
||||
---@field labelStyle? RuntimeNodeProps
|
||||
|
||||
---@class RuntimePillOpts: RuntimeNodeInit
|
||||
---@field panelStyle? RuntimeNodeProps
|
||||
---@field textStyle? RuntimeNodeProps
|
||||
|
||||
---@class RuntimeTextButtonOpts: RuntimeNodeInit
|
||||
---@field variant? 'primary'|'secondary'|'ghost'
|
||||
|
||||
---@class RuntimeListItemOpts: RuntimeTextButtonOpts
|
||||
---@field selected? boolean
|
||||
---@field activeColor? string
|
||||
---@field inactiveColor? string
|
||||
|
||||
---@class RuntimeTabItem
|
||||
---@field id? string
|
||||
---@field key? string
|
||||
---@field text string
|
||||
---@field handler? string
|
||||
---@field selected? boolean
|
||||
|
||||
---@class RuntimeTabsOpts: RuntimeNodeInit
|
||||
---@field tabs RuntimeTabItem[]
|
||||
---@field selected? string
|
||||
---@field gap? number
|
||||
---@field itemWidth? number
|
||||
---@field itemHeight? number
|
||||
---@field activeColor? string
|
||||
---@field inactiveColor? string
|
||||
---@field buttonStyle? RuntimeNodeProps
|
||||
|
||||
---@class RuntimeActionItem
|
||||
---@field id? string
|
||||
---@field text string
|
||||
---@field handler? string
|
||||
---@field visible? boolean
|
||||
---@field color? string
|
||||
---@field style? RuntimeNodeProps
|
||||
|
||||
---@class RuntimeActionRowOpts: RuntimeNodeInit
|
||||
---@field actions RuntimeActionItem[]
|
||||
---@field gap? number
|
||||
---@field itemWidth? number
|
||||
---@field itemHeight? number
|
||||
---@field buttonStyle? RuntimeNodeProps
|
||||
|
||||
---@class RuntimePanelHeaderOpts: RuntimeNodeInit
|
||||
---@field eyebrow? string
|
||||
---@field title string
|
||||
---@field summary? string
|
||||
---@field gap? number
|
||||
---@field eyebrowId? string
|
||||
---@field titleId? string
|
||||
---@field summaryId? string
|
||||
---@field eyebrowHeight? number
|
||||
---@field titleHeight? number
|
||||
---@field summaryHeight? number
|
||||
---@field eyebrowStyle? RuntimeNodeProps
|
||||
---@field titleStyle? RuntimeNodeProps
|
||||
---@field summaryStyle? RuntimeNodeProps
|
||||
|
||||
---@class RuntimeWidgetTheme
|
||||
---@field primary? string
|
||||
---@field secondary? string
|
||||
---@field success? string
|
||||
---@field overlay? string
|
||||
---@field surface? string
|
||||
---@field surfaceAlt? string
|
||||
---@field card? string
|
||||
---@field text? string
|
||||
---@field muted? string
|
||||
---@field progress? string
|
||||
---@field transparent? string
|
||||
|
||||
---@class RuntimeWidgets
|
||||
local widgets = {}
|
||||
|
||||
-- -----------------------------------------------------------------------------
|
||||
-- Theme/config
|
||||
-- -----------------------------------------------------------------------------
|
||||
|
||||
---@type RuntimeWidgetTheme
|
||||
local widget_theme = {
|
||||
primary = "#ff2563eb",
|
||||
secondary = "#ff475569",
|
||||
success = "#ff22c55e",
|
||||
overlay = "#99000000",
|
||||
surface = "#ff1e293b",
|
||||
surfaceAlt = "#ff334155",
|
||||
card = "#ee111827",
|
||||
text = "#ffffffff",
|
||||
muted = "#ffe5e7eb",
|
||||
progress = "#ff22c55e",
|
||||
transparent = "#00000000"
|
||||
}
|
||||
|
||||
-- -----------------------------------------------------------------------------
|
||||
-- Internal helpers
|
||||
-- -----------------------------------------------------------------------------
|
||||
|
||||
---@generic T: table
|
||||
---@param base? T
|
||||
---@param opts? table
|
||||
---@return T
|
||||
local function merge(base, opts)
|
||||
local result = {}
|
||||
for key, value in pairs(base or {}) do
|
||||
result[key] = value
|
||||
end
|
||||
for key, value in pairs(opts or {}) do
|
||||
result[key] = value
|
||||
end
|
||||
return result
|
||||
end
|
||||
|
||||
---@param value any
|
||||
---@return boolean
|
||||
local function is_table(value)
|
||||
return type(value) == "table"
|
||||
end
|
||||
|
||||
---@param source? table
|
||||
---@param keys string[]
|
||||
---@return table
|
||||
local function without_keys(source, keys)
|
||||
local result = merge(source or {}, nil)
|
||||
for _, key in ipairs(keys) do
|
||||
result[key] = nil
|
||||
end
|
||||
return result
|
||||
end
|
||||
|
||||
---@param opts? RuntimeNodeInit
|
||||
---@return RuntimeNodeInit
|
||||
local function normalize_box(opts)
|
||||
local result = merge(opts or {}, nil)
|
||||
if result.width == nil then
|
||||
result.width = result.w or result.size
|
||||
end
|
||||
if result.height == nil then
|
||||
result.height = result.h or result.size
|
||||
end
|
||||
result.w = nil
|
||||
result.h = nil
|
||||
result.size = nil
|
||||
return result
|
||||
end
|
||||
|
||||
---@param opts? RuntimeNodeInit
|
||||
---@param fallback? table
|
||||
---@return RuntimeNodeInit
|
||||
local function with_box(opts, fallback)
|
||||
return merge(fallback or {}, normalize_box(opts))
|
||||
end
|
||||
|
||||
---@param target RuntimeNode[]
|
||||
---@param source? RuntimeNode[]
|
||||
---@return RuntimeNode[]
|
||||
local function append_all(target, source)
|
||||
for _, node in ipairs(source or {}) do
|
||||
table.insert(target, node)
|
||||
end
|
||||
return target
|
||||
end
|
||||
|
||||
---@param parent string
|
||||
---@param opts? RuntimeNodeProps
|
||||
---@return RuntimeNodeProps
|
||||
local function child_opts(parent, opts)
|
||||
return merge(opts or {}, { parent = parent })
|
||||
end
|
||||
|
||||
---@param value table
|
||||
---@param opts? table
|
||||
---@param key string
|
||||
---@return table, table
|
||||
local function collection_args(value, opts, key)
|
||||
local options = opts or {}
|
||||
local items = value
|
||||
if is_table(value) and value[key] ~= nil then
|
||||
options = value
|
||||
items = value[key]
|
||||
end
|
||||
return items or {}, options
|
||||
end
|
||||
|
||||
---@param tokens? RuntimeWidgetTheme
|
||||
---@return RuntimeWidgets
|
||||
function widgets.configure(tokens)
|
||||
widget_theme = merge(widget_theme, tokens or {})
|
||||
return widgets
|
||||
end
|
||||
|
||||
---@param name string
|
||||
---@return string
|
||||
local function theme_color(name)
|
||||
return widget_theme[name] or "#ffffffff"
|
||||
end
|
||||
|
||||
-- -----------------------------------------------------------------------------
|
||||
-- Text helpers
|
||||
-- -----------------------------------------------------------------------------
|
||||
|
||||
---@param id string
|
||||
---@param text string|RuntimeNodeInit
|
||||
---@param x? number
|
||||
---@param y? number
|
||||
---@param width? number
|
||||
---@param height? number
|
||||
---@param opts? RuntimeNodeInit
|
||||
---@return RuntimeNode
|
||||
function widgets.label(id, text, x, y, width, height, opts)
|
||||
local fallback = { text = "", x = 0, y = 0, width = 0, height = 0 }
|
||||
local options
|
||||
if is_table(text) then
|
||||
options = with_box(text, fallback)
|
||||
else
|
||||
options = with_box(merge({ text = text, x = x, y = y, width = width, height = height }, opts), fallback)
|
||||
end
|
||||
return runtime_ui.text(id, merge({
|
||||
color = theme_color("muted"),
|
||||
fontSize = 14,
|
||||
textAlign = "left"
|
||||
}, options))
|
||||
end
|
||||
|
||||
---@param id string
|
||||
---@param text string|RuntimeNodeInit
|
||||
---@param x? number
|
||||
---@param y? number
|
||||
---@param width? number
|
||||
---@param height? number
|
||||
---@param opts? RuntimeNodeInit
|
||||
---@return RuntimeNode
|
||||
function widgets.section_title(id, text, x, y, width, height, opts)
|
||||
local options
|
||||
if is_table(text) then
|
||||
options = text
|
||||
else
|
||||
options = merge({ text = text, x = x, y = y, width = width, height = height }, opts)
|
||||
end
|
||||
return widgets.label(id, merge({
|
||||
color = theme_color("text"),
|
||||
fontSize = 18,
|
||||
textAlign = "left"
|
||||
}, options))
|
||||
end
|
||||
|
||||
-- -----------------------------------------------------------------------------
|
||||
-- Badges and buttons
|
||||
-- -----------------------------------------------------------------------------
|
||||
|
||||
---@param id string
|
||||
---@param text string|RuntimePillOpts
|
||||
---@param x? number
|
||||
---@param y? number
|
||||
---@param width? number
|
||||
---@param height? number
|
||||
---@param opts? RuntimePillOpts
|
||||
---@return RuntimeNode[]
|
||||
function widgets.pill(id, text, x, y, width, height, opts)
|
||||
local options
|
||||
if is_table(text) then
|
||||
options = with_box(text, { text = "", x = 0, y = 0, width = 0, height = 0 })
|
||||
else
|
||||
options = with_box(merge({ text = text, x = x, y = y, width = width, height = height }, opts), {
|
||||
text = "",
|
||||
x = 0,
|
||||
y = 0,
|
||||
width = 0,
|
||||
height = 0
|
||||
})
|
||||
end
|
||||
local panel_opts = merge({
|
||||
color = theme_color("surfaceAlt"),
|
||||
radius = options.height / 2
|
||||
}, options.panelStyle)
|
||||
panel_opts = merge(panel_opts, without_keys(options, { "text", "panelStyle", "textStyle" }))
|
||||
local text_opts = child_opts(id, merge({
|
||||
color = theme_color("text"),
|
||||
fontSize = 12,
|
||||
textAlign = "center"
|
||||
}, options.textStyle))
|
||||
return {
|
||||
runtime_ui.panel(id, panel_opts),
|
||||
runtime_ui.text(id .. "_text", merge({
|
||||
text = options.text or "",
|
||||
x = 0,
|
||||
y = 0,
|
||||
width = options.width,
|
||||
height = options.height
|
||||
}, text_opts))
|
||||
}
|
||||
end
|
||||
|
||||
---@param id string
|
||||
---@param text string|RuntimeTextButtonOpts
|
||||
---@param x? number
|
||||
---@param y? number
|
||||
---@param width? number
|
||||
---@param height? number
|
||||
---@param handler? string
|
||||
---@param opts? RuntimeTextButtonOpts
|
||||
---@return RuntimeNode
|
||||
function widgets.text_button(id, text, x, y, width, height, handler, opts)
|
||||
local options
|
||||
if is_table(text) then
|
||||
options = with_box(text, { text = "", x = 0, y = 0, width = 0, height = 0 })
|
||||
else
|
||||
options = with_box(merge({ text = text, x = x, y = y, width = width, height = height, handler = handler }, opts), {
|
||||
text = "",
|
||||
x = 0,
|
||||
y = 0,
|
||||
width = 0,
|
||||
height = 0
|
||||
})
|
||||
end
|
||||
local variant = options.variant or "primary"
|
||||
local color = theme_color("primary")
|
||||
if variant == "secondary" then
|
||||
color = theme_color("secondary")
|
||||
elseif variant == "ghost" then
|
||||
color = theme_color("transparent")
|
||||
end
|
||||
return runtime_ui.button(id, merge({
|
||||
color = color,
|
||||
radius = 10,
|
||||
fontSize = 13,
|
||||
textAlign = "center"
|
||||
}, without_keys(options, { "variant" })))
|
||||
end
|
||||
|
||||
---@param id string
|
||||
---@param text string|RuntimeListItemOpts
|
||||
---@param x? number
|
||||
---@param y? number
|
||||
---@param width? number
|
||||
---@param height? number
|
||||
---@param handler? string
|
||||
---@param opts? RuntimeListItemOpts
|
||||
---@return RuntimeNode
|
||||
function widgets.list_item(id, text, x, y, width, height, handler, opts)
|
||||
local options
|
||||
if is_table(text) then
|
||||
options = with_box(text, { text = "", x = 0, y = 0, width = 0, height = 0 })
|
||||
else
|
||||
options = with_box(merge({ text = text, x = x, y = y, width = width, height = height, handler = handler }, opts), {
|
||||
text = "",
|
||||
x = 0,
|
||||
y = 0,
|
||||
width = 0,
|
||||
height = 0
|
||||
})
|
||||
end
|
||||
local selected = options.selected == true
|
||||
local color = selected and (options.activeColor or theme_color("primary")) or (options.inactiveColor or theme_color("surface"))
|
||||
return widgets.text_button(id, merge({
|
||||
color = color,
|
||||
radius = 10,
|
||||
fontSize = 12
|
||||
}, without_keys(options, { "selected", "activeColor", "inactiveColor" })))
|
||||
end
|
||||
|
||||
---@param id string
|
||||
---@param tabs RuntimeTabItem[]|RuntimeTabsOpts
|
||||
---@param opts? RuntimeTabsOpts
|
||||
---@return RuntimeNode[]
|
||||
function widgets.tabs(id, tabs, opts)
|
||||
local items, options = collection_args(tabs, opts, "tabs")
|
||||
|
||||
local origin_x = options.x or 0
|
||||
local origin_y = options.y or 0
|
||||
local gap = options.gap or 6
|
||||
local item_w = options.itemWidth or options.width or options.w or 72
|
||||
local item_h = options.itemHeight or options.height or options.h or 24
|
||||
local nodes = {}
|
||||
|
||||
for index, tab in ipairs(items) do
|
||||
local key = tab.key or tostring(index)
|
||||
local selected = tab.selected == true or key == options.selected
|
||||
local node_id = tab.id or (id .. "_" .. key)
|
||||
local style = merge({
|
||||
x = origin_x + (index - 1) * (item_w + gap),
|
||||
y = origin_y,
|
||||
w = item_w,
|
||||
h = item_h,
|
||||
text = tab.text or key,
|
||||
handler = tab.handler or key,
|
||||
selected = selected,
|
||||
activeColor = options.activeColor,
|
||||
inactiveColor = options.inactiveColor
|
||||
}, options.buttonStyle)
|
||||
style = merge(style, without_keys(options, {
|
||||
"tabs",
|
||||
"selected",
|
||||
"gap",
|
||||
"itemWidth",
|
||||
"itemHeight",
|
||||
"activeColor",
|
||||
"inactiveColor",
|
||||
"buttonStyle",
|
||||
"x",
|
||||
"y",
|
||||
"width",
|
||||
"height",
|
||||
"w",
|
||||
"h"
|
||||
}))
|
||||
nodes[index] = widgets.list_item(node_id, style)
|
||||
end
|
||||
|
||||
return nodes
|
||||
end
|
||||
|
||||
---@param id string
|
||||
---@param actions RuntimeActionItem[]|RuntimeActionRowOpts
|
||||
---@param opts? RuntimeActionRowOpts
|
||||
---@return RuntimeNode[]
|
||||
function widgets.action_row(id, actions, opts)
|
||||
local items, options = collection_args(actions, opts, "actions")
|
||||
|
||||
local count = #items
|
||||
local gap = options.gap or 8
|
||||
local origin_x = options.x or 0
|
||||
local origin_y = options.y or 0
|
||||
local item_h = options.itemHeight or options.height or options.h or 32
|
||||
local item_w = options.itemWidth
|
||||
if item_w == nil and (options.width ~= nil or options.w ~= nil) and count > 0 then
|
||||
item_w = ((options.width or options.w) - gap * (count - 1)) / count
|
||||
end
|
||||
item_w = item_w or 88
|
||||
|
||||
local nodes = {}
|
||||
for index, action in ipairs(items) do
|
||||
local visible = action.visible ~= false
|
||||
local style = merge({
|
||||
x = origin_x + (index - 1) * (item_w + gap),
|
||||
y = origin_y,
|
||||
w = item_w,
|
||||
h = item_h,
|
||||
text = visible and (action.text or "") or "",
|
||||
handler = visible and (action.handler or "noop") or "noop",
|
||||
color = action.color or (index == 1 and theme_color("primary") or theme_color("surfaceAlt")),
|
||||
visible = visible
|
||||
}, options.buttonStyle)
|
||||
style = merge(style, action.style)
|
||||
style = merge(style, without_keys(options, {
|
||||
"actions",
|
||||
"gap",
|
||||
"itemWidth",
|
||||
"itemHeight",
|
||||
"buttonStyle",
|
||||
"x",
|
||||
"y",
|
||||
"width",
|
||||
"height",
|
||||
"w",
|
||||
"h"
|
||||
}))
|
||||
nodes[index] = widgets.text_button(action.id or (id .. "_" .. tostring(index)), style)
|
||||
end
|
||||
|
||||
return nodes
|
||||
end
|
||||
|
||||
-- -----------------------------------------------------------------------------
|
||||
-- Panels and headers
|
||||
-- -----------------------------------------------------------------------------
|
||||
|
||||
---@param id string
|
||||
---@param opts RuntimePanelHeaderOpts
|
||||
---@return RuntimeNode[]
|
||||
function widgets.panel_header(id, opts)
|
||||
local options = with_box(opts or {}, { x = 0, y = 0, width = 0, height = 0, title = "" })
|
||||
local nodes = {}
|
||||
local cursor = options.y
|
||||
local gap = options.gap or 4
|
||||
local layer = options.layer
|
||||
local parent = options.parent
|
||||
|
||||
if options.eyebrow ~= nil and options.eyebrow ~= "" then
|
||||
local h = options.eyebrowHeight or 20
|
||||
table.insert(nodes, widgets.label(options.eyebrowId or (id .. "_eyebrow"), merge({
|
||||
text = options.eyebrow,
|
||||
x = options.x,
|
||||
y = cursor,
|
||||
width = options.width,
|
||||
height = h,
|
||||
parent = parent,
|
||||
layer = layer,
|
||||
color = theme_color("primary"),
|
||||
fontSize = 13
|
||||
}, options.eyebrowStyle)))
|
||||
cursor = cursor + h + gap
|
||||
end
|
||||
|
||||
local title_h = options.titleHeight or 28
|
||||
table.insert(nodes, widgets.section_title(options.titleId or (id .. "_title"), merge({
|
||||
text = options.title or "",
|
||||
x = options.x,
|
||||
y = cursor,
|
||||
width = options.width,
|
||||
height = title_h,
|
||||
parent = parent,
|
||||
layer = layer,
|
||||
fontSize = 20
|
||||
}, options.titleStyle)))
|
||||
cursor = cursor + title_h + gap
|
||||
|
||||
if options.summary ~= nil and options.summary ~= "" then
|
||||
local h = options.summaryHeight or 22
|
||||
table.insert(nodes, widgets.label(options.summaryId or (id .. "_summary"), merge({
|
||||
text = options.summary,
|
||||
x = options.x,
|
||||
y = cursor,
|
||||
width = options.width,
|
||||
height = h,
|
||||
parent = parent,
|
||||
layer = layer,
|
||||
color = theme_color("muted"),
|
||||
fontSize = 12
|
||||
}, options.summaryStyle)))
|
||||
end
|
||||
|
||||
return nodes
|
||||
end
|
||||
|
||||
---@param id string
|
||||
---@param width number|RuntimeNodeInit
|
||||
---@param height? number
|
||||
---@param opts? RuntimeNodeInit
|
||||
---@return RuntimeNode
|
||||
function widgets.overlay(id, width, height, opts)
|
||||
if is_table(width) then
|
||||
return runtime_ui.rect(id, merge({
|
||||
x = 0,
|
||||
y = 0,
|
||||
color = theme_color("overlay"),
|
||||
layer = 900
|
||||
}, width))
|
||||
end
|
||||
return runtime_ui.rect(id, 0, 0, width, height, merge({
|
||||
color = theme_color("overlay"),
|
||||
layer = 900
|
||||
}, opts))
|
||||
end
|
||||
|
||||
---@param id string
|
||||
---@param x number|RuntimeNodeInit
|
||||
---@param y? number
|
||||
---@param width? number
|
||||
---@param height? number
|
||||
---@param opts? RuntimeNodeInit
|
||||
---@return RuntimeNode
|
||||
function widgets.card(id, x, y, width, height, opts)
|
||||
if is_table(x) then
|
||||
return runtime_ui.panel(id, merge({
|
||||
x = 0,
|
||||
y = 0,
|
||||
width = 0,
|
||||
height = 0,
|
||||
color = theme_color("card"),
|
||||
radius = 14,
|
||||
layer = 910
|
||||
}, x))
|
||||
end
|
||||
return runtime_ui.panel(id, x, y, width, height, merge({
|
||||
color = theme_color("card"),
|
||||
radius = 14,
|
||||
layer = 910
|
||||
}, opts))
|
||||
end
|
||||
|
||||
-- -----------------------------------------------------------------------------
|
||||
-- Progress
|
||||
-- -----------------------------------------------------------------------------
|
||||
|
||||
---@param id string
|
||||
---@param x number|RuntimeNodeInit
|
||||
---@param y? number
|
||||
---@param width? number
|
||||
---@param height? number
|
||||
---@param value? number
|
||||
---@param opts? RuntimeNodeInit
|
||||
---@return RuntimeNode
|
||||
function widgets.progress_bar(id, x, y, width, height, value, opts)
|
||||
if is_table(x) then
|
||||
return runtime_ui.progress(id, merge({
|
||||
x = 0,
|
||||
y = 0,
|
||||
width = 0,
|
||||
height = 0,
|
||||
value = 0,
|
||||
color = theme_color("progress"),
|
||||
radius = 6
|
||||
}, x))
|
||||
end
|
||||
return runtime_ui.progress(id, x, y, width, height, value, merge({
|
||||
color = theme_color("progress"),
|
||||
radius = 6
|
||||
}, opts))
|
||||
end
|
||||
|
||||
---@param id string
|
||||
---@param label string
|
||||
---@param x number
|
||||
---@param y number
|
||||
---@param width number
|
||||
---@param height number
|
||||
---@param value number
|
||||
---@param opts? RuntimeLabeledProgressOpts
|
||||
---@return RuntimeNode[]
|
||||
function widgets.labeled_progress(id, label, x, y, width, height, value, opts)
|
||||
local label_height = opts ~= nil and opts.labelHeight or 24
|
||||
local progress_opts = merge(opts or {}, nil)
|
||||
progress_opts.labelHeight = nil
|
||||
progress_opts.labelStyle = nil
|
||||
return {
|
||||
runtime_ui.text(id .. "_label", label, x, y, width, label_height, merge({
|
||||
color = theme_color("text"),
|
||||
fontSize = 16
|
||||
}, opts ~= nil and opts.labelStyle or nil)),
|
||||
widgets.progress_bar(id, x, y + label_height + 6, width, height, value, progress_opts)
|
||||
}
|
||||
end
|
||||
|
||||
-- -----------------------------------------------------------------------------
|
||||
-- Dialogs
|
||||
-- -----------------------------------------------------------------------------
|
||||
|
||||
---@param parent string
|
||||
---@param id string
|
||||
---@param buttons RuntimeDialogButton[]
|
||||
---@param x number
|
||||
---@param y number
|
||||
---@param width number
|
||||
---@param height number
|
||||
---@param gap? number
|
||||
---@param opts? RuntimeNodeProps
|
||||
---@return RuntimeNode[]
|
||||
function widgets.button_row(parent, id, buttons, x, y, width, height, gap, opts)
|
||||
local nodes = {}
|
||||
local count = #buttons
|
||||
if count == 0 then
|
||||
return nodes
|
||||
end
|
||||
|
||||
local actual_gap = gap or 8
|
||||
local button_width = (width - actual_gap * (count - 1)) / count
|
||||
for index, button in ipairs(buttons) do
|
||||
local button_id = button.id or (id .. "_button_" .. index)
|
||||
table.insert(nodes, runtime_ui.button(
|
||||
button_id,
|
||||
button.text or "OK",
|
||||
x + (index - 1) * (button_width + actual_gap),
|
||||
y,
|
||||
button_width,
|
||||
height,
|
||||
button.handler,
|
||||
child_opts(parent, merge({
|
||||
layer = 920,
|
||||
color = button.color or theme_color("primary"),
|
||||
radius = 10
|
||||
}, opts))
|
||||
))
|
||||
end
|
||||
return nodes
|
||||
end
|
||||
|
||||
---@param id string
|
||||
---@param title string
|
||||
---@param message string
|
||||
---@param x number
|
||||
---@param y number
|
||||
---@param width number
|
||||
---@param height number
|
||||
---@param opts? RuntimeDialogOpts
|
||||
---@return RuntimeNode[]
|
||||
function widgets.dialog(id, title, message, x, y, width, height, opts)
|
||||
local options = opts or {}
|
||||
local nodes = {}
|
||||
local layer = options.layer or 900
|
||||
local screen_width = options.screenWidth or 720
|
||||
local screen_height = options.screenHeight or 720
|
||||
|
||||
if options.overlay ~= false then
|
||||
table.insert(nodes, widgets.overlay(id .. "_overlay", screen_width, screen_height, {
|
||||
layer = layer,
|
||||
color = options.overlayColor or theme_color("overlay"),
|
||||
interactive = options.blockInput == true
|
||||
}))
|
||||
end
|
||||
|
||||
table.insert(nodes, widgets.card(id, x, y, width, height, merge({
|
||||
layer = layer + 1,
|
||||
color = options.color or theme_color("card"),
|
||||
radius = options.radius or 16
|
||||
}, options.panelStyle)))
|
||||
|
||||
table.insert(nodes, runtime_ui.text(
|
||||
id .. "_title",
|
||||
title or "",
|
||||
20,
|
||||
18,
|
||||
width - 40,
|
||||
32,
|
||||
child_opts(id, merge({
|
||||
layer = layer + 2,
|
||||
color = options.titleColor or theme_color("text"),
|
||||
fontSize = options.titleSize or 22
|
||||
}, options.titleStyle))
|
||||
))
|
||||
|
||||
table.insert(nodes, runtime_ui.text(
|
||||
id .. "_message",
|
||||
message or "",
|
||||
20,
|
||||
60,
|
||||
width - 40,
|
||||
height - 116,
|
||||
child_opts(id, merge({
|
||||
layer = layer + 2,
|
||||
color = options.messageColor or theme_color("muted"),
|
||||
fontSize = options.messageSize or 16
|
||||
}, options.messageStyle))
|
||||
))
|
||||
|
||||
append_all(nodes, widgets.button_row(
|
||||
id,
|
||||
id,
|
||||
options.buttons or {},
|
||||
20,
|
||||
height - 48,
|
||||
width - 40,
|
||||
34,
|
||||
options.buttonGap or 8,
|
||||
options.buttonStyle
|
||||
))
|
||||
|
||||
return nodes
|
||||
end
|
||||
|
||||
return widgets
|
||||
Reference in New Issue
Block a user