---@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