Skip to content

流式布局 UI API 参考 ​

所有扩展方法返回控件本身,支持链式调用。

核心类:LayoutExtensions(布局)、BuilderExtensions(控件属性)、UI(静态构建器)。

Game HUD and round-end presentation ​

For a game HUD or visual reskin, also read game-visual-production. Choose scene-matching surface/accent/text colors, typography, panel silhouettes and spacing as project tokens before assembling controls. Group health, weapon/ammo and mission/score by importance; keep central aiming space clear. The optional industrial HUD recipe gives a concrete GameUI palette/layout starter, not a mandatory theme.

Gameplay-critical state needs a visible transition. On defeat, show a clear central result panel with a primary restart action; a short screen tint/dim transition can reinforce it when appropriate. Do not rely solely on a small top-bar text change and frozen picture. Route button and keyboard restart through the same round reset, hide/reset transient overlays, and verify the click does not also fire a weapon through the UI.

Test the intended round-end policy for movement, aim, attacks, enemy updates and spawning. Gate gameplay input while a blocking result panel is open, retain its UI input, and check that restart resets health, projectiles, counters and stale pressed inputs. Use screenshots plus ui.snapshot / ui.get_rect and actual button interaction; correct control rectangles alone do not establish visual clarity.

> 先检查项目的对齐默认策略:当前游戏模式引用的 GameDataGameUI 可通过 > UseTopLeftContentAlignmentDefaults 选择新建控件的内容对齐默认值。旧项目缺少该字段或关闭时, > HorizontalContentAlignment / VerticalContentAlignment 保持历史 Center/Center;新版项目模板默认开启, > 改为 Left/Top。该开关只影响之后创建的控件,不改写显式值,也不改变 PositionType。 > 可复用库和需要固定布局语义的控件仍应显式设置对齐,不依赖宿主项目开关。 > > 在历史 Center 模式下,当子控件未显式设置 HorizontalAlignment / VerticalAlignment 时,会继承父控件的 > Center 对齐,导致 Margin 从中心偏移而非从左上角偏移,产生文字偏右下的视觉问题。 > 多个子控件更危险:若容器内有多个子控件都未设对齐,它们全部被居中到同一区域后完全重叠。 > > 按预期布局选择对齐方式。旧项目或可复用库若要从左上角用 Margin 定位,显式设置 Left/Top;需要自动排列时使用对应方向的流式容器。 > 流式扩展与对象初始化器配置同一套属性,可以混用;两者都需检查尺寸、对齐和父子关系。

> 生命周期警告:RemoveFromParent() / RemoveFromVisualTree() 只把控件从 UI 树摘下,ClearChildren() 只移除直接子控件;这些 API 不释放控件、子控件树、事件或 native control。 > 浮字、弹窗、临时面板、战斗 HUD、临时 Canvas 等关闭后不复用的 UI 必须调用 Destroy()。 > 只有临时隐藏、换父节点、对象池复用时才使用 RemoveFromParent() / ClearChildren()。对 Canvas / CanvasAnimated,长期只摘树不销毁在移动端更容易累积渲染资源压力。

发布图片加载 ​

PC 和手机发布都可能把 PNG/JPG 转为 KTX 等纹理容器,路径后缀仍保持不变。不要用 GameGraph.ResourceSystem.Image.Load(path) 的 CPU 解码结果判断 GameUI/Canvas 图片是否存在或允许绘制;CPU 读取失败可能只是格式不支持。普通 UI 直接使用逻辑图片路径。确需提前加载独立散图以实现缺图回落时,用 GameGraph.ResourceSystem.Texture2D.Load(path),现有客户端经 TextureX 取得 Texture2D;不要每帧加载。图集虚拟子图不能按独立纹理文件检查。正确示例与旧客户端边界见发布图片的加载与检查,发布后还需在目标平台验证实际显示。

图集与大图子区域 ​

