Loading...
Searching...
No Matches
Tweener.h
Go to the documentation of this file.
1// Copyright (c) 2025 Dave R. Smith.
2// Malena Framework — Licensed under PolyForm Noncommercial 1.0.0; commercial use requires a paid license. See LICENSE.
3
4//
5// Tweener.h — a generic, delta-time value animator.
6//
7// Interpolates any lerpable value (float, sf::Vector2f, sf::Color) from a start
8// to an end over a duration, using an Easing curve, advanced by REAL elapsed
9// time. This is the stable replacement for the old frame-count-based movement
10// code that used to live in Positionable.
11//
12
13#ifndef MALENA_TWEENER_H
14#define MALENA_TWEENER_H
15
16#pragma once
17
21#include <algorithm>
22#include <cmath>
23#include <functional>
24
25namespace ml
26{
27 namespace detail
28 {
29 // ── lerp overloads — one per interpolatable type ─────────────────────
30 inline float lerp(float a, float b, float t) { return a + (b - a) * t; }
31
32 inline sf::Vector2f lerp(const sf::Vector2f& a, const sf::Vector2f& b, float t)
33 {
34 return { a.x + (b.x - a.x) * t, a.y + (b.y - a.y) * t };
35 }
36
37 inline sf::Color lerp(const sf::Color& a, const sf::Color& b, float t)
38 {
39 auto ch = [t](std::uint8_t x, std::uint8_t y) -> std::uint8_t {
40 const float v = static_cast<float>(x) + (static_cast<float>(y) - x) * t;
41 return static_cast<std::uint8_t>(std::clamp(v, 0.f, 255.f) + 0.5f);
42 };
43 return { ch(a.r, b.r), ch(a.g, b.g), ch(a.b, b.b), ch(a.a, b.a) };
44 }
45 } // namespace detail
46
65 template<class T>
66 class Tweener
67 {
68 public:
69 Tweener() = default;
70
71 Tweener(T from, T to, float duration, Easing::Fn easing = Easing::Linear)
72 : _from(from), _to(to), _value(from),
73 _duration(duration > 0.f ? duration : 0.f), _easing(easing ? easing : Easing::Linear)
74 {}
75
76 // ── Fluent configuration ─────────────────────────────────────────────
77 Tweener& delay(float seconds) { _delay = std::max(0.f, seconds); return *this; }
78 Tweener& easing(Easing::Fn fn) { _easing = fn ? fn : Easing::Linear; return *this; }
79 Tweener& loop(bool on = true) { _loop = on; return *this; }
80 Tweener& pingPong(bool on = true) { _pingPong = on; return *this; }
81 Tweener& onUpdate(std::function<void(const T&)> cb) { _onUpdate = std::move(cb); return *this; }
82 Tweener& onComplete(std::function<void()> cb) { _onComplete = std::move(cb); return *this; }
83
84 // ── Drive ─────────────────────────────────────────────────────────────
86 void update(float dt)
87 {
88 if (_done) return;
89 if (dt < 0.f) dt = 0.f;
90
91 // Hold at the start value through the delay window.
92 if (_delay > 0.f)
93 {
94 _delay -= dt;
95 if (_delay > 0.f) { emit(_from); return; }
96 dt = -_delay; // spend the leftover on the tween
97 _delay = 0.f;
98 }
99
100 // Zero-duration tween snaps straight to the end.
101 if (_duration <= 0.f)
102 {
103 _value = _to;
104 emit(_value);
105 finish();
106 return;
107 }
108
109 _elapsed += dt;
110 const float eased = _easing(phase());
111 _value = detail::lerp(_from, _to, eased);
112 emit(_value);
113
114 if (!_loop && !_pingPong && _elapsed >= _duration) finish();
115 else if (_pingPong && !_loop && _elapsed >= 2.f * _duration) finish();
116 }
117
119 void complete()
120 {
121 if (_done) return;
122 _value = _to;
123 emit(_value);
124 finish();
125 }
126
128 void stop() { _done = true; }
129
130 [[nodiscard]] bool done() const { return _done; }
131 [[nodiscard]] const T& value() const { return _value; }
132
133 private:
134 // Normalized eased-input in [0,1], honoring loop / ping-pong.
135 [[nodiscard]] float phase() const
136 {
137 if (_duration <= 0.f) return 1.f;
138 float p = _elapsed / _duration; // 0..∞ in "durations"
139 if (_pingPong)
140 {
141 float c = std::fmod(p, 2.f); // 0..2 triangle
142 if (!_loop) c = std::min(p, 2.f); // one there-and-back
143 return c <= 1.f ? c : 2.f - c;
144 }
145 if (_loop) return std::fmod(p, 1.f);
146 return std::clamp(p, 0.f, 1.f);
147 }
148
149 void emit(const T& v) const { if (_onUpdate) _onUpdate(v); }
150
151 void finish()
152 {
153 _done = true;
154 if (_onComplete) _onComplete();
155 }
156
157 T _from{}, _to{}, _value{};
158 float _duration = 0.f;
159 float _delay = 0.f;
160 float _elapsed = 0.f;
161 Easing::Fn _easing = Easing::Linear;
162 bool _loop = false;
163 bool _pingPong = false;
164 bool _done = false;
165 std::function<void(const T&)> _onUpdate;
166 std::function<void()> _onComplete;
167 };
168
169} // namespace ml
170
171#endif // MALENA_TWEENER_H
void update(float dt)
Advance by dt seconds. Safe to keep calling after completion (no-op).
Definition Tweener.h:86
Tweener & onUpdate(std::function< void(const T &)> cb)
Definition Tweener.h:81
Tweener & onComplete(std::function< void()> cb)
Definition Tweener.h:82
void complete()
Jump to the end immediately, firing onUpdate(end) + onComplete once.
Definition Tweener.h:119
Tweener & delay(float seconds)
Definition Tweener.h:77
Tweener & pingPong(bool on=true)
Definition Tweener.h:80
void stop()
Stop where it is — no onComplete. (Used by cancel paths.).
Definition Tweener.h:128
Tweener()=default
const T & value() const
Definition Tweener.h:131
Tweener & easing(Easing::Fn fn)
Definition Tweener.h:78
Tweener(T from, T to, float duration, Easing::Fn easing=Easing::Linear)
Definition Tweener.h:71
Tweener & loop(bool on=true)
Definition Tweener.h:79
bool done() const
Definition Tweener.h:130
std::uint8_t a
std::uint8_t b
std::uint8_t g
std::uint8_t r
float lerp(float a, float b, float t)
Definition Tweener.h:30
Definition Animate.h:25
Vector2< float > Vector2f
Easing curves for animation, as pure functions of normalized time.
Definition Easing.h:33
static float Linear(float t)
Definition Easing.h:39
float(*)(float) Fn
Definition Easing.h:34