---@type RuntimeUi local runtime_ui = runtime.import("runtime_ui") ---@type RuntimeWidgets local widgets = runtime.import("runtime_widgets") local theme = runtime.import("theme") local styles = runtime.import("styles") local state = runtime.import("state") local examples = runtime.import("examples") local i18n = runtime.import("i18n") ---@class ShowcaseUi local ui = {} local base_preview_ids = { "sample_rect", "sample_circle", "sample_line", "sample_image_node", "sample_sprite_node", "sample_progress", "widget_progress_label", "widget_progress" } local text_preview_ids = { "text_plain_title", "text_plain_body", "text_style_badge", "text_rich_note" } local button_preview_ids = { "button_hint", "button_primary", "button_secondary", "button_disabled", "button_state_text" } local button_image_preview_ids = { "image_button_hint", "image_button_normal", "image_button_toggle", "image_button_disabled", "image_button_state_text" } local sprite_preview_ids = { "sprite_image_demo", "sprite_sprite_demo", "sprite_frame_demo", "sprite_label_demo" } local layout_preview_ids = { "layout_canvas", "layout_chip_1", "layout_chip_2", "layout_chip_3", "layout_chip_4", "layout_label" } local radio_preview_ids = { "radio_title", "radio_audio_dot", "radio_audio_label", "radio_spine_dot", "radio_spine_label", "radio_lua_dot", "radio_lua_label", "radio_value_text" } local list_preview_ids = { "list_panel", "list_row_1", "list_row_2", "list_row_3", "list_row_4", "list_row_5", "list_row_1_text", "list_row_2_text", "list_row_3_text", "list_row_4_text", "list_row_5_text", "list_value_text" } local particle_preview_ids = { "particle_burst", "particle_trail", "particle_snow", "particle_label" } local responsive_preview_ids = { "responsive_info", "responsive_device", "responsive_sidebar", "responsive_content" } ---@return table local function metrics() local screen_w = theme.screen_width local screen_h = theme.screen_height local margin = 20 local gap = 16 local header_h = 82 local status_h = 30 local content_h = screen_h - header_h - status_h - margin local compact = screen_w < 640 local menu_w = compact and 188 or 220 local detail_w = screen_w - margin * 2 - gap - menu_w local content_y = header_h local preview_h = 148 local action_y = content_y + content_h - preview_h - 54 local preview_y = content_y + content_h - preview_h - 10 local code_h = action_y - content_y - 150 if code_h < 128 then code_h = 128 end return { screen_w = screen_w, screen_h = screen_h, margin = margin, gap = gap, compact = compact, menu_x = margin, menu_y = content_y, menu_w = menu_w, detail_x = margin + menu_w + gap, detail_y = content_y, detail_w = detail_w, content_h = content_h, action_y = action_y, preview_y = preview_y, preview_h = preview_h, code_h = code_h, status_y = screen_h - status_h - 8 } end ---@param example ShowcaseExample ---@return boolean local function preview_mode(example) if example.id == "text_demo" then return "text" end if example.id == "buttons" then return "buttons" end if example.id == "button_images" then return "button_images" end if example.id == "sprites" then return "sprites" end if example.id == "layout_demo" then return "layout" end if example.id == "radio_group" then return "radio" end if example.id == "list_view" then return "list" end if example.id == "particles" then return "particle" end if example.id == "responsive" then return "responsive" end return "base" end ---@param nodes RuntimeNode[] ---@param text string ---@param y number ---@param m table local function append_menu_group(nodes, text, y, m) table.insert(nodes, widgets.label("group_" .. tostring(y), { text = text, x = 16, y = y, w = m.menu_w - 32, h = 18, parent = "example_list_panel", color = theme.muted, fontSize = 11, layer = 30 })) end ---@param nodes RuntimeNode[] ---@param example ShowcaseExample ---@param y number ---@param m table local function append_menu_item(nodes, example, y, m) local selected = example.id == state.selected_example table.insert(nodes, widgets.list_item("example_" .. example.id, { text = i18n.example_label(example), x = 14, y = y, w = m.menu_w - 28, h = 27, handler = "select_example", parent = "example_list_panel", selected = selected, activeColor = theme.primary, inactiveColor = "#ff1e293b", radius = 10, fontSize = 11, layer = 31 })) end ---@param selected ShowcaseExample ---@param m table ---@return RuntimeNode[] local function detail_action_nodes(selected, m) local actions = {} for index = 1, 3 do local action = selected.actions[index] actions[index] = { id = "detail_action_" .. index, text = action ~= nil and i18n.action_text(action) or "", handler = action ~= nil and action.handler or "noop", visible = action ~= nil, color = index == 1 and "#ff2563eb" or "#ff334155" } end return widgets.action_row("detail_action", actions, { x = m.detail_x + 20, y = m.action_y, width = m.detail_w - 40, itemHeight = 34, gap = 12, radius = 10, fontSize = 12, layer = 35 }) end ---@param example ShowcaseExample ---@return string local function detail_body(example) if state.detail_tab == "params" then return i18n.example_params(example) end return example.code end ---@return string local function detail_copy_text() if state.detail_tab == "params" then return i18n.t("copy_params") end return i18n.t("copy_code") end ---@param text string ---@return integer local function line_count(text) local value = text or "" local count = 1 local start = 1 while true do local index = string.find(value, "\n", start, true) if index == nil then return count end count = count + 1 start = index + 1 end end local detail_text_y local detail_padding_left = 14 local detail_padding_top = 12 local detail_padding_right = 14 local detail_padding_bottom = 12 ---@param m table ---@return number local function detail_viewport_width(m) return m.detail_w - 40 - detail_padding_left - detail_padding_right end ---@param m table ---@return number local function detail_viewport_height(m) return m.code_h + 18 - detail_padding_top - detail_padding_bottom end ---@param example ShowcaseExample ---@return number local function detail_text_height(example) return line_count(detail_body(example)) * 16 + 8 end ---@param example ShowcaseExample ---@param m table ---@return number local function detail_content_height(example, m) local estimated = detail_text_y(example, m) + detail_text_height(example) + 18 local viewport_h = detail_viewport_height(m) if estimated < viewport_h then return viewport_h end return estimated end ---@param m table ---@return number local function detail_content_width(m) local viewport_w = detail_viewport_width(m) if state.detail_tab == "params" then return viewport_w end if line_count(detail_body(examples.find(state.selected_example))) <= 7 then return viewport_w end return viewport_w * 1.45 end ---@param example ShowcaseExample ---@param m table ---@return number local function detail_text_width(example, m) local viewport_w = detail_viewport_width(m) if state.detail_tab == "params" then return viewport_w * 0.78 end local content_w = detail_content_width(m) - 28 local centered_w = viewport_w * 0.78 if line_count(detail_body(example)) <= 7 then return centered_w end return content_w end ---@param example ShowcaseExample ---@param m table ---@return number local function detail_text_x(example, m) local viewport_w = detail_viewport_width(m) local width = detail_text_width(example, m) if state.detail_tab == "code" and width > viewport_w then return 14 end local x = (viewport_w - width) / 2 if x < 0 then return 0 end return x end ---@param example ShowcaseExample ---@param m table ---@return number detail_text_y = function(example, m) return 0 end ---@return string local function responsive_text() local scale = state.viewport_scale or 1 local width = state.screen_width or theme.screen_width local height = state.screen_height or theme.screen_height local viewport_w = state.viewport_width or theme.screen_width local viewport_h = state.viewport_height or theme.screen_height return i18n.t("responsive_design") .. ": " .. tostring(theme.screen_width) .. "x" .. tostring(theme.screen_height) .. " | " .. i18n.t("responsive_screen") .. ": " .. tostring(math.floor(width)) .. "x" .. tostring(math.floor(height)) .. " | " .. i18n.t("responsive_viewport") .. ": " .. tostring(math.floor(viewport_w)) .. "x" .. tostring(math.floor(viewport_h)) .. " @" .. string.format("%.2f", scale) end ---@return number local function responsive_device_width() if state.responsive_mode == "phone" then return 92 end if state.responsive_mode == "tablet" then return 124 end return 148 end ---@param nodes RuntimeNode[] ---@param selected ShowcaseExample local function append_preview_nodes(nodes, selected) local mode = preview_mode(selected) local base_visible = mode == "base" local text_visible = mode == "text" local button_visible = mode == "buttons" local button_image_visible = mode == "button_images" local sprite_visible = mode == "sprites" local layout_visible = mode == "layout" local radio_visible = mode == "radio" local list_visible = mode == "list" local particle_visible = mode == "particle" local responsive_visible = mode == "responsive" table.insert(nodes, runtime_ui.rect("sample_rect", 16, 48, 70, 44, { parent = "preview_panel", color = theme.primary, radius = 8, layer = 25, visible = base_visible })) table.insert(nodes, runtime_ui.circle("sample_circle", 104, 42, 54, { parent = "preview_panel", color = theme.success, interactive = true, onTap = "demo_anim", layer = 25, visible = base_visible })) table.insert(nodes, runtime_ui.line("sample_line", 176, 70, 82, 0, { parent = "preview_panel", color = theme.warning, strokeWidth = 4, layer = 25, visible = base_visible })) table.insert(nodes, runtime_ui.image("sample_image_node", "sample_image", 278, 42, 48, 48, { parent = "preview_panel", layer = 25, visible = base_visible })) table.insert(nodes, runtime_ui.sprite("sample_sprite_node", "sample_image", 340, 42, 48, 48, { parent = "preview_panel", layer = 25, visible = base_visible })) table.insert(nodes, runtime_ui.progress("sample_progress", 16, 112, 180, 16, state.progress, { parent = "preview_panel", color = theme.success, radius = 8, layer = 25, visible = base_visible })) local progress_nodes = widgets.labeled_progress("widget_progress", "Widget Progress", 220, 98, 170, 12, state.progress, { parent = "preview_panel", labelStyle = { parent = "preview_panel", color = theme.muted, fontSize = 12, layer = 25, visible = base_visible }, color = theme.primary, layer = 25, visible = base_visible }) runtime_ui.append_all(nodes, progress_nodes) table.insert(nodes, runtime_ui.text("text_plain_title", state.text_variant == "plain" and "Text: 纯文本组件" or "Text: 样式已切换", 16, 46, 260, 24, { parent = "preview_panel", color = state.text_variant == "plain" and theme.text or theme.warning, fontSize = state.text_variant == "plain" and 18 or 20, layer = 25, visible = text_visible })) table.insert(nodes, runtime_ui.text("text_plain_body", "字段:text / color / fontSize / alpha", 16, 78, 310, 20, { parent = "preview_panel", color = theme.muted, fontSize = 12, layer = 25, visible = text_visible })) table.insert(nodes, runtime_ui.rect("text_style_badge", 16, 108, 92, 24, { parent = "preview_panel", color = state.text_variant == "plain" and theme.primary or theme.purple, radius = 8, layer = 25, visible = text_visible })) table.insert(nodes, runtime_ui.text("text_rich_note", "富文本:当前未支持 richText/spans", 124, 111, 240, 18, { parent = "preview_panel", color = theme.warning, fontSize = 12, layer = 26, visible = text_visible })) table.insert(nodes, runtime_ui.text("button_hint", "button: text + background + onTap", 16, 44, 240, 20, { parent = "preview_panel", color = theme.muted, fontSize = 12, layer = 25, visible = button_visible })) table.insert(nodes, runtime_ui.button("button_primary", "主按钮", 16, 76, 108, 34, "demo_button_primary", { parent = "preview_panel", color = state.button_active and theme.primary or "#ff475569", radius = 10, fontSize = 13, layer = 26, visible = button_visible })) table.insert(nodes, runtime_ui.button("button_secondary", "次按钮", 138, 76, 108, 34, "demo_button_primary", { parent = "preview_panel", color = "#ff334155", radius = 10, fontSize = 13, layer = 26, visible = button_visible })) table.insert(nodes, runtime_ui.button("button_disabled", "禁用态", 260, 76, 108, 34, "noop", { parent = "preview_panel", color = "#ff1f2937", radius = 10, fontSize = 13, alpha = 0.55, interactive = false, layer = 26, visible = button_visible })) table.insert(nodes, runtime_ui.text("button_state_text", state.button_active and "状态:可点击" or "状态:已置灰", 16, 118, 220, 18, { parent = "preview_panel", color = theme.warning, fontSize = 12, layer = 26, visible = button_visible })) table.insert(nodes, runtime_ui.text("image_button_hint", "button image: normal / pressed / disabled", 16, 44, 300, 20, { parent = "preview_panel", color = theme.muted, fontSize = 12, layer = 25, visible = button_image_visible })) table.insert(nodes, runtime_ui.button("image_button_normal", { text = "按住 Pressed", x = 16, y = 76, w = 116, h = 40, handler = "demo_button_image_tap", parent = "preview_panel", asset = "button_normal", pressedAsset = "button_pressed", disabledAsset = "button_disabled", radius = 10, fontSize = 12, layer = 26, visible = button_image_visible })) table.insert(nodes, runtime_ui.button("image_button_toggle", { text = state.button_image_enabled and "可切换" or "已禁用", x = 144, y = 76, w = 116, h = 40, handler = "demo_button_image_tap", parent = "preview_panel", asset = "button_normal", pressedAsset = "button_pressed", disabledAsset = "button_disabled", radius = 10, fontSize = 12, interactive = state.button_image_enabled, layer = 26, visible = button_image_visible })) table.insert(nodes, runtime_ui.button("image_button_disabled", { text = "Disabled", x = 272, y = 76, w = 116, h = 40, handler = "noop", parent = "preview_panel", asset = "button_normal", pressedAsset = "button_pressed", disabledAsset = "button_disabled", radius = 10, fontSize = 12, interactive = false, layer = 26, visible = button_image_visible })) table.insert(nodes, runtime_ui.text("image_button_state_text", state.button_image_enabled and "中间按钮:interactive=true" or "中间按钮:interactive=false,显示 disabledAsset", 16, 122, 360, 18, { parent = "preview_panel", color = theme.warning, fontSize = 12, layer = 26, visible = button_image_visible })) table.insert(nodes, runtime_ui.image("sprite_image_demo", "sample_image", 24, 54, 64, 64, { parent = "preview_panel", layer = 25, visible = sprite_visible })) table.insert(nodes, runtime_ui.sprite("sprite_sprite_demo", "sample_image", 118, 54, 64, 64, { parent = "preview_panel", layer = 26, visible = sprite_visible })) table.insert(nodes, runtime_ui.rect("sprite_frame_demo", 212, 54, 64, 64, { parent = "preview_panel", color = state.sprite_variant == "image" and theme.primary or theme.purple, radius = 8, layer = 25, visible = sprite_visible })) table.insert(nodes, runtime_ui.text("sprite_label_demo", "image / sprite 都通过 manifest key 加载", 20, 120, 300, 18, { parent = "preview_panel", color = theme.muted, fontSize = 12, layer = 26, visible = sprite_visible })) table.insert(nodes, runtime_ui.panel("layout_canvas", 16, 42, 360, 86, { parent = "preview_panel", color = "#ff111827", radius = 10, layer = 24, visible = layout_visible })) table.insert(nodes, runtime_ui.rect("layout_chip_1", 34, 70, 58, 28, { parent = "preview_panel", color = theme.primary, radius = 8, layer = 26, visible = layout_visible })) table.insert(nodes, runtime_ui.rect("layout_chip_2", 108, 70, 58, 28, { parent = "preview_panel", color = theme.success, radius = 8, layer = 26, visible = layout_visible })) table.insert(nodes, runtime_ui.rect("layout_chip_3", 182, 70, 58, 28, { parent = "preview_panel", color = theme.warning, radius = 8, layer = 26, visible = layout_visible })) table.insert(nodes, runtime_ui.rect("layout_chip_4", 256, 70, 58, 28, { parent = "preview_panel", color = theme.purple, radius = 8, layer = 26, visible = layout_visible })) table.insert(nodes, runtime_ui.text("layout_label", "layout.row:gap + align + margin", 28, 112, 280, 18, { parent = "preview_panel", color = theme.muted, fontSize = 12, layer = 26, visible = layout_visible })) table.insert(nodes, runtime_ui.text("radio_title", "RadioGroup: Lua 组合单选项", 16, 44, 260, 20, { parent = "preview_panel", color = theme.muted, fontSize = 12, layer = 25, visible = radio_visible })) local radio_options = { { key = "audio", label = "Audio", y = 72 }, { key = "spine", label = "Spine", y = 96 }, { key = "Lua", label = "Lua", y = 120 } } for _, option in ipairs(radio_options) do local selected_radio = state.radio_selected == option.key table.insert(nodes, runtime_ui.circle("radio_" .. string.lower(option.key) .. "_dot", 18, option.y, 14, { parent = "preview_panel", color = selected_radio and theme.primary or "#ff475569", layer = 25, visible = radio_visible })) table.insert(nodes, runtime_ui.text("radio_" .. string.lower(option.key) .. "_label", option.label, 42, option.y - 2, 120, 18, { parent = "preview_panel", color = selected_radio and theme.text or theme.muted, fontSize = 12, layer = 25, visible = radio_visible })) end table.insert(nodes, runtime_ui.text("radio_value_text", "当前选择:" .. state.radio_selected, 190, 96, 160, 18, { parent = "preview_panel", color = theme.warning, fontSize = 12, layer = 25, visible = radio_visible })) local list_horizontal = state.list_axis == "horizontal" table.insert(nodes, runtime_ui.list_view("list_panel", 16, 42, 320, 72, { parent = "preview_panel", color = "#ff111827", radius = 10, contentWidth = list_horizontal and 650 or 430, contentHeight = list_horizontal and 90 or 150, scrollX = state.list_scroll_x, scrollY = state.list_scroll_y, virtualized = true, cacheExtent = 24, inertia = true, onScroll = "demo_list_scrolled", scrollbarThumbColor = theme.warning, scrollbarTrackColor = "#33475569", scrollbarThickness = 6, layer = 24, visible = list_visible })) local list_items = { { key = "Lua", label = "Lua 脚本层" }, { key = "Runtime", label = "Runtime 协议层" }, { key = "Flame", label = "Flame 渲染层" }, { key = "Diff", label = "Diff 更新流" }, { key = "Command", label = "Command 动作流" } } for index, item in ipairs(list_items) do local selected_item = state.list_selected == item.key local row_x = list_horizontal and (8 + (index - 1) * 126) or 8 local row_y = list_horizontal and 18 or (8 + (index - 1) * 28) local row_w = list_horizontal and 116 or 292 table.insert(nodes, runtime_ui.button("list_row_" .. index, "", row_x, row_y, row_w, 24, "demo_list_pick_" .. index, { parent = "list_panel", color = selected_item and theme.primary or "#ff1e293b", radius = 7, layer = 25, visible = list_visible })) table.insert(nodes, runtime_ui.text("list_row_" .. index .. "_text", item.label, 12, 4, 180, 16, { parent = "list_row_" .. index, color = theme.text, fontSize = 11, textAlign = "left", layer = 26, visible = list_visible })) end table.insert(nodes, runtime_ui.text("list_value_text", state.list_axis .. " scrollX=" .. tostring(state.list_scroll_x) .. " scrollY=" .. tostring(state.list_scroll_y), 16, 120, 300, 16, { parent = "preview_panel", color = theme.warning, fontSize = 11, layer = 26, visible = list_visible })) table.insert(nodes, runtime_ui.particle("particle_burst", 32, 42, 120, 92, { parent = "preview_panel", preset = "burst", count = 42, duration = 0.85, color = "#ffffcc33", colorTo = "#00ffcc33", radius = 2.8, speedMin = 60, speedMax = 180, gravityY = 90, spread = 360, autoRemove = false, layer = 26, visible = particle_visible })) table.insert(nodes, runtime_ui.particle("particle_trail", 168, 46, 120, 86, { parent = "preview_panel", preset = "trail", count = 24, duration = 0.7, color = "#ff38bdf8", radius = 2.2, speedMin = 20, speedMax = 80, autoRemove = false, layer = 26, visible = particle_visible })) table.insert(nodes, runtime_ui.particle("particle_snow", 304, 34, 120, 104, { parent = "preview_panel", preset = "snow", count = 56, duration = 8, color = "#ccffffff", radius = 1.5, autoRemove = false, fadeOut = false, layer = 26, visible = particle_visible })) table.insert(nodes, runtime_ui.text("particle_label", "particle: burst / trail / snow", 16, 120, 280, 18, { parent = "preview_panel", color = theme.warning, fontSize = 11, layer = 27, visible = particle_visible })) local width = responsive_device_width() table.insert(nodes, runtime_ui.text("responsive_info", responsive_text(), 16, 112, 390, 18, { parent = "preview_panel", color = theme.muted, fontSize = 10, layer = 26, visible = responsive_visible })) table.insert(nodes, runtime_ui.rect("responsive_device", 172, 44, width, 62, { parent = "preview_panel", color = "#ff1d4ed8", radius = 10, layer = 26, visible = responsive_visible })) table.insert(nodes, runtime_ui.rect("responsive_sidebar", 184, 56, width * 0.26, 38, { parent = "preview_panel", color = theme.primary, radius = 6, layer = 27, visible = responsive_visible })) table.insert(nodes, runtime_ui.rect("responsive_content", 192 + width * 0.26, 56, width * 0.56, 38, { parent = "preview_panel", color = theme.success, radius = 6, layer = 27, visible = responsive_visible })) end ---@return RuntimeNode[] function ui.create_nodes() local nodes = {} local selected = examples.find(state.selected_example) local m = metrics() table.insert(nodes, runtime_ui.rect("app_bg", 0, 0, theme.screen_width, theme.screen_height, { color = theme.background, layer = 0 })) table.insert(nodes, runtime_ui.text("app_title", i18n.t("app_title"), 24, 18, 360, 34, styles.title)) table.insert(nodes, runtime_ui.text("app_subtitle", i18n.t("app_subtitle"), 24, 52, 660, 24, styles.label)) table.insert(nodes, runtime_ui.panel("example_list_panel", m.menu_x, m.menu_y, m.menu_w, m.content_h, styles.card)) table.insert(nodes, widgets.section_title("example_list_title", { text = i18n.t("examples_title"), x = 16, y = 14, w = m.menu_w - 32, h = 24, parent = "example_list_panel", color = theme.text, fontSize = 20, layer = 25 })) local menu_y = 44 local last_group = nil for _, example in ipairs(examples.all()) do local group = i18n.example_group(example) if group ~= last_group then append_menu_group(nodes, group, menu_y, m) menu_y = menu_y + 15 last_group = group end append_menu_item(nodes, example, menu_y, m) menu_y = menu_y + 32 end table.insert(nodes, runtime_ui.panel("detail_panel", m.detail_x, m.detail_y, m.detail_w, m.content_h, styles.card)) runtime_ui.append_all(nodes, widgets.panel_header("detail", { eyebrow = i18n.example_group(selected) .. " / " .. i18n.example_field(selected, "category"), title = i18n.example_field(selected, "title"), summary = i18n.example_field(selected, "summary"), x = 20, y = 16, w = m.detail_w - 40, parent = "detail_panel", layer = 25, gap = 4, eyebrowHeight = 20, titleHeight = 30, summaryHeight = 24, eyebrowId = "detail_category", titleId = "detail_title", summaryId = "detail_summary", eyebrowStyle = { color = theme.primary, fontSize = 13 }, titleStyle = { color = theme.text, fontSize = 21 }, summaryStyle = { color = theme.muted, fontSize = 12 } })) runtime_ui.append_all(nodes, widgets.tabs("detail_tab", { { id = "detail_tab_code", key = "code", text = i18n.t("tab_code"), handler = "detail_tab_code" }, { id = "detail_tab_params", key = "params", text = i18n.t("tab_params"), handler = "detail_tab_params" } }, { x = m.detail_x + 20, y = m.detail_y + 104, itemWidth = 72, itemHeight = 24, gap = 6, selected = state.detail_tab, activeColor = theme.primary, inactiveColor = "#ff334155", radius = 8, fontSize = 11, interactive = true, layer = 90 })) table.insert(nodes, widgets.text_button("detail_copy", { text = detail_copy_text(), x = m.detail_x + m.detail_w - 116, y = m.detail_y + 104, w = 96, h = 24, handler = "copy_detail", color = "#ff475569", radius = 8, fontSize = 11, interactive = true, layer = 90 })) table.insert(nodes, runtime_ui.list_view("code_panel", m.detail_x + 20, m.detail_y + 132, m.detail_w - 40, m.code_h + 18, { color = "#ff020617", radius = 12, contentWidth = detail_content_width(m), contentHeight = detail_content_height(selected, m), scrollX = 0, scrollY = 0, paddingLeft = detail_padding_left, paddingTop = detail_padding_top, paddingRight = detail_padding_right, paddingBottom = detail_padding_bottom, virtualized = false, inertia = true, scrollbarThumbColor = theme.primary, scrollbarTrackColor = "#33475569", scrollbarThickness = 5, layer = 20 })) table.insert(nodes, runtime_ui.text("detail_code", detail_body(selected), detail_text_x(selected, m), detail_text_y(selected, m), detail_text_width(selected, m), detail_text_height(selected), { parent = "code_panel", color = "#ffe2e8f0", fontSize = 11, textAlign = "left", layer = 25 })) runtime_ui.append_all(nodes, detail_action_nodes(selected, m)) table.insert(nodes, runtime_ui.panel("preview_panel", m.detail_x + 20, m.preview_y, m.detail_w - 40, m.preview_h, { color = "#ff0b1120", radius = 12, layer = 20 })) table.insert(nodes, widgets.section_title("preview_title", { text = i18n.t("preview_title"), x = 14, y = 10, w = 220, h = 22, parent = "preview_panel", color = theme.text, fontSize = 15, layer = 25 })) append_preview_nodes(nodes, selected) table.insert(nodes, runtime_ui.text("status_text", state.status, 24, m.status_y, theme.screen_width - 48, 26, { color = theme.warning, fontSize = 14, layer = 50 })) return nodes end ---@param text string ---@return RuntimeNodeUpdate[] function ui.status_updates(text) state.status = text return { runtime_ui.text_update("status_text", text) } end ---@param selected ShowcaseExample ---@return RuntimeNodeUpdate[] local function preview_visibility_updates(selected) local updates = {} local mode = preview_mode(selected) local groups = { { ids = base_preview_ids, visible = mode == "base" }, { ids = text_preview_ids, visible = mode == "text" }, { ids = button_preview_ids, visible = mode == "buttons" }, { ids = button_image_preview_ids, visible = mode == "button_images" }, { ids = sprite_preview_ids, visible = mode == "sprites" }, { ids = layout_preview_ids, visible = mode == "layout" }, { ids = radio_preview_ids, visible = mode == "radio" }, { ids = list_preview_ids, visible = mode == "list" }, { ids = particle_preview_ids, visible = mode == "particle" }, { ids = responsive_preview_ids, visible = mode == "responsive" } } for _, group in ipairs(groups) do for _, id in ipairs(group.ids) do table.insert(updates, runtime_ui.visible_update(id, group.visible)) end end return updates end ---@param selected ShowcaseExample ---@return RuntimeNodeUpdate[] function ui.example_updates(selected) local m = metrics() local updates = { runtime_ui.text_update("detail_category", i18n.example_group(selected) .. " / " .. i18n.example_field(selected, "category")), runtime_ui.text_update("detail_title", i18n.example_field(selected, "title")), runtime_ui.text_update("detail_summary", i18n.example_field(selected, "summary")), runtime_ui.text_update("detail_code", detail_body(selected)), runtime_ui.update("detail_code", { x = detail_text_x(selected, m), y = detail_text_y(selected, m), width = detail_text_width(selected, m), height = detail_text_height(selected), textAlign = "left" }), runtime_ui.update("code_panel", { contentWidth = detail_content_width(m), contentHeight = detail_content_height(selected, m), scrollX = 0, scrollY = 0 }), runtime_ui.update("detail_tab_code", { text = i18n.t("tab_code"), onTap = "detail_tab_code", interactive = true, layer = 90, color = state.detail_tab == "code" and theme.primary or "#ff334155" }), runtime_ui.update("detail_tab_params", { text = i18n.t("tab_params"), onTap = "detail_tab_params", interactive = true, layer = 90, color = state.detail_tab == "params" and theme.primary or "#ff334155" }), runtime_ui.update("detail_copy", { text = detail_copy_text(), onTap = "copy_detail", interactive = true, layer = 90 }), runtime_ui.text_update("status_text", i18n.t("selected_prefix") .. i18n.example_field(selected, "title")) } for _, example in ipairs(examples.all()) do table.insert(updates, runtime_ui.update("example_" .. example.id, { text = i18n.example_label(example), color = example.id == selected.id and theme.primary or "#ff1e293b" })) end for index = 1, 3 do local action = selected.actions[index] if action ~= nil then table.insert(updates, runtime_ui.update("detail_action_" .. index, { text = i18n.action_text(action), onTap = action.handler, visible = true, color = index == 1 and "#ff2563eb" or "#ff334155" })) else table.insert(updates, runtime_ui.update("detail_action_" .. index, { text = "", onTap = "noop", visible = false })) end end local preview_updates = preview_visibility_updates(selected) for _, update in ipairs(preview_updates) do table.insert(updates, update) end table.insert(updates, runtime_ui.update("status_text", { width = m.screen_w - 48 })) return updates end ---@return RuntimeNodeUpdate[] function ui.locale_updates() local selected = examples.find(state.selected_example) local updates = { runtime_ui.text_update("app_title", i18n.t("app_title")), runtime_ui.text_update("app_subtitle", i18n.t("app_subtitle")), runtime_ui.text_update("example_list_title", i18n.t("examples_title")), runtime_ui.text_update("preview_title", i18n.t("preview_title")), runtime_ui.text_update("responsive_info", responsive_text()) } local detail_updates = ui.example_updates(selected) for _, update in ipairs(detail_updates) do table.insert(updates, update) end return updates end ---@return RuntimeNodeUpdate[] function ui.responsive_updates() local width = responsive_device_width() return { runtime_ui.text_update("responsive_info", responsive_text()), runtime_ui.update("responsive_device", { width = width }), runtime_ui.update("responsive_sidebar", { width = width * 0.26 }), runtime_ui.update("responsive_content", { x = 192 + width * 0.26, width = width * 0.56 }) } end ---@return RuntimeNodeUpdate[] function ui.text_updates() return { runtime_ui.update("text_plain_title", { text = state.text_variant == "plain" and "Text: 纯文本组件" or "Text: 样式已切换", color = state.text_variant == "plain" and theme.text or theme.warning, fontSize = state.text_variant == "plain" and 18 or 20 }), runtime_ui.update("text_style_badge", { color = state.text_variant == "plain" and theme.primary or theme.purple }) } end ---@return RuntimeNodeUpdate[] function ui.radio_updates() local updates = { runtime_ui.text_update("radio_value_text", "当前选择:" .. state.radio_selected) } local options = { "audio", "spine", "lua" } for _, key in ipairs(options) do local value = key == "lua" and "Lua" or key local selected = state.radio_selected == value table.insert(updates, runtime_ui.update("radio_" .. key .. "_dot", { color = selected and theme.primary or "#ff475569" })) table.insert(updates, runtime_ui.update("radio_" .. key .. "_label", { color = selected and theme.text or theme.muted })) end return updates end ---@return RuntimeNodeUpdate[] function ui.list_updates() local horizontal = state.list_axis == "horizontal" local updates = { runtime_ui.update("list_panel", { contentWidth = horizontal and 650 or 430, contentHeight = horizontal and 90 or 150, scrollX = state.list_scroll_x, scrollY = state.list_scroll_y }), runtime_ui.text_update("list_value_text", state.list_axis .. " scrollX=" .. tostring(state.list_scroll_x) .. " scrollY=" .. tostring(state.list_scroll_y)) } local items = { "Lua", "Runtime", "Flame", "Diff", "Command" } for index, key in ipairs(items) do local row_x = horizontal and (8 + (index - 1) * 126) or 8 local row_y = horizontal and 18 or (8 + (index - 1) * 28) local row_w = horizontal and 116 or 292 table.insert(updates, runtime_ui.update("list_row_" .. index, { x = row_x, y = row_y, width = row_w, color = state.list_selected == key and theme.primary or "#ff1e293b" })) table.insert(updates, runtime_ui.update("list_row_" .. index .. "_text", { textAlign = "left" })) end return updates end ---@return RuntimeNodeUpdate[] function ui.button_updates() return { runtime_ui.update("button_primary", { color = state.button_active and theme.primary or "#ff475569", alpha = state.button_active and 1 or 0.65 }), runtime_ui.text_update("button_state_text", state.button_active and "状态:可点击" or "状态:已置灰") } end ---@return RuntimeNodeUpdate[] function ui.button_image_updates() return { runtime_ui.update("image_button_toggle", { text = state.button_image_enabled and "可切换" or "已禁用", interactive = state.button_image_enabled }), runtime_ui.text_update("image_button_state_text", state.button_image_enabled and "中间按钮:interactive=true" or "中间按钮:interactive=false,显示 disabledAsset") } end ---@return RuntimeNodeUpdate[] function ui.sprite_updates() return { runtime_ui.update("sprite_frame_demo", { color = state.sprite_variant == "image" and theme.primary or theme.purple, rotation = state.sprite_variant == "image" and 0 or 0.12 }), runtime_ui.text_update("sprite_label_demo", state.sprite_variant == "image" and "当前:image 原图节点" or "当前:sprite 可动画节点") } end ---@param mode string ---@return RuntimeNodeUpdate[] function ui.layout_updates(mode) if mode == "column" then return { runtime_ui.update("layout_chip_1", { x = 44, y = 46 }), runtime_ui.update("layout_chip_2", { x = 44, y = 74 }), runtime_ui.update("layout_chip_3", { x = 44, y = 102 }), runtime_ui.update("layout_chip_4", { x = 44, y = 130 }), runtime_ui.text_update("layout_label", "layout.column:纵向排列 + gap") } end if mode == "box" then return { runtime_ui.update("layout_chip_1", { x = 44, y = 54 }), runtime_ui.update("layout_chip_2", { x = 116, y = 54 }), runtime_ui.update("layout_chip_3", { x = 44, y = 90 }), runtime_ui.update("layout_chip_4", { x = 116, y = 90 }), runtime_ui.text_update("layout_label", "layout.box:2 行 × 2 列网格") } end return { runtime_ui.update("layout_chip_1", { x = 34, y = 70 }), runtime_ui.update("layout_chip_2", { x = 108, y = 70 }), runtime_ui.update("layout_chip_3", { x = 182, y = 70 }), runtime_ui.update("layout_chip_4", { x = 256, y = 70 }), runtime_ui.text_update("layout_label", "layout.row:横向排列 + gap") } end ---@return RuntimeNodeUpdate[] function ui.progress_updates() return { runtime_ui.update("sample_progress", { value = state.progress }), runtime_ui.update("widget_progress", { value = state.progress }) } end ---@return RuntimeNodeUpdate[] function ui.visibility_updates() return { runtime_ui.visible_update("sample_rect", state.visible), runtime_ui.alpha_update("sample_circle", state.visible and 1 or 0.35) } end ---@return RuntimeNode[] function ui.dialog_nodes() return widgets.dialog("sample_dialog", "Widget Dialog", "这是 Lua 侧组合组件:overlay + card + text + button。输出仍然只是普通 RuntimeNode。", 150, 190, 420, 230, { screenWidth = theme.screen_width, screenHeight = theme.screen_height, buttons = { { text = "播放音效", handler = "demo_sound" }, { text = "关闭", handler = "close_dialog", color = theme.danger } } }) end ---@return { id: string }[] function ui.dialog_removes() return { { id = "sample_dialog_overlay" }, { id = "sample_dialog" }, { id = "sample_dialog_title" }, { id = "sample_dialog_message" }, { id = "sample_dialog_button_1" }, { id = "sample_dialog_button_2" } } end ---@return RuntimeNode[] function ui.temp_nodes() local m = metrics() return { runtime_ui.rect("temp_node", m.detail_x + 270, m.preview_y + 98, 90, 28, { color = theme.warning, radius = 8, layer = 45 }), runtime_ui.text("temp_node_text", "临时节点", m.detail_x + 284, m.preview_y + 103, 72, 18, { color = "#ff111827", fontSize = 13, layer = 46 }) } end return ui