用户要让普通 UI 控件显示图集里的一小块时,先读自定义 UI 图集,使用现有图集 JSON 配置流程。控件没有 SourceRect / UV 属性,不代表必须改用 Canvas。

  1. 读取大图尺寸,按用户指定或实际素材识别出每个图标的像素矩形,确认取图区域没有越界。
  2. 在项目 ui/atlas/ 写入大图与同名区域 JSON,例如 custom_icons.png + custom_icons.json;避开自动生成的 atlas_1 等名称。
  3. 合并 ui/atlas/atlas.json 清单,保留现有条目。AtlasPath 和 ConfigPath 相对于 ui/,分别填写 atlas/custom_icons.png 和 atlas/custom_icons.json。
  4. 区域数组条目的 RelativePath 带 ui/,例如 ui/image/icons/sword.png;控件 Image 省略该前缀,填 image/icons/sword.png。逻辑名称对应的散图不需要存在。区域格式和必要字段以指南示例为准,名称不要重复。
  5. 在现有 UI 中给控件配置该 Image 与显示尺寸;通过数据编辑器修改控件数据时继续走 DataManager/MCP。图集 JSON 是资源配置,可以由 AI 直接写文件。
  6. 写完配置后重新预览,结合截图和 ui.snapshot 确认每块图标的内容、区域和显示尺寸。图集缓存不属于普通 ImageCache.Invalidate 的刷新保证,修改配置后重载项目,必要时重启编辑器。

自动打包会用 ui/atlas/ 下同名配对的 PNG/JPG 与 JSON 重建清单,因此保留自定义文件对。Windows PIE 验证通过后,移动端发布仍应单独验收;不要把编译、控件矩形或图片缓存失效返回成功当作显示正确的证据。

发布后,若 Editor MCP 提供 project_publish_verify_resources,用该次成功操作返回的 publish_identity.platform_variants 选择实际变体,启动只读校验,再按返回的 verification_id 调用 status/wait 续查。同一发布操作及变体重复启动会复用任务;等待超时不重发项目。清单和区域 JSON 差异必须处理,PNG 转 KTX 的哈希差异则按容器/格式检查。工具的 not_checked_decoder_unavailable 表示未核对像素;最后仍用目标客户端实际显示验收。旧发布缺少身份时不猜 URL/版本。完整参数见发布包校验 MCP。

转换 FGUI 图集前检查透明裁边和旋转:当前普通区域格式没有裁边偏移和旋转字段,OriginSize 不会自动居中或恢复裁边位置。优先关闭 trimImage 和旋转;已有裁边数据必须利用来源偏移补回透明画布再打包,缺少偏移时不要猜。完整矩形的九宫格使用 CompressType: 0 与 Border: "左 上 右 下" 四侧像素宽度;只有分块压缩格式 CompressType: 4 才使用 Grid-0 至 Grid-8。详细示例见图集指南。

OriginSize 是图片尺寸查询所用的逻辑尺寸,Width/Height 是图集实际存储矩形。完整图片预先缩小后可保留原逻辑尺寸,配合 Scaled: true 换算九宫格采样边宽;九宫格重复区域压缩也可保留处理前尺寸,但不使用整图缩放换算。不要把尺寸差异解释成透明裁边或自动补边。

图集黑白边或串色时,先读黑白边与图块串色:按相邻区域串色、透明像素 RGB 污染、预乘 Alpha 约定、Mipmap 和发布压缩分别定位。生成图集时区分 Padding、Extrude 和 Alpha Bleed;无 Mipmap 的双线性 UI 可从每侧 Extrude 2 像素、原始矩形间距 4 像素起步,按目标缩放/压缩调整,不能当作全平台保证。区域矩形不含外部保护区,OriginSize 和九宫格 Border 不用于补偿保护区。不要未经核对就预乘图片、修改混合模式、内缩所有 UV 或编造区域 JSON 的 Mipmap 字段。

