布局系统
布局系统位于 core/layout.h,是 header-only 的纯计算模块,不依赖 OpenGL。DSL 中的 Row / Column / Stack 最终都会转换成 core::Node 进行 measure 和 layout。
容器类型
enum class LayoutType {
Row,
Column,
Stack
};
Row:子元素横向排列。Column:子元素纵向排列。Stack:子元素叠放。
DSL 写法:
ui.row("toolbar")
ui.column("content")
ui.stack("root")
对齐
enum class Align {
START,
CENTER,
END
};
每个布局节点有:
mainAlign:主轴对齐。crossAlign:交叉轴对齐。
规则:
Row主轴是 x,crossAlign控制 y。Column主轴是 y,crossAlign控制 x。Stack使用mainAlign控制 y,使用crossAlign控制 x。
DSL 写法:
.justifyContent(core::Align::CENTER)
.alignItems(core::Align::CENTER)
.align(core::Align::CENTER, core::Align::CENTER)
尺寸模式
enum class SizeMode {
Fixed,
WrapContent,
Fill
};
SizeValue:
SizeValue::fixed(240.0f)
SizeValue::wrapContent()
SizeValue::fill()
DSL 写法:
.width(240.0f)
.height(70.0f)
.size(240.0f, 70.0f)
.width(core::SizeValue::fill())
.height(core::SizeValue::wrapContent())
.fill()
.wrapContent()
当前 Fill 比较基础:会使用父级传入的 available size,没有实现 flex-grow / flex-shrink 的分配算法。
位置
布局位置是逻辑坐标,原点在左上角,y 向下。
.x(40.0f)
.y(24.0f)
.position(40.0f, 24.0f)
在 Stack 中,指定 x/y 会覆盖对应方向的自动对齐;未指定时按 align 规则放置。
在 Row / Column 中,子项位置主要由布局流决定,x/y 当前仍会进入 layout node,但推荐把显式定位主要用于 Stack。
Margin 与 Gap
Margin:
.margin(8.0f)
.margin(12.0f, 8.0f)
.margin(12.0f, 8.0f, 12.0f, 8.0f)
Gap:
.gap(12.0f)
.spacing(12.0f)
gap/spacing 只对 Row 和 Column 的子元素间距有意义。
Measure / Layout 流程
Runtime 每次 compose 后会调用:
ui.layout(screen);
内部流程:
1. 把 DSL Element 树转换为 core::Node 树。 2. 调用 measure(availableWidth, availableHeight) 计算尺寸。 3. 调用 layout(x, y) 计算最终 frame。 4. 把每个 Node::frame() 写回对应 Element::frame。
最终 frame 存在逻辑坐标中,渲染阶段再乘 dpiScale 转成 framebuffer 像素。
Row 示例
ui.row("toolbar")
.size(420.0f, 64.0f)
.gap(12.0f)
.alignItems(core::Align::CENTER)
.content([&] {
components::button(ui, "save")
.size(120.0f, 44.0f)
.text("Save")
.build();
components::button(ui, "cancel")
.size(120.0f, 44.0f)
.text("Cancel")
.build();
});
Row 会:
1. 计算所有子元素 outer width 总和。 2. 加上 gap。 3. 根据 justifyContent 决定 x 起点。 4. 根据 alignItems 决定每个子元素 y。
Column 示例
ui.column("panel.content")
.size(360.0f, 260.0f)
.gap(10.0f)
.justifyContent(core::Align::CENTER)
.alignItems(core::Align::CENTER)
.content([&] {
ui.text("title")
.size(300.0f, 42.0f)
.text("Gallery")
.fontSize(32.0f)
.build();
components::button(ui, "primary")
.size(220.0f, 58.0f)
.text("Open")
.build();
});
Column 会:
1. 计算所有子元素 outer height 总和。 2. 加上 gap。 3. 根据 justifyContent 决定 y 起点。 4. 根据 alignItems 决定每个子元素 x。
Stack 示例
ui.stack("stage")
.size(600.0f, 320.0f)
.content([&] {
ui.rect("stage.bg")
.size(600.0f, 320.0f)
.color({0.10f, 0.12f, 0.16f, 1.0f})
.build();
ui.rect("actor")
.x(active ? 420.0f : 40.0f)
.y(80.0f)
.size(120.0f, 80.0f)
.transition(0.3f)
.build();
});
Stack 会按声明顺序绘制,后声明的元素盖在前面的元素上。
与脏区渲染的关系
布局系统只计算逻辑 frame。Runtime 会用 frame 和视觉属性计算 dirty rect:
- frame 变化会标记旧区域和新区域的 union。
- shadow / blur 会扩大脏区。
- text frame / color / opacity 变化会标记文本区域。
因此写 DSL 页面时不需要手动标记脏区。
当前限制
- 没有 padding 属性;可以用内部容器和 margin 组合实现。
- 没有 min/max width/height。
- 没有 flex-grow / flex-shrink。
- 没有 wrap row / flow layout。
- 有基础矩形 clip;scroll 由
.clip()+ 内容 offset +components::scroll组合。 - 有基础 z-index;它只影响同级绘制和 topmost hit-test,不参与布局。