Initial flame_lua_runtime package
22
example/assets/games/flight/manifest.json
Normal file
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"gameId": "flight",
|
||||
"name": "Lua 飞行棋基础版",
|
||||
"version": "0.1.0",
|
||||
"runtimeApiVersion": 1,
|
||||
"entry": "scripts/main.lua",
|
||||
"assetsBase": "assets",
|
||||
"modules": {
|
||||
"runtime_ui": "runtime:runtime_ui.lua",
|
||||
"runtime_widgets": "runtime:runtime_widgets.lua",
|
||||
"runtime_commands": "runtime:runtime_commands.lua",
|
||||
"layout": "runtime:layout.lua",
|
||||
"theme": "scripts/theme.lua",
|
||||
"styles": "scripts/styles.lua",
|
||||
"state": "scripts/state.lua",
|
||||
"board": "scripts/board.lua",
|
||||
"rules": "scripts/rules.lua",
|
||||
"ui": "scripts/ui.lua",
|
||||
"animation": "scripts/animation.lua"
|
||||
},
|
||||
"resources": {}
|
||||
}
|
||||
17
example/assets/games/flight/scripts/animation.lua
Normal file
@@ -0,0 +1,17 @@
|
||||
---@type RuntimeCommands
|
||||
local commands = runtime.import("runtime_commands")
|
||||
|
||||
local animation = {}
|
||||
|
||||
function animation.move_piece(piece_id, path)
|
||||
return commands.move_path(piece_id, path, {
|
||||
duration = 0.55,
|
||||
onComplete = "piece_move_done"
|
||||
})
|
||||
end
|
||||
|
||||
function animation.toast(text)
|
||||
return commands.toast(text)
|
||||
end
|
||||
|
||||
return animation
|
||||
91
example/assets/games/flight/scripts/board.lua
Normal file
@@ -0,0 +1,91 @@
|
||||
local board = {
|
||||
route_length = 40,
|
||||
finish_progress = 40,
|
||||
start = {
|
||||
red = 1,
|
||||
yellow = 11,
|
||||
blue = 21,
|
||||
green = 31
|
||||
},
|
||||
homes = {
|
||||
red = {
|
||||
{ x = 92, y = 132 },
|
||||
{ x = 142, y = 132 }
|
||||
},
|
||||
yellow = {
|
||||
{ x = 518, y = 132 },
|
||||
{ x = 568, y = 132 }
|
||||
},
|
||||
blue = {
|
||||
{ x = 518, y = 548 },
|
||||
{ x = 568, y = 548 }
|
||||
},
|
||||
green = {
|
||||
{ x = 92, y = 548 },
|
||||
{ x = 142, y = 548 }
|
||||
}
|
||||
},
|
||||
finish = {
|
||||
red = {
|
||||
{ x = 290, y = 330 },
|
||||
{ x = 250, y = 330 }
|
||||
},
|
||||
yellow = {
|
||||
{ x = 350, y = 330 },
|
||||
{ x = 390, y = 330 }
|
||||
},
|
||||
blue = {
|
||||
{ x = 350, y = 390 },
|
||||
{ x = 390, y = 390 }
|
||||
},
|
||||
green = {
|
||||
{ x = 290, y = 390 },
|
||||
{ x = 250, y = 390 }
|
||||
}
|
||||
},
|
||||
route = {}
|
||||
}
|
||||
|
||||
local function add(x, y)
|
||||
table.insert(board.route, { x = x, y = y })
|
||||
end
|
||||
|
||||
for i = 0, 9 do add(200 + i * 32, 104) end
|
||||
for i = 0, 9 do add(520, 136 + i * 32) end
|
||||
for i = 0, 9 do add(488 - i * 32, 456) end
|
||||
for i = 0, 9 do add(168, 424 - i * 32) end
|
||||
|
||||
function board.home_position(piece)
|
||||
local index = piece.id:sub(-1) == "2" and 2 or 1
|
||||
return board.homes[piece.owner][index]
|
||||
end
|
||||
|
||||
function board.finish_position(piece)
|
||||
local index = piece.id:sub(-1) == "2" and 2 or 1
|
||||
return board.finish[piece.owner][index]
|
||||
end
|
||||
|
||||
function board.route_index(owner, progress)
|
||||
local start = board.start[owner]
|
||||
local index = start + progress - 1
|
||||
while index > board.route_length do
|
||||
index = index - board.route_length
|
||||
end
|
||||
return index
|
||||
end
|
||||
|
||||
function board.route_position(owner, progress)
|
||||
return board.route[board.route_index(owner, progress)]
|
||||
end
|
||||
|
||||
function board.position(piece)
|
||||
if piece.status == "home" then
|
||||
return board.home_position(piece)
|
||||
end
|
||||
if piece.status == "finished" then
|
||||
return board.finish_position(piece)
|
||||
end
|
||||
return board.route_position(piece.owner, piece.progress)
|
||||
end
|
||||
|
||||
return board
|
||||
154
example/assets/games/flight/scripts/main.lua
Normal file
@@ -0,0 +1,154 @@
|
||||
local state = runtime.import("state")
|
||||
local rules = runtime.import("rules")
|
||||
local ui = runtime.import("ui")
|
||||
local animation = runtime.import("animation")
|
||||
---@type RuntimeWidgets
|
||||
local widgets = runtime.import("runtime_widgets")
|
||||
local theme = runtime.import("theme")
|
||||
|
||||
widgets.configure({
|
||||
primary = theme.colors.dice_button,
|
||||
secondary = theme.colors.board_inner,
|
||||
success = theme.colors.green,
|
||||
overlay = "#99000000",
|
||||
surface = theme.colors.top_bar,
|
||||
surfaceAlt = theme.colors.board_inner,
|
||||
card = theme.colors.top_bar,
|
||||
text = theme.colors.text,
|
||||
muted = theme.colors.muted_text,
|
||||
progress = theme.colors.green,
|
||||
transparent = "#00000000"
|
||||
})
|
||||
|
||||
function smoke_test(ctx)
|
||||
return ctx ~= nil
|
||||
and ctx.runtimeApiVersion ~= nil
|
||||
and state.current_player ~= nil
|
||||
and rules.plan_move ~= nil
|
||||
and ui.create_board_nodes ~= nil
|
||||
and animation.move_piece ~= nil
|
||||
and widgets.dialog ~= nil
|
||||
end
|
||||
|
||||
function init(ctx)
|
||||
return {
|
||||
render = { creates = ui.create_board_nodes() },
|
||||
ui = { creates = ui.create_ui_nodes() },
|
||||
commands = {}
|
||||
}
|
||||
end
|
||||
|
||||
local function handle_roll_dice()
|
||||
if state.phase == "game_over" then
|
||||
return { commands = { animation.toast("游戏已结束") } }
|
||||
end
|
||||
|
||||
if state.phase ~= "waiting_roll" then
|
||||
return { commands = { animation.toast("请先完成当前移动") } }
|
||||
end
|
||||
|
||||
state.dice = rules.next_dice()
|
||||
local movable = rules.movable_pieces()
|
||||
|
||||
if #movable == 0 then
|
||||
local message = state.current_player .. " 无可移动飞机"
|
||||
if state.dice ~= 6 then
|
||||
state.current_player = rules.next_player()
|
||||
end
|
||||
state.phase = "waiting_roll"
|
||||
return {
|
||||
ui = { updates = ui.dice_and_status_updates() },
|
||||
render = { updates = ui.highlight_updates(rules.all_piece_ids(), false) },
|
||||
commands = { animation.toast(message) }
|
||||
}
|
||||
end
|
||||
|
||||
state.phase = "waiting_piece"
|
||||
return {
|
||||
ui = { updates = ui.dice_and_status_updates() },
|
||||
render = { updates = ui.highlight_updates(movable, true) }
|
||||
}
|
||||
end
|
||||
|
||||
local function handle_piece_tap(piece_id)
|
||||
if state.phase ~= "waiting_piece" then
|
||||
return {}
|
||||
end
|
||||
|
||||
local piece = state.pieces[piece_id]
|
||||
if piece == nil or piece.owner ~= state.current_player or not rules.can_move(piece, state.dice) then
|
||||
return { commands = { animation.toast("该飞机不能移动") } }
|
||||
end
|
||||
|
||||
local plan = rules.plan_move(piece, state.dice)
|
||||
state.phase = "animating"
|
||||
state.selected_piece = piece_id
|
||||
state.pending_move = plan
|
||||
|
||||
return {
|
||||
ui = { updates = ui.dice_and_status_updates() },
|
||||
render = { updates = ui.highlight_updates(rules.all_piece_ids(), false) },
|
||||
commands = { animation.move_piece(piece_id, plan.path) }
|
||||
}
|
||||
end
|
||||
|
||||
local function handle_move_done()
|
||||
local plan = state.pending_move
|
||||
if plan == nil then
|
||||
state.phase = "waiting_roll"
|
||||
return { ui = { updates = ui.dice_and_status_updates() } }
|
||||
end
|
||||
|
||||
rules.commit_move(plan)
|
||||
local moved_piece = state.pieces[plan.piece_id]
|
||||
local updates = ui.piece_position_updates(plan.captures)
|
||||
table.insert(updates, ui.piece_position_update(plan.piece_id))
|
||||
|
||||
local messages = {}
|
||||
if #plan.captures > 0 then
|
||||
table.insert(messages, "撞回 " .. tostring(#plan.captures) .. " 架飞机")
|
||||
end
|
||||
|
||||
if rules.player_finished(moved_piece.owner) then
|
||||
state.winner = moved_piece.owner
|
||||
state.phase = "game_over"
|
||||
table.insert(messages, moved_piece.owner .. " 获胜")
|
||||
else
|
||||
if state.dice ~= 6 then
|
||||
state.current_player = rules.next_player()
|
||||
else
|
||||
table.insert(messages, "掷出 6,额外回合")
|
||||
end
|
||||
state.phase = "waiting_roll"
|
||||
end
|
||||
|
||||
state.selected_piece = nil
|
||||
state.pending_move = nil
|
||||
|
||||
local commands = {}
|
||||
if #messages > 0 then
|
||||
commands = { animation.toast(table.concat(messages, ";")) }
|
||||
end
|
||||
|
||||
return {
|
||||
ui = { updates = ui.dice_and_status_updates() },
|
||||
render = { updates = updates },
|
||||
commands = commands
|
||||
}
|
||||
end
|
||||
|
||||
function on_event(event)
|
||||
if event.handler == "roll_dice" then
|
||||
return handle_roll_dice()
|
||||
end
|
||||
|
||||
if event.handler == "piece_tap" then
|
||||
return handle_piece_tap(event.target)
|
||||
end
|
||||
|
||||
if event.handler == "piece_move_done" then
|
||||
return handle_move_done()
|
||||
end
|
||||
|
||||
return {}
|
||||
end
|
||||
153
example/assets/games/flight/scripts/rules.lua
Normal file
@@ -0,0 +1,153 @@
|
||||
local state = runtime.import("state")
|
||||
local board = runtime.import("board")
|
||||
|
||||
local rules = {}
|
||||
|
||||
local function piece_index(piece_id)
|
||||
return piece_id:sub(-1) == "2" and 2 or 1
|
||||
end
|
||||
|
||||
function rules.next_player()
|
||||
for index, player in ipairs(state.players) do
|
||||
if player == state.current_player then
|
||||
local next_index = index + 1
|
||||
if next_index > #state.players then
|
||||
next_index = 1
|
||||
end
|
||||
return state.players[next_index]
|
||||
end
|
||||
end
|
||||
return state.players[1]
|
||||
end
|
||||
|
||||
function rules.next_dice()
|
||||
state.dice_index = state.dice_index + 1
|
||||
if state.dice_index > #state.dice_values then
|
||||
state.dice_index = 1
|
||||
end
|
||||
return state.dice_values[state.dice_index]
|
||||
end
|
||||
|
||||
function rules.can_move(piece, dice)
|
||||
if piece == nil or piece.status == "finished" then
|
||||
return false
|
||||
end
|
||||
if piece.status == "home" then
|
||||
return dice == 6
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
function rules.movable_pieces()
|
||||
local result = {}
|
||||
for id, piece in pairs(state.pieces) do
|
||||
if piece.owner == state.current_player and rules.can_move(piece, state.dice) then
|
||||
table.insert(result, id)
|
||||
end
|
||||
end
|
||||
return result
|
||||
end
|
||||
|
||||
function rules.all_piece_ids()
|
||||
local result = {}
|
||||
for id, _ in pairs(state.pieces) do
|
||||
table.insert(result, id)
|
||||
end
|
||||
return result
|
||||
end
|
||||
|
||||
function rules.piece_position(piece)
|
||||
return board.position(piece)
|
||||
end
|
||||
|
||||
function rules.plan_move(piece, dice)
|
||||
local path = {}
|
||||
local final_status = "route"
|
||||
local final_progress = piece.progress
|
||||
|
||||
if piece.status == "home" then
|
||||
final_progress = 1
|
||||
table.insert(path, board.route_position(piece.owner, final_progress))
|
||||
else
|
||||
for step = 1, dice do
|
||||
local progress = piece.progress + step
|
||||
if progress >= board.finish_progress then
|
||||
final_status = "finished"
|
||||
final_progress = board.finish_progress
|
||||
table.insert(path, board.finish_position(piece))
|
||||
break
|
||||
end
|
||||
table.insert(path, board.route_position(piece.owner, progress))
|
||||
final_progress = progress
|
||||
end
|
||||
end
|
||||
|
||||
local captures = {}
|
||||
if final_status == "route" then
|
||||
local landing_index = board.route_index(piece.owner, final_progress)
|
||||
for id, other in pairs(state.pieces) do
|
||||
if id ~= piece.id and other.owner ~= piece.owner and other.status == "route" then
|
||||
if board.route_index(other.owner, other.progress) == landing_index then
|
||||
table.insert(captures, id)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return {
|
||||
piece_id = piece.id,
|
||||
final_status = final_status,
|
||||
final_progress = final_progress,
|
||||
path = path,
|
||||
captures = captures
|
||||
}
|
||||
end
|
||||
|
||||
function rules.commit_move(plan)
|
||||
local piece = state.pieces[plan.piece_id]
|
||||
piece.status = plan.final_status
|
||||
piece.progress = plan.final_progress
|
||||
|
||||
for _, captured_id in ipairs(plan.captures) do
|
||||
local captured = state.pieces[captured_id]
|
||||
captured.status = "home"
|
||||
captured.progress = 0
|
||||
end
|
||||
end
|
||||
|
||||
function rules.player_finished(owner)
|
||||
for _, piece in pairs(state.pieces) do
|
||||
if piece.owner == owner and piece.status ~= "finished" then
|
||||
return false
|
||||
end
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
function rules.status_text()
|
||||
if state.winner ~= nil then
|
||||
return "胜利者: " .. state.winner
|
||||
end
|
||||
if state.phase == "waiting_roll" then
|
||||
return "请 " .. state.current_player .. " 掷骰子"
|
||||
end
|
||||
if state.phase == "waiting_piece" then
|
||||
return "请选择 " .. state.current_player .. " 的飞机"
|
||||
end
|
||||
if state.phase == "animating" then
|
||||
return "飞机移动中..."
|
||||
end
|
||||
return "准备中"
|
||||
end
|
||||
|
||||
function rules.piece_label(piece)
|
||||
if piece.status == "home" then
|
||||
return "待飞"
|
||||
end
|
||||
if piece.status == "finished" then
|
||||
return "到达"
|
||||
end
|
||||
return tostring(piece.progress)
|
||||
end
|
||||
|
||||
return rules
|
||||
589
example/assets/games/flight/scripts/runtime_defs.lua
Normal file
@@ -0,0 +1,589 @@
|
||||
---@meta
|
||||
--- COMMON RUNTIME TYPES SECTION.
|
||||
--- Source of truth: tool/lua_runtime_defs_common.lua
|
||||
--- After editing this common section, run:
|
||||
--- dart run tool/generate_lua_runtime_defs.dart
|
||||
|
||||
|
||||
---@alias RuntimeNodeType
|
||||
---| 'panel'
|
||||
---| 'button'
|
||||
---| 'text'
|
||||
---| 'circle'
|
||||
---| 'rect'
|
||||
---| 'line'
|
||||
---| 'progress'
|
||||
---| 'listView'
|
||||
---| 'sprite'
|
||||
---| 'image'
|
||||
---| 'spine'
|
||||
---| 'particle'
|
||||
|
||||
---@alias RuntimeAnchor
|
||||
---| 'center'
|
||||
---| 'topLeft'
|
||||
---| 'topRight'
|
||||
---| 'bottomLeft'
|
||||
---| 'bottomRight'
|
||||
|
||||
---@alias RuntimeTextAlign
|
||||
---| 'left'
|
||||
---| 'center'
|
||||
---| 'right'
|
||||
|
||||
---@alias RuntimeParticlePreset
|
||||
---| 'burst'
|
||||
---| 'trail'
|
||||
---| 'snow'
|
||||
---| 'confetti'
|
||||
|
||||
---@alias RuntimeCommandType
|
||||
---| 'move_path'
|
||||
---| 'move_to'
|
||||
---| 'fade_to'
|
||||
---| 'scale_to'
|
||||
---| 'rotate_to'
|
||||
---| 'remove_node'
|
||||
---| 'sequence'
|
||||
---| 'parallel'
|
||||
---| 'delay'
|
||||
---| 'toast'
|
||||
---| 'play_sound'
|
||||
---| 'play_bgm'
|
||||
---| 'pause_bgm'
|
||||
---| 'resume_bgm'
|
||||
---| 'stop_bgm'
|
||||
---| 'preload_resources'
|
||||
---| 'evict_resources'
|
||||
---| 'cancel_commands'
|
||||
---| 'play_spine_animation'
|
||||
---| 'copy_text'
|
||||
|
||||
---@alias RuntimeEventType
|
||||
---| 'tap'
|
||||
---| 'animation_done'
|
||||
---| 'resize'
|
||||
---| 'scroll'
|
||||
|
||||
---@alias RuntimeScaleMode
|
||||
---| 'fit'
|
||||
---| 'fill'
|
||||
---| 'stretch'
|
||||
---| 'none'
|
||||
|
||||
---@alias RuntimeLayoutAlign
|
||||
---| 'start'
|
||||
---| 'center'
|
||||
---| 'end'
|
||||
|
||||
---@alias RuntimeButtonVariant
|
||||
---| 'primary'
|
||||
---| 'secondary'
|
||||
---| 'ghost'
|
||||
|
||||
---@class (exact) RuntimeNode
|
||||
---@field id string
|
||||
---@field type RuntimeNodeType
|
||||
---@field parent? string
|
||||
---@field asset? string Normal image/sprite/spine asset key. For button nodes this is the normal-state image.
|
||||
---@field pressedAsset? string Button pressed-state image asset key.
|
||||
---@field disabledAsset? string Button disabled-state image asset key.
|
||||
---@field animation? string
|
||||
---@field skin? string
|
||||
---@field loop? boolean
|
||||
---@field text? string
|
||||
---@field x? number
|
||||
---@field y? number
|
||||
---@field width? number
|
||||
---@field height? number
|
||||
---@field paddingLeft? number
|
||||
---@field paddingTop? number
|
||||
---@field paddingRight? number
|
||||
---@field paddingBottom? number
|
||||
---@field anchor? RuntimeAnchor
|
||||
---@field layer? integer
|
||||
---@field visible? boolean
|
||||
---@field alpha? number
|
||||
---@field scale? number
|
||||
---@field rotation? number
|
||||
---@field color? string
|
||||
---@field fontSize? number
|
||||
---@field textAlign? RuntimeTextAlign
|
||||
---@field radius? number
|
||||
---@field strokeWidth? number
|
||||
---@field value? number
|
||||
---@field scrollX? number
|
||||
---@field scrollY? number
|
||||
---@field contentWidth? number
|
||||
---@field contentHeight? number
|
||||
---@field virtualized? boolean
|
||||
---@field cacheExtent? number
|
||||
---@field inertia? boolean
|
||||
---@field scrollbarThumbColor? string
|
||||
---@field scrollbarTrackColor? string
|
||||
---@field scrollbarThickness? number
|
||||
---@field scrollbarVisible? boolean
|
||||
---@field interactive? boolean
|
||||
---@field onTap? string
|
||||
---@field onScroll? string
|
||||
---@field preset? RuntimeParticlePreset
|
||||
---@field count? integer
|
||||
---@field duration? number
|
||||
---@field speedMin? number
|
||||
---@field speedMax? number
|
||||
---@field gravityX? number
|
||||
---@field gravityY? number
|
||||
---@field spread? number
|
||||
---@field colorTo? string
|
||||
---@field radiusTo? number
|
||||
---@field autoRemove? boolean
|
||||
---@field fadeOut? boolean
|
||||
|
||||
---@class (exact) RuntimeNodeProps
|
||||
---@field type? RuntimeNodeType
|
||||
---@field parent? string
|
||||
---@field asset? string Normal image/sprite/spine asset key. For button nodes this is the normal-state image.
|
||||
---@field pressedAsset? string Button pressed-state image asset key.
|
||||
---@field disabledAsset? string Button disabled-state image asset key.
|
||||
---@field animation? string
|
||||
---@field skin? string
|
||||
---@field loop? boolean
|
||||
---@field text? string
|
||||
---@field x? number
|
||||
---@field y? number
|
||||
---@field width? number
|
||||
---@field height? number
|
||||
---@field paddingLeft? number
|
||||
---@field paddingTop? number
|
||||
---@field paddingRight? number
|
||||
---@field paddingBottom? number
|
||||
---@field anchor? RuntimeAnchor
|
||||
---@field layer? integer
|
||||
---@field visible? boolean
|
||||
---@field alpha? number
|
||||
---@field scale? number
|
||||
---@field rotation? number
|
||||
---@field color? string
|
||||
---@field fontSize? number
|
||||
---@field textAlign? RuntimeTextAlign
|
||||
---@field radius? number
|
||||
---@field strokeWidth? number
|
||||
---@field value? number
|
||||
---@field scrollX? number
|
||||
---@field scrollY? number
|
||||
---@field contentWidth? number
|
||||
---@field contentHeight? number
|
||||
---@field virtualized? boolean
|
||||
---@field cacheExtent? number
|
||||
---@field inertia? boolean
|
||||
---@field scrollbarThumbColor? string
|
||||
---@field scrollbarTrackColor? string
|
||||
---@field scrollbarThickness? number
|
||||
---@field scrollbarVisible? boolean
|
||||
---@field interactive? boolean
|
||||
---@field onTap? string
|
||||
---@field onScroll? string
|
||||
---@field preset? RuntimeParticlePreset
|
||||
---@field count? integer
|
||||
---@field duration? number
|
||||
---@field speedMin? number
|
||||
---@field speedMax? number
|
||||
---@field gravityX? number
|
||||
---@field gravityY? number
|
||||
---@field spread? number
|
||||
---@field colorTo? string
|
||||
---@field radiusTo? number
|
||||
---@field autoRemove? boolean
|
||||
---@field fadeOut? boolean
|
||||
|
||||
---Helper-only fields accepted by runtime_ui/runtime_widgets. They are normalized
|
||||
---before the node/update crosses the Dart Runtime protocol boundary.
|
||||
---@class RuntimeNodeInit: RuntimeNodeProps
|
||||
---@field w? number Alias for width.
|
||||
---@field h? number Alias for height.
|
||||
---@field size? number Alias for both width and height.
|
||||
---@field handler? string Alias for onTap.
|
||||
---@field onClick? string Alias for onTap.
|
||||
|
||||
---@class (exact) RuntimeNodeUpdate
|
||||
---@field id string
|
||||
---@field props RuntimeNodeProps
|
||||
|
||||
---@class (exact) RuntimeNodeRemove
|
||||
---@field id string
|
||||
|
||||
---@class (exact) RuntimeDiffSection
|
||||
---@field creates? RuntimeNode[]
|
||||
---@field updates? RuntimeNodeUpdate[]
|
||||
---@field removes? (string|RuntimeNodeRemove)[]
|
||||
|
||||
---@class (exact) RuntimeDiff
|
||||
---@field render? RuntimeDiffSection
|
||||
---@field ui? RuntimeDiffSection
|
||||
---@field commands? RuntimeCommand[]
|
||||
|
||||
---@class (exact) RuntimeEvent
|
||||
---@field type RuntimeEventType|string
|
||||
---@field target? string
|
||||
---@field handler? string
|
||||
---@field x? number
|
||||
---@field y? number
|
||||
---@field data? table
|
||||
|
||||
---@class (exact) RuntimeCommand
|
||||
---@field type RuntimeCommandType
|
||||
---@field target? string
|
||||
---@field scope? string
|
||||
---@field id? string
|
||||
---@field group? string
|
||||
---@field commandGroup? string
|
||||
---@field onComplete? string
|
||||
---@field duration? number
|
||||
---@field commands? RuntimeCommand[]
|
||||
---@field path? RuntimePoint[]
|
||||
---@field x? number
|
||||
---@field y? number
|
||||
---@field alpha? number
|
||||
---@field scale? number
|
||||
---@field angle? number
|
||||
---@field text? string
|
||||
---@field message? string
|
||||
---@field asset? string
|
||||
---@field name? string
|
||||
---@field volume? number
|
||||
---@field channel? string
|
||||
---@field loop? boolean
|
||||
---@field failOnError? boolean
|
||||
---@field animation? string
|
||||
---@field track? integer
|
||||
---@field queue? boolean
|
||||
---@field delay? number
|
||||
|
||||
---@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
|
||||
---@field name? string
|
||||
|
||||
---@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 (exact) RuntimePoint
|
||||
---@field x number
|
||||
---@field y number
|
||||
|
||||
---@class (exact) RuntimeLocaleContext
|
||||
---@field requested string
|
||||
---@field resolved string
|
||||
---@field default string
|
||||
---@field supported string[]
|
||||
---@field languageCode string
|
||||
---@field scriptCode? string
|
||||
---@field countryCode? string
|
||||
|
||||
---@class (exact) RuntimeScreenContext
|
||||
---@field width number
|
||||
---@field height number
|
||||
|
||||
---@class (exact) RuntimeDesignContext
|
||||
---@field width number
|
||||
---@field height number
|
||||
|
||||
---@class (exact) RuntimeViewportContext
|
||||
---@field x number
|
||||
---@field y number
|
||||
---@field width number
|
||||
---@field height number
|
||||
---@field scaleX number
|
||||
---@field scaleY number
|
||||
---@field scaleMode RuntimeScaleMode|string
|
||||
|
||||
---@class (exact) RuntimeContext
|
||||
---@field screen RuntimeScreenContext
|
||||
---@field design RuntimeDesignContext
|
||||
---@field viewport RuntimeViewportContext
|
||||
---@field seed integer
|
||||
---@field runtimeApiVersion integer
|
||||
---@field gameId string
|
||||
---@field gameVersion string
|
||||
---@field locale? RuntimeLocaleContext
|
||||
|
||||
---@class RuntimeUi
|
||||
---@field style fun(base?: RuntimeNodeProps, opts?: RuntimeNodeProps): RuntimeNodeProps
|
||||
---@field with_parent fun(parent: string, opts?: RuntimeNodeProps): RuntimeNodeProps
|
||||
---@field node fun(node_type: RuntimeNodeType, id: string, opts?: RuntimeNodeInit): RuntimeNode
|
||||
---@field panel fun(id: string, x: number|RuntimeNodeInit, y?: number, width?: number, height?: number, opts?: RuntimeNodeInit): RuntimeNode
|
||||
---@field rect fun(id: string, x: number|RuntimeNodeInit, y?: number, width?: number, height?: number, opts?: RuntimeNodeInit): RuntimeNode
|
||||
---@field circle fun(id: string, x: number|RuntimeNodeInit, y?: number, size?: number, opts?: RuntimeNodeInit): RuntimeNode
|
||||
---@field line fun(id: string, x: number|RuntimeNodeInit, y?: number, width?: number, height?: number, opts?: RuntimeNodeInit): RuntimeNode
|
||||
---@field progress fun(id: string, x: number|RuntimeNodeInit, y?: number, width?: number, height?: number, value?: number, opts?: RuntimeNodeInit): RuntimeNode
|
||||
---@field particle fun(id: string, x: number|RuntimeNodeInit, y?: number, width?: number, height?: number, opts?: RuntimeNodeInit): RuntimeNode
|
||||
---@field text fun(id: string, text: string|RuntimeNodeInit, x?: number, y?: number, width?: number, height?: number, opts?: RuntimeNodeInit): RuntimeNode
|
||||
---@field button fun(id: string, text: string|RuntimeNodeInit, x?: number, y?: number, width?: number, height?: number, handler?: string, opts?: RuntimeNodeInit): RuntimeNode
|
||||
---@field list_view fun(id: string, x: number|RuntimeNodeInit, y?: number, width?: number, height?: number, opts?: RuntimeNodeInit): RuntimeNode
|
||||
---@field image fun(id: string, asset: string|RuntimeNodeInit, x?: number, y?: number, width?: number, height?: number, opts?: RuntimeNodeInit): RuntimeNode
|
||||
---@field sprite fun(id: string, asset: string|RuntimeNodeInit, x?: number, y?: number, width?: number, height?: number, opts?: RuntimeNodeInit): RuntimeNode
|
||||
---@field spine fun(id: string, asset: string|RuntimeNodeInit, x?: number, y?: number, width?: number, height?: number, animation?: string, opts?: RuntimeNodeInit): RuntimeNode
|
||||
---@field update fun(id: string, props: RuntimeNodeInit): RuntimeNodeUpdate
|
||||
---@field text_update fun(id: string, text: string): RuntimeNodeUpdate
|
||||
---@field visible_update fun(id: string, visible: boolean): RuntimeNodeUpdate
|
||||
---@field alpha_update fun(id: string, alpha: number): RuntimeNodeUpdate
|
||||
---@field scale_update fun(id: string, scale: number): RuntimeNodeUpdate
|
||||
---@field position_update fun(id: string, x: number, y: number): RuntimeNodeUpdate
|
||||
---@field size_update fun(id: string, width: number, height: number): RuntimeNodeUpdate
|
||||
---@field transform_update fun(id: string, x: number, y: number, scale: number, rotation: number): RuntimeNodeUpdate
|
||||
---@field batch_update fun(ids: string[], props: RuntimeNodeInit): RuntimeNodeUpdate[]
|
||||
---@field append fun(nodes: RuntimeNode[], node: RuntimeNode): RuntimeNode[]
|
||||
---@field append_all fun(nodes: RuntimeNode[], extra_nodes: RuntimeNode[]): RuntimeNode[]
|
||||
|
||||
---@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 RuntimeLabeledProgressOpts: RuntimeNodeInit
|
||||
---@field labelHeight? number
|
||||
---@field labelStyle? RuntimeNodeProps
|
||||
|
||||
---@class RuntimePillOpts: RuntimeNodeInit
|
||||
---@field panelStyle? RuntimeNodeProps
|
||||
---@field textStyle? RuntimeNodeProps
|
||||
|
||||
---@class RuntimeTextButtonOpts: RuntimeNodeInit
|
||||
---@field variant? RuntimeButtonVariant
|
||||
|
||||
---@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
|
||||
---@field configure fun(tokens?: RuntimeWidgetTheme): RuntimeWidgets
|
||||
---@field label fun(id: string, text: string|RuntimeNodeInit, x?: number, y?: number, width?: number, height?: number, opts?: RuntimeNodeInit): RuntimeNode
|
||||
---@field section_title fun(id: string, text: string|RuntimeNodeInit, x?: number, y?: number, width?: number, height?: number, opts?: RuntimeNodeInit): RuntimeNode
|
||||
---@field pill fun(id: string, text: string|RuntimePillOpts, x?: number, y?: number, width?: number, height?: number, opts?: RuntimePillOpts): RuntimeNode[]
|
||||
---@field text_button fun(id: string, text: string|RuntimeTextButtonOpts, x?: number, y?: number, width?: number, height?: number, handler?: string, opts?: RuntimeTextButtonOpts): RuntimeNode
|
||||
---@field list_item fun(id: string, text: string|RuntimeListItemOpts, x?: number, y?: number, width?: number, height?: number, handler?: string, opts?: RuntimeListItemOpts): RuntimeNode
|
||||
---@field tabs fun(id: string, tabs: RuntimeTabItem[]|RuntimeTabsOpts, opts?: RuntimeTabsOpts): RuntimeNode[]
|
||||
---@field action_row fun(id: string, actions: RuntimeActionItem[]|RuntimeActionRowOpts, opts?: RuntimeActionRowOpts): RuntimeNode[]
|
||||
---@field panel_header fun(id: string, opts: RuntimePanelHeaderOpts): RuntimeNode[]
|
||||
---@field overlay fun(id: string, width: number|RuntimeNodeInit, height?: number, opts?: RuntimeNodeInit): RuntimeNode
|
||||
---@field card fun(id: string, x: number|RuntimeNodeInit, y?: number, width?: number, height?: number, opts?: RuntimeNodeInit): RuntimeNode
|
||||
---@field progress_bar fun(id: string, x: number|RuntimeNodeInit, y?: number, width?: number, height?: number, value?: number, opts?: RuntimeNodeInit): RuntimeNode
|
||||
---@field labeled_progress fun(id: string, label: string, x: number, y: number, width: number, height: number, value: number, opts?: RuntimeLabeledProgressOpts): RuntimeNode[]
|
||||
---@field button_row fun(parent: string, id: string, buttons: RuntimeDialogButton[], x: number, y: number, width: number, height: number, gap?: number, opts?: RuntimeNodeProps): RuntimeNode[]
|
||||
---@field dialog fun(id: string, title: string, message: string, x: number, y: number, width: number, height: number, opts?: RuntimeDialogOpts): RuntimeNode[]
|
||||
|
||||
---@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
|
||||
---@field item fun(node: RuntimeNode, opts?: RuntimeLayoutItemOpts): RuntimeLayoutItem
|
||||
---@field local_position fun(origin: RuntimePoint, position: RuntimePoint): RuntimePoint
|
||||
---@field row fun(parent?: string, items: (RuntimeNode|RuntimeLayoutItem)[], opts?: RuntimeLinearLayoutOpts): RuntimeNode[]
|
||||
---@field column fun(parent?: string, items: (RuntimeNode|RuntimeLayoutItem)[], opts?: RuntimeLinearLayoutOpts): RuntimeNode[]
|
||||
---@field stack fun(parent?: string, items: (RuntimeNode|RuntimeLayoutItem)[], opts?: RuntimeLinearLayoutOpts): RuntimeNode[]
|
||||
---@field box fun(parent?: string, items: (RuntimeNode|RuntimeLayoutItem)[], opts?: RuntimeBoxLayoutOpts): RuntimeNode[]
|
||||
|
||||
---@class RuntimeCommands
|
||||
---@field toast fun(text: string, opts?: RuntimeCommandOpts): RuntimeCommand
|
||||
---@field copy_text fun(text: string, opts?: RuntimeCommandOpts): RuntimeCommand
|
||||
---@field delay fun(duration: number, opts?: RuntimeCommandOpts): RuntimeCommand
|
||||
---@field sequence fun(items: RuntimeCommand[], opts?: RuntimeCommandOpts): RuntimeCommand
|
||||
---@field parallel fun(items: RuntimeCommand[], opts?: RuntimeCommandOpts): RuntimeCommand
|
||||
---@field move_path fun(target: string, path: RuntimePoint[], opts?: RuntimeCommandOpts): RuntimeCommand
|
||||
---@field move_to fun(target: string, x: number, y: number, opts?: RuntimeCommandOpts): RuntimeCommand
|
||||
---@field fade_to fun(target: string, alpha: number, opts?: RuntimeCommandOpts): RuntimeCommand
|
||||
---@field scale_to fun(target: string, scale: number, opts?: RuntimeCommandOpts): RuntimeCommand
|
||||
---@field rotate_to fun(target: string, angle: number, opts?: RuntimeCommandOpts): RuntimeCommand
|
||||
---@field remove_node fun(target: string, opts?: RuntimeCommandOpts): RuntimeCommand
|
||||
---@field play_spine_animation fun(target: string, animation: string, opts?: RuntimeSpineCommandOpts): RuntimeCommand
|
||||
---@field play_sound fun(asset: string, opts?: RuntimeAudioCommandOpts): RuntimeCommand
|
||||
---@field play_bgm fun(asset: string, opts?: RuntimeBgmCommandOpts): RuntimeCommand
|
||||
---@field pause_bgm fun(channel?: string, opts?: RuntimeCommandOpts): RuntimeCommand
|
||||
---@field resume_bgm fun(channel?: string, opts?: RuntimeCommandOpts): RuntimeCommand
|
||||
---@field stop_bgm fun(channel?: string, opts?: RuntimeCommandOpts): RuntimeCommand
|
||||
---@field preload_group fun(group: string, opts?: RuntimeResourceCommandOpts): RuntimeCommand
|
||||
---@field evict_group fun(group: string, opts?: RuntimeCommandOpts): RuntimeCommand
|
||||
---@field cancel_id fun(id: string): RuntimeCommand
|
||||
---@field cancel_group fun(group: string): RuntimeCommand
|
||||
---@field cancel_scope fun(scope: string): RuntimeCommand
|
||||
|
||||
---@class RuntimeImportApi
|
||||
---@field import fun(moduleName: string): table
|
||||
|
||||
---@type RuntimeImportApi
|
||||
runtime = runtime
|
||||
|
||||
---@alias PlayerColor 'red'|'yellow'|'blue'|'green'
|
||||
---@alias GamePhase 'waiting_roll'|'waiting_piece'|'animating'|'game_over'
|
||||
---@alias PieceStatus 'home'|'route'|'finished'
|
||||
|
||||
---@class (exact) PieceState
|
||||
---@field id string
|
||||
---@field owner PlayerColor
|
||||
---@field progress integer
|
||||
---@field status PieceStatus
|
||||
|
||||
---@class (exact) LudoState
|
||||
---@field current_player PlayerColor
|
||||
---@field phase GamePhase
|
||||
---@field dice_index integer
|
||||
---@field dice_values integer[]
|
||||
---@field dice? integer
|
||||
---@field selected_piece? string
|
||||
---@field pending_move? table
|
||||
---@field winner? PlayerColor
|
||||
---@field players PlayerColor[]
|
||||
---@field pieces table<string, PieceState>
|
||||
|
||||
---@class (exact) BoardPoint
|
||||
---@field x number
|
||||
---@field y number
|
||||
|
||||
---@class (exact) BoardData
|
||||
---@field red_home BoardPoint[]
|
||||
---@field blue_home BoardPoint[]
|
||||
---@field path BoardPoint[]
|
||||
---@field start table<PlayerColor, integer>
|
||||
22
example/assets/games/flight/scripts/state.lua
Normal file
@@ -0,0 +1,22 @@
|
||||
---@type table
|
||||
return {
|
||||
players = { "red", "yellow", "blue", "green" },
|
||||
current_player = "red",
|
||||
phase = "waiting_roll",
|
||||
dice_index = 0,
|
||||
dice_values = { 6, 3, 4, 6, 2, 5, 1, 6, 4, 2, 6, 5 },
|
||||
dice = nil,
|
||||
selected_piece = nil,
|
||||
pending_move = nil,
|
||||
winner = nil,
|
||||
pieces = {
|
||||
red_1 = { id = "red_1", owner = "red", status = "home", progress = 0 },
|
||||
red_2 = { id = "red_2", owner = "red", status = "home", progress = 0 },
|
||||
yellow_1 = { id = "yellow_1", owner = "yellow", status = "home", progress = 0 },
|
||||
yellow_2 = { id = "yellow_2", owner = "yellow", status = "home", progress = 0 },
|
||||
blue_1 = { id = "blue_1", owner = "blue", status = "home", progress = 0 },
|
||||
blue_2 = { id = "blue_2", owner = "blue", status = "home", progress = 0 },
|
||||
green_1 = { id = "green_1", owner = "green", status = "home", progress = 0 },
|
||||
green_2 = { id = "green_2", owner = "green", status = "home", progress = 0 }
|
||||
}
|
||||
}
|
||||
81
example/assets/games/flight/scripts/styles.lua
Normal file
@@ -0,0 +1,81 @@
|
||||
local theme = runtime.import("theme")
|
||||
local colors = theme.colors
|
||||
local styles = {}
|
||||
|
||||
styles.layers = {
|
||||
board = 1,
|
||||
cell = 2,
|
||||
home = 3,
|
||||
plane = 10,
|
||||
hud = 100,
|
||||
hud_content = 101
|
||||
}
|
||||
|
||||
styles.board = {
|
||||
color = colors.board,
|
||||
layer = styles.layers.board
|
||||
}
|
||||
|
||||
styles.center = {
|
||||
color = colors.board_inner,
|
||||
layer = styles.layers.board
|
||||
}
|
||||
|
||||
styles.route_cell = {
|
||||
anchor = "center",
|
||||
color = colors.route_cell,
|
||||
layer = styles.layers.cell
|
||||
}
|
||||
|
||||
styles.home_panel = function(owner)
|
||||
return {
|
||||
color = colors[owner .. "_home"],
|
||||
layer = styles.layers.home
|
||||
}
|
||||
end
|
||||
|
||||
styles.hud = {
|
||||
color = colors.top_bar,
|
||||
layer = styles.layers.hud
|
||||
}
|
||||
|
||||
styles.hud_text = {
|
||||
fontSize = 20,
|
||||
color = colors.text,
|
||||
layer = styles.layers.hud_content
|
||||
}
|
||||
|
||||
styles.small_text = {
|
||||
fontSize = 14,
|
||||
color = colors.muted_text,
|
||||
layer = styles.layers.hud_content
|
||||
}
|
||||
|
||||
styles.dice_button = {
|
||||
color = colors.dice_button,
|
||||
fontSize = 20,
|
||||
layer = styles.layers.hud_content
|
||||
}
|
||||
|
||||
styles.plane = function(owner)
|
||||
return {
|
||||
anchor = "center",
|
||||
color = colors[owner],
|
||||
layer = styles.layers.plane,
|
||||
interactive = true,
|
||||
onTap = "piece_tap",
|
||||
alpha = 0.95
|
||||
}
|
||||
end
|
||||
|
||||
styles.plane_highlight = {
|
||||
scale = 1.25,
|
||||
alpha = 1
|
||||
}
|
||||
|
||||
styles.plane_normal = {
|
||||
scale = 1,
|
||||
alpha = 0.95
|
||||
}
|
||||
|
||||
return styles
|
||||
22
example/assets/games/flight/scripts/theme.lua
Normal file
@@ -0,0 +1,22 @@
|
||||
return {
|
||||
title = "Lua 飞行棋",
|
||||
colors = {
|
||||
background = "#0f172a",
|
||||
board = "#1e293b",
|
||||
board_inner = "#334155",
|
||||
route_cell = "#f8fafc",
|
||||
route_text = "#0f172a",
|
||||
top_bar = "#020617",
|
||||
dice_button = "#2563eb",
|
||||
text = "#ffffff",
|
||||
muted_text = "#cbd5e1",
|
||||
red = "#ef4444",
|
||||
yellow = "#eab308",
|
||||
blue = "#3b82f6",
|
||||
green = "#22c55e",
|
||||
red_home = "#7f1d1d",
|
||||
yellow_home = "#713f12",
|
||||
blue_home = "#1e3a8a",
|
||||
green_home = "#14532d"
|
||||
}
|
||||
}
|
||||
130
example/assets/games/flight/scripts/ui.lua
Normal file
@@ -0,0 +1,130 @@
|
||||
---@type RuntimeUi
|
||||
local runtime_ui = runtime.import("runtime_ui")
|
||||
---@type RuntimeLayout
|
||||
local layout = runtime.import("layout")
|
||||
local theme = runtime.import("theme")
|
||||
local styles = runtime.import("styles")
|
||||
local state = runtime.import("state")
|
||||
local board = runtime.import("board")
|
||||
local rules = runtime.import("rules")
|
||||
|
||||
local ui = {}
|
||||
|
||||
local function color_name(owner)
|
||||
if owner == "red" then return "红" end
|
||||
if owner == "yellow" then return "黄" end
|
||||
if owner == "blue" then return "蓝" end
|
||||
if owner == "green" then return "绿" end
|
||||
return owner
|
||||
end
|
||||
|
||||
local function piece_size(piece)
|
||||
if piece.status == "finished" then
|
||||
return 30
|
||||
end
|
||||
return 34
|
||||
end
|
||||
|
||||
function ui.highlight_updates(ids, enabled)
|
||||
return runtime_ui.batch_update(ids, enabled and styles.plane_highlight or styles.plane_normal)
|
||||
end
|
||||
|
||||
function ui.piece_position_update(piece_id)
|
||||
local piece = state.pieces[piece_id]
|
||||
local pos = rules.piece_position(piece)
|
||||
return runtime_ui.update(piece_id, {
|
||||
x = pos.x,
|
||||
y = pos.y,
|
||||
width = piece_size(piece),
|
||||
height = piece_size(piece),
|
||||
scale = 1,
|
||||
alpha = 0.95
|
||||
})
|
||||
end
|
||||
|
||||
function ui.piece_position_updates(ids)
|
||||
local updates = {}
|
||||
for _, id in ipairs(ids) do
|
||||
table.insert(updates, ui.piece_position_update(id))
|
||||
end
|
||||
return updates
|
||||
end
|
||||
|
||||
function ui.all_piece_position_updates()
|
||||
return ui.piece_position_updates(rules.all_piece_ids())
|
||||
end
|
||||
|
||||
function ui.create_board_nodes()
|
||||
local nodes = {
|
||||
runtime_ui.panel("board_panel", 28, 82, 664, 610, styles.board),
|
||||
runtime_ui.panel("center_airport", 252, 292, 168, 136, styles.center),
|
||||
runtime_ui.text("board_title", theme.title, 268, 318, 140, 32, styles.hud_text),
|
||||
runtime_ui.text("board_tip", "掷 6 起飞 · 撞子回家 · 全部到达获胜", 206, 360, 320, 28, styles.small_text),
|
||||
runtime_ui.panel("home_red", 62, 98, 128, 92, styles.home_panel("red")),
|
||||
runtime_ui.panel("home_yellow", 498, 98, 128, 92, styles.home_panel("yellow")),
|
||||
runtime_ui.panel("home_blue", 498, 516, 128, 92, styles.home_panel("blue")),
|
||||
runtime_ui.panel("home_green", 62, 516, 128, 92, styles.home_panel("green")),
|
||||
runtime_ui.text("label_red", "红方", 96, 102, 60, 24, styles.small_text),
|
||||
runtime_ui.text("label_yellow", "黄方", 532, 102, 60, 24, styles.small_text),
|
||||
runtime_ui.text("label_blue", "蓝方", 532, 520, 60, 24, styles.small_text),
|
||||
runtime_ui.text("label_green", "绿方", 96, 520, 60, 24, styles.small_text)
|
||||
}
|
||||
|
||||
for index, cell in ipairs(board.route) do
|
||||
runtime_ui.append(nodes, runtime_ui.circle("route_" .. tostring(index), cell.x, cell.y, 24, styles.route_cell))
|
||||
end
|
||||
|
||||
for _, owner in ipairs(state.players) do
|
||||
local start = board.route[board.start[owner]]
|
||||
runtime_ui.append(nodes, runtime_ui.text("start_" .. owner, color_name(owner), start.x - 14, start.y - 14, 36, 24, {
|
||||
fontSize = 14,
|
||||
color = theme.colors[owner],
|
||||
layer = styles.layers.cell + 1
|
||||
}))
|
||||
end
|
||||
|
||||
for id, piece in pairs(state.pieces) do
|
||||
local pos = rules.piece_position(piece)
|
||||
runtime_ui.append(nodes, runtime_ui.circle(id, pos.x, pos.y, piece_size(piece), styles.plane(piece.owner)))
|
||||
end
|
||||
|
||||
return nodes
|
||||
end
|
||||
|
||||
function ui.create_ui_nodes()
|
||||
local hud_items = layout.row("top_bar", {
|
||||
runtime_ui.text("turn_text", "当前: 红方", 0, 0, 120, 32, styles.hud_text),
|
||||
runtime_ui.text("dice_text", "骰子: -", 0, 0, 100, 32, styles.hud_text),
|
||||
runtime_ui.text("status_text", rules.status_text(), 0, 0, 260, 32, styles.hud_text),
|
||||
layout.item(
|
||||
runtime_ui.button("dice_button", "掷骰子", 0, 0, 120, 48, "roll_dice", styles.dice_button),
|
||||
{ marginLeft = 18 }
|
||||
)
|
||||
}, {
|
||||
x = 24,
|
||||
height = 76,
|
||||
gap = 16,
|
||||
align = "center"
|
||||
})
|
||||
|
||||
local nodes = { runtime_ui.panel("top_bar", 0, 0, 720, 76, styles.hud) }
|
||||
return runtime_ui.append_all(nodes, hud_items)
|
||||
end
|
||||
|
||||
function ui.turn_name(owner)
|
||||
return "当前: " .. color_name(owner) .. "方"
|
||||
end
|
||||
|
||||
function ui.dice_and_status_updates()
|
||||
return {
|
||||
runtime_ui.text_update("dice_text", "骰子: " .. tostring(state.dice or "-")),
|
||||
runtime_ui.text_update("turn_text", ui.turn_name(state.current_player)),
|
||||
runtime_ui.text_update("status_text", rules.status_text())
|
||||
}
|
||||
end
|
||||
|
||||
function ui.status_update()
|
||||
return { runtime_ui.text_update("status_text", rules.status_text()) }
|
||||
end
|
||||
|
||||
return ui
|
||||
BIN
example/assets/games/ludo/assets/board.png
Normal file
|
After Width: | Height: | Size: 68 B |
BIN
example/assets/games/ludo/assets/dice.wav
Normal file
BIN
example/assets/games/ludo/assets/piece_blue.png
Normal file
|
After Width: | Height: | Size: 68 B |
BIN
example/assets/games/ludo/assets/piece_red.png
Normal file
|
After Width: | Height: | Size: 68 B |
55
example/assets/games/ludo/manifest.json
Normal file
@@ -0,0 +1,55 @@
|
||||
{
|
||||
"gameId": "ludo",
|
||||
"name": "Lua Ludo 示例",
|
||||
"version": "0.1.0",
|
||||
"runtimeApiVersion": 1,
|
||||
"entry": "scripts/main.lua",
|
||||
"assetsBase": "assets",
|
||||
"defaultLocale": "zh-Hans",
|
||||
"supportedLocales": [
|
||||
"zh-Hans",
|
||||
"en"
|
||||
],
|
||||
"display": {
|
||||
"designWidth": 720,
|
||||
"designHeight": 720,
|
||||
"scaleMode": "fit"
|
||||
},
|
||||
"modules": {
|
||||
"runtime_ui": "runtime:runtime_ui.lua",
|
||||
"runtime_widgets": "runtime:runtime_widgets.lua",
|
||||
"runtime_commands": "runtime:runtime_commands.lua",
|
||||
"layout": "runtime:layout.lua",
|
||||
"i18n": "scripts/i18n.lua",
|
||||
"theme": "scripts/theme.lua",
|
||||
"styles": "scripts/styles.lua",
|
||||
"state": "scripts/state.lua",
|
||||
"board": "scripts/board.lua",
|
||||
"rules": "scripts/rules.lua",
|
||||
"ui": "scripts/ui.lua",
|
||||
"animation": "scripts/animation.lua"
|
||||
},
|
||||
"resources": {
|
||||
"board": {
|
||||
"type": "image",
|
||||
"path": "assets/board.png",
|
||||
"group": "board"
|
||||
},
|
||||
"piece_red": {
|
||||
"type": "image",
|
||||
"path": "assets/piece_red.png",
|
||||
"group": "pieces"
|
||||
},
|
||||
"piece_blue": {
|
||||
"type": "image",
|
||||
"path": "assets/piece_blue.png",
|
||||
"group": "pieces"
|
||||
},
|
||||
"dice": {
|
||||
"type": "audio",
|
||||
"path": "assets/dice.wav",
|
||||
"preload": "lazy",
|
||||
"group": "sfx"
|
||||
}
|
||||
}
|
||||
}
|
||||
37
example/assets/games/ludo/scripts/animation.lua
Normal file
@@ -0,0 +1,37 @@
|
||||
---@type RuntimeCommands
|
||||
local commands = runtime.import("runtime_commands")
|
||||
|
||||
local animation = {}
|
||||
|
||||
function animation.move_piece(piece_id, path)
|
||||
return commands.move_path(piece_id, path, {
|
||||
duration = 0.7,
|
||||
onComplete = "piece_move_done"
|
||||
})
|
||||
end
|
||||
|
||||
function animation.toast(text)
|
||||
return commands.toast(text)
|
||||
end
|
||||
|
||||
function animation.play_sound(name)
|
||||
return commands.play_sound(name)
|
||||
end
|
||||
|
||||
function animation.play_bgm(name, channel)
|
||||
return commands.play_bgm(name, { channel = channel or "bgm" })
|
||||
end
|
||||
|
||||
function animation.pause_bgm(channel)
|
||||
return commands.pause_bgm(channel)
|
||||
end
|
||||
|
||||
function animation.resume_bgm(channel)
|
||||
return commands.resume_bgm(channel)
|
||||
end
|
||||
|
||||
function animation.stop_bgm(channel)
|
||||
return commands.stop_bgm(channel)
|
||||
end
|
||||
|
||||
return animation
|
||||
20
example/assets/games/ludo/scripts/board.lua
Normal file
@@ -0,0 +1,20 @@
|
||||
---@type BoardData
|
||||
return {
|
||||
red_home = {
|
||||
{x = 80, y = 520},
|
||||
{x = 140, y = 520}
|
||||
},
|
||||
blue_home = {
|
||||
{x = 520, y = 120},
|
||||
{x = 580, y = 120}
|
||||
},
|
||||
path = {
|
||||
{x = 120, y = 420}, {x = 180, y = 420}, {x = 240, y = 420},
|
||||
{x = 300, y = 420}, {x = 360, y = 420}, {x = 420, y = 420},
|
||||
{x = 480, y = 420}, {x = 480, y = 360}, {x = 480, y = 300},
|
||||
{x = 480, y = 240}, {x = 420, y = 240}, {x = 360, y = 240},
|
||||
{x = 300, y = 240}, {x = 240, y = 240}, {x = 180, y = 240},
|
||||
{x = 120, y = 240}, {x = 120, y = 300}, {x = 120, y = 360}
|
||||
},
|
||||
start = { red = 1, blue = 10 }
|
||||
}
|
||||
103
example/assets/games/ludo/scripts/i18n.lua
Normal file
@@ -0,0 +1,103 @@
|
||||
---@class RuntimeI18n
|
||||
local i18n = {}
|
||||
|
||||
local default_locale = "zh-Hans"
|
||||
local current_locale = default_locale
|
||||
|
||||
local messages = {
|
||||
["zh-Hans"] = {
|
||||
title = "Lua 飞行棋",
|
||||
roll_button = "掷骰子",
|
||||
dice_empty = "骰子: -",
|
||||
dice_value = "骰子: {value}",
|
||||
current_player = "当前玩家: {player}",
|
||||
["player.red"] = "红方",
|
||||
["player.blue"] = "蓝方",
|
||||
["toast.move_first"] = "请先移动棋子",
|
||||
["toast.no_movable_piece"] = "无可移动棋子",
|
||||
["toast.invalid_piece"] = "该棋子不能移动"
|
||||
},
|
||||
en = {
|
||||
title = "Lua Ludo",
|
||||
roll_button = "Roll",
|
||||
dice_empty = "Dice: -",
|
||||
dice_value = "Dice: {value}",
|
||||
current_player = "Current player: {player}",
|
||||
["player.red"] = "Red",
|
||||
["player.blue"] = "Blue",
|
||||
["toast.move_first"] = "Move a piece first",
|
||||
["toast.no_movable_piece"] = "No movable pieces",
|
||||
["toast.invalid_piece"] = "This piece cannot move"
|
||||
}
|
||||
}
|
||||
|
||||
---@param locale? string
|
||||
---@return string
|
||||
local function normalize_locale(locale)
|
||||
if type(locale) ~= "string" or locale == "" then
|
||||
return default_locale
|
||||
end
|
||||
locale = string.gsub(locale, "_", "-")
|
||||
if messages[locale] ~= nil then
|
||||
return locale
|
||||
end
|
||||
|
||||
local language = string.match(locale, "^([A-Za-z]+)")
|
||||
if language ~= nil and messages[language] ~= nil then
|
||||
return language
|
||||
end
|
||||
return default_locale
|
||||
end
|
||||
|
||||
---@param value string
|
||||
---@return string
|
||||
local function escape_pattern(value)
|
||||
return string.gsub(value, "([%(%)%.%%%+%-%*%?%[%]%^%$])", "%%%1")
|
||||
end
|
||||
|
||||
---@param template string
|
||||
---@param vars? table<string, string|number>
|
||||
---@return string
|
||||
local function interpolate(template, vars)
|
||||
if vars == nil then
|
||||
return template
|
||||
end
|
||||
|
||||
local result = template
|
||||
for key, value in pairs(vars) do
|
||||
result = string.gsub(result, "{" .. escape_pattern(key) .. "}", tostring(value))
|
||||
end
|
||||
return result
|
||||
end
|
||||
|
||||
---@param ctx? RuntimeContext
|
||||
function i18n.configure(ctx)
|
||||
local locale = nil
|
||||
if ctx ~= nil and ctx.locale ~= nil then
|
||||
locale = ctx.locale.resolved or ctx.locale.requested or ctx.locale.default
|
||||
end
|
||||
current_locale = normalize_locale(locale)
|
||||
end
|
||||
|
||||
---@return string
|
||||
function i18n.locale()
|
||||
return current_locale
|
||||
end
|
||||
|
||||
---@param key string
|
||||
---@param vars? table<string, string|number>
|
||||
---@return string
|
||||
function i18n.t(key, vars)
|
||||
local bundle = messages[current_locale] or messages[default_locale]
|
||||
local fallback = messages[default_locale]
|
||||
local value = bundle[key] or fallback[key] or key
|
||||
return interpolate(value, vars)
|
||||
end
|
||||
|
||||
---@param color PlayerColor
|
||||
---@return string
|
||||
function i18n.player(color)
|
||||
return i18n.t("player." .. color)
|
||||
end
|
||||
|
||||
return i18n
|
||||
117
example/assets/games/ludo/scripts/main.lua
Normal file
@@ -0,0 +1,117 @@
|
||||
local state = runtime.import("state")
|
||||
local rules = runtime.import("rules")
|
||||
local ui = runtime.import("ui")
|
||||
local animation = runtime.import("animation")
|
||||
local i18n = runtime.import("i18n")
|
||||
---@type RuntimeWidgets
|
||||
local widgets = runtime.import("runtime_widgets")
|
||||
local theme = runtime.import("theme")
|
||||
|
||||
widgets.configure({
|
||||
primary = theme.colors.dice_button,
|
||||
secondary = theme.colors.board,
|
||||
success = theme.colors.blue,
|
||||
overlay = "#99000000",
|
||||
surface = theme.colors.top_bar,
|
||||
surfaceAlt = theme.colors.board,
|
||||
card = theme.colors.top_bar,
|
||||
text = theme.colors.text,
|
||||
muted = "#ffcbd5e1",
|
||||
progress = theme.colors.blue,
|
||||
transparent = "#00000000"
|
||||
})
|
||||
|
||||
function smoke_test(ctx)
|
||||
i18n.configure(ctx)
|
||||
return ctx ~= nil
|
||||
and ctx.runtimeApiVersion ~= nil
|
||||
and state.current_player ~= nil
|
||||
and rules.next_player ~= nil
|
||||
and ui.create_board_nodes ~= nil
|
||||
and animation.move_piece ~= nil
|
||||
and widgets.dialog ~= nil
|
||||
end
|
||||
|
||||
function init(ctx)
|
||||
i18n.configure(ctx)
|
||||
return {
|
||||
render = { creates = ui.create_board_nodes() },
|
||||
ui = { creates = ui.create_ui_nodes() },
|
||||
commands = {}
|
||||
}
|
||||
end
|
||||
|
||||
local function handle_roll_dice()
|
||||
if state.phase ~= "waiting_roll" then
|
||||
return { commands = { animation.toast(i18n.t("toast.move_first")) } }
|
||||
end
|
||||
|
||||
state.dice = rules.next_dice()
|
||||
local movable = rules.movable_pieces()
|
||||
|
||||
if #movable == 0 then
|
||||
state.current_player = rules.next_player()
|
||||
state.phase = "waiting_roll"
|
||||
return {
|
||||
ui = { updates = ui.dice_and_turn_updates(state.dice, state.current_player) },
|
||||
render = { updates = ui.highlight_updates(rules.all_piece_ids(), false) },
|
||||
commands = { animation.toast(i18n.t("toast.no_movable_piece")) }
|
||||
}
|
||||
end
|
||||
|
||||
state.phase = "waiting_piece"
|
||||
return {
|
||||
ui = { updates = ui.dice_update(state.dice) },
|
||||
render = { updates = ui.highlight_updates(movable, true) },
|
||||
commands = { animation.play_sound("dice") }
|
||||
}
|
||||
end
|
||||
|
||||
local function handle_piece_tap(piece_id)
|
||||
if state.phase ~= "waiting_piece" then
|
||||
return {}
|
||||
end
|
||||
|
||||
local piece = state.pieces[piece_id]
|
||||
if piece == nil or piece.owner ~= state.current_player or not rules.can_move(piece, state.dice) then
|
||||
return { commands = { animation.toast(i18n.t("toast.invalid_piece")) } }
|
||||
end
|
||||
|
||||
local path = rules.calculate_path(piece, state.dice)
|
||||
rules.apply_move(piece, state.dice)
|
||||
state.phase = "animating"
|
||||
state.selected_piece = piece_id
|
||||
|
||||
return {
|
||||
render = { updates = ui.highlight_updates(rules.all_piece_ids(), false) },
|
||||
commands = { animation.move_piece(piece_id, path) }
|
||||
}
|
||||
end
|
||||
|
||||
local function handle_move_done()
|
||||
if state.dice ~= 6 then
|
||||
state.current_player = rules.next_player()
|
||||
end
|
||||
state.phase = "waiting_roll"
|
||||
state.selected_piece = nil
|
||||
|
||||
return {
|
||||
ui = { updates = ui.turn_update(state.current_player) }
|
||||
}
|
||||
end
|
||||
|
||||
function on_event(event)
|
||||
if event.handler == "roll_dice" then
|
||||
return handle_roll_dice()
|
||||
end
|
||||
|
||||
if event.handler == "piece_tap" then
|
||||
return handle_piece_tap(event.target)
|
||||
end
|
||||
|
||||
if event.handler == "piece_move_done" then
|
||||
return handle_move_done()
|
||||
end
|
||||
|
||||
return {}
|
||||
end
|
||||
88
example/assets/games/ludo/scripts/rules.lua
Normal file
@@ -0,0 +1,88 @@
|
||||
local state = runtime.import("state")
|
||||
local board = runtime.import("board")
|
||||
|
||||
local rules = {}
|
||||
|
||||
function rules.next_player()
|
||||
if state.current_player == "red" then
|
||||
return "blue"
|
||||
end
|
||||
return "red"
|
||||
end
|
||||
|
||||
function rules.next_dice()
|
||||
state.dice_index = state.dice_index + 1
|
||||
if state.dice_index > #state.dice_values then
|
||||
state.dice_index = 1
|
||||
end
|
||||
return state.dice_values[state.dice_index]
|
||||
end
|
||||
|
||||
function rules.piece_home_position(piece)
|
||||
if piece.owner == "red" then
|
||||
if piece.id == "red_1" then return board.red_home[1] end
|
||||
return board.red_home[2]
|
||||
end
|
||||
if piece.id == "blue_1" then return board.blue_home[1] end
|
||||
return board.blue_home[2]
|
||||
end
|
||||
|
||||
function rules.can_move(piece, dice)
|
||||
if piece.status == "home" then
|
||||
return dice == 6
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
function rules.movable_pieces()
|
||||
local result = {}
|
||||
for id, piece in pairs(state.pieces) do
|
||||
if piece.owner == state.current_player and rules.can_move(piece, state.dice) then
|
||||
table.insert(result, id)
|
||||
end
|
||||
end
|
||||
return result
|
||||
end
|
||||
|
||||
function rules.all_piece_ids()
|
||||
local result = {}
|
||||
for id, _ in pairs(state.pieces) do
|
||||
table.insert(result, id)
|
||||
end
|
||||
return result
|
||||
end
|
||||
|
||||
function rules.calculate_path(piece, dice)
|
||||
local path = {}
|
||||
if piece.status == "home" then
|
||||
local start_index = board.start[piece.owner]
|
||||
local pos = board.path[start_index]
|
||||
table.insert(path, {x = pos.x, y = pos.y})
|
||||
return path
|
||||
end
|
||||
|
||||
for i = 1, dice do
|
||||
local index = piece.path_index + i
|
||||
while index > #board.path do
|
||||
index = index - #board.path
|
||||
end
|
||||
local pos = board.path[index]
|
||||
table.insert(path, {x = pos.x, y = pos.y})
|
||||
end
|
||||
return path
|
||||
end
|
||||
|
||||
function rules.apply_move(piece, dice)
|
||||
if piece.status == "home" then
|
||||
piece.status = "path"
|
||||
piece.path_index = board.start[piece.owner]
|
||||
return
|
||||
end
|
||||
|
||||
piece.path_index = piece.path_index + dice
|
||||
while piece.path_index > #board.path do
|
||||
piece.path_index = piece.path_index - #board.path
|
||||
end
|
||||
end
|
||||
|
||||
return rules
|
||||
587
example/assets/games/ludo/scripts/runtime_defs.lua
Normal file
@@ -0,0 +1,587 @@
|
||||
---@meta
|
||||
--- COMMON RUNTIME TYPES SECTION.
|
||||
--- Source of truth: tool/lua_runtime_defs_common.lua
|
||||
--- After editing this common section, run:
|
||||
--- dart run tool/generate_lua_runtime_defs.dart
|
||||
|
||||
|
||||
---@alias RuntimeNodeType
|
||||
---| 'panel'
|
||||
---| 'button'
|
||||
---| 'text'
|
||||
---| 'circle'
|
||||
---| 'rect'
|
||||
---| 'line'
|
||||
---| 'progress'
|
||||
---| 'listView'
|
||||
---| 'sprite'
|
||||
---| 'image'
|
||||
---| 'spine'
|
||||
---| 'particle'
|
||||
|
||||
---@alias RuntimeAnchor
|
||||
---| 'center'
|
||||
---| 'topLeft'
|
||||
---| 'topRight'
|
||||
---| 'bottomLeft'
|
||||
---| 'bottomRight'
|
||||
|
||||
---@alias RuntimeTextAlign
|
||||
---| 'left'
|
||||
---| 'center'
|
||||
---| 'right'
|
||||
|
||||
---@alias RuntimeParticlePreset
|
||||
---| 'burst'
|
||||
---| 'trail'
|
||||
---| 'snow'
|
||||
---| 'confetti'
|
||||
|
||||
---@alias RuntimeCommandType
|
||||
---| 'move_path'
|
||||
---| 'move_to'
|
||||
---| 'fade_to'
|
||||
---| 'scale_to'
|
||||
---| 'rotate_to'
|
||||
---| 'remove_node'
|
||||
---| 'sequence'
|
||||
---| 'parallel'
|
||||
---| 'delay'
|
||||
---| 'toast'
|
||||
---| 'play_sound'
|
||||
---| 'play_bgm'
|
||||
---| 'pause_bgm'
|
||||
---| 'resume_bgm'
|
||||
---| 'stop_bgm'
|
||||
---| 'preload_resources'
|
||||
---| 'evict_resources'
|
||||
---| 'cancel_commands'
|
||||
---| 'play_spine_animation'
|
||||
---| 'copy_text'
|
||||
|
||||
---@alias RuntimeEventType
|
||||
---| 'tap'
|
||||
---| 'animation_done'
|
||||
---| 'resize'
|
||||
---| 'scroll'
|
||||
|
||||
---@alias RuntimeScaleMode
|
||||
---| 'fit'
|
||||
---| 'fill'
|
||||
---| 'stretch'
|
||||
---| 'none'
|
||||
|
||||
---@alias RuntimeLayoutAlign
|
||||
---| 'start'
|
||||
---| 'center'
|
||||
---| 'end'
|
||||
|
||||
---@alias RuntimeButtonVariant
|
||||
---| 'primary'
|
||||
---| 'secondary'
|
||||
---| 'ghost'
|
||||
|
||||
---@class (exact) RuntimeNode
|
||||
---@field id string
|
||||
---@field type RuntimeNodeType
|
||||
---@field parent? string
|
||||
---@field asset? string Normal image/sprite/spine asset key. For button nodes this is the normal-state image.
|
||||
---@field pressedAsset? string Button pressed-state image asset key.
|
||||
---@field disabledAsset? string Button disabled-state image asset key.
|
||||
---@field animation? string
|
||||
---@field skin? string
|
||||
---@field loop? boolean
|
||||
---@field text? string
|
||||
---@field x? number
|
||||
---@field y? number
|
||||
---@field width? number
|
||||
---@field height? number
|
||||
---@field paddingLeft? number
|
||||
---@field paddingTop? number
|
||||
---@field paddingRight? number
|
||||
---@field paddingBottom? number
|
||||
---@field anchor? RuntimeAnchor
|
||||
---@field layer? integer
|
||||
---@field visible? boolean
|
||||
---@field alpha? number
|
||||
---@field scale? number
|
||||
---@field rotation? number
|
||||
---@field color? string
|
||||
---@field fontSize? number
|
||||
---@field textAlign? RuntimeTextAlign
|
||||
---@field radius? number
|
||||
---@field strokeWidth? number
|
||||
---@field value? number
|
||||
---@field scrollX? number
|
||||
---@field scrollY? number
|
||||
---@field contentWidth? number
|
||||
---@field contentHeight? number
|
||||
---@field virtualized? boolean
|
||||
---@field cacheExtent? number
|
||||
---@field inertia? boolean
|
||||
---@field scrollbarThumbColor? string
|
||||
---@field scrollbarTrackColor? string
|
||||
---@field scrollbarThickness? number
|
||||
---@field scrollbarVisible? boolean
|
||||
---@field interactive? boolean
|
||||
---@field onTap? string
|
||||
---@field onScroll? string
|
||||
---@field preset? RuntimeParticlePreset
|
||||
---@field count? integer
|
||||
---@field duration? number
|
||||
---@field speedMin? number
|
||||
---@field speedMax? number
|
||||
---@field gravityX? number
|
||||
---@field gravityY? number
|
||||
---@field spread? number
|
||||
---@field colorTo? string
|
||||
---@field radiusTo? number
|
||||
---@field autoRemove? boolean
|
||||
---@field fadeOut? boolean
|
||||
|
||||
---@class (exact) RuntimeNodeProps
|
||||
---@field type? RuntimeNodeType
|
||||
---@field parent? string
|
||||
---@field asset? string Normal image/sprite/spine asset key. For button nodes this is the normal-state image.
|
||||
---@field pressedAsset? string Button pressed-state image asset key.
|
||||
---@field disabledAsset? string Button disabled-state image asset key.
|
||||
---@field animation? string
|
||||
---@field skin? string
|
||||
---@field loop? boolean
|
||||
---@field text? string
|
||||
---@field x? number
|
||||
---@field y? number
|
||||
---@field width? number
|
||||
---@field height? number
|
||||
---@field paddingLeft? number
|
||||
---@field paddingTop? number
|
||||
---@field paddingRight? number
|
||||
---@field paddingBottom? number
|
||||
---@field anchor? RuntimeAnchor
|
||||
---@field layer? integer
|
||||
---@field visible? boolean
|
||||
---@field alpha? number
|
||||
---@field scale? number
|
||||
---@field rotation? number
|
||||
---@field color? string
|
||||
---@field fontSize? number
|
||||
---@field textAlign? RuntimeTextAlign
|
||||
---@field radius? number
|
||||
---@field strokeWidth? number
|
||||
---@field value? number
|
||||
---@field scrollX? number
|
||||
---@field scrollY? number
|
||||
---@field contentWidth? number
|
||||
---@field contentHeight? number
|
||||
---@field virtualized? boolean
|
||||
---@field cacheExtent? number
|
||||
---@field inertia? boolean
|
||||
---@field scrollbarThumbColor? string
|
||||
---@field scrollbarTrackColor? string
|
||||
---@field scrollbarThickness? number
|
||||
---@field scrollbarVisible? boolean
|
||||
---@field interactive? boolean
|
||||
---@field onTap? string
|
||||
---@field onScroll? string
|
||||
---@field preset? RuntimeParticlePreset
|
||||
---@field count? integer
|
||||
---@field duration? number
|
||||
---@field speedMin? number
|
||||
---@field speedMax? number
|
||||
---@field gravityX? number
|
||||
---@field gravityY? number
|
||||
---@field spread? number
|
||||
---@field colorTo? string
|
||||
---@field radiusTo? number
|
||||
---@field autoRemove? boolean
|
||||
---@field fadeOut? boolean
|
||||
|
||||
---Helper-only fields accepted by runtime_ui/runtime_widgets. They are normalized
|
||||
---before the node/update crosses the Dart Runtime protocol boundary.
|
||||
---@class RuntimeNodeInit: RuntimeNodeProps
|
||||
---@field w? number Alias for width.
|
||||
---@field h? number Alias for height.
|
||||
---@field size? number Alias for both width and height.
|
||||
---@field handler? string Alias for onTap.
|
||||
---@field onClick? string Alias for onTap.
|
||||
|
||||
---@class (exact) RuntimeNodeUpdate
|
||||
---@field id string
|
||||
---@field props RuntimeNodeProps
|
||||
|
||||
---@class (exact) RuntimeNodeRemove
|
||||
---@field id string
|
||||
|
||||
---@class (exact) RuntimeDiffSection
|
||||
---@field creates? RuntimeNode[]
|
||||
---@field updates? RuntimeNodeUpdate[]
|
||||
---@field removes? (string|RuntimeNodeRemove)[]
|
||||
|
||||
---@class (exact) RuntimeDiff
|
||||
---@field render? RuntimeDiffSection
|
||||
---@field ui? RuntimeDiffSection
|
||||
---@field commands? RuntimeCommand[]
|
||||
|
||||
---@class (exact) RuntimeEvent
|
||||
---@field type RuntimeEventType|string
|
||||
---@field target? string
|
||||
---@field handler? string
|
||||
---@field x? number
|
||||
---@field y? number
|
||||
---@field data? table
|
||||
|
||||
---@class (exact) RuntimeCommand
|
||||
---@field type RuntimeCommandType
|
||||
---@field target? string
|
||||
---@field scope? string
|
||||
---@field id? string
|
||||
---@field group? string
|
||||
---@field commandGroup? string
|
||||
---@field onComplete? string
|
||||
---@field duration? number
|
||||
---@field commands? RuntimeCommand[]
|
||||
---@field path? RuntimePoint[]
|
||||
---@field x? number
|
||||
---@field y? number
|
||||
---@field alpha? number
|
||||
---@field scale? number
|
||||
---@field angle? number
|
||||
---@field text? string
|
||||
---@field message? string
|
||||
---@field asset? string
|
||||
---@field name? string
|
||||
---@field volume? number
|
||||
---@field channel? string
|
||||
---@field loop? boolean
|
||||
---@field failOnError? boolean
|
||||
---@field animation? string
|
||||
---@field track? integer
|
||||
---@field queue? boolean
|
||||
---@field delay? number
|
||||
|
||||
---@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
|
||||
---@field name? string
|
||||
|
||||
---@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 (exact) RuntimePoint
|
||||
---@field x number
|
||||
---@field y number
|
||||
|
||||
---@class (exact) RuntimeLocaleContext
|
||||
---@field requested string
|
||||
---@field resolved string
|
||||
---@field default string
|
||||
---@field supported string[]
|
||||
---@field languageCode string
|
||||
---@field scriptCode? string
|
||||
---@field countryCode? string
|
||||
|
||||
---@class (exact) RuntimeScreenContext
|
||||
---@field width number
|
||||
---@field height number
|
||||
|
||||
---@class (exact) RuntimeDesignContext
|
||||
---@field width number
|
||||
---@field height number
|
||||
|
||||
---@class (exact) RuntimeViewportContext
|
||||
---@field x number
|
||||
---@field y number
|
||||
---@field width number
|
||||
---@field height number
|
||||
---@field scaleX number
|
||||
---@field scaleY number
|
||||
---@field scaleMode RuntimeScaleMode|string
|
||||
|
||||
---@class (exact) RuntimeContext
|
||||
---@field screen RuntimeScreenContext
|
||||
---@field design RuntimeDesignContext
|
||||
---@field viewport RuntimeViewportContext
|
||||
---@field seed integer
|
||||
---@field runtimeApiVersion integer
|
||||
---@field gameId string
|
||||
---@field gameVersion string
|
||||
---@field locale? RuntimeLocaleContext
|
||||
|
||||
---@class RuntimeUi
|
||||
---@field style fun(base?: RuntimeNodeProps, opts?: RuntimeNodeProps): RuntimeNodeProps
|
||||
---@field with_parent fun(parent: string, opts?: RuntimeNodeProps): RuntimeNodeProps
|
||||
---@field node fun(node_type: RuntimeNodeType, id: string, opts?: RuntimeNodeInit): RuntimeNode
|
||||
---@field panel fun(id: string, x: number|RuntimeNodeInit, y?: number, width?: number, height?: number, opts?: RuntimeNodeInit): RuntimeNode
|
||||
---@field rect fun(id: string, x: number|RuntimeNodeInit, y?: number, width?: number, height?: number, opts?: RuntimeNodeInit): RuntimeNode
|
||||
---@field circle fun(id: string, x: number|RuntimeNodeInit, y?: number, size?: number, opts?: RuntimeNodeInit): RuntimeNode
|
||||
---@field line fun(id: string, x: number|RuntimeNodeInit, y?: number, width?: number, height?: number, opts?: RuntimeNodeInit): RuntimeNode
|
||||
---@field progress fun(id: string, x: number|RuntimeNodeInit, y?: number, width?: number, height?: number, value?: number, opts?: RuntimeNodeInit): RuntimeNode
|
||||
---@field particle fun(id: string, x: number|RuntimeNodeInit, y?: number, width?: number, height?: number, opts?: RuntimeNodeInit): RuntimeNode
|
||||
---@field text fun(id: string, text: string|RuntimeNodeInit, x?: number, y?: number, width?: number, height?: number, opts?: RuntimeNodeInit): RuntimeNode
|
||||
---@field button fun(id: string, text: string|RuntimeNodeInit, x?: number, y?: number, width?: number, height?: number, handler?: string, opts?: RuntimeNodeInit): RuntimeNode
|
||||
---@field list_view fun(id: string, x: number|RuntimeNodeInit, y?: number, width?: number, height?: number, opts?: RuntimeNodeInit): RuntimeNode
|
||||
---@field image fun(id: string, asset: string|RuntimeNodeInit, x?: number, y?: number, width?: number, height?: number, opts?: RuntimeNodeInit): RuntimeNode
|
||||
---@field sprite fun(id: string, asset: string|RuntimeNodeInit, x?: number, y?: number, width?: number, height?: number, opts?: RuntimeNodeInit): RuntimeNode
|
||||
---@field spine fun(id: string, asset: string|RuntimeNodeInit, x?: number, y?: number, width?: number, height?: number, animation?: string, opts?: RuntimeNodeInit): RuntimeNode
|
||||
---@field update fun(id: string, props: RuntimeNodeInit): RuntimeNodeUpdate
|
||||
---@field text_update fun(id: string, text: string): RuntimeNodeUpdate
|
||||
---@field visible_update fun(id: string, visible: boolean): RuntimeNodeUpdate
|
||||
---@field alpha_update fun(id: string, alpha: number): RuntimeNodeUpdate
|
||||
---@field scale_update fun(id: string, scale: number): RuntimeNodeUpdate
|
||||
---@field position_update fun(id: string, x: number, y: number): RuntimeNodeUpdate
|
||||
---@field size_update fun(id: string, width: number, height: number): RuntimeNodeUpdate
|
||||
---@field transform_update fun(id: string, x: number, y: number, scale: number, rotation: number): RuntimeNodeUpdate
|
||||
---@field batch_update fun(ids: string[], props: RuntimeNodeInit): RuntimeNodeUpdate[]
|
||||
---@field append fun(nodes: RuntimeNode[], node: RuntimeNode): RuntimeNode[]
|
||||
---@field append_all fun(nodes: RuntimeNode[], extra_nodes: RuntimeNode[]): RuntimeNode[]
|
||||
|
||||
---@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 RuntimeLabeledProgressOpts: RuntimeNodeInit
|
||||
---@field labelHeight? number
|
||||
---@field labelStyle? RuntimeNodeProps
|
||||
|
||||
---@class RuntimePillOpts: RuntimeNodeInit
|
||||
---@field panelStyle? RuntimeNodeProps
|
||||
---@field textStyle? RuntimeNodeProps
|
||||
|
||||
---@class RuntimeTextButtonOpts: RuntimeNodeInit
|
||||
---@field variant? RuntimeButtonVariant
|
||||
|
||||
---@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
|
||||
---@field configure fun(tokens?: RuntimeWidgetTheme): RuntimeWidgets
|
||||
---@field label fun(id: string, text: string|RuntimeNodeInit, x?: number, y?: number, width?: number, height?: number, opts?: RuntimeNodeInit): RuntimeNode
|
||||
---@field section_title fun(id: string, text: string|RuntimeNodeInit, x?: number, y?: number, width?: number, height?: number, opts?: RuntimeNodeInit): RuntimeNode
|
||||
---@field pill fun(id: string, text: string|RuntimePillOpts, x?: number, y?: number, width?: number, height?: number, opts?: RuntimePillOpts): RuntimeNode[]
|
||||
---@field text_button fun(id: string, text: string|RuntimeTextButtonOpts, x?: number, y?: number, width?: number, height?: number, handler?: string, opts?: RuntimeTextButtonOpts): RuntimeNode
|
||||
---@field list_item fun(id: string, text: string|RuntimeListItemOpts, x?: number, y?: number, width?: number, height?: number, handler?: string, opts?: RuntimeListItemOpts): RuntimeNode
|
||||
---@field tabs fun(id: string, tabs: RuntimeTabItem[]|RuntimeTabsOpts, opts?: RuntimeTabsOpts): RuntimeNode[]
|
||||
---@field action_row fun(id: string, actions: RuntimeActionItem[]|RuntimeActionRowOpts, opts?: RuntimeActionRowOpts): RuntimeNode[]
|
||||
---@field panel_header fun(id: string, opts: RuntimePanelHeaderOpts): RuntimeNode[]
|
||||
---@field overlay fun(id: string, width: number|RuntimeNodeInit, height?: number, opts?: RuntimeNodeInit): RuntimeNode
|
||||
---@field card fun(id: string, x: number|RuntimeNodeInit, y?: number, width?: number, height?: number, opts?: RuntimeNodeInit): RuntimeNode
|
||||
---@field progress_bar fun(id: string, x: number|RuntimeNodeInit, y?: number, width?: number, height?: number, value?: number, opts?: RuntimeNodeInit): RuntimeNode
|
||||
---@field labeled_progress fun(id: string, label: string, x: number, y: number, width: number, height: number, value: number, opts?: RuntimeLabeledProgressOpts): RuntimeNode[]
|
||||
---@field button_row fun(parent: string, id: string, buttons: RuntimeDialogButton[], x: number, y: number, width: number, height: number, gap?: number, opts?: RuntimeNodeProps): RuntimeNode[]
|
||||
---@field dialog fun(id: string, title: string, message: string, x: number, y: number, width: number, height: number, opts?: RuntimeDialogOpts): RuntimeNode[]
|
||||
|
||||
---@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
|
||||
---@field item fun(node: RuntimeNode, opts?: RuntimeLayoutItemOpts): RuntimeLayoutItem
|
||||
---@field local_position fun(origin: RuntimePoint, position: RuntimePoint): RuntimePoint
|
||||
---@field row fun(parent?: string, items: (RuntimeNode|RuntimeLayoutItem)[], opts?: RuntimeLinearLayoutOpts): RuntimeNode[]
|
||||
---@field column fun(parent?: string, items: (RuntimeNode|RuntimeLayoutItem)[], opts?: RuntimeLinearLayoutOpts): RuntimeNode[]
|
||||
---@field stack fun(parent?: string, items: (RuntimeNode|RuntimeLayoutItem)[], opts?: RuntimeLinearLayoutOpts): RuntimeNode[]
|
||||
---@field box fun(parent?: string, items: (RuntimeNode|RuntimeLayoutItem)[], opts?: RuntimeBoxLayoutOpts): RuntimeNode[]
|
||||
|
||||
---@class RuntimeCommands
|
||||
---@field toast fun(text: string, opts?: RuntimeCommandOpts): RuntimeCommand
|
||||
---@field copy_text fun(text: string, opts?: RuntimeCommandOpts): RuntimeCommand
|
||||
---@field delay fun(duration: number, opts?: RuntimeCommandOpts): RuntimeCommand
|
||||
---@field sequence fun(items: RuntimeCommand[], opts?: RuntimeCommandOpts): RuntimeCommand
|
||||
---@field parallel fun(items: RuntimeCommand[], opts?: RuntimeCommandOpts): RuntimeCommand
|
||||
---@field move_path fun(target: string, path: RuntimePoint[], opts?: RuntimeCommandOpts): RuntimeCommand
|
||||
---@field move_to fun(target: string, x: number, y: number, opts?: RuntimeCommandOpts): RuntimeCommand
|
||||
---@field fade_to fun(target: string, alpha: number, opts?: RuntimeCommandOpts): RuntimeCommand
|
||||
---@field scale_to fun(target: string, scale: number, opts?: RuntimeCommandOpts): RuntimeCommand
|
||||
---@field rotate_to fun(target: string, angle: number, opts?: RuntimeCommandOpts): RuntimeCommand
|
||||
---@field remove_node fun(target: string, opts?: RuntimeCommandOpts): RuntimeCommand
|
||||
---@field play_spine_animation fun(target: string, animation: string, opts?: RuntimeSpineCommandOpts): RuntimeCommand
|
||||
---@field play_sound fun(asset: string, opts?: RuntimeAudioCommandOpts): RuntimeCommand
|
||||
---@field play_bgm fun(asset: string, opts?: RuntimeBgmCommandOpts): RuntimeCommand
|
||||
---@field pause_bgm fun(channel?: string, opts?: RuntimeCommandOpts): RuntimeCommand
|
||||
---@field resume_bgm fun(channel?: string, opts?: RuntimeCommandOpts): RuntimeCommand
|
||||
---@field stop_bgm fun(channel?: string, opts?: RuntimeCommandOpts): RuntimeCommand
|
||||
---@field preload_group fun(group: string, opts?: RuntimeResourceCommandOpts): RuntimeCommand
|
||||
---@field evict_group fun(group: string, opts?: RuntimeCommandOpts): RuntimeCommand
|
||||
---@field cancel_id fun(id: string): RuntimeCommand
|
||||
---@field cancel_group fun(group: string): RuntimeCommand
|
||||
---@field cancel_scope fun(scope: string): RuntimeCommand
|
||||
|
||||
---@class RuntimeImportApi
|
||||
---@field import fun(moduleName: string): table
|
||||
|
||||
---@type RuntimeImportApi
|
||||
runtime = runtime
|
||||
|
||||
---@alias PlayerColor 'red'|'blue'
|
||||
---@alias GamePhase 'waiting_roll'|'waiting_piece'|'animating'
|
||||
---@alias PieceStatus 'home'|'path'|'finished'
|
||||
|
||||
---@class (exact) PieceState
|
||||
---@field id string
|
||||
---@field owner PlayerColor
|
||||
---@field path_index integer
|
||||
---@field status PieceStatus
|
||||
|
||||
---@class (exact) LudoState
|
||||
---@field current_player PlayerColor
|
||||
---@field phase GamePhase
|
||||
---@field dice_index integer
|
||||
---@field dice_values integer[]
|
||||
---@field dice? integer
|
||||
---@field selected_piece? string
|
||||
---@field players PlayerColor[]
|
||||
---@field pieces table<string, PieceState>
|
||||
|
||||
---@class (exact) BoardPoint
|
||||
---@field x number
|
||||
---@field y number
|
||||
|
||||
---@class (exact) BoardData
|
||||
---@field red_home BoardPoint[]
|
||||
---@field blue_home BoardPoint[]
|
||||
---@field path BoardPoint[]
|
||||
---@field start table<PlayerColor, integer>
|
||||
15
example/assets/games/ludo/scripts/state.lua
Normal file
@@ -0,0 +1,15 @@
|
||||
---@type LudoState
|
||||
return {
|
||||
current_player = "red",
|
||||
phase = "waiting_roll",
|
||||
dice_index = 0,
|
||||
dice_values = {6, 3, 5, 2, 6, 4},
|
||||
selected_piece = nil,
|
||||
players = {"red", "blue"},
|
||||
pieces = {
|
||||
red_1 = { id = "red_1", owner = "red", path_index = 0, status = "home" },
|
||||
red_2 = { id = "red_2", owner = "red", path_index = 0, status = "home" },
|
||||
blue_1 = { id = "blue_1", owner = "blue", path_index = 0, status = "home" },
|
||||
blue_2 = { id = "blue_2", owner = "blue", path_index = 0, status = "home" }
|
||||
}
|
||||
}
|
||||
80
example/assets/games/ludo/scripts/styles.lua
Normal file
@@ -0,0 +1,80 @@
|
||||
local theme = runtime.import("theme")
|
||||
|
||||
local colors = theme.colors
|
||||
local styles = {}
|
||||
|
||||
styles.layers = {
|
||||
background = 0,
|
||||
board = 1,
|
||||
board_content = 2,
|
||||
piece = 10,
|
||||
hud = 100,
|
||||
hud_content = 101
|
||||
}
|
||||
|
||||
styles.board_panel = {
|
||||
color = colors.board,
|
||||
layer = styles.layers.background
|
||||
}
|
||||
|
||||
styles.board_title = {
|
||||
fontSize = 24,
|
||||
color = colors.text,
|
||||
layer = styles.layers.board_content
|
||||
}
|
||||
|
||||
styles.red_home = {
|
||||
color = colors.red_home,
|
||||
layer = styles.layers.board
|
||||
}
|
||||
|
||||
styles.blue_home = {
|
||||
color = colors.blue_home,
|
||||
layer = styles.layers.board
|
||||
}
|
||||
|
||||
styles.path_cell = {
|
||||
anchor = "center",
|
||||
color = colors.path_cell,
|
||||
layer = styles.layers.board_content
|
||||
}
|
||||
|
||||
styles.top_bar = {
|
||||
color = colors.top_bar,
|
||||
layer = styles.layers.hud
|
||||
}
|
||||
|
||||
styles.hud_text = {
|
||||
fontSize = 22,
|
||||
color = colors.text,
|
||||
layer = styles.layers.hud_content
|
||||
}
|
||||
|
||||
styles.dice_button = {
|
||||
color = colors.dice_button,
|
||||
fontSize = 20,
|
||||
layer = styles.layers.hud_content
|
||||
}
|
||||
|
||||
styles.piece_highlight = {
|
||||
scale = 1.25,
|
||||
alpha = 1
|
||||
}
|
||||
|
||||
styles.piece_normal = {
|
||||
scale = 1,
|
||||
alpha = 0.95
|
||||
}
|
||||
|
||||
function styles.piece(owner)
|
||||
return {
|
||||
anchor = "center",
|
||||
asset = "piece_" .. owner,
|
||||
color = colors[owner],
|
||||
layer = styles.layers.piece,
|
||||
interactive = true,
|
||||
onTap = "piece_tap"
|
||||
}
|
||||
end
|
||||
|
||||
return styles
|
||||
18
example/assets/games/ludo/scripts/theme.lua
Normal file
@@ -0,0 +1,18 @@
|
||||
local i18n = runtime.import("i18n")
|
||||
|
||||
return {
|
||||
title = function()
|
||||
return i18n.t("title")
|
||||
end,
|
||||
colors = {
|
||||
red = "#ef4444",
|
||||
blue = "#3b82f6",
|
||||
top_bar = "#020617",
|
||||
dice_button = "#2563eb",
|
||||
board = "#1e293b",
|
||||
red_home = "#7f1d1d",
|
||||
blue_home = "#1e3a8a",
|
||||
path_cell = "#f8fafc",
|
||||
text = "#ffffff"
|
||||
}
|
||||
}
|
||||
74
example/assets/games/ludo/scripts/ui.lua
Normal file
@@ -0,0 +1,74 @@
|
||||
---@type RuntimeUi
|
||||
local runtime_ui = runtime.import("runtime_ui")
|
||||
---@type RuntimeLayout
|
||||
local layout = runtime.import("layout")
|
||||
local theme = runtime.import("theme")
|
||||
local i18n = runtime.import("i18n")
|
||||
local styles = runtime.import("styles")
|
||||
local state = runtime.import("state")
|
||||
local board = runtime.import("board")
|
||||
local rules = runtime.import("rules")
|
||||
|
||||
local ui = {}
|
||||
local board_origin = { x = 40, y = 90 }
|
||||
|
||||
function ui.highlight_updates(ids, enabled)
|
||||
return runtime_ui.batch_update(ids, enabled and styles.piece_highlight or styles.piece_normal)
|
||||
end
|
||||
|
||||
function ui.create_board_nodes()
|
||||
local nodes = {
|
||||
runtime_ui.sprite("board_panel", "board", 40, 90, 640, 520, styles.board_panel),
|
||||
runtime_ui.text("board_title", theme.title(), 240, 15, 180, 40, runtime_ui.with_parent("board_panel", styles.board_title)),
|
||||
runtime_ui.panel("red_home", 20, 390, 130, 90, runtime_ui.with_parent("board_panel", styles.red_home)),
|
||||
runtime_ui.panel("blue_home", 460, -10, 130, 90, runtime_ui.with_parent("board_panel", styles.blue_home))
|
||||
}
|
||||
|
||||
for index, cell in ipairs(board.path) do
|
||||
local pos = layout.local_position(board_origin, cell)
|
||||
runtime_ui.append(nodes, runtime_ui.circle("cell_" .. tostring(index), pos.x, pos.y, 28, runtime_ui.with_parent("board_panel", styles.path_cell)))
|
||||
end
|
||||
|
||||
for id, piece in pairs(state.pieces) do
|
||||
local pos = rules.piece_home_position(piece)
|
||||
runtime_ui.append(nodes, runtime_ui.circle(id, pos.x, pos.y, 44, styles.piece(piece.owner)))
|
||||
end
|
||||
|
||||
return nodes
|
||||
end
|
||||
|
||||
function ui.create_ui_nodes()
|
||||
local hud_items = layout.row("top_bar", {
|
||||
runtime_ui.text("turn_text", i18n.t("current_player", { player = i18n.player(state.current_player) }), 0, 0, 230, 32, styles.hud_text),
|
||||
runtime_ui.text("dice_text", i18n.t("dice_empty"), 0, 0, 120, 32, styles.hud_text),
|
||||
layout.item(
|
||||
runtime_ui.button("dice_button", i18n.t("roll_button"), 0, 0, 130, 48, "roll_dice", styles.dice_button),
|
||||
{ marginLeft = 134 }
|
||||
)
|
||||
}, {
|
||||
x = 24,
|
||||
height = 76,
|
||||
gap = 16,
|
||||
align = "center"
|
||||
})
|
||||
|
||||
local nodes = { runtime_ui.panel("top_bar", 0, 0, 720, 76, styles.top_bar) }
|
||||
return runtime_ui.append_all(nodes, hud_items)
|
||||
end
|
||||
|
||||
function ui.dice_and_turn_updates(dice, current_player)
|
||||
return {
|
||||
runtime_ui.text_update("dice_text", i18n.t("dice_value", { value = dice })),
|
||||
runtime_ui.text_update("turn_text", i18n.t("current_player", { player = i18n.player(current_player) }))
|
||||
}
|
||||
end
|
||||
|
||||
function ui.dice_update(dice)
|
||||
return { runtime_ui.text_update("dice_text", i18n.t("dice_value", { value = dice })) }
|
||||
end
|
||||
|
||||
function ui.turn_update(current_player)
|
||||
return { runtime_ui.text_update("turn_text", i18n.t("current_player", { player = i18n.player(current_player) })) }
|
||||
end
|
||||
|
||||
return ui
|
||||
BIN
example/assets/games/showcase/assets/button_disabled.png
Normal file
|
After Width: | Height: | Size: 138 B |
BIN
example/assets/games/showcase/assets/button_normal.png
Normal file
|
After Width: | Height: | Size: 136 B |
BIN
example/assets/games/showcase/assets/button_pressed.png
Normal file
|
After Width: | Height: | Size: 136 B |
BIN
example/assets/games/showcase/assets/click.wav
Normal file
BIN
example/assets/games/showcase/assets/sample.png
Normal file
|
After Width: | Height: | Size: 70 B |
62
example/assets/games/showcase/manifest.json
Normal file
@@ -0,0 +1,62 @@
|
||||
{
|
||||
"gameId": "showcase",
|
||||
"name": "Lua Runtime Showcase",
|
||||
"version": "0.1.0",
|
||||
"runtimeApiVersion": 1,
|
||||
"entry": "scripts/main.lua",
|
||||
"assetsBase": "assets",
|
||||
"defaultLocale": "zh-Hans",
|
||||
"supportedLocales": [
|
||||
"zh-Hans",
|
||||
"en"
|
||||
],
|
||||
"display": {
|
||||
"designWidth": 720,
|
||||
"designHeight": 720,
|
||||
"scaleMode": "fit"
|
||||
},
|
||||
"modules": {
|
||||
"runtime_ui": "runtime:runtime_ui.lua",
|
||||
"runtime_widgets": "runtime:runtime_widgets.lua",
|
||||
"runtime_commands": "runtime:runtime_commands.lua",
|
||||
"layout": "runtime:layout.lua",
|
||||
"theme": "scripts/theme.lua",
|
||||
"styles": "scripts/styles.lua",
|
||||
"state": "scripts/state.lua",
|
||||
"i18n": "scripts/i18n.lua",
|
||||
"examples": "scripts/examples.lua",
|
||||
"ui": "scripts/ui.lua"
|
||||
},
|
||||
"resources": {
|
||||
"sample_image": {
|
||||
"type": "image",
|
||||
"path": "assets/sample.png",
|
||||
"preload": "optional",
|
||||
"group": "media"
|
||||
},
|
||||
"click": {
|
||||
"type": "audio",
|
||||
"path": "assets/click.wav",
|
||||
"preload": "lazy",
|
||||
"group": "media"
|
||||
},
|
||||
"button_normal": {
|
||||
"type": "image",
|
||||
"path": "assets/button_normal.png",
|
||||
"preload": "optional",
|
||||
"group": "media"
|
||||
},
|
||||
"button_pressed": {
|
||||
"type": "image",
|
||||
"path": "assets/button_pressed.png",
|
||||
"preload": "optional",
|
||||
"group": "media"
|
||||
},
|
||||
"button_disabled": {
|
||||
"type": "image",
|
||||
"path": "assets/button_disabled.png",
|
||||
"preload": "optional",
|
||||
"group": "media"
|
||||
}
|
||||
}
|
||||
}
|
||||
819
example/assets/games/showcase/scripts/examples.lua
Normal file
@@ -0,0 +1,819 @@
|
||||
---@class (exact) ShowcaseAction
|
||||
---@field text string
|
||||
---@field text_en? string
|
||||
---@field handler string
|
||||
|
||||
---@class (exact) ShowcaseExample
|
||||
---@field id string
|
||||
---@field group string
|
||||
---@field group_en? string
|
||||
---@field category string
|
||||
---@field category_en? string
|
||||
---@field menu string
|
||||
---@field menu_en? string
|
||||
---@field title string
|
||||
---@field title_en? string
|
||||
---@field summary string
|
||||
---@field summary_en? string
|
||||
---@field code string
|
||||
---@field params string
|
||||
---@field params_en? string
|
||||
---@field actions ShowcaseAction[]
|
||||
|
||||
---@class ShowcaseExamples
|
||||
local examples = {}
|
||||
|
||||
---@type ShowcaseExample[]
|
||||
examples.items = {
|
||||
{
|
||||
id = "nodes",
|
||||
group = "基础功能",
|
||||
group_en = "Basics",
|
||||
category = "节点",
|
||||
category_en = "Nodes",
|
||||
menu = "基础节点",
|
||||
menu_en = "Nodes",
|
||||
title = "RuntimeNode 基础组件",
|
||||
title_en = "RuntimeNode primitives",
|
||||
summary = "基础节点、交互和属性更新。",
|
||||
summary_en = "Primitive nodes, interaction and property updates.",
|
||||
code = [[local runtime_ui = runtime.import("runtime_ui")
|
||||
|
||||
runtime_ui.circle("hero", {
|
||||
x = 80,
|
||||
y = 64,
|
||||
size = 48,
|
||||
color = "#ff22c55e",
|
||||
interactive = true,
|
||||
handler = "demo_anim"
|
||||
})]],
|
||||
params = [[参数说明:type/circle/rect/line 定义节点类型;
|
||||
x/y/width/height 定位尺寸;
|
||||
w/h 是 width/height 的便利别名;
|
||||
size 会同时设置 width 和 height;
|
||||
handler/onClick 是 onTap 的便利别名;
|
||||
color/alpha/scale/rotation 控制表现;
|
||||
interactive/onTap 打开点击事件。
|
||||
|
||||
填写样例:
|
||||
runtime_ui.circle("hero", {
|
||||
x = 80,
|
||||
y = 64,
|
||||
size = 48,
|
||||
color = "#ff22c55e",
|
||||
alpha = 0.9,
|
||||
scale = 1.0,
|
||||
interactive = true,
|
||||
handler = "demo_anim"
|
||||
})]],
|
||||
params_en = [[Params: type/circle/rect/line choose node kind;
|
||||
x/y/width/height place it;
|
||||
color/alpha/scale/rotation style it;
|
||||
interactive/onTap enable taps.]],
|
||||
actions = {
|
||||
{ text = "更新进度", text_en = "Progress", handler = "demo_progress" },
|
||||
{ text = "显隐/透明", text_en = "Visibility", handler = "demo_visibility" }
|
||||
}
|
||||
},
|
||||
{
|
||||
id = "text_demo",
|
||||
group = "基础功能",
|
||||
group_en = "Basics",
|
||||
category = "文本",
|
||||
category_en = "Text",
|
||||
menu = "文本",
|
||||
menu_en = "Text",
|
||||
title = "Text 文本组件",
|
||||
title_en = "Text component",
|
||||
summary = "当前支持纯文本、颜色和字号;暂不支持富文本。",
|
||||
summary_en = "Plain text, color and font size; rich text is not supported yet.",
|
||||
code = [[runtime_ui.text("label", "Hello Lua", 20, 40, 220, 28, {
|
||||
color = "#ffe2e8f0",
|
||||
fontSize = 18
|
||||
})
|
||||
|
||||
-- 当前协议没有 richText/spans 字段]],
|
||||
params = [[参数说明:text 是纯文本;
|
||||
color/fontSize/alpha 控制整体样式;
|
||||
textAlign 可填 "left" / "center" / "right";
|
||||
当前没有 richText/spans,不能做局部文字样式。
|
||||
|
||||
填写样例:
|
||||
runtime_ui.text("label", "Hello Lua", 20, 40, 220, 28, {
|
||||
color = "#ffe2e8f0",
|
||||
fontSize = 18,
|
||||
textAlign = "left"
|
||||
})]],
|
||||
params_en = [[Params: text is plain copy;
|
||||
color/fontSize/alpha style the whole node;
|
||||
richText/spans are not supported yet.]],
|
||||
actions = {
|
||||
{ text = "改文案", text_en = "Change", handler = "demo_text_change" },
|
||||
{ text = "样式切换", text_en = "Style", handler = "demo_text_style" }
|
||||
}
|
||||
},
|
||||
{
|
||||
id = "buttons",
|
||||
group = "基础功能",
|
||||
group_en = "Basics",
|
||||
category = "按钮",
|
||||
category_en = "Buttons",
|
||||
menu = "按钮交互",
|
||||
menu_en = "Buttons",
|
||||
title = "基础按钮和点击事件",
|
||||
title_en = "Buttons and tap events",
|
||||
summary = "button 节点、onTap handler、状态更新和禁用态。",
|
||||
summary_en = "Button nodes, onTap handlers, state updates and disabled style.",
|
||||
code = [[runtime_ui.button("ok", {
|
||||
text = "确认",
|
||||
x = 20,
|
||||
y = 40,
|
||||
w = 120,
|
||||
h = 34,
|
||||
handler = "submit",
|
||||
color = theme.primary,
|
||||
radius = 10,
|
||||
fontSize = 13,
|
||||
asset = "button_normal",
|
||||
pressedAsset = "button_pressed",
|
||||
disabledAsset = "button_disabled"
|
||||
})
|
||||
|
||||
-- asset/pressedAsset/disabledAsset 都是 manifest image 资源 key。
|
||||
-- event.handler == "submit" 时返回 Diff/Command]],
|
||||
params = [[参数说明:button = 背景 + 文本 + onTap;
|
||||
w/h 是 width/height 的便利别名;
|
||||
handler/onClick 是 onTap 的便利别名;
|
||||
color/radius/fontSize 控制样式;
|
||||
asset/pressedAsset/disabledAsset 可配置 normal/pressed/disabled 三态图片;
|
||||
interactive=false 可禁用命中并使用 disabledAsset。
|
||||
|
||||
填写样例:
|
||||
runtime_ui.button("ok", {
|
||||
text = "确认",
|
||||
x = 20,
|
||||
y = 40,
|
||||
w = 120,
|
||||
h = 34,
|
||||
handler = "submit",
|
||||
color = theme.primary,
|
||||
radius = 10,
|
||||
fontSize = 13,
|
||||
asset = "button_normal",
|
||||
pressedAsset = "button_pressed",
|
||||
disabledAsset = "button_disabled",
|
||||
interactive = true
|
||||
})]],
|
||||
params_en = [[Params: button combines background, label and onTap;
|
||||
color/radius/fontSize style it;
|
||||
interactive=false disables hit testing.]],
|
||||
actions = {
|
||||
{ text = "点击按钮", text_en = "Tap", handler = "demo_button_primary" },
|
||||
{ text = "切换状态", text_en = "Toggle", handler = "demo_button_toggle" }
|
||||
}
|
||||
},
|
||||
{
|
||||
id = "button_images",
|
||||
group = "基础功能",
|
||||
group_en = "Basics",
|
||||
category = "按钮",
|
||||
category_en = "Buttons",
|
||||
menu = "按钮三态图",
|
||||
menu_en = "Button skins",
|
||||
title = "Button 三态图片",
|
||||
title_en = "Button state images",
|
||||
summary = "button 使用 normal / pressed / disabled 三态图片资源。",
|
||||
summary_en = "Buttons can render normal, pressed and disabled image assets.",
|
||||
code = [[runtime_ui.button("start", {
|
||||
text = "开始",
|
||||
x = 20,
|
||||
y = 40,
|
||||
w = 132,
|
||||
h = 40,
|
||||
handler = "start",
|
||||
asset = "button_normal",
|
||||
pressedAsset = "button_pressed",
|
||||
disabledAsset = "button_disabled"
|
||||
})
|
||||
|
||||
runtime_ui.button("locked", {
|
||||
text = "禁用",
|
||||
x = 170,
|
||||
y = 40,
|
||||
w = 132,
|
||||
h = 40,
|
||||
handler = "noop",
|
||||
asset = "button_normal",
|
||||
pressedAsset = "button_pressed",
|
||||
disabledAsset = "button_disabled",
|
||||
interactive = false
|
||||
})]],
|
||||
params = [[参数说明:
|
||||
asset = normal 状态图片资源 key;
|
||||
pressedAsset = 按下状态图片资源 key;
|
||||
disabledAsset = 禁用状态图片资源 key;
|
||||
interactive=false 时 Runtime 自动选择 disabledAsset;
|
||||
按住按钮时 Runtime 自动切换 pressedAsset;
|
||||
未配置对应状态图片时回退到 asset,再回退到 color/radius 背景。
|
||||
|
||||
manifest 示例:
|
||||
"button_normal": { "type": "image", "path": "assets/button_normal.png" },
|
||||
"button_pressed": { "type": "image", "path": "assets/button_pressed.png" },
|
||||
"button_disabled": { "type": "image", "path": "assets/button_disabled.png" }]],
|
||||
params_en = [[Params:
|
||||
asset is the normal-state image resource key;
|
||||
pressedAsset is used while the button is pressed;
|
||||
disabledAsset is used when interactive=false;
|
||||
missing state images fall back to asset, then to color/radius background.]],
|
||||
actions = {
|
||||
{ text = "点击图片按钮", text_en = "Tap", handler = "demo_button_image_tap" },
|
||||
{ text = "切换禁用", text_en = "Toggle", handler = "demo_button_image_toggle" }
|
||||
}
|
||||
},
|
||||
{
|
||||
id = "sprites",
|
||||
group = "基础功能",
|
||||
group_en = "Basics",
|
||||
category = "精灵",
|
||||
category_en = "Sprites",
|
||||
menu = "图片精灵",
|
||||
menu_en = "Sprites",
|
||||
title = "图片和 Sprite 节点",
|
||||
title_en = "Image and sprite nodes",
|
||||
summary = "image/sprite 使用 manifest 资源 key,不暴露真实路径。",
|
||||
summary_en = "Image/sprite nodes use manifest resource keys, not raw paths.",
|
||||
code = [[runtime_ui.image("avatar", "sample_image", 24, 48, 56, 56)
|
||||
runtime_ui.sprite("icon", "sample_image", 104, 48, 56, 56, {
|
||||
layer = 20
|
||||
})]],
|
||||
params = [[参数说明:asset 必须是 manifest 资源 key;
|
||||
image/sprite 不暴露真实路径;
|
||||
layer 控制绘制顺序。
|
||||
|
||||
填写样例:
|
||||
-- manifest.resources.sample_image.type = "image"
|
||||
runtime_ui.image("avatar", "sample_image", 24, 48, 56, 56, {
|
||||
layer = 20,
|
||||
alpha = 1
|
||||
})]],
|
||||
params_en = [[Params: asset must be a manifest resource key;
|
||||
image/sprite never expose raw paths;
|
||||
layer controls draw order.]],
|
||||
actions = {
|
||||
{ text = "精灵动画", text_en = "Animate", handler = "demo_sprite_anim" },
|
||||
{ text = "切换样式", text_en = "Style", handler = "demo_sprite_style" }
|
||||
}
|
||||
},
|
||||
{
|
||||
id = "radio_group",
|
||||
group = "基础功能",
|
||||
group_en = "Basics",
|
||||
category = "选择",
|
||||
category_en = "Selection",
|
||||
menu = "RadioGroup",
|
||||
menu_en = "Radio",
|
||||
title = "RadioGroup 组合模式",
|
||||
title_en = "RadioGroup composition",
|
||||
summary = "Lua 用 circle/text/button 组合单选项。",
|
||||
summary_en = "Lua composes radio options from circle/text/button nodes.",
|
||||
code = [[-- RadioGroup 不是 Dart 原生控件
|
||||
-- Lua 输出普通 RuntimeNode:circle + text + button
|
||||
|
||||
runtime_ui.circle("radio_dot", 24, 52, 14, { color = theme.primary })
|
||||
runtime_ui.text("radio_label", "Audio", 46, 49, 120, 20)]],
|
||||
params = [[参数说明:RadioGroup 是 Lua 组合,不是原生控件;
|
||||
用 circle/text/button 组合,并由 Lua state 保存选中值。
|
||||
|
||||
填写样例:
|
||||
local option = { key = "audio", label = "Audio", y = 72 }
|
||||
runtime_ui.circle("radio_" .. option.key .. "_dot", 18, option.y, 14, {
|
||||
color = selected and theme.primary or "#ff475569"
|
||||
})
|
||||
runtime_ui.button("radio_" .. option.key .. "_hit", "", 8, option.y - 4, 180, 24, "demo_radio_audio", {
|
||||
color = "#00000000",
|
||||
interactive = true
|
||||
})]],
|
||||
params_en = [[Params: RadioGroup is Lua composition, not a native control;
|
||||
compose circle/text/button and keep selection in Lua state.]],
|
||||
actions = {
|
||||
{ text = "选 Audio", text_en = "Audio", handler = "demo_radio_audio" },
|
||||
{ text = "选 Spine", text_en = "Spine", handler = "demo_radio_spine" },
|
||||
{ text = "选 Lua", text_en = "Lua", handler = "demo_radio_lua" }
|
||||
}
|
||||
},
|
||||
{
|
||||
id = "list_view",
|
||||
group = "基础功能",
|
||||
group_en = "Basics",
|
||||
category = "列表",
|
||||
category_en = "List",
|
||||
menu = "ListView",
|
||||
menu_en = "List",
|
||||
title = "原生 ListView 容器",
|
||||
title_en = "Native ListView container",
|
||||
summary = "双轴滚动、惯性、回调、滚动条样式和虚拟化。",
|
||||
summary_en = "Two-axis scroll, inertia, callbacks, styled scrollbar and culling.",
|
||||
code = [[runtime_ui.list_view("list", 16, 42, 260, 72, {
|
||||
contentWidth = 420,
|
||||
contentHeight = 150,
|
||||
scrollX = state.list_scroll_x,
|
||||
scrollY = state.list_scroll_y,
|
||||
virtualized = true,
|
||||
cacheExtent = 24,
|
||||
inertia = true,
|
||||
onScroll = "demo_list_scrolled"
|
||||
})
|
||||
|
||||
runtime_ui.button("row_1", "Lua", 8, 8, 220, 24, "select", {
|
||||
parent = "list"
|
||||
})]],
|
||||
params = [[参数说明:scrollX/scrollY 是双轴偏移;
|
||||
contentWidth/contentHeight 是内容尺寸;
|
||||
virtualized/cacheExtent 控制直接子节点裁剪;
|
||||
inertia 开启拖动惯性;
|
||||
onScroll 接收滚动回调;
|
||||
scrollbarVisible=false 可隐藏滚动条;
|
||||
scrollbarThumbColor/scrollbarTrackColor/scrollbarThickness 控制样式。
|
||||
|
||||
填写样例:
|
||||
local opts = {
|
||||
contentWidth = 420,
|
||||
contentHeight = 150,
|
||||
scrollX = 0,
|
||||
scrollY = 0,
|
||||
virtualized = true,
|
||||
cacheExtent = 24,
|
||||
inertia = true,
|
||||
onScroll = "demo_list_scrolled",
|
||||
scrollbarVisible = false
|
||||
}
|
||||
runtime_ui.list_view("list", 16, 42, 260, 72, opts)
|
||||
|
||||
-- 子节点必须把 parent 指向 listView
|
||||
runtime_ui.button("row_1", "Lua", 8, 8, 220, 24, "select", {
|
||||
parent = "list"
|
||||
})]],
|
||||
params_en = [[Params: scrollX/scrollY are two-axis offsets;
|
||||
contentWidth/contentHeight define content size;
|
||||
virtualized/cacheExtent cull children;
|
||||
inertia enables momentum;
|
||||
onScroll emits callbacks;
|
||||
scrollbar* styles bars.]],
|
||||
actions = {
|
||||
{ text = "横/竖排列", text_en = "Axis", handler = "demo_list_horizontal" },
|
||||
{ text = "下一项", text_en = "Next", handler = "demo_list_next" },
|
||||
{ text = "重置滚动", text_en = "Reset", handler = "demo_list_reset" }
|
||||
}
|
||||
},
|
||||
{
|
||||
id = "particles",
|
||||
group = "基础功能",
|
||||
group_en = "Basics",
|
||||
category = "特效",
|
||||
category_en = "Effects",
|
||||
menu = "粒子特效",
|
||||
menu_en = "Particles",
|
||||
title = "Particle 粒子特效",
|
||||
title_en = "Particle effects",
|
||||
summary = "Lua 描述粒子参数,Dart Runtime 创建 Flame 粒子组件。",
|
||||
summary_en = "Lua describes particle params; Dart Runtime creates Flame particles.",
|
||||
code = [[runtime_ui.particle("hit_fx", 220, 140, 160, 160, {
|
||||
preset = "burst",
|
||||
count = 40,
|
||||
duration = 0.6,
|
||||
color = "#ffffcc33",
|
||||
colorTo = "#00ffcc33",
|
||||
radius = 2.4,
|
||||
radiusTo = 0,
|
||||
speedMin = 60,
|
||||
speedMax = 180,
|
||||
gravityY = 120,
|
||||
spread = 360,
|
||||
autoRemove = true,
|
||||
fadeOut = true,
|
||||
layer = 80
|
||||
})]],
|
||||
params = [[参数说明:particle 是 Runtime 原生节点,Lua 不持有 Flame 粒子对象;
|
||||
preset 可填 "burst" / "trail" / "snow" / "confetti";
|
||||
count 是粒子数量;
|
||||
duration 是生命周期秒数;
|
||||
color/colorTo 控制颜色渐变;
|
||||
radius/radiusTo 控制尺寸变化;
|
||||
speedMin/speedMax 控制初速度范围;
|
||||
gravityX/gravityY 控制加速度;
|
||||
spread 控制发散角度;
|
||||
autoRemove=true 表示生命周期结束后自动移除;
|
||||
fadeOut=true 表示随进度淡出。
|
||||
|
||||
填写样例:
|
||||
local opts = {
|
||||
preset = "confetti",
|
||||
count = 72,
|
||||
duration = 1.2,
|
||||
color = "#ffff4d6d",
|
||||
colorTo = "#fffacc15",
|
||||
radius = 2.6,
|
||||
radiusTo = 0,
|
||||
speedMin = 120,
|
||||
speedMax = 260,
|
||||
gravityY = 240,
|
||||
spread = 90,
|
||||
autoRemove = true,
|
||||
fadeOut = true
|
||||
}
|
||||
runtime_ui.particle("win_fx", 280, 72, 220, 160, opts)]],
|
||||
params_en = [[Params: particle is a native Runtime node; Lua never owns Flame particle objects;
|
||||
preset: burst/trail/snow/confetti;
|
||||
count controls particle amount;
|
||||
duration is lifetime in seconds;
|
||||
color/colorTo define gradient;
|
||||
radius/radiusTo define size change;
|
||||
speedMin/speedMax define initial speed;
|
||||
gravityX/gravityY define acceleration;
|
||||
spread defines emission angle;
|
||||
autoRemove removes after lifetime;
|
||||
fadeOut fades by progress.]],
|
||||
actions = {
|
||||
{ text = "爆发", text_en = "Burst", handler = "demo_particle_burst" },
|
||||
{ text = "彩纸", text_en = "Confetti", handler = "demo_particle_confetti" },
|
||||
{ text = "雪花", text_en = "Snow", handler = "demo_particle_snow" }
|
||||
}
|
||||
},
|
||||
{
|
||||
id = "layout_demo",
|
||||
group = "基础功能",
|
||||
group_en = "Basics",
|
||||
category = "布局",
|
||||
category_en = "Layout",
|
||||
menu = "布局 Helper",
|
||||
menu_en = "Layout",
|
||||
title = "Lua layout helper",
|
||||
title_en = "Lua layout helper",
|
||||
summary = "layout.row/column/box 消费临时 margin,不污染协议。",
|
||||
summary_en = "layout.row/column/box consume temporary margin without polluting protocol tables.",
|
||||
code = [[local layout = runtime.import("layout")
|
||||
|
||||
layout.box("panel", {
|
||||
runtime_ui.rect("a", 0, 0, 42, 28),
|
||||
runtime_ui.rect("b", 0, 0, 42, 28),
|
||||
runtime_ui.rect("c", 0, 0, 42, 28),
|
||||
runtime_ui.rect("d", 0, 0, 42, 28)
|
||||
}, {
|
||||
x = 16,
|
||||
y = 48,
|
||||
rows = 2,
|
||||
columns = 2,
|
||||
cellWidth = 58,
|
||||
cellHeight = 34,
|
||||
gapX = 8,
|
||||
gapY = 8,
|
||||
align = "center",
|
||||
valign = "center"
|
||||
})]],
|
||||
params = [[参数说明:layout.item 的 margin 只用于 Lua 布局计算,不会进入 RuntimeNode props;
|
||||
row/column 处理单轴排列;
|
||||
box 按 rows/columns 把节点排成几排几列;
|
||||
cellWidth/cellHeight 是单元格尺寸;
|
||||
gapX/gapY 是列间距和行间距;
|
||||
align/valign 控制节点在单元格内的水平/垂直对齐。
|
||||
|
||||
填写样例:
|
||||
layout.box("panel", {
|
||||
layout.item(runtime_ui.rect("a", 0, 0, 42, 28), { marginRight = 4 }),
|
||||
runtime_ui.rect("b", 0, 0, 42, 28),
|
||||
runtime_ui.rect("c", 0, 0, 42, 28),
|
||||
runtime_ui.rect("d", 0, 0, 42, 28)
|
||||
}, {
|
||||
x = 16,
|
||||
y = 48,
|
||||
rows = 2,
|
||||
columns = 2,
|
||||
cellWidth = 58,
|
||||
cellHeight = 34,
|
||||
gapX = 8,
|
||||
gapY = 8,
|
||||
align = "center",
|
||||
valign = "center"
|
||||
})]],
|
||||
params_en = [[Params: layout.item margins are Lua-only layout metadata and never enter RuntimeNode props;
|
||||
box uses rows/columns to place nodes in a grid;
|
||||
cellWidth/cellHeight define cells;
|
||||
gapX/gapY define column and row gaps;
|
||||
align/valign align each node inside its cell.]],
|
||||
actions = {
|
||||
{ text = "横向布局", text_en = "Row", handler = "demo_layout_row" },
|
||||
{ text = "纵向布局", text_en = "Column", handler = "demo_layout_column" },
|
||||
{ text = "Box 网格", text_en = "Box", handler = "demo_layout_box" }
|
||||
}
|
||||
},
|
||||
{
|
||||
id = "diff",
|
||||
group = "运行协议",
|
||||
group_en = "Runtime Protocol",
|
||||
category = "Diff",
|
||||
category_en = "Diff",
|
||||
menu = "Diff 更新",
|
||||
menu_en = "Diff",
|
||||
title = "GameDiff 创建、更新、移除",
|
||||
title_en = "GameDiff create/update/remove",
|
||||
summary = "Diff 创建、更新、移除节点。",
|
||||
summary_en = "Create, update and remove nodes with GameDiff.",
|
||||
code = [[return {
|
||||
render = {
|
||||
creates = { node },
|
||||
updates = { { id = "node", props = { x = 120 } } },
|
||||
removes = { { id = "node" } }
|
||||
}
|
||||
}]],
|
||||
params = [[参数说明:creates 创建节点;
|
||||
updates 只更新 props;
|
||||
removes 只需要 id。
|
||||
Runtime 会白名单校验未知字段。
|
||||
|
||||
填写样例:
|
||||
return {
|
||||
ui = {
|
||||
creates = {
|
||||
runtime_ui.text("tip", "Hello", 20, 20, 160, 24, { textAlign = "left" })
|
||||
},
|
||||
updates = {
|
||||
{ id = "tip", props = { text = "Updated", color = "#ffffa000" } }
|
||||
},
|
||||
removes = {
|
||||
{ id = "tip" }
|
||||
}
|
||||
}
|
||||
}]],
|
||||
params_en = [[Params: creates add nodes;
|
||||
updates patch props;
|
||||
removes only need ids.
|
||||
Runtime rejects unknown fields by whitelist.]],
|
||||
actions = {
|
||||
{ text = "创建/删除", text_en = "Create/remove", handler = "demo_temp" },
|
||||
{ text = "Toast", text_en = "Toast", handler = "demo_toast" }
|
||||
}
|
||||
},
|
||||
{
|
||||
id = "commands",
|
||||
group = "运行协议",
|
||||
group_en = "Runtime Protocol",
|
||||
category = "命令",
|
||||
category_en = "Commands",
|
||||
menu = "命令动画",
|
||||
menu_en = "Commands",
|
||||
title = "RuntimeCommand 动作和组合",
|
||||
title_en = "RuntimeCommand actions",
|
||||
summary = "动作、组合、取消和完成回调。",
|
||||
summary_en = "Actions, composition, cancellation and callbacks.",
|
||||
code = [[commands.sequence({
|
||||
commands.move_to("actor", 220, 104, { duration = 0.55 }),
|
||||
commands.parallel({
|
||||
commands.scale_to("actor", 1.45, { duration = 0.35 }),
|
||||
commands.fade_to("actor", 0.45, { duration = 0.35 })
|
||||
})
|
||||
}, { group = "demo_anim", onComplete = "done" })]],
|
||||
params = [[参数说明:target 指向节点;
|
||||
duration 控制时长;
|
||||
sequence 串行;
|
||||
parallel 并行;
|
||||
group/id/scope 可用于取消。
|
||||
|
||||
填写样例:
|
||||
local move_opts = {
|
||||
duration = 0.55,
|
||||
id = "intro_move",
|
||||
group = "intro",
|
||||
scope = "dialog_1"
|
||||
}
|
||||
commands.move_to("actor", 220, 104, move_opts)
|
||||
|
||||
commands.cancel_group("intro")]],
|
||||
params_en = [[Params: target points to a node;
|
||||
duration controls timing;
|
||||
sequence runs serially;
|
||||
parallel runs together;
|
||||
group/id/scope support cancellation.]],
|
||||
actions = {
|
||||
{ text = "动画序列", text_en = "Animate", handler = "demo_anim" },
|
||||
{ text = "取消动画", text_en = "Cancel", handler = "demo_cancel" }
|
||||
}
|
||||
},
|
||||
{
|
||||
id = "widgets",
|
||||
group = "Lua 表现层",
|
||||
group_en = "Lua Layer",
|
||||
category = "Widget",
|
||||
category_en = "Widget",
|
||||
menu = "组合组件",
|
||||
menu_en = "Widgets",
|
||||
title = "Lua Widget 组合组件",
|
||||
title_en = "Lua widget composition",
|
||||
summary = "Lua 组合组件仍输出普通 RuntimeNode。",
|
||||
summary_en = "Lua widgets still output plain RuntimeNode tables.",
|
||||
code = [[local widgets = runtime.import("runtime_widgets")
|
||||
|
||||
widgets.dialog("dialog", "标题", "内容", 150, 190, 420, 230, {
|
||||
buttons = {
|
||||
{ text = "确定", handler = "close_dialog" }
|
||||
}
|
||||
})]],
|
||||
params = [[参数说明:widgets.dialog 是 Lua helper,输出普通 RuntimeNode;
|
||||
buttons 数组定义文案和 handler。
|
||||
|
||||
填写样例:
|
||||
local dialog_opts = {
|
||||
buttons = {
|
||||
{ text = "确定", handler = "close_dialog" },
|
||||
{ text = "取消", handler = "cancel_dialog" }
|
||||
},
|
||||
modal = true
|
||||
}
|
||||
widgets.dialog("dialog", "标题", "内容", 150, 190, 420, 230, dialog_opts)]],
|
||||
params_en = [[Params: widgets.dialog is a Lua helper that emits plain RuntimeNodes;
|
||||
buttons define labels and handlers.]],
|
||||
actions = {
|
||||
{ text = "打开 Dialog", text_en = "Open Dialog", handler = "demo_dialog" },
|
||||
{ text = "关闭 Dialog", text_en = "Close Dialog", handler = "close_dialog" }
|
||||
}
|
||||
},
|
||||
{
|
||||
id = "audio",
|
||||
group = "资源能力",
|
||||
group_en = "Assets",
|
||||
category = "资源",
|
||||
category_en = "Assets",
|
||||
menu = "资源音频",
|
||||
menu_en = "Audio",
|
||||
title = "资源、音效和 BGM",
|
||||
title_en = "Assets, SFX and BGM",
|
||||
summary = "资源 key、图片、音效、BGM 和资源组。",
|
||||
summary_en = "Resource keys, images, SFX, BGM and groups.",
|
||||
code = [[commands.play_sound("click", { volume = 0.8 })
|
||||
commands.play_bgm("click", { channel = "demo", loop = true })
|
||||
commands.preload_group("media", { failOnError = true })]],
|
||||
params = [[参数说明:asset/name 使用 manifest audio key;
|
||||
volume 范围 0~1;
|
||||
channel 管理 BGM;
|
||||
group 管理资源预载/释放。
|
||||
|
||||
填写样例:
|
||||
commands.play_sound("click", {
|
||||
volume = 0.8,
|
||||
id = "click_sfx"
|
||||
})
|
||||
commands.play_bgm("click", {
|
||||
channel = "demo",
|
||||
loop = true,
|
||||
volume = 0.5
|
||||
})
|
||||
commands.preload_group("media", { failOnError = true })]],
|
||||
params_en = [[Params: asset/name use manifest audio keys;
|
||||
volume is 0..1;
|
||||
channel manages BGM;
|
||||
group manages resource preload/evict.]],
|
||||
actions = {
|
||||
{ text = "音效", text_en = "SFX", handler = "demo_sound" },
|
||||
{ text = "BGM 循环", text_en = "BGM loop", handler = "demo_bgm" },
|
||||
{ text = "预载/释放", text_en = "Preload/evict", handler = "demo_resource" }
|
||||
}
|
||||
},
|
||||
{
|
||||
id = "spine",
|
||||
group = "资源能力",
|
||||
group_en = "Assets",
|
||||
category = "骨骼",
|
||||
category_en = "Spine",
|
||||
menu = "Spine 模板",
|
||||
menu_en = "Spine",
|
||||
title = "Spine 接入模板",
|
||||
title_en = "Spine integration template",
|
||||
summary = "Spine 资源、节点和动画命令模板。",
|
||||
summary_en = "Template for Spine resources, nodes and animation commands.",
|
||||
code = [[-- manifest.resources
|
||||
"hero_spine": {
|
||||
"type": "spine",
|
||||
"atlas": "assets/hero.atlas",
|
||||
"skeleton": "assets/hero.skel"
|
||||
}
|
||||
|
||||
runtime_ui.spine("hero", "hero_spine", 100, 120, 160, 220, "idle")
|
||||
commands.play_spine_animation("hero", "attack", { loop = false })]],
|
||||
params = [[参数说明:spine 节点使用 manifest spine key;
|
||||
animation/skin/loop 描述播放状态;
|
||||
track/queue/delay 控制动画命令。
|
||||
|
||||
填写样例:
|
||||
runtime_ui.spine("hero", "hero_spine", 100, 120, 160, 220, "idle", {
|
||||
skin = "default",
|
||||
loop = true
|
||||
})
|
||||
commands.play_spine_animation("hero", "attack", {
|
||||
track = 0,
|
||||
loop = false,
|
||||
queue = false,
|
||||
delay = 0
|
||||
})]],
|
||||
params_en = [[Params: spine nodes use manifest spine keys;
|
||||
animation/skin/loop describe playback;
|
||||
track/queue/delay control animation commands.]],
|
||||
actions = {
|
||||
{ text = "播放音效", text_en = "SFX", handler = "demo_sound" },
|
||||
{ text = "Toast", text_en = "Toast", handler = "demo_toast" }
|
||||
}
|
||||
},
|
||||
{
|
||||
id = "i18n",
|
||||
group = "平台能力",
|
||||
group_en = "Platform",
|
||||
category = "本地化",
|
||||
category_en = "I18N",
|
||||
menu = "多语言",
|
||||
menu_en = "I18N",
|
||||
title = "Lua 多语言 Showcase",
|
||||
title_en = "Lua-owned localization",
|
||||
summary = "Lua 管理文案表、回退和语言切换。",
|
||||
summary_en = "Lua owns copy tables, fallback and locale switching.",
|
||||
code = [[local i18n = runtime.import("i18n")
|
||||
|
||||
i18n.apply_context(ctx)
|
||||
runtime_ui.text("title", i18n.t("app_title"), 24, 18, 360, 34)
|
||||
|
||||
-- 点击按钮:i18n.toggle_locale() 后更新所有文案]],
|
||||
params = [[参数说明:Runtime 只传 ctx.locale;
|
||||
Lua 自己管理 messages、fallback、刷新 Diff。
|
||||
|
||||
填写样例:
|
||||
local messages = {
|
||||
["zh-Hans"] = { app_title = "Lua Showcase" },
|
||||
en = { app_title = "Lua Showcase" }
|
||||
}
|
||||
local locale = ctx.locale or "zh-Hans"
|
||||
local text = (messages[locale] or messages["zh-Hans"]).app_title]],
|
||||
params_en = [[Params: Runtime only passes ctx.locale;
|
||||
Lua owns messages, fallback and refresh diffs.]],
|
||||
actions = {
|
||||
{ text = "切换语言", text_en = "Toggle locale", handler = "demo_i18n_toggle" },
|
||||
{ text = "刷新文案", text_en = "Refresh copy", handler = "demo_i18n_refresh" }
|
||||
}
|
||||
},
|
||||
{
|
||||
id = "responsive",
|
||||
group = "平台能力",
|
||||
group_en = "Platform",
|
||||
category = "适配",
|
||||
category_en = "Layout",
|
||||
menu = "分辨率适配",
|
||||
menu_en = "Responsive",
|
||||
title = "分辨率适配 Showcase",
|
||||
title_en = "Responsive layout showcase",
|
||||
summary = "设计分辨率、viewport 和布局模拟。",
|
||||
summary_en = "Design size, viewport and layout simulation.",
|
||||
code = [[-- manifest.display
|
||||
{
|
||||
"designWidth": 720,
|
||||
"designHeight": 720,
|
||||
"scaleMode": "fit"
|
||||
}
|
||||
|
||||
-- ctx.screen / ctx.design / ctx.viewport
|
||||
-- Lua 根据设计宽度选择 compact / desktop 布局]],
|
||||
params = [[参数说明:manifest.display 声明设计分辨率;
|
||||
ctx.screen/design/viewport 给 Lua 做布局决策。
|
||||
|
||||
填写样例:
|
||||
-- manifest.display
|
||||
{
|
||||
designWidth = 720,
|
||||
designHeight = 720,
|
||||
scaleMode = "fit"
|
||||
}
|
||||
|
||||
local compact = ctx.screen.width < 640
|
||||
local menu_w = compact and 220 or 320]],
|
||||
params_en = [[Params: manifest.display declares design size;
|
||||
ctx.screen/design/viewport let Lua make layout decisions.]],
|
||||
actions = {
|
||||
{ text = "手机宽度", text_en = "Phone", handler = "demo_responsive_phone" },
|
||||
{ text = "平板宽度", text_en = "Tablet", handler = "demo_responsive_tablet" },
|
||||
{ text = "桌面宽度", text_en = "Desktop", handler = "demo_responsive_desktop" }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
---@return ShowcaseExample[]
|
||||
function examples.all()
|
||||
return examples.items
|
||||
end
|
||||
|
||||
---@param id string
|
||||
---@return ShowcaseExample
|
||||
function examples.find(id)
|
||||
for _, example in ipairs(examples.items) do
|
||||
if example.id == id then
|
||||
return example
|
||||
end
|
||||
end
|
||||
return examples.items[1]
|
||||
end
|
||||
|
||||
return examples
|
||||
151
example/assets/games/showcase/scripts/i18n.lua
Normal file
@@ -0,0 +1,151 @@
|
||||
local state = runtime.import("state")
|
||||
|
||||
---@class ShowcaseI18n
|
||||
local i18n = {}
|
||||
|
||||
local fallback_locale = "zh-Hans"
|
||||
|
||||
local messages = {
|
||||
["zh-Hans"] = {
|
||||
app_title = "Lua Runtime Showcase",
|
||||
app_subtitle = "仿 Cocos Showcase:左侧管理所有示例,右侧查看说明并直接运行。",
|
||||
examples_title = "Examples",
|
||||
preview_title = "Preview / Runtime Stage",
|
||||
tab_code = "代码",
|
||||
tab_params = "参数",
|
||||
copy_code = "复制代码",
|
||||
copy_params = "复制参数",
|
||||
selected_prefix = "已选择示例:",
|
||||
status_ready = "点击按钮查看 RuntimeEvent -> Lua -> Diff / Command 示例",
|
||||
i18n_current = "当前语言",
|
||||
i18n_zh = "中文文案:运行时只传 locale,翻译由 Lua 包管理。",
|
||||
i18n_en = "English copy: Lua owns text resources and fallback policy.",
|
||||
i18n_switched = "已切换语言:",
|
||||
responsive_title = "分辨率适配",
|
||||
responsive_design = "设计分辨率",
|
||||
responsive_screen = "屏幕",
|
||||
responsive_viewport = "视口",
|
||||
responsive_phone = "模拟手机宽度",
|
||||
responsive_tablet = "模拟平板宽度",
|
||||
responsive_desktop = "模拟桌面宽度"
|
||||
},
|
||||
en = {
|
||||
app_title = "Lua Runtime Showcase",
|
||||
app_subtitle = "Cocos-style showcase: select examples on the left, inspect and run them on the right.",
|
||||
examples_title = "Examples",
|
||||
preview_title = "Preview / Runtime Stage",
|
||||
tab_code = "Code",
|
||||
tab_params = "Params",
|
||||
copy_code = "Copy code",
|
||||
copy_params = "Copy params",
|
||||
selected_prefix = "Selected example: ",
|
||||
status_ready = "Tap actions to inspect RuntimeEvent -> Lua -> Diff / Command.",
|
||||
i18n_current = "Current locale",
|
||||
i18n_zh = "中文文案:运行时只传 locale,翻译由 Lua 包管理。",
|
||||
i18n_en = "English copy: Lua owns text resources and fallback policy.",
|
||||
i18n_switched = "Locale switched: ",
|
||||
responsive_title = "Responsive Layout",
|
||||
responsive_design = "Design",
|
||||
responsive_screen = "Screen",
|
||||
responsive_viewport = "Viewport",
|
||||
responsive_phone = "Simulate phone width",
|
||||
responsive_tablet = "Simulate tablet width",
|
||||
responsive_desktop = "Simulate desktop width"
|
||||
}
|
||||
}
|
||||
|
||||
---@param locale? string
|
||||
---@return string
|
||||
local function normalize(locale)
|
||||
if locale == nil or locale == "" then
|
||||
return fallback_locale
|
||||
end
|
||||
if messages[locale] ~= nil then
|
||||
return locale
|
||||
end
|
||||
|
||||
local language = string.match(locale, "^([A-Za-z]+)")
|
||||
if language ~= nil and messages[language] ~= nil then
|
||||
return language
|
||||
end
|
||||
return fallback_locale
|
||||
end
|
||||
|
||||
---@param ctx? RuntimeContext
|
||||
function i18n.apply_context(ctx)
|
||||
local locale = fallback_locale
|
||||
if ctx ~= nil and ctx.locale ~= nil and ctx.locale.resolved ~= nil then
|
||||
locale = ctx.locale.resolved
|
||||
end
|
||||
state.locale = normalize(locale)
|
||||
end
|
||||
|
||||
---@param locale string
|
||||
function i18n.set_locale(locale)
|
||||
state.locale = normalize(locale)
|
||||
end
|
||||
|
||||
---@return string
|
||||
function i18n.toggle_locale()
|
||||
if state.locale == "en" then
|
||||
state.locale = "zh-Hans"
|
||||
else
|
||||
state.locale = "en"
|
||||
end
|
||||
return state.locale
|
||||
end
|
||||
|
||||
---@return string
|
||||
function i18n.current_locale()
|
||||
return normalize(state.locale)
|
||||
end
|
||||
|
||||
---@param key string
|
||||
---@return string
|
||||
function i18n.t(key)
|
||||
local locale = normalize(state.locale)
|
||||
local bundle = messages[locale] or messages[fallback_locale]
|
||||
return bundle[key] or messages[fallback_locale][key] or key
|
||||
end
|
||||
|
||||
---@param example ShowcaseExample
|
||||
---@param field string
|
||||
---@return string
|
||||
function i18n.example_field(example, field)
|
||||
if i18n.current_locale() == "en" then
|
||||
local en_value = example[field .. "_en"]
|
||||
if en_value ~= nil and en_value ~= "" then
|
||||
return en_value
|
||||
end
|
||||
end
|
||||
return example[field]
|
||||
end
|
||||
|
||||
---@param example ShowcaseExample
|
||||
---@return string
|
||||
function i18n.example_label(example)
|
||||
return i18n.example_field(example, "menu")
|
||||
end
|
||||
|
||||
---@param example ShowcaseExample
|
||||
---@return string
|
||||
function i18n.example_params(example)
|
||||
return i18n.example_field(example, "params")
|
||||
end
|
||||
|
||||
---@param example ShowcaseExample
|
||||
---@return string
|
||||
function i18n.example_group(example)
|
||||
return i18n.example_field(example, "group")
|
||||
end
|
||||
|
||||
---@param action ShowcaseAction
|
||||
---@return string
|
||||
function i18n.action_text(action)
|
||||
if i18n.current_locale() == "en" and action.text_en ~= nil and action.text_en ~= "" then
|
||||
return action.text_en
|
||||
end
|
||||
return action.text
|
||||
end
|
||||
|
||||
return i18n
|
||||
698
example/assets/games/showcase/scripts/main.lua
Normal file
@@ -0,0 +1,698 @@
|
||||
local state = runtime.import("state")
|
||||
local ui = runtime.import("ui")
|
||||
---@type RuntimeUi
|
||||
local runtime_ui = runtime.import("runtime_ui")
|
||||
local examples = runtime.import("examples")
|
||||
local i18n = runtime.import("i18n")
|
||||
---@type RuntimeCommands
|
||||
local commands = runtime.import("runtime_commands")
|
||||
---@type RuntimeWidgets
|
||||
local widgets = runtime.import("runtime_widgets")
|
||||
local theme = runtime.import("theme")
|
||||
|
||||
widgets.configure({
|
||||
primary = theme.primary,
|
||||
secondary = "#ff475569",
|
||||
success = theme.success,
|
||||
overlay = "#99000000",
|
||||
surface = "#ff1e293b",
|
||||
surfaceAlt = theme.border,
|
||||
card = "#ee111827",
|
||||
text = theme.text,
|
||||
muted = theme.muted,
|
||||
progress = theme.success,
|
||||
transparent = "#00000000"
|
||||
})
|
||||
|
||||
function smoke_test(ctx)
|
||||
return ctx ~= nil
|
||||
and ctx.runtimeApiVersion ~= nil
|
||||
and examples.all ~= nil
|
||||
and i18n.t ~= nil
|
||||
and ui.create_nodes ~= nil
|
||||
and widgets.dialog ~= nil
|
||||
and commands.sequence ~= nil
|
||||
end
|
||||
|
||||
local function apply_context(ctx)
|
||||
if ctx == nil then
|
||||
return
|
||||
end
|
||||
i18n.apply_context(ctx)
|
||||
if ctx.screen ~= nil then
|
||||
state.screen_width = ctx.screen.width or state.screen_width
|
||||
state.screen_height = ctx.screen.height or state.screen_height
|
||||
end
|
||||
if ctx.viewport ~= nil then
|
||||
state.viewport_width = ctx.viewport.width or state.viewport_width
|
||||
state.viewport_height = ctx.viewport.height or state.viewport_height
|
||||
state.viewport_scale = ctx.viewport.scaleX or state.viewport_scale
|
||||
end
|
||||
end
|
||||
|
||||
function init(ctx)
|
||||
apply_context(ctx)
|
||||
state.status = i18n.t("status_ready")
|
||||
return {
|
||||
render = { creates = ui.create_nodes() },
|
||||
ui = {},
|
||||
commands = {}
|
||||
}
|
||||
end
|
||||
|
||||
---@param text string
|
||||
---@return RuntimeDiff
|
||||
local function status_only(text)
|
||||
return {
|
||||
ui = { updates = ui.status_updates(text) }
|
||||
}
|
||||
end
|
||||
|
||||
---@return RuntimeDiff
|
||||
local function handle_anim()
|
||||
return {
|
||||
ui = { updates = ui.status_updates("执行 sequence + parallel:移动、缩放、旋转、淡入淡出。") },
|
||||
commands = {
|
||||
commands.sequence({
|
||||
commands.move_path("sample_circle", {
|
||||
{ x = 104, y = 42 },
|
||||
{ x = 180, y = 44 },
|
||||
{ x = 248, y = 78 }
|
||||
}, { duration = 0.75, group = "demo_anim" }),
|
||||
commands.parallel({
|
||||
commands.scale_to("sample_circle", 1.45, { duration = 0.35, group = "demo_anim" }),
|
||||
commands.rotate_to("sample_circle", 6.28, { duration = 0.35, group = "demo_anim" }),
|
||||
commands.fade_to("sample_circle", 0.45, { duration = 0.35, group = "demo_anim" })
|
||||
}, { group = "demo_anim" }),
|
||||
commands.parallel({
|
||||
commands.move_to("sample_circle", 104, 42, { duration = 0.55, group = "demo_anim" }),
|
||||
commands.scale_to("sample_circle", 1, { duration = 0.55, group = "demo_anim" }),
|
||||
commands.fade_to("sample_circle", 1, { duration = 0.55, group = "demo_anim" })
|
||||
}, { group = "demo_anim" })
|
||||
}, { id = "demo_anim_sequence", group = "demo_anim", onComplete = "demo_anim_done" })
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
---@return RuntimeDiff
|
||||
local function handle_cancel()
|
||||
return {
|
||||
ui = { updates = ui.status_updates("已发送 cancel_commands:group = demo_anim。") },
|
||||
commands = { commands.cancel_group("demo_anim") }
|
||||
}
|
||||
end
|
||||
|
||||
---@return RuntimeDiff
|
||||
local function handle_progress()
|
||||
state.progress = state.progress + 0.15
|
||||
if state.progress > 1 then
|
||||
state.progress = 0.05
|
||||
end
|
||||
local updates = ui.progress_updates()
|
||||
local status_updates = ui.status_updates("通过 NodeDiff.update 更新 progress.value = " .. tostring(state.progress))
|
||||
for _, update in ipairs(status_updates) do
|
||||
table.insert(updates, update)
|
||||
end
|
||||
return { render = { updates = updates } }
|
||||
end
|
||||
|
||||
---@return RuntimeDiff
|
||||
local function handle_visibility()
|
||||
state.visible = not state.visible
|
||||
local updates = ui.visibility_updates()
|
||||
local status_updates = ui.status_updates("通过 visible / alpha 更新节点显示状态。")
|
||||
for _, update in ipairs(status_updates) do
|
||||
table.insert(updates, update)
|
||||
end
|
||||
return { render = { updates = updates } }
|
||||
end
|
||||
|
||||
---@return RuntimeDiff
|
||||
local function handle_dialog()
|
||||
if state.dialog_open then
|
||||
return status_only("Dialog 已经打开;点击关闭按钮移除它。")
|
||||
end
|
||||
state.dialog_open = true
|
||||
return {
|
||||
ui = {
|
||||
creates = ui.dialog_nodes(),
|
||||
updates = ui.status_updates("创建 widgets.dialog:Lua 组合普通 RuntimeNode。")
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
---@return RuntimeDiff
|
||||
local function handle_close_dialog()
|
||||
state.dialog_open = false
|
||||
return {
|
||||
ui = {
|
||||
removes = ui.dialog_removes(),
|
||||
updates = ui.status_updates("通过 GameDiff.removes 移除 Dialog 节点。")
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
---@return RuntimeDiff
|
||||
local function handle_temp()
|
||||
if state.temp_node_visible then
|
||||
state.temp_node_visible = false
|
||||
return {
|
||||
render = { removes = { { id = "temp_node" }, { id = "temp_node_text" } } },
|
||||
ui = { updates = ui.status_updates("通过 GameDiff.removes 移除临时节点。") }
|
||||
}
|
||||
end
|
||||
|
||||
state.temp_node_visible = true
|
||||
return {
|
||||
render = { creates = ui.temp_nodes() },
|
||||
ui = { updates = ui.status_updates("创建临时节点,并用 delay + remove_node 自动删除。") },
|
||||
commands = {
|
||||
commands.sequence({
|
||||
commands.delay(1.2, { id = "temp_delay", group = "temp" }),
|
||||
commands.remove_node("temp_node", { group = "temp" }),
|
||||
commands.remove_node("temp_node_text", { group = "temp", onComplete = "temp_removed" })
|
||||
}, { group = "temp" })
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
---@return RuntimeDiff
|
||||
local function handle_sound()
|
||||
return {
|
||||
ui = { updates = ui.status_updates("播放 manifest 资源 click:commands.play_sound('click')。") },
|
||||
commands = { commands.play_sound("click", { volume = 0.8, onComplete = "sound_done" }) }
|
||||
}
|
||||
end
|
||||
|
||||
---@return RuntimeDiff
|
||||
local function handle_bgm()
|
||||
if state.bgm_state == "stopped" then
|
||||
state.bgm_state = "playing"
|
||||
return {
|
||||
ui = { updates = ui.status_updates("启动 BGM channel=demo:play_bgm,可继续点击切换暂停/恢复/停止。") },
|
||||
commands = { commands.play_bgm("click", { channel = "demo", loop = true, volume = 0.25 }) }
|
||||
}
|
||||
end
|
||||
|
||||
if state.bgm_state == "playing" then
|
||||
state.bgm_state = "paused"
|
||||
return {
|
||||
ui = { updates = ui.status_updates("暂停 BGM:pause_bgm('demo')。") },
|
||||
commands = { commands.pause_bgm("demo") }
|
||||
}
|
||||
end
|
||||
|
||||
if state.bgm_state == "paused" then
|
||||
state.bgm_state = "resumed"
|
||||
return {
|
||||
ui = { updates = ui.status_updates("恢复 BGM:resume_bgm('demo')。") },
|
||||
commands = { commands.resume_bgm("demo") }
|
||||
}
|
||||
end
|
||||
|
||||
state.bgm_state = "stopped"
|
||||
return {
|
||||
ui = { updates = ui.status_updates("停止 BGM:stop_bgm('demo')。") },
|
||||
commands = { commands.stop_bgm("demo") }
|
||||
}
|
||||
end
|
||||
|
||||
---@return RuntimeDiff
|
||||
local function handle_text_change()
|
||||
state.text_variant = state.text_variant == "plain" and "styled" or "plain"
|
||||
local updates = ui.text_updates()
|
||||
local status_updates = ui.status_updates("Text 当前是纯文本协议;富文本 richText/spans 尚未支持。")
|
||||
for _, update in ipairs(status_updates) do
|
||||
table.insert(updates, update)
|
||||
end
|
||||
return { ui = { updates = updates } }
|
||||
end
|
||||
|
||||
---@return RuntimeDiff
|
||||
local function handle_text_style()
|
||||
state.text_variant = state.text_variant == "plain" and "styled" or "plain"
|
||||
local updates = ui.text_updates()
|
||||
local status_updates = ui.status_updates("已切换 text 的 color/fontSize;不是富文本。")
|
||||
for _, update in ipairs(status_updates) do
|
||||
table.insert(updates, update)
|
||||
end
|
||||
return { ui = { updates = updates } }
|
||||
end
|
||||
|
||||
---@return RuntimeDiff
|
||||
local function handle_button_primary()
|
||||
return {
|
||||
ui = { updates = ui.status_updates("按钮点击:RuntimeEvent.tap -> Lua handler。") },
|
||||
commands = { commands.play_sound("click", { volume = 0.55 }) }
|
||||
}
|
||||
end
|
||||
|
||||
---@return RuntimeDiff
|
||||
local function handle_button_toggle()
|
||||
state.button_active = not state.button_active
|
||||
local updates = ui.button_updates()
|
||||
local status_updates = ui.status_updates(state.button_active and "按钮恢复可点击状态。" or "按钮切换为置灰状态。")
|
||||
for _, update in ipairs(status_updates) do
|
||||
table.insert(updates, update)
|
||||
end
|
||||
return { ui = { updates = updates } }
|
||||
end
|
||||
|
||||
---@return RuntimeDiff
|
||||
local function handle_button_image_tap()
|
||||
return {
|
||||
ui = { updates = ui.status_updates("图片按钮点击:按下时显示 pressedAsset,松开回到 asset。") },
|
||||
commands = { commands.play_sound("click", { volume = 0.45 }) }
|
||||
}
|
||||
end
|
||||
|
||||
---@return RuntimeDiff
|
||||
local function handle_button_image_toggle()
|
||||
state.button_image_enabled = not state.button_image_enabled
|
||||
local updates = ui.button_image_updates()
|
||||
local status_updates = ui.status_updates(state.button_image_enabled and "图片按钮恢复 interactive=true。" or "图片按钮设为 interactive=false,显示 disabledAsset。")
|
||||
for _, update in ipairs(status_updates) do
|
||||
table.insert(updates, update)
|
||||
end
|
||||
return { ui = { updates = updates } }
|
||||
end
|
||||
|
||||
---@return RuntimeDiff
|
||||
local function handle_sprite_anim()
|
||||
return {
|
||||
ui = { updates = ui.status_updates("精灵节点执行 move_to + rotate_to。") },
|
||||
commands = {
|
||||
commands.parallel({
|
||||
commands.move_to("sprite_sprite_demo", 154, 54, { duration = 0.35, group = "sprite_demo" }),
|
||||
commands.rotate_to("sprite_sprite_demo", 6.28, { duration = 0.35, group = "sprite_demo" })
|
||||
}, { group = "sprite_demo", onComplete = "sprite_anim_done" })
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
---@return RuntimeDiff
|
||||
local function handle_sprite_style()
|
||||
state.sprite_variant = state.sprite_variant == "image" and "sprite" or "image"
|
||||
local updates = ui.sprite_updates()
|
||||
local status_updates = ui.status_updates("切换 sprite 示例样式:" .. state.sprite_variant)
|
||||
for _, update in ipairs(status_updates) do
|
||||
table.insert(updates, update)
|
||||
end
|
||||
return { ui = { updates = updates } }
|
||||
end
|
||||
|
||||
---@param value string
|
||||
---@return RuntimeDiff
|
||||
local function handle_radio(value)
|
||||
state.radio_selected = value
|
||||
local updates = ui.radio_updates()
|
||||
local status_updates = ui.status_updates("RadioGroup 选择:" .. value)
|
||||
for _, update in ipairs(status_updates) do
|
||||
table.insert(updates, update)
|
||||
end
|
||||
return { ui = { updates = updates } }
|
||||
end
|
||||
|
||||
---@param index integer
|
||||
---@return RuntimeDiff
|
||||
local function handle_list_pick(index)
|
||||
local items = { "Lua", "Runtime", "Flame", "Diff", "Command" }
|
||||
if index < 1 then
|
||||
index = #items
|
||||
elseif index > #items then
|
||||
index = 1
|
||||
end
|
||||
state.list_selected = items[index]
|
||||
if state.list_axis == "horizontal" then
|
||||
state.list_scroll_x = math.min(math.max((index - 2) * 126, 0), 330)
|
||||
state.list_scroll_y = 0
|
||||
else
|
||||
state.list_scroll_x = 0
|
||||
state.list_scroll_y = math.min(math.max((index - 2) * 28, 0), 78)
|
||||
end
|
||||
local updates = ui.list_updates()
|
||||
local status_updates = ui.status_updates("ListView 选中:" .. state.list_selected .. ",双轴滚动已更新。")
|
||||
for _, update in ipairs(status_updates) do
|
||||
table.insert(updates, update)
|
||||
end
|
||||
return { ui = { updates = updates } }
|
||||
end
|
||||
|
||||
---@return RuntimeDiff
|
||||
local function handle_list_horizontal()
|
||||
state.list_axis = state.list_axis == "horizontal" and "vertical" or "horizontal"
|
||||
state.list_scroll_x = 0
|
||||
state.list_scroll_y = 0
|
||||
local updates = ui.list_updates()
|
||||
local status_updates = ui.status_updates("ListView 排列方向:" .. state.list_axis)
|
||||
for _, update in ipairs(status_updates) do
|
||||
table.insert(updates, update)
|
||||
end
|
||||
return { ui = { updates = updates } }
|
||||
end
|
||||
|
||||
---@return RuntimeDiff
|
||||
local function handle_list_reset()
|
||||
state.list_scroll_x = 0
|
||||
state.list_scroll_y = 0
|
||||
local updates = ui.list_updates()
|
||||
local status_updates = ui.status_updates("ListView 滚动已重置;滚轮/拖拽会触发 onScroll。")
|
||||
for _, update in ipairs(status_updates) do
|
||||
table.insert(updates, update)
|
||||
end
|
||||
return { ui = { updates = updates } }
|
||||
end
|
||||
|
||||
---@param event RuntimeEvent
|
||||
---@return RuntimeDiff
|
||||
local function handle_list_scrolled(event)
|
||||
if event.data ~= nil then
|
||||
state.list_scroll_x = event.data.scrollX or state.list_scroll_x
|
||||
state.list_scroll_y = event.data.scrollY or state.list_scroll_y
|
||||
end
|
||||
return {
|
||||
ui = { updates = ui.status_updates("onScroll 回调:" .. tostring(state.list_scroll_x) .. ", " .. tostring(state.list_scroll_y)) }
|
||||
}
|
||||
end
|
||||
|
||||
---@param step integer
|
||||
---@return RuntimeDiff
|
||||
---@param preset string
|
||||
---@return RuntimeDiff
|
||||
local function handle_particle(preset)
|
||||
state.particle_seed = (state.particle_seed or 0) + 1
|
||||
local count = 42 + (state.particle_seed % 5)
|
||||
local updates = {
|
||||
runtime_ui.update("particle_burst", {
|
||||
preset = preset == "confetti" and "confetti" or "burst",
|
||||
count = count,
|
||||
duration = preset == "confetti" and 1.2 or 0.85,
|
||||
color = preset == "confetti" and "#ffff4d6d" or "#ffffcc33",
|
||||
colorTo = preset == "confetti" and "#fffacc15" or "#00ffcc33",
|
||||
gravityY = preset == "confetti" and 240 or 90,
|
||||
spread = preset == "confetti" and 90 or 360,
|
||||
visible = preset ~= "snow"
|
||||
}),
|
||||
runtime_ui.update("particle_trail", {
|
||||
visible = preset == "burst"
|
||||
}),
|
||||
runtime_ui.update("particle_snow", {
|
||||
count = 56 + (state.particle_seed % 7),
|
||||
visible = preset == "snow"
|
||||
})
|
||||
}
|
||||
local status_updates = ui.status_updates("Particle 预设:" .. preset)
|
||||
for _, update in ipairs(status_updates) do
|
||||
table.insert(updates, update)
|
||||
end
|
||||
return { ui = { updates = updates } }
|
||||
end
|
||||
|
||||
local function handle_list(step)
|
||||
local items = { "Lua", "Runtime", "Flame", "Diff", "Command" }
|
||||
local index = 1
|
||||
for i, value in ipairs(items) do
|
||||
if value == state.list_selected then
|
||||
index = i
|
||||
break
|
||||
end
|
||||
end
|
||||
return handle_list_pick(index + step)
|
||||
end
|
||||
|
||||
---@param mode string
|
||||
---@return RuntimeDiff
|
||||
local function handle_layout(mode)
|
||||
state.layout_mode = mode
|
||||
local updates = ui.layout_updates(mode)
|
||||
local status_updates = ui.status_updates("布局示例:" .. mode)
|
||||
for _, update in ipairs(status_updates) do
|
||||
table.insert(updates, update)
|
||||
end
|
||||
return { ui = { updates = updates } }
|
||||
end
|
||||
|
||||
---@return RuntimeDiff
|
||||
local function handle_resource()
|
||||
if state.resource_state == "ready" then
|
||||
state.resource_state = "evicted"
|
||||
return {
|
||||
ui = { updates = ui.status_updates("释放资源组 media:evict_resources。再次点击会 preload。") },
|
||||
commands = { commands.evict_group("media") }
|
||||
}
|
||||
end
|
||||
|
||||
state.resource_state = "ready"
|
||||
return {
|
||||
ui = { updates = ui.status_updates("预载资源组 media:preload_resources。") },
|
||||
commands = { commands.preload_group("media", { failOnError = true }) }
|
||||
}
|
||||
end
|
||||
|
||||
---@return RuntimeDiff
|
||||
---@return RuntimeDiff
|
||||
local function handle_i18n_toggle()
|
||||
local locale = i18n.toggle_locale()
|
||||
return {
|
||||
ui = { updates = ui.locale_updates() },
|
||||
commands = { commands.toast(i18n.t("i18n_switched") .. locale) }
|
||||
}
|
||||
end
|
||||
|
||||
---@return RuntimeDiff
|
||||
local function handle_i18n_refresh()
|
||||
return {
|
||||
ui = { updates = ui.locale_updates() }
|
||||
}
|
||||
end
|
||||
|
||||
---@param mode string
|
||||
---@return RuntimeDiff
|
||||
local function handle_responsive(mode)
|
||||
state.responsive_mode = mode
|
||||
local label = i18n.t("responsive_desktop")
|
||||
if mode == "phone" then
|
||||
label = i18n.t("responsive_phone")
|
||||
elseif mode == "tablet" then
|
||||
label = i18n.t("responsive_tablet")
|
||||
end
|
||||
local updates = ui.responsive_updates()
|
||||
local status_updates = ui.status_updates(label)
|
||||
for _, update in ipairs(status_updates) do
|
||||
table.insert(updates, update)
|
||||
end
|
||||
return { ui = { updates = updates } }
|
||||
end
|
||||
|
||||
---@param event RuntimeEvent
|
||||
---@return RuntimeDiff
|
||||
local function handle_resize(event)
|
||||
if event.data ~= nil then
|
||||
apply_context(event.data)
|
||||
end
|
||||
return { ui = { updates = ui.responsive_updates() } }
|
||||
end
|
||||
|
||||
---@return RuntimeDiff
|
||||
local function handle_toast()
|
||||
return {
|
||||
ui = { updates = ui.status_updates("发送 toast 命令:Runtime 会显示临时 overlay 并自动移除。") },
|
||||
commands = { commands.toast("Hello from Lua showcase") }
|
||||
}
|
||||
end
|
||||
|
||||
---@param target? string
|
||||
---@return RuntimeDiff
|
||||
local function handle_select_example(target)
|
||||
local id = string.sub(target or "example_nodes", 9)
|
||||
local selected = examples.find(id)
|
||||
state.selected_example = selected.id
|
||||
state.detail_tab = "code"
|
||||
return {
|
||||
ui = { updates = ui.example_updates(selected) }
|
||||
}
|
||||
end
|
||||
|
||||
---@param tab string
|
||||
---@return RuntimeDiff
|
||||
local function handle_detail_tab(tab)
|
||||
state.detail_tab = tab
|
||||
return { ui = { updates = ui.example_updates(examples.find(state.selected_example)) } }
|
||||
end
|
||||
|
||||
---@return RuntimeDiff
|
||||
local function handle_copy_detail()
|
||||
local selected = examples.find(state.selected_example)
|
||||
local text = selected.code
|
||||
local label = "代码"
|
||||
if state.detail_tab == "params" then
|
||||
text = i18n.example_params(selected)
|
||||
label = "参数说明"
|
||||
end
|
||||
return {
|
||||
ui = { updates = ui.status_updates("已复制" .. label .. "到剪贴板。") },
|
||||
commands = { commands.copy_text(text) }
|
||||
}
|
||||
end
|
||||
|
||||
function on_event(event)
|
||||
if event.handler == "select_example" then
|
||||
return handle_select_example(event.target)
|
||||
end
|
||||
if event.handler == "detail_tab_code" then
|
||||
return handle_detail_tab("code")
|
||||
end
|
||||
if event.handler == "detail_tab_params" then
|
||||
return handle_detail_tab("params")
|
||||
end
|
||||
if event.handler == "copy_detail" then
|
||||
return handle_copy_detail()
|
||||
end
|
||||
if event.handler == "demo_anim" then
|
||||
return handle_anim()
|
||||
end
|
||||
if event.handler == "demo_cancel" then
|
||||
return handle_cancel()
|
||||
end
|
||||
if event.handler == "demo_progress" then
|
||||
return handle_progress()
|
||||
end
|
||||
if event.handler == "demo_visibility" then
|
||||
return handle_visibility()
|
||||
end
|
||||
if event.handler == "demo_dialog" then
|
||||
return handle_dialog()
|
||||
end
|
||||
if event.handler == "close_dialog" then
|
||||
return handle_close_dialog()
|
||||
end
|
||||
if event.handler == "demo_temp" then
|
||||
return handle_temp()
|
||||
end
|
||||
if event.handler == "demo_text_change" then
|
||||
return handle_text_change()
|
||||
end
|
||||
if event.handler == "demo_text_style" then
|
||||
return handle_text_style()
|
||||
end
|
||||
if event.handler == "demo_button_primary" then
|
||||
return handle_button_primary()
|
||||
end
|
||||
if event.handler == "demo_button_toggle" then
|
||||
return handle_button_toggle()
|
||||
end
|
||||
if event.handler == "demo_button_image_tap" then
|
||||
return handle_button_image_tap()
|
||||
end
|
||||
if event.handler == "demo_button_image_toggle" then
|
||||
return handle_button_image_toggle()
|
||||
end
|
||||
if event.handler == "demo_sprite_anim" then
|
||||
return handle_sprite_anim()
|
||||
end
|
||||
if event.handler == "demo_sprite_style" then
|
||||
return handle_sprite_style()
|
||||
end
|
||||
if event.handler == "demo_radio_audio" then
|
||||
return handle_radio("audio")
|
||||
end
|
||||
if event.handler == "demo_radio_spine" then
|
||||
return handle_radio("spine")
|
||||
end
|
||||
if event.handler == "demo_radio_lua" then
|
||||
return handle_radio("Lua")
|
||||
end
|
||||
if event.handler == "demo_list_prev" then
|
||||
return handle_list(-1)
|
||||
end
|
||||
if event.handler == "demo_list_next" then
|
||||
return handle_list(1)
|
||||
end
|
||||
if event.handler == "demo_list_horizontal" then
|
||||
return handle_list_horizontal()
|
||||
end
|
||||
if event.handler == "demo_list_reset" then
|
||||
return handle_list_reset()
|
||||
end
|
||||
if event.handler == "demo_list_scrolled" then
|
||||
return handle_list_scrolled(event)
|
||||
end
|
||||
if event.handler == "demo_list_pick_1" then
|
||||
return handle_list_pick(1)
|
||||
end
|
||||
if event.handler == "demo_list_pick_2" then
|
||||
return handle_list_pick(2)
|
||||
end
|
||||
if event.handler == "demo_list_pick_3" then
|
||||
return handle_list_pick(3)
|
||||
end
|
||||
if event.handler == "demo_list_pick_4" then
|
||||
return handle_list_pick(4)
|
||||
end
|
||||
if event.handler == "demo_list_pick_5" then
|
||||
return handle_list_pick(5)
|
||||
end
|
||||
if event.handler == "demo_particle_burst" then
|
||||
return handle_particle("burst")
|
||||
end
|
||||
if event.handler == "demo_particle_confetti" then
|
||||
return handle_particle("confetti")
|
||||
end
|
||||
if event.handler == "demo_particle_snow" then
|
||||
return handle_particle("snow")
|
||||
end
|
||||
if event.handler == "demo_layout_row" then
|
||||
return handle_layout("row")
|
||||
end
|
||||
if event.handler == "demo_layout_column" then
|
||||
return handle_layout("column")
|
||||
end
|
||||
if event.handler == "demo_layout_box" then
|
||||
return handle_layout("box")
|
||||
end
|
||||
if event.handler == "demo_sound" then
|
||||
return handle_sound()
|
||||
end
|
||||
if event.handler == "demo_bgm" then
|
||||
return handle_bgm()
|
||||
end
|
||||
if event.handler == "demo_resource" then
|
||||
return handle_resource()
|
||||
end
|
||||
if event.handler == "demo_i18n_toggle" then
|
||||
return handle_i18n_toggle()
|
||||
end
|
||||
if event.handler == "demo_i18n_refresh" then
|
||||
return handle_i18n_refresh()
|
||||
end
|
||||
if event.handler == "demo_responsive_phone" then
|
||||
return handle_responsive("phone")
|
||||
end
|
||||
if event.handler == "demo_responsive_tablet" then
|
||||
return handle_responsive("tablet")
|
||||
end
|
||||
if event.handler == "demo_responsive_desktop" then
|
||||
return handle_responsive("desktop")
|
||||
end
|
||||
if event.handler == "demo_toast" then
|
||||
return handle_toast()
|
||||
end
|
||||
if event.type == "resize" then
|
||||
return handle_resize(event)
|
||||
end
|
||||
if event.handler == "demo_anim_done" then
|
||||
return status_only("动画完成事件:RuntimeCommand.onComplete -> RuntimeEvent -> Lua。")
|
||||
end
|
||||
if event.handler == "temp_removed" then
|
||||
state.temp_node_visible = false
|
||||
return status_only("临时节点已由 remove_node 命令删除。")
|
||||
end
|
||||
if event.handler == "sound_done" then
|
||||
return status_only("音效播放完成事件已回到 Lua。")
|
||||
end
|
||||
if event.handler == "sprite_anim_done" then
|
||||
return status_only("精灵动画完成。")
|
||||
end
|
||||
|
||||
return {}
|
||||
end
|
||||
606
example/assets/games/showcase/scripts/runtime_defs.lua
Normal file
@@ -0,0 +1,606 @@
|
||||
---@meta
|
||||
--- COMMON RUNTIME TYPES SECTION.
|
||||
--- Source of truth: tool/lua_runtime_defs_common.lua
|
||||
--- After editing this common section, run:
|
||||
--- dart run tool/generate_lua_runtime_defs.dart
|
||||
|
||||
|
||||
---@alias RuntimeNodeType
|
||||
---| 'panel'
|
||||
---| 'button'
|
||||
---| 'text'
|
||||
---| 'circle'
|
||||
---| 'rect'
|
||||
---| 'line'
|
||||
---| 'progress'
|
||||
---| 'listView'
|
||||
---| 'sprite'
|
||||
---| 'image'
|
||||
---| 'spine'
|
||||
---| 'particle'
|
||||
|
||||
---@alias RuntimeAnchor
|
||||
---| 'center'
|
||||
---| 'topLeft'
|
||||
---| 'topRight'
|
||||
---| 'bottomLeft'
|
||||
---| 'bottomRight'
|
||||
|
||||
---@alias RuntimeTextAlign
|
||||
---| 'left'
|
||||
---| 'center'
|
||||
---| 'right'
|
||||
|
||||
---@alias RuntimeParticlePreset
|
||||
---| 'burst'
|
||||
---| 'trail'
|
||||
---| 'snow'
|
||||
---| 'confetti'
|
||||
|
||||
---@alias RuntimeCommandType
|
||||
---| 'move_path'
|
||||
---| 'move_to'
|
||||
---| 'fade_to'
|
||||
---| 'scale_to'
|
||||
---| 'rotate_to'
|
||||
---| 'remove_node'
|
||||
---| 'sequence'
|
||||
---| 'parallel'
|
||||
---| 'delay'
|
||||
---| 'toast'
|
||||
---| 'play_sound'
|
||||
---| 'play_bgm'
|
||||
---| 'pause_bgm'
|
||||
---| 'resume_bgm'
|
||||
---| 'stop_bgm'
|
||||
---| 'preload_resources'
|
||||
---| 'evict_resources'
|
||||
---| 'cancel_commands'
|
||||
---| 'play_spine_animation'
|
||||
---| 'copy_text'
|
||||
|
||||
---@alias RuntimeEventType
|
||||
---| 'tap'
|
||||
---| 'animation_done'
|
||||
---| 'resize'
|
||||
---| 'scroll'
|
||||
|
||||
---@alias RuntimeScaleMode
|
||||
---| 'fit'
|
||||
---| 'fill'
|
||||
---| 'stretch'
|
||||
---| 'none'
|
||||
|
||||
---@alias RuntimeLayoutAlign
|
||||
---| 'start'
|
||||
---| 'center'
|
||||
---| 'end'
|
||||
|
||||
---@alias RuntimeButtonVariant
|
||||
---| 'primary'
|
||||
---| 'secondary'
|
||||
---| 'ghost'
|
||||
|
||||
---@class (exact) RuntimeNode
|
||||
---@field id string
|
||||
---@field type RuntimeNodeType
|
||||
---@field parent? string
|
||||
---@field asset? string Normal image/sprite/spine asset key. For button nodes this is the normal-state image.
|
||||
---@field pressedAsset? string Button pressed-state image asset key.
|
||||
---@field disabledAsset? string Button disabled-state image asset key.
|
||||
---@field animation? string
|
||||
---@field skin? string
|
||||
---@field loop? boolean
|
||||
---@field text? string
|
||||
---@field x? number
|
||||
---@field y? number
|
||||
---@field width? number
|
||||
---@field height? number
|
||||
---@field paddingLeft? number
|
||||
---@field paddingTop? number
|
||||
---@field paddingRight? number
|
||||
---@field paddingBottom? number
|
||||
---@field anchor? RuntimeAnchor
|
||||
---@field layer? integer
|
||||
---@field visible? boolean
|
||||
---@field alpha? number
|
||||
---@field scale? number
|
||||
---@field rotation? number
|
||||
---@field color? string
|
||||
---@field fontSize? number
|
||||
---@field textAlign? RuntimeTextAlign
|
||||
---@field radius? number
|
||||
---@field strokeWidth? number
|
||||
---@field value? number
|
||||
---@field scrollX? number
|
||||
---@field scrollY? number
|
||||
---@field contentWidth? number
|
||||
---@field contentHeight? number
|
||||
---@field virtualized? boolean
|
||||
---@field cacheExtent? number
|
||||
---@field inertia? boolean
|
||||
---@field scrollbarThumbColor? string
|
||||
---@field scrollbarTrackColor? string
|
||||
---@field scrollbarThickness? number
|
||||
---@field scrollbarVisible? boolean
|
||||
---@field interactive? boolean
|
||||
---@field onTap? string
|
||||
---@field onScroll? string
|
||||
---@field preset? RuntimeParticlePreset
|
||||
---@field count? integer
|
||||
---@field duration? number
|
||||
---@field speedMin? number
|
||||
---@field speedMax? number
|
||||
---@field gravityX? number
|
||||
---@field gravityY? number
|
||||
---@field spread? number
|
||||
---@field colorTo? string
|
||||
---@field radiusTo? number
|
||||
---@field autoRemove? boolean
|
||||
---@field fadeOut? boolean
|
||||
|
||||
---@class (exact) RuntimeNodeProps
|
||||
---@field type? RuntimeNodeType
|
||||
---@field parent? string
|
||||
---@field asset? string Normal image/sprite/spine asset key. For button nodes this is the normal-state image.
|
||||
---@field pressedAsset? string Button pressed-state image asset key.
|
||||
---@field disabledAsset? string Button disabled-state image asset key.
|
||||
---@field animation? string
|
||||
---@field skin? string
|
||||
---@field loop? boolean
|
||||
---@field text? string
|
||||
---@field x? number
|
||||
---@field y? number
|
||||
---@field width? number
|
||||
---@field height? number
|
||||
---@field paddingLeft? number
|
||||
---@field paddingTop? number
|
||||
---@field paddingRight? number
|
||||
---@field paddingBottom? number
|
||||
---@field anchor? RuntimeAnchor
|
||||
---@field layer? integer
|
||||
---@field visible? boolean
|
||||
---@field alpha? number
|
||||
---@field scale? number
|
||||
---@field rotation? number
|
||||
---@field color? string
|
||||
---@field fontSize? number
|
||||
---@field textAlign? RuntimeTextAlign
|
||||
---@field radius? number
|
||||
---@field strokeWidth? number
|
||||
---@field value? number
|
||||
---@field scrollX? number
|
||||
---@field scrollY? number
|
||||
---@field contentWidth? number
|
||||
---@field contentHeight? number
|
||||
---@field virtualized? boolean
|
||||
---@field cacheExtent? number
|
||||
---@field inertia? boolean
|
||||
---@field scrollbarThumbColor? string
|
||||
---@field scrollbarTrackColor? string
|
||||
---@field scrollbarThickness? number
|
||||
---@field scrollbarVisible? boolean
|
||||
---@field interactive? boolean
|
||||
---@field onTap? string
|
||||
---@field onScroll? string
|
||||
---@field preset? RuntimeParticlePreset
|
||||
---@field count? integer
|
||||
---@field duration? number
|
||||
---@field speedMin? number
|
||||
---@field speedMax? number
|
||||
---@field gravityX? number
|
||||
---@field gravityY? number
|
||||
---@field spread? number
|
||||
---@field colorTo? string
|
||||
---@field radiusTo? number
|
||||
---@field autoRemove? boolean
|
||||
---@field fadeOut? boolean
|
||||
|
||||
---Helper-only fields accepted by runtime_ui/runtime_widgets. They are normalized
|
||||
---before the node/update crosses the Dart Runtime protocol boundary.
|
||||
---@class RuntimeNodeInit: RuntimeNodeProps
|
||||
---@field w? number Alias for width.
|
||||
---@field h? number Alias for height.
|
||||
---@field size? number Alias for both width and height.
|
||||
---@field handler? string Alias for onTap.
|
||||
---@field onClick? string Alias for onTap.
|
||||
|
||||
---@class (exact) RuntimeNodeUpdate
|
||||
---@field id string
|
||||
---@field props RuntimeNodeProps
|
||||
|
||||
---@class (exact) RuntimeNodeRemove
|
||||
---@field id string
|
||||
|
||||
---@class (exact) RuntimeDiffSection
|
||||
---@field creates? RuntimeNode[]
|
||||
---@field updates? RuntimeNodeUpdate[]
|
||||
---@field removes? (string|RuntimeNodeRemove)[]
|
||||
|
||||
---@class (exact) RuntimeDiff
|
||||
---@field render? RuntimeDiffSection
|
||||
---@field ui? RuntimeDiffSection
|
||||
---@field commands? RuntimeCommand[]
|
||||
|
||||
---@class (exact) RuntimeEvent
|
||||
---@field type RuntimeEventType|string
|
||||
---@field target? string
|
||||
---@field handler? string
|
||||
---@field x? number
|
||||
---@field y? number
|
||||
---@field data? table
|
||||
|
||||
---@class (exact) RuntimeCommand
|
||||
---@field type RuntimeCommandType
|
||||
---@field target? string
|
||||
---@field scope? string
|
||||
---@field id? string
|
||||
---@field group? string
|
||||
---@field commandGroup? string
|
||||
---@field onComplete? string
|
||||
---@field duration? number
|
||||
---@field commands? RuntimeCommand[]
|
||||
---@field path? RuntimePoint[]
|
||||
---@field x? number
|
||||
---@field y? number
|
||||
---@field alpha? number
|
||||
---@field scale? number
|
||||
---@field angle? number
|
||||
---@field text? string
|
||||
---@field message? string
|
||||
---@field asset? string
|
||||
---@field name? string
|
||||
---@field volume? number
|
||||
---@field channel? string
|
||||
---@field loop? boolean
|
||||
---@field failOnError? boolean
|
||||
---@field animation? string
|
||||
---@field track? integer
|
||||
---@field queue? boolean
|
||||
---@field delay? number
|
||||
|
||||
---@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
|
||||
---@field name? string
|
||||
|
||||
---@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 (exact) RuntimePoint
|
||||
---@field x number
|
||||
---@field y number
|
||||
|
||||
---@class (exact) RuntimeLocaleContext
|
||||
---@field requested string
|
||||
---@field resolved string
|
||||
---@field default string
|
||||
---@field supported string[]
|
||||
---@field languageCode string
|
||||
---@field scriptCode? string
|
||||
---@field countryCode? string
|
||||
|
||||
---@class (exact) RuntimeScreenContext
|
||||
---@field width number
|
||||
---@field height number
|
||||
|
||||
---@class (exact) RuntimeDesignContext
|
||||
---@field width number
|
||||
---@field height number
|
||||
|
||||
---@class (exact) RuntimeViewportContext
|
||||
---@field x number
|
||||
---@field y number
|
||||
---@field width number
|
||||
---@field height number
|
||||
---@field scaleX number
|
||||
---@field scaleY number
|
||||
---@field scaleMode RuntimeScaleMode|string
|
||||
|
||||
---@class (exact) RuntimeContext
|
||||
---@field screen RuntimeScreenContext
|
||||
---@field design RuntimeDesignContext
|
||||
---@field viewport RuntimeViewportContext
|
||||
---@field seed integer
|
||||
---@field runtimeApiVersion integer
|
||||
---@field gameId string
|
||||
---@field gameVersion string
|
||||
---@field locale? RuntimeLocaleContext
|
||||
|
||||
---@class RuntimeUi
|
||||
---@field style fun(base?: RuntimeNodeProps, opts?: RuntimeNodeProps): RuntimeNodeProps
|
||||
---@field with_parent fun(parent: string, opts?: RuntimeNodeProps): RuntimeNodeProps
|
||||
---@field node fun(node_type: RuntimeNodeType, id: string, opts?: RuntimeNodeInit): RuntimeNode
|
||||
---@field panel fun(id: string, x: number|RuntimeNodeInit, y?: number, width?: number, height?: number, opts?: RuntimeNodeInit): RuntimeNode
|
||||
---@field rect fun(id: string, x: number|RuntimeNodeInit, y?: number, width?: number, height?: number, opts?: RuntimeNodeInit): RuntimeNode
|
||||
---@field circle fun(id: string, x: number|RuntimeNodeInit, y?: number, size?: number, opts?: RuntimeNodeInit): RuntimeNode
|
||||
---@field line fun(id: string, x: number|RuntimeNodeInit, y?: number, width?: number, height?: number, opts?: RuntimeNodeInit): RuntimeNode
|
||||
---@field progress fun(id: string, x: number|RuntimeNodeInit, y?: number, width?: number, height?: number, value?: number, opts?: RuntimeNodeInit): RuntimeNode
|
||||
---@field particle fun(id: string, x: number|RuntimeNodeInit, y?: number, width?: number, height?: number, opts?: RuntimeNodeInit): RuntimeNode
|
||||
---@field text fun(id: string, text: string|RuntimeNodeInit, x?: number, y?: number, width?: number, height?: number, opts?: RuntimeNodeInit): RuntimeNode
|
||||
---@field button fun(id: string, text: string|RuntimeNodeInit, x?: number, y?: number, width?: number, height?: number, handler?: string, opts?: RuntimeNodeInit): RuntimeNode
|
||||
---@field list_view fun(id: string, x: number|RuntimeNodeInit, y?: number, width?: number, height?: number, opts?: RuntimeNodeInit): RuntimeNode
|
||||
---@field image fun(id: string, asset: string|RuntimeNodeInit, x?: number, y?: number, width?: number, height?: number, opts?: RuntimeNodeInit): RuntimeNode
|
||||
---@field sprite fun(id: string, asset: string|RuntimeNodeInit, x?: number, y?: number, width?: number, height?: number, opts?: RuntimeNodeInit): RuntimeNode
|
||||
---@field spine fun(id: string, asset: string|RuntimeNodeInit, x?: number, y?: number, width?: number, height?: number, animation?: string, opts?: RuntimeNodeInit): RuntimeNode
|
||||
---@field update fun(id: string, props: RuntimeNodeInit): RuntimeNodeUpdate
|
||||
---@field text_update fun(id: string, text: string): RuntimeNodeUpdate
|
||||
---@field visible_update fun(id: string, visible: boolean): RuntimeNodeUpdate
|
||||
---@field alpha_update fun(id: string, alpha: number): RuntimeNodeUpdate
|
||||
---@field scale_update fun(id: string, scale: number): RuntimeNodeUpdate
|
||||
---@field position_update fun(id: string, x: number, y: number): RuntimeNodeUpdate
|
||||
---@field size_update fun(id: string, width: number, height: number): RuntimeNodeUpdate
|
||||
---@field transform_update fun(id: string, x: number, y: number, scale: number, rotation: number): RuntimeNodeUpdate
|
||||
---@field batch_update fun(ids: string[], props: RuntimeNodeInit): RuntimeNodeUpdate[]
|
||||
---@field append fun(nodes: RuntimeNode[], node: RuntimeNode): RuntimeNode[]
|
||||
---@field append_all fun(nodes: RuntimeNode[], extra_nodes: RuntimeNode[]): RuntimeNode[]
|
||||
|
||||
---@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 RuntimeLabeledProgressOpts: RuntimeNodeInit
|
||||
---@field labelHeight? number
|
||||
---@field labelStyle? RuntimeNodeProps
|
||||
|
||||
---@class RuntimePillOpts: RuntimeNodeInit
|
||||
---@field panelStyle? RuntimeNodeProps
|
||||
---@field textStyle? RuntimeNodeProps
|
||||
|
||||
---@class RuntimeTextButtonOpts: RuntimeNodeInit
|
||||
---@field variant? RuntimeButtonVariant
|
||||
|
||||
---@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
|
||||
---@field configure fun(tokens?: RuntimeWidgetTheme): RuntimeWidgets
|
||||
---@field label fun(id: string, text: string|RuntimeNodeInit, x?: number, y?: number, width?: number, height?: number, opts?: RuntimeNodeInit): RuntimeNode
|
||||
---@field section_title fun(id: string, text: string|RuntimeNodeInit, x?: number, y?: number, width?: number, height?: number, opts?: RuntimeNodeInit): RuntimeNode
|
||||
---@field pill fun(id: string, text: string|RuntimePillOpts, x?: number, y?: number, width?: number, height?: number, opts?: RuntimePillOpts): RuntimeNode[]
|
||||
---@field text_button fun(id: string, text: string|RuntimeTextButtonOpts, x?: number, y?: number, width?: number, height?: number, handler?: string, opts?: RuntimeTextButtonOpts): RuntimeNode
|
||||
---@field list_item fun(id: string, text: string|RuntimeListItemOpts, x?: number, y?: number, width?: number, height?: number, handler?: string, opts?: RuntimeListItemOpts): RuntimeNode
|
||||
---@field tabs fun(id: string, tabs: RuntimeTabItem[]|RuntimeTabsOpts, opts?: RuntimeTabsOpts): RuntimeNode[]
|
||||
---@field action_row fun(id: string, actions: RuntimeActionItem[]|RuntimeActionRowOpts, opts?: RuntimeActionRowOpts): RuntimeNode[]
|
||||
---@field panel_header fun(id: string, opts: RuntimePanelHeaderOpts): RuntimeNode[]
|
||||
---@field overlay fun(id: string, width: number|RuntimeNodeInit, height?: number, opts?: RuntimeNodeInit): RuntimeNode
|
||||
---@field card fun(id: string, x: number|RuntimeNodeInit, y?: number, width?: number, height?: number, opts?: RuntimeNodeInit): RuntimeNode
|
||||
---@field progress_bar fun(id: string, x: number|RuntimeNodeInit, y?: number, width?: number, height?: number, value?: number, opts?: RuntimeNodeInit): RuntimeNode
|
||||
---@field labeled_progress fun(id: string, label: string, x: number, y: number, width: number, height: number, value: number, opts?: RuntimeLabeledProgressOpts): RuntimeNode[]
|
||||
---@field button_row fun(parent: string, id: string, buttons: RuntimeDialogButton[], x: number, y: number, width: number, height: number, gap?: number, opts?: RuntimeNodeProps): RuntimeNode[]
|
||||
---@field dialog fun(id: string, title: string, message: string, x: number, y: number, width: number, height: number, opts?: RuntimeDialogOpts): RuntimeNode[]
|
||||
|
||||
---@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
|
||||
---@field item fun(node: RuntimeNode, opts?: RuntimeLayoutItemOpts): RuntimeLayoutItem
|
||||
---@field local_position fun(origin: RuntimePoint, position: RuntimePoint): RuntimePoint
|
||||
---@field row fun(parent?: string, items: (RuntimeNode|RuntimeLayoutItem)[], opts?: RuntimeLinearLayoutOpts): RuntimeNode[]
|
||||
---@field column fun(parent?: string, items: (RuntimeNode|RuntimeLayoutItem)[], opts?: RuntimeLinearLayoutOpts): RuntimeNode[]
|
||||
---@field stack fun(parent?: string, items: (RuntimeNode|RuntimeLayoutItem)[], opts?: RuntimeLinearLayoutOpts): RuntimeNode[]
|
||||
---@field box fun(parent?: string, items: (RuntimeNode|RuntimeLayoutItem)[], opts?: RuntimeBoxLayoutOpts): RuntimeNode[]
|
||||
|
||||
---@class RuntimeCommands
|
||||
---@field toast fun(text: string, opts?: RuntimeCommandOpts): RuntimeCommand
|
||||
---@field copy_text fun(text: string, opts?: RuntimeCommandOpts): RuntimeCommand
|
||||
---@field delay fun(duration: number, opts?: RuntimeCommandOpts): RuntimeCommand
|
||||
---@field sequence fun(items: RuntimeCommand[], opts?: RuntimeCommandOpts): RuntimeCommand
|
||||
---@field parallel fun(items: RuntimeCommand[], opts?: RuntimeCommandOpts): RuntimeCommand
|
||||
---@field move_path fun(target: string, path: RuntimePoint[], opts?: RuntimeCommandOpts): RuntimeCommand
|
||||
---@field move_to fun(target: string, x: number, y: number, opts?: RuntimeCommandOpts): RuntimeCommand
|
||||
---@field fade_to fun(target: string, alpha: number, opts?: RuntimeCommandOpts): RuntimeCommand
|
||||
---@field scale_to fun(target: string, scale: number, opts?: RuntimeCommandOpts): RuntimeCommand
|
||||
---@field rotate_to fun(target: string, angle: number, opts?: RuntimeCommandOpts): RuntimeCommand
|
||||
---@field remove_node fun(target: string, opts?: RuntimeCommandOpts): RuntimeCommand
|
||||
---@field play_spine_animation fun(target: string, animation: string, opts?: RuntimeSpineCommandOpts): RuntimeCommand
|
||||
---@field play_sound fun(asset: string, opts?: RuntimeAudioCommandOpts): RuntimeCommand
|
||||
---@field play_bgm fun(asset: string, opts?: RuntimeBgmCommandOpts): RuntimeCommand
|
||||
---@field pause_bgm fun(channel?: string, opts?: RuntimeCommandOpts): RuntimeCommand
|
||||
---@field resume_bgm fun(channel?: string, opts?: RuntimeCommandOpts): RuntimeCommand
|
||||
---@field stop_bgm fun(channel?: string, opts?: RuntimeCommandOpts): RuntimeCommand
|
||||
---@field preload_group fun(group: string, opts?: RuntimeResourceCommandOpts): RuntimeCommand
|
||||
---@field evict_group fun(group: string, opts?: RuntimeCommandOpts): RuntimeCommand
|
||||
---@field cancel_id fun(id: string): RuntimeCommand
|
||||
---@field cancel_group fun(group: string): RuntimeCommand
|
||||
---@field cancel_scope fun(scope: string): RuntimeCommand
|
||||
|
||||
---@class RuntimeImportApi
|
||||
---@field import fun(moduleName: string): table
|
||||
|
||||
---@type RuntimeImportApi
|
||||
runtime = runtime
|
||||
|
||||
---@class (exact) ShowcaseAction
|
||||
---@field text string
|
||||
---@field text_en? string
|
||||
---@field handler string
|
||||
|
||||
---@class (exact) ShowcaseExample
|
||||
---@field id string
|
||||
---@field group string
|
||||
---@field group_en? string
|
||||
---@field category string
|
||||
---@field category_en? string
|
||||
---@field menu string
|
||||
---@field menu_en? string
|
||||
---@field title string
|
||||
---@field title_en? string
|
||||
---@field summary string
|
||||
---@field summary_en? string
|
||||
---@field code string
|
||||
---@field params string
|
||||
---@field params_en? string
|
||||
---@field actions ShowcaseAction[]
|
||||
|
||||
---@class (exact) ShowcaseState
|
||||
---@field selected_example string
|
||||
---@field detail_tab string
|
||||
---@field progress number
|
||||
---@field visible boolean
|
||||
---@field dialog_open boolean
|
||||
---@field temp_node_visible boolean
|
||||
---@field bgm_state string
|
||||
---@field resource_state string
|
||||
---@field text_variant string
|
||||
---@field button_active boolean
|
||||
---@field sprite_variant string
|
||||
---@field layout_mode string
|
||||
---@field radio_selected string
|
||||
---@field list_selected string
|
||||
---@field list_axis string
|
||||
---@field list_scroll_x number
|
||||
---@field list_scroll_y number
|
||||
---@field locale string
|
||||
---@field screen_width number
|
||||
---@field screen_height number
|
||||
---@field viewport_width number
|
||||
---@field viewport_height number
|
||||
---@field viewport_scale number
|
||||
---@field responsive_mode string
|
||||
---@field status string
|
||||
32
example/assets/games/showcase/scripts/state.lua
Normal file
@@ -0,0 +1,32 @@
|
||||
---@class ShowcaseState
|
||||
local state = {
|
||||
selected_example = "nodes",
|
||||
detail_tab = "code",
|
||||
progress = 0.35,
|
||||
visible = true,
|
||||
dialog_open = false,
|
||||
temp_node_visible = false,
|
||||
bgm_state = "stopped",
|
||||
resource_state = "ready",
|
||||
text_variant = "plain",
|
||||
button_active = true,
|
||||
button_image_enabled = true,
|
||||
sprite_variant = "image",
|
||||
layout_mode = "row",
|
||||
radio_selected = "audio",
|
||||
list_selected = "Lua",
|
||||
list_axis = "vertical",
|
||||
list_scroll_x = 0,
|
||||
list_scroll_y = 0,
|
||||
locale = "zh-Hans",
|
||||
screen_width = 720,
|
||||
screen_height = 720,
|
||||
viewport_width = 720,
|
||||
viewport_height = 720,
|
||||
viewport_scale = 1,
|
||||
responsive_mode = "desktop",
|
||||
particle_seed = 0,
|
||||
status = "点击按钮查看 RuntimeEvent -> Lua -> Diff / Command 示例"
|
||||
}
|
||||
|
||||
return state
|
||||
44
example/assets/games/showcase/scripts/styles.lua
Normal file
@@ -0,0 +1,44 @@
|
||||
local theme = runtime.import("theme")
|
||||
|
||||
---@class ShowcaseStyles
|
||||
local styles = {}
|
||||
|
||||
styles.title = {
|
||||
color = theme.text,
|
||||
fontSize = 24,
|
||||
layer = 20
|
||||
}
|
||||
|
||||
styles.label = {
|
||||
color = theme.muted,
|
||||
fontSize = 14,
|
||||
layer = 20
|
||||
}
|
||||
|
||||
styles.value = {
|
||||
color = theme.text,
|
||||
fontSize = 16,
|
||||
layer = 20
|
||||
}
|
||||
|
||||
styles.button = {
|
||||
color = "#ff2563eb",
|
||||
radius = 10,
|
||||
fontSize = 14,
|
||||
layer = 30
|
||||
}
|
||||
|
||||
styles.small_button = {
|
||||
color = "#ff334155",
|
||||
radius = 8,
|
||||
fontSize = 12,
|
||||
layer = 30
|
||||
}
|
||||
|
||||
styles.card = {
|
||||
color = theme.card,
|
||||
radius = 14,
|
||||
layer = 10
|
||||
}
|
||||
|
||||
return styles
|
||||
18
example/assets/games/showcase/scripts/theme.lua
Normal file
@@ -0,0 +1,18 @@
|
||||
---@class ShowcaseTheme
|
||||
local theme = {
|
||||
screen_width = 720,
|
||||
screen_height = 720,
|
||||
background = "#ff0f172a",
|
||||
panel = "#ff111827",
|
||||
card = "#ff1f2937",
|
||||
border = "#ff334155",
|
||||
text = "#fff8fafc",
|
||||
muted = "#ff94a3b8",
|
||||
primary = "#ff38bdf8",
|
||||
success = "#ff22c55e",
|
||||
warning = "#fff59e0b",
|
||||
danger = "#ffef4444",
|
||||
purple = "#ffa855f7"
|
||||
}
|
||||
|
||||
return theme
|
||||
1300
example/assets/games/showcase/scripts/ui.lua
Normal file
27
example/assets/games/template/manifest.json
Normal file
@@ -0,0 +1,27 @@
|
||||
{
|
||||
"gameId": "template",
|
||||
"name": "Lua Runtime Template",
|
||||
"version": "0.1.0",
|
||||
"runtimeApiVersion": 1,
|
||||
"entry": "scripts/main.lua",
|
||||
"assetsBase": "assets",
|
||||
"defaultLocale": "zh-Hans",
|
||||
"supportedLocales": [
|
||||
"zh-Hans",
|
||||
"en"
|
||||
],
|
||||
"display": {
|
||||
"designWidth": 720,
|
||||
"designHeight": 720,
|
||||
"scaleMode": "fit"
|
||||
},
|
||||
"modules": {
|
||||
"runtime_ui": "runtime:runtime_ui.lua",
|
||||
"runtime_commands": "runtime:runtime_commands.lua",
|
||||
"runtime_widgets": "runtime:runtime_widgets.lua",
|
||||
"layout": "runtime:layout.lua",
|
||||
"state": "scripts/state.lua",
|
||||
"ui": "scripts/ui.lua"
|
||||
},
|
||||
"resources": {}
|
||||
}
|
||||
35
example/assets/games/template/scripts/main.lua
Normal file
@@ -0,0 +1,35 @@
|
||||
local state = runtime.import("state")
|
||||
local ui = runtime.import("ui")
|
||||
---@type RuntimeCommands
|
||||
local commands = runtime.import("runtime_commands")
|
||||
|
||||
function smoke_test(ctx)
|
||||
return ctx ~= nil
|
||||
and ctx.runtimeApiVersion ~= nil
|
||||
and ui.create_nodes ~= nil
|
||||
and commands.toast ~= nil
|
||||
end
|
||||
|
||||
function init(ctx)
|
||||
state.status = "Template ready: " .. tostring(ctx.gameId or "template")
|
||||
return {
|
||||
render = { creates = ui.create_nodes() },
|
||||
ui = {},
|
||||
commands = {}
|
||||
}
|
||||
end
|
||||
|
||||
function on_event(event)
|
||||
if event.handler == "template_start" then
|
||||
state.started = true
|
||||
state.clicks = state.clicks + 1
|
||||
state.status = "收到 tap 事件,Lua 已返回 Diff 和 toast command。"
|
||||
return {
|
||||
ui = { updates = ui.state_updates() },
|
||||
commands = {
|
||||
commands.toast("Template event handled in Lua", { duration = 1.2 })
|
||||
}
|
||||
}
|
||||
end
|
||||
return {}
|
||||
end
|
||||
557
example/assets/games/template/scripts/runtime_defs.lua
Normal file
@@ -0,0 +1,557 @@
|
||||
---@meta
|
||||
--- COMMON RUNTIME TYPES SECTION.
|
||||
--- Source of truth: tool/lua_runtime_defs_common.lua
|
||||
--- After editing this common section, run:
|
||||
--- dart run tool/generate_lua_runtime_defs.dart
|
||||
|
||||
|
||||
---@alias RuntimeNodeType
|
||||
---| 'panel'
|
||||
---| 'button'
|
||||
---| 'text'
|
||||
---| 'circle'
|
||||
---| 'rect'
|
||||
---| 'line'
|
||||
---| 'progress'
|
||||
---| 'listView'
|
||||
---| 'sprite'
|
||||
---| 'image'
|
||||
---| 'spine'
|
||||
---| 'particle'
|
||||
|
||||
---@alias RuntimeAnchor
|
||||
---| 'center'
|
||||
---| 'topLeft'
|
||||
---| 'topRight'
|
||||
---| 'bottomLeft'
|
||||
---| 'bottomRight'
|
||||
|
||||
---@alias RuntimeTextAlign
|
||||
---| 'left'
|
||||
---| 'center'
|
||||
---| 'right'
|
||||
|
||||
---@alias RuntimeParticlePreset
|
||||
---| 'burst'
|
||||
---| 'trail'
|
||||
---| 'snow'
|
||||
---| 'confetti'
|
||||
|
||||
---@alias RuntimeCommandType
|
||||
---| 'move_path'
|
||||
---| 'move_to'
|
||||
---| 'fade_to'
|
||||
---| 'scale_to'
|
||||
---| 'rotate_to'
|
||||
---| 'remove_node'
|
||||
---| 'sequence'
|
||||
---| 'parallel'
|
||||
---| 'delay'
|
||||
---| 'toast'
|
||||
---| 'play_sound'
|
||||
---| 'play_bgm'
|
||||
---| 'pause_bgm'
|
||||
---| 'resume_bgm'
|
||||
---| 'stop_bgm'
|
||||
---| 'preload_resources'
|
||||
---| 'evict_resources'
|
||||
---| 'cancel_commands'
|
||||
---| 'play_spine_animation'
|
||||
---| 'copy_text'
|
||||
|
||||
---@alias RuntimeEventType
|
||||
---| 'tap'
|
||||
---| 'animation_done'
|
||||
---| 'resize'
|
||||
---| 'scroll'
|
||||
|
||||
---@alias RuntimeScaleMode
|
||||
---| 'fit'
|
||||
---| 'fill'
|
||||
---| 'stretch'
|
||||
---| 'none'
|
||||
|
||||
---@alias RuntimeLayoutAlign
|
||||
---| 'start'
|
||||
---| 'center'
|
||||
---| 'end'
|
||||
|
||||
---@alias RuntimeButtonVariant
|
||||
---| 'primary'
|
||||
---| 'secondary'
|
||||
---| 'ghost'
|
||||
|
||||
---@class (exact) RuntimeNode
|
||||
---@field id string
|
||||
---@field type RuntimeNodeType
|
||||
---@field parent? string
|
||||
---@field asset? string Normal image/sprite/spine asset key. For button nodes this is the normal-state image.
|
||||
---@field pressedAsset? string Button pressed-state image asset key.
|
||||
---@field disabledAsset? string Button disabled-state image asset key.
|
||||
---@field animation? string
|
||||
---@field skin? string
|
||||
---@field loop? boolean
|
||||
---@field text? string
|
||||
---@field x? number
|
||||
---@field y? number
|
||||
---@field width? number
|
||||
---@field height? number
|
||||
---@field paddingLeft? number
|
||||
---@field paddingTop? number
|
||||
---@field paddingRight? number
|
||||
---@field paddingBottom? number
|
||||
---@field anchor? RuntimeAnchor
|
||||
---@field layer? integer
|
||||
---@field visible? boolean
|
||||
---@field alpha? number
|
||||
---@field scale? number
|
||||
---@field rotation? number
|
||||
---@field color? string
|
||||
---@field fontSize? number
|
||||
---@field textAlign? RuntimeTextAlign
|
||||
---@field radius? number
|
||||
---@field strokeWidth? number
|
||||
---@field value? number
|
||||
---@field scrollX? number
|
||||
---@field scrollY? number
|
||||
---@field contentWidth? number
|
||||
---@field contentHeight? number
|
||||
---@field virtualized? boolean
|
||||
---@field cacheExtent? number
|
||||
---@field inertia? boolean
|
||||
---@field scrollbarThumbColor? string
|
||||
---@field scrollbarTrackColor? string
|
||||
---@field scrollbarThickness? number
|
||||
---@field scrollbarVisible? boolean
|
||||
---@field interactive? boolean
|
||||
---@field onTap? string
|
||||
---@field onScroll? string
|
||||
---@field preset? RuntimeParticlePreset
|
||||
---@field count? integer
|
||||
---@field duration? number
|
||||
---@field speedMin? number
|
||||
---@field speedMax? number
|
||||
---@field gravityX? number
|
||||
---@field gravityY? number
|
||||
---@field spread? number
|
||||
---@field colorTo? string
|
||||
---@field radiusTo? number
|
||||
---@field autoRemove? boolean
|
||||
---@field fadeOut? boolean
|
||||
|
||||
---@class (exact) RuntimeNodeProps
|
||||
---@field type? RuntimeNodeType
|
||||
---@field parent? string
|
||||
---@field asset? string Normal image/sprite/spine asset key. For button nodes this is the normal-state image.
|
||||
---@field pressedAsset? string Button pressed-state image asset key.
|
||||
---@field disabledAsset? string Button disabled-state image asset key.
|
||||
---@field animation? string
|
||||
---@field skin? string
|
||||
---@field loop? boolean
|
||||
---@field text? string
|
||||
---@field x? number
|
||||
---@field y? number
|
||||
---@field width? number
|
||||
---@field height? number
|
||||
---@field paddingLeft? number
|
||||
---@field paddingTop? number
|
||||
---@field paddingRight? number
|
||||
---@field paddingBottom? number
|
||||
---@field anchor? RuntimeAnchor
|
||||
---@field layer? integer
|
||||
---@field visible? boolean
|
||||
---@field alpha? number
|
||||
---@field scale? number
|
||||
---@field rotation? number
|
||||
---@field color? string
|
||||
---@field fontSize? number
|
||||
---@field textAlign? RuntimeTextAlign
|
||||
---@field radius? number
|
||||
---@field strokeWidth? number
|
||||
---@field value? number
|
||||
---@field scrollX? number
|
||||
---@field scrollY? number
|
||||
---@field contentWidth? number
|
||||
---@field contentHeight? number
|
||||
---@field virtualized? boolean
|
||||
---@field cacheExtent? number
|
||||
---@field inertia? boolean
|
||||
---@field scrollbarThumbColor? string
|
||||
---@field scrollbarTrackColor? string
|
||||
---@field scrollbarThickness? number
|
||||
---@field scrollbarVisible? boolean
|
||||
---@field interactive? boolean
|
||||
---@field onTap? string
|
||||
---@field onScroll? string
|
||||
---@field preset? RuntimeParticlePreset
|
||||
---@field count? integer
|
||||
---@field duration? number
|
||||
---@field speedMin? number
|
||||
---@field speedMax? number
|
||||
---@field gravityX? number
|
||||
---@field gravityY? number
|
||||
---@field spread? number
|
||||
---@field colorTo? string
|
||||
---@field radiusTo? number
|
||||
---@field autoRemove? boolean
|
||||
---@field fadeOut? boolean
|
||||
|
||||
---Helper-only fields accepted by runtime_ui/runtime_widgets. They are normalized
|
||||
---before the node/update crosses the Dart Runtime protocol boundary.
|
||||
---@class RuntimeNodeInit: RuntimeNodeProps
|
||||
---@field w? number Alias for width.
|
||||
---@field h? number Alias for height.
|
||||
---@field size? number Alias for both width and height.
|
||||
---@field handler? string Alias for onTap.
|
||||
---@field onClick? string Alias for onTap.
|
||||
|
||||
---@class (exact) RuntimeNodeUpdate
|
||||
---@field id string
|
||||
---@field props RuntimeNodeProps
|
||||
|
||||
---@class (exact) RuntimeNodeRemove
|
||||
---@field id string
|
||||
|
||||
---@class (exact) RuntimeDiffSection
|
||||
---@field creates? RuntimeNode[]
|
||||
---@field updates? RuntimeNodeUpdate[]
|
||||
---@field removes? (string|RuntimeNodeRemove)[]
|
||||
|
||||
---@class (exact) RuntimeDiff
|
||||
---@field render? RuntimeDiffSection
|
||||
---@field ui? RuntimeDiffSection
|
||||
---@field commands? RuntimeCommand[]
|
||||
|
||||
---@class (exact) RuntimeEvent
|
||||
---@field type RuntimeEventType|string
|
||||
---@field target? string
|
||||
---@field handler? string
|
||||
---@field x? number
|
||||
---@field y? number
|
||||
---@field data? table
|
||||
|
||||
---@class (exact) RuntimeCommand
|
||||
---@field type RuntimeCommandType
|
||||
---@field target? string
|
||||
---@field scope? string
|
||||
---@field id? string
|
||||
---@field group? string
|
||||
---@field commandGroup? string
|
||||
---@field onComplete? string
|
||||
---@field duration? number
|
||||
---@field commands? RuntimeCommand[]
|
||||
---@field path? RuntimePoint[]
|
||||
---@field x? number
|
||||
---@field y? number
|
||||
---@field alpha? number
|
||||
---@field scale? number
|
||||
---@field angle? number
|
||||
---@field text? string
|
||||
---@field message? string
|
||||
---@field asset? string
|
||||
---@field name? string
|
||||
---@field volume? number
|
||||
---@field channel? string
|
||||
---@field loop? boolean
|
||||
---@field failOnError? boolean
|
||||
---@field animation? string
|
||||
---@field track? integer
|
||||
---@field queue? boolean
|
||||
---@field delay? number
|
||||
|
||||
---@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
|
||||
---@field name? string
|
||||
|
||||
---@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 (exact) RuntimePoint
|
||||
---@field x number
|
||||
---@field y number
|
||||
|
||||
---@class (exact) RuntimeLocaleContext
|
||||
---@field requested string
|
||||
---@field resolved string
|
||||
---@field default string
|
||||
---@field supported string[]
|
||||
---@field languageCode string
|
||||
---@field scriptCode? string
|
||||
---@field countryCode? string
|
||||
|
||||
---@class (exact) RuntimeScreenContext
|
||||
---@field width number
|
||||
---@field height number
|
||||
|
||||
---@class (exact) RuntimeDesignContext
|
||||
---@field width number
|
||||
---@field height number
|
||||
|
||||
---@class (exact) RuntimeViewportContext
|
||||
---@field x number
|
||||
---@field y number
|
||||
---@field width number
|
||||
---@field height number
|
||||
---@field scaleX number
|
||||
---@field scaleY number
|
||||
---@field scaleMode RuntimeScaleMode|string
|
||||
|
||||
---@class (exact) RuntimeContext
|
||||
---@field screen RuntimeScreenContext
|
||||
---@field design RuntimeDesignContext
|
||||
---@field viewport RuntimeViewportContext
|
||||
---@field seed integer
|
||||
---@field runtimeApiVersion integer
|
||||
---@field gameId string
|
||||
---@field gameVersion string
|
||||
---@field locale? RuntimeLocaleContext
|
||||
|
||||
---@class RuntimeUi
|
||||
---@field style fun(base?: RuntimeNodeProps, opts?: RuntimeNodeProps): RuntimeNodeProps
|
||||
---@field with_parent fun(parent: string, opts?: RuntimeNodeProps): RuntimeNodeProps
|
||||
---@field node fun(node_type: RuntimeNodeType, id: string, opts?: RuntimeNodeInit): RuntimeNode
|
||||
---@field panel fun(id: string, x: number|RuntimeNodeInit, y?: number, width?: number, height?: number, opts?: RuntimeNodeInit): RuntimeNode
|
||||
---@field rect fun(id: string, x: number|RuntimeNodeInit, y?: number, width?: number, height?: number, opts?: RuntimeNodeInit): RuntimeNode
|
||||
---@field circle fun(id: string, x: number|RuntimeNodeInit, y?: number, size?: number, opts?: RuntimeNodeInit): RuntimeNode
|
||||
---@field line fun(id: string, x: number|RuntimeNodeInit, y?: number, width?: number, height?: number, opts?: RuntimeNodeInit): RuntimeNode
|
||||
---@field progress fun(id: string, x: number|RuntimeNodeInit, y?: number, width?: number, height?: number, value?: number, opts?: RuntimeNodeInit): RuntimeNode
|
||||
---@field particle fun(id: string, x: number|RuntimeNodeInit, y?: number, width?: number, height?: number, opts?: RuntimeNodeInit): RuntimeNode
|
||||
---@field text fun(id: string, text: string|RuntimeNodeInit, x?: number, y?: number, width?: number, height?: number, opts?: RuntimeNodeInit): RuntimeNode
|
||||
---@field button fun(id: string, text: string|RuntimeNodeInit, x?: number, y?: number, width?: number, height?: number, handler?: string, opts?: RuntimeNodeInit): RuntimeNode
|
||||
---@field list_view fun(id: string, x: number|RuntimeNodeInit, y?: number, width?: number, height?: number, opts?: RuntimeNodeInit): RuntimeNode
|
||||
---@field image fun(id: string, asset: string|RuntimeNodeInit, x?: number, y?: number, width?: number, height?: number, opts?: RuntimeNodeInit): RuntimeNode
|
||||
---@field sprite fun(id: string, asset: string|RuntimeNodeInit, x?: number, y?: number, width?: number, height?: number, opts?: RuntimeNodeInit): RuntimeNode
|
||||
---@field spine fun(id: string, asset: string|RuntimeNodeInit, x?: number, y?: number, width?: number, height?: number, animation?: string, opts?: RuntimeNodeInit): RuntimeNode
|
||||
---@field update fun(id: string, props: RuntimeNodeInit): RuntimeNodeUpdate
|
||||
---@field text_update fun(id: string, text: string): RuntimeNodeUpdate
|
||||
---@field visible_update fun(id: string, visible: boolean): RuntimeNodeUpdate
|
||||
---@field alpha_update fun(id: string, alpha: number): RuntimeNodeUpdate
|
||||
---@field scale_update fun(id: string, scale: number): RuntimeNodeUpdate
|
||||
---@field position_update fun(id: string, x: number, y: number): RuntimeNodeUpdate
|
||||
---@field size_update fun(id: string, width: number, height: number): RuntimeNodeUpdate
|
||||
---@field transform_update fun(id: string, x: number, y: number, scale: number, rotation: number): RuntimeNodeUpdate
|
||||
---@field batch_update fun(ids: string[], props: RuntimeNodeInit): RuntimeNodeUpdate[]
|
||||
---@field append fun(nodes: RuntimeNode[], node: RuntimeNode): RuntimeNode[]
|
||||
---@field append_all fun(nodes: RuntimeNode[], extra_nodes: RuntimeNode[]): RuntimeNode[]
|
||||
|
||||
---@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 RuntimeLabeledProgressOpts: RuntimeNodeInit
|
||||
---@field labelHeight? number
|
||||
---@field labelStyle? RuntimeNodeProps
|
||||
|
||||
---@class RuntimePillOpts: RuntimeNodeInit
|
||||
---@field panelStyle? RuntimeNodeProps
|
||||
---@field textStyle? RuntimeNodeProps
|
||||
|
||||
---@class RuntimeTextButtonOpts: RuntimeNodeInit
|
||||
---@field variant? RuntimeButtonVariant
|
||||
|
||||
---@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
|
||||
---@field configure fun(tokens?: RuntimeWidgetTheme): RuntimeWidgets
|
||||
---@field label fun(id: string, text: string|RuntimeNodeInit, x?: number, y?: number, width?: number, height?: number, opts?: RuntimeNodeInit): RuntimeNode
|
||||
---@field section_title fun(id: string, text: string|RuntimeNodeInit, x?: number, y?: number, width?: number, height?: number, opts?: RuntimeNodeInit): RuntimeNode
|
||||
---@field pill fun(id: string, text: string|RuntimePillOpts, x?: number, y?: number, width?: number, height?: number, opts?: RuntimePillOpts): RuntimeNode[]
|
||||
---@field text_button fun(id: string, text: string|RuntimeTextButtonOpts, x?: number, y?: number, width?: number, height?: number, handler?: string, opts?: RuntimeTextButtonOpts): RuntimeNode
|
||||
---@field list_item fun(id: string, text: string|RuntimeListItemOpts, x?: number, y?: number, width?: number, height?: number, handler?: string, opts?: RuntimeListItemOpts): RuntimeNode
|
||||
---@field tabs fun(id: string, tabs: RuntimeTabItem[]|RuntimeTabsOpts, opts?: RuntimeTabsOpts): RuntimeNode[]
|
||||
---@field action_row fun(id: string, actions: RuntimeActionItem[]|RuntimeActionRowOpts, opts?: RuntimeActionRowOpts): RuntimeNode[]
|
||||
---@field panel_header fun(id: string, opts: RuntimePanelHeaderOpts): RuntimeNode[]
|
||||
---@field overlay fun(id: string, width: number|RuntimeNodeInit, height?: number, opts?: RuntimeNodeInit): RuntimeNode
|
||||
---@field card fun(id: string, x: number|RuntimeNodeInit, y?: number, width?: number, height?: number, opts?: RuntimeNodeInit): RuntimeNode
|
||||
---@field progress_bar fun(id: string, x: number|RuntimeNodeInit, y?: number, width?: number, height?: number, value?: number, opts?: RuntimeNodeInit): RuntimeNode
|
||||
---@field labeled_progress fun(id: string, label: string, x: number, y: number, width: number, height: number, value: number, opts?: RuntimeLabeledProgressOpts): RuntimeNode[]
|
||||
---@field button_row fun(parent: string, id: string, buttons: RuntimeDialogButton[], x: number, y: number, width: number, height: number, gap?: number, opts?: RuntimeNodeProps): RuntimeNode[]
|
||||
---@field dialog fun(id: string, title: string, message: string, x: number, y: number, width: number, height: number, opts?: RuntimeDialogOpts): RuntimeNode[]
|
||||
|
||||
---@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
|
||||
---@field item fun(node: RuntimeNode, opts?: RuntimeLayoutItemOpts): RuntimeLayoutItem
|
||||
---@field local_position fun(origin: RuntimePoint, position: RuntimePoint): RuntimePoint
|
||||
---@field row fun(parent?: string, items: (RuntimeNode|RuntimeLayoutItem)[], opts?: RuntimeLinearLayoutOpts): RuntimeNode[]
|
||||
---@field column fun(parent?: string, items: (RuntimeNode|RuntimeLayoutItem)[], opts?: RuntimeLinearLayoutOpts): RuntimeNode[]
|
||||
---@field stack fun(parent?: string, items: (RuntimeNode|RuntimeLayoutItem)[], opts?: RuntimeLinearLayoutOpts): RuntimeNode[]
|
||||
---@field box fun(parent?: string, items: (RuntimeNode|RuntimeLayoutItem)[], opts?: RuntimeBoxLayoutOpts): RuntimeNode[]
|
||||
|
||||
---@class RuntimeCommands
|
||||
---@field toast fun(text: string, opts?: RuntimeCommandOpts): RuntimeCommand
|
||||
---@field copy_text fun(text: string, opts?: RuntimeCommandOpts): RuntimeCommand
|
||||
---@field delay fun(duration: number, opts?: RuntimeCommandOpts): RuntimeCommand
|
||||
---@field sequence fun(items: RuntimeCommand[], opts?: RuntimeCommandOpts): RuntimeCommand
|
||||
---@field parallel fun(items: RuntimeCommand[], opts?: RuntimeCommandOpts): RuntimeCommand
|
||||
---@field move_path fun(target: string, path: RuntimePoint[], opts?: RuntimeCommandOpts): RuntimeCommand
|
||||
---@field move_to fun(target: string, x: number, y: number, opts?: RuntimeCommandOpts): RuntimeCommand
|
||||
---@field fade_to fun(target: string, alpha: number, opts?: RuntimeCommandOpts): RuntimeCommand
|
||||
---@field scale_to fun(target: string, scale: number, opts?: RuntimeCommandOpts): RuntimeCommand
|
||||
---@field rotate_to fun(target: string, angle: number, opts?: RuntimeCommandOpts): RuntimeCommand
|
||||
---@field remove_node fun(target: string, opts?: RuntimeCommandOpts): RuntimeCommand
|
||||
---@field play_spine_animation fun(target: string, animation: string, opts?: RuntimeSpineCommandOpts): RuntimeCommand
|
||||
---@field play_sound fun(asset: string, opts?: RuntimeAudioCommandOpts): RuntimeCommand
|
||||
---@field play_bgm fun(asset: string, opts?: RuntimeBgmCommandOpts): RuntimeCommand
|
||||
---@field pause_bgm fun(channel?: string, opts?: RuntimeCommandOpts): RuntimeCommand
|
||||
---@field resume_bgm fun(channel?: string, opts?: RuntimeCommandOpts): RuntimeCommand
|
||||
---@field stop_bgm fun(channel?: string, opts?: RuntimeCommandOpts): RuntimeCommand
|
||||
---@field preload_group fun(group: string, opts?: RuntimeResourceCommandOpts): RuntimeCommand
|
||||
---@field evict_group fun(group: string, opts?: RuntimeCommandOpts): RuntimeCommand
|
||||
---@field cancel_id fun(id: string): RuntimeCommand
|
||||
---@field cancel_group fun(group: string): RuntimeCommand
|
||||
---@field cancel_scope fun(scope: string): RuntimeCommand
|
||||
|
||||
---@class RuntimeImportApi
|
||||
---@field import fun(moduleName: string): table
|
||||
|
||||
---@type RuntimeImportApi
|
||||
runtime = runtime
|
||||
8
example/assets/games/template/scripts/state.lua
Normal file
@@ -0,0 +1,8 @@
|
||||
---@class TemplateState
|
||||
local state = {
|
||||
started = false,
|
||||
clicks = 0,
|
||||
status = "点击 Start 验证 RuntimeEvent -> Lua -> Diff / Command"
|
||||
}
|
||||
|
||||
return state
|
||||
100
example/assets/games/template/scripts/ui.lua
Normal file
@@ -0,0 +1,100 @@
|
||||
---@type RuntimeUi
|
||||
local runtime_ui = runtime.import("runtime_ui")
|
||||
---@type RuntimeWidgets
|
||||
local widgets = runtime.import("runtime_widgets")
|
||||
local state = runtime.import("state")
|
||||
|
||||
widgets.configure({
|
||||
primary = "#ff2563eb",
|
||||
secondary = "#ff475569",
|
||||
success = "#ff22c55e",
|
||||
overlay = "#99000000",
|
||||
surface = "#ff1e293b",
|
||||
surfaceAlt = "#ff334155",
|
||||
card = "#ee111827",
|
||||
text = "#ffe2e8f0",
|
||||
muted = "#ff94a3b8",
|
||||
progress = "#ff22c55e",
|
||||
transparent = "#00000000"
|
||||
})
|
||||
|
||||
---@class TemplateUi
|
||||
local ui = {}
|
||||
|
||||
---@return RuntimeNode[]
|
||||
function ui.create_nodes()
|
||||
return {
|
||||
runtime_ui.rect("template_bg", {
|
||||
x = 0,
|
||||
y = 0,
|
||||
w = 720,
|
||||
h = 720,
|
||||
color = "#ff0f172a"
|
||||
}),
|
||||
runtime_ui.panel("template_card", {
|
||||
x = 72,
|
||||
y = 96,
|
||||
w = 576,
|
||||
h = 360,
|
||||
color = "#ee111827",
|
||||
radius = 18,
|
||||
layer = 5
|
||||
}),
|
||||
widgets.section_title("template_title", {
|
||||
text = "Lua Runtime Template",
|
||||
x = 104,
|
||||
y = 132,
|
||||
w = 420,
|
||||
h = 34,
|
||||
color = "#ffe2e8f0",
|
||||
fontSize = 24,
|
||||
layer = 10
|
||||
}),
|
||||
widgets.label("template_desc", {
|
||||
text = "最小接入示例:一个 manifest、一个 main.lua、一组 RuntimeNode。",
|
||||
x = 104,
|
||||
y = 178,
|
||||
w = 500,
|
||||
h = 24,
|
||||
color = "#ff94a3b8",
|
||||
fontSize = 14,
|
||||
layer = 10
|
||||
}),
|
||||
runtime_ui.button("template_start", {
|
||||
text = state.started and "Started" or "Start",
|
||||
x = 104,
|
||||
y = 236,
|
||||
w = 144,
|
||||
h = 44,
|
||||
handler = "template_start",
|
||||
color = state.started and "#ff16a34a" or "#ff2563eb",
|
||||
radius = 12,
|
||||
fontSize = 15,
|
||||
layer = 10
|
||||
}),
|
||||
runtime_ui.text("template_counter", "Clicks: " .. tostring(state.clicks), 272, 246, 180, 24, {
|
||||
color = "#ffe2e8f0",
|
||||
fontSize = 15,
|
||||
layer = 10
|
||||
}),
|
||||
runtime_ui.text("template_status", state.status, 104, 320, 500, 24, {
|
||||
color = "#fffacc15",
|
||||
fontSize = 13,
|
||||
layer = 10
|
||||
})
|
||||
}
|
||||
end
|
||||
|
||||
---@return RuntimeNodeUpdate[]
|
||||
function ui.state_updates()
|
||||
return {
|
||||
runtime_ui.update("template_start", {
|
||||
text = state.started and "Started" or "Start",
|
||||
color = state.started and "#ff16a34a" or "#ff2563eb"
|
||||
}),
|
||||
runtime_ui.text_update("template_counter", "Clicks: " .. tostring(state.clicks)),
|
||||
runtime_ui.text_update("template_status", state.status)
|
||||
}
|
||||
end
|
||||
|
||||
return ui
|
||||