透明像素 RGB 颜色扩散检查清单 ​

  • [ ] 检查轮廓附近 Alpha = 0 像素的 RGB 是否被黑色或白色填充;同时查看半透明边缘是否已经带有底色。仅看透明背景预览不能确认 RGB 是否正确。
  • [ ] 核对源图和渲染链路使用 Straight Alpha 还是 Premultiplied Alpha。下面的 RGB 扩散针对非预乘源图;预乘数据的全透明像素应满足其预乘约定,不直接写入非零 RGB。
  • [ ] 需要 Alpha Bleed / Color Dilation 时,将同一子图片中邻近可见像素的 RGB 扩散到全透明区域,保持整个 Alpha 通道逐像素不变;此处理不改写原有可见或半透明像素。半透明边缘已有的底色应另行从原稿修正。
  • [ ] 按子图片分别扩散,再进行边缘扩展、图集打包及目标纹理转换;处理已有图集时按各区域隔离,避免颜色从另一个图标扩散过来。RGB 扩散与矩形外的 Extrude 分别检查。
  • [ ] 在源图及实际导出/发布纹理中复查边缘颜色和 Alpha 约定,并在浅/深背景、原尺寸及实际缩放下截图验收;记录是否执行过颜色扩散,不把清单勾选当作显示正确的证据。

需要动态指定每次绘制的源矩形时再使用 Canvas.DrawImage 源区域重载;普通图集显示不需要 CPU 裁图、Resize 或重建纹理。

Loading 页面 ​

当用户要自定义 Loading 页面、启动遮罩、场景切换进度或加载提示时,先看 docs/systems/LoadingUIScreen.md。

按以下优先级选择方案:

  1. 关闭框架默认启动 Loading:在当前 GameDataGameUI 中设置 ShowDefaultLoading = false。如果 GameMode 仍引用 gamesparkcore.ScopeData.GameDataGameUI.Default,先创建项目自己的 GameDataGameUI,再让 GameDataGameMode.GameUI 指向它;不要修改依赖库数据。
  2. 换肤框架默认启动 Loading:创建项目自己的 GameDataLoadingUI,优先继承 GameSparkCore 的“Loading界面模板”或 gamesparkcore.ScopeData.GameDataLoadingUI.Default;在 GameDataGameUI 中保持 ShowDefaultLoading = true,并通过 DefaultLoadingUI 引用它。
  3. 代码动态显示同款 Loading:使用 new LoadingUI(configuration) 复用 GameDataLoadingUI 样式。GameEntryLoadingSystem.ShowLoading(...) 和无参 LoadingUI 工厂使用兼容默认样式,不会自动读取项目的 DefaultLoadingUI。
  4. 完全自建页面:只有结构与内置 Loading 明显不同时,才用 Page、Panel、Label、Image、Progress 等控件搭页面,并设置 .ZIndex(StandardUIType.Loading.ExpectedZIndex ?? 10000);临时遮罩结束后调用 Destroy()。

框架在 Game.OnGameplaySettingsInitialization 读取当前 GameDataGameMode.GameUI 的 ShowDefaultLoading / DefaultLoadingUI 并创建启动 Loading。不要为了关闭或换肤在 OnGameDataInitialization 中回写 ScopeData,也不要同时注册另一套相同的启动遮罩。

兼容边界:旧项目缺少新字段时仍显示历史默认样式;DefaultLoadingUI 为空时使用兼容默认配置;ShowDefaultLoading = false 只关闭框架自动创建的启动 Loading,不影响项目代码主动创建的临时 Loading。

依赖地图内容时等待 Scene.Loaded / OnLoadEnd,不要把 GameStart 当作场景加载完成。原生 App 启动图或平台 splash screen 不属于项目内 GameUI,不能用 LoadingUI 解决。

运行时排版验证 ​

