Files
2026-06-07 22:53:58 +08:00

699 lines
21 KiB
Lua
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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_commandsgroup = 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.dialogLua 组合普通 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 资源 clickcommands.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=demoplay_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("暂停 BGMpause_bgm('demo')。") },
commands = { commands.pause_bgm("demo") }
}
end
if state.bgm_state == "paused" then
state.bgm_state = "resumed"
return {
ui = { updates = ui.status_updates("恢复 BGMresume_bgm('demo')。") },
commands = { commands.resume_bgm("demo") }
}
end
state.bgm_state = "stopped"
return {
ui = { updates = ui.status_updates("停止 BGMstop_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("释放资源组 mediaevict_resources。再次点击会 preload。") },
commands = { commands.evict_group("media") }
}
end
state.resource_state = "ready"
return {
ui = { updates = ui.status_updates("预载资源组 mediapreload_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