Skip to content

流式布局扩展语法教程 ​

字号、宽高和间距使用 UI 设计坐标,随画布缩放。正文不要直接套用普通 App 的 12–16 DIP/sp;选择字号、放大框体与验收不同窗口前,先读 UI 字号与可读性。

🚀 快速开始 ​

流式扩展提供链式配置控件属性的写法。下列示例中的设计尺寸应按项目画布与显示需求调整。

什么是流式布局? ​

流式布局是一种链式调用的UI构建方式,每个方法都返回控件本身,支持连续调用多个方法:

csharp
// 属性赋值
var button = new Button();
button.Width = 120;
button.Height = 40;
button.HorizontalAlignment = HorizontalAlignment.Center;
button.VerticalAlignment = VerticalAlignment.Center;
button.Background = new SolidColorBrush(Colors.Primary);

// 对应的流式写法
var button = Button()
    .Size(120, 40)
    .Center()
    .Background(Colors.Primary);

📚 第一章:基础语法 ​

1.1 导入命名空间 ​

csharp
using GameUI.Control.Extensions;

1.2 创建基础控件 ​

csharp
// 创建控件
var panel = Panel();
var label = Label();
var button = Button();

// 创建带文本的控件
var title = Label("欢迎使用WasiCore");
var submitButton = TextButton("提交");

1.3 设置基础属性 ​

csharp
// 尺寸设置
control.Size(200, 100)          // 宽200,高100
        .Width(200)             // 只设置宽度
        .Height(100);           // 只设置高度

// 位置设置
control.AbsolutePosition(50, 30); // 相对父容器左上角的绝对位置
control.RelativeOffset(0, -20);   // 相对正常排版位置向上偏移

// 可见性和状态
control.Show()                  // 显示
        .Hide()                 // 隐藏
        .Enable()               // 启用
        .Disable();             // 禁用

1.4 链式调用示例 ​

csharp
var welcomeLabel = Label("欢迎来到游戏世界!")
    .FontSize(24)               // 字体大小
    .Bold()                     // 粗体
    .TextColor(Colors.Primary)  // 文字颜色
    .Center()                   // 居中对齐
    .Padding(20)                // 内边距
    .Show();                    // 显示

📐 第二章:布局与对齐 ​

2.1 尺寸控制 ​

csharp
// 固定尺寸
control.Size(200, 100);         // 固定宽高
control.Square(150);            // 正方形150x150

// 自动尺寸(根据内容自适应)
control.AutoWidth()             // 宽度自动适应内容
        .AutoHeight()           // 高度自动适应内容
        .AutoSize();            // 宽高都自动

// 最小/最大尺寸
control.MinWidth(100)           // 最小宽度
        .MaxWidth(500)          // 最大宽度
        .MinHeight(50)          // 最小高度
        .MaxHeight(300);        // 最大高度

2.2 对齐方式 ​

csharp
// 水平对齐
control.AlignLeft()             // 左对齐
        .Center()               // 水平+垂直居中
        .AlignRight();          // 右对齐

// 垂直对齐  
control.AlignTop()              // 顶部对齐
        .Center()               // 水平+垂直居中
        .AlignBottom();         // 底部对齐

// 拉伸对齐
control.StretchHorizontal()     // 水平拉伸
        .StretchVertical()      // 垂直拉伸
        .Stretch();             // 双向拉伸

// 居中快捷方式
control.Center();               // 水平+垂直居中

当前游戏模式引用的 GameDataGameUI 决定新建控件未显式配置时的内容对齐默认值:

  • UseTopLeftContentAlignmentDefaults = false 或字段缺失:兼容旧项目,默认 Center/Center。
  • UseTopLeftContentAlignmentDefaults = true:默认 Left/Top;新版项目模板主动开启。

该设置只影响之后创建的控件,不修改 PositionType,也不覆盖代码或数编模板里的显式对齐值。 可复用地图库应继续显式设置关键对齐,避免行为随宿主项目配置变化。

2.3 边距设置 ​

csharp
// 统一边距
control.Margin(10)              // 四周边距10
        .Padding(15);           // 四周内边距15