当编辑器 MCP 提供 runtime_call_tool,并且用户已经启动普通调试、调试不编译或纯客户端调试时,AI 助手应优先使用 Runtime MCP 获取实时画面和 UI 控件数据,再决定如何调整布局。

推荐闭环:

  1. 调用 debug.capture_screenshot 保存当前客户端截图,确认真实视觉状态。一般布局检查可传 maxWidth: 1920, maxHeight: 1080 或 maxWidth: 1280, maxHeight: 720 限制图片尺寸;需要辨认细小文字或像素细节时保留原始分辨率。
  2. 调用 ui.snapshot 获取 GameUI 控件树、文本、可见性和 rect_px。
  3. 用 ui.find 查找目标按钮、标签、面板或输入框。
  4. 用 ui.get_rect 获取目标控件的屏幕像素坐标和尺寸。
  5. 根据截图与坐标调整 .Size()、.Margin()、.Padding()、.Align*()、.Flow*()、.WidthGrow() 等布局代码。
  6. 重新启动调试或刷新客户端后再次截图和查询,确认排版已符合预期。

不要只靠猜测坐标或用户口述来修 UI。若截图和 ui.snapshot 不一致,优先相信截图中的最终视觉结果,再用控件树定位可能的布局来源。Canvas 直接绘制的内容不一定出现在 ui.snapshot 中,此时用截图判断视觉问题,用 UI inspector 检查外围 GameUI 容器。

尺寸 ​

csharp
control.Size(200, 100)       // 宽高
control.Size(150)            // 正方形
control.Width(200)
control.Height(100)
control.AutoWidth()          // 自动宽度
control.AutoHeight()         // 自动高度
control.AutoSize()           // 自动宽高

位置 ​

csharp
control.AbsolutePosition(100, 50) // 相对父容器左上角的绝对定位
control.RelativeOffset(10, 20)    // 相对正常排版位置的偏移
control.Position(100, 50)         // 只改坐标,保留当前 PositionType;通常不应单独使用

对齐 ​

csharp
control.AlignLeft() / .AlignRight() / .AlignTop() / .AlignMiddle() / .AlignBottom()
control.Center()
control.StretchHorizontal() / .StretchVertical() / .Stretch()

边距 ​

csharp
control.Margin(10)           // 四边均匀
control.Margin(20, 10)       // 水平, 垂直
control.Margin(10, 5, 10, 5) // 左, 上, 右, 下
control.Padding(15)          // 同上模式

流式布局 ​

csharp
control.FlowHorizontal() / .FlowVertical()
control.ContentAlignHorizontal(HorizontalContentAlignment.Left)
control.ContentAlignVertical(VerticalContentAlignment.Top)
control.ContentCenter()

// Flexbox 风格(基于 FlowOrientation 智能选择轴向)
control.JustifySpaceBetween()    // 主轴 space-between
control.JustifySpaceAround()     // 主轴 space-around
control.AlignSpaceBetween()      // 交叉轴 space-between
control.AlignSpaceAround()       // 交叉轴 space-around
control.JustifyStretch()         // 主轴拉伸
control.AlignStretch()           // 交叉轴拉伸

// 组合
control.HorizontalSpread()       // = FlowHorizontal + JustifySpaceBetween
control.VerticalSpread()         // = FlowVertical + JustifySpaceBetween

// Flex 增长/收缩
control.WidthGrow(1.0f) / .HeightGrow(0.5f) / .GrowRatio(1, 2)
control.WidthShrink(0.5f) / .HeightShrink(0.3f) / .ShrinkRatio(0.5, 0.5)
control.FlexBasis(100, 50) / .FlexBasisWidth(100) / .FlexBasisHeight(50)

快速堆叠 ​

csharp
control.VStack(spacing: 10)  // 垂直堆叠
control.HStack(spacing: 15)  // 水平堆叠

控件属性 ​

csharp
control.Visible(true) / .Hidden()
control.Enabled(false) / .Disabled()
control.DataContext(data)
control.Background(Color.Blue) / .Background(brush)
control.Opacity(0.8f)

