11#define M_PI 3.14159265358979323846
18float clamp01(
float t) {
return std::clamp(
t, 0.f, 1.f); }
20std::string normalizeEase(
const std::string &ease) {
21 if (ease.empty())
return "smoothstep";
25float sampleProgress(
double nowMs,
double startMs,
double delayMs,
double durationMs) {
26 const double elapsed = nowMs - startMs - delayMs;
27 if (elapsed < 0.0)
return -1.f;
28 if (durationMs <= 0.0)
return 1.f;
29 if (elapsed >= durationMs)
return 1.f;
30 return float(elapsed / durationMs);
37 if (!
kind ||
kind[0] ==
'\0' || std::strcmp(
kind,
"smoothstep") == 0)
return t *
t * (3.f - 2.f *
t);
38 if (std::strcmp(
kind,
"linear") == 0)
return t;
39 if (std::strcmp(
kind,
"inQuad") == 0)
return t *
t;
40 if (std::strcmp(
kind,
"outQuad") == 0)
return 1.f - (1.f -
t) * (1.f -
t);
41 if (std::strcmp(
kind,
"inOutQuad") == 0)
return t < 0.5f ? 2.f *
t *
t : 1.f - std::pow(-2.f *
t + 2.f, 2.f) * 0.5f;
42 if (std::strcmp(
kind,
"inCubic") == 0)
return t *
t *
t;
43 if (std::strcmp(
kind,
"outCubic") == 0)
return 1.f - std::pow(1.f -
t, 3.f);
44 if (std::strcmp(
kind,
"inOutCubic") == 0)
45 return t < 0.5f ? 4.f *
t *
t *
t : 1.f - std::pow(-2.f *
t + 2.f, 3.f) * 0.5f;
46 if (std::strcmp(
kind,
"inSine") == 0)
return 1.f - std::cos(
t *
float(
M_PI) * 0.5f);
47 if (std::strcmp(
kind,
"outSine") == 0)
return std::sin(
t *
float(
M_PI) * 0.5f);
48 if (std::strcmp(
kind,
"inOutSine") == 0)
return -(std::cos(
float(
M_PI) *
t) - 1.f) * 0.5f;
49 if (std::strcmp(
kind,
"inExpo") == 0)
return t <= 0.f ? 0.f : std::pow(2.f, 10.f *
t - 10.f);
50 if (std::strcmp(
kind,
"outExpo") == 0)
return t >= 1.f ? 1.f : 1.f - std::pow(2.f, -10.f *
t);
51 if (std::strcmp(
kind,
"inOutExpo") == 0) {
52 if (
t <= 0.f)
return 0.f;
53 if (
t >= 1.f)
return 1.f;
54 return t < 0.5f ? std::pow(2.f, 20.f *
t - 10.f) * 0.5f : (2.f - std::pow(2.f, -20.f *
t + 10.f)) * 0.5f;
57 constexpr float backS = 1.70158f;
58 if (std::strcmp(
kind,
"inBack") == 0)
return t *
t * ((backS + 1.f) *
t - backS);
59 if (std::strcmp(
kind,
"outBack") == 0) {
60 const float u =
t - 1.f;
61 return u *
u * ((backS + 1.f) *
u + backS) + 1.f;
63 if (std::strcmp(
kind,
"inOutBack") == 0) {
64 const float s = backS * 1.525f;
66 const float u =
t * 2.f;
67 return 0.5f * (
u *
u * ((
s + 1.f) *
u -
s));
69 const float u =
t * 2.f - 2.f;
70 return 0.5f * (
u *
u * ((
s + 1.f) *
u +
s) + 2.f);
73 if (std::strcmp(
kind,
"inElastic") == 0) {
74 if (t <= 0.f || t >= 1.f)
return t;
75 return -std::pow(2.f, 10.f *
t - 10.f) * std::sin((
t * 10.f - 10.75f) * (2.f *
float(
M_PI) / 3.f));
77 if (std::strcmp(
kind,
"outElastic") == 0) {
78 if (t <= 0.f || t >= 1.f)
return t;
79 return std::pow(2.f, -10.f *
t) * std::sin((
t * 10.f - 0.75f) * (2.f *
float(
M_PI) / 3.f)) + 1.f;
81 if (std::strcmp(
kind,
"inOutElastic") == 0) {
82 if (t <= 0.f || t >= 1.f)
return t;
84 return -0.5f * std::pow(2.f, 20.f *
t - 10.f) * std::sin((20.f *
t - 11.125f) * (2.f *
float(
M_PI) / 4.5f));
86 return std::pow(2.f, -20.f *
t + 10.f) * std::sin((20.f *
t - 11.125f) * (2.f *
float(
M_PI) / 4.5f)) * 0.5f +
90 auto outBounce = [](
float x) {
91 constexpr float n1 = 7.5625f;
92 constexpr float d1 = 2.75f;
93 if (
x < 1.f / d1)
return n1 *
x *
x;
96 return n1 *
x *
x + 0.75f;
100 return n1 *
x *
x + 0.9375f;
103 return n1 *
x *
x + 0.984375f;
105 if (std::strcmp(
kind,
"inBounce") == 0)
return 1.f - outBounce(1.f -
t);
106 if (std::strcmp(
kind,
"outBounce") == 0)
return outBounce(
t);
107 if (std::strcmp(
kind,
"inOutBounce") == 0) {
108 return t < 0.5f ? (1.f - outBounce(1.f - 2.f *
t)) * 0.5f : (1.f + outBounce(2.f *
t - 1.f)) * 0.5f;
116 return std::chrono::duration<double, std::milli>(std::chrono::steady_clock::now().time_since_epoch()).count();
120 return a.table ==
b.table &&
a.type ==
b.type &&
a.id ==
b.id &&
a.generation ==
b.generation;
125 std::remove_if(hostTweens_.begin(), hostTweens_.end(),
126 [&](
const UiHostTween &
t) { return sameHost(t.host, host) && t.target == target; }),
131 itemTweens_.erase(std::remove_if(itemTweens_.begin(), itemTweens_.end(),
132 [&](
const UiItemTween &
t) {
133 return sameHost(t.host, host) && t.nodeId == id && t.target == target;
138void UiTweenDriver::applyHost(UiHostTween &
t,
float k) {
144 auto m =
host->get().meta();
149 m->animDrivePos =
true;
150 m->posX =
t.fromX + (
t.toX -
t.fromX) * ease;
151 m->posY =
t.fromY + (
t.toY -
t.fromY) * ease;
155 m->animDriveSize =
true;
156 m->sizeX =
t.fromX + (
t.toX -
t.fromX) * ease;
157 m->sizeY =
t.fromY + (
t.toY -
t.fromY) * ease;
161 m->overlayBgAlpha = std::clamp(
t.fromX + (
t.toX -
t.fromX) * ease, 0.f, 1.f);
166void UiTweenDriver::applyItem(UiItemTween &
t,
float k) {
172 auto node =
host->get().findById(
t.nodeId);
177 auto &
n =
node->get();
183 n.posX =
t.fromX + (
t.toX -
t.fromX) * ease;
184 n.posY =
t.fromY + (
t.toY -
t.fromY) * ease;
190 float delayMs,
double nowMs) {
192 if (!resolved)
return;
193 auto m = resolved->get().meta();
198 t.fromX =
m->hasPos ?
m->posX : 0.f;
199 t.fromY =
m->hasPos ?
m->posY : 0.f;
203 t.durationMs = std::max(0.0,
double(durationMs));
204 t.delayMs = std::max(0.0,
double(delayMs));
205 t.ease = normalizeEase(ease);
207 m->animDrivePos =
true;
208 if (
t.durationMs <= 0.0 &&
t.delayMs <= 0.0) {
213 hostTweens_.push_back(std::move(
t));
217 float delayMs,
double nowMs) {
219 if (!resolved)
return;
220 auto m = resolved->get().meta();
225 t.fromX =
m->hasSize ?
m->sizeX : 0.f;
226 t.fromY =
m->hasSize ?
m->sizeY : 0.f;
230 t.durationMs = std::max(0.0,
double(durationMs));
231 t.delayMs = std::max(0.0,
double(delayMs));
232 t.ease = normalizeEase(ease);
234 m->animDriveSize =
true;
235 if (
t.durationMs <= 0.0 &&
t.delayMs <= 0.0) {
240 hostTweens_.push_back(std::move(
t));
244 float delayMs,
double nowMs) {
246 if (!resolved)
return;
247 auto m = resolved->get().meta();
252 t.fromX =
m->overlayBgAlpha;
253 t.toX = std::clamp(alpha, 0.f, 1.f);
255 t.durationMs = std::max(0.0,
double(durationMs));
256 t.delayMs = std::max(0.0,
double(delayMs));
257 t.ease = normalizeEase(ease);
258 if (
t.durationMs <= 0.0 &&
t.delayMs <= 0.0) {
259 m->overlayBgAlpha =
t.toX;
262 hostTweens_.push_back(std::move(
t));
266 float durationMs,
const std::string &ease,
float delayMs,
273 auto node = resolved->get().findById(
id);
283 t.fromX =
node->get().opacity;
284 t.toX = std::clamp(
opacity, 0.f, 1.f);
286 t.durationMs = std::max(0.0,
double(durationMs));
287 t.delayMs = std::max(0.0,
double(delayMs));
288 t.ease = normalizeEase(ease);
289 if (
t.durationMs <= 0.0 &&
t.delayMs <= 0.0) {
290 node->get().opacity =
t.toX;
293 itemTweens_.push_back(std::move(
t));
298 float durationMs,
const std::string &ease,
float delayMs,
303 "UiTweenDriver.animateItemPos: host handle is stale"));
305 auto node = resolved->get().findById(
id);
311 auto &
n =
node->get();
321 t.durationMs = std::max(0.0,
double(durationMs));
322 t.delayMs = std::max(0.0,
double(delayMs));
323 t.ease = normalizeEase(ease);
325 if (
t.durationMs <= 0.0 &&
t.delayMs <= 0.0) {
330 itemTweens_.push_back(std::move(
t));
335 hostTweens_.erase(std::remove_if(hostTweens_.begin(), hostTweens_.end(),
336 [&](
const UiHostTween &
t) { return sameHost(t.host, host); }),
341 itemTweens_.erase(std::remove_if(itemTweens_.begin(), itemTweens_.end(),
343 if (!sameHost(t.host, host)) return false;
344 return id.empty() || t.nodeId == id;
355 if (hostTweens_.empty() && itemTweens_.empty())
return;
357 for (
auto &
t : hostTweens_) {
362 const float k = sampleProgress(nowMs,
t.startMs,
t.delayMs,
t.durationMs);
363 if (k < 0.f)
continue;
365 if (k >= 1.f)
t.host = {};
367 hostTweens_.erase(std::remove_if(hostTweens_.begin(), hostTweens_.end(),
368 [](
const UiHostTween &
t) { return !UIHost::resolve(t.host).has_value(); }),
371 for (
auto &
t : itemTweens_) {
376 const float k = sampleProgress(nowMs,
t.startMs,
t.delayMs,
t.durationMs);
377 if (k < 0.f)
continue;
379 if (k >= 1.f)
t.host = {};
381 itemTweens_.erase(std::remove_if(itemTweens_.begin(), itemTweens_.end(),
382 [](
const UiItemTween &
t) { return !UIHost::resolve(t.host).has_value(); }),
Stable, structured diagnostics shared by engine modules.
Lightweight UI-owned tween driver for host/item visual properties.
static Diagnostic error(DiagnosticCode code, std::string message, std::string path={}, DiagnosticDetails details={}, std::string source={})
Construct an error diagnostic with the standard error severity.
Move-only operation result carrying either a value or Status.
static Result success(T value)
Construct a successful result owning value.
static Result failure(Status status)
Construct a failed result from a structured status.
static Status success(StatusCode code=StatusCode::Ok)
Construct a successful status with an explicit non-error outcome.
static eve::OptionalRef< UIHost > resolve(UIHostHandle handle) noexcept
Resolves a UI host handle with ECS generation checking.
void animateHostSize(UIHostHandle host, float w, float h, float durationMs, const std::string &ease, float delayMs, double nowMs)
Animate selected-host explicit window size.
void cancelItem(UIHostHandle host, const std::string &id={})
Cancel every pending item tween for host + id (empty id = all items).
void tick(double nowMs)
Advance all pending tweens to nowMs.
void cancelAll(UIHostHandle host)
Cancel every pending tween for host.
eve::Result< void > animateItemPos(UIHostHandle host, const std::string &id, float x, float y, float durationMs, const std::string &ease, float delayMs, double nowMs)
Animate one absolute node's placement offset.
void cancelHost(UIHostHandle host)
Cancel every pending host tween for host.
eve::Result< void > animateItemOpacity(UIHostHandle host, const std::string &id, float opacity, float durationMs, const std::string &ease, float delayMs, double nowMs)
Animate one node's transient opacity.
void animateHostOverlayAlpha(UIHostHandle host, float alpha, float durationMs, const std::string &ease, float delayMs, double nowMs)
Animate selected-host overlay background alpha.
void animateHostPos(UIHostHandle host, float x, float y, float durationMs, const std::string &ease, float delayMs, double nowMs)
Animate selected-host window position.
UiItemTweenTarget
Retained-node property driven by a float or vec2 tween.
@ Opacity
UINode.opacity (transient)
@ Pos
UINode.posX / posY (absolute placement)
UiHostTweenTarget
Host window property driven by a vec2 or float tween.
@ OverlayAlpha
Meta.overlayBgAlpha
@ Size
Meta.sizeX / sizeY (px)
@ Pos
Meta.posX / posY (px)
float evaluateUiEase(float t, const char *kind)
Evaluate a UI tween ease curve in [0,1].
double uiTweenWallClockMs()
Wall-clock milliseconds from steady_clock (presentation clock).
ecs::EntityHandle UIHostHandle
One host-level tween slot.
One item-level tween slot scoped to a host + node id.