// 水平/垂直边距
control.Margin(20, 10)          // 水平20,垂直10
        .Padding(15, 5);        // 水平15,垂直5

// 四个方向边距
control.Margin(10, 5, 10, 5);   // 左、上、右、下

🏗️ 第三章:容器布局 ​

3.1 垂直堆叠 (VStack) ​

csharp
// 基础垂直堆叠
var vertical = VStack(16,       // 间距16像素
    Label("标题"),
    Label("内容1"),
    Label("内容2"),
    Button("确定")
);

// 复杂垂直布局
var loginForm = VStack(24,
    Title("用户登录", 32),
    
    VStack(16,                  // 表单字段分组
        Label("用户名"),
        Input().Padding(12, 8),
        
        Label("密码"),
        Input().Padding(12, 8)
    ),
    
    Button("登录")
        .Size(200, 44)
        .Center()
);

3.2 水平堆叠 (HStack) ​

csharp
// 基础水平堆叠
var horizontal = HStack(12,     // 间距12像素
    Button("取消"),
    Button("确定"),
    Button("重置")
);

// 工具栏示例
var toolbar = HStack(8,
    Button("保存").Size(80, 32),
    Button("撤销").Size(80, 32),
    Spacer(),                   // 弹性间距
    Label("状态:已保存"),
    Button("设置").Size(60, 32)
);

3.3 嵌套布局 ​

csharp
// 复杂嵌套布局
var mainLayout = VStack(20,
    // 头部区域
    HStack(16,
        Label("应用标题").FontSize(24).Bold(),
        Spacer(),
        Button("用户")
    ).Padding(20, 16),
    
    HDivider(),                 // 分隔线
    
    // 主要内容区域
    HStack(0,
        // 左侧边栏
        VStack(12,
            Button("首页"),
            Button("设置"),
            Button("帮助")
        ).Padding(16).Width(200),
        
        VDivider(),             // 垂直分隔线
        
        // 右侧内容
        VStack(16,
            Label("主要内容区域"),
            Panel().Stretch()   // 占满剩余空间
        ).Padding(20).Stretch()
    ).Stretch()
);

🎨 第四章:样式与外观 ​

4.1 颜色与背景 ​

csharp
// 背景颜色
control.Background(Colors.Primary)      // 使用预设颜色
        .Background(Color.Blue)         // 使用系统颜色
        .Background(Color.FromArgb(255, 100, 150, 200)); // 自定义颜色

// 文字颜色
label.TextColor(Colors.OnPrimary)       // 主色调上的文字色
     .TextColor(Color.White);           // 白色文字

4.2 字体样式 ​

csharp
// 字体设置
label.FontSize(18)              // 字体大小
     .Bold()                    // 粗体
     .Italic()                  // 斜体
     .TextColor(Colors.OnSurface); // 文字颜色

// 预设文本样式
Title("页面标题")               // 预设标题样式
Subtitle("副标题")              // 预设副标题样式  
Body("正文内容")                // 预设正文样式
Caption("说明文字")             // 预设说明文字样式

4.3 视觉效果 ​

csharp
// 圆角
control.CornerRadius(8)         // 8像素圆角
        .CornerRadius(12);      // 12像素圆角

// 透明度
control.Opacity(0.8f)           // 80%不透明度
        .Opacity(0.5f);         // 50%不透明度

🚀 第五章:高级功能 ​

5.1 Flexbox布局 ​

csharp
// Flexbox API - CSS标准命名
// 增长控制 - 控制元素如何占用剩余空间
control.WidthGrow(1.0f)         // 宽度增长比例
       .HeightGrow(0.5f)        // 高度增长比例
       .GrowRatio(1, 2)         // 同时设置宽高增长比例

// 收缩控制 - 控制元素在空间不足时如何收缩
control.WidthShrink(0.5f)       // 宽度收缩比例
       .HeightShrink(0.3f)      // 高度收缩比例
       .ShrinkRatio(0.5, 0.5)   // 同时设置宽高收缩比例