文本控件 ​

先按项目设计分辨率和目标窗口选择字号,详细规则见 UI 字号与可读性。

  • FontSize 是设计坐标单位,会随整个画布缩放;默认 1920×1080 画布在 1280×720 窗口中,16 只对应约 10.7 屏幕像素。不要照搬 App 的 12–16 DIP/sp 正文习惯。
  • 默认画布的桌面 720p 起点:正文/按钮 24–28,辅助文字 20–24,标题 36–48。按最小目标窗口和实际移动设备截图调整,不把此范围当成所有平台的硬编码标准。
  • 不依赖兼容默认字号或 DesignTokens.BaseFontSize 保证正文可读;不要再手动乘 DevicePixelRatio。放大字号时同步调整框体和间距,长说明用受约束的宽度、换行与自动高度。
  • 验收必须覆盖短文本更新为长文本;不要通过 Shrink 或改全局设计分辨率掩盖小字或溢出。
csharp
label.Text("Hello").TextColor(Color.Red).FontSize(26).Bold().Italic()
button.Text("按钮").TextColor(Color.White).FontSize(26).Bold()

需要在一个 Label 中混排粗体、颜色、换行或局部可点击文字时,使用 Label.Text 富文本标签和 Label.OnLinkClicked,并先阅读 label-rich-text。

TextWrap 已开但不换行或被裁剪 ​

TextWrap 控制文字换行,不建立 Label 的宽度约束。父面板有固定宽度,也不保证 AutoWidth 子 Label 在测量时收到该宽度;自然宽度可能仍按整段文本测量。详细示例和排查见流式布局指南。

  • 在有限宽度的垂直流式容器中,优先用 .AutoWidth().StretchHorizontal().AutoHeight().TextWrap();AutoWidth 清除模板的显式宽度,横向拉伸才可在该容器的交叉轴上生效。固定文本列也可用 .Width(...).AutoHeight().TextWrap()。不要把横向拉伸套用到所有布局方向。
  • 完整说明用换行与自动高度,父容器应能增高或滚动;固定高度可能已经换行但把后续行裁掉。TextTrimming.Clip 可作绘制边界兜底,不能建立宽度约束,也不能代替多行空间。标题/列表按用途选择 Ellipsis,短计分牌才考虑 Shrink;不要手工截字或插入换行来掩盖布局问题。
  • 用 ui.find、ui.snapshot / ui.get_rect 核对目标 Label 与父内容区的 rect_px,考虑 Padding/Margin 与画布缩放。不要把设计坐标的 Width/Padding 直接与屏幕像素相减。截图同时用于判断实际换行与裁剪;尺寸本身不能证明没有引擎 bug。
  • 存在多个 Gameplay 时按 Runtime 路由说明 发现并固定目标,再进行整条 UI 检查。重新开局后重新发现目标。调试启动和 DLL 部署沿用项目的调试工具流程,不为仅客户端 UI 改动强制替换两端 DLL。
  • 验收短文本 → 超出可用宽度的长文本 → 短文本:宽度保持约束,高度按内容增减(考虑 MinHeight/固定高度等显式限制),父布局同步更新。不要只验证启动文案。
  • 如果实际有限宽度、换行和高度策略已正确,却仍不换行或动态更新后尺寸不变,保留复现与运行时数据,继续检查模板覆盖、控件类型、富文本、字体度量及布局失效通知;此时仍可能是实现 bug,不要通过每帧强制刷新绕过。

自定义字体 ​

Label.Font / Input.Font / TextButton.Font 传的是字体 family,不是 .ttf / .otf 文件路径。自定义字体不生效时,优先读取 ui-custom-font。

项目私有字体示例:

text
res/font/my_title/my_title.ttf
ref/fontref.txt: font/my_title
csharp
#if CLIENT
var label = UI.Label("标题");
label.Font = "font/my_title"u8;
label.FontSize = 32;
#endif

