基础图元与文本图元
底层 OpenGL 绘制能力主要在:
core/primitive.hcore/text.hcore/text.cppcore/image.hcore/image.cppcore/dsl.hcore/dsl_runtime.h
页面和组件推荐使用 DSL,不要直接创建 primitive。core::dsl::Runtime 会把 DSL 元素同步到底层绘制对象,并统一处理 layout、事件、动画、dirty rect、framebuffer cache 和 DPI scale。
DSL 到 Primitive
| DSL 元素 | 底层对象 | 说明 |
|---|---|---|
ui.row / ui.column / ui.stack | 无绘制 primitive | 只参与布局、裁剪、z-index、事件命中和渲染遍历 |
ui.rect | RoundedRectPrimitive | 圆角矩形、颜色、渐变、边框、阴影、blur、transform |
ui.polygon | PolygonPrimitive | 多边形填充,当前 piechart 扇区和 tooltip 指针使用它 |
ui.text / ui.label | TextPrimitive | SDF 文本,支持字体、字号、换行、对齐 |
ui.image | ImagePrimitive | 本地图片、网络图片、SVG、Bing daily 图片 |
组件层只组合这些 DSL 元素。例如 components::button(ui, id) 内部仍然是 Stack + Rect + Row + Text,不是新的渲染对象。
基础类型
struct Vec2 {
float x = 0.0f;
float y = 0.0f;
};
struct Color {
float r = 1.0f;
float g = 1.0f;
float b = 1.0f;
float a = 1.0f;
};
struct Rect {
float x = 0.0f;
float y = 0.0f;
float width = 0.0f;
float height = 0.0f;
};
坐标默认是逻辑坐标,左上角为原点,x 向右,y 向下。Runtime 渲染时会乘以 dpiScale 转成 framebuffer 像素坐标。
Rect::contains(...) 用于基础矩形命中测试。当前 transform 后的 hit-test 仍按布局矩形计算,不跟随旋转和缩放后的真实形状。
视觉结构
enum class GradientDirection {
Horizontal,
Vertical
};
struct Gradient {
bool enabled = false;
Color start;
Color end;
GradientDirection direction = GradientDirection::Vertical;
};
struct Border {
float width = 0.0f;
Color color;
};
struct Shadow {
bool enabled = false;
Vec2 offset = {0.0f, 4.0f};
float blur = 8.0f;
float spread = 0.0f;
Color color = {0.0f, 0.0f, 0.0f, 0.28f};
};
struct Transform {
Vec2 translate = {0.0f, 0.0f};
Vec2 scale = {1.0f, 1.0f};
float rotate = 0.0f;
Vec2 origin = {0.5f, 0.5f};
};
这些结构由 DSL builder 写入目标状态,再由 Runtime 按 id 推进动画并同步到底层 primitive。
Rect 图元
RoundedRectPrimitive 是最常用的基础图元。
ui.rect("card")
.size(360.0f, 260.0f)
.color({0.10f, 0.12f, 0.16f, 1.0f})
.radius(18.0f)
.border(1.0f, {0.23f, 0.29f, 0.38f, 1.0f})
.shadow(24.0f, 0.0f, 10.0f, {0.0f, 0.0f, 0.0f, 0.28f})
.transition(0.18f)
.build();
支持能力:
boundscolorgradientbordershadowbluropacityradiustransformstates(normal, hover, pressed)
.states(...) 是 Runtime 维护的交互状态,不是 primitive 自己监听鼠标。它会开启 interactive,并根据 hover / pressed 插值颜色。
Polygon 图元
PolygonPrimitive 使用 GL_TRIANGLE_FAN 绘制点集。点坐标是元素局部坐标,实际渲染时会叠加元素 bounds 和 transform。
ui.polygon("tooltip.pointer")
.size(24.0f, 10.0f)
.points({
{12.0f, 0.0f},
{24.0f, 10.0f},
{0.0f, 10.0f},
})
.color({0.12f, 0.14f, 0.18f, 1.0f})
.build();
当前支持:
points(...)point(x, y)clearPoints()coloropacitytransformstates(normal, hover, pressed)
当前不支持 polygon 的渐变、边框、圆角、阴影和 blur。饼图扇区由组件把圆弧拆成多个点后交给 ui.polygon(...) 绘制。
TextPrimitive
文本图元在 core/text.h 和 core/text.cpp。它使用 3rd/stb_truetype.h 生成 glyph,并用 SDF 方式渲染。
ui.text("title")
.size(420.0f, 48.0f)
.text("Hello")
.customFont("YouSheBiaoTiHei")
.fontSize(24.0f)
.lineHeight(30.0f)
.color({1.0f, 1.0f, 1.0f, 1.0f})
.horizontalAlign(core::HorizontalAlign::Center)
.verticalAlign(core::VerticalAlign::Center)
.build();
TextStyle 当前包含:
textfontFamilyfontSizefontWeightcolormaxWidthwraphorizontalAlignverticalAlignlineHeight
文本实现会缓存字体文件数据、glyph、layout 结果和顶点数据。只有文本内容、位置、字号、换行、对齐、visual scale 等影响排版或顶点的数据变化时才会重建顶点;单纯颜色和透明度变化不需要重新排版。
ImagePrimitive
图片图元在 core/image.h 和 core/image.cpp。
ui.image("logo")
.size(120.0f, 120.0f)
.source("assets/icon.svg")
.contain()
.radius(18.0f)
.build();
支持来源:
- 本地图片路径。
assets/下的资源文件。http:///https://网络图片。- SVG 图片。
bingDaily(idx, mkt)内置 Bing 每日图。
支持 fit:
cover()/ImageFit::Covercontain()/ImageFit::Containstretch()/ImageFit::Stretch
网络图片和 Bing daily 可能存在 pending load。ImagePrimitive::consumeRemoteImageReady() 用于 Runtime 感知远程图片已准备好并触发刷新。
Shadow 与 Blur
阴影会扩大 dirty rect。Runtime 会把阴影区域纳入脏区,避免局部重绘时留下旧阴影。
ui.rect("card")
.shadow(24.0f, 0.0f, 10.0f, {0.0f, 0.0f, 0.0f, 0.28f})
.build();
blur(...) 当前是 backdrop blur,不是独立 layer blur。它依赖背后的 framebuffer cache 内容:
ui.rect("glass")
.color({0.90f, 0.96f, 1.0f, 0.24f})
.blur(18.0f)
.build();
为了避免 blur 采样到半旧半新的 cache,Runtime 会检查 dirty rect 是否触碰 blur 的保护区域;触碰时本帧会升级为 full redraw,优先保证正确性。代价是 blur 附近交互更贵,所以业务页面应谨慎使用大面积 blur。
Transform
ui.rect("actor")
.translate(20.0f, 0.0f)
.scale(1.1f)
.rotate(0.2f)
.transformOrigin(0.5f, 0.5f)
.transition(0.24f)
.animate(core::AnimProperty::Transform)
.build();
Runtime 的 dirty rect 会考虑 transform 后的外接矩形,避免旋转或缩放后残留旧区域。当前 hit-test 仍按未 transform 的布局矩形计算。
Dirty Rect 关系
Runtime 会按 id 缓存 RectInstance、PolygonInstance、TextInstance、ImageInstance。每帧更新时:
- hover / pressed 状态变化会产生 dirty rect。
- transition 动画推进会产生 dirty rect。
- frame、color、opacity、radius、border、shadow、blur、transform 等变化会产生 dirty rect。
- text frame、text color、opacity 变化会产生 dirty rect。
- image frame、tint、opacity、radius、transform 变化会产生 dirty rect。
- polygon frame、color、opacity、transform 变化会产生 dirty rect。
渲染时优先使用离屏 framebuffer cache + scissor 局部重绘。需要完整正确性的场景,例如窗口尺寸变化、页面重组、blur 保护区被触碰,会走 full redraw。
当前限制
- 页面和组件不应直接
new / initialize / render / destroyprimitive。 - 复杂文本排版、IME 组合态和撤销栈不属于
TextPrimitive,由上层 input 能力继续扩展。 PolygonPrimitive适合凸多边形或按 triangle fan 可正确表达的形状;复杂凹多边形需要后续三角剖分。- 圆角 clip 和复杂 overflow 规则还没有完整实现。
- transform 后的 hit-test 不跟随旋转和缩放。
- 大 blur、大阴影和大量动态图元都会扩大重绘成本,需要在组件层控制动画范围和脏区范围。