// Flex基础尺寸 - 设置元素的初始尺寸
control.FlexBasis(100, 50)      // 设置基础宽高
       .FlexBasisWidth(100)     // 仅设置基础宽度
       .FlexBasisHeight(50)     // 仅设置基础高度

// 实际应用示例
HStack(8,
    Panel().Background(DesignColors.Primary).Size(100, 50),     // 固定尺寸
    Panel().Background(DesignColors.Secondary).GrowRatio(1, 1), // flex: 1 1 auto
    Panel().Background(DesignColors.Success).GrowRatio(2, 1)    // flex: 2 1 auto
).Width(400);

5.2 响应式布局 ​

csharp
// 页面根(推荐)
UI.Page()
  .Background(Colors.Background)
  .Show()
  .AddToRoot();

// 普通容器占满父容器
panel.Stretch()                 // 占满整个父容器
     .Show();

// 填满父容器(嵌套控件)
contentPanel.FillParent()       // 填满父容器(适用于嵌套控件)
            .Background(Colors.Surface);

// 响应式尺寸 - 基于断点系统自动调整
panel.ResponsiveWidth(320, 800)     // 最小320px,最大800px
     .ResponsiveHeight(240, 600)    // 最小240px,最大600px
     .Center();                     // 居中显示

// 响应式容器 - 预设尺寸
panel.ResponsiveContainer(ResponsiveContainerSize.Standard)  // 标准容器
     .ResponsiveContainer(ResponsiveContainerSize.Compact);  // 紧凑容器

// 响应式可见性 - 基于断点控制显示
sidebar.ResponsiveVisibility(ResponsiveVisibility.MediumAndUp); // 中等屏幕及以上显示
mobileMenu.ResponsiveVisibility(ResponsiveVisibility.SmallOnly); // 仅小屏幕显示

5.3 滚动容器 ​

csharp
// 可滚动的垂直堆叠
var scrollContent = ScrollableVStack(8,
    ...GetListItems()       // 获取列表项
).Height(300);

// 可滚动的水平堆叠
var horizontalScroll = ScrollableHStack(8,
    ...GetImages()          // 获取图片列表
).Width(400);

5.4 条件布局 ​

csharp
// 根据条件显示不同内容
var dynamicPanel = VStack(16,
    Title("动态内容"),
    
    // 条件显示
    isLoggedIn ? 
        VStack(8,
            Label($"欢迎,{username}"),
            Button("退出登录")
        ) :
        VStack(8,
            Label("请先登录"),
            Button("立即登录")
        ),
    
    Button("继续")
);

💡 第六章:实用模式 ​

6.1 卡片布局 ​

csharp
// 信息卡片
var infoCard = Panel()
    .MinHeight(120)
    .Padding(24)
    .CornerRadius(12)
    .Background(Colors.Surface)
    .Add(
        VStack(12,
            Title("卡片标题", 18),
            Body("这是卡片的详细内容..."),
            HStack(8,
                Primary("操作1"),
                Secondary("操作2")
            )
        )
    );

6.2 对话框布局 ​

csharp
// 确认对话框
var confirmDialog = Panel()
    .Size(400, 200)
    .Center()
    .CornerRadius(16)
    .Background(Colors.Surface)
    .Add(
        VStack(24,
            Title("确认操作", 20).Center(),
            Body("您确定要执行此操作吗?").Center(),
            HStack(16,
                Secondary("取消").Click(() => CloseDialog()),
                Primary("确定").Click(() => ExecuteAction())
            ).Center()
        ).Padding(32)
    );

6.3 列表项布局 ​

csharp
// 标准列表项
Panel CreateListItem(string title, string subtitle, string action)
{
    return Panel()
        .Padding(20, 16)
        .Background(Colors.Surface)
        .Add(
            HStack(16,
                VStack(4,
                    Body(title, 16).Bold(),
                    Caption(subtitle)
                ),
                Spacer(),
                Secondary(action).FontSize(14)
            )
        );
}

