载入中...
搜索中...
未找到
UiTween.cpp
浏览该文件的文档.
1#include "ui/UiTween.h"
2
3#include "common/Diagnostic.h"
4
5#include <algorithm>
6#include <chrono>
7#include <cmath>
8#include <cstring>
9
10#ifndef M_PI
11#define M_PI 3.14159265358979323846
12#endif
13
14namespace eve::ui {
15
16namespace {
17
18float clamp01(float t) { return std::clamp(t, 0.f, 1.f); }
19
20std::string normalizeEase(const std::string &ease) {
21 if (ease.empty()) return "smoothstep";
22 return ease;
23}
24
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; // still delayed
28 if (durationMs <= 0.0) return 1.f;
29 if (elapsed >= durationMs) return 1.f;
30 return float(elapsed / durationMs);
31}
32
33} // namespace
34
35float evaluateUiEase(float t, const char *kind) {
36 t = clamp01(t);
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;
55 }
56
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;
62 }
63 if (std::strcmp(kind, "inOutBack") == 0) {
64 const float s = backS * 1.525f;
65 if (t < 0.5f) {
66 const float u = t * 2.f;
67 return 0.5f * (u * u * ((s + 1.f) * u - s));
68 }
69 const float u = t * 2.f - 2.f;
70 return 0.5f * (u * u * ((s + 1.f) * u + s) + 2.f);
71 }
72
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));
76 }
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;
80 }
81 if (std::strcmp(kind, "inOutElastic") == 0) {
82 if (t <= 0.f || t >= 1.f) return t;
83 if (t < 0.5f) {
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));
85 }
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 +
87 1.f;
88 }
89
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;
94 if (x < 2.f / d1) {
95 x -= 1.5f / d1;
96 return n1 * x * x + 0.75f;
97 }
98 if (x < 2.5f / d1) {
99 x -= 2.25f / d1;
100 return n1 * x * x + 0.9375f;
101 }
102 x -= 2.625f / d1;
103 return n1 * x * x + 0.984375f;
104 };
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;
109 }
110
111 // Unknown kind: linear (documented; avoids throwing on script typos).
112 return t;
113}
114
116 return std::chrono::duration<double, std::milli>(std::chrono::steady_clock::now().time_since_epoch()).count();
117}
118
119bool UiTweenDriver::sameHost(UIHostHandle a, UIHostHandle b) noexcept {
120 return a.table == b.table && a.type == b.type && a.id == b.id && a.generation == b.generation;
121}
122
123void UiTweenDriver::replaceHost(UIHostHandle host, UiHostTweenTarget target) {
124 hostTweens_.erase(
125 std::remove_if(hostTweens_.begin(), hostTweens_.end(),
126 [&](const UiHostTween &t) { return sameHost(t.host, host) && t.target == target; }),
127 hostTweens_.end());
128}
129
130void UiTweenDriver::replaceItem(UIHostHandle host, const std::string &id, UiItemTweenTarget 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;
134 }),
135 itemTweens_.end());
136}
137
138void UiTweenDriver::applyHost(UiHostTween &t, float k) {
139 auto host = UIHost::resolve(t.host);
140 if (!host) {
141 t.host = {};
142 return;
143 }
144 auto m = host->get().meta();
145 const float ease = evaluateUiEase(k, t.ease.c_str());
146 switch (t.target) {
148 m->hasPos = true;
149 m->animDrivePos = true;
150 m->posX = t.fromX + (t.toX - t.fromX) * ease;
151 m->posY = t.fromY + (t.toY - t.fromY) * ease;
152 break;
154 m->hasSize = true;
155 m->animDriveSize = true;
156 m->sizeX = t.fromX + (t.toX - t.fromX) * ease;
157 m->sizeY = t.fromY + (t.toY - t.fromY) * ease;
158 break;
160 // Back/Elastic can overshoot; keep retained alpha in [0,1].
161 m->overlayBgAlpha = std::clamp(t.fromX + (t.toX - t.fromX) * ease, 0.f, 1.f);
162 break;
163 }
164}
165
166void UiTweenDriver::applyItem(UiItemTween &t, float k) {
167 auto host = UIHost::resolve(t.host);
168 if (!host) {
169 t.host = {};
170 return;
171 }
172 auto node = host->get().findById(t.nodeId);
173 if (!node) {
174 t.host = {};
175 return;
176 }
177 auto &n = node->get();
178 const float ease = evaluateUiEase(k, t.ease.c_str());
179 switch (t.target) {
180 case UiItemTweenTarget::Opacity: n.opacity = std::clamp(t.fromX + (t.toX - t.fromX) * ease, 0.f, 1.f); break;
182 n.absolute = true;
183 n.posX = t.fromX + (t.toX - t.fromX) * ease;
184 n.posY = t.fromY + (t.toY - t.fromY) * ease;
185 break;
186 }
187}
188
189void UiTweenDriver::animateHostPos(UIHostHandle host, float x, float y, float durationMs, const std::string &ease,
190 float delayMs, double nowMs) {
191 auto resolved = UIHost::resolve(host);
192 if (!resolved) return;
193 auto m = resolved->get().meta();
194 replaceHost(host, UiHostTweenTarget::Pos);
196 t.host = host;
197 t.target = UiHostTweenTarget::Pos;
198 t.fromX = m->hasPos ? m->posX : 0.f;
199 t.fromY = m->hasPos ? m->posY : 0.f;
200 t.toX = x;
201 t.toY = y;
202 t.startMs = nowMs;
203 t.durationMs = std::max(0.0, double(durationMs));
204 t.delayMs = std::max(0.0, double(delayMs));
205 t.ease = normalizeEase(ease);
206 m->hasPos = true;
207 m->animDrivePos = true;
208 if (t.durationMs <= 0.0 && t.delayMs <= 0.0) {
209 m->posX = t.toX;
210 m->posY = t.toY;
211 return;
212 }
213 hostTweens_.push_back(std::move(t));
214}
215
216void UiTweenDriver::animateHostSize(UIHostHandle host, float w, float h, float durationMs, const std::string &ease,
217 float delayMs, double nowMs) {
218 auto resolved = UIHost::resolve(host);
219 if (!resolved) return;
220 auto m = resolved->get().meta();
221 replaceHost(host, UiHostTweenTarget::Size);
223 t.host = host;
224 t.target = UiHostTweenTarget::Size;
225 t.fromX = m->hasSize ? m->sizeX : 0.f;
226 t.fromY = m->hasSize ? m->sizeY : 0.f;
227 t.toX = w;
228 t.toY = h;
229 t.startMs = nowMs;
230 t.durationMs = std::max(0.0, double(durationMs));
231 t.delayMs = std::max(0.0, double(delayMs));
232 t.ease = normalizeEase(ease);
233 m->hasSize = true;
234 m->animDriveSize = true;
235 if (t.durationMs <= 0.0 && t.delayMs <= 0.0) {
236 m->sizeX = t.toX;
237 m->sizeY = t.toY;
238 return;
239 }
240 hostTweens_.push_back(std::move(t));
241}
242
243void UiTweenDriver::animateHostOverlayAlpha(UIHostHandle host, float alpha, float durationMs, const std::string &ease,
244 float delayMs, double nowMs) {
245 auto resolved = UIHost::resolve(host);
246 if (!resolved) return;
247 auto m = resolved->get().meta();
250 t.host = host;
252 t.fromX = m->overlayBgAlpha;
253 t.toX = std::clamp(alpha, 0.f, 1.f);
254 t.startMs = nowMs;
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;
260 return;
261 }
262 hostTweens_.push_back(std::move(t));
263}
264
266 float durationMs, const std::string &ease, float delayMs,
267 double nowMs) {
268 auto resolved = UIHost::resolve(host);
269 if (!resolved) {
271 eve::DiagnosticCode::StaleHandle, "UiTweenDriver.animateItemOpacity: host handle is stale"));
272 }
273 auto node = resolved->get().findById(id);
274 if (!node) {
276 eve::DiagnosticCode::NotFound, "UiTweenDriver.animateItemOpacity: node '" + id + "' not found"));
277 }
278 replaceItem(host, id, UiItemTweenTarget::Opacity);
280 t.host = host;
281 t.nodeId = id;
283 t.fromX = node->get().opacity;
284 t.toX = std::clamp(opacity, 0.f, 1.f);
285 t.startMs = nowMs;
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;
292 }
293 itemTweens_.push_back(std::move(t));
295}
296
298 float durationMs, const std::string &ease, float delayMs,
299 double nowMs) {
300 auto resolved = UIHost::resolve(host);
301 if (!resolved) {
303 "UiTweenDriver.animateItemPos: host handle is stale"));
304 }
305 auto node = resolved->get().findById(id);
306 if (!node) {
308 eve::DiagnosticCode::NotFound, "UiTweenDriver.animateItemPos: node '" + id + "' not found"));
309 }
310 replaceItem(host, id, UiItemTweenTarget::Pos);
311 auto &n = node->get();
313 t.host = host;
314 t.nodeId = id;
315 t.target = UiItemTweenTarget::Pos;
316 t.fromX = n.posX;
317 t.fromY = n.posY;
318 t.toX = x;
319 t.toY = y;
320 t.startMs = nowMs;
321 t.durationMs = std::max(0.0, double(durationMs));
322 t.delayMs = std::max(0.0, double(delayMs));
323 t.ease = normalizeEase(ease);
324 n.absolute = true;
325 if (t.durationMs <= 0.0 && t.delayMs <= 0.0) {
326 n.posX = t.toX;
327 n.posY = t.toY;
329 }
330 itemTweens_.push_back(std::move(t));
332}
333
335 hostTweens_.erase(std::remove_if(hostTweens_.begin(), hostTweens_.end(),
336 [&](const UiHostTween &t) { return sameHost(t.host, host); }),
337 hostTweens_.end());
338}
339
340void UiTweenDriver::cancelItem(UIHostHandle host, const std::string &id) {
341 itemTweens_.erase(std::remove_if(itemTweens_.begin(), itemTweens_.end(),
342 [&](const UiItemTween &t) {
343 if (!sameHost(t.host, host)) return false;
344 return id.empty() || t.nodeId == id;
345 }),
346 itemTweens_.end());
347}
348
353
354void UiTweenDriver::tick(double nowMs) {
355 if (hostTweens_.empty() && itemTweens_.empty()) return;
356
357 for (auto &t : hostTweens_) {
358 if (!UIHost::resolve(t.host)) {
359 t.host = {};
360 continue;
361 }
362 const float k = sampleProgress(nowMs, t.startMs, t.delayMs, t.durationMs);
363 if (k < 0.f) continue; // delay
364 applyHost(t, k);
365 if (k >= 1.f) t.host = {};
366 }
367 hostTweens_.erase(std::remove_if(hostTweens_.begin(), hostTweens_.end(),
368 [](const UiHostTween &t) { return !UIHost::resolve(t.host).has_value(); }),
369 hostTweens_.end());
370
371 for (auto &t : itemTweens_) {
372 if (!UIHost::resolve(t.host)) {
373 t.host = {};
374 continue;
375 }
376 const float k = sampleProgress(nowMs, t.startMs, t.delayMs, t.durationMs);
377 if (k < 0.f) continue;
378 applyItem(t, k);
379 if (k >= 1.f) t.host = {};
380 }
381 itemTweens_.erase(std::remove_if(itemTweens_.begin(), itemTweens_.end(),
382 [](const UiItemTween &t) { return !UIHost::resolve(t.host).has_value(); }),
383 itemTweens_.end());
384}
385
386} // namespace eve::ui
LogicalId target
float w
Definition AnimClip.cpp:738
float y
Definition AnimClip.cpp:738
float x
Definition AnimClip.cpp:738
const std::string & s
Stable, structured diagnostics shared by engine modules.
float u
Definition Grass.cpp:233
glm::vec3 n
Definition Grass.cpp:63
int h
TokenKind kind
MeleePoint3 b
Definition MeleeHit.cpp:41
MeleePoint3 a
Definition MeleeHit.cpp:40
#define M_PI
std::string id
Definition PlayHost.cpp:108
float t
const RoadNode * node
float opacity
UIHostHandle host
Lightweight UI-owned tween driver for host/item visual properties.
float m[16]
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.
Definition Diagnostic.h:125
Move-only operation result carrying either a value or Status.
Definition Result.h:155
static Result success(T value)
Construct a successful result owning value.
Definition Result.h:164
static Result failure(Status status)
Construct a failed result from a structured status.
Definition Result.h:175
static Status success(StatusCode code=StatusCode::Ok)
Construct a successful status with an explicit non-error outcome.
Definition Status.h:81
static eve::OptionalRef< UIHost > resolve(UIHostHandle handle) noexcept
Resolves a UI host handle with ECS generation checking.
Definition UIHost.cpp:61
void animateHostSize(UIHostHandle host, float w, float h, float durationMs, const std::string &ease, float delayMs, double nowMs)
Animate selected-host explicit window size.
Definition UiTween.cpp:216
void cancelItem(UIHostHandle host, const std::string &id={})
Cancel every pending item tween for host + id (empty id = all items).
Definition UiTween.cpp:340
void tick(double nowMs)
Advance all pending tweens to nowMs.
Definition UiTween.cpp:354
void cancelAll(UIHostHandle host)
Cancel every pending tween for host.
Definition UiTween.cpp:349
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.
Definition UiTween.cpp:297
void cancelHost(UIHostHandle host)
Cancel every pending host tween for host.
Definition UiTween.cpp:334
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.
Definition UiTween.cpp:265
void animateHostOverlayAlpha(UIHostHandle host, float alpha, float durationMs, const std::string &ease, float delayMs, double nowMs)
Animate selected-host overlay background alpha.
Definition UiTween.cpp:243
void animateHostPos(UIHostHandle host, float x, float y, float durationMs, const std::string &ease, float delayMs, double nowMs)
Animate selected-host window position.
Definition UiTween.cpp:189
UiItemTweenTarget
Retained-node property driven by a float or vec2 tween.
Definition UiTween.h:40
@ Opacity
UINode.opacity (transient)
@ Pos
UINode.posX / posY (absolute placement)
UiHostTweenTarget
Host window property driven by a vec2 or float tween.
Definition UiTween.h:33
@ 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].
Definition UiTween.cpp:35
double uiTweenWallClockMs()
Wall-clock milliseconds from steady_clock (presentation clock).
Definition UiTween.cpp:115
ecs::EntityHandle UIHostHandle
Definition UIHost.h:16
One host-level tween slot.
Definition UiTween.h:46
UIHostHandle host
Definition UiTween.h:47
One item-level tween slot scoped to a host + node id.
Definition UiTween.h:60
UIHostHandle host
Definition UiTween.h:61