新增或修改 ref/fontref.txt 后需要重新进入地图。当前版本先手写维护字体 ref;未来可能会有类似资源收集工具的字体 ref 自动收集工具。

容器 ​

csharp
container.AddChild(child).AddChildren(child1, child2, child3)

页面级界面优先使用 UI.Page() 作为根容器:

csharp
var page = UI.Page(
    UI.ScrollableVStack(16,
        UI.Title("设置"),
        UI.Label("这里放页面内容")
    ).FillParent()
).AddToRoot();
  • Page 天生占满 viewport,不需要再调用 FullScreen()。
  • 需要滚动时,在 Page 内部放 ScrollableVStack、ScrollPanel 等可滚动容器。
  • FillParent() 仍然用于页面内部的嵌套容器,而不是定义页面本身的语义。

事件 ​

csharp
button.OnClick((sender, e) => { /* 逻辑 */ })
button.OnClick(() => { /* 简化 */ })

外观样式 ​

csharp
control.CornerRadius(8)
control.ZIndex(10)
control.Opacity(0.8f)
control.ClipContent()        // 裁剪超出内容
control.NoClip()             // 允许内容溢出
control.MinWidth(100).MaxWidth(300).MinHeight(50).MaxHeight(200)

图片翻转限制 ​

ImageFlipX、ImageFlipY 以及 .Flip()、.FlipX()、.FlipY() 只对支持图片渲染的控件生效。不要对 PanelScrollable 使用这些属性或扩展方法:它的底层滚动控件不支持图片翻转,扩展方法会记录警告。需要翻转图片时请创建普通 GameUI.Control.Primitive.Panel,例如 new Panel { Image = "icons/arrow.png", ImageFlipX = true }。

UI.Panel() 是便捷构建器,当前返回 PanelScrollable(并关闭滚动);因此需要图片翻转时不要把 UI.Panel() 当作普通 Panel,应显式创建 new Panel()。

UI 序列帧 ​

加载动画、按钮光效、头像框、面板装饰等简单 UI 序列帧优先使用 Sprites 控件。普通单图 sprite sheet 要配置 .SpriteSheet(frameWidth, frameHeight, frameCount, framesPerRow);文件夹序列通常只需要设置 .Image(...)、播放速度和 Play()。

需要 2D 角色、命名动画、图层、命中框或 Canvas 游戏循环时,改用 AnimatedImageSource + CanvasAnimated。详细选型见 ui-sprite-animation。

响应式设计 ​

csharp
control.ResponsiveWidth(100, 300)           // min, max
control.ResponsiveHeight(50, 150)
control.ResponsiveSize(100, 300, 50, 150)   // wMin, wMax, hMin, hMax
label.ResponsiveFontSize(12, 24, 1.2f)     // min, max, multiplier
control.ResponsiveSpacing(8, 24)
control.ResponsivePadding(12, 32)
control.ResponsiveContainer(ResponsiveContainerSize.Standard)
button.ResponsiveButton(ResponsiveButtonSize.Medium)
container.ResponsiveOrientation(Orientation.Horizontal, Orientation.Vertical)
control.ResponsiveVisibility(ResponsiveVisibility.MediumAndUp)

屏幕尺寸变化与 viewport 事件 ​

需要在窗口缩放、屏幕旋转、DPI 或移动端安全区变化后重新布局时,优先订阅 ScreenViewport.Primary.OnViewportChanged。该事件在 WidthPx、HeightPx、Size、 DisplayOrientations、DevicePixelRatio、SafeZonePadding 和 GlobalOffsetPx 完成更新后触发; 同尺寸的 Surface 重建也会经过这条通知链路。

csharp
#if CLIENT
var viewport = ScreenViewport.Primary;

