Initial flame_lua_runtime package

This commit is contained in:
gem
2026-06-07 22:53:58 +08:00
commit 733b2fb798
262 changed files with 28439 additions and 0 deletions

View 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 输出普通 RuntimeNodecircle + 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

View 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

View 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_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

View 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

View 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

View 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

View 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

File diff suppressed because it is too large Load Diff