// 使用示例
var itemList = VStack(4,
    CreateListItem("项目1", "详细描述1", "编辑"),
    CreateListItem("项目2", "详细描述2", "删除"),
    CreateListItem("项目3", "详细描述3", "查看")
);

布局与控件生命周期 ​

流式扩展与对象初始化器最终配置同一套控件属性,可以混用。是否换行、对齐或重叠取决于尺寸约束、布局方向和父子关系,链式语法本身不保证布局正确。

  • 按用途选择固定尺寸、自动尺寸、拉伸和响应式规则;设计坐标中的固定尺寸也是有效配置。适配关系见 UI 尺寸与适配入口。
  • 使用 TextWrap 时仍要建立 Label 的有限宽度,并为多行提供高度或滚动空间,见下方常见问题。
  • 复用控件时检查当前父节点、状态和事件;不再使用时调用 Destroy()。RemoveFromParent() / ClearChildren() 只摘树,不能当作资源释放。
  • 屏幕变化时更新已有控件;订阅事件在页面销毁时解除,见屏幕变化示例。

🛠️ 第八章:常见问题 ​

Q1: 如何让控件填满父容器? ​

csharp
// 水平填满
control.StretchHorizontal();

// 垂直填满  
control.StretchVertical();

// 完全填满父容器
control.Stretch();

// 页面根占满视口(推荐)
Page().AddToRoot();

// 嵌套控件填满父容器
control.FillParent();

Q2: 如何创建等宽的按钮? ​

csharp
var buttonRow = HStack(12,
    Primary("按钮1").MinWidth(100),
    Secondary("按钮2").MinWidth(100),
    Success("按钮3").MinWidth(100)
);

Q3: 如何处理溢出内容? ​

csharp
// 使用滚动容器
var scrollableContent = ScrollableVStack(8,
    ...manyItems
).Height(300);  // 限制高度,超出部分可滚动

Q3.1: Label 设置 TextWrap 后仍不换行? ​

TextWrap 允许文字在给定宽度内换行,但不会替 Label 设置最大宽度。父面板的宽度有限,也不代表它会自动把该宽度作为每个子控件的测量宽度。如果 Label 仍按文字的自然宽度测量,一整段文字可以形成一个超出父面板的单行 Label。

对固定宽度、向下增高的说明面板,可以使用:

csharp
#if CLIENT
using GameUI.Control.Extensions;
using static GameUI.Control.Extensions.UI;

var panel = Panel().Width(440).AutoHeight().Padding(16).FlowVertical();
var message = Label("WASD / 摇杆移动")
    .FontSize(24)
    .AutoWidth().StretchHorizontal().AutoHeight().TextWrap()
    .TextTrimming(GameUI.Control.Enum.TextTrimming.Clip);
message.Parent = panel;

// panel 按页面结构加入可视树;之后可更新同一 Label 的文本。
message.Text = "西巷民居:沿着石板路穿过小镇,经过水井和花田,寻找藏在屋舍旁的苔光水晶。";
#endif

这组配置各有用途:

设置用途与边界
父容器 Width + FlowVertical提供有限宽度,沿垂直方向排列子控件
Label AutoWidth + StretchHorizontal清除显式宽度,让垂直流式布局沿交叉轴把 Label 拉伸到可用内宽
Label AutoHeight + TextWrap保持字号,换行后根据内容计算高度
父容器 AutoHeight随说明增高;受页面高度限制时应改用滚动等合适布局
TextTrimming.Clip防止文字绘制越界;不能代替宽度约束或多行空间

父容器的 Padding 和子控件的 Margin 都会占用可用空间。StretchHorizontal 不是所有容器的统一解法:横向流式容器中的水平轴是主轴,应按列宽或 flex 分配空间;已有固定宽度的文本列可用 .Width(...).AutoHeight().TextWrap()。不要在拉伸之后再次设置固定宽度,或用 AutoSize 清掉需要保持的高度/宽度策略。