void RefreshLayout()
{
    var contentWidth = viewport.Size.Width;
    var safe = viewport.SafeZonePadding;

    // 更新已有控件,不要在每次回调中重复创建整棵 UI 树。
    contentPanel.Padding = new Thickness(safe.Left, safe.Top, safe.Right, safe.Bottom);
    contentPanel.Width = contentWidth;
}

viewport.OnViewportChanged += RefreshLayout;
RefreshLayout(); // 首次订阅后主动按当前状态布局
#endif

使用边界:

  • 只关心物理像素宽高时可订阅 OnSizeChanged;只关心横竖屏时可订阅 OnOrientationChanged。需要保证整体布局正确时使用统一的 OnViewportChanged。
  • 回调中直接读取 viewport 当前属性,不要依赖事件参数中的旧尺寸;框架会先更新状态,再触发事件。
  • 通过事件更新已有控件,保持操作幂等;页面根优先使用 UI.Page() / .FillParent() 和响应式布局,避免把固定像素坐标当作全屏布局方案。
  • 同尺寸 Surface 重建通知从客户端能力版本 4 开始可用。旧客户端仍会通知实际 drawable 尺寸变化,但不会保证同尺寸重建单独触发回调。
  • 页面或系统销毁时解除订阅,避免长期持有已销毁控件;如果同一页面会重复进入,确保订阅和解除订阅成对出现。

高级布局 ​

csharp
// 网格
var grid = TrueGrid(3, rowSpacing: 8, columnSpacing: 12, child1, child2, ...);
var autoGrid = SimpleGrid(10, item1, item2, ...);

// 语义化组件
control.Card(padding: 20, radius: 8, elevation: 2)
button.Button(height: 44, padding: 16, radius: 4)
input.Input(height: 40, width: 280, padding: 12)
item.ListItem(height: 48, padding: 16)

// 文本样式
label.Title(fontSize: 24, margin: 16)
label.Subtitle(fontSize: 18, margin: 12)
label.Body(fontSize: 16, margin: 8)
label.Caption(fontSize: 12, margin: 4)

静态构建器 ​

csharp
var page = UI.Page();
var panel = UI.Panel();
var button = UI.Button();
var label = UI.Label("Hello");

var vstack = UI.VStack(spacing: 10,
    UI.Label("标题"),
    UI.Button(),
    UI.Label("底部")
);

var hstack = UI.HStack(spacing: 15, UI.Button(), UI.Label("说明"));
var centered = CenterContainer(UI.Label("居中文本"));

// 预定义样式
UI.Title("标题", size: 24)
UI.Subtitle("副标题", fontSize: 18)
UI.Primary("主要按钮")
UI.Secondary("次要按钮")
UI.Button("按钮")

内置颜色 ​

csharp
UI.Colors.Primary / .Secondary / .Success / .Warning / .Error
UI.Colors.Background / .Surface
UI.Colors.OnPrimary / .OnSurface / .OnBackground

使用示例 ​

登录界面 ​

csharp
var loginScreen = Page(
    VStack(20,
        Label("欢迎登录")
            .FontSize(32).Bold().Center().Margin(0, 50, 0, 30),
        VStack(15,
            Label("用户名").Background(Colors.Surface).Padding(15, 10).StretchHorizontal().Height(40),
            Label("密码").Background(Colors.Surface).Padding(15, 10).StretchHorizontal().Height(40)
        ).Margin(40, 0),
        HStack(20,
            Button().Size(120, 40).Background(Colors.Secondary),
            Button().Size(120, 40).Background(Colors.Primary)
        ).Center().Margin(0, 30, 0, 0)
    ).FillParent().Background(Colors.Background)
).AddToRoot();

工具栏 ​

csharp
var toolbar = Panel().Size(400, 50)
    .HorizontalSpread()
    .Add(Button("新建"), Button("编辑"), Button("删除"), Button("设置"));

更多详细信息 ​

更多 API 与组合示例见 reference.md。