14#include <PhosphorAnimation/phosphoranimation_export.h>
17#include <QLoggingCategory>
36Q_DECLARE_EXPORTED_LOGGING_CATEGORY(lcAnimatedValue, PHOSPHORANIMATION_EXPORT)
64 qCWarning(lcAnimatedValue) <<
"start() rejected: null clock";
69 qCWarning(lcAnimatedValue) <<
"start() rejected: non-finite from/to";
73 m_from = std::move(from);
75 m_spec = std::move(spec);
80 m_state.duration = 1.0;
82 m_lastTickTime.reset();
83 m_loggedStatelessDegrade =
false;
84 m_loggedNegativeDt =
false;
85 m_loggedTransformDegrade =
false;
86 m_loggedEpochMismatch =
false;
91 m_isAnimating =
false;
98 m_spec.clock->requestFrame();
107 qCWarning(lcAnimatedValue) <<
"retarget() rejected: no stored spec (never started)";
111 qCWarning(lcAnimatedValue) <<
"retarget() rejected: non-finite newTo";
115 const T newFrom = m_current;
123 qCWarning(lcAnimatedValue) <<
"retarget() rejected: non-finite current value; finishing at target";
130 const auto curve = effectiveCurve();
131 const bool stateful = curve && curve->isStateful();
133 qreal newVelocity = 0.0;
136 if constexpr (std::same_as<T, QTransform>) {
141 if (!pureTranslate) {
142 if (!m_loggedTransformDegrade) {
143 qCDebug(lcAnimatedValue) <<
"QTransform PreserveVelocity degrading to PreservePosition: "
144 <<
"non-translate components present (Frobenius metric mixes units)";
145 m_loggedTransformDegrade =
true;
158 newVelocity = (m_state.velocity * oldDistance) / newDistance;
159 }
else if (!stateful && !m_loggedStatelessDegrade) {
160 qCDebug(lcAnimatedValue) <<
"PreserveVelocity degrading to PreservePosition on stateless curve"
161 << (curve ? curve->typeId() : QStringLiteral(
"null"));
162 m_loggedStatelessDegrade =
true;
174 m_to = std::move(newTo);
177 m_state.velocity = newVelocity;
179 m_state.startValue = 0.0;
181 m_lastTickTime.reset();
183 if (qFuzzyIsNull(newDistance)) {
186 m_state.velocity = 0.0;
187 m_isAnimating =
false;
192 m_isAnimating =
true;
193 m_isComplete =
false;
194 m_spec.clock->requestFrame();
201 return retarget(std::move(newTo), m_spec.retargetPolicy);
212 m_isAnimating =
false;
213 m_isComplete =
false;
217 template<ColorSpace OtherSpace>
221 template<ColorSpace OtherSpace>
227 if (!m_isAnimating) {
232 m_state.velocity = 0.0;
233 m_isAnimating =
false;
235 if (m_spec.onValueChanged) {
236 m_spec.onValueChanged(m_current);
239 if (m_isComplete && m_spec.onComplete) {
248 if (!m_isAnimating || !m_spec.clock) {
252 const auto now = m_spec.clock->now();
256 m_lastTickTime = now;
258 if (m_spec.onValueChanged) {
259 m_spec.onValueChanged(m_current);
262 m_spec.clock->requestFrame();
267 const auto elapsed = now - *m_startTime;
268 const qreal dtSeconds = std::chrono::duration<qreal>(now - *m_lastTickTime).count();
269 m_lastTickTime = now;
271 if (dtSeconds < 0.0) {
272 if (!m_loggedNegativeDt) {
273 qCWarning(lcAnimatedValue) <<
"negative dt from clock (" << dtSeconds
274 <<
"s) — treating as zero-step. Monotonicity contract violated.";
275 m_loggedNegativeDt =
true;
277 m_spec.clock->requestFrame();
281 const auto curve = effectiveCurve();
283 bool complete =
false;
285 if (curve->isStateful()) {
327 const qreal elapsedMs = std::chrono::duration<qreal, std::milli>(elapsed).count();
328 qreal lifetimeMs = curve->settleTime() * 1000.0;
329 if (m_spec.maxLifetimeMs) {
330 lifetimeMs = qMin(lifetimeMs,
static_cast<qreal
>(*m_spec.maxLifetimeMs));
339 if (qAbs(m_state.value - 1.0) <= 1.0e-6 && qAbs(m_state.velocity) <= 1.0e-6) {
341 }
else if (lifetimeMs > 0.0 && std::isfinite(lifetimeMs) && elapsedMs >= lifetimeMs) {
343 }
else if (elapsed > kSafetyCap) {
344 qCWarning(lcAnimatedValue) <<
"stateful curve exceeded safety cap; forcing completion";
348 const qreal durationMs = m_spec.profile.effectiveDuration();
349 const qreal elapsedMs = std::chrono::duration<qreal, std::milli>(elapsed).count();
350 if (!std::isfinite(durationMs) || durationMs <= 0.0 || elapsedMs >= durationMs) {
354 const qreal t = elapsedMs / durationMs;
355 m_state.value = curve->evaluate(t);
362 m_state.velocity = 0.0;
363 m_isAnimating =
false;
365 if (m_spec.onValueChanged) {
366 m_spec.onValueChanged(m_current);
369 if (m_isComplete && m_spec.onComplete) {
373 m_current = lerpStateValue();
374 if (m_spec.onValueChanged) {
375 m_spec.onValueChanged(m_current);
378 m_spec.clock->requestFrame();
389 return m_state.velocity;
397 return m_isAnimating;
419 QRectF bounds() const
420 requires detail::PositionalGeometric<T>;
422 QRectF boundsAt(QPointF anchor) const
423 requires detail::SizeGeometric<T>;
425 std::pair<QSizeF, QSizeF> sweptSize() const
426 requires detail::SizeGeometric<T>;
429 requires std::same_as<T, QRectF>;
431 std::pair<T, T> sweptRange() const
432 requires detail::ScalarValue<T>;
434 static constexpr std::chrono::seconds safetyCap() noexcept
440 static constexpr std::chrono::seconds kSafetyCap{60};
441 static constexpr int kOvershootSamples = 50;
443 T lerpStateValue()
const
458 return Interpolate<T>::lerp(m_from, m_to, progress);
462 std::shared_ptr<const Curve> effectiveCurve()
const
465 return m_cachedCurve;
470 QRectF boundsImpl() const
471 requires detail::PositionalGeometric<T>;
473 std::pair<QSizeF, QSizeF> sweptSizeImpl() const
474 requires detail::SizeGeometric<T>;
476 template<typename Sampler>
477 void sampleOvershoots(qreal& minX, qreal& minY, qreal& maxX, qreal& maxY, const Sampler& sampleAt) const;
479 std::pair<T, T> sweptRangeImpl() const;
485 MotionSpec<T> m_spec;
486 std::shared_ptr<const Curve> m_cachedCurve;
487 std::optional<std::chrono::nanoseconds> m_startTime;
488 std::optional<std::chrono::nanoseconds> m_lastTickTime;
489 bool m_isAnimating =
false;
490 bool m_isComplete =
false;
491 bool m_loggedStatelessDegrade =
false;
492 bool m_loggedNegativeDt =
false;
493 bool m_loggedTransformDegrade =
false;
494 bool m_loggedEpochMismatch =
false;
Animation-wide UI bounds (duration, stagger interval).
Unified motion primitive: one value of type T transitioning from start to target over time,...
Definition AnimatedValue.h:45
const CurveState & state() const
Definition AnimatedValue.h:391
AnimatedValue(const AnimatedValue &)=delete
bool retarget(T newTo, RetargetPolicy policy)
Redirect in-flight animation to a new target.
Definition AnimatedValue.h:104
T value() const
Definition AnimatedValue.h:383
T to() const
Definition AnimatedValue.h:411
void cancel()
Stop animating; leave value() at its current position.
Definition AnimatedValue.h:210
AnimatedValue & operator=(const AnimatedValue &)=delete
bool isAnimating() const
Definition AnimatedValue.h:395
bool isComplete() const
Definition AnimatedValue.h:399
T from() const
Definition AnimatedValue.h:407
qreal velocity() const
Definition AnimatedValue.h:387
void finish()
Snap to target immediately, fire onValueChanged + onComplete.
Definition AnimatedValue.h:225
bool retarget(T newTo)
Convenience overload using m_spec.retargetPolicy.
Definition AnimatedValue.h:199
AnimatedValue(AnimatedValue &&) noexcept=default
void advance()
Advance animation by one paint tick.
Definition AnimatedValue.h:246
const MotionSpec< T > & spec() const
Definition AnimatedValue.h:403
Polymorphic base for all animation curves.
Definition Curve.h:45
Abstract clock interface for the motion runtime.
Definition IMotionClock.h:18
constexpr float MaxShaderTimeDeltaSeconds
Hard ceiling on a per-frame time delta handed to a shader or to a physics integrator,...
Definition AnimationLimits.h:160
QColor lerpColorOkLab(const QColor &from, const QColor &to, qreal t)
Definition Interpolate.h:191
bool isPureTranslate(const QTransform &t)
True if the 2x2 linear part is identity (only translation present).
Definition Interpolate.h:306
Definition AnimatedValue.h:32
constexpr qreal kRectSizeEpsilonPx
Sub-pixel epsilon for rect size-change detection.
Definition Interpolate.h:26
PHOSPHORANIMATION_EXPORT std::shared_ptr< const Curve > defaultFallbackCurve()
qreal boundCurveProgress(qreal progress)
Bound a curve's output to the overshoot envelope [Limits::MinCurveProgress, Limits::MaxCurveProgress]...
Definition Curve.h:118
RetargetPolicy
How an in-flight AnimatedValue<T> reshapes on retarget().
Definition RetargetPolicy.h:9
@ ResetVelocity
Zero velocity on retarget; motion restarts from rest toward the new target.
@ PreserveVelocity
Carry velocity across the segment boundary, re-scaled to the new distance. Default.
@ PreservePosition
Position-continuous only; velocity treatment delegated to the curve's natural behaviour.
ColorSpace
Interpolation space selector for AnimatedValue<QColor, ...>.
Definition Interpolate.h:29
@ Linear
sRGB -> linear lerp -> sRGB (radiometrically correct)
@ OkLab
sRGB -> OkLab lerp -> sRGB (perceptually uniform)
Mutable state for stateful curve progression (springs carry position+velocity across frames; stateles...
Definition Curve.h:20
qreal startValue
Start of current segment — stateless step() lerps from here to target.
Definition Curve.h:27
Type-specific linear interpolation and path-distance for AnimatedValue<T>.
Definition Interpolate.h:36
Runtime call-site bundle for starting an AnimatedValue<T>.
Definition MotionSpec.h:25