排查时先通过 ui.find 找到实际 Label,用 ui.snapshot / ui.get_rect 核对 Label 和父容器的屏幕矩形,再配合截图判断文字是否换行、是否被裁剪。rect_px 是屏幕像素,Width/Padding 通常是设计坐标;比较内容区时先统一单位,详见 UI 字号与画布缩放。多模拟客户端先按 Runtime MCP 目标路由 选择同一目标,调试重新开局后重新发现。

Label 宽度超过父内容区时,应优先检查约束与测量过程,但不能仅凭尺寸就断言引擎没有 bug。反过来,宽度正确也不等于文字一定完整显示:Label 或父容器的固定高度、Min/Max 尺寸、裁剪策略都可能影响后续行。检查实际模板是否覆盖 TextWrap、宽高或对齐,再检查富文本和字体。

动态文字需要验证三个阶段:短文本、足够长而需要换行的文本、重新变短的文本。固定宽度应保持,高度应在正常布局更新后按内容增减(显式最小高度等限制除外),后续控件和父面板应同步排版。初始短文字正常,不能证明后续长文字已正确处理。当前文本更新链路会更新文字布局,并对自动尺寸的 Label 标记布局失效;如果已有正确约束却不更新,应保留最小复现继续排查实现,不要默认添加每帧强制刷新。

完整说明优先保持字号并换行;单行名称按用途选择省略号。手动插入换行适用于作者明确指定的段落,不能修复缺失的宽度约束。

Q4: 如何创建响应式间距? ​

csharp
// 使用屏幕比例计算间距
var spacing = (int)(ScreenViewport.Primary.Size.Width * 0.02f); // 屏幕宽度的2%
var layout = VStack(spacing, ...items);

📐 第九章:断点系统与响应式设计 ​

9.1 断点系统 ​

WasiCore使用五级断点系统,基于常见设备尺寸:

csharp
// 断点定义 (DesignTokens)
XSmall:  < 480px   // 小手机
Small:   480-768px // 大手机/小平板
Medium:  768-1024px // 平板
Large:   1024-1440px // 桌面
XLarge:  > 1440px   // 大屏桌面

9.2 响应式API详解 ​

csharp
// 基础响应式尺寸
control.ResponsiveWidth(minWidth, maxWidth)     // 响应式宽度
       .ResponsiveHeight(minHeight, maxHeight)  // 响应式高度
       .ResponsiveSize(minW, maxW, minH, maxH)  // 同时设置宽高

// 响应式字体和间距
label.ResponsiveFontSize(12, 24, 1.2f)  // 最小12px,最大24px,倍数1.2
control.ResponsiveSpacing(8, 24)        // 响应式外边距
       .ResponsivePadding(12, 32)       // 响应式内边距

// 响应式容器尺寸
control.ResponsiveContainer(ResponsiveContainerSize.Standard)
       .ResponsiveContainer(ResponsiveContainerSize.Compact)
       .ResponsiveContainer(ResponsiveContainerSize.Comfortable)

// 响应式可见性控制
control.ResponsiveVisibility(ResponsiveVisibility.MediumAndUp)  // 中等屏幕及以上
       .ResponsiveVisibility(ResponsiveVisibility.SmallOnly)    // 仅小屏幕
       .ResponsiveVisibility(ResponsiveVisibility.LargeAndDown) // 大屏幕及以下

// 响应式布局方向
container.ResponsiveOrientation(
    Orientation.Horizontal,  // 横屏/大屏使用水平布局
    Orientation.Vertical     // 竖屏/小屏使用垂直布局
)

9.3 实际应用示例 ​

csharp
// 响应式导航栏
var navbar = HStack(16,
    Logo().ResponsiveWidth(120, 200),  // Logo大小随屏幕调整
    
    // 导航菜单 - 大屏显示,小屏隐藏
    HStack(24,
        NavButton("首页"),
        NavButton("产品"),
        NavButton("关于")
    ).ResponsiveVisibility(ResponsiveVisibility.MediumAndUp),
    
    // 汉堡菜单 - 小屏显示,大屏隐藏
    MenuButton()
        .ResponsiveVisibility(ResponsiveVisibility.SmallAndDown)
        .Size(32, 32)
);

