动画系统
动画系统位于 core/animation.h,并由 core::dsl::Runtime 接入 DSL。页面和组件只声明目标状态,Runtime 负责插值、重绘节奏和脏区标记。
核心类型
enum class Ease {
Linear,
InQuad,
OutQuad,
InOutQuad,
OutCubic,
InOutCubic,
OutBack
};
struct Transition {
bool enabled;
float durationSeconds;
float delaySeconds;
Ease ease;
AnimProperty properties;
};
template <typename T>
class AnimatedValue;
template <typename T>
class SmoothedValue;
AnimatedValue<T>:用于 DSL 属性从旧目标值过渡到新目标值。SmoothedValue<T>:用于 hover / press 这种持续朝目标靠近的交互状态。
DSL 写法
ui.rect("actor")
.x(active ? 420.0f : 40.0f)
.opacity(active ? 0.42f : 1.0f)
.rotate(active ? 0.36f : 0.0f)
.transition(0.42f, core::Ease::OutBack)
.build();
也可以传完整 transition:
core::Transition t = core::Transition::make(0.24f, core::Ease::OutCubic)
.animate(core::AnimProperty::Color | core::AnimProperty::Opacity);
ui.rect("panel")
.color(nextColor)
.opacity(nextOpacity)
.transition(t)
.build();
可动画属性
Rect:
FrameColorOpacityRadiusBorderShadowBlurTransform
Text:
FrameTextColorOpacity
Hover / Press 动画
交互矩形通过 states 开启:
ui.rect("button.bg")
.states(normal, hover, pressed)
.onClick(callback)
.build();
Runtime 会维护:
hoverBlendpressBlend
颜色混合路径:
normal -> hover -> pressed
按钮按压缩放通过 visualStateFrom 共享某个交互 rect 的 press 状态:
ui.stack("button")
.size(w, h)
.visualStateFrom("button.bg", 0.965f)
.content([&] {
ui.rect("button.bg")
.size(w, h)
.states(normal, hover, pressed)
.build();
});
动画与 compose
动画不是通过每帧改 DSL 树实现的。
推荐模式:
1. app 状态改变。 2. compose 声明新的目标属性。 3. Runtime 发现目标值变化。 4. AnimatedValue<T> 从当前值插到新目标值。 5. 每帧只标记相关 dirty rect。
hover / press 是 Runtime 内部状态变化,不会触发重新 compose。
click 回调可能修改 app 状态,所以 Runtime 会设置 needsCompose(),app/dsl_app.h 再重新 compose。
动画与脏区
动画推进时,Runtime 会计算:
dirty = union(oldVisualRect, newVisualRect)
对 Rect,visual rect 会考虑:
- frame
- shadow
- blur
对 Text,dirty rect 使用文本 frame。
然后 Runtime 使用 framebuffer cache + scissor 只重绘脏区。
当前限制
- 没有 timeline / keyframe API。
- 没有 repeat / yoyo。
- 没有动画完成回调。
- 没有独立动画对象句柄。
- shadow / blur 动画虽然支持,但成本较高,建议谨慎使用。
- 当前脏区会保守扩大,优先保证正确性。