// 响应式卡片网格
var cardGrid = TrueGrid(
    GetResponsiveGridColumns(),  // 根据屏幕尺寸自动计算列数
    rowSpacing: 16,
    columnSpacing: 16,
    ...GetCards()
);

// 响应式侧边栏布局
var layout = HStack(0,
    // 侧边栏 - 大屏显示,小屏隐藏
    Sidebar()
        .ResponsiveWidth(200, 300)
        .ResponsiveVisibility(ResponsiveVisibility.LargeAndUp),
    
    // 主内容区 - 自适应剩余空间
    MainContent()
        .GrowRatio(1, 1)
        .ResponsivePadding(16, 32)
);

9.4 屏幕变化时的布局更新 ​

窗口尺寸变化并不一定意味着 drawable 宽高变化:屏幕旋转、设备像素比、安全区或移动端 Surface 重建都可能要求重新布局。因此需要整体刷新界面时,使用 ScreenViewport.Primary.OnViewportChanged,并在回调中读取更新后的 viewport 状态。

csharp
#if CLIENT
var viewport = ScreenViewport.Primary;

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

    rootPanel.Padding = new Thickness(safe.Left, safe.Top, safe.Right, safe.Bottom);
    rootPanel.Width = visibleSize.Width;
    rootPanel.Height = visibleSize.Height;
}

viewport.OnViewportChanged += RefreshLayout;
RefreshLayout();
#endif

最佳实践:

  • OnViewportChanged 适合统一处理尺寸、方向、DPI、安全区和裁切偏移;只关心单一维度时再使用 OnSizeChanged 或 OnOrientationChanged。
  • 回调触发时 viewport 属性已经更新完成,应直接读取 Size、DisplayOrientations、DevicePixelRatio 和 SafeZonePadding,不要缓存初始化时的屏幕尺寸。
  • 更新已有控件并保持回调幂等,不要在每次屏幕变化时重复创建整页 UI;页面根优先使用 Page()、FillParent()、Stretch() 和响应式扩展。
  • 同尺寸 Surface 重建通知需要客户端能力版本 4。旧客户端仍兼容实际 drawable 尺寸变化,但不能依赖它们为同尺寸重建单独抛出事件。
  • 页面销毁或重复进入时成对管理事件订阅,避免回调继续访问已销毁控件。

🔗 第十章:相关资源 ​

参考文档 ​

常用扩展方法速查 ​

基础属性 ​

csharp
.Show() / .Hide()           // 显示/隐藏
.Enable() / .Disable()      // 启用/禁用
.Size(w, h) / .Width(w) / .Height(h)  // 尺寸
.AbsolutePosition(x, y)     // 父容器左上角绝对位置
.RelativeOffset(x, y)       // 相对正常排版位置的偏移
.Position(x, y)             // 保留当前 PositionType 的底层坐标设置
.Background(color)          // 背景色
.Opacity(value)             // 透明度
.CornerRadius(radius)       // 圆角

布局对齐 ​

csharp
.Center()                   // 居中
.AlignLeft() / .AlignRight()  // 水平对齐
.AlignTop() / .AlignBottom()  // 垂直对齐
.Stretch() / .StretchHorizontal() / .StretchVertical()  // 拉伸
.FullScreen()               // 全屏(根级控件)
.FillParent()               // 填满父容器(通用)
.Margin(all) / .Padding(all)    // 边距
.AutoWidth() / .AutoHeight() / .AutoSize()  // 自动尺寸

容器创建 ​

csharp
VStack(spacing, ...items)   // 垂直堆叠
HStack(spacing, ...items)   // 水平堆叠
ScrollableVStack(spacing, ...items)  // 可滚动垂直堆叠
ScrollableHStack(spacing, ...items)  // 可滚动水平堆叠
Page()                      // 页面级根容器(占满 viewport)
Panel() / Label() / Button()  // 基础控件

预设样式 ​

csharp
Title(text, size)          // 标题
Subtitle(text, size)       // 副标题
Body(text, size)           // 正文
Caption(text, size)        // 说明文字
Primary(text) / Secondary(text)  // 预设按钮