From 44c8b1fabdab07caca425735e581d0a82fda96be Mon Sep 17 00:00:00 2001 From: Leonardo Date: Sun, 17 Aug 2025 16:13:27 -0300 Subject: [PATCH 01/58] remove string-based `via` overloads for easing --- include/tween.h | 36 +----------------------------------- include/tween.tcc | 42 ------------------------------------------ include/tweenone.tcc | 42 ------------------------------------------ src/sandbox.cc | 2 +- 4 files changed, 2 insertions(+), 120 deletions(-) diff --git a/include/tween.h b/include/tween.h index ad64dfd..9c85f0f 100644 --- a/include/tween.h +++ b/include/tween.h @@ -130,39 +130,7 @@ namespace tweeny { */ template tween & via(easing::enumerated enumerated, Fs... fs); - /** - * @brief Specifies the easing function for the last added point, accepting an easing name as a `std::string` value. - * - * This will specify the easing between the last tween point added by @p to and its previous step. - * You can mix-and-match enumerated easings, functions and easing names. - * - * **Example**: - * - * @code - * auto tween = tweeny::from(0.0f, 100.0f).to(100.0f, 0.0f).via(tweeny::easing::linear, "backOut"); - * - * @param fs The functions - * @returns *this - * @see tweeny::easing - */ - template tween & via(const std::string & easing, Fs... fs); - - /** - * @brief Specifies the easing function for the last added point, accepting an easing name as a `const char *` value. - * - * This will specify the easing between the last tween point added by @p to and its previous step. - * You can mix-and-match enumerated easings, functions and easing names. - * - * **Example**: - * - * @code - * auto tween = tweeny::from(0.0f, 100.0f).to(100.0f, 0.0f).via(tweeny::easing::linear, "backOut"); - * - * @param fs The functions - * @returns *this - * @see tweeny::easing - */ - template tween & via(const char * easing, Fs... fs); + /** * @brief Specifies the easing function for a specific point. @@ -602,8 +570,6 @@ namespace tweeny { template tween & via(Fs... fs); ///< @sa tween::via template tween & via(int index, Fs... fs); ///< @sa tween::via template tween & via(tweeny::easing::enumerated enumerated, Fs... fs); ///< @sa tween::via - template tween & via(const std::string & easing, Fs... fs); ///< @sa tween::via - template tween & via(const char * easing, Fs... fs); ///< @sa tween::via template tween & during(Ds... ds); ///< @sa tween::during const T & step(int32_t dt, bool suppressCallbacks = false); ///< @sa tween::step(int32_t dt, bool suppressCallbacks) const T & step(uint32_t dt, bool suppressCallbacks = false); ///< @sa tween::step(uint32_t dt, bool suppressCallbacks) diff --git a/include/tween.tcc b/include/tween.tcc index dd0e77f..662a529 100644 --- a/include/tween.tcc +++ b/include/tween.tcc @@ -107,49 +107,7 @@ namespace tweeny { } } - template - template - tween & tween::via(const std::string & easing, Fs... vs) { - if (easing == "stepped") return via(easing::stepped, vs...); - if (easing == "linear") return via(easing::linear, vs...); - if (easing == "quadraticIn") return via(easing::quadraticIn, vs...); - if (easing == "quadraticOut") return via(easing::quadraticOut, vs...); - if (easing == "quadraticInOut") return via(easing::quadraticInOut, vs...); - if (easing == "cubicIn") return via(easing::cubicIn, vs...); - if (easing == "cubicOut") return via(easing::cubicOut, vs...); - if (easing == "cubicInOut") return via(easing::cubicInOut, vs...); - if (easing == "quarticIn") return via(easing::quarticIn, vs...); - if (easing == "quarticOut") return via(easing::quarticOut, vs...); - if (easing == "quarticInOut") return via(easing::quarticInOut, vs...); - if (easing == "quinticIn") return via(easing::quinticIn, vs...); - if (easing == "quinticOut") return via(easing::quinticOut, vs...); - if (easing == "quinticInOut") return via(easing::quinticInOut, vs...); - if (easing == "sinusoidalIn") return via(easing::sinusoidalIn, vs...); - if (easing == "sinusoidalOut") return via(easing::sinusoidalOut, vs...); - if (easing == "sinusoidalInOut") return via(easing::sinusoidalInOut, vs...); - if (easing == "exponentialIn") return via(easing::exponentialIn, vs...); - if (easing == "exponentialOut") return via(easing::exponentialOut, vs...); - if (easing == "exponentialInOut") return via(easing::exponentialInOut, vs...); - if (easing == "circularIn") return via(easing::circularIn, vs...); - if (easing == "circularOut") return via(easing::circularOut, vs...); - if (easing == "circularInOut") return via(easing::circularInOut, vs...); - if (easing == "bounceIn") return via(easing::bounceIn, vs...); - if (easing == "bounceOut") return via(easing::bounceOut, vs...); - if (easing == "bounceInOut") return via(easing::bounceInOut, vs...); - if (easing == "elasticIn") return via(easing::elasticIn, vs...); - if (easing == "elasticOut") return via(easing::elasticOut, vs...); - if (easing == "elasticInOut") return via(easing::elasticInOut, vs...); - if (easing == "backIn") return via(easing::backIn, vs...); - if (easing == "backOut") return via(easing::backOut, vs...); - if (easing == "backInOut") return via(easing::backInOut, vs...); - return via(easing::def, vs...); - } - template - template - tween & tween::via(const char * easing, Fs... vs) { - return via(std::string(easing)); - } template template diff --git a/include/tweenone.tcc b/include/tweenone.tcc index c2a52ad..8fed71f 100644 --- a/include/tweenone.tcc +++ b/include/tweenone.tcc @@ -99,49 +99,7 @@ namespace tweeny { } } - template - template - tween & tween::via(const std::string & easing, Fs... vs) { - if (easing == "stepped") return via(easing::stepped, vs...); - if (easing == "linear") return via(easing::linear, vs...); - if (easing == "quadraticIn") return via(easing::quadraticIn, vs...); - if (easing == "quadraticOut") return via(easing::quadraticOut, vs...); - if (easing == "quadraticInOut") return via(easing::quadraticInOut, vs...); - if (easing == "cubicIn") return via(easing::cubicIn, vs...); - if (easing == "cubicOut") return via(easing::cubicOut, vs...); - if (easing == "cubicInOut") return via(easing::cubicInOut, vs...); - if (easing == "quarticIn") return via(easing::quarticIn, vs...); - if (easing == "quarticOut") return via(easing::quarticOut, vs...); - if (easing == "quarticInOut") return via(easing::quarticInOut, vs...); - if (easing == "quinticIn") return via(easing::quinticIn, vs...); - if (easing == "quinticOut") return via(easing::quinticOut, vs...); - if (easing == "quinticInOut") return via(easing::quinticInOut, vs...); - if (easing == "sinusoidalIn") return via(easing::sinusoidalIn, vs...); - if (easing == "sinusoidalOut") return via(easing::sinusoidalOut, vs...); - if (easing == "sinusoidalInOut") return via(easing::sinusoidalInOut, vs...); - if (easing == "exponentialIn") return via(easing::exponentialIn, vs...); - if (easing == "exponentialOut") return via(easing::exponentialOut, vs...); - if (easing == "exponentialInOut") return via(easing::exponentialInOut, vs...); - if (easing == "circularIn") return via(easing::circularIn, vs...); - if (easing == "circularOut") return via(easing::circularOut, vs...); - if (easing == "circularInOut") return via(easing::circularInOut, vs...); - if (easing == "bounceIn") return via(easing::bounceIn, vs...); - if (easing == "bounceOut") return via(easing::bounceOut, vs...); - if (easing == "bounceInOut") return via(easing::bounceInOut, vs...); - if (easing == "elasticIn") return via(easing::elasticIn, vs...); - if (easing == "elasticOut") return via(easing::elasticOut, vs...); - if (easing == "elasticInOut") return via(easing::elasticInOut, vs...); - if (easing == "backIn") return via(easing::backIn, vs...); - if (easing == "backOut") return via(easing::backOut, vs...); - if (easing == "backInOut") return via(easing::backInOut, vs...); - return via(easing::def, vs...); - } - template - template - tween & tween::via(const char * easing, Fs... vs) { - return via(std::string(easing)); - } template template diff --git a/src/sandbox.cc b/src/sandbox.cc index b1bea48..191abc3 100644 --- a/src/sandbox.cc +++ b/src/sandbox.cc @@ -1,6 +1,6 @@ #include "tweeny.h" int main() { - auto tween1 = tweeny::from(0.0, 1.0f).to(1.0f, 0.0f).via("stepped", "linear"); + auto tween1 = tweeny::from(0.0, 1.0f).to(1.0f, 0.0f).via(tweeny::easing::stepped, tweeny::easing::linear); return 0; } \ No newline at end of file From b19932b922449bd942bbb0e334127e5f11daea4f Mon Sep 17 00:00:00 2001 From: Leonardo Date: Sun, 17 Aug 2025 16:16:45 -0300 Subject: [PATCH 02/58] remove enum-based `via` overloads for easing --- include/tween.h | 20 -------------------- include/tween.tcc | 40 ---------------------------------------- include/tweenone.tcc | 40 ---------------------------------------- 3 files changed, 100 deletions(-) diff --git a/include/tween.h b/include/tween.h index 9c85f0f..deccdbd 100644 --- a/include/tween.h +++ b/include/tween.h @@ -110,25 +110,6 @@ namespace tweeny { template tween & via(Fs... fs); - /** - * @brief Specifies the easing function for the last added point, accepting an enumeration. - * - * This will specify the easing between the last tween point added by @p to and its previous step. You can - * use a value from the @p tweeny::easing::enumerated enum. You can then have an enumeration of your own - * poiting to this enumerated enums, or use it directly. You can mix-and-match enumerated easings, functions - * and easing names. - * - * **Example**: - * - * @code - * auto tween1 = tweeny::from(0).to(100).via(tweeny::easing::enumerated::linear); - * auto tween2 = tweeny::from(0.0f, 100.0f).to(100.0f, 0.0f).via(tweeny::easing::linear, "backOut"); - * - * @param fs The functions - * @returns *this - * @see tweeny::easing - */ - template tween & via(easing::enumerated enumerated, Fs... fs); @@ -569,7 +550,6 @@ namespace tweeny { tween & to(T t); ///< @sa tween::to template tween & via(Fs... fs); ///< @sa tween::via template tween & via(int index, Fs... fs); ///< @sa tween::via - template tween & via(tweeny::easing::enumerated enumerated, Fs... fs); ///< @sa tween::via template tween & during(Ds... ds); ///< @sa tween::during const T & step(int32_t dt, bool suppressCallbacks = false); ///< @sa tween::step(int32_t dt, bool suppressCallbacks) const T & step(uint32_t dt, bool suppressCallbacks = false); ///< @sa tween::step(uint32_t dt, bool suppressCallbacks) diff --git a/include/tween.tcc b/include/tween.tcc index 662a529..df80fe7 100644 --- a/include/tween.tcc +++ b/include/tween.tcc @@ -66,46 +66,6 @@ namespace tweeny { return *this; } - template - template - tween & tween::via(easing::enumerated enumerated, Fs... vs) { - switch (enumerated) { - case easing::enumerated::def: return via(easing::def, vs...); - case easing::enumerated::linear: return via(easing::linear, vs...); - case easing::enumerated::stepped: return via(easing::stepped, vs...); - case easing::enumerated::quadraticIn: return via(easing::quadraticIn, vs...); - case easing::enumerated::quadraticOut: return via(easing::quadraticOut, vs...); - case easing::enumerated::quadraticInOut: return via(easing::quadraticInOut, vs...); - case easing::enumerated::cubicIn: return via(easing::cubicIn, vs...); - case easing::enumerated::cubicOut: return via(easing::cubicOut, vs...); - case easing::enumerated::cubicInOut: return via(easing::cubicInOut, vs...); - case easing::enumerated::quarticIn: return via(easing::quarticIn, vs...); - case easing::enumerated::quarticOut: return via(easing::quarticOut, vs...); - case easing::enumerated::quarticInOut: return via(easing::quarticInOut, vs...); - case easing::enumerated::quinticIn: return via(easing::quinticIn, vs...); - case easing::enumerated::quinticOut: return via(easing::quinticOut, vs...); - case easing::enumerated::quinticInOut: return via(easing::quinticInOut, vs...); - case easing::enumerated::sinusoidalIn: return via(easing::sinusoidalIn, vs...); - case easing::enumerated::sinusoidalOut: return via(easing::sinusoidalOut, vs...); - case easing::enumerated::sinusoidalInOut: return via(easing::sinusoidalInOut, vs...); - case easing::enumerated::exponentialIn: return via(easing::exponentialIn, vs...); - case easing::enumerated::exponentialOut: return via(easing::exponentialOut, vs...); - case easing::enumerated::exponentialInOut: return via(easing::exponentialInOut, vs...); - case easing::enumerated::circularIn: return via(easing::circularIn, vs...); - case easing::enumerated::circularOut: return via(easing::circularOut, vs...); - case easing::enumerated::circularInOut: return via(easing::circularInOut, vs...); - case easing::enumerated::bounceIn: return via(easing::bounceIn, vs...); - case easing::enumerated::bounceOut: return via(easing::bounceOut, vs...); - case easing::enumerated::bounceInOut: return via(easing::bounceInOut, vs...); - case easing::enumerated::elasticIn: return via(easing::elasticIn, vs...); - case easing::enumerated::elasticOut: return via(easing::elasticOut, vs...); - case easing::enumerated::elasticInOut: return via(easing::elasticInOut, vs...); - case easing::enumerated::backIn: return via(easing::backIn, vs...); - case easing::enumerated::backOut: return via(easing::backOut, vs...); - case easing::enumerated::backInOut: return via(easing::backInOut, vs...); - default: return via(easing::def, vs...); - } - } diff --git a/include/tweenone.tcc b/include/tweenone.tcc index 8fed71f..1aa40c3 100644 --- a/include/tweenone.tcc +++ b/include/tweenone.tcc @@ -58,46 +58,6 @@ namespace tweeny { return *this; } - template - template - tween & tween::via(easing::enumerated enumerated, Fs... vs) { - switch (enumerated) { - case easing::enumerated::def: return via(easing::def, vs...); - case easing::enumerated::linear: return via(easing::linear, vs...); - case easing::enumerated::stepped: return via(easing::stepped, vs...); - case easing::enumerated::quadraticIn: return via(easing::quadraticIn, vs...); - case easing::enumerated::quadraticOut: return via(easing::quadraticOut, vs...); - case easing::enumerated::quadraticInOut: return via(easing::quadraticInOut, vs...); - case easing::enumerated::cubicIn: return via(easing::cubicIn, vs...); - case easing::enumerated::cubicOut: return via(easing::cubicOut, vs...); - case easing::enumerated::cubicInOut: return via(easing::cubicInOut, vs...); - case easing::enumerated::quarticIn: return via(easing::quarticIn, vs...); - case easing::enumerated::quarticOut: return via(easing::quarticOut, vs...); - case easing::enumerated::quarticInOut: return via(easing::quarticInOut, vs...); - case easing::enumerated::quinticIn: return via(easing::quinticIn, vs...); - case easing::enumerated::quinticOut: return via(easing::quinticOut, vs...); - case easing::enumerated::quinticInOut: return via(easing::quinticInOut, vs...); - case easing::enumerated::sinusoidalIn: return via(easing::sinusoidalIn, vs...); - case easing::enumerated::sinusoidalOut: return via(easing::sinusoidalOut, vs...); - case easing::enumerated::sinusoidalInOut: return via(easing::sinusoidalInOut, vs...); - case easing::enumerated::exponentialIn: return via(easing::exponentialIn, vs...); - case easing::enumerated::exponentialOut: return via(easing::exponentialOut, vs...); - case easing::enumerated::exponentialInOut: return via(easing::exponentialInOut, vs...); - case easing::enumerated::circularIn: return via(easing::circularIn, vs...); - case easing::enumerated::circularOut: return via(easing::circularOut, vs...); - case easing::enumerated::circularInOut: return via(easing::circularInOut, vs...); - case easing::enumerated::bounceIn: return via(easing::bounceIn, vs...); - case easing::enumerated::bounceOut: return via(easing::bounceOut, vs...); - case easing::enumerated::bounceInOut: return via(easing::bounceInOut, vs...); - case easing::enumerated::elasticIn: return via(easing::elasticIn, vs...); - case easing::enumerated::elasticOut: return via(easing::elasticOut, vs...); - case easing::enumerated::elasticInOut: return via(easing::elasticInOut, vs...); - case easing::enumerated::backIn: return via(easing::backIn, vs...); - case easing::enumerated::backOut: return via(easing::backOut, vs...); - case easing::enumerated::backInOut: return via(easing::backInOut, vs...); - default: return via(easing::def, vs...); - } - } From 8bc747129a231d861c8cc3b163d55d51a2f74e52 Mon Sep 17 00:00:00 2001 From: Leonardo Date: Sun, 17 Aug 2025 16:19:51 -0300 Subject: [PATCH 03/58] simplify `via` method templates and adjust type usage --- include/tween.h | 4 ++-- include/tween.tcc | 8 ++++---- include/tweenone.tcc | 8 ++++---- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/include/tween.h b/include/tween.h index deccdbd..638a36a 100644 --- a/include/tween.h +++ b/include/tween.h @@ -81,7 +81,7 @@ namespace tweeny { * @param t, vs Point values * @returns *this */ - tween & to(T t, Ts... vs); + tween & to(T t, Ts... vs); /** * @brief Specifies the easing function for the last added point. @@ -107,7 +107,7 @@ namespace tweeny { * @returns *this * @see tweeny::easing */ - template tween & via(Fs... fs); + template tween & via(Fs... fs); diff --git a/include/tween.tcc b/include/tween.tcc index df80fe7..10a54a2 100644 --- a/include/tween.tcc +++ b/include/tween.tcc @@ -54,15 +54,15 @@ namespace tweeny { template template - inline tween & tween::via(Fs... vs) { - points.at(points.size() - 2).via(vs...); + inline tween & tween::via(Fs... fs) { + points.at(points.size() - 2).via(fs...); return *this; } template template - inline tween & tween::via(int index, Fs... vs) { - points.at(static_cast(index)).via(vs...); + inline tween & tween::via(const int index, Fs... fs) { + points.at(static_cast(index)).via(fs...); return *this; } diff --git a/include/tweenone.tcc b/include/tweenone.tcc index 1aa40c3..489d836 100644 --- a/include/tweenone.tcc +++ b/include/tweenone.tcc @@ -46,15 +46,15 @@ namespace tweeny { template template - inline tween & tween::via(Fs... vs) { - points.at(points.size() - 2).via(vs...); + inline tween & tween::via(Fs... fs) { + points.at(points.size() - 2).via(fs...); return *this; } template template - inline tween & tween::via(int index, Fs... vs) { - points.at(static_cast(index)).via(vs...); + inline tween & tween::via(int index, Fs... fs) { + points.at(static_cast(index)).via(fs...); return *this; } From eea7c916d07ba94268f2f9723d0ca867ac454085 Mon Sep 17 00:00:00 2001 From: Leonardo Date: Sun, 17 Aug 2025 16:20:20 -0300 Subject: [PATCH 04/58] remove unnecessary `inline` specifiers from tween template methods --- include/tween.tcc | 38 +++++++++++++++++++------------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/include/tween.tcc b/include/tween.tcc index 10a54a2..37d53a7 100644 --- a/include/tween.tcc +++ b/include/tween.tcc @@ -41,27 +41,27 @@ namespace tweeny { } } - template inline tween tween::from(T t, Ts... vs) { return tween(t, vs...); } - template inline tween::tween() { } - template inline tween::tween(T t, Ts... vs) { + template tween tween::from(T t, Ts... vs) { return tween(t, vs...); } + template tween::tween() { } + template tween::tween(T t, Ts... vs) { points.emplace_back(t, vs...); } - template inline tween & tween::to(T t, Ts... vs) { + template tween & tween::to(T t, Ts... vs) { points.emplace_back(t, vs...); return *this; } template template - inline tween & tween::via(Fs... fs) { + tween & tween::via(Fs... fs) { points.at(points.size() - 2).via(fs...); return *this; } template template - inline tween & tween::via(const int index, Fs... fs) { + tween & tween::via(const int index, Fs... fs) { points.at(static_cast(index)).via(fs...); return *this; } @@ -71,7 +71,7 @@ namespace tweeny { template template - inline tween & tween::during(Ds... ds) { + tween & tween::during(Ds... ds) { total = 0; points.at(points.size() - 2).during(ds...); for (detail::tweenpoint & p : points) { @@ -82,17 +82,17 @@ namespace tweeny { } template - inline const typename detail::tweentraits::valuesType & tween::step(int32_t dt, bool suppress) { + const typename detail::tweentraits::valuesType & tween::step(int32_t dt, bool suppress) { return step(static_cast(dt)/static_cast(total), suppress); } template - inline const typename detail::tweentraits::valuesType & tween::step(uint32_t dt, bool suppress) { + const typename detail::tweentraits::valuesType & tween::step(uint32_t dt, bool suppress) { return step(static_cast(dt), suppress); } template - inline const typename detail::tweentraits::valuesType & tween::step(float dp, bool suppress) { + const typename detail::tweentraits::valuesType & tween::step(float dp, bool suppress) { dp *= currentDirection; seek(currentProgress + dp, true); if (!suppress) dispatch(onStepCallbacks); @@ -100,7 +100,7 @@ namespace tweeny { } template - inline const typename detail::tweentraits::valuesType & tween::seek(float p, bool suppress) { + const typename detail::tweentraits::valuesType & tween::seek(float p, bool suppress) { p = detail::clip(p, 0.0f, 1.0f); currentProgress = p; render(p); @@ -109,18 +109,18 @@ namespace tweeny { } template - inline const typename detail::tweentraits::valuesType & tween::seek(int32_t t, bool suppress) { + const typename detail::tweentraits::valuesType & tween::seek(int32_t t, bool suppress) { return seek(static_cast(t) / static_cast(total), suppress); } template - inline uint32_t tween::duration() const { + uint32_t tween::duration() const { return total; } template template - inline void tween::interpolate(float prog, unsigned point, typename traits::valuesType & values, detail::int2type) const { + void tween::interpolate(float prog, unsigned point, typename traits::valuesType & values, detail::int2type) const { auto & p = points.at(point); auto pointDuration = uint32_t(p.duration() - (p.stacked - (prog * static_cast(total)))); float pointTotal = static_cast(pointDuration) / static_cast(p.duration(I)); @@ -131,7 +131,7 @@ namespace tweeny { } template - inline void tween::interpolate(float prog, unsigned point, typename traits::valuesType & values, detail::int2type<0>) const { + void tween::interpolate(float prog, unsigned point, typename traits::valuesType & values, detail::int2type<0>) const { auto & p = points.at(point); auto pointDuration = uint32_t(p.duration() - (p.stacked - (prog * static_cast(total)))); float pointTotal = static_cast(pointDuration) / static_cast(p.duration(0)); @@ -141,7 +141,7 @@ namespace tweeny { } template - inline void tween::render(float p) { + void tween::render(float p) { currentPoint = pointAt(p); interpolate(p, currentPoint, current, detail::int2type{ }); } @@ -243,16 +243,16 @@ namespace tweeny { } template - inline const typename detail::tweentraits::valuesType & tween::jump(std::size_t p, bool suppress) { + const typename detail::tweentraits::valuesType & tween::jump(std::size_t p, bool suppress) { p = detail::clip(p, static_cast(0), points.size() -1); return seek(static_cast(points.at(p).stacked), suppress); } - template inline uint16_t tween::point() const { + template uint16_t tween::point() const { return currentPoint; } - template inline uint16_t tween::pointAt(float progress) const { + template uint16_t tween::pointAt(float progress) const { progress = detail::clip(progress, 0.0f, 1.0f); uint32_t t = static_cast(progress * total); uint16_t point = 0; From abec634434be3ab476b1b8b0641038052d827199 Mon Sep 17 00:00:00 2001 From: Leonardo Date: Sun, 17 Aug 2025 16:24:38 -0300 Subject: [PATCH 05/58] use nested namespace syntax for `tweeny::detail` --- include/dispatcher.h | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/include/dispatcher.h b/include/dispatcher.h index 17f9a75..8798578 100644 --- a/include/dispatcher.h +++ b/include/dispatcher.h @@ -30,24 +30,24 @@ #include -namespace tweeny { - namespace detail { - template struct seq { }; - template struct gens : gens { }; - template struct gens<0, S...> { - typedef seq type; - }; - - template - R dispatch(Func && f, TupleType && args, seq) { - return f(std::get(args) ...); - } - - template - R call(Func && f, const std::tuple & args) { - return dispatch(f, args, typename gens::type()); - } + +namespace tweeny::detail { + template struct seq { }; + template struct gens : gens { }; + template struct gens<0, S...> { + typedef seq type; + }; + + template + R dispatch(Func && f, TupleType && args, seq) { + return f(std::get(args) ...); + } + + template + R call(Func && f, const std::tuple & args) { + return dispatch(f, args, typename gens::type()); } } + #endif //TWEENY_DISPATCHER_H From 7497b7d98876350ed40494da326b6b1e11d17137 Mon Sep 17 00:00:00 2001 From: Leonardo Date: Sun, 17 Aug 2025 16:25:06 -0300 Subject: [PATCH 06/58] remove `enumerated` easing enum from easing class --- include/easing.h | 44 +------------------------------------------- 1 file changed, 1 insertion(+), 43 deletions(-) diff --git a/include/easing.h b/include/easing.h index 48f2835..71887ad 100644 --- a/include/easing.h +++ b/include/easing.h @@ -129,48 +129,6 @@ namespace tweeny { */ class easing { public: - /** - * @brief Enumerates all easings to aid in runtime when adding easins to a tween using tween::via - * - * The aim of this enum is to help in situations where the easing doesn't come straight from the C++ - * code but rather from a configuration file or some sort of external paramenter. - */ - enum class enumerated { - def, - linear, - stepped, - quadraticIn, - quadraticOut, - quadraticInOut, - cubicIn, - cubicOut, - cubicInOut, - quarticIn, - quarticOut, - quarticInOut, - quinticIn, - quinticOut, - quinticInOut, - sinusoidalIn, - sinusoidalOut, - sinusoidalInOut, - exponentialIn, - exponentialOut, - exponentialInOut, - circularIn, - circularOut, - circularInOut, - bounceIn, - bounceOut, - bounceInOut, - elasticIn, - elasticOut, - elasticInOut, - backIn, - backOut, - backInOut - }; - /** * @ingroup stepped * @brief Value is constant. @@ -181,7 +139,7 @@ namespace tweeny { return start; } } stepped = steppedEasing{}; - + /** * @ingroup default * @brief Values change with constant speed for arithmetic type only. The non-arithmetic it will be constant. From a86f18179ffdde427a304f2addca664754439b6b Mon Sep 17 00:00:00 2001 From: Leonardo Date: Sun, 17 Aug 2025 16:31:59 -0300 Subject: [PATCH 07/58] update easing implementations, simplify logic and adopt modern C++ features --- include/easing.h | 95 +++++++++++++++++++++++++----------------------- 1 file changed, 50 insertions(+), 45 deletions(-) diff --git a/include/easing.h b/include/easing.h index 71887ad..a72e5b9 100644 --- a/include/easing.h +++ b/include/easing.h @@ -136,6 +136,8 @@ namespace tweeny { static constexpr struct steppedEasing { template static T run(float position, T start, T end) { + (void) position; + (void) end; return start; } } stepped = steppedEasing{}; @@ -148,7 +150,7 @@ namespace tweeny { template struct voidify { using type = void; }; template using void_t = typename voidify::type; - template + template struct supports_arithmetic_operations : std::false_type {}; template @@ -162,7 +164,7 @@ namespace tweeny { template - static typename std::enable_if::value, T>::type run(float position, T start, T end) { + static std::enable_if_t, T> run(float position, T start, T end) { return static_cast(roundf((end - start) * position + start)); } @@ -172,7 +174,9 @@ namespace tweeny { } template - static typename std::enable_if::value, T>::type run(float position, T start, T end) { + static std::enable_if_t::value, T> run(float position, T start, T end) { + (void) position; + (void) end; return start; } } def = defaultEasing{}; @@ -183,12 +187,12 @@ namespace tweeny { */ static constexpr struct linearEasing { template - static typename std::enable_if::value, T>::type run(float position, T start, T end) { + static std::enable_if_t, T> run(float position, T start, T end) { return static_cast(roundf((end - start) * position + start)); } template - static typename std::enable_if::value, T>::type run(float position, T start, T end) { + static std::enable_if_t, T> run(float position, T start, T end) { return static_cast((end - start) * position + start); } } linear = linearEasing{}; @@ -224,11 +228,11 @@ namespace tweeny { static T run(float position, T start, T end) { position *= 2; if (position < 1) { - return static_cast(((end - start) / 2) * position * position + start); + return static_cast((end - start) / 2 * position * position + start); } --position; - return static_cast((-(end - start) / 2) * (position * (position - 2) - 1) + start); + return static_cast(-(end - start) / 2 * (position * (position - 2) - 1) + start); } } quadraticInOut = quadraticInOutEasing{}; @@ -264,10 +268,10 @@ namespace tweeny { static T run(float position, T start, T end) { position *= 2; if (position < 1) { - return static_cast(((end - start) / 2) * position * position * position + start); + return static_cast((end - start) / 2 * position * position * position + start); } position -= 2; - return static_cast(((end - start) / 2) * (position * position * position + 2) + start); + return static_cast((end - start) / 2 * (position * position * position + 2) + start); } } cubicInOut = cubicInOutEasing{}; @@ -303,11 +307,11 @@ namespace tweeny { static T run(float position, T start, T end) { position *= 2; if (position < 1) { - return static_cast(((end - start) / 2) * (position * position * position * position) + + return static_cast((end - start) / 2 * (position * position * position * position) + start); } position -= 2; - return static_cast((-(end - start) / 2) * (position * position * position * position - 2) + + return static_cast(-(end - start) / 2 * (position * position * position * position - 2) + start); } } quarticInOut = quarticInOutEasing{}; @@ -346,12 +350,12 @@ namespace tweeny { position *= 2; if (position < 1) { return static_cast( - ((end - start) / 2) * (position * position * position * position * position) + + (end - start) / 2 * (position * position * position * position * position) + start); } position -= 2; return static_cast( - ((end - start) / 2) * (position * position * position * position * position + 2) + + (end - start) / 2 * (position * position * position * position * position + 2) + start); } } quinticInOut = quinticInOutEasing{}; @@ -362,7 +366,7 @@ namespace tweeny { */ static constexpr struct sinusoidalInEasing { template - static T run(float position, T start, T end) { + static T run(const float position, T start, T end) { return static_cast(-(end - start) * cosf(position * static_cast(M_PI) / 2) + (end - start) + start); } } sinusoidalIn = sinusoidalInEasing{}; @@ -373,7 +377,7 @@ namespace tweeny { */ static constexpr struct sinusoidalOutEasing { template - static T run(float position, T start, T end) { + static T run(const float position, T start, T end) { return static_cast((end - start) * sinf(position * static_cast(M_PI) / 2) + start); } } sinusoidalOut = sinusoidalOutEasing{}; @@ -384,8 +388,8 @@ namespace tweeny { */ static constexpr struct sinusoidalInOutEasing { template - static T run(float position, T start, T end) { - return static_cast((-(end - start) / 2) * (cosf(position * static_cast(M_PI)) - 1) + start); + static T run(const float position, T start, T end) { + return static_cast(-(end - start) / 2 * (cosf(position * static_cast(M_PI)) - 1) + start); } } sinusoidalInOut = sinusoidalInOutEasing{}; @@ -395,7 +399,7 @@ namespace tweeny { */ static constexpr struct exponentialInEasing { template - static T run(float position, T start, T end) { + static T run(const float position, T start, T end) { return static_cast((end - start) * powf(2, 10 * (position - 1)) + start); } } exponentialIn = exponentialInEasing{}; @@ -406,7 +410,7 @@ namespace tweeny { */ static constexpr struct exponentialOutEasing { template - static T run(float position, T start, T end) { + static T run(const float position, T start, T end) { return static_cast((end - start) * (-powf(2, -10 * position) + 1) + start); } } exponentialOut = exponentialOutEasing{}; @@ -420,10 +424,10 @@ namespace tweeny { static T run(float position, T start, T end) { position *= 2; if (position < 1) { - return static_cast(((end - start) / 2) * powf(2, 10 * (position - 1)) + start); + return static_cast((end - start) / 2 * powf(2, 10 * (position - 1)) + start); } --position; - return static_cast(((end - start) / 2) * (-powf(2, -10 * position) + 2) + start); + return static_cast((end - start) / 2 * (-powf(2, -10 * position) + 2) + start); } } exponentialInOut = exponentialInOutEasing{}; @@ -433,7 +437,7 @@ namespace tweeny { */ static constexpr struct circularInEasing { template - static T run(float position, T start, T end) { + static T run(const float position, T start, T end) { return static_cast( -(end - start) * (sqrtf(1 - position * position) - 1) + start ); } } circularIn = circularInEasing{}; @@ -446,7 +450,7 @@ namespace tweeny { template static T run(float position, T start, T end) { --position; - return static_cast((end - start) * (sqrtf(1 - position * position)) + start); + return static_cast((end - start) * sqrtf(1 - position * position) + start); } } circularOut = circularOutEasing{}; @@ -459,11 +463,11 @@ namespace tweeny { static T run(float position, T start, T end) { position *= 2; if (position < 1) { - return static_cast((-(end - start) / 2) * (sqrtf(1 - position * position) - 1) + start); + return static_cast(-(end - start) / 2 * (sqrtf(1 - position * position) - 1) + start); } position -= 2; - return static_cast(((end - start) / 2) * (sqrtf(1 - position * position) + 1) + start); + return static_cast((end - start) / 2 * (sqrtf(1 - position * position) + 1) + start); } } circularInOut = circularInOutEasing{}; @@ -474,7 +478,7 @@ namespace tweeny { static constexpr struct bounceInEasing { template static T run(float position, T start, T end) { - return (end - start) - bounceOut.run((1 - position), T(), (end - start)) + start; + return end - start - bounceOut.run(1 - position, T(), (end - start)) + start; } } bounceIn = bounceInEasing{}; @@ -486,18 +490,19 @@ namespace tweeny { template static T run(float position, T start, T end) { T c = end - start; - if (position < (1 / 2.75f)) { + if (position < 1 / 2.75f) { return static_cast(c * (7.5625f * position * position) + start); - } else if (position < (2.0f / 2.75f)) { + } + if (position < 2.0f / 2.75f) { float postFix = position -= (1.5f / 2.75f); return static_cast(c * (7.5625f * (postFix) * position + .75f) + start); - } else if (position < (2.5f / 2.75f)) { + } + if (position < 2.5f / 2.75f) { float postFix = position -= (2.25f / 2.75f); return static_cast(c * (7.5625f * (postFix) * position + .9375f) + start); - } else { - float postFix = position -= (2.625f / 2.75f); - return static_cast(c * (7.5625f * (postFix) * position + .984375f) + start); } + const float postFix = position -= (2.625f / 2.75f); + return static_cast(c * (7.5625f * postFix * position + .984375f) + start); } } bounceOut = bounceOutEasing{}; @@ -508,8 +513,8 @@ namespace tweeny { static constexpr struct bounceInOutEasing { template static T run(float position, T start, T end) { - if (position < 0.5f) return static_cast(bounceIn.run(position * 2, T(), (end - start)) * .5f + start); - else return static_cast(bounceOut.run((position * 2 - 1), T(), (end - start)) * .5f + (end - start) * .5f + start); + if (position < 0.5f) return static_cast(bounceIn.run(position * 2, T(), end - start) * .5f + start); + return static_cast(bounceOut.run(position * 2 - 1, T(), end - start) * .5f + (end - start) * .5f + start); } } bounceInOut = bounceInOutEasing{}; @@ -522,9 +527,9 @@ namespace tweeny { static T run(float position, T start, T end) { if (position <= 0.00001f) return start; if (position >= 0.999f) return end; - float p = .3f; + const float p = .3f; auto a = end - start; - float s = p / 4; + const float s = p / 4; float postFix = a * powf(2, 10 * (position -= 1)); // this is a fix, again, with post-increment operators return static_cast(-(postFix * sinf((position - s) * (2 * static_cast(M_PI)) / p)) + start); @@ -537,7 +542,7 @@ namespace tweeny { */ static constexpr struct elasticOutEasing { template - static T run(float position, T start, T end) { + static T run(const float position, T start, T end) { if (position <= 0.00001f) return start; if (position >= 0.999f) return end; float p = .3f; @@ -557,7 +562,7 @@ namespace tweeny { if (position <= 0.00001f) return start; if (position >= 0.999f) return end; position *= 2; - float p = (.3f * 1.5f); + float p = .3f * 1.5f; auto a = end - start; float s = p / 4; float postFix; @@ -578,9 +583,9 @@ namespace tweeny { static constexpr struct backInEasing { template static T run(float position, T start, T end) { - float s = 1.70158f; + const float s = 1.70158f; float postFix = position; - return static_cast((end - start) * (postFix) * position * ((s + 1) * position - s) + start); + return static_cast((end - start) * postFix * position * ((s + 1) * position - s) + start); } } backIn = backInEasing{}; @@ -591,7 +596,7 @@ namespace tweeny { static constexpr struct backOutEasing { template static T run(float position, T start, T end) { - float s = 1.70158f; + const float s = 1.70158f; position -= 1; return static_cast((end - start) * ((position) * position * ((s + 1) * position + s) + 1) + start); } @@ -609,10 +614,10 @@ namespace tweeny { auto b = start; auto c = end - start; float d = 1; - s *= (1.525f); - if ((t /= d / 2) < 1) return static_cast(c / 2 * (t * t * (((s) + 1) * t - s)) + b); - float postFix = t -= 2; - return static_cast(c / 2 * ((postFix) * t * (((s) + 1) * t + s) + 2) + b); + s *= 1.525f; + if ((t /= d / 2) < 1) return static_cast(c / 2 * (t * t * ((s + 1) * t - s)) + b); + const float postFix = t -= 2; + return static_cast(c / 2 * (postFix * t * ((s + 1) * t + s) + 2) + b); } } backInOut = backInOutEasing{}; }; From e12994a03dc42c590508d58e6016d7382ae324c5 Mon Sep 17 00:00:00 2001 From: Leonardo Date: Sun, 17 Aug 2025 16:37:17 -0300 Subject: [PATCH 08/58] refactor: remove redundant `inline` specifiers, adopt `const` for parameters, and simplify template methods --- include/easingresolve.h | 6 ++-- include/tweenone.tcc | 76 +++++++++++++++++++---------------------- 2 files changed, 38 insertions(+), 44 deletions(-) diff --git a/include/easingresolve.h b/include/easingresolve.h index f7d266e..e188cc8 100644 --- a/include/easingresolve.h +++ b/include/easingresolve.h @@ -34,8 +34,8 @@ #include #include "easing.h" -namespace tweeny { - namespace detail { + + namespace tweeny::detail { using std::get; template @@ -122,6 +122,6 @@ namespace tweeny { DECLARE_EASING_RESOLVE(elastic); DECLARE_EASING_RESOLVE(back); } -} + #endif //TWEENY_EASINGRESOLVE_H diff --git a/include/tweenone.tcc b/include/tweenone.tcc index 489d836..8c82f1a 100644 --- a/include/tweenone.tcc +++ b/include/tweenone.tcc @@ -29,31 +29,28 @@ #ifndef TWEENY_TWEENONE_TCC #define TWEENY_TWEENONE_TCC -#include "tween.h" -#include "dispatcher.h" - namespace tweeny { - template inline tween tween::from(T t) { return tween(t); } - template inline tween::tween() { } - template inline tween::tween(T t) { + template tween tween::from(T t) { return tween(t); } + template tween::tween() = default; + template tween::tween(T t) { points.emplace_back(t); } - template inline tween & tween::to(T t) { + template tween & tween::to(T t) { points.emplace_back(t); return *this; } template template - inline tween & tween::via(Fs... fs) { + tween & tween::via(Fs... fs) { points.at(points.size() - 2).via(fs...); return *this; } template template - inline tween & tween::via(int index, Fs... fs) { + tween & tween::via(const int index, Fs... fs) { points.at(static_cast(index)).via(fs...); return *this; } @@ -63,7 +60,7 @@ namespace tweeny { template template - inline tween & tween::during(Ds... ds) { + tween & tween::during(Ds... ds) { total = 0; points.at(points.size() - 2).during(ds...); for (detail::tweenpoint & p : points) { @@ -74,51 +71,51 @@ namespace tweeny { } template - inline const T & tween::step(int32_t dt, bool suppress) { - return step(static_cast(dt)/static_cast(total), suppress); + const T & tween::step(const int32_t dt, const bool suppressCallbacks) { + return step(static_cast(dt)/static_cast(total), suppressCallbacks); } template - inline const T & tween::step(uint32_t dt, bool suppress) { - return step(static_cast(dt), suppress); + const T & tween::step(const uint32_t dt, const bool suppressCallbacks) { + return step(static_cast(dt), suppressCallbacks); } template - inline const T & tween::step(float dp, bool suppress) { + const T & tween::step(float dp, const bool suppressCallbacks) { dp *= currentDirection; seek(currentProgress + dp, true); - if (!suppress) dispatch(onStepCallbacks); + if (!suppressCallbacks) dispatch(onStepCallbacks); return current; } template - inline const T & tween::seek(float p, bool suppress) { + const T & tween::seek(float p, const bool suppressCallbacks) { p = detail::clip(p, 0.0f, 1.0f); currentProgress = p; render(p); - if (!suppress) dispatch(onSeekCallbacks); + if (!suppressCallbacks) dispatch(onSeekCallbacks); return current; } template - inline const T & tween::seek(int32_t t, bool suppress) { - return seek(static_cast(t) / static_cast(total), suppress); + const T & tween::seek(const int32_t d, const bool suppressCallbacks) { + return seek(static_cast(d) / static_cast(total), suppressCallbacks); } template - inline const T & tween::seek(uint32_t t, bool suppress) { - return seek(static_cast(t) / static_cast(total), suppress); + const T & tween::seek(const uint32_t t, const bool suppressCallbacks) { + return seek(static_cast(t) / static_cast(total), suppressCallbacks); } template - inline uint32_t tween::duration() const { + uint32_t tween::duration() const { return total; } template - inline void tween::interpolate(float prog, unsigned point, T & value) const { + void tween::interpolate(const float prog, unsigned point, T & value) const { auto & p = points.at(point); - auto pointDuration = uint32_t(p.duration() - (p.stacked - (prog * static_cast(total)))); + auto pointDuration = uint32_t(p.duration() - (p.stacked - prog * static_cast(total))); float pointTotal = static_cast(pointDuration) / static_cast(p.duration()); if (pointTotal > 1.0f) pointTotal = 1.0f; auto easing = std::get<0>(p.easings); @@ -126,7 +123,7 @@ namespace tweeny { } template - inline void tween::render(float p) { + void tween::render(const float p) { currentPoint = pointAt(p); interpolate(p, currentPoint, current); } @@ -139,13 +136,13 @@ namespace tweeny { template tween & tween::onStep(typename detail::tweentraits::noValuesCallbackType callback) { - onStepCallbacks.push_back([callback](tween & tween, T) { return callback(tween); }); + onStepCallbacks.push_back([callback](tween & tween, T) { return callback(tween); }); return *this; } template tween & tween::onStep(typename detail::tweentraits::noTweenCallbackType callback) { - onStepCallbacks.push_back([callback](tween &, T v) { return callback(v); }); + onStepCallbacks.push_back([callback](tween &, T v) { return callback(v); }); return *this; } @@ -157,13 +154,13 @@ namespace tweeny { template tween & tween::onSeek(typename detail::tweentraits::noValuesCallbackType callback) { - onSeekCallbacks.push_back([callback](tween & t, T) { return callback(t); }); + onSeekCallbacks.push_back([callback](tween & t, T) { return callback(t); }); return *this; } template tween & tween::onSeek(typename detail::tweentraits::noTweenCallbackType callback) { - onSeekCallbacks.push_back([callback](tween &, T v) { return callback(v); }); + onSeekCallbacks.push_back([callback](tween &, T v) { return callback(v); }); return *this; } @@ -172,11 +169,10 @@ namespace tweeny { std::vector dismissed; for (size_t i = 0; i < cbVector.size(); ++i) { auto && cb = cbVector[i]; - bool dismiss = cb(*this, current); - if (dismiss) dismissed.push_back(i); + if (cb(*this, current)) dismissed.push_back(i); } - if (dismissed.size() > 0) { + if (!dismissed.empty()) { for (size_t i = 0; i < dismissed.size(); ++i) { size_t index = dismissed[i]; cbVector[index] = cbVector.at(cbVector.size() - 1 - i); @@ -199,7 +195,7 @@ namespace tweeny { } template - T tween::peek(uint32_t time) const { + T tween::peek(const uint32_t time) const { T value; float progress = static_cast(time) / static_cast(total); interpolate(progress, pointAt(progress), value); @@ -230,18 +226,16 @@ namespace tweeny { } template - inline const T & tween::jump(size_t p, bool suppress) { - p = detail::clip(p, static_cast(0), points.size() -1); - return seek(points.at(p).stacked, suppress); + const T & tween::jump(size_t point, bool suppressCallbacks) { + point = detail::clip(point, static_cast(0), points.size() -1); + return seek(points.at(point).stacked, suppressCallbacks); } - template inline uint16_t tween::point() const { + template uint16_t tween::point() const { return currentPoint; } - - - template inline uint16_t tween::pointAt(float progress) const { + template uint16_t tween::pointAt(float progress) const { progress = detail::clip(progress, 0.0f, 1.0f); auto t = static_cast(progress * total); uint16_t point = 0; From 68476236e013b9d77267823f6f293545e6f95e8f Mon Sep 17 00:00:00 2001 From: Leonardo Date: Sun, 17 Aug 2025 16:37:47 -0300 Subject: [PATCH 09/58] use nested namespace syntax for `tweeny::detail` in int2type header --- include/int2type.h | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/include/int2type.h b/include/int2type.h index a0d8f17..8adf77c 100644 --- a/include/int2type.h +++ b/include/int2type.h @@ -29,9 +29,9 @@ #ifndef TWEENY_INT2TYPE_H #define TWEENY_INT2TYPE_H -namespace tweeny { - namespace detail { - template struct int2type { }; - } -} + + namespace tweeny::detail { + template struct int2type { }; + } + #endif //TWEENY_INT2TYPE_H From 6c1e8d8417a893a90d94600367ca28202a235d62 Mon Sep 17 00:00:00 2001 From: Leonardo Date: Sun, 17 Aug 2025 16:39:56 -0300 Subject: [PATCH 10/58] simplify `easingresolve` implementation and adopt modern C++ syntax --- include/easingresolve.h | 167 ++++++++++++++++++++-------------------- 1 file changed, 83 insertions(+), 84 deletions(-) diff --git a/include/easingresolve.h b/include/easingresolve.h index e188cc8..17d8281 100644 --- a/include/easingresolve.h +++ b/include/easingresolve.h @@ -34,94 +34,93 @@ #include #include "easing.h" +namespace tweeny::detail { + using std::get; + + template + struct easingresolve { + static void impl(FunctionTuple &b, Fs... fs) { + if (sizeof...(Fs) == 0) return; + easingresolve::impl(b, fs...); + } + }; + + template + struct easingresolve { + static void impl(FunctionTuple &b, F1 f1, Fs... fs) { + get(b) = f1; + easingresolve::impl(b, fs...); + } + }; + + template + struct easingresolve { + typedef std::tuple_element_t ArgType; - namespace tweeny::detail { - using std::get; - - template - struct easingresolve { - static void impl(FunctionTuple &b, Fs... fs) { - if (sizeof...(Fs) == 0) return; - easingresolve::impl(b, fs...); - } - }; - - template - struct easingresolve { - static void impl(FunctionTuple &b, F1 f1, Fs... fs) { - get(b) = f1; - easingresolve::impl(b, fs...); - } - }; - - template - struct easingresolve { - typedef typename std::tuple_element::type ArgType; - - static void impl(FunctionTuple &b, easing::steppedEasing, Fs... fs) { - get(b) = easing::stepped.run; - easingresolve::impl(b, fs...); - } - }; - - template - struct easingresolve { - typedef typename std::tuple_element::type ArgType; - - static void impl(FunctionTuple &b, easing::linearEasing, Fs... fs) { - get(b) = easing::linear.run; - easingresolve::impl(b, fs...); - } - }; - template - struct easingresolve { - typedef typename std::tuple_element::type ArgType; - - static void impl(FunctionTuple &b, easing::defaultEasing, Fs... fs) { - get(b) = easing::def.run; - easingresolve::impl(b, fs...); - } - }; - - #define DECLARE_EASING_RESOLVE(__EASING_TYPE__) \ - template \ - struct easingresolve { \ - typedef typename std::tuple_element::type ArgType; \ - static void impl(FunctionTuple & b, decltype(easing::__EASING_TYPE__ ## In), Fs... fs) { \ - get(b) = easing::__EASING_TYPE__ ## In.run; \ - easingresolve::impl(b, fs...); \ - } \ - }; \ - \ - template \ - struct easingresolve { \ - typedef typename std::tuple_element::type ArgType; \ - static void impl(FunctionTuple & b, decltype(easing::__EASING_TYPE__ ## Out), Fs... fs) { \ - get(b) = easing::__EASING_TYPE__ ## Out.run; \ - easingresolve::impl(b, fs...); \ - } \ - }; \ - \ - template \ - struct easingresolve { \ - typedef typename std::tuple_element::type ArgType; \ - static void impl(FunctionTuple & b, decltype(easing::__EASING_TYPE__ ## InOut), Fs... fs) { \ - get(b) = easing::__EASING_TYPE__ ## InOut.run; \ - easingresolve::impl(b, fs...); \ - } \ + static void impl(FunctionTuple &b, easing::steppedEasing, Fs... fs) { + get(b) = easing::stepped.run; + easingresolve::impl(b, fs...); } + }; - DECLARE_EASING_RESOLVE(quadratic); - DECLARE_EASING_RESOLVE(cubic); - DECLARE_EASING_RESOLVE(quartic); - DECLARE_EASING_RESOLVE(quintic); - DECLARE_EASING_RESOLVE(sinusoidal); - DECLARE_EASING_RESOLVE(exponential); - DECLARE_EASING_RESOLVE(circular); - DECLARE_EASING_RESOLVE(bounce); - DECLARE_EASING_RESOLVE(elastic); - DECLARE_EASING_RESOLVE(back); + template + struct easingresolve { + typedef std::tuple_element_t ArgType; + + static void impl(FunctionTuple &b, easing::linearEasing, Fs... fs) { + get(b) = easing::linear.run; + easingresolve::impl(b, fs...); + } + }; + template + struct easingresolve { + typedef std::tuple_element_t ArgType; + + static void impl(FunctionTuple &b, easing::defaultEasing, Fs... fs) { + get(b) = easing::def.run; + easingresolve::impl(b, fs...); + } + }; + + #define DECLARE_EASING_RESOLVE(__EASING_TYPE__) \ + template \ + struct easingresolve { \ + typedef typename std::tuple_element::type ArgType; \ + static void impl(FunctionTuple & b, decltype(easing::__EASING_TYPE__ ## In), Fs... fs) { \ + get(b) = easing::__EASING_TYPE__ ## In.run; \ + easingresolve::impl(b, fs...); \ + } \ + }; \ + \ + template \ + struct easingresolve { \ + typedef typename std::tuple_element::type ArgType; \ + static void impl(FunctionTuple & b, decltype(easing::__EASING_TYPE__ ## Out), Fs... fs) { \ + get(b) = easing::__EASING_TYPE__ ## Out.run; \ + easingresolve::impl(b, fs...); \ + } \ + }; \ + \ + template \ + struct easingresolve { \ + typedef typename std::tuple_element::type ArgType; \ + static void impl(FunctionTuple & b, decltype(easing::__EASING_TYPE__ ## InOut), Fs... fs) { \ + get(b) = easing::__EASING_TYPE__ ## InOut.run; \ + easingresolve::impl(b, fs...); \ + } \ } + DECLARE_EASING_RESOLVE(quadratic); + DECLARE_EASING_RESOLVE(cubic); + DECLARE_EASING_RESOLVE(quartic); + DECLARE_EASING_RESOLVE(quintic); + DECLARE_EASING_RESOLVE(sinusoidal); + DECLARE_EASING_RESOLVE(exponential); + DECLARE_EASING_RESOLVE(circular); + DECLARE_EASING_RESOLVE(bounce); + DECLARE_EASING_RESOLVE(elastic); + DECLARE_EASING_RESOLVE(back); +} + #endif //TWEENY_EASINGRESOLVE_H From 2108ddd94eec5ebfc18c9daad5d7cd1fc2d003e7 Mon Sep 17 00:00:00 2001 From: Leonardo Date: Sun, 17 Aug 2025 17:12:52 -0300 Subject: [PATCH 11/58] adopt modern C++ features in tween class templates Refactored to use `const`, removed redundant `inline`, and simplified methods. --- include/tween.h | 156 ++++++++++++++++++++----------------------- include/tween.tcc | 52 +++++++-------- include/tweenone.tcc | 10 +-- 3 files changed, 100 insertions(+), 118 deletions(-) diff --git a/include/tween.h b/include/tween.h index 638a36a..997b2a1 100644 --- a/include/tween.h +++ b/include/tween.h @@ -30,9 +30,7 @@ #ifndef TWEENY_TWEEN_H #define TWEENY_TWEEN_H -#include #include -#include #include #include "tweentraits.h" @@ -55,9 +53,8 @@ namespace tweeny { * @p t The first value in the point * @p vs The remaining values */ - static tween from(T t, Ts... vs); + static tween from(T t, Ts... vs); - public: /** * @brief Default constructor for a tween * @@ -78,7 +75,8 @@ namespace tweeny { * auto t = tweeny::from(0).to(100).to(200); * @endcode * - * @param t, vs Point values + * @param t The first point value + * @param vs Point values * @returns *this */ tween & to(T t, Ts... vs); @@ -90,7 +88,7 @@ namespace tweeny { * use any callable object. Additionally, you can use the easing objects specified in the class @p easing. * * If it is a multi-value point, you can either specify a single easing function that will be used for - * every value or you can specify an easing function for each value. You can mix and match callable objects, + * every value, or you can specify an easing function for each value. You can mix and match callable objects, * lambdas and bundled easing objects. * * **Example**: @@ -109,26 +107,22 @@ namespace tweeny { */ template tween & via(Fs... fs); - - - - /** * @brief Specifies the easing function for a specific point. * - * Points starts at index 0. The index 0 refers to the first @p to call. + * Points start at index 0. Index 0 refers to the first @p to call. * Using this function without adding a point with @p to leads to undefined - * behaviour. + * behavior. * * @param index The tween point index * @param fs The functions * @returns *this * @see tweeny::easing */ - template tween & via(int index, Fs... fs); + template tween & via(int index, Fs... fs); /** - * @brief Specifies the duration, typically in milliseconds, for the tweening of values in last point. + * @brief Specifies the duration, typically in milliseconds, for the tweening of values in the last point. * * You can either specify a single duration for all values or give every value its own duration. Value types * must be convertible to the uint16_t type. @@ -137,14 +131,14 @@ namespace tweeny { * * @code * // Specify that the first point will be reached in 100 milliseconds and the first value in the second - * // point in 100, whereas the second value will be reached in 500. + * // point in 100, whereas the second value in the second point will be reached in 500. * auto tween = tweeny::from(0, 0).to(100, 200).during(100).to(200, 300).during(100, 500); * @endcode * * @param ds Duration values * @returns *this */ - template tween & during(Ds... ds); + template tween & during(Ds... ds); /** * @brief Steps the animation by the designated delta amount. @@ -155,10 +149,10 @@ namespace tweeny { * **Example**: * * @code - * // tween duration is 100ms + * // tween duration is 100 ms * auto tween = tweeny::from(0).to(100).during(100); * - * // steps for 16ms + * // steps for 16 ms * tween.step(16); * @endcode * @@ -188,10 +182,10 @@ namespace tweeny { * **Example**: * * @code - * // tween duration is 100ms + * // tween duration is 100 ms * auto tween = tweeny::from(0).to(100).during(100); * - * // steps for 16ms + * // steps for 16 ms * tween.step(0.001f); * @endcode * @@ -240,7 +234,7 @@ namespace tweeny { * @brief Adds a callback that will be called when stepping occurs, accepting both the tween and * its values. * - * You can add as many callbacks as you want. Its arguments types must be equal to the argument types + * You can add as many callbacks as you want. Its argument types must be equal to the argument types * of a tween instance, preceded by a variable of the tween type. Callbacks can be of any callable type. It will only be called * via tween::step() functions. For seek callbacks, see tween::onSeek(). * @@ -253,13 +247,13 @@ namespace tweeny { * **Example**: * * @code - * auto t = tweeny:from(0).to(100).during(100); + * auto t = tweeny::from(0).to(100).during(100); * * // pass a lambda - * t.onStep([](tweeny::tween & t, int v) { printf("%d ", v); return false; }); + * t.onStep([](tweeny::tween & t, int v) { printf("%d", v); return false; }); * * // pass a functor instance - * struct ftor { void operator()(tweeny::tween & t, int v) { printf("%d ", v); return false; } }; + * struct ftor { void operator()(tweeny::tween & t, int v) { printf("%d", v); return false; } }; * t.onStep(ftor()); * @endcode * @sa step @@ -267,7 +261,7 @@ namespace tweeny { * @sa onSeek * @param callback A callback in with the prototype `bool callback(tween & t, Ts...)` */ - tween & onStep(typename detail::tweentraits::callbackType callback); + tween & onStep(typename detail::tweentraits::callbackType callback); /** * @brief Adds a callback that will be called when stepping occurs, accepting only the tween. @@ -285,13 +279,13 @@ namespace tweeny { * **Example**: * * @code - * auto t = tweeny:from(0).to(100).during(100); + * auto t = tweeny::from(0).to(100).during(100); * * // pass a lambda - * t.onStep([](tweeny::tween & t) { printf("%d ", t.value()); return false; }); + * t.onStep([](tweeny::tween & t) { printf("%d", t.value()); return false; }); * * // pass a functor instance - * struct ftor { void operator()(tweeny::tween & t) { printf("%d ", t.values()); return false; } }; + * struct ftor { void operator()(tweeny::tween & t) { printf("%d", t.values()); return false; } }; * t.onStep(ftor()); * @endcode * @sa step @@ -299,7 +293,7 @@ namespace tweeny { * @sa onSeek * @param callback A callback in the form `bool f(tween & t)` */ - tween & onStep(typename detail::tweentraits::noValuesCallbackType callback); + tween & onStep(typename detail::tweentraits::noValuesCallbackType callback); /** * @brief Adds a callback that will be called when stepping occurs, accepting only the tween values. @@ -317,13 +311,13 @@ namespace tweeny { * **Example**: * * @code - * auto t = tweeny:from(0).to(100).during(100); + * auto t = tweeny::from(0).to(100).during(100); * * // pass a lambda - * t.onStep([](int v) { printf("%d ", v); return false; }); + * t.onStep([](int v) { printf("%d", v); return false; }); * * // pass a functor instance - * struct ftor { void operator()(int x) { printf("%d ", x); return false; } }; + * struct ftor { void operator ()(int x) { printf("%d", x); return false; } }; * t.onStep(ftor()); * @endcode * @sa step @@ -331,12 +325,12 @@ namespace tweeny { * @sa onSeek * @param callback A callback in the form `bool f(Ts...)` */ - tween & onStep(typename detail::tweentraits::noTweenCallbackType callback); + tween & onStep(typename detail::tweentraits::noTweenCallbackType callback); /** - * @brief Adds a callback for that will be called when seeking occurs + * @brief Adds a callback for that will be called when seeking * - * You can add as many callbacks as you want. Its arguments types must be equal to the argument types + * You can add as many callbacks as you want. Its argument types must be equal to the argument types * of a tween instance, preceded by a variable of the tween typve. Callbacks can be of any callable type. It will be called * via tween::seek() functions. For step callbacks, see tween::onStep(). * @@ -349,18 +343,18 @@ namespace tweeny { * **Example**: * * @code - * auto t = t:from(0).to(100).during(100); + * auto t = tweeny::from(0).to(100).during(100); * * // pass a lambda - * t.onSeek([](tweeny::tween & t, int v) { printf("%d ", v); }); + * t.onSeek([](tweeny::tween & t, int v) { printf("%d", v); }); * * // pass a functor instance - * struct ftor { void operator()(tweeny::tween & t, int v) { printf("%d ", v); } }; + * struct ftor { void operator()(tweeny::tween & t, int v) { printf("%d", v); } }; * t.onSeek(ftor()); * @endcode * @param callback A callback in with the prototype `bool callback(tween & t, Ts...)` */ - tween & onSeek(typename detail::tweentraits::callbackType callback); + tween & onSeek(typename detail::tweentraits::callbackType callback); /** * @brief Adds a callback for that will be called when seeking occurs, accepting only the tween values. @@ -378,18 +372,18 @@ namespace tweeny { * **Example**: * * @code - * auto t = t:from(0).to(100).during(100); + * auto t = tweeny::from(0).to(100).during(100); * * // pass a lambda - * t.onSeek([](int v) { printf("%d ", v); }); + * t.onSeek([](int v) { printf("%d", v); }); * * // pass a functor instance - * struct ftor { void operator()(int v) { printf("%d ", v); return false; } }; + * struct ftor { void operator ()(int v) { printf("%d", v); return false; } }; * t.onSeek(ftor()); * @endcode * @param callback A callback in the form `bool f(Ts...)` */ - tween & onSeek(typename detail::tweentraits::noTweenCallbackType callback); + tween & onSeek(typename detail::tweentraits::noTweenCallbackType callback); /** * @brief Adds a callback for that will be called when seeking occurs, accepting only the tween. @@ -418,14 +412,14 @@ namespace tweeny { * @endcode * @param callback A callback in the form `bool f(tween & t)` */ - tween & onSeek(typename detail::tweentraits::noValuesCallbackType callback); + tween & onSeek(typename detail::tweentraits::noValuesCallbackType callback); /** * @brief Returns the total duration of this tween * * @returns The duration of all the tween points. */ - uint32_t duration() const; + [[nodiscard]] uint32_t duration() const; /** * @brief Returns the current tween values @@ -458,35 +452,35 @@ namespace tweeny { /** * @brief Returns the current currentProgress of the interpolation. * - * 0 means its at the values passed in the construction, 1 means the last step. + * 0 means it's at the values passed in the construction; 1 means the last step. * @returns the current currentProgress between 0 and 1 (inclusive) */ - float progress() const; + [[nodiscard]] float progress() const; /** * @brief Sets the direction of this tween forward. * - * Note that this only affects tween::step() function. + * Note that this only affects the tween::step () function. * @returns *this * @sa backward */ - tween & forward(); + tween & forward(); /** * @brief Sets the direction of this tween backward. * - * Note that this only affects tween::step() function. + * Note that this only affects the tween::step () function. * @returns *this * @sa forward */ - tween & backward(); + tween & backward(); /** * @brief Returns the current direction of this tween * - * @returns -1 If it is mobin backwards in time, 1 if it is moving forward in time + * @returns -1 If it is moving backwards in time, 1 if it is moving forward in time */ - int direction() const; + [[nodiscard]] int direction() const; /** * @brief Jumps to a specific tween point @@ -505,12 +499,10 @@ namespace tweeny { * * @returns Current tween point */ - uint16_t point() const; + [[nodiscard]] uint16_t point() const; - private /* member types */: + private: using traits = detail::tweentraits; - - private /* member variables */: uint32_t total = 0; // total runtime uint16_t currentPoint = 0; // current point float currentProgress = 0; // current progress @@ -520,14 +512,13 @@ namespace tweeny { std::vector onSeekCallbacks; int8_t currentDirection = 1; - private: /* member functions */ - tween(T t, Ts... vs); + explicit tween(T t, Ts... vs); template void interpolate(float prog, unsigned point, typename traits::valuesType & values, detail::int2type) const; void interpolate(float prog, unsigned point, typename traits::valuesType & values, detail::int2type<0>) const; void render(float p); void dispatch(std::vector & cbVector); - uint16_t pointAt(float progress) const; + [[nodiscard]] uint16_t pointAt(float progress) const; }; /** @@ -543,41 +534,37 @@ namespace tweeny { template class tween { public: - static tween from(T t); - - public: + static tween from(T t); tween(); ///< @sa tween::tween - tween & to(T t); ///< @sa tween::to - template tween & via(Fs... fs); ///< @sa tween::via - template tween & via(int index, Fs... fs); ///< @sa tween::via - template tween & during(Ds... ds); ///< @sa tween::during + tween & to(T t); ///< @sa tween::to + template tween & via(Fs... fs); ///< @sa tween::via + template tween & via(int index, Fs... fs); ///< @sa tween::via + template tween & during(Ds... ds); ///< @sa tween::during const T & step(int32_t dt, bool suppressCallbacks = false); ///< @sa tween::step(int32_t dt, bool suppressCallbacks) const T & step(uint32_t dt, bool suppressCallbacks = false); ///< @sa tween::step(uint32_t dt, bool suppressCallbacks) const T & step(float dp, bool suppressCallbacks = false); ///< @sa tween::step(float dp, bool suppressCallbacks) const T & seek(float p, bool suppressCallbacks = false); ///< @sa tween::seek(float p, bool suppressCallbacks) const T & seek(int32_t d, bool suppressCallbacks = false); ///< @sa tween::seek(int32_t d, bool suppressCallbacks) const T & seek(uint32_t d, bool suppressCallbacks = false); ///< @sa tween::seek(uint32_t d, bool suppressCallbacks) - tween & onStep(typename detail::tweentraits::callbackType callback); ///< @sa tween::onStep - tween & onStep(typename detail::tweentraits::noValuesCallbackType callback); ///< @sa tween::onStep - tween & onStep(typename detail::tweentraits::noTweenCallbackType callback); ///< @sa tween::onStep - tween & onSeek(typename detail::tweentraits::callbackType callback); ///< @sa tween::onSeek - tween & onSeek(typename detail::tweentraits::noValuesCallbackType callback); ///< @sa tween::onSeek - tween & onSeek(typename detail::tweentraits::noTweenCallbackType callback); ///< @sa tween::onSeek + tween & onStep(typename detail::tweentraits::callbackType callback); ///< @sa tween::onStep + tween & onStep(typename detail::tweentraits::noValuesCallbackType callback); ///< @sa tween::onStep + tween & onStep(typename detail::tweentraits::noTweenCallbackType callback); ///< @sa tween::onStep + tween & onSeek(typename detail::tweentraits::callbackType callback); ///< @sa tween::onSeek + tween & onSeek(typename detail::tweentraits::noValuesCallbackType callback); ///< @sa tween::onSeek + tween & onSeek(typename detail::tweentraits::noTweenCallbackType callback); ///< @sa tween::onSeek const T & peek() const; ///< @sa tween::peek T peek(float progress) const; ///< @sa tween::peek T peek(uint32_t time) const; ///< @sa tween::peek - uint32_t duration() const; ///< @sa tween::duration - float progress() const; ///< @sa tween::progress - tween & forward(); ///< @sa tween::forward - tween & backward(); ///< @sa tween::backward - int direction() const; ///< @sa tween::direction + [[nodiscard]] uint32_t duration() const; ///< @sa tween::duration + [[nodiscard]] float progress() const; ///< @sa tween::progress + tween & forward(); ///< @sa tween::forward + tween & backward(); ///< @sa tween::backward + [[nodiscard]] int direction() const; ///< @sa tween::direction const T & jump(size_t point, bool suppressCallbacks = false); ///< @sa tween::jump - uint16_t point() const; ///< @sa tween::point + [[nodiscard]] uint16_t point() const; ///< @sa tween::point - private /* member types */: + private: using traits = detail::tweentraits; - - private /* member variables */: uint32_t total = 0; // total runtime uint16_t currentPoint = 0; // current point float currentProgress = 0; // current progress @@ -587,13 +574,12 @@ namespace tweeny { std::vector onSeekCallbacks; int8_t currentDirection = 1; - private: /* member functions */ - tween(T t); + explicit tween(T t); void interpolate(float prog, unsigned point, T & value) const; void render(float p); void dispatch(std::vector & cbVector); - uint16_t pointAt(float progress) const; + [[nodiscard]] uint16_t pointAt(float progress) const; }; } diff --git a/include/tween.tcc b/include/tween.tcc index 37d53a7..9092bd7 100644 --- a/include/tween.tcc +++ b/include/tween.tcc @@ -41,8 +41,8 @@ namespace tweeny { } } - template tween tween::from(T t, Ts... vs) { return tween(t, vs...); } - template tween::tween() { } + template tween tween::from(T t, Ts... vs) { return tween(t, vs...); } + template tween::tween() = default; template tween::tween(T t, Ts... vs) { points.emplace_back(t, vs...); } @@ -66,9 +66,6 @@ namespace tweeny { return *this; } - - - template template tween & tween::during(Ds... ds) { @@ -82,35 +79,35 @@ namespace tweeny { } template - const typename detail::tweentraits::valuesType & tween::step(int32_t dt, bool suppress) { - return step(static_cast(dt)/static_cast(total), suppress); + const typename detail::tweentraits::valuesType & tween::step(const int32_t dt, const bool suppressCallbacks) { + return step(static_cast(dt)/static_cast(total), suppressCallbacks); } template - const typename detail::tweentraits::valuesType & tween::step(uint32_t dt, bool suppress) { - return step(static_cast(dt), suppress); + const typename detail::tweentraits::valuesType & tween::step(const uint32_t dt, const bool suppressCallbacks) { + return step(static_cast(dt), suppressCallbacks); } template - const typename detail::tweentraits::valuesType & tween::step(float dp, bool suppress) { + const typename detail::tweentraits::valuesType & tween::step(float dp, const bool suppressCallbacks) { dp *= currentDirection; seek(currentProgress + dp, true); - if (!suppress) dispatch(onStepCallbacks); + if (!suppressCallbacks) dispatch(onStepCallbacks); return current; } template - const typename detail::tweentraits::valuesType & tween::seek(float p, bool suppress) { + const typename detail::tweentraits::valuesType & tween::seek(float p, const bool suppressCallbacks) { p = detail::clip(p, 0.0f, 1.0f); currentProgress = p; render(p); - if (!suppress) dispatch(onSeekCallbacks); + if (!suppressCallbacks) dispatch(onSeekCallbacks); return current; } template - const typename detail::tweentraits::valuesType & tween::seek(int32_t t, bool suppress) { - return seek(static_cast(t) / static_cast(total), suppress); + const typename detail::tweentraits::valuesType & tween::seek(const int32_t d, const bool suppressCallbacks) { + return seek(static_cast(d) / static_cast(total), suppressCallbacks); } template @@ -122,7 +119,7 @@ namespace tweeny { template void tween::interpolate(float prog, unsigned point, typename traits::valuesType & values, detail::int2type) const { auto & p = points.at(point); - auto pointDuration = uint32_t(p.duration() - (p.stacked - (prog * static_cast(total)))); + const auto pointDuration = static_cast(p.duration() - (p.stacked - prog * static_cast(total))); float pointTotal = static_cast(pointDuration) / static_cast(p.duration(I)); if (pointTotal > 1.0f) pointTotal = 1.0f; auto easing = std::get(p.easings); @@ -133,7 +130,7 @@ namespace tweeny { template void tween::interpolate(float prog, unsigned point, typename traits::valuesType & values, detail::int2type<0>) const { auto & p = points.at(point); - auto pointDuration = uint32_t(p.duration() - (p.stacked - (prog * static_cast(total)))); + auto pointDuration = static_cast(p.duration() - (p.stacked - prog * static_cast(total))); float pointTotal = static_cast(pointDuration) / static_cast(p.duration(0)); if (pointTotal > 1.0f) pointTotal = 1.0f; auto easing = std::get<0>(p.easings); @@ -154,13 +151,13 @@ namespace tweeny { template tween & tween::onStep(typename detail::tweentraits::noValuesCallbackType callback) { - onStepCallbacks.push_back([callback](tween & t, T, Ts...) { return callback(t); }); + onStepCallbacks.push_back([callback](tween & t, T, Ts...) { return callback(t); }); return *this; } template tween & tween::onStep(typename detail::tweentraits::noTweenCallbackType callback) { - onStepCallbacks.push_back([callback](tween &, T t, Ts... vs) { return callback(t, vs...); }); + onStepCallbacks.push_back([callback](tween &, T t, Ts... vs) { return callback(t, vs...); }); return *this; } @@ -172,13 +169,13 @@ namespace tweeny { template tween & tween::onSeek(typename detail::tweentraits::noValuesCallbackType callback) { - onSeekCallbacks.push_back([callback](tween & t, T, Ts...) { return callback(t); }); + onSeekCallbacks.push_back([callback](tween & t, T, Ts...) { return callback(t); }); return *this; } template tween & tween::onSeek(typename detail::tweentraits::noTweenCallbackType callback) { - onSeekCallbacks.push_back([callback](tween &, T t, Ts... vs) { return callback(t, vs...); }); + onSeekCallbacks.push_back([callback](tween &, T t, Ts... vs) { return callback(t, vs...); }); return *this; } @@ -187,11 +184,10 @@ namespace tweeny { std::vector dismissed; for (size_t i = 0; i < cbVector.size(); ++i) { auto && cb = cbVector[i]; - bool dismiss = detail::call(cb, std::tuple_cat(std::make_tuple(std::ref(*this)), current)); - if (dismiss) dismissed.push_back(i); + if (detail::call(cb, std::tuple_cat(std::make_tuple(std::ref(*this)), current))) dismissed.push_back(i); } - if (dismissed.size() > 0) { + if (!dismissed.empty()) { for (size_t i = 0; i < dismissed.size(); ++i) { size_t index = dismissed[i]; cbVector[index] = cbVector.at(cbVector.size() - 1 - i); @@ -243,9 +239,9 @@ namespace tweeny { } template - const typename detail::tweentraits::valuesType & tween::jump(std::size_t p, bool suppress) { - p = detail::clip(p, static_cast(0), points.size() -1); - return seek(static_cast(points.at(p).stacked), suppress); + const typename detail::tweentraits::valuesType & tween::jump(std::size_t point, bool suppressCallbacks) { + point = detail::clip(point, static_cast(0), points.size() -1); + return seek(static_cast(points.at(point).stacked), suppressCallbacks); } template uint16_t tween::point() const { @@ -254,7 +250,7 @@ namespace tweeny { template uint16_t tween::pointAt(float progress) const { progress = detail::clip(progress, 0.0f, 1.0f); - uint32_t t = static_cast(progress * total); + auto t = static_cast(progress * total); uint16_t point = 0; while (t > points.at(point).stacked) point++; if (point > 0 && t <= points.at(point - 1u).stacked) point--; diff --git a/include/tweenone.tcc b/include/tweenone.tcc index 8c82f1a..8ff922f 100644 --- a/include/tweenone.tcc +++ b/include/tweenone.tcc @@ -103,8 +103,8 @@ namespace tweeny { } template - const T & tween::seek(const uint32_t t, const bool suppressCallbacks) { - return seek(static_cast(t) / static_cast(total), suppressCallbacks); + const T & tween::seek(const uint32_t d, const bool suppressCallbacks) { + return seek(static_cast(d) / static_cast(total), suppressCallbacks); } template @@ -115,7 +115,7 @@ namespace tweeny { template void tween::interpolate(const float prog, unsigned point, T & value) const { auto & p = points.at(point); - auto pointDuration = uint32_t(p.duration() - (p.stacked - prog * static_cast(total))); + const auto pointDuration = static_cast(p.duration() - (p.stacked - prog * static_cast(total))); float pointTotal = static_cast(pointDuration) / static_cast(p.duration()); if (pointTotal > 1.0f) pointTotal = 1.0f; auto easing = std::get<0>(p.easings); @@ -188,7 +188,7 @@ namespace tweeny { template - T tween::peek(float progress) const { + T tween::peek(const float progress) const { T value; interpolate(progress, pointAt(progress), value); return value; @@ -197,7 +197,7 @@ namespace tweeny { template T tween::peek(const uint32_t time) const { T value; - float progress = static_cast(time) / static_cast(total); + const float progress = static_cast(time) / static_cast(total); interpolate(progress, pointAt(progress), value); return value; } From 569b632dd6e53359328030e9c95c505bad2c8b83 Mon Sep 17 00:00:00 2001 From: Leonardo Date: Sun, 17 Aug 2025 17:23:33 -0300 Subject: [PATCH 12/58] adopt modern C++ features in tweenpoint templates Changed to use `const`, `constexpr`, and `nodiscard`, simplified method implementations and adopted nested namespace syntax. --- include/tweenpoint.h | 80 +++++++++++----------- include/tweenpoint.tcc | 151 +++++++++++++++++++++-------------------- 2 files changed, 117 insertions(+), 114 deletions(-) diff --git a/include/tweenpoint.h b/include/tweenpoint.h index 732446c..7deaf49 100644 --- a/include/tweenpoint.h +++ b/include/tweenpoint.h @@ -31,51 +31,47 @@ #ifndef TWEENY_TWEENPOINT_H #define TWEENY_TWEENPOINT_H +#include "tweentraits.h" -#include -#include -#include "tweentraits.h" + namespace tweeny::detail { + /* + * The tweenpoint class aids in the management of a tweening point by the tween class. + * This class is private. + */ + template + struct tweenpoint { + typedef tweentraits traits; + + typename traits::valuesType values; + typename traits::durationsArrayType durations; + typename traits::easingCollection easings; + typename traits::callbackType onEnterCallbacks; + uint32_t stacked{}; + + /* Constructs a tweenpoint from a set of values, filling their durations and easings */ + explicit tweenpoint(Ts... vs); + + /* Set the duration for all the values at this point */ + template void during(D duration); + + /* Sets the duration for each value in this point */ + template void during(Ds... duration); + + /* Sets the easing functions of each value */ + template void via(Fs... fs); + + /* Sets the same easing function for all values */ + template void via(F f); + + /* Returns the highest duration value in the duration array */ + [[nodiscard]] uint16_t duration() const; + + /* Returns the tween duration of that specific value */ + [[nodiscard]] uint16_t duration(size_t valueIndex) const; + }; + } -namespace tweeny { - namespace detail { - /* - * The tweenpoint class aids in the management of a tweening point by the tween class. - * This class is private. - */ - template - struct tweenpoint { - typedef detail::tweentraits traits; - - typename traits::valuesType values; - typename traits::durationsArrayType durations; - typename traits::easingCollection easings; - typename traits::callbackType onEnterCallbacks; - uint32_t stacked; - - /* Constructs a tweenpoint from a set of values, filling their durations and easings */ - tweenpoint(Ts... vs); - - /* Set the duration for all the values in this point */ - template void during(D milis); - - /* Sets the duration for each value in this point */ - template void during(Ds... vs); - - /* Sets the easing functions of each value */ - template void via(Fs... fs); - - /* Sets the same easing function for all values */ - template void via(F f); - - /* Returns the highest value in duration array */ - uint16_t duration() const; - - /* Returns the value of that specific value */ - uint16_t duration(size_t i) const; - }; - } -} #include "tweenpoint.tcc" diff --git a/include/tweenpoint.tcc b/include/tweenpoint.tcc index c5cda0e..bdb588c 100644 --- a/include/tweenpoint.tcc +++ b/include/tweenpoint.tcc @@ -38,78 +38,85 @@ #include "easingresolve.h" #include "int2type.h" -namespace tweeny { - namespace detail { - template void easingfill(EasingCollectionT & f, EasingT easing, int2type) { - easingresolve::impl(f, easing); - easingfill(f, easing, int2type{ }); - } - - template void easingfill(EasingCollectionT & f, EasingT easing, int2type<0>) { - easingresolve<0, TypeTupleT, EasingCollectionT, EasingT>::impl(f, easing); - } - - - template - struct are_same; - - template - struct are_same - { - static const bool value = std::is_same::value && are_same::value; - }; - - template - struct are_same - { - static const bool value = true; - }; - - - template - inline tweenpoint::tweenpoint(Ts... vs) : values{vs...} { - during(static_cast(0)); - via(easing::def); - } - - template - template - inline void tweenpoint::during(D milis) { - for (uint16_t & t : durations) { t = static_cast(milis); } - } - - template - template - inline void tweenpoint::during(Ds... milis) { - static_assert(sizeof...(Ds) == sizeof...(Ts), - "Amount of durations should be equal to the amount of values in a point"); - std::array list = {{ milis... }}; - std::copy(list.begin(), list.end(), durations.begin()); - } - - template - template - inline void tweenpoint::via(Fs... fs) { - static_assert(sizeof...(Fs) == sizeof...(Ts), - "Number of functions passed to via() must be equal the number of values."); - detail::easingresolve<0, std::tuple, typename traits::easingCollection, Fs...>::impl(easings, fs...); - } - - template - template - inline void tweenpoint::via(F f) { - easingfill(easings, f, int2type{ }); - } - - template - inline uint16_t tweenpoint::duration() const { - return *std::max_element(durations.begin(), durations.end()); - } - - template - inline uint16_t tweenpoint::duration(size_t i) const { - return durations.at(i); - } + +namespace tweeny::detail { + template < + typename TypeTupleT, + typename EasingCollectionT, + typename EasingT, size_t I + > void easingfill(EasingCollectionT & f, EasingT easing, int2type) { + easingresolve::impl(f, easing); + easingfill(f, easing, int2type{}); + } + + template < + typename TypeTupleT, + typename EasingCollectionT, + typename EasingT + > void easingfill(EasingCollectionT & f, EasingT easing, int2type<0>) { + easingresolve<0, TypeTupleT, EasingCollectionT, EasingT>::impl(f, easing); + } + + template + struct are_same; + + template + struct are_same + { + static const bool value = std::is_same_v && are_same::value; + }; + + template + struct are_same + { + static constexpr bool value = true; + }; + + + template + tweenpoint::tweenpoint(Ts... vs) : values{vs...} { + during(static_cast(0)); + via(easing::def); + } + + template + template + void tweenpoint::during(D duration) { + for (uint16_t & t : durations) { t = static_cast(duration); } + } + + template + template + void tweenpoint::during(Ds... duration) { + static_assert(sizeof...(Ds) == sizeof...(Ts), + "Amount of durations should be equal to the amount of values in a point"); + std::array list = {{ duration... }}; + std::copy(list.begin(), list.end(), durations.begin()); + } + + template + template + void tweenpoint::via(Fs... fs) { + static_assert(sizeof...(Fs) == sizeof...(Ts), + "Number of functions passed to via() must be equal the number of values."); + easingresolve<0, std::tuple, typename traits::easingCollection, Fs...>::impl(easings, fs...); + } + + template + template + void tweenpoint::via(F f) { + easingfill(easings, f, int2type{ }); + } + + template + uint16_t tweenpoint::duration() const { + return *std::max_element(durations.begin(), durations.end()); + } + + template + uint16_t tweenpoint::duration(size_t valueIndex) const { + return durations.at(valueIndex); } } + #endif //TWEENY_TWEENPOINT_TCC From 90ab934cb70ba9b0d1b3242de7f91eb77f6244b4 Mon Sep 17 00:00:00 2001 From: Leonardo Date: Sun, 17 Aug 2025 17:27:36 -0300 Subject: [PATCH 13/58] adopt modern C++ features in traits and header Simplified `equal` struct, replaced `std::is_same` with `std::is_same_v`, and updated template parameter syntax. --- include/tweentraits.h | 6 +++--- include/tweeny.h | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/include/tweentraits.h b/include/tweentraits.h index ff22938..aac63ad 100644 --- a/include/tweentraits.h +++ b/include/tweentraits.h @@ -39,15 +39,15 @@ namespace tweeny { namespace detail { - template struct equal {}; + template struct equal {}; template struct equal { enum { value = true }; }; template struct equal { - enum { value = std::is_same::value && equal::value && equal::value }; + enum { value = std::is_same_v && equal::value && equal::value }; }; template struct first { typedef T type; }; - template + template struct valuetype { }; template diff --git a/include/tweeny.h b/include/tweeny.h index 3c7ba44..770184a 100644 --- a/include/tweeny.h +++ b/include/tweeny.h @@ -38,7 +38,7 @@ * * * The Fine @ref manual * * The tweeny::from global function, to start a new tween. - * * The tweeny::tween class itself, that has all the interesting methods for a tween. + * * The tweeny::tween class itself that has all the interesting methods for a tween. * * The modules page has a list of type of easings. * * This is how the API looks like: @@ -99,4 +99,4 @@ namespace tweeny { #include "tweeny.tcc" -#endif //TWEENY_TWEENY_H +#endif //TWEENY_H From 266b9109140810480983c9fa705c8e3bcec6aa4e Mon Sep 17 00:00:00 2001 From: Leonardo Date: Sun, 17 Aug 2025 19:01:40 -0300 Subject: [PATCH 14/58] use modern CMake features and enforce C++17 standards Updated `CMakeLists.txt` to enforce C++17, replaced custom C++11 feature flags with `cxx_std_17`, and utilized namespaced target aliases. Enhanced scripts to utilize modern CMake practices, improved Python/quom checks, and updated installation/export configurations. --- CMakeLists.txt | 66 ++++++++++++++++---------------- cmake/GenerateSingleHeader.cmake | 10 ++--- cmake/SetupExports.cmake | 8 +++- 3 files changed, 43 insertions(+), 41 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 8e61884..761373f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -25,10 +25,15 @@ # target that uses tweeny, a simple `target_link_libraries(target tweeny)` is sufficient to set up include and link # instructions. -cmake_minimum_required(VERSION 3.0...3.28) +cmake_minimum_required(VERSION 3.23...3.28) cmake_policy(SET CMP0063 NEW) project(Tweeny LANGUAGES CXX VERSION 3.2.0) +# Enforce C++17 for targets built in this project +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD_REQUIRED ON) +set(CMAKE_CXX_EXTENSIONS OFF) + # Setup variables and options option(TWEENY_BUILD_DOCUMENTATION "Attempts to build the documentation. You'll need doxygen and graphviz installed" OFF) option(TWEENY_BUILD_SINGLE_HEADER "Joins together all header files in a single one. Needs Python 3.6 and quom installed" OFF) @@ -37,21 +42,11 @@ option(TWEENY_BUILD_SANDBOX "Adds a 'sandbox' target that links to tweeny. Usefu # The library target add_library(tweeny INTERFACE) -# Specify the C++ features a compiler should have to use this library. -if(NOT CMAKE_SYSTEM_NAME STREQUAL "Emscripten") - target_compile_features(tweeny - INTERFACE - cxx_auto_type - cxx_variadic_templates - cxx_lambdas - cxx_nullptr - cxx_right_angle_brackets - cxx_static_assert - cxx_template_template_parameters - ) -else() - list(APPEND CMAKE_CXX_FLAGS -std=c++11) -endif() +# Require C++17 for consumers of this interface library. +target_compile_features(tweeny INTERFACE cxx_std_17) + +# Provide namespaced alias for consumers. +add_library(tweeny::tweeny ALIAS tweeny) # Set up include directories target_include_directories(tweeny INTERFACE @@ -59,10 +54,29 @@ target_include_directories(tweeny INTERFACE $ ) +# Attach headers to the interface target for IDEs and installation +target_sources(tweeny + INTERFACE + FILE_SET HEADERS + BASE_DIRS ${CMAKE_CURRENT_SOURCE_DIR}/include + FILES + include/tweeny.h + include/tweeny.tcc + include/tween.h + include/tween.tcc + include/tweenone.tcc + include/tweenpoint.h + include/tweenpoint.tcc + include/tweentraits.h + include/easing.h + include/easingresolve.h + include/int2type.h + include/dispatcher.h +) + # Set up install include(GNUInstallDirs) -install(TARGETS tweeny EXPORT TweenyTargets) -install(DIRECTORY include/ DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/tweeny) +install(TARGETS tweeny EXPORT TweenyTargets FILE_SET HEADERS) # Set up export and config include(cmake/SetupExports.cmake) @@ -71,22 +85,6 @@ if (TWEENY_BUILD_DOCUMENTATION) add_subdirectory(doc) endif() -# This library is a convenience library to force files appear in the IDE properly. -add_library(tweeny-dummy - include/tweeny.h - include/tweeny.tcc - include/tween.h - include/tween.tcc - include/tweenone.tcc - include/tweenpoint.h - include/tweenpoint.tcc - include/tweentraits.h - include/easing.h - include/easingresolve.h - include/int2type.h - include/dispatcher.h) -set_target_properties(tweeny-dummy PROPERTIES LINKER_LANGUAGE CXX EXCLUDE_FROM_ALL TRUE) - if (TWEENY_BUILD_SINGLE_HEADER) include(cmake/GenerateSingleHeader.cmake) endif() diff --git a/cmake/GenerateSingleHeader.cmake b/cmake/GenerateSingleHeader.cmake index 08ff5e1..64c6b6d 100644 --- a/cmake/GenerateSingleHeader.cmake +++ b/cmake/GenerateSingleHeader.cmake @@ -1,14 +1,14 @@ # This cmake script is used to generate a single header file with all of tweeny -find_package(Python 3.6 QUIET) +find_package(Python3 3.6 COMPONENTS Interpreter QUIET) -if (NOT PYTHON_FOUND) - message(STATUS "Python 3.6 not found. Single-header include file will NOT be created") +if (NOT Python3_Interpreter_FOUND) + message(STATUS "Python 3.6+ interpreter not found. Single-header include file will NOT be created") return() endif() find_program(QUOM_EXECUTABLE NAMES quom) -if (QUOM_EXECUTABLE-NOTFOUND) - message(STATUS "quom program not found. Install it with pip or easy_install") +if (NOT QUOM_EXECUTABLE) + message(STATUS "quom program not found. Install it with: pip3 install quom") return() endif() diff --git a/cmake/SetupExports.cmake b/cmake/SetupExports.cmake index 692cc93..95686c4 100644 --- a/cmake/SetupExports.cmake +++ b/cmake/SetupExports.cmake @@ -27,13 +27,17 @@ include(CMakePackageConfigHelpers) include(GNUInstallDirs) # Setup install of exported targets -install(EXPORT TweenyTargets DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/Tweeny) +install( + EXPORT TweenyTargets + NAMESPACE tweeny:: + DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/tweeny +) # Macro to write config write_basic_package_version_file( "${CMAKE_CURRENT_BINARY_DIR}/TweenyConfigVersion.cmake" VERSION ${Tweeny_VERSION} - COMPATIBILITY AnyNewerVersion + COMPATIBILITY SameMajorVersion ) # Setup install of version config From 262f27dc5bfc2a69805f6d1f965ba3b877ffbc29 Mon Sep 17 00:00:00 2001 From: Leonardo Date: Sun, 17 Aug 2025 19:15:19 -0300 Subject: [PATCH 15/58] add testing infrastructure with Catch2 Enabled testing setup via CMake with `TWEENY_BUILD_TESTS` option. This includes integration of Catch2 v3, a basic sanity test, and configuration for automatic test discovery. --- CMakeLists.txt | 6 ++++++ tests/CMakeLists.txt | 12 ++++++++++++ tests/sanity.cpp | 5 +++++ 3 files changed, 23 insertions(+) create mode 100644 tests/CMakeLists.txt create mode 100644 tests/sanity.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 761373f..1eaac17 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -38,6 +38,7 @@ set(CMAKE_CXX_EXTENSIONS OFF) option(TWEENY_BUILD_DOCUMENTATION "Attempts to build the documentation. You'll need doxygen and graphviz installed" OFF) option(TWEENY_BUILD_SINGLE_HEADER "Joins together all header files in a single one. Needs Python 3.6 and quom installed" OFF) option(TWEENY_BUILD_SANDBOX "Adds a 'sandbox' target that links to tweeny. Useful when exploring tweeny" OFF) +option(TWEENY_BUILD_TESTS "Build Tweeny tests (requires Catch2 v3 to be findable)" OFF) # The library target add_library(tweeny INTERFACE) @@ -85,6 +86,11 @@ if (TWEENY_BUILD_DOCUMENTATION) add_subdirectory(doc) endif() +if (TWEENY_BUILD_TESTS) + enable_testing() + add_subdirectory(tests) +endif() + if (TWEENY_BUILD_SINGLE_HEADER) include(cmake/GenerateSingleHeader.cmake) endif() diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt new file mode 100644 index 0000000..97419d3 --- /dev/null +++ b/tests/CMakeLists.txt @@ -0,0 +1,12 @@ +cmake_minimum_required(VERSION 3.15) +find_package(Catch2 3 REQUIRED CONFIG) + +add_executable(tweeny_tests + sanity.cpp +) + +target_compile_features(tweeny_tests PRIVATE cxx_std_17) +target_link_libraries(tweeny_tests PRIVATE Catch2::Catch2WithMain tweeny::tweeny) + +include(Catch) +catch_discover_tests(tweeny_tests) diff --git a/tests/sanity.cpp b/tests/sanity.cpp new file mode 100644 index 0000000..404602c --- /dev/null +++ b/tests/sanity.cpp @@ -0,0 +1,5 @@ +#include + +TEST_CASE("sanity") { + REQUIRE(1 + 1 == 2); +} From 4d681f322b904d7c1d6ba0de277994e77520ceb9 Mon Sep 17 00:00:00 2001 From: Leonardo Date: Sun, 17 Aug 2025 22:32:03 -0300 Subject: [PATCH 16/58] refactor: rename `stacked` to `start`, add time-based seek overload Replaced `stacked` with `start` across the tween and tweenpoint classes for clarity and consistency. Added an overload for `seek` to support `uint32_t` time-based arguments. Extended tests to cover new functionality and ensure correctness. --- CMakeLists.txt | 2 +- include/tween.tcc | 17 ++- include/tweenone.tcc | 10 +- include/tweenpoint.h | 2 +- tests/CMakeLists.txt | 1 + tests/sanity.cpp | 4 + tests/tween_tests.cpp | 284 ++++++++++++++++++++++++++++++++++++++++++ 7 files changed, 307 insertions(+), 13 deletions(-) create mode 100644 tests/tween_tests.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 1eaac17..501757c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -38,7 +38,7 @@ set(CMAKE_CXX_EXTENSIONS OFF) option(TWEENY_BUILD_DOCUMENTATION "Attempts to build the documentation. You'll need doxygen and graphviz installed" OFF) option(TWEENY_BUILD_SINGLE_HEADER "Joins together all header files in a single one. Needs Python 3.6 and quom installed" OFF) option(TWEENY_BUILD_SANDBOX "Adds a 'sandbox' target that links to tweeny. Useful when exploring tweeny" OFF) -option(TWEENY_BUILD_TESTS "Build Tweeny tests (requires Catch2 v3 to be findable)" OFF) +option(TWEENY_BUILD_TESTS "Build Tweeny tests (requires Catch2 v3 to be findable via CMake)" OFF) # The library target add_library(tweeny INTERFACE) diff --git a/include/tween.tcc b/include/tween.tcc index 9092bd7..56a87be 100644 --- a/include/tween.tcc +++ b/include/tween.tcc @@ -72,8 +72,8 @@ namespace tweeny { total = 0; points.at(points.size() - 2).during(ds...); for (detail::tweenpoint & p : points) { + p.start = total; total += p.duration(); - p.stacked = total; } return *this; } @@ -110,6 +110,11 @@ namespace tweeny { return seek(static_cast(d) / static_cast(total), suppressCallbacks); } + template + const typename detail::tweentraits::valuesType & tween::seek(const uint32_t d, const bool suppressCallbacks) { + return seek(static_cast(d) / static_cast(total), suppressCallbacks); + } + template uint32_t tween::duration() const { return total; @@ -119,7 +124,7 @@ namespace tweeny { template void tween::interpolate(float prog, unsigned point, typename traits::valuesType & values, detail::int2type) const { auto & p = points.at(point); - const auto pointDuration = static_cast(p.duration() - (p.stacked - prog * static_cast(total))); + const auto pointDuration = static_cast(p.duration() - (p.start - prog * static_cast(total))); float pointTotal = static_cast(pointDuration) / static_cast(p.duration(I)); if (pointTotal > 1.0f) pointTotal = 1.0f; auto easing = std::get(p.easings); @@ -130,7 +135,7 @@ namespace tweeny { template void tween::interpolate(float prog, unsigned point, typename traits::valuesType & values, detail::int2type<0>) const { auto & p = points.at(point); - auto pointDuration = static_cast(p.duration() - (p.stacked - prog * static_cast(total))); + auto pointDuration = static_cast(p.duration() - (p.start - prog * static_cast(total))); float pointTotal = static_cast(pointDuration) / static_cast(p.duration(0)); if (pointTotal > 1.0f) pointTotal = 1.0f; auto easing = std::get<0>(p.easings); @@ -241,7 +246,7 @@ namespace tweeny { template const typename detail::tweentraits::valuesType & tween::jump(std::size_t point, bool suppressCallbacks) { point = detail::clip(point, static_cast(0), points.size() -1); - return seek(static_cast(points.at(point).stacked), suppressCallbacks); + return seek(static_cast(points.at(point).start), suppressCallbacks); } template uint16_t tween::point() const { @@ -252,8 +257,8 @@ namespace tweeny { progress = detail::clip(progress, 0.0f, 1.0f); auto t = static_cast(progress * total); uint16_t point = 0; - while (t > points.at(point).stacked) point++; - if (point > 0 && t <= points.at(point - 1u).stacked) point--; + while (t > points.at(point).start) point++; + if (point > 0 && t <= points.at(point - 1u).start) point--; return point; } } diff --git a/include/tweenone.tcc b/include/tweenone.tcc index 8ff922f..b5babe6 100644 --- a/include/tweenone.tcc +++ b/include/tweenone.tcc @@ -64,8 +64,8 @@ namespace tweeny { total = 0; points.at(points.size() - 2).during(ds...); for (detail::tweenpoint & p : points) { + p.start = total; total += p.duration(); - p.stacked = total; } return *this; } @@ -115,7 +115,7 @@ namespace tweeny { template void tween::interpolate(const float prog, unsigned point, T & value) const { auto & p = points.at(point); - const auto pointDuration = static_cast(p.duration() - (p.stacked - prog * static_cast(total))); + const auto pointDuration = static_cast(p.duration() - (p.start - prog * static_cast(total))); float pointTotal = static_cast(pointDuration) / static_cast(p.duration()); if (pointTotal > 1.0f) pointTotal = 1.0f; auto easing = std::get<0>(p.easings); @@ -228,7 +228,7 @@ namespace tweeny { template const T & tween::jump(size_t point, bool suppressCallbacks) { point = detail::clip(point, static_cast(0), points.size() -1); - return seek(points.at(point).stacked, suppressCallbacks); + return seek(points.at(point).start, suppressCallbacks); } template uint16_t tween::point() const { @@ -239,8 +239,8 @@ namespace tweeny { progress = detail::clip(progress, 0.0f, 1.0f); auto t = static_cast(progress * total); uint16_t point = 0; - while (t > points.at(point).stacked) point++; - if (point > 0 && t <= points.at(point - 1u).stacked) point--; + while (t > points.at(point).start) point++; + if (point > 0 && t <= points.at(point - 1u).start) point--; return point; } } diff --git a/include/tweenpoint.h b/include/tweenpoint.h index 7deaf49..a14d432 100644 --- a/include/tweenpoint.h +++ b/include/tweenpoint.h @@ -47,7 +47,7 @@ typename traits::durationsArrayType durations; typename traits::easingCollection easings; typename traits::callbackType onEnterCallbacks; - uint32_t stacked{}; + uint32_t start{}; /* Constructs a tweenpoint from a set of values, filling their durations and easings */ explicit tweenpoint(Ts... vs); diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 97419d3..8aef591 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -3,6 +3,7 @@ find_package(Catch2 3 REQUIRED CONFIG) add_executable(tweeny_tests sanity.cpp + tween_tests.cpp ) target_compile_features(tweeny_tests PRIVATE cxx_std_17) diff --git a/tests/sanity.cpp b/tests/sanity.cpp index 404602c..edf89ff 100644 --- a/tests/sanity.cpp +++ b/tests/sanity.cpp @@ -1,5 +1,9 @@ #include +#include +TEST_CASE("sanity - the test framework runs", "[sanity]") { + REQUIRE(1 + 1 == 2); +} TEST_CASE("sanity") { REQUIRE(1 + 1 == 2); } diff --git a/tests/tween_tests.cpp b/tests/tween_tests.cpp new file mode 100644 index 0000000..7decbd9 --- /dev/null +++ b/tests/tween_tests.cpp @@ -0,0 +1,284 @@ +#include +#include "tweeny.h" + +namespace { + constexpr float eps = 1e-4f; + bool approxf(const float a, const float b) { return std::fabs(a - b) <= eps; } +} + +TEST_CASE("tween - step by duration (time-based)", "[tween][step][duration]") { + auto tw = tweeny::from(0.0f).to(100.0f).during(100).via(tweeny::easing::linear); + REQUIRE(tw.duration() == 100); + REQUIRE(approxf(tw.progress(), 0.0f)); + + SECTION("25% progress at t=25") { + tw.step(25u); + REQUIRE(approxf(tw.progress(), 0.25f)); + REQUIRE(approxf(tw.peek(), 25.0f)); + } + SECTION("halfway at t=50") { + tw.step(50u); + REQUIRE(approxf(tw.progress(), 0.5f)); + REQUIRE(approxf(tw.peek(), 50.0f)); + } + SECTION("full at t=100 clamps to 1.0") { + tw.step(100u); + REQUIRE(approxf(tw.progress(), 1.0f)); + REQUIRE(approxf(tw.peek(), 100.0f)); + } +} + +TEST_CASE("tween - step by progress (fractional)", "[tween][step][progress]") { + auto tw = tweeny::from(0.0f).to(100.0f).during(100).via(tweeny::easing::linear); + REQUIRE(approxf(tw.progress(), 0.0f)); + + SECTION("step by 0.25") { + tw.step(0.25f); + REQUIRE(approxf(tw.progress(), 0.25f)); + REQUIRE(approxf(tw.peek(), 25.0f)); + } + SECTION("two steps of 0.5 saturate at 1.0") { + tw.step(0.5f); + tw.step(0.75f); // this should saturate to 1.0 overall + REQUIRE(approxf(tw.progress(), 1.0f)); + REQUIRE(approxf(tw.peek(), 100.0f)); + } +} + +TEST_CASE("tween - seek to absolute progress and time", "[tween][seek]") { + auto tw = tweeny::from(0.0f).to(100.0f).during(200).via(tweeny::easing::linear); + + SECTION("seek by progress") { + tw.seek(0.5f); + REQUIRE(approxf(tw.progress(), 0.5f)); + REQUIRE(approxf(tw.peek(), 50.0f)); + } + + SECTION("seek by time") { + tw.seek(50u); + REQUIRE(approxf(tw.progress(), 0.25f)); + REQUIRE(approxf(tw.peek(), 25.0f)); + tw.seek(200u); + REQUIRE(approxf(tw.progress(), 1.0f)); + REQUIRE(approxf(tw.peek(), 100.0f)); + } +} + +TEST_CASE("tween - multiple points: jump and point indexing", "[tween][jump][points]") { + auto tw = tweeny::from(0.0f) + .to(50.0f).during(100).via(tweeny::easing::linear) + .to(100.0f).during(100).via(tweeny::easing::linear); + + REQUIRE(tw.point() == 0); + + SECTION("jump to point 1 goes to second segment start") { + const auto & v = tw.jump(1); + REQUIRE(tw.point() == 1); + REQUIRE(approxf(v, 50.0f)); + REQUIRE(approxf(tw.progress(), 0.5f)); + } + + SECTION("jump to point 1 then step progresses within that segment") { + tw.jump(1); + tw.step(50u); // half of 100 + REQUIRE(tw.point() == 1); + REQUIRE(approxf(tw.progress(), 0.75f)); + REQUIRE(approxf(tw.peek(), 75.0f)); + } +} + +TEST_CASE("tween - callbacks onStep/onSeek", "[tween][callbacks]") { + auto tw = tweeny::from(0.0f).to(100.0f).during(100).via(tweeny::easing::linear); + + int stepCalls = 0; + int seekCalls = 0; + float lastValue = -1.0f; + + tw.onStep([&](auto & /*t*/, float v) { + ++stepCalls; + lastValue = v; + return true; + }); + + tw.onSeek([&](auto & /*t*/, float v) { + ++seekCalls; + lastValue = v; + return true; + }); + + SECTION("onStep triggers on time step") { + tw.step(25u); + REQUIRE(stepCalls >= 1); + REQUIRE(approxf(lastValue, 25.0f)); + } + + SECTION("onSeek triggers on seek") { + tw.seek(0.5f); + REQUIRE(seekCalls >= 1); + REQUIRE(approxf(lastValue, 50.0f)); + } + + SECTION("suppressed callbacks do not fire") { + stepCalls = seekCalls = 0; + tw.seek(0.25f, true); + tw.step(25u, true); + REQUIRE(stepCalls == 0); + REQUIRE(seekCalls == 0); + } +} + +TEST_CASE("tween - heterogeneous values (linear, equal durations)", "[tween][heterogeneous][linear]") { + // x, y, scale, alpha + auto tw = tweeny::from(0.0f, 400.0f, 0.5f, 0.0f) + .to(100.0f, 100.0f, 1.0f, 1.0f) + .during(300, 300, 300, 300) + .via(tweeny::easing::linear); + + // Halfway through time should be halfway in values for all components + auto [x, y, s, a] = tw.step(150u); + REQUIRE(approxf(x, 50.0f)); + REQUIRE(approxf(y, 250.0f)); + REQUIRE(approxf(s, 0.75f)); + REQUIRE(approxf(a, 0.5f)); + + // peek() should match current and not change state + const auto & cur = tw.peek(); + REQUIRE(approxf(std::get<0>(cur), x)); + REQUIRE(approxf(std::get<1>(cur), y)); + REQUIRE(approxf(std::get<2>(cur), s)); + REQUIRE(approxf(std::get<3>(cur), a)); + + // Absolute peek by time (end) + auto endVals = tw.peek(300u); + REQUIRE(approxf(std::get<0>(endVals), 100.0f)); + REQUIRE(approxf(std::get<1>(endVals), 100.0f)); + REQUIRE(approxf(std::get<2>(endVals), 1.0f)); + REQUIRE(approxf(std::get<3>(endVals), 1.0f)); +} + +TEST_CASE("tween - mixed types (float, int) with linear easing and rounding", "[tween][heterogeneous][int]") { + auto tw = tweeny::from(0.0f, 0) + .to(10.0f, 10) + .during(100) + .via(tweeny::easing::linear); + + auto [f, i] = tw.step(50u); + REQUIRE(approxf(f, 5.0f)); + REQUIRE(i == 5); // integral branch rounds +} + +TEST_CASE("tween - per-component durations saturate independently", "[tween][heterogeneous][durations]") { + // First component finishes 4x faster than the second + auto tw = tweeny::from(0.0f, 0.0f) + .to(10.0f, 10.0f) + .during(50, 200) + .via(tweeny::easing::linear); + + // At t=50: first should be complete; second should be at 0.25 + auto [a, b] = tw.seek(50u); + REQUIRE(approxf(a, 10.0f)); + REQUIRE(approxf(b, 2.5f)); + + // At t=200: both complete + auto [a2, b2] = tw.seek(200u); + REQUIRE(approxf(a2, 10.0f)); + REQUIRE(approxf(b2, 10.0f)); +} + +TEST_CASE("tween - via(index) overrides easing for a specific segment (stepped)", "[tween][via-index][stepped]") { + // Build two segments; leave first as default (linear for float), set second as stepped + auto tw = tweeny::from(0.0f) + .to(100.0f).during(100) + .to(200.0f).during(100); + tw.via(1, tweeny::easing::stepped); + + // Middle of first segment: linear 0 -> 100 + REQUIRE(approxf(tw.seek(50u), 50.0f)); + + // Middle of second segment (t=150): stepped keeps start value (100) + REQUIRE(approxf(tw.seek(150u), 100.0f)); + + // End clamps to final + REQUIRE(approxf(tw.seek(200u), 200.0f)); +} + +TEST_CASE("tween - direction forward/backward and seek unaffected", "[tween][direction]") { + auto tw = tweeny::from(0.0f).to(100.0f).during(100).via(tweeny::easing::linear); + + tw.forward(); + tw.step(25u); + REQUIRE(approxf(tw.progress(), 0.25f)); + REQUIRE(approxf(tw.peek(), 25.0f)); + + tw.backward(); + tw.step(25u); + REQUIRE(approxf(tw.progress(), 0.0f)); + REQUIRE(approxf(tw.peek(), 0.0f)); + + // Seek should ignore direction and set absolute time/progress + tw.backward(); + tw.seek(50u); + REQUIRE(approxf(tw.progress(), 0.5f)); + REQUIRE(approxf(tw.peek(), 50.0f)); +} + +TEST_CASE("tween - peek(progress/time) does not mutate state", "[tween][peek]") { + auto tw = tweeny::from(0.0f).to(100.0f).during(100).via(tweeny::easing::linear); + REQUIRE(approxf(tw.progress(), 0.0f)); + + auto p0 = tw.progress(); + auto pv = tw.peek(0.5f); + (void)pv; + REQUIRE(approxf(tw.progress(), p0)); + + auto pv2 = tw.peek(50u); + (void)pv2; + REQUIRE(approxf(tw.progress(), p0)); +} + +TEST_CASE("tween - multi-point timeline scrubbing and point indexing (linear)", "[tween][timeline][seek][point]") { + auto tl = tweeny::from(0.0f) + .to(100.0f).during(200).via(tweeny::easing::linear) // segment 0 + .to(200.0f).during(400).via(tweeny::easing::linear) // segment 1 + .to(300.0f).during(400).via(tweeny::easing::linear); // segment 2 + + // t=100: middle of segment 0 + REQUIRE(approxf(tl.seek(100u), 50.0f)); + REQUIRE(tl.point() == 0); + + // t=300: 100 into seg0 + 200 into seg1 (half of seg1) + REQUIRE(approxf(tl.seek(300u), 150.0f)); + REQUIRE(tl.point() == 1); + + // t=900: 1000 total; 300 into seg2 (half of seg2) + REQUIRE(approxf(tl.seek(900u), 250.0f)); + REQUIRE(tl.point() == 2); +} + +TEST_CASE("tween - callbacks one-shot removal semantics", "[tween][callbacks][removal]") { + SECTION("onStep one-shot") { + auto tw = tweeny::from(0).to(100).during(100).via(tweeny::easing::linear); + int calls = 0; + tw.onStep([&](auto &, int) { + ++calls; + return true; // remove after first call + }); + + tw.step(10u); + tw.step(10u); + REQUIRE(calls == 1); + } + + SECTION("onSeek one-shot") { + auto tw = tweeny::from(0.0f).to(1.0f).during(100).via(tweeny::easing::linear); + int calls = 0; + tw.onSeek([&](auto &, float) { + ++calls; + return true; // remove after first call + }); + + tw.seek(0.25f); + tw.seek(0.5f); + REQUIRE(calls == 1); + } +} From ff002fd3c4571cfaa2262a8ecd2cc39cbce4fd90 Mon Sep 17 00:00:00 2001 From: Leonardo Date: Tue, 19 Aug 2025 01:03:08 -0300 Subject: [PATCH 17/58] The great refactoring --- CMakeLists.txt | 57 ++- include/dispatcher.h | 53 --- include/easingresolve.h | 126 ----- include/int2type.h | 37 -- include/tween.h | 589 ------------------------ include/tween.tcc | 266 ----------- include/tweenone.tcc | 247 ---------- include/tweenpoint.h | 78 ---- include/tweenpoint.tcc | 122 ----- include/tweentraits.h | 76 --- include/tweeny.h | 102 ---- include/tweeny.tcc | 40 -- include/tweeny/detail/interpolate.h | 60 +++ include/tweeny/detail/key-frame.h | 131 ++++++ include/tweeny/detail/tuple-utilities.h | 61 +++ include/tweeny/detail/tween-value.h | 65 +++ include/tweeny/detail/value-container.h | 95 ++++ include/{ => tweeny/easing}/easing.h | 100 ++++ include/tweeny/tween.h | 59 +++ include/tweeny/tweeny.h | 129 ++++++ src/sandbox.cc | 8 +- src/tweeny/tween.tcc | 137 ++++++ tests/CMakeLists.txt | 1 - tests/sanity.cpp | 3 - tests/tween_tests.cpp | 284 ------------ 25 files changed, 871 insertions(+), 2055 deletions(-) delete mode 100644 include/dispatcher.h delete mode 100644 include/easingresolve.h delete mode 100644 include/int2type.h delete mode 100644 include/tween.h delete mode 100644 include/tween.tcc delete mode 100644 include/tweenone.tcc delete mode 100644 include/tweenpoint.h delete mode 100644 include/tweenpoint.tcc delete mode 100644 include/tweentraits.h delete mode 100644 include/tweeny.h delete mode 100644 include/tweeny.tcc create mode 100644 include/tweeny/detail/interpolate.h create mode 100644 include/tweeny/detail/key-frame.h create mode 100644 include/tweeny/detail/tuple-utilities.h create mode 100644 include/tweeny/detail/tween-value.h create mode 100644 include/tweeny/detail/value-container.h rename include/{ => tweeny/easing}/easing.h (87%) create mode 100644 include/tweeny/tween.h create mode 100644 include/tweeny/tweeny.h create mode 100644 src/tweeny/tween.tcc delete mode 100644 tests/tween_tests.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 501757c..cc0b6a5 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -51,28 +51,27 @@ add_library(tweeny::tweeny ALIAS tweeny) # Set up include directories target_include_directories(tweeny INTERFACE - $ - $ + $ + $ + $ + $ ) # Attach headers to the interface target for IDEs and installation -target_sources(tweeny - INTERFACE - FILE_SET HEADERS - BASE_DIRS ${CMAKE_CURRENT_SOURCE_DIR}/include - FILES - include/tweeny.h - include/tweeny.tcc - include/tween.h - include/tween.tcc - include/tweenone.tcc - include/tweenpoint.h - include/tweenpoint.tcc - include/tweentraits.h - include/easing.h - include/easingresolve.h - include/int2type.h - include/dispatcher.h +target_sources(tweeny INTERFACE + FILE_SET HEADERS + BASE_DIRS + ${CMAKE_CURRENT_SOURCE_DIR}/include/tweeny + ${CMAKE_CURRENT_SOURCE_DIR}/src/tweeny + FILES + include/tweeny/tweeny.h + include/tweeny/tween.h + src/tweeny/tween.tcc + include/tweeny/detail/key-frame.h + include/tweeny/detail/traits.h + include/tweeny/detail/value-container.h + include/tweeny/detail/easing.h + include/tweeny/detail/easing-resolve.h ) # Set up install @@ -83,19 +82,19 @@ install(TARGETS tweeny EXPORT TweenyTargets FILE_SET HEADERS) include(cmake/SetupExports.cmake) if (TWEENY_BUILD_DOCUMENTATION) - add_subdirectory(doc) -endif() + add_subdirectory(doc) +endif () if (TWEENY_BUILD_TESTS) - enable_testing() - add_subdirectory(tests) -endif() + enable_testing() + add_subdirectory(tests) +endif () if (TWEENY_BUILD_SINGLE_HEADER) - include(cmake/GenerateSingleHeader.cmake) -endif() + include(cmake/GenerateSingleHeader.cmake) +endif () if (TWEENY_BUILD_SANDBOX) - add_executable(sandbox src/sandbox.cc) - target_link_libraries(sandbox tweeny) -endif() + add_executable(sandbox src/sandbox.cc) + target_link_libraries(sandbox tweeny) +endif () diff --git a/include/dispatcher.h b/include/dispatcher.h deleted file mode 100644 index 8798578..0000000 --- a/include/dispatcher.h +++ /dev/null @@ -1,53 +0,0 @@ -/* - This file is part of the Tweeny library. - - Copyright (c) 2016-2021 Leonardo Guilherme Lucena de Freitas - Copyright (c) 2016 Guilherme R. Costa - - Permission is hereby granted, free of charge, to any person obtaining a copy of - this software and associated documentation files (the "Software"), to deal in - the Software without restriction, including without limitation the rights to - use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of - the Software, and to permit persons to whom the Software is furnished to do so, - subject to the following conditions: - - The above copyright notice and this permission notice shall be included in all - copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS - FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR - COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER - IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -*/ - -/* This file contains code to help call a function applying a tuple as its arguments. - * This code is private and not documented. */ - -#ifndef TWEENY_DISPATCHER_H -#define TWEENY_DISPATCHER_H - -#include - - -namespace tweeny::detail { - template struct seq { }; - template struct gens : gens { }; - template struct gens<0, S...> { - typedef seq type; - }; - - template - R dispatch(Func && f, TupleType && args, seq) { - return f(std::get(args) ...); - } - - template - R call(Func && f, const std::tuple & args) { - return dispatch(f, args, typename gens::type()); - } -} - - -#endif //TWEENY_DISPATCHER_H diff --git a/include/easingresolve.h b/include/easingresolve.h deleted file mode 100644 index 17d8281..0000000 --- a/include/easingresolve.h +++ /dev/null @@ -1,126 +0,0 @@ -/* - This file is part of the Tweeny library. - - Copyright (c) 2016-2021 Leonardo Guilherme Lucena de Freitas - Copyright (c) 2016 Guilherme R. Costa - - Permission is hereby granted, free of charge, to any person obtaining a copy of - this software and associated documentation files (the "Software"), to deal in - the Software without restriction, including without limitation the rights to - use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of - the Software, and to permit persons to whom the Software is furnished to do so, - subject to the following conditions: - - The above copyright notice and this permission notice shall be included in all - copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS - FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR - COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER - IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -*/ - -/* - * This file provides the easing resolution mechanism so that the library user can mix lambdas and the bundled - * pre-defined easing functions. It shall not be used directly. - * This file is private. - */ - -#ifndef TWEENY_EASINGRESOLVE_H -#define TWEENY_EASINGRESOLVE_H - -#include -#include "easing.h" - -namespace tweeny::detail { - using std::get; - - template - struct easingresolve { - static void impl(FunctionTuple &b, Fs... fs) { - if (sizeof...(Fs) == 0) return; - easingresolve::impl(b, fs...); - } - }; - - template - struct easingresolve { - static void impl(FunctionTuple &b, F1 f1, Fs... fs) { - get(b) = f1; - easingresolve::impl(b, fs...); - } - }; - - template - struct easingresolve { - typedef std::tuple_element_t ArgType; - - static void impl(FunctionTuple &b, easing::steppedEasing, Fs... fs) { - get(b) = easing::stepped.run; - easingresolve::impl(b, fs...); - } - }; - - template - struct easingresolve { - typedef std::tuple_element_t ArgType; - - static void impl(FunctionTuple &b, easing::linearEasing, Fs... fs) { - get(b) = easing::linear.run; - easingresolve::impl(b, fs...); - } - }; - template - struct easingresolve { - typedef std::tuple_element_t ArgType; - - static void impl(FunctionTuple &b, easing::defaultEasing, Fs... fs) { - get(b) = easing::def.run; - easingresolve::impl(b, fs...); - } - }; - - #define DECLARE_EASING_RESOLVE(__EASING_TYPE__) \ - template \ - struct easingresolve { \ - typedef typename std::tuple_element::type ArgType; \ - static void impl(FunctionTuple & b, decltype(easing::__EASING_TYPE__ ## In), Fs... fs) { \ - get(b) = easing::__EASING_TYPE__ ## In.run; \ - easingresolve::impl(b, fs...); \ - } \ - }; \ - \ - template \ - struct easingresolve { \ - typedef typename std::tuple_element::type ArgType; \ - static void impl(FunctionTuple & b, decltype(easing::__EASING_TYPE__ ## Out), Fs... fs) { \ - get(b) = easing::__EASING_TYPE__ ## Out.run; \ - easingresolve::impl(b, fs...); \ - } \ - }; \ - \ - template \ - struct easingresolve { \ - typedef typename std::tuple_element::type ArgType; \ - static void impl(FunctionTuple & b, decltype(easing::__EASING_TYPE__ ## InOut), Fs... fs) { \ - get(b) = easing::__EASING_TYPE__ ## InOut.run; \ - easingresolve::impl(b, fs...); \ - } \ - } - - DECLARE_EASING_RESOLVE(quadratic); - DECLARE_EASING_RESOLVE(cubic); - DECLARE_EASING_RESOLVE(quartic); - DECLARE_EASING_RESOLVE(quintic); - DECLARE_EASING_RESOLVE(sinusoidal); - DECLARE_EASING_RESOLVE(exponential); - DECLARE_EASING_RESOLVE(circular); - DECLARE_EASING_RESOLVE(bounce); - DECLARE_EASING_RESOLVE(elastic); - DECLARE_EASING_RESOLVE(back); -} - - -#endif //TWEENY_EASINGRESOLVE_H diff --git a/include/int2type.h b/include/int2type.h deleted file mode 100644 index 8adf77c..0000000 --- a/include/int2type.h +++ /dev/null @@ -1,37 +0,0 @@ -/* - This file is part of the Tweeny library. - - Copyright (c) 2016-2021 Leonardo Guilherme Lucena de Freitas - Copyright (c) 2016 Guilherme R. Costa - - Permission is hereby granted, free of charge, to any person obtaining a copy of - this software and associated documentation files (the "Software"), to deal in - the Software without restriction, including without limitation the rights to - use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of - the Software, and to permit persons to whom the Software is furnished to do so, - subject to the following conditions: - - The above copyright notice and this permission notice shall be included in all - copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS - FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR - COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER - IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -*/ - -/* - * This file declares a helper struct to create a type from a integer value, to aid in template tricks. - * This file is private. - */ -#ifndef TWEENY_INT2TYPE_H -#define TWEENY_INT2TYPE_H - - - namespace tweeny::detail { - template struct int2type { }; - } - -#endif //TWEENY_INT2TYPE_H diff --git a/include/tween.h b/include/tween.h deleted file mode 100644 index 997b2a1..0000000 --- a/include/tween.h +++ /dev/null @@ -1,589 +0,0 @@ -/* - This file is part of the Tweeny library. - - Copyright (c) 2016-2021 Leonardo Guilherme Lucena de Freitas - Copyright (c) 2016 Guilherme R. Costa - - Permission is hereby granted, free of charge, to any person obtaining a copy of - this software and associated documentation files (the "Software"), to deal in - the Software without restriction, including without limitation the rights to - use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of - the Software, and to permit persons to whom the Software is furnished to do so, - subject to the following conditions: - - The above copyright notice and this permission notice shall be included in all - copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS - FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR - COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER - IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -*/ - -/** - * @file tween.h - * This file contains the core of tweeny: the main tween class. - */ - -#ifndef TWEENY_TWEEN_H -#define TWEENY_TWEEN_H - -#include -#include - -#include "tweentraits.h" -#include "tweenpoint.h" - -namespace tweeny { - /** - * @brief The tween class is the core class of tweeny. It controls the interpolation steps, easings and durations. - * - * It should not be constructed manually but rather from @p tweeny::from, to facilitate template argument - * deduction (and also to keep your code clean). - */ - template - class tween { - public: - /** - * @brief Instantiates a tween from a starting point. - * - * This is a static factory helper function to be used by @p tweeny::from. You should not use this directly. - * @p t The first value in the point - * @p vs The remaining values - */ - static tween from(T t, Ts... vs); - - /** - * @brief Default constructor for a tween - * - * This constructor is provided to facilitate the usage of containers of tweens (e.g, std::vector). It - * should not be used manually as the tweening created by it is invalid. - */ - tween(); - - /** - * @brief Adds a new point in this tweening. - * - * This will add a new tweening point with the specified values. Next calls to @p via and @p during - * will refer to this point. - * - * **Example** - * - * @code - * auto t = tweeny::from(0).to(100).to(200); - * @endcode - * - * @param t The first point value - * @param vs Point values - * @returns *this - */ - tween & to(T t, Ts... vs); - - /** - * @brief Specifies the easing function for the last added point. - * - * This will specify the easing between the last tween point added by @p to and its previous step. You can - * use any callable object. Additionally, you can use the easing objects specified in the class @p easing. - * - * If it is a multi-value point, you can either specify a single easing function that will be used for - * every value, or you can specify an easing function for each value. You can mix and match callable objects, - * lambdas and bundled easing objects. - * - * **Example**: - * - * @code - * // use bundled linear easing - * auto tween1 = tweeny::from(0).to(100).via(tweeny::easing::linear); - * - * // use custom lambda easing - * auto tween2 = tweeny::from(0).to(100).via([](float p, int a, int b) { return (b-a) * p + a; }); - * @endcode - * - * @param fs The functions - * @returns *this - * @see tweeny::easing - */ - template tween & via(Fs... fs); - - /** - * @brief Specifies the easing function for a specific point. - * - * Points start at index 0. Index 0 refers to the first @p to call. - * Using this function without adding a point with @p to leads to undefined - * behavior. - * - * @param index The tween point index - * @param fs The functions - * @returns *this - * @see tweeny::easing - */ - template tween & via(int index, Fs... fs); - - /** - * @brief Specifies the duration, typically in milliseconds, for the tweening of values in the last point. - * - * You can either specify a single duration for all values or give every value its own duration. Value types - * must be convertible to the uint16_t type. - * - * **Example**: - * - * @code - * // Specify that the first point will be reached in 100 milliseconds and the first value in the second - * // point in 100, whereas the second value in the second point will be reached in 500. - * auto tween = tweeny::from(0, 0).to(100, 200).during(100).to(200, 300).during(100, 500); - * @endcode - * - * @param ds Duration values - * @returns *this - */ - template tween & during(Ds... ds); - - /** - * @brief Steps the animation by the designated delta amount. - * - * You should call this every frame of your application, passing in the amount of delta time that - * you want to animate. - * - * **Example**: - * - * @code - * // tween duration is 100 ms - * auto tween = tweeny::from(0).to(100).during(100); - * - * // steps for 16 ms - * tween.step(16); - * @endcode - * - * @param dt Delta duration - * @param suppressCallbacks (Optional) Suppress callbacks registered with tween::onStep() - * @returns std::tuple with the current tween values. - */ - const typename detail::tweentraits::valuesType & step(int32_t dt, bool suppressCallbacks = false); - - /** - * @brief Steps the animation by the designated delta amount. - * - * You should call this every frame of your application, passing in the amount of delta time that - * you want to animate. This overload exists to match unsigned int arguments. - * - * @param dt Delta duration - * @param suppressCallbacks (Optional) Suppress callbacks registered with tween::onStep() - * @returns std::tuple with the current tween values. - */ - const typename detail::tweentraits::valuesType & step(uint32_t dt, bool suppressCallbacks = false); - - /** - * @brief Steps the animation by the designated percentage amount. - * - * You can use this function to step the tweening by a specified percentage delta. - - * **Example**: - * - * @code - * // tween duration is 100 ms - * auto tween = tweeny::from(0).to(100).during(100); - * - * // steps for 16 ms - * tween.step(0.001f); - * @endcode - * - * @param dp Delta percentage, between `0.0f` and `1.0f` - * @param suppressCallbacks (Optional) Suppress callbacks registered with tween::onStep() - * @returns std::tuple with the current tween values. - */ - const typename detail::tweentraits::valuesType & step(float dp, bool suppressCallbacks = false); - - /** - * @brief Seeks to a specified point in time based on the currentProgress. - * - * This function sets the current animation time and currentProgress. Callbacks set by @p call will be triggered. - * - * @param p The percentage to seek to, between 0.0f and 1.0f, inclusive. - * @param suppressCallbacks (Optional) Suppress callbacks registered with tween::onSeek() - * @returns std::tuple with the current tween values. - */ - const typename detail::tweentraits::valuesType & seek(float p, bool suppressCallbacks = false); - - /** - * @brief Seeks to a specified point in time. - * - * This function sets the current animation time and currentProgress. Callbacks set by @p call will be triggered. - * - * @param d The duration to seek to, between 0 and the total duration. - * @param suppressCallbacks (Optional) Suppress callbacks registered with tween::onSeek() - * @returns std::tuple with the current tween values. - * @see duration - */ - const typename detail::tweentraits::valuesType & seek(int32_t d, bool suppressCallbacks = false); - - /** - * @brief Seeks to a specified point in time. - * - * This function sets the current animation time and currentProgress. Callbacks set by @p call will be triggered. - * - * @param d The duration to seek to, between 0 and the total duration. - * @param suppressCallbacks (Optional) Suppress callbacks registered with tween::onSeek() - * @returns std::tuple with the current tween values. - * @see duration - */ - const typename detail::tweentraits::valuesType & seek(uint32_t d, bool suppressCallbacks = false); - - /** - * @brief Adds a callback that will be called when stepping occurs, accepting both the tween and - * its values. - * - * You can add as many callbacks as you want. Its argument types must be equal to the argument types - * of a tween instance, preceded by a variable of the tween type. Callbacks can be of any callable type. It will only be called - * via tween::step() functions. For seek callbacks, see tween::onSeek(). - * - * Keep in mind that the function will be *copied* into an array, so any variable captured by value - * will also be copied with it. - * - * If the callback returns false, it will be called next time. If it returns true, it will be removed from - * the callback queue. - * - * **Example**: - * - * @code - * auto t = tweeny::from(0).to(100).during(100); - * - * // pass a lambda - * t.onStep([](tweeny::tween & t, int v) { printf("%d", v); return false; }); - * - * // pass a functor instance - * struct ftor { void operator()(tweeny::tween & t, int v) { printf("%d", v); return false; } }; - * t.onStep(ftor()); - * @endcode - * @sa step - * @sa seek - * @sa onSeek - * @param callback A callback in with the prototype `bool callback(tween & t, Ts...)` - */ - tween & onStep(typename detail::tweentraits::callbackType callback); - - /** - * @brief Adds a callback that will be called when stepping occurs, accepting only the tween. - * - * You can add as many callbacks as you want. It must receive the tween as an argument. - * Callbacks can be of any callable type. It will only be called - * via tween::step() functions. For seek callbacks, see tween::onSeek(). - * - * Keep in mind that the function will be *copied* into an array, so any variable captured by value - * will also be copied with it. - * - * If the callback returns false, it will be called next time. If it returns true, it will be removed from - * the callback queue. - * - * **Example**: - * - * @code - * auto t = tweeny::from(0).to(100).during(100); - * - * // pass a lambda - * t.onStep([](tweeny::tween & t) { printf("%d", t.value()); return false; }); - * - * // pass a functor instance - * struct ftor { void operator()(tweeny::tween & t) { printf("%d", t.values()); return false; } }; - * t.onStep(ftor()); - * @endcode - * @sa step - * @sa seek - * @sa onSeek - * @param callback A callback in the form `bool f(tween & t)` - */ - tween & onStep(typename detail::tweentraits::noValuesCallbackType callback); - - /** - * @brief Adds a callback that will be called when stepping occurs, accepting only the tween values. - * - * You can add as many callbacks as you want. It must receive the tween values as an argument. - * Callbacks can be of any callable type. It will only be called - * via tween::step() functions. For seek callbacks, see tween::onSeek(). - * - * Keep in mind that the function will be *copied* into an array, so any variable captured by value - * will also be copied with it. - * - * If the callback returns false, it will be called next time. If it returns true, it will be removed from - * the callback queue. - * - * **Example**: - * - * @code - * auto t = tweeny::from(0).to(100).during(100); - * - * // pass a lambda - * t.onStep([](int v) { printf("%d", v); return false; }); - * - * // pass a functor instance - * struct ftor { void operator ()(int x) { printf("%d", x); return false; } }; - * t.onStep(ftor()); - * @endcode - * @sa step - * @sa seek - * @sa onSeek - * @param callback A callback in the form `bool f(Ts...)` - */ - tween & onStep(typename detail::tweentraits::noTweenCallbackType callback); - - /** - * @brief Adds a callback for that will be called when seeking - * - * You can add as many callbacks as you want. Its argument types must be equal to the argument types - * of a tween instance, preceded by a variable of the tween typve. Callbacks can be of any callable type. It will be called - * via tween::seek() functions. For step callbacks, see tween::onStep(). - * - * Keep in mind that the function will be *copied* into an array, so any variable captured by value - * will also be copied with it. - * - * If the callback returns false, it will be called next time. If it returns true, it will be removed from - * the callback queue. - * - * **Example**: - * - * @code - * auto t = tweeny::from(0).to(100).during(100); - * - * // pass a lambda - * t.onSeek([](tweeny::tween & t, int v) { printf("%d", v); }); - * - * // pass a functor instance - * struct ftor { void operator()(tweeny::tween & t, int v) { printf("%d", v); } }; - * t.onSeek(ftor()); - * @endcode - * @param callback A callback in with the prototype `bool callback(tween & t, Ts...)` - */ - tween & onSeek(typename detail::tweentraits::callbackType callback); - - /** - * @brief Adds a callback for that will be called when seeking occurs, accepting only the tween values. - * - * You can add as many callbacks as you want. It must receive the tween as an argument. - * Callbacks can be of any callable type. It will be called - * via tween::seek() functions. For step callbacks, see tween::onStep(). - * - * Keep in mind that the function will be *copied* into an array, so any variable captured by value - * will also be copied again. - * - * If the callback returns false, it will be called next time. If it returns true, it will be removed from - * the callback queue. - * - * **Example**: - * - * @code - * auto t = tweeny::from(0).to(100).during(100); - * - * // pass a lambda - * t.onSeek([](int v) { printf("%d", v); }); - * - * // pass a functor instance - * struct ftor { void operator ()(int v) { printf("%d", v); return false; } }; - * t.onSeek(ftor()); - * @endcode - * @param callback A callback in the form `bool f(Ts...)` - */ - tween & onSeek(typename detail::tweentraits::noTweenCallbackType callback); - - /** - * @brief Adds a callback for that will be called when seeking occurs, accepting only the tween. - * - * You can add as many callbacks as you want. It must receive the tween as an argument. - * Callbacks can be of any callable type. It will be called - * via tween::seek() functions. For step callbacks, see tween::onStep(). - * - * Keep in mind that the function will be *copied* into an array, so any variable captured by value - * will also be copied again. - * - * If the callback returns false, it will be called next time. If it returns true, it will be removed from - * the callback queue. - * - * **Example**: - * - * @code - * auto t = t:from(0).to(100).during(100); - * - * // pass a lambda - * t.onSeek([](tweeny::tween & t) { printf("%d ", t.value()); return false; }); - * - * // pass a functor instance - * struct ftor { void operator()(tweeny::tween & t) { printf("%d ", t.value()); return false; } }; - * t.onSeek(ftor()); - * @endcode - * @param callback A callback in the form `bool f(tween & t)` - */ - tween & onSeek(typename detail::tweentraits::noValuesCallbackType callback); - - /** - * @brief Returns the total duration of this tween - * - * @returns The duration of all the tween points. - */ - [[nodiscard]] uint32_t duration() const; - - /** - * @brief Returns the current tween values - * - * This returns the current tween value as returned by the - * tween::step() function, except that it does not perform a step. - * @returns std::tuple with the current tween values. - */ - const typename detail::tweentraits::valuesType & peek() const; - - /** - * @brief Calculates and returns the tween values at a given progress - * - * This returns the tween value at the requested progress, without stepping - * or seeking. - * @returns std::tuple with the current tween values. - */ - const typename detail::tweentraits::valuesType peek(float progress) const; - - - /** - * @brief Calculates and return the tween values at a given time - * - * This returns the tween values at the requested time, without stepping - * or seeking. - * @returns std::tuple with the calculated tween values. - */ - const typename detail::tweentraits::valuesType peek(uint32_t time) const; - - /** - * @brief Returns the current currentProgress of the interpolation. - * - * 0 means it's at the values passed in the construction; 1 means the last step. - * @returns the current currentProgress between 0 and 1 (inclusive) - */ - [[nodiscard]] float progress() const; - - /** - * @brief Sets the direction of this tween forward. - * - * Note that this only affects the tween::step () function. - * @returns *this - * @sa backward - */ - tween & forward(); - - /** - * @brief Sets the direction of this tween backward. - * - * Note that this only affects the tween::step () function. - * @returns *this - * @sa forward - */ - tween & backward(); - - /** - * @brief Returns the current direction of this tween - * - * @returns -1 If it is moving backwards in time, 1 if it is moving forward in time - */ - [[nodiscard]] int direction() const; - - /** - * @brief Jumps to a specific tween point - * - * This will seek the tween to a percentage matching the beginning of that step. - * - * @param point The point to seek to. 0 means the point passed in tweeny::from - * @param suppressCallbacks (optional) set to true to suppress seek() callbacks - * @returns current values - * @sa seek - */ - const typename detail::tweentraits::valuesType & jump(size_t point, bool suppressCallbacks = false); - - /** - * @brief Returns the current tween point - * - * @returns Current tween point - */ - [[nodiscard]] uint16_t point() const; - - private: - using traits = detail::tweentraits; - uint32_t total = 0; // total runtime - uint16_t currentPoint = 0; // current point - float currentProgress = 0; // current progress - std::vector> points; - typename traits::valuesType current; - std::vector onStepCallbacks; - std::vector onSeekCallbacks; - int8_t currentDirection = 1; - - /* member functions */ - explicit tween(T t, Ts... vs); - template void interpolate(float prog, unsigned point, typename traits::valuesType & values, detail::int2type) const; - void interpolate(float prog, unsigned point, typename traits::valuesType & values, detail::int2type<0>) const; - void render(float p); - void dispatch(std::vector & cbVector); - [[nodiscard]] uint16_t pointAt(float progress) const; - }; - - /** - * @brief Class specialization when a tween has a single value - * - * This class is preferred automatically by your compiler when your tween has only one value. It exists mainly - * so that you dont need to use std::get<0> to obtain a single value when using tween::step, tween::seek or any other - * value returning function. Other than that, you should look at the - * tweeny::tween documentation. - * - * Except for this little detail, this class methods and behaviours are exactly the same. - */ - template - class tween { - public: - static tween from(T t); - tween(); ///< @sa tween::tween - tween & to(T t); ///< @sa tween::to - template tween & via(Fs... fs); ///< @sa tween::via - template tween & via(int index, Fs... fs); ///< @sa tween::via - template tween & during(Ds... ds); ///< @sa tween::during - const T & step(int32_t dt, bool suppressCallbacks = false); ///< @sa tween::step(int32_t dt, bool suppressCallbacks) - const T & step(uint32_t dt, bool suppressCallbacks = false); ///< @sa tween::step(uint32_t dt, bool suppressCallbacks) - const T & step(float dp, bool suppressCallbacks = false); ///< @sa tween::step(float dp, bool suppressCallbacks) - const T & seek(float p, bool suppressCallbacks = false); ///< @sa tween::seek(float p, bool suppressCallbacks) - const T & seek(int32_t d, bool suppressCallbacks = false); ///< @sa tween::seek(int32_t d, bool suppressCallbacks) - const T & seek(uint32_t d, bool suppressCallbacks = false); ///< @sa tween::seek(uint32_t d, bool suppressCallbacks) - tween & onStep(typename detail::tweentraits::callbackType callback); ///< @sa tween::onStep - tween & onStep(typename detail::tweentraits::noValuesCallbackType callback); ///< @sa tween::onStep - tween & onStep(typename detail::tweentraits::noTweenCallbackType callback); ///< @sa tween::onStep - tween & onSeek(typename detail::tweentraits::callbackType callback); ///< @sa tween::onSeek - tween & onSeek(typename detail::tweentraits::noValuesCallbackType callback); ///< @sa tween::onSeek - tween & onSeek(typename detail::tweentraits::noTweenCallbackType callback); ///< @sa tween::onSeek - const T & peek() const; ///< @sa tween::peek - T peek(float progress) const; ///< @sa tween::peek - T peek(uint32_t time) const; ///< @sa tween::peek - [[nodiscard]] uint32_t duration() const; ///< @sa tween::duration - [[nodiscard]] float progress() const; ///< @sa tween::progress - tween & forward(); ///< @sa tween::forward - tween & backward(); ///< @sa tween::backward - [[nodiscard]] int direction() const; ///< @sa tween::direction - const T & jump(size_t point, bool suppressCallbacks = false); ///< @sa tween::jump - [[nodiscard]] uint16_t point() const; ///< @sa tween::point - - private: - using traits = detail::tweentraits; - uint32_t total = 0; // total runtime - uint16_t currentPoint = 0; // current point - float currentProgress = 0; // current progress - std::vector> points; - T current; - std::vector onStepCallbacks; - std::vector onSeekCallbacks; - int8_t currentDirection = 1; - - /* member functions */ - explicit tween(T t); - void interpolate(float prog, unsigned point, T & value) const; - void render(float p); - void dispatch(std::vector & cbVector); - [[nodiscard]] uint16_t pointAt(float progress) const; - }; -} - -#include "tween.tcc" -#include "tweenone.tcc" - -#endif //TWEENY_TWEEN_H diff --git a/include/tween.tcc b/include/tween.tcc deleted file mode 100644 index 56a87be..0000000 --- a/include/tween.tcc +++ /dev/null @@ -1,266 +0,0 @@ -/* - This file is part of the Tweeny library. - - Copyright (c) 2016-2021 Leonardo Guilherme Lucena de Freitas - Copyright (c) 2016 Guilherme R. Costa - - Permission is hereby granted, free of charge, to any person obtaining a copy of - this software and associated documentation files (the "Software"), to deal in - the Software without restriction, including without limitation the rights to - use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of - the Software, and to permit persons to whom the Software is furnished to do so, - subject to the following conditions: - - The above copyright notice and this permission notice shall be included in all - copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS - FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR - COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER - IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -*/ - -/* - * The purpose of this file is to hold implementations for the tween.h file. - */ - -#ifndef TWEENY_TWEEN_TCC -#define TWEENY_TWEEN_TCC - -#include "tween.h" -#include "dispatcher.h" - -namespace tweeny { - - namespace detail { - template - T clip(const T & n, const T & lower, const T & upper) { - return std::max(lower, std::min(n, upper)); - } - } - - template tween tween::from(T t, Ts... vs) { return tween(t, vs...); } - template tween::tween() = default; - template tween::tween(T t, Ts... vs) { - points.emplace_back(t, vs...); - } - - template tween & tween::to(T t, Ts... vs) { - points.emplace_back(t, vs...); - return *this; - } - - template - template - tween & tween::via(Fs... fs) { - points.at(points.size() - 2).via(fs...); - return *this; - } - - template - template - tween & tween::via(const int index, Fs... fs) { - points.at(static_cast(index)).via(fs...); - return *this; - } - - template - template - tween & tween::during(Ds... ds) { - total = 0; - points.at(points.size() - 2).during(ds...); - for (detail::tweenpoint & p : points) { - p.start = total; - total += p.duration(); - } - return *this; - } - - template - const typename detail::tweentraits::valuesType & tween::step(const int32_t dt, const bool suppressCallbacks) { - return step(static_cast(dt)/static_cast(total), suppressCallbacks); - } - - template - const typename detail::tweentraits::valuesType & tween::step(const uint32_t dt, const bool suppressCallbacks) { - return step(static_cast(dt), suppressCallbacks); - } - - template - const typename detail::tweentraits::valuesType & tween::step(float dp, const bool suppressCallbacks) { - dp *= currentDirection; - seek(currentProgress + dp, true); - if (!suppressCallbacks) dispatch(onStepCallbacks); - return current; - } - - template - const typename detail::tweentraits::valuesType & tween::seek(float p, const bool suppressCallbacks) { - p = detail::clip(p, 0.0f, 1.0f); - currentProgress = p; - render(p); - if (!suppressCallbacks) dispatch(onSeekCallbacks); - return current; - } - - template - const typename detail::tweentraits::valuesType & tween::seek(const int32_t d, const bool suppressCallbacks) { - return seek(static_cast(d) / static_cast(total), suppressCallbacks); - } - - template - const typename detail::tweentraits::valuesType & tween::seek(const uint32_t d, const bool suppressCallbacks) { - return seek(static_cast(d) / static_cast(total), suppressCallbacks); - } - - template - uint32_t tween::duration() const { - return total; - } - - template - template - void tween::interpolate(float prog, unsigned point, typename traits::valuesType & values, detail::int2type) const { - auto & p = points.at(point); - const auto pointDuration = static_cast(p.duration() - (p.start - prog * static_cast(total))); - float pointTotal = static_cast(pointDuration) / static_cast(p.duration(I)); - if (pointTotal > 1.0f) pointTotal = 1.0f; - auto easing = std::get(p.easings); - std::get(values) = easing(pointTotal, std::get(p.values), std::get(points.at(point+1).values)); - interpolate(prog, point, values, detail::int2type{ }); - } - - template - void tween::interpolate(float prog, unsigned point, typename traits::valuesType & values, detail::int2type<0>) const { - auto & p = points.at(point); - auto pointDuration = static_cast(p.duration() - (p.start - prog * static_cast(total))); - float pointTotal = static_cast(pointDuration) / static_cast(p.duration(0)); - if (pointTotal > 1.0f) pointTotal = 1.0f; - auto easing = std::get<0>(p.easings); - std::get<0>(values) = easing(pointTotal, std::get<0>(p.values), std::get<0>(points.at(point+1).values)); - } - - template - void tween::render(float p) { - currentPoint = pointAt(p); - interpolate(p, currentPoint, current, detail::int2type{ }); - } - - template - tween & tween::onStep(typename detail::tweentraits::callbackType callback) { - onStepCallbacks.push_back(callback); - return *this; - } - - template - tween & tween::onStep(typename detail::tweentraits::noValuesCallbackType callback) { - onStepCallbacks.push_back([callback](tween & t, T, Ts...) { return callback(t); }); - return *this; - } - - template - tween & tween::onStep(typename detail::tweentraits::noTweenCallbackType callback) { - onStepCallbacks.push_back([callback](tween &, T t, Ts... vs) { return callback(t, vs...); }); - return *this; - } - - template - tween & tween::onSeek(typename detail::tweentraits::callbackType callback) { - onSeekCallbacks.push_back(callback); - return *this; - } - - template - tween & tween::onSeek(typename detail::tweentraits::noValuesCallbackType callback) { - onSeekCallbacks.push_back([callback](tween & t, T, Ts...) { return callback(t); }); - return *this; - } - - template - tween & tween::onSeek(typename detail::tweentraits::noTweenCallbackType callback) { - onSeekCallbacks.push_back([callback](tween &, T t, Ts... vs) { return callback(t, vs...); }); - return *this; - } - - template - void tween::dispatch(std::vector & cbVector) { - std::vector dismissed; - for (size_t i = 0; i < cbVector.size(); ++i) { - auto && cb = cbVector[i]; - if (detail::call(cb, std::tuple_cat(std::make_tuple(std::ref(*this)), current))) dismissed.push_back(i); - } - - if (!dismissed.empty()) { - for (size_t i = 0; i < dismissed.size(); ++i) { - size_t index = dismissed[i]; - cbVector[index] = cbVector.at(cbVector.size() - 1 - i); - } - cbVector.resize(cbVector.size() - dismissed.size()); - } - } - - template - const typename detail::tweentraits::valuesType & tween::peek() const { - return current; - } - - template - const typename detail::tweentraits::valuesType tween::peek(float progress) const { - typename detail::tweentraits::valuesType values; - interpolate(progress, pointAt(progress), values, detail::int2type{ }); - return values; - } - - template - const typename detail::tweentraits::valuesType tween::peek(uint32_t time) const { - typename detail::tweentraits::valuesType values; - float progress = static_cast(time) / static_cast(total); - interpolate(progress, pointAt(progress), values, detail::int2type{ }); - return values; - } - - template - float tween::progress() const { - return currentProgress; - } - - template - tween & tween::forward() { - currentDirection = 1; - return *this; - } - - template - tween & tween::backward() { - currentDirection = -1; - return *this; - } - - template - int tween::direction() const { - return currentDirection; - } - - template - const typename detail::tweentraits::valuesType & tween::jump(std::size_t point, bool suppressCallbacks) { - point = detail::clip(point, static_cast(0), points.size() -1); - return seek(static_cast(points.at(point).start), suppressCallbacks); - } - - template uint16_t tween::point() const { - return currentPoint; - } - - template uint16_t tween::pointAt(float progress) const { - progress = detail::clip(progress, 0.0f, 1.0f); - auto t = static_cast(progress * total); - uint16_t point = 0; - while (t > points.at(point).start) point++; - if (point > 0 && t <= points.at(point - 1u).start) point--; - return point; - } -} - -#endif //TWEENY_TWEEN_TCC diff --git a/include/tweenone.tcc b/include/tweenone.tcc deleted file mode 100644 index b5babe6..0000000 --- a/include/tweenone.tcc +++ /dev/null @@ -1,247 +0,0 @@ -/* - This file is part of the Tweeny library. - - Copyright (c) 2016-2021 Leonardo Guilherme Lucena de Freitas - Copyright (c) 2016 Guilherme R. Costa - - Permission is hereby granted, free of charge, to any person obtaining a copy of - this software and associated documentation files (the "Software"), to deal in - the Software without restriction, including without limitation the rights to - use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of - the Software, and to permit persons to whom the Software is furnished to do so, - subject to the following conditions: - - The above copyright notice and this permission notice shall be included in all - copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS - FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR - COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER - IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -*/ - -/* - * The purpose of this file is to hold implementations for the tween.h file, s - * pecializing on the single value case. - */ -#ifndef TWEENY_TWEENONE_TCC -#define TWEENY_TWEENONE_TCC - -namespace tweeny { - template tween tween::from(T t) { return tween(t); } - template tween::tween() = default; - template tween::tween(T t) { - points.emplace_back(t); - } - - template tween & tween::to(T t) { - points.emplace_back(t); - return *this; - } - - template - template - tween & tween::via(Fs... fs) { - points.at(points.size() - 2).via(fs...); - return *this; - } - - template - template - tween & tween::via(const int index, Fs... fs) { - points.at(static_cast(index)).via(fs...); - return *this; - } - - - - - template - template - tween & tween::during(Ds... ds) { - total = 0; - points.at(points.size() - 2).during(ds...); - for (detail::tweenpoint & p : points) { - p.start = total; - total += p.duration(); - } - return *this; - } - - template - const T & tween::step(const int32_t dt, const bool suppressCallbacks) { - return step(static_cast(dt)/static_cast(total), suppressCallbacks); - } - - template - const T & tween::step(const uint32_t dt, const bool suppressCallbacks) { - return step(static_cast(dt), suppressCallbacks); - } - - template - const T & tween::step(float dp, const bool suppressCallbacks) { - dp *= currentDirection; - seek(currentProgress + dp, true); - if (!suppressCallbacks) dispatch(onStepCallbacks); - return current; - } - - template - const T & tween::seek(float p, const bool suppressCallbacks) { - p = detail::clip(p, 0.0f, 1.0f); - currentProgress = p; - render(p); - if (!suppressCallbacks) dispatch(onSeekCallbacks); - return current; - } - - template - const T & tween::seek(const int32_t d, const bool suppressCallbacks) { - return seek(static_cast(d) / static_cast(total), suppressCallbacks); - } - - template - const T & tween::seek(const uint32_t d, const bool suppressCallbacks) { - return seek(static_cast(d) / static_cast(total), suppressCallbacks); - } - - template - uint32_t tween::duration() const { - return total; - } - - template - void tween::interpolate(const float prog, unsigned point, T & value) const { - auto & p = points.at(point); - const auto pointDuration = static_cast(p.duration() - (p.start - prog * static_cast(total))); - float pointTotal = static_cast(pointDuration) / static_cast(p.duration()); - if (pointTotal > 1.0f) pointTotal = 1.0f; - auto easing = std::get<0>(p.easings); - value = easing(pointTotal, std::get<0>(p.values), std::get<0>(points.at(point+1).values)); - } - - template - void tween::render(const float p) { - currentPoint = pointAt(p); - interpolate(p, currentPoint, current); - } - - template - tween & tween::onStep(typename detail::tweentraits::callbackType callback) { - onStepCallbacks.push_back(callback); - return *this; - } - - template - tween & tween::onStep(typename detail::tweentraits::noValuesCallbackType callback) { - onStepCallbacks.push_back([callback](tween & tween, T) { return callback(tween); }); - return *this; - } - - template - tween & tween::onStep(typename detail::tweentraits::noTweenCallbackType callback) { - onStepCallbacks.push_back([callback](tween &, T v) { return callback(v); }); - return *this; - } - - template - tween & tween::onSeek(typename detail::tweentraits::callbackType callback) { - onSeekCallbacks.push_back(callback); - return *this; - } - - template - tween & tween::onSeek(typename detail::tweentraits::noValuesCallbackType callback) { - onSeekCallbacks.push_back([callback](tween & t, T) { return callback(t); }); - return *this; - } - - template - tween & tween::onSeek(typename detail::tweentraits::noTweenCallbackType callback) { - onSeekCallbacks.push_back([callback](tween &, T v) { return callback(v); }); - return *this; - } - - template - void tween::dispatch(std::vector & cbVector) { - std::vector dismissed; - for (size_t i = 0; i < cbVector.size(); ++i) { - auto && cb = cbVector[i]; - if (cb(*this, current)) dismissed.push_back(i); - } - - if (!dismissed.empty()) { - for (size_t i = 0; i < dismissed.size(); ++i) { - size_t index = dismissed[i]; - cbVector[index] = cbVector.at(cbVector.size() - 1 - i); - } - cbVector.resize(cbVector.size() - dismissed.size()); - } - } - - template - const T & tween::peek() const { - return current; - } - - - template - T tween::peek(const float progress) const { - T value; - interpolate(progress, pointAt(progress), value); - return value; - } - - template - T tween::peek(const uint32_t time) const { - T value; - const float progress = static_cast(time) / static_cast(total); - interpolate(progress, pointAt(progress), value); - return value; - } - - - template - float tween::progress() const { - return currentProgress; - } - - template - tween & tween::forward() { - currentDirection = 1; - return *this; - } - - template - tween & tween::backward() { - currentDirection = -1; - return *this; - } - - template - int tween::direction() const { - return currentDirection; - } - - template - const T & tween::jump(size_t point, bool suppressCallbacks) { - point = detail::clip(point, static_cast(0), points.size() -1); - return seek(points.at(point).start, suppressCallbacks); - } - - template uint16_t tween::point() const { - return currentPoint; - } - - template uint16_t tween::pointAt(float progress) const { - progress = detail::clip(progress, 0.0f, 1.0f); - auto t = static_cast(progress * total); - uint16_t point = 0; - while (t > points.at(point).start) point++; - if (point > 0 && t <= points.at(point - 1u).start) point--; - return point; - } -} -#endif //TWEENY_TWEENONE_TCC diff --git a/include/tweenpoint.h b/include/tweenpoint.h deleted file mode 100644 index a14d432..0000000 --- a/include/tweenpoint.h +++ /dev/null @@ -1,78 +0,0 @@ -/* - This file is part of the Tweeny library. - - Copyright (c) 2016-2021 Leonardo Guilherme Lucena de Freitas - Copyright (c) 2016 Guilherme R. Costa - - Permission is hereby granted, free of charge, to any person obtaining a copy of - this software and associated documentation files (the "Software"), to deal in - the Software without restriction, including without limitation the rights to - use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of - the Software, and to permit persons to whom the Software is furnished to do so, - subject to the following conditions: - - The above copyright notice and this permission notice shall be included in all - copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS - FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR - COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER - IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -*/ - -/* - * This file provides the declarations for a tween point utility class. A tweenpoint holds the tween values, - * easings and durations. - */ - - -#ifndef TWEENY_TWEENPOINT_H -#define TWEENY_TWEENPOINT_H - -#include "tweentraits.h" - - - namespace tweeny::detail { - /* - * The tweenpoint class aids in the management of a tweening point by the tween class. - * This class is private. - */ - template - struct tweenpoint { - typedef tweentraits traits; - - typename traits::valuesType values; - typename traits::durationsArrayType durations; - typename traits::easingCollection easings; - typename traits::callbackType onEnterCallbacks; - uint32_t start{}; - - /* Constructs a tweenpoint from a set of values, filling their durations and easings */ - explicit tweenpoint(Ts... vs); - - /* Set the duration for all the values at this point */ - template void during(D duration); - - /* Sets the duration for each value in this point */ - template void during(Ds... duration); - - /* Sets the easing functions of each value */ - template void via(Fs... fs); - - /* Sets the same easing function for all values */ - template void via(F f); - - /* Returns the highest duration value in the duration array */ - [[nodiscard]] uint16_t duration() const; - - /* Returns the tween duration of that specific value */ - [[nodiscard]] uint16_t duration(size_t valueIndex) const; - }; - } - - -#include "tweenpoint.tcc" - -#endif //TWEENY_TWEENPOINT_H diff --git a/include/tweenpoint.tcc b/include/tweenpoint.tcc deleted file mode 100644 index bdb588c..0000000 --- a/include/tweenpoint.tcc +++ /dev/null @@ -1,122 +0,0 @@ -/* - This file is part of the Tweeny library. - - Copyright (c) 2016-2021 Leonardo Guilherme Lucena de Freitas - Copyright (c) 2016 Guilherme R. Costa - - Permission is hereby granted, free of charge, to any person obtaining a copy of - this software and associated documentation files (the "Software"), to deal in - the Software without restriction, including without limitation the rights to - use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of - the Software, and to permit persons to whom the Software is furnished to do so, - subject to the following conditions: - - The above copyright notice and this permission notice shall be included in all - copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS - FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR - COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER - IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -*/ - -/* - * This file implements the tweenpoint class - */ - -#ifndef TWEENY_TWEENPOINT_TCC -#define TWEENY_TWEENPOINT_TCC - -#include -#include - -#include "tweenpoint.h" -#include "tweentraits.h" -#include "easing.h" -#include "easingresolve.h" -#include "int2type.h" - - -namespace tweeny::detail { - template < - typename TypeTupleT, - typename EasingCollectionT, - typename EasingT, size_t I - > void easingfill(EasingCollectionT & f, EasingT easing, int2type) { - easingresolve::impl(f, easing); - easingfill(f, easing, int2type{}); - } - - template < - typename TypeTupleT, - typename EasingCollectionT, - typename EasingT - > void easingfill(EasingCollectionT & f, EasingT easing, int2type<0>) { - easingresolve<0, TypeTupleT, EasingCollectionT, EasingT>::impl(f, easing); - } - - template - struct are_same; - - template - struct are_same - { - static const bool value = std::is_same_v && are_same::value; - }; - - template - struct are_same - { - static constexpr bool value = true; - }; - - - template - tweenpoint::tweenpoint(Ts... vs) : values{vs...} { - during(static_cast(0)); - via(easing::def); - } - - template - template - void tweenpoint::during(D duration) { - for (uint16_t & t : durations) { t = static_cast(duration); } - } - - template - template - void tweenpoint::during(Ds... duration) { - static_assert(sizeof...(Ds) == sizeof...(Ts), - "Amount of durations should be equal to the amount of values in a point"); - std::array list = {{ duration... }}; - std::copy(list.begin(), list.end(), durations.begin()); - } - - template - template - void tweenpoint::via(Fs... fs) { - static_assert(sizeof...(Fs) == sizeof...(Ts), - "Number of functions passed to via() must be equal the number of values."); - easingresolve<0, std::tuple, typename traits::easingCollection, Fs...>::impl(easings, fs...); - } - - template - template - void tweenpoint::via(F f) { - easingfill(easings, f, int2type{ }); - } - - template - uint16_t tweenpoint::duration() const { - return *std::max_element(durations.begin(), durations.end()); - } - - template - uint16_t tweenpoint::duration(size_t valueIndex) const { - return durations.at(valueIndex); - } -} - -#endif //TWEENY_TWEENPOINT_TCC diff --git a/include/tweentraits.h b/include/tweentraits.h deleted file mode 100644 index aac63ad..0000000 --- a/include/tweentraits.h +++ /dev/null @@ -1,76 +0,0 @@ -/* - This file is part of the Tweeny library. - - Copyright (c) 2016-2021 Leonardo Guilherme Lucena de Freitas - Copyright (c) 2016 Guilherme R. Costa - - Permission is hereby granted, free of charge, to any person obtaining a copy of - this software and associated documentation files (the "Software"), to deal in - the Software without restriction, including without limitation the rights to - use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of - the Software, and to permit persons to whom the Software is furnished to do so, - subject to the following conditions: - - The above copyright notice and this permission notice shall be included in all - copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS - FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR - COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER - IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -*/ - -/* - * This file provides useful typedefs and traits for a tween. - */ - -#ifndef TWEENY_TWEENTRAITS_H -#define TWEENY_TWEENTRAITS_H - -#include -#include -#include -#include - -namespace tweeny { - template class tween; - - namespace detail { - - template struct equal {}; - template struct equal { enum { value = true }; }; - template struct equal { - enum { value = std::is_same_v && equal::value && equal::value }; - }; - - template struct first { typedef T type; }; - - template - struct valuetype { }; - - template - struct valuetype { - typedef std::tuple type; - }; - - template - struct valuetype { - typedef std::array::type, sizeof...(Ts)> type; - }; - - template - struct tweentraits { - typedef std::tuple...> easingCollection; - typedef std::function &, Ts...)> callbackType; - typedef std::function &)> noValuesCallbackType; - typedef std::function noTweenCallbackType; - typedef typename valuetype::value, Ts...>::type valuesType; - typedef std::array durationsArrayType; - typedef tween type; - }; - } -} - -#endif //TWEENY_TWEENTRAITS_H diff --git a/include/tweeny.h b/include/tweeny.h deleted file mode 100644 index 770184a..0000000 --- a/include/tweeny.h +++ /dev/null @@ -1,102 +0,0 @@ -/* - This file is part of the Tweeny library. - - Copyright (c) 2016-2021 Leonardo Guilherme Lucena de Freitas - Copyright (c) 2016 Guilherme R. Costa - - Permission is hereby granted, free of charge, to any person obtaining a copy of - this software and associated documentation files (the "Software"), to deal in - the Software without restriction, including without limitation the rights to - use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of - the Software, and to permit persons to whom the Software is furnished to do so, - subject to the following conditions: - - The above copyright notice and this permission notice shall be included in all - copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS - FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR - COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER - IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -*/ - -/** - * @file tweeny.h - * This file is the main header file for Tweeny. You should not need to include anything else. - */ - -/** - * @mainpage Tweeny - * - * Tweeny is an inbetweening library designed for the creation of complex animations for games and other beautiful - * interactive software. It leverages features of modern C++ to empower developers with an intuitive API for - * declaring tweenings of any type of value, as long as they support arithmetic operations. - * - * This document contains Tweeny's API reference. The most interesting parts are: - * - * * The Fine @ref manual - * * The tweeny::from global function, to start a new tween. - * * The tweeny::tween class itself that has all the interesting methods for a tween. - * * The modules page has a list of type of easings. - * - * This is how the API looks like: - * - * @code - * - * #include "tweeny.h" - * - * using tweeny::easing; - * - * int main() { - * // steps 1% each iteration - * auto tween = tweeny::from(0).to(100).during(100).via(easing::linear); - * while (tween.progress() < 1.0f) tween.step(0.01f); - * - * // a tween with multiple values - * auto tween2 = tweeny::from(0, 1.0f).to(1200, 7.0f).during(1000).via(easing::backInOut, easing::linear); - * - * // a tween with multiple points, different easings and durations - * auto tween3 = tweeny::from(0, 0) - * .to(100, 100).during(100).via(easing::backOut, easing::backOut) - * .to(200, 200).during(500).via(easing::linear); - * return 0; - * } - * - * @endcode - * - * **Examples** - * - * * Check tweeny-demos repository to see demonstration code - * - * **Useful links and references** - * * Tim Groleau's easing function generator (requires flash) - * * Easing cheat sheet (contains graphics!) - */ - -#ifndef TWEENY_H -#define TWEENY_H - -#include "tween.h" -#include "easing.h" - -/** - * @brief The tweeny namespace contains all symbols and names for the Tweeny library. - */ -namespace tweeny { - /** - * @brief Creates a tween starting from the values defined in the arguments. - * - * Starting values can have heterogeneous types, even user-defined types, provided they implement the - * four arithmetic operators (+, -, * and /). The types used will also define the type of each next step, the type - * of the callback and the type of arguments the passed easing functions must have. - * - * @sa tweeny::tween - */ - template tween from(Ts... vs); -} - -#include "tweeny.tcc" - -#endif //TWEENY_H diff --git a/include/tweeny.tcc b/include/tweeny.tcc deleted file mode 100644 index e2defac..0000000 --- a/include/tweeny.tcc +++ /dev/null @@ -1,40 +0,0 @@ -/* - This file is part of the Tweeny library. - - Copyright (c) 2016-2021 Leonardo Guilherme Lucena de Freitas - Copyright (c) 2016 Guilherme R. Costa - - Permission is hereby granted, free of charge, to any person obtaining a copy of - this software and associated documentation files (the "Software"), to deal in - the Software without restriction, including without limitation the rights to - use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of - the Software, and to permit persons to whom the Software is furnished to do so, - subject to the following conditions: - - The above copyright notice and this permission notice shall be included in all - copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS - FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR - COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER - IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -*/ - -/* - * This file provides the implementation for tweeny.h - */ - -#ifndef TWEENY_TWEENY_TCC -#define TWEENY_TWEENY_TCC - -#include "tween.h" - -namespace tweeny { - template inline tween from(Ts... vs) { - return tween::from(vs...); - } -} - -#endif //TWEENY_TWEENY_TCC diff --git a/include/tweeny/detail/interpolate.h b/include/tweeny/detail/interpolate.h new file mode 100644 index 0000000..5ef1417 --- /dev/null +++ b/include/tweeny/detail/interpolate.h @@ -0,0 +1,60 @@ +/* +This file is part of the Tweeny library. + +Copyright (c) 2016-2025 Leonardo Guilherme Lucena de Freitas +Copyright (c) 2016 Guilherme R. Costa + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: +# +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ + +#ifndef TWEENY_INTERPOLATE_H +#define TWEENY_INTERPOLATE_H +#include + +#include "key-frame.h" +#include "easing/easing.h" + +namespace tweeny::detail { + template + static auto interpolate_one( + float t, + const key_frame & base, + const key_frame & next + ) -> decltype(std::get(base.values)) { + const auto & start = std::get(base.values); + const auto & end = std::get(next.values); + const auto & func = std::get(base.easing_functions); + if (func) return func(t, start, end); + return easing::def(t, start, end); + } + + template + static auto interpolate_values( + float t, + const key_frame & base, + const key_frame & next, + std::index_sequence + ) -> typename key_frame::values_t { + using values_t = typename key_frame::values_t; + values_t out{}; + ((std::get(out) = interpolate_one(t, base, next)), ...); + return out; + } +} + +#endif //TWEENY_INTERPOLATE_H diff --git a/include/tweeny/detail/key-frame.h b/include/tweeny/detail/key-frame.h new file mode 100644 index 0000000..c1cc1b5 --- /dev/null +++ b/include/tweeny/detail/key-frame.h @@ -0,0 +1,131 @@ +/* +This file is part of the Tweeny library. + +Copyright (c) 2016-2025 Leonardo Guilherme Lucena de Freitas +Copyright (c) 2016 Guilherme R. Costa + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: +# +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ + +#ifndef TWEENY_KEY_FRAME_H +#define TWEENY_KEY_FRAME_H + + +#include + +#include "value-container.h" + +#include +#include +#include + +namespace tweeny::detail { + template + /** + * @brief Represents a key frame in a tween. + * + * The key_frame class encapsulates the attributes and behaviors + * of a single frame in an animation sequence, allowing for storage + * and manipulation of key values at specific points. + * + * @warning This structure is private and shouldn't be used directly + */ + struct key_frame { + typedef value_container_t values_t; + typedef std::tuple...> value_easing_functions_t; + typedef std::array value_tween_frame_counts_t; + + /** + * @brief Constructs a key_frame object initialized with provided values. + * + * This constructor initializes the key_frame object by setting its position to 0, + * populating the values container with the provided values `vs...`, and initializing + * the easing functions and tween frame counts with default values. + * + * @param vs The values to initialize the key_frame, passed as a parameter pack of type `ValueTypes...`. + * These values are stored in a container, which can either be a tuple or an array depending + * on their types. + * @return A key_frame object initialized with the provided values. + */ + explicit key_frame(ValueTypes... vs) : position(0), values{vs...}, easing_functions(), tween_frame_counts() {} + + + /** + * Holds the 'position' of the key frame within the animation sequence. + * + * This variable represents the position of the current key frame as an unsigned 32-bit integer. + * It refers to the frame index at which this key frame is located + * within the timeline of a tween. + */ + uint32_t position; + + /** + * @typedef values_t + * @brief Represents a container to store values in `key_frame`. + * + * The `values_t` type alias is used to hold the data values associated with + * the `key_frame` structure. The container type is determined at compile-time + * based on the type consistency of the provided value types. If all value types + * are the same, the container is implemented as a `std::array`. Otherwise, it + * is implemented as a `std::tuple`. + */ + values_t values; + + /** + * @typedef value_easing_functions_t easing_functions + * @brief Represents a tuple of easing function objects for each value component in a key frame. + * + * This variable contains easing functions used to interpolate or transform + * individual value components over time in a tweening operation. + * Each easing function is specified as a callable object (e.g., a lambda or function) + * following the signature `(float progress, ValueType start, ValueType end) -> ValueType`. + * + * It is used within the context of the tween system to determine how intermediate + * values between this key frame and the next are computed based on the specified easing functions. + */ + value_easing_functions_t easing_functions; + + /** + * An array that represents the number of frames allocated for tweening + * each value in a key frame. The size of the array corresponds to the + * number of values being tweened, and each entry specifies the frame + * count for the respective value. + * + * This array is populated either by specifying explicit frame counts + * for each value during a tween's construction or by using a uniform + * frame count for all values. The highest count is used to calculate + * the position of key frames and manage the timing of individual + * value transitions within a tween animation. + */ + value_tween_frame_counts_t tween_frame_counts; + + /** + * @brief Retrieves the highest frame count from the tween_frame_counts array. + * + * This function searches through the tween_frame_counts array to find + * and return the maximum frame count value. + * + * @return The highest frame count as an unsigned 16-bit integer. + */ + [[nodiscard]] uint16_t highest_frame_count() const { + return *std::max_element(tween_frame_counts.begin(), tween_frame_counts.end()); + } + }; +} + +#endif //TWEENY_KEY_FRAME_H diff --git a/include/tweeny/detail/tuple-utilities.h b/include/tweeny/detail/tuple-utilities.h new file mode 100644 index 0000000..c1da2d6 --- /dev/null +++ b/include/tweeny/detail/tuple-utilities.h @@ -0,0 +1,61 @@ +/* +This file is part of the Tweeny library. + +Copyright (c) 2016-2025 Leonardo Guilherme Lucena de Freitas +Copyright (c) 2016 Guilherme R. Costa + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: +# +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ + +#ifndef TWEENY_TUPLE_UTILITIES_H +#define TWEENY_TUPLE_UTILITIES_H +#include +#include + +namespace tweeny::detail { + + /** + * Implements the creation of a tuple with repeated elements of the given value. + * + * @param v The value to be repeated in the tuple. + * @param sequence An index sequence used to generate the tuple with the required size. + * @return A tuple where each element is a copy of the input value. + * + * @warning This function is private and shouldn't be used directly + */ + template + static auto make_repeated_tuple_impl(const T & v, std::index_sequence sequence) { + (void) sequence; + return std::make_tuple(((void) I, v)...); + } + + /** + * Creates a tuple of size N where each element is a copy of the given value. + * + * @param v The value to be repeated in the tuple. + * @return A tuple of size N with each element being a copy of the input value. + * + * @warning This function is private and shouldn't be used directly + */ + template + static auto make_repeated_tuple(const T & v) { + return make_repeated_tuple_impl(v, std::make_index_sequence{}); + } +} + +#endif //TWEENY_TUPLE_UTILITIES_H diff --git a/include/tweeny/detail/tween-value.h b/include/tweeny/detail/tween-value.h new file mode 100644 index 0000000..adde589 --- /dev/null +++ b/include/tweeny/detail/tween-value.h @@ -0,0 +1,65 @@ +/* +This file is part of the Tweeny library. + +Copyright (c) 2016-2025 Leonardo Guilherme Lucena de Freitas +Copyright (c) 2016 Guilherme R. Costa + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: +# +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ + +#ifndef TWEENY_TWEEN_VALUE_H +#define TWEENY_TWEEN_VALUE_H + +#include +#include "value-container.h" + +namespace tweeny::detail { + + /** + * @struct tween_value + * @brief Metafunction that resolves to a type based on the provided template arguments. + * + * This struct determines the appropriate type depending on the number and types + * of its template parameters. If there is only one parameter (First), it resolves + * to that type. Otherwise, it resolves to a `value_container_t` type, which is + * constructed using the provided `First` and additional `Rest` types. + * + * @tparam First The first type parameter used for evaluation. + * @tparam Rest Additional type parameters provided for consideration. These are + * used to construct a `value_container_t` if more than one parameter is passed. + * + * @see value_container_t + * @see tween_value_t + * + * @warning This structure is private and shouldn't be used directly + */ + template + struct tween_value { + using type = std::conditional_t< + sizeof...(Rest) == 0, + First, + value_container_t + >; + }; + + template + using tween_value_t = typename tween_value::type; + +} + +#endif // TWEENY_TWEEN_VALUE_H diff --git a/include/tweeny/detail/value-container.h b/include/tweeny/detail/value-container.h new file mode 100644 index 0000000..6c59998 --- /dev/null +++ b/include/tweeny/detail/value-container.h @@ -0,0 +1,95 @@ +/* +This file is part of the Tweeny library. + +Copyright (c) 2016-2025 Leonardo Guilherme Lucena de Freitas +Copyright (c) 2016 Guilherme R. Costa + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: +# +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ + +#ifndef TWEENY_VALUE_CONTAINER_H +#define TWEENY_VALUE_CONTAINER_H + +#include +#include + +namespace tweeny::detail { + /** + * @struct all_same + * @brief A type trait that evaluates to `std::true_type`. + * + * This specialization of the `all_same` struct is used when no template parameters + * are provided. It serves as a base case for type pack evaluation and always evaluates + * to `true_type`, representing that an empty pack is trivially considered to have + * "all the same" types. + * + * This type trait is mainly used as a base case in conjunction with other + * template specializations that check for type consistency within a parameter pack. + * + * @warning This structure is private and shouldn't be used directly + */ + template + struct all_same : std::true_type {}; + + + /** + * @struct all_same + * @brief A type trait to check if all types in a parameter pack are the same. + * + * This struct inherits from `std::conjunction` with the result of comparing all + * types in the parameter pack `Rest...` using `std::is_same`, relative to the first + * type `First`. The resulting value will evaluate to `true_type` if all types are the + * same, otherwise to `false_type`. + * + * @tparam First The first type in the parameter pack to compare against. + * @tparam Rest The remaining types in the parameter pack. + * + * @warning This structure is private and shouldn't be used directly + */ + template + struct all_same : std::conjunction...> {}; + + /** + * @brief A compile-time boolean that is true if all types in the provided parameter pack are the same, false otherwise. + * + * Determines whether all types in the template parameter pack `Ts...` are the same type. This is evaluated at + * compile-time using the `all_same` trait. + * + * Example usage: + * @code + * static_assert(all_same_v == true); + * static_assert(all_same_v == false); + * @endcode + * + * @tparam Ts pack of types to be evaluated + * + * @warning This value is private and shouldn't be used directly + */ + template + inline constexpr bool all_same_v = all_same::value; + + template + using value_container_t = + std::conditional_t< + all_same_v, + std::array>, sizeof...(Ts)>, + std::tuple + >; +} + +#endif //TWEENY_VALUE_CONTAINER_H diff --git a/include/easing.h b/include/tweeny/easing/easing.h similarity index 87% rename from include/easing.h rename to include/tweeny/easing/easing.h index a72e5b9..1548bc9 100644 --- a/include/easing.h +++ b/include/tweeny/easing/easing.h @@ -140,6 +140,11 @@ namespace tweeny { (void) end; return start; } + + template + T operator()(float position, T start, T end) const { + return run(position, start, end); + } } stepped = steppedEasing{}; /** @@ -179,6 +184,11 @@ namespace tweeny { (void) end; return start; } + + template + T operator()(float position, T start, T end) const { + return run(position, start, end); + } } def = defaultEasing{}; /** @@ -195,6 +205,11 @@ namespace tweeny { static std::enable_if_t, T> run(float position, T start, T end) { return static_cast((end - start) * position + start); } + + template + T operator()(float position, T start, T end) const { + return run(position, start, end); + } } linear = linearEasing{}; /** @@ -206,6 +221,11 @@ namespace tweeny { static T run(float position, T start, T end) { return static_cast((end - start) * position * position + start); } + + template + T operator()(float position, T start, T end) const { + return run(position, start, end); + } } quadraticIn = quadraticInEasing{}; /** @@ -217,6 +237,11 @@ namespace tweeny { static T run(float position, T start, T end) { return static_cast((-(end - start)) * position * (position - 2) + start); } + + template + T operator()(float position, T start, T end) const { + return run(position, start, end); + } } quadraticOut = quadraticOutEasing{}; /** @@ -234,6 +259,11 @@ namespace tweeny { --position; return static_cast(-(end - start) / 2 * (position * (position - 2) - 1) + start); } + + template + T operator()(float position, T start, T end) const { + return run(position, start, end); + } } quadraticInOut = quadraticInOutEasing{}; /** @@ -245,6 +275,11 @@ namespace tweeny { static T run(float position, T start, T end) { return static_cast((end - start) * position * position * position + start); } + + template + T operator()(float position, T start, T end) const { + return run(position, start, end); + } } cubicIn = cubicInEasing{}; /** @@ -257,6 +292,11 @@ namespace tweeny { --position; return static_cast((end - start) * (position * position * position + 1) + start); } + + template + T operator()(float position, T start, T end) const { + return run(position, start, end); + } } cubicOut = cubicOutEasing{}; /** @@ -440,6 +480,11 @@ namespace tweeny { static T run(const float position, T start, T end) { return static_cast( -(end - start) * (sqrtf(1 - position * position) - 1) + start ); } + + template + T operator()(float position, T start, T end) const { + return run(position, start, end); + } } circularIn = circularInEasing{}; /** @@ -452,6 +497,11 @@ namespace tweeny { --position; return static_cast((end - start) * sqrtf(1 - position * position) + start); } + + template + T operator()(float position, T start, T end) const { + return run(position, start, end); + } } circularOut = circularOutEasing{}; /** @@ -469,6 +519,11 @@ namespace tweeny { position -= 2; return static_cast((end - start) / 2 * (sqrtf(1 - position * position) + 1) + start); } + + template + T operator()(float position, T start, T end) const { + return run(position, start, end); + } } circularInOut = circularInOutEasing{}; /** @@ -480,6 +535,11 @@ namespace tweeny { static T run(float position, T start, T end) { return end - start - bounceOut.run(1 - position, T(), (end - start)) + start; } + + template + T operator()(float position, T start, T end) const { + return run(position, start, end); + } } bounceIn = bounceInEasing{}; /** @@ -504,6 +564,11 @@ namespace tweeny { const float postFix = position -= (2.625f / 2.75f); return static_cast(c * (7.5625f * postFix * position + .984375f) + start); } + + template + T operator()(float position, T start, T end) const { + return run(position, start, end); + } } bounceOut = bounceOutEasing{}; /** @@ -516,6 +581,11 @@ namespace tweeny { if (position < 0.5f) return static_cast(bounceIn.run(position * 2, T(), end - start) * .5f + start); return static_cast(bounceOut.run(position * 2 - 1, T(), end - start) * .5f + (end - start) * .5f + start); } + + template + T operator()(float position, T start, T end) const { + return run(position, start, end); + } } bounceInOut = bounceInOutEasing{}; /** @@ -534,6 +604,11 @@ namespace tweeny { a * powf(2, 10 * (position -= 1)); // this is a fix, again, with post-increment operators return static_cast(-(postFix * sinf((position - s) * (2 * static_cast(M_PI)) / p)) + start); } + + template + T operator()(float position, T start, T end) const { + return run(position, start, end); + } } elasticIn = elasticInEasing{}; /** @@ -550,6 +625,11 @@ namespace tweeny { float s = p / 4; return static_cast(a * powf(2, -10 * position) * sinf((position - s) * (2 * static_cast(M_PI)) / p) + end); } + + template + T operator()(float position, T start, T end) const { + return run(position, start, end); + } } elasticOut = elasticOutEasing{}; /** @@ -574,6 +654,11 @@ namespace tweeny { postFix = a * powf(2, -10 * (position -= 1)); // postIncrement is evil return static_cast(postFix * sinf((position - s) * (2 * static_cast(M_PI)) / p) * .5f + end); } + + template + T operator()(float position, T start, T end) const { + return run(position, start, end); + } } elasticInOut = elasticInOutEasing{}; /** @@ -587,6 +672,11 @@ namespace tweeny { float postFix = position; return static_cast((end - start) * postFix * position * ((s + 1) * position - s) + start); } + + template + T operator()(float position, T start, T end) const { + return run(position, start, end); + } } backIn = backInEasing{}; /** @@ -600,6 +690,11 @@ namespace tweeny { position -= 1; return static_cast((end - start) * ((position) * position * ((s + 1) * position + s) + 1) + start); } + + template + T operator()(float position, T start, T end) const { + return run(position, start, end); + } } backOut = backOutEasing{}; /** @@ -619,6 +714,11 @@ namespace tweeny { const float postFix = t -= 2; return static_cast(c / 2 * (postFix * t * ((s + 1) * t + s) + 2) + b); } + + template + T operator()(float position, T start, T end) const { + return run(position, start, end); + } } backInOut = backInOutEasing{}; }; } diff --git a/include/tweeny/tween.h b/include/tweeny/tween.h new file mode 100644 index 0000000..1afd35b --- /dev/null +++ b/include/tweeny/tween.h @@ -0,0 +1,59 @@ +/* +This file is part of the Tweeny library. + +Copyright (c) 2016-2025 Leonardo Guilherme Lucena de Freitas +Copyright (c) 2016 Guilherme R. Costa + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: +# +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ + +#ifndef TWEENY_TWEEN_H +#define TWEENY_TWEEN_H +#include +#include + +#include "detail/key-frame.h" +#include "detail/tween-value.h" + +namespace tweeny { + template + class tween { + typedef detail::key_frame key_frame_t; + typedef std::vector key_frames_t; + + public: + using tween_value_t = detail::tween_value_t; + + explicit tween(const key_frames_t & key_frames); + explicit tween(key_frames_t & key_frames); + + auto step(int32_t frames) -> tween_value_t; + + private: + key_frames_t key_frames; + uint32_t current_frame = 0; + tween_value_t current_value; + + auto interpolate() -> tween_value_t; + auto find_key_frame_index(uint32_t frame) -> std::size_t; + }; +} + +#include "tween.tcc" + +#endif //TWEENY_TWEEN_H diff --git a/include/tweeny/tweeny.h b/include/tweeny/tweeny.h new file mode 100644 index 0000000..a2e6819 --- /dev/null +++ b/include/tweeny/tweeny.h @@ -0,0 +1,129 @@ +/* +This file is part of the Tweeny library. + +Copyright (c) 2016-2025 Leonardo Guilherme Lucena de Freitas +Copyright (c) 2016 Guilherme R. Costa + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: +# +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ + +#ifndef TWEENY_TWEENY_H +#define TWEENY_TWEENY_H + +#include "tween.h" +#include + +#include "detail/tuple-utilities.h" + +namespace tweeny { + template + class tweeny_builder { + typedef std::vector> key_frames_t; + typedef tween tween_t; + static size_t constexpr value_count = 1 + sizeof...(RemainingValues); + + public: + explicit tweeny_builder(FirstValue firstValue, RemainingValues... remainingValues) { + key_frames.emplace_back(firstValue, remainingValues...); + } + + tweeny_builder & to(const FirstValue & firstValue, const RemainingValues &... remainingValues) { + key_frames.emplace_back(firstValue, remainingValues...); + return *this; + } + + template tweeny_builder & via(EasingFunctionTypes... easing_functions) { + static_assert(sizeof...(EasingFunctionTypes) == value_count, + "via() must have one easing function per tween component"); + + /* HOLD. + * the intention here is to access the “previous” key frame + * after you’ve just pushed a new one with to(...), via(...) configures the easing + * for the segment that starts at the previous frame. + */ + auto & key_frame = key_frames.at(key_frames.size() - 2); + key_frame.easing_functions = std::make_tuple(easing_functions...); + return *this; + } + + template + tweeny_builder & via(EasingFunctionType easing_function) { + auto & key_frame = key_frames.at(key_frames.size() - 2); + key_frame.easing_functions = detail::make_repeated_tuple(easing_function); + return *this; + } + + template + tweeny_builder & during(FrameCountsType... frame_counts) { + static_assert(sizeof...(FrameCountsType) == value_count, + "during() must have one frame count per tween component"); + + static_assert((std::is_same_v, uint32_t> && ...), + "during() parameters must be of type uint32_t"); + + auto & key_frame = key_frames.at(key_frames.size() - 2); + std::array frame_counts_array = { frame_counts... }; + std::copy( + std::begin(frame_counts_array), + std::end(frame_counts_array), + std::begin(key_frame.tween_frame_counts)); + + fix_frame_positions(); + return *this; + } + + tweeny_builder & during(uint16_t frame_count) { + auto & key_frame = key_frames.at(key_frames.size() - 2); + std::fill( + std::begin(key_frame.tween_frame_counts), + std::end(key_frame.tween_frame_counts), + frame_count + ); + + fix_frame_positions(); + return *this; + } + + tween_t build() const & { + return tween(key_frames); + } + + tween_t build() && { + return tween(std::move(key_frames)); + } + + private: + key_frames_t key_frames; + void fix_frame_positions() { + uint16_t key_frame_position = 0; + for (auto & key_frame : key_frames) { + key_frame.position = key_frame_position; + key_frame_position += key_frame.highest_frame_count(); + } + } + }; + + template + tweeny_builder from(FirstValue first_value, RemainingValues... remaining_values) { + return tweeny_builder(first_value, remaining_values...); + } +} + +#include "easing/easing.h" + +#endif //TWEENY_TWEENY_H diff --git a/src/sandbox.cc b/src/sandbox.cc index 191abc3..bbdd8fc 100644 --- a/src/sandbox.cc +++ b/src/sandbox.cc @@ -1,6 +1,10 @@ #include "tweeny.h" int main() { - auto tween1 = tweeny::from(0.0, 1.0f).to(1.0f, 0.0f).via(tweeny::easing::stepped, tweeny::easing::linear); + auto builder = tweeny::from(0.0f).to(1.0f).via(tweeny::easing::linear).during(100U); + auto x = builder.build(); + + auto v = x.step(50); + return 0; -} \ No newline at end of file +} diff --git a/src/tweeny/tween.tcc b/src/tweeny/tween.tcc new file mode 100644 index 0000000..bcdbd89 --- /dev/null +++ b/src/tweeny/tween.tcc @@ -0,0 +1,137 @@ +/* +This file is part of the Tweeny library. + +Copyright (c) 2016-2025 Leonardo Guilherme Lucena de Freitas +Copyright (c) 2016 Guilherme R. Costa + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: +# +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ + +#ifndef TWEENY_TWEEN_TCC +#define TWEENY_TWEEN_TCC + +#include +#include +#include +#include + +#include "detail/interpolate.h" +#include "tweeny/easing/easing.h" + +namespace tweeny::detail { + static float clampf(const float v, const float min = 0.0f, const float max = 1.0f) { + if (v < min) return min; + if (v > max) return max; + return v; + } +} + +template +tweeny::tween::tween(const key_frames_t & key_frames) : key_frames(key_frames) { } + +template +tweeny::tween::tween(key_frames_t & key_frames) : key_frames(std::move(key_frames)) { } + +template +auto tweeny::tween::find_key_frame_index(uint32_t frame) -> size_t { + std::size_t i = 0; + while (i + 1 < key_frames.size() && frame >= key_frames[i + 1].position) { ++i; } + return i; +} + + +template +auto tweeny::tween::step(const int32_t frames) -> tween_value_t { + if (frames < 0) { + const auto dec = static_cast(-frames); + if (dec > current_frame) current_frame = 0; + else current_frame -= dec; + } else { + current_frame += static_cast(frames); + } + + current_value = interpolate(); + return current_value; +} + +template +auto tweeny::tween::interpolate() -> tween_value_t { + constexpr std::size_t ValuesCount = sizeof...(RemainingValueTypes) + 1; + + if (key_frames.empty()) { + if constexpr (ValuesCount == 1) { + return FirstValueType{}; + } else { + return typename key_frame_t::values_t{}; + } + } + + // If before or at the first key frame, return its value + auto & first_key_frame = key_frames.front(); + if (current_frame <= first_key_frame.position) { + const auto & v = first_key_frame.values; + if constexpr (ValuesCount == 1) { + return std::get<0>(v); + } else { + return v; + } + } + + std::size_t keyframe_index = find_key_frame_index(current_frame); + + // If next at or past the last key frame, return its value + auto & last_key_frame = key_frames.back(); + if (keyframe_index + 1 >= key_frames.size()) { + const auto & v = last_key_frame.values; + if constexpr (ValuesCount == 1) { + return std::get<0>(v); + } else { + return v; + } + } + + const key_frame_t & base_key_frame = key_frames[keyframe_index]; + const key_frame_t & next_key_frame = key_frames[keyframe_index + 1]; + + const int64_t base_pos = base_key_frame.position; + const uint32_t next_pos = next_key_frame.position; + const int64_t current_frame_i64 = current_frame; + + float tweening_progress = 1.0f; + if (next_pos > base_pos) { + const float numerator = current_frame_i64 - base_pos; + const float denominator = next_pos - base_pos; + tweening_progress = numerator / denominator; + } + tweening_progress = detail::clampf(tweening_progress); + + auto values = detail::interpolate_values( + tweening_progress, + base_key_frame, + next_key_frame, + std::make_index_sequence{} + ); + + if constexpr (ValuesCount == 1) { + return std::get<0>(values); + } else { + return values; + } +} + +#endif //TWEENY_TWEEN_TCC diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 8aef591..97419d3 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -3,7 +3,6 @@ find_package(Catch2 3 REQUIRED CONFIG) add_executable(tweeny_tests sanity.cpp - tween_tests.cpp ) target_compile_features(tweeny_tests PRIVATE cxx_std_17) diff --git a/tests/sanity.cpp b/tests/sanity.cpp index edf89ff..ff99846 100644 --- a/tests/sanity.cpp +++ b/tests/sanity.cpp @@ -4,6 +4,3 @@ TEST_CASE("sanity - the test framework runs", "[sanity]") { REQUIRE(1 + 1 == 2); } -TEST_CASE("sanity") { - REQUIRE(1 + 1 == 2); -} diff --git a/tests/tween_tests.cpp b/tests/tween_tests.cpp deleted file mode 100644 index 7decbd9..0000000 --- a/tests/tween_tests.cpp +++ /dev/null @@ -1,284 +0,0 @@ -#include -#include "tweeny.h" - -namespace { - constexpr float eps = 1e-4f; - bool approxf(const float a, const float b) { return std::fabs(a - b) <= eps; } -} - -TEST_CASE("tween - step by duration (time-based)", "[tween][step][duration]") { - auto tw = tweeny::from(0.0f).to(100.0f).during(100).via(tweeny::easing::linear); - REQUIRE(tw.duration() == 100); - REQUIRE(approxf(tw.progress(), 0.0f)); - - SECTION("25% progress at t=25") { - tw.step(25u); - REQUIRE(approxf(tw.progress(), 0.25f)); - REQUIRE(approxf(tw.peek(), 25.0f)); - } - SECTION("halfway at t=50") { - tw.step(50u); - REQUIRE(approxf(tw.progress(), 0.5f)); - REQUIRE(approxf(tw.peek(), 50.0f)); - } - SECTION("full at t=100 clamps to 1.0") { - tw.step(100u); - REQUIRE(approxf(tw.progress(), 1.0f)); - REQUIRE(approxf(tw.peek(), 100.0f)); - } -} - -TEST_CASE("tween - step by progress (fractional)", "[tween][step][progress]") { - auto tw = tweeny::from(0.0f).to(100.0f).during(100).via(tweeny::easing::linear); - REQUIRE(approxf(tw.progress(), 0.0f)); - - SECTION("step by 0.25") { - tw.step(0.25f); - REQUIRE(approxf(tw.progress(), 0.25f)); - REQUIRE(approxf(tw.peek(), 25.0f)); - } - SECTION("two steps of 0.5 saturate at 1.0") { - tw.step(0.5f); - tw.step(0.75f); // this should saturate to 1.0 overall - REQUIRE(approxf(tw.progress(), 1.0f)); - REQUIRE(approxf(tw.peek(), 100.0f)); - } -} - -TEST_CASE("tween - seek to absolute progress and time", "[tween][seek]") { - auto tw = tweeny::from(0.0f).to(100.0f).during(200).via(tweeny::easing::linear); - - SECTION("seek by progress") { - tw.seek(0.5f); - REQUIRE(approxf(tw.progress(), 0.5f)); - REQUIRE(approxf(tw.peek(), 50.0f)); - } - - SECTION("seek by time") { - tw.seek(50u); - REQUIRE(approxf(tw.progress(), 0.25f)); - REQUIRE(approxf(tw.peek(), 25.0f)); - tw.seek(200u); - REQUIRE(approxf(tw.progress(), 1.0f)); - REQUIRE(approxf(tw.peek(), 100.0f)); - } -} - -TEST_CASE("tween - multiple points: jump and point indexing", "[tween][jump][points]") { - auto tw = tweeny::from(0.0f) - .to(50.0f).during(100).via(tweeny::easing::linear) - .to(100.0f).during(100).via(tweeny::easing::linear); - - REQUIRE(tw.point() == 0); - - SECTION("jump to point 1 goes to second segment start") { - const auto & v = tw.jump(1); - REQUIRE(tw.point() == 1); - REQUIRE(approxf(v, 50.0f)); - REQUIRE(approxf(tw.progress(), 0.5f)); - } - - SECTION("jump to point 1 then step progresses within that segment") { - tw.jump(1); - tw.step(50u); // half of 100 - REQUIRE(tw.point() == 1); - REQUIRE(approxf(tw.progress(), 0.75f)); - REQUIRE(approxf(tw.peek(), 75.0f)); - } -} - -TEST_CASE("tween - callbacks onStep/onSeek", "[tween][callbacks]") { - auto tw = tweeny::from(0.0f).to(100.0f).during(100).via(tweeny::easing::linear); - - int stepCalls = 0; - int seekCalls = 0; - float lastValue = -1.0f; - - tw.onStep([&](auto & /*t*/, float v) { - ++stepCalls; - lastValue = v; - return true; - }); - - tw.onSeek([&](auto & /*t*/, float v) { - ++seekCalls; - lastValue = v; - return true; - }); - - SECTION("onStep triggers on time step") { - tw.step(25u); - REQUIRE(stepCalls >= 1); - REQUIRE(approxf(lastValue, 25.0f)); - } - - SECTION("onSeek triggers on seek") { - tw.seek(0.5f); - REQUIRE(seekCalls >= 1); - REQUIRE(approxf(lastValue, 50.0f)); - } - - SECTION("suppressed callbacks do not fire") { - stepCalls = seekCalls = 0; - tw.seek(0.25f, true); - tw.step(25u, true); - REQUIRE(stepCalls == 0); - REQUIRE(seekCalls == 0); - } -} - -TEST_CASE("tween - heterogeneous values (linear, equal durations)", "[tween][heterogeneous][linear]") { - // x, y, scale, alpha - auto tw = tweeny::from(0.0f, 400.0f, 0.5f, 0.0f) - .to(100.0f, 100.0f, 1.0f, 1.0f) - .during(300, 300, 300, 300) - .via(tweeny::easing::linear); - - // Halfway through time should be halfway in values for all components - auto [x, y, s, a] = tw.step(150u); - REQUIRE(approxf(x, 50.0f)); - REQUIRE(approxf(y, 250.0f)); - REQUIRE(approxf(s, 0.75f)); - REQUIRE(approxf(a, 0.5f)); - - // peek() should match current and not change state - const auto & cur = tw.peek(); - REQUIRE(approxf(std::get<0>(cur), x)); - REQUIRE(approxf(std::get<1>(cur), y)); - REQUIRE(approxf(std::get<2>(cur), s)); - REQUIRE(approxf(std::get<3>(cur), a)); - - // Absolute peek by time (end) - auto endVals = tw.peek(300u); - REQUIRE(approxf(std::get<0>(endVals), 100.0f)); - REQUIRE(approxf(std::get<1>(endVals), 100.0f)); - REQUIRE(approxf(std::get<2>(endVals), 1.0f)); - REQUIRE(approxf(std::get<3>(endVals), 1.0f)); -} - -TEST_CASE("tween - mixed types (float, int) with linear easing and rounding", "[tween][heterogeneous][int]") { - auto tw = tweeny::from(0.0f, 0) - .to(10.0f, 10) - .during(100) - .via(tweeny::easing::linear); - - auto [f, i] = tw.step(50u); - REQUIRE(approxf(f, 5.0f)); - REQUIRE(i == 5); // integral branch rounds -} - -TEST_CASE("tween - per-component durations saturate independently", "[tween][heterogeneous][durations]") { - // First component finishes 4x faster than the second - auto tw = tweeny::from(0.0f, 0.0f) - .to(10.0f, 10.0f) - .during(50, 200) - .via(tweeny::easing::linear); - - // At t=50: first should be complete; second should be at 0.25 - auto [a, b] = tw.seek(50u); - REQUIRE(approxf(a, 10.0f)); - REQUIRE(approxf(b, 2.5f)); - - // At t=200: both complete - auto [a2, b2] = tw.seek(200u); - REQUIRE(approxf(a2, 10.0f)); - REQUIRE(approxf(b2, 10.0f)); -} - -TEST_CASE("tween - via(index) overrides easing for a specific segment (stepped)", "[tween][via-index][stepped]") { - // Build two segments; leave first as default (linear for float), set second as stepped - auto tw = tweeny::from(0.0f) - .to(100.0f).during(100) - .to(200.0f).during(100); - tw.via(1, tweeny::easing::stepped); - - // Middle of first segment: linear 0 -> 100 - REQUIRE(approxf(tw.seek(50u), 50.0f)); - - // Middle of second segment (t=150): stepped keeps start value (100) - REQUIRE(approxf(tw.seek(150u), 100.0f)); - - // End clamps to final - REQUIRE(approxf(tw.seek(200u), 200.0f)); -} - -TEST_CASE("tween - direction forward/backward and seek unaffected", "[tween][direction]") { - auto tw = tweeny::from(0.0f).to(100.0f).during(100).via(tweeny::easing::linear); - - tw.forward(); - tw.step(25u); - REQUIRE(approxf(tw.progress(), 0.25f)); - REQUIRE(approxf(tw.peek(), 25.0f)); - - tw.backward(); - tw.step(25u); - REQUIRE(approxf(tw.progress(), 0.0f)); - REQUIRE(approxf(tw.peek(), 0.0f)); - - // Seek should ignore direction and set absolute time/progress - tw.backward(); - tw.seek(50u); - REQUIRE(approxf(tw.progress(), 0.5f)); - REQUIRE(approxf(tw.peek(), 50.0f)); -} - -TEST_CASE("tween - peek(progress/time) does not mutate state", "[tween][peek]") { - auto tw = tweeny::from(0.0f).to(100.0f).during(100).via(tweeny::easing::linear); - REQUIRE(approxf(tw.progress(), 0.0f)); - - auto p0 = tw.progress(); - auto pv = tw.peek(0.5f); - (void)pv; - REQUIRE(approxf(tw.progress(), p0)); - - auto pv2 = tw.peek(50u); - (void)pv2; - REQUIRE(approxf(tw.progress(), p0)); -} - -TEST_CASE("tween - multi-point timeline scrubbing and point indexing (linear)", "[tween][timeline][seek][point]") { - auto tl = tweeny::from(0.0f) - .to(100.0f).during(200).via(tweeny::easing::linear) // segment 0 - .to(200.0f).during(400).via(tweeny::easing::linear) // segment 1 - .to(300.0f).during(400).via(tweeny::easing::linear); // segment 2 - - // t=100: middle of segment 0 - REQUIRE(approxf(tl.seek(100u), 50.0f)); - REQUIRE(tl.point() == 0); - - // t=300: 100 into seg0 + 200 into seg1 (half of seg1) - REQUIRE(approxf(tl.seek(300u), 150.0f)); - REQUIRE(tl.point() == 1); - - // t=900: 1000 total; 300 into seg2 (half of seg2) - REQUIRE(approxf(tl.seek(900u), 250.0f)); - REQUIRE(tl.point() == 2); -} - -TEST_CASE("tween - callbacks one-shot removal semantics", "[tween][callbacks][removal]") { - SECTION("onStep one-shot") { - auto tw = tweeny::from(0).to(100).during(100).via(tweeny::easing::linear); - int calls = 0; - tw.onStep([&](auto &, int) { - ++calls; - return true; // remove after first call - }); - - tw.step(10u); - tw.step(10u); - REQUIRE(calls == 1); - } - - SECTION("onSeek one-shot") { - auto tw = tweeny::from(0.0f).to(1.0f).during(100).via(tweeny::easing::linear); - int calls = 0; - tw.onSeek([&](auto &, float) { - ++calls; - return true; // remove after first call - }); - - tw.seek(0.25f); - tw.seek(0.5f); - REQUIRE(calls == 1); - } -} From 1d1e9c568d811343f3069c16b221d23bbc82836b Mon Sep 17 00:00:00 2001 From: Leonardo Date: Tue, 19 Aug 2025 21:06:39 -0300 Subject: [PATCH 18/58] enhance precision, modernize methods, and enforce strict warnings --- CMakeLists.txt | 7 +++++++ include/tweeny/detail/interpolate.h | 17 ++++++++++++++++- include/tweeny/detail/key-frame.h | 4 ++-- include/tweeny/tween.h | 5 +++-- include/tweeny/tweeny.h | 5 +++-- src/sandbox.cc | 8 ++++++-- src/tweeny/tween.tcc | 8 ++++---- 7 files changed, 41 insertions(+), 13 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index cc0b6a5..37cd609 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -97,4 +97,11 @@ endif () if (TWEENY_BUILD_SANDBOX) add_executable(sandbox src/sandbox.cc) target_link_libraries(sandbox tweeny) + + # Enable very strict warnings and treat warnings as errors for the sandbox target + target_compile_options(sandbox PRIVATE + $<$:-Wall -Wextra -Wpedantic -Wconversion -Wsign-conversion -Wshadow -Wold-style-cast -Wcast-align -Woverloaded-virtual -Wnull-dereference -Wdouble-promotion -Wformat=2 -Wimplicit-fallthrough -Wswitch-enum -Wzero-as-null-pointer-constant -Wuseless-cast -Werror> + $<$:-Wall -Wextra -Wpedantic -Wconversion -Wsign-conversion -Wshadow -Wold-style-cast -Wcast-align -Woverloaded-virtual -Wnull-dereference -Wformat=2 -Wimplicit-fallthrough -Wswitch-enum -Wzero-as-null-pointer-constant -Wuseless-cast -Werror> + $<$:/W4 /WX /permissive-> + ) endif () diff --git a/include/tweeny/detail/interpolate.h b/include/tweeny/detail/interpolate.h index 5ef1417..dc4a981 100644 --- a/include/tweeny/detail/interpolate.h +++ b/include/tweeny/detail/interpolate.h @@ -25,6 +25,8 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. #ifndef TWEENY_INTERPOLATE_H #define TWEENY_INTERPOLATE_H #include +#include +#include #include "key-frame.h" #include "easing/easing.h" @@ -35,7 +37,20 @@ namespace tweeny::detail { float t, const key_frame & base, const key_frame & next - ) -> decltype(std::get(base.values)) { + ) -> std::remove_reference_t(base.values))> { + const auto & start = std::get(base.values); + const auto & end = std::get(next.values); + const auto & func = std::get(base.easing_functions); + if (func) return func(t, start, end); + return easing::def(t, start, end); + } + + template + static auto interpolate_one( + float t, + const key_frame & base, + const key_frame & next + ) -> std::remove_reference_t(base.values))> { const auto & start = std::get(base.values); const auto & end = std::get(next.values); const auto & func = std::get(base.easing_functions); diff --git a/include/tweeny/detail/key-frame.h b/include/tweeny/detail/key-frame.h index c1cc1b5..c45aba9 100644 --- a/include/tweeny/detail/key-frame.h +++ b/include/tweeny/detail/key-frame.h @@ -120,9 +120,9 @@ namespace tweeny::detail { * This function searches through the tween_frame_counts array to find * and return the maximum frame count value. * - * @return The highest frame count as an unsigned 16-bit integer. + * @return The highest frame count as an unsigned 32-bit integer. */ - [[nodiscard]] uint16_t highest_frame_count() const { + [[nodiscard]] uint32_t highest_frame_count() const { return *std::max_element(tween_frame_counts.begin(), tween_frame_counts.end()); } }; diff --git a/include/tweeny/tween.h b/include/tweeny/tween.h index 1afd35b..e1cdcf1 100644 --- a/include/tweeny/tween.h +++ b/include/tweeny/tween.h @@ -26,6 +26,7 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. #define TWEENY_TWEEN_H #include #include +#include #include "detail/key-frame.h" #include "detail/tween-value.h" @@ -39,8 +40,8 @@ namespace tweeny { public: using tween_value_t = detail::tween_value_t; - explicit tween(const key_frames_t & key_frames); - explicit tween(key_frames_t & key_frames); + explicit tween(const key_frames_t & key_frames_input); + explicit tween(key_frames_t & key_frames_input); auto step(int32_t frames) -> tween_value_t; diff --git a/include/tweeny/tweeny.h b/include/tweeny/tweeny.h index a2e6819..1561824 100644 --- a/include/tweeny/tweeny.h +++ b/include/tweeny/tweeny.h @@ -55,6 +55,7 @@ namespace tweeny { * the intention here is to access the “previous” key frame * after you’ve just pushed a new one with to(...), via(...) configures the easing * for the segment that starts at the previous frame. + * That is why you see -2 */ auto & key_frame = key_frames.at(key_frames.size() - 2); key_frame.easing_functions = std::make_tuple(easing_functions...); @@ -87,7 +88,7 @@ namespace tweeny { return *this; } - tweeny_builder & during(uint16_t frame_count) { + tweeny_builder & during(uint32_t frame_count) { auto & key_frame = key_frames.at(key_frames.size() - 2); std::fill( std::begin(key_frame.tween_frame_counts), @@ -110,7 +111,7 @@ namespace tweeny { private: key_frames_t key_frames; void fix_frame_positions() { - uint16_t key_frame_position = 0; + uint32_t key_frame_position = 0; for (auto & key_frame : key_frames) { key_frame.position = key_frame_position; key_frame_position += key_frame.highest_frame_count(); diff --git a/src/sandbox.cc b/src/sandbox.cc index bbdd8fc..967ad61 100644 --- a/src/sandbox.cc +++ b/src/sandbox.cc @@ -1,10 +1,14 @@ +#include + #include "tweeny.h" int main() { - auto builder = tweeny::from(0.0f).to(1.0f).via(tweeny::easing::linear).during(100U); + const auto builder = tweeny::from(0.0f).to(35.0f).via(tweeny::easing::linear).during(100U); auto x = builder.build(); - auto v = x.step(50); + const auto v = x.step(1); + + printf("%f\n", static_cast(v)); return 0; } diff --git a/src/tweeny/tween.tcc b/src/tweeny/tween.tcc index bcdbd89..2f4d728 100644 --- a/src/tweeny/tween.tcc +++ b/src/tweeny/tween.tcc @@ -42,10 +42,10 @@ namespace tweeny::detail { } template -tweeny::tween::tween(const key_frames_t & key_frames) : key_frames(key_frames) { } +tweeny::tween::tween(const key_frames_t & key_frames_input) : key_frames(key_frames_input) { } template -tweeny::tween::tween(key_frames_t & key_frames) : key_frames(std::move(key_frames)) { } +tweeny::tween::tween(key_frames_t & key_frames_input) : key_frames(std::move(key_frames_input)) { } template auto tweeny::tween::find_key_frame_index(uint32_t frame) -> size_t { @@ -114,8 +114,8 @@ auto tweeny::tween::interpolate() -> twe float tweening_progress = 1.0f; if (next_pos > base_pos) { - const float numerator = current_frame_i64 - base_pos; - const float denominator = next_pos - base_pos; + const auto numerator = static_cast(current_frame_i64 - base_pos); + const auto denominator = static_cast(next_pos - base_pos); tweening_progress = numerator / denominator; } tweening_progress = detail::clampf(tweening_progress); From 268d2061ab7d7b70a4e0f95fa39c37d26522e0df Mon Sep 17 00:00:00 2001 From: Leonardo Date: Tue, 19 Aug 2025 22:19:39 -0300 Subject: [PATCH 19/58] use lambda for `return_value` logic in `interpolate` --- src/tweeny/tween.tcc | 26 +++++++++++--------------- 1 file changed, 11 insertions(+), 15 deletions(-) diff --git a/src/tweeny/tween.tcc b/src/tweeny/tween.tcc index 2f4d728..3781099 100644 --- a/src/tweeny/tween.tcc +++ b/src/tweeny/tween.tcc @@ -73,6 +73,14 @@ template auto tweeny::tween::interpolate() -> tween_value_t { constexpr std::size_t ValuesCount = sizeof...(RemainingValueTypes) + 1; + const auto as_return_value = [](const auto& val) -> tween_value_t { + if constexpr (ValuesCount == 1) { + return std::get<0>(val); + } else { + return val; + } + }; + if (key_frames.empty()) { if constexpr (ValuesCount == 1) { return FirstValueType{}; @@ -85,11 +93,7 @@ auto tweeny::tween::interpolate() -> twe auto & first_key_frame = key_frames.front(); if (current_frame <= first_key_frame.position) { const auto & v = first_key_frame.values; - if constexpr (ValuesCount == 1) { - return std::get<0>(v); - } else { - return v; - } + return as_return_value(v); } std::size_t keyframe_index = find_key_frame_index(current_frame); @@ -98,11 +102,7 @@ auto tweeny::tween::interpolate() -> twe auto & last_key_frame = key_frames.back(); if (keyframe_index + 1 >= key_frames.size()) { const auto & v = last_key_frame.values; - if constexpr (ValuesCount == 1) { - return std::get<0>(v); - } else { - return v; - } + return as_return_value(v); } const key_frame_t & base_key_frame = key_frames[keyframe_index]; @@ -127,11 +127,7 @@ auto tweeny::tween::interpolate() -> twe std::make_index_sequence{} ); - if constexpr (ValuesCount == 1) { - return std::get<0>(values); - } else { - return values; - } + return as_return_value(values); } #endif //TWEENY_TWEEN_TCC From 1943eaf116c95dbbeacce152495d514d90dd5e6a Mon Sep 17 00:00:00 2001 From: Leonardo Date: Tue, 19 Aug 2025 22:32:58 -0300 Subject: [PATCH 20/58] add `seek` and `jump` methods to `tween` --- include/tweeny/tween.h | 2 ++ src/tweeny/tween.tcc | 30 ++++++++++++++++++++++++------ 2 files changed, 26 insertions(+), 6 deletions(-) diff --git a/include/tweeny/tween.h b/include/tweeny/tween.h index e1cdcf1..21a6b42 100644 --- a/include/tweeny/tween.h +++ b/include/tweeny/tween.h @@ -43,6 +43,8 @@ namespace tweeny { explicit tween(const key_frames_t & key_frames_input); explicit tween(key_frames_t & key_frames_input); + auto seek(uint32_t frame) -> tween_value_t; + auto jump(std::size_t key_frame) -> tween_value_t; auto step(int32_t frames) -> tween_value_t; private: diff --git a/src/tweeny/tween.tcc b/src/tweeny/tween.tcc index 3781099..3f22536 100644 --- a/src/tweeny/tween.tcc +++ b/src/tweeny/tween.tcc @@ -55,18 +55,36 @@ auto tweeny::tween::find_key_frame_index } +template +auto tweeny::tween::seek(uint32_t frame) -> tween_value_t { + current_frame = frame; + current_value = interpolate(); + return current_value; +} + +template +auto tweeny::tween::jump(std::size_t key_frame) -> tween_value_t { + if (key_frames.empty()) { + return seek(0); + } + if (key_frame >= key_frames.size()) { + key_frame = key_frames.size() - 1; + } + const auto frame = static_cast(key_frames[key_frame].position); + return seek(frame); +} + template auto tweeny::tween::step(const int32_t frames) -> tween_value_t { + uint32_t target_frame = current_frame; if (frames < 0) { const auto dec = static_cast(-frames); - if (dec > current_frame) current_frame = 0; - else current_frame -= dec; + if (dec > target_frame) target_frame = 0; + else target_frame -= dec; } else { - current_frame += static_cast(frames); + target_frame += static_cast(frames); } - - current_value = interpolate(); - return current_value; + return seek(target_frame); } template From 866d257b03e698be886e0da435ca412d3bb8f127 Mon Sep 17 00:00:00 2001 From: Leonardo Date: Tue, 19 Aug 2025 23:23:04 -0300 Subject: [PATCH 21/58] add multiple easing functions as individual headers --- CMakeLists.txt | 5 +- include/tweeny/easing/back.h | 84 ++++ include/tweeny/easing/bounce.h | 87 ++++ include/tweeny/easing/circular.h | 81 ++++ include/tweeny/easing/cubic.h | 78 +++ include/tweeny/easing/def.h | 79 +++ include/tweeny/easing/easing.h | 712 +--------------------------- include/tweeny/easing/elastic.h | 104 ++++ include/tweeny/easing/exponential.h | 79 +++ include/tweeny/easing/linear.h | 54 +++ include/tweeny/easing/quadratic.h | 77 +++ include/tweeny/easing/quartic.h | 78 +++ include/tweeny/easing/quintic.h | 78 +++ include/tweeny/easing/sinusoidal.h | 78 +++ include/tweeny/easing/stepped.h | 46 ++ {src => include}/tweeny/tween.tcc | 0 16 files changed, 1022 insertions(+), 698 deletions(-) create mode 100644 include/tweeny/easing/back.h create mode 100644 include/tweeny/easing/bounce.h create mode 100644 include/tweeny/easing/circular.h create mode 100644 include/tweeny/easing/cubic.h create mode 100644 include/tweeny/easing/def.h create mode 100644 include/tweeny/easing/elastic.h create mode 100644 include/tweeny/easing/exponential.h create mode 100644 include/tweeny/easing/linear.h create mode 100644 include/tweeny/easing/quadratic.h create mode 100644 include/tweeny/easing/quartic.h create mode 100644 include/tweeny/easing/quintic.h create mode 100644 include/tweeny/easing/sinusoidal.h create mode 100644 include/tweeny/easing/stepped.h rename {src => include}/tweeny/tween.tcc (100%) diff --git a/CMakeLists.txt b/CMakeLists.txt index 37cd609..a06f2c4 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -52,9 +52,7 @@ add_library(tweeny::tweeny ALIAS tweeny) # Set up include directories target_include_directories(tweeny INTERFACE $ - $ $ - $ ) # Attach headers to the interface target for IDEs and installation @@ -62,11 +60,10 @@ target_sources(tweeny INTERFACE FILE_SET HEADERS BASE_DIRS ${CMAKE_CURRENT_SOURCE_DIR}/include/tweeny - ${CMAKE_CURRENT_SOURCE_DIR}/src/tweeny FILES include/tweeny/tweeny.h include/tweeny/tween.h - src/tweeny/tween.tcc + include/tweeny/tween.tcc include/tweeny/detail/key-frame.h include/tweeny/detail/traits.h include/tweeny/detail/value-container.h diff --git a/include/tweeny/easing/back.h b/include/tweeny/easing/back.h new file mode 100644 index 0000000..539be58 --- /dev/null +++ b/include/tweeny/easing/back.h @@ -0,0 +1,84 @@ +/* +This file is part of the Tweeny library. + +Copyright (c) 2016-2025 Leonardo Guilherme Lucena de Freitas +Copyright (c) 2016 Guilherme R. Costa + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: +# +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ + +#ifndef TWEENY_EASING_BACK_H +#define TWEENY_EASING_BACK_H + +namespace tweeny::detail { + struct backInEasing { + template + static T run(float position, T start, T end) { + constexpr float s = 1.70158f; + float postFix = position; + return static_cast((end - start) * postFix * position * ((s + 1) * position - s) + start); + } + + template + T operator()(const float position, T start, T end) const { + return run(position, start, end); + } + }; + + struct backOutEasing { + template + static T run(float position, T start, T end) { + constexpr float s = 1.70158f; + position -= 1; + return static_cast((end - start) * ((position) * position * ((s + 1) * position + s) + 1) + start); + } + + template + T operator()(float position, T start, T end) const { + return run(position, start, end); + } + }; + + struct backInOutEasing { + template + static T run(float position, T start, T end) { + float s = 1.70158f; + float t = position; + auto b = start; + auto c = end - start; + constexpr float d = 1; + s *= 1.525f; + if ((t /= d / 2) < 1) return static_cast(c / 2 * (t * t * ((s + 1) * t - s)) + b); + const float postFix = t -= 2; + return static_cast(c / 2 * (postFix * t * ((s + 1) * t + s) + 2) + b); + } + + template + T operator()(const float position, T start, T end) const { + return run(position, start, end); + } + }; +} + +namespace tweeny::easing { + inline constexpr detail::backInEasing backIn{}; + inline constexpr detail::backOutEasing backOut{}; + inline constexpr detail::backInOutEasing backInOut{}; +} + +#endif // TWEENY_EASING_BACK_H diff --git a/include/tweeny/easing/bounce.h b/include/tweeny/easing/bounce.h new file mode 100644 index 0000000..fc9e210 --- /dev/null +++ b/include/tweeny/easing/bounce.h @@ -0,0 +1,87 @@ +/* +This file is part of the Tweeny library. + +Copyright (c) 2016-2025 Leonardo Guilherme Lucena de Freitas +Copyright (c) 2016 Guilherme R. Costa + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: +# +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ + +#ifndef TWEENY_EASING_BOUNCE_H +#define TWEENY_EASING_BOUNCE_H + +namespace tweeny::detail { + struct bounceOutEasing { + template + static T run(float position, T start, T end) { + T c = end - start; + if (position < 1 / 2.75f) { + return static_cast(c * (7.5625f * position * position) + start); + } + if (position < 2.0f / 2.75f) { + const float postFix = position -= 1.5f / 2.75f; + return static_cast(c * (7.5625f * (postFix) * position + .75f) + start); + } + if (position < 2.5f / 2.75f) { + const float postFix = position -= 2.25f / 2.75f; + return static_cast(c * (7.5625f * postFix * position + .9375f) + start); + } + const float postFix = position -= (2.625f / 2.75f); + return static_cast(c * (7.5625f * postFix * position + .984375f) + start); + } + + template + T operator()(const float position, T start, T end) const { + return run(position, start, end); + } + }; + + struct bounceInEasing { + template + static T run(const float position, T start, T end) { + return end - start - bounceOutEasing::run(1 - position, T(), (end - start)) + start; + } + + template + T operator()(const float position, T start, T end) const { + return run(position, start, end); + } + }; + + struct bounceInOutEasing { + template + static T run(const float position, T start, T end) { + if (position < 0.5f) return static_cast(bounceInEasing::run(position * 2, T(), end - start) * .5f + start); + return static_cast(bounceOutEasing::run(position * 2 - 1, T(), end - start) * .5f + (end - start) * .5f + + start); + } + + template + T operator()(float position, T start, T end) const { + return run(position, start, end); + } + }; +} + +namespace tweeny::easing { + inline constexpr detail::bounceInEasing bounceIn{}; + inline constexpr detail::bounceOutEasing bounceOut{}; + inline constexpr detail::bounceInOutEasing bounceInOut{}; +} + +#endif // TWEENY_EASING_BOUNCE_H diff --git a/include/tweeny/easing/circular.h b/include/tweeny/easing/circular.h new file mode 100644 index 0000000..21141a0 --- /dev/null +++ b/include/tweeny/easing/circular.h @@ -0,0 +1,81 @@ +/* +This file is part of the Tweeny library. + +Copyright (c) 2016-2025 Leonardo Guilherme Lucena de Freitas +Copyright (c) 2016 Guilherme R. Costa + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: +# +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ + +#ifndef TWEENY_EASING_CIRCULAR_H +#define TWEENY_EASING_CIRCULAR_H + +#include + +namespace tweeny::detail { + struct circularInEasing { + template + static T run(const float position, T start, T end) { + return static_cast(-(end - start) * (sqrtf(1 - position * position) - 1) + start); + } + + template + T operator()(const float position, T start, T end) const { + return run(position, start, end); + } + }; + + struct circularOutEasing { + template + static T run(float position, T start, T end) { + --position; + return static_cast((end - start) * sqrtf(1 - position * position) + start); + } + + template + T operator()(const float position, T start, T end) const { + return run(position, start, end); + } + }; + + struct circularInOutEasing { + template + static T run(float position, T start, T end) { + position *= 2; + if (position < 1) { + return static_cast(-(end - start) / 2 * (sqrtf(1 - position * position) - 1) + start); + } + + position -= 2; + return static_cast((end - start) / 2 * (sqrtf(1 - position * position) + 1) + start); + } + + template + T operator()(float position, T start, T end) const { + return run(position, start, end); + } + }; +} + +namespace tweeny::easing { + inline constexpr detail::circularInEasing circularIn{}; + inline constexpr detail::circularOutEasing circularOut{}; + inline constexpr detail::circularInOutEasing circularInOut{}; +} + +#endif // TWEENY_EASING_CIRCULAR_H diff --git a/include/tweeny/easing/cubic.h b/include/tweeny/easing/cubic.h new file mode 100644 index 0000000..a3cfb6c --- /dev/null +++ b/include/tweeny/easing/cubic.h @@ -0,0 +1,78 @@ +/* +This file is part of the Tweeny library. + +Copyright (c) 2016-2025 Leonardo Guilherme Lucena de Freitas +Copyright (c) 2016 Guilherme R. Costa + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: +# +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ + +#ifndef TWEENY_EASING_CUBIC_H +#define TWEENY_EASING_CUBIC_H + +namespace tweeny::detail { + struct cubicInEasing { + template + static T run(const float position, T start, T end) { + return static_cast((end - start) * position * position * position + start); + } + + template + T operator()(const float position, T start, T end) const { + return run(position, start, end); + } + }; + + struct cubicOutEasing { + template + static T run(float position, T start, T end) { + --position; + return static_cast((end - start) * (position * position * position + 1) + start); + } + + template + T operator()(const float position, T start, T end) const { + return run(position, start, end); + } + }; + + struct cubicInOutEasing { + template + static T run(float position, T start, T end) { + position *= 2; + if (position < 1) { + return static_cast((end - start) / 2 * position * position * position + start); + } + position -= 2; + return static_cast((end - start) / 2 * (position * position * position + 2) + start); + } + + template + T operator()(const float position, T start, T end) const { + return run(position, start, end); + } + }; +} + +namespace tweeny::easing { + inline constexpr detail::cubicInEasing cubicIn{}; + inline constexpr detail::cubicOutEasing cubicOut{}; + inline constexpr detail::cubicInOutEasing cubicInOut{}; +} + +#endif // TWEENY_EASING_CUBIC_H diff --git a/include/tweeny/easing/def.h b/include/tweeny/easing/def.h new file mode 100644 index 0000000..46cebb0 --- /dev/null +++ b/include/tweeny/easing/def.h @@ -0,0 +1,79 @@ +/* +This file is part of the Tweeny library. + +Copyright (c) 2016-2025 Leonardo Guilherme Lucena de Freitas +Copyright (c) 2016 Guilherme R. Costa + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: +# +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ + +#ifndef TWEENY_EASING_DEF_H +#define TWEENY_EASING_DEF_H + +#include +#include +#include + +namespace tweeny::detail { + template struct voidify { + using type = void; + }; + + template using void_t = typename voidify::type; + + template + struct supports_arithmetic_operations : std::false_type {}; + + template + struct supports_arithmetic_operations() + std::declval()), + decltype(std::declval() - std::declval()), + decltype(std::declval() * std::declval()), + decltype(std::declval() * std::declval()), + decltype(std::declval() * std::declval()) + >> : std::true_type {}; + + struct defaultEasing { + template + static std::enable_if_t, T> run(float position, T start, T end) { + return static_cast(roundf((end - start) * position + start)); + } + + template + static typename std::enable_if::value && !std::is_integral::value, T>::type + run(float position, T start, T end) { + return static_cast((end - start) * position + start); + } + + template + static std::enable_if_t::value, T> run(float /*position*/, T start, T /*end*/) { + return start; + } + + template + T operator()(float position, T start, T end) const { + return run(position, start, end); + } + }; +} + +namespace tweeny::easing { + inline constexpr detail::defaultEasing def{}; +} + +#endif // TWEENY_EASING_DEF_H diff --git a/include/tweeny/easing/easing.h b/include/tweeny/easing/easing.h index 1548bc9..ac6ca10 100644 --- a/include/tweeny/easing/easing.h +++ b/include/tweeny/easing/easing.h @@ -1,7 +1,7 @@ /* This file is part of the Tweeny library. - Copyright (c) 2016-2021 Leonardo Guilherme Lucena de Freitas + Copyright (c) 2016-2025 Leonardo Guilherme Lucena de Freitas Copyright (c) 2016 Guilherme R. Costa Permission is hereby granted, free of charge, to any person obtaining a copy of @@ -10,7 +10,7 @@ use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: - + # The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. @@ -24,702 +24,26 @@ /** * @file easing.h - * The purpose of this file is to list all bundled easings. All easings are based on Robert Penner's easing - * functions: http://robertpenner.com/easing/ + * The purpose of this file is to list all bundled easings. Each easing is defined + * in its own header under include/tweeny/easing/. Users may include individual + * easing headers or this header to get all easings. */ #ifndef TWEENY_EASING_H #define TWEENY_EASING_H -#include -#include - -#ifndef M_PI -#define M_PI 3.14159265358979323846 -#endif - -/** - * @defgroup easings Easings - * @brief Bundled easing functions based on - * Robert Penner's Easing Functions - * @details You should plug these functions into @ref tweeny::tween::via function to specify the easing used in a tween. - * @sa tweeny::easing - * @{ - *//** - * @defgroup stepped Stepped - * @{ - * @brief The value does not change. No interpolation is used. - * @} - *//** - * @defgroup default Default - * @{ - * @brief A default mode for arithmetic values it will change in constant speed, for non-arithmetic value will be constant. - * @} - *//** - * @defgroup linear Linear - * @{ - * @brief The most boring ever easing function. It has no acceleration and change values in constant speed. - * @} - *//** - * @defgroup quadratic Quadratic - * @{ - * @brief The most commonly used easing functions. - * @} - *//** - * @defgroup cubic Cubic - * @{ - * @brief A bit curvier than the quadratic easing. - * @} - *//** - * @defgroup quartic Quartic - * @{ - * @brief A steeper curve. Acceleration changes faster than Cubic. - * @} - *//** - * @defgroup quintic Quintic - * @{ - * @brief An even steeper curve. Acceleration changes really fast. - * @} - *//** - * @defgroup sinuisodal Sinuisodal - * @{ - * @brief A very gentle curve, gentlier than quadratic. - * @} - *//** - * @defgroup exponential Exponential - * @{ - * @brief A very steep curve, based on the `p(t) = 2^(10*(t-1))` equation. - * @} - *//** - * @defgroup circular Circular - * @{ - * @brief A smooth, circular slope that resembles the arc of an circle. - * @} - *//** - * @defgroup back Back - * @{ - * @brief An easing function that has a "cute" natural coming back effect. - * @} - *//** - * @defgroup elastic Elastic - * @{ - * @brief An elastic easing function. Values go a little past the maximum/minimum in an elastic effect. - * @} - *//** - * @defgroup bounce Bounce - * @{ - * @brief A bouncing easing function. Values "bounce" around the maximum/minumum. - * @} - *//** - * @} - */ - -namespace tweeny { - /** - * @brief The easing class holds all the bundled easings. - * - * You should pass the easing function to the @p tweeny::tween::via method, to set the easing function that will - * be used to interpolate values in a tween point. - * - * **Example**: - * - * @code - * auto tween = tweeny::from(0).to(100).via(tweeny::easing::linear); - * @endcode - */ - class easing { - public: - /** - * @ingroup stepped - * @brief Value is constant. - */ - static constexpr struct steppedEasing { - template - static T run(float position, T start, T end) { - (void) position; - (void) end; - return start; - } - - template - T operator()(float position, T start, T end) const { - return run(position, start, end); - } - } stepped = steppedEasing{}; - - /** - * @ingroup default - * @brief Values change with constant speed for arithmetic type only. The non-arithmetic it will be constant. - */ - static constexpr struct defaultEasing { - template struct voidify { using type = void; }; - template using void_t = typename voidify::type; - - template - struct supports_arithmetic_operations : std::false_type {}; - - template - struct supports_arithmetic_operations() + std::declval()), - decltype(std::declval() - std::declval()), - decltype(std::declval() * std::declval()), - decltype(std::declval() * std::declval()), - decltype(std::declval() * std::declval()) - >> : std::true_type{}; - - - template - static std::enable_if_t, T> run(float position, T start, T end) { - return static_cast(roundf((end - start) * position + start)); - } - - template - static typename std::enable_if::value && !std::is_integral::value, T>::type run(float position, T start, T end) { - return static_cast((end - start) * position + start); - } - - template - static std::enable_if_t::value, T> run(float position, T start, T end) { - (void) position; - (void) end; - return start; - } - - template - T operator()(float position, T start, T end) const { - return run(position, start, end); - } - } def = defaultEasing{}; - - /** - * @ingroup linear - * @brief Values change with constant speed. - */ - static constexpr struct linearEasing { - template - static std::enable_if_t, T> run(float position, T start, T end) { - return static_cast(roundf((end - start) * position + start)); - } - - template - static std::enable_if_t, T> run(float position, T start, T end) { - return static_cast((end - start) * position + start); - } - - template - T operator()(float position, T start, T end) const { - return run(position, start, end); - } - } linear = linearEasing{}; - - /** - * @ingroup quadratic - * @brief Accelerate initial values with a quadratic equation. - */ - static constexpr struct quadraticInEasing { - template - static T run(float position, T start, T end) { - return static_cast((end - start) * position * position + start); - } - - template - T operator()(float position, T start, T end) const { - return run(position, start, end); - } - } quadraticIn = quadraticInEasing{}; - - /** - * @ingroup quadratic - * @brief Deaccelerate ending values with a quadratic equation. - */ - static constexpr struct quadraticOutEasing { - template - static T run(float position, T start, T end) { - return static_cast((-(end - start)) * position * (position - 2) + start); - } - - template - T operator()(float position, T start, T end) const { - return run(position, start, end); - } - } quadraticOut = quadraticOutEasing{}; - - /** - * @ingroup quadratic - * @brief Acceelerate initial and deaccelerate ending values with a quadratic equation. - */ - static constexpr struct quadraticInOutEasing { - template - static T run(float position, T start, T end) { - position *= 2; - if (position < 1) { - return static_cast((end - start) / 2 * position * position + start); - } - - --position; - return static_cast(-(end - start) / 2 * (position * (position - 2) - 1) + start); - } - - template - T operator()(float position, T start, T end) const { - return run(position, start, end); - } - } quadraticInOut = quadraticInOutEasing{}; - - /** - * @ingroup cubic - * @brief Aaccelerate initial values with a cubic equation. - */ - static constexpr struct cubicInEasing { - template - static T run(float position, T start, T end) { - return static_cast((end - start) * position * position * position + start); - } - - template - T operator()(float position, T start, T end) const { - return run(position, start, end); - } - } cubicIn = cubicInEasing{}; - - /** - * @ingroup cubic - * @brief Deaccelerate ending values with a cubic equation. - */ - static constexpr struct cubicOutEasing { - template - static T run(float position, T start, T end) { - --position; - return static_cast((end - start) * (position * position * position + 1) + start); - } - - template - T operator()(float position, T start, T end) const { - return run(position, start, end); - } - } cubicOut = cubicOutEasing{}; - - /** - * @ingroup cubic - * @brief Acceelerate initial and deaccelerate ending values with a cubic equation. - */ - static constexpr struct cubicInOutEasing { - template - static T run(float position, T start, T end) { - position *= 2; - if (position < 1) { - return static_cast((end - start) / 2 * position * position * position + start); - } - position -= 2; - return static_cast((end - start) / 2 * (position * position * position + 2) + start); - } - } cubicInOut = cubicInOutEasing{}; - - /** - * @ingroup quartic - * @brief Acceelerate initial values with a quartic equation. - */ - static constexpr struct quarticInEasing { - template - static T run(float position, T start, T end) { - return static_cast((end - start) * position * position * position * position + start); - } - } quarticIn = quarticInEasing{}; - - /** - * @ingroup quartic - * @brief Deaccelerate ending values with a quartic equation. - */ - static constexpr struct quarticOutEasing { - template - static T run(float position, T start, T end) { - --position; - return static_cast( -(end - start) * (position * position * position * position - 1) + start); - } - } quarticOut = quarticOutEasing{}; - - /** - * @ingroup quartic - * @brief Acceelerate initial and deaccelerate ending values with a quartic equation. - */ - static constexpr struct quarticInOutEasing { - template - static T run(float position, T start, T end) { - position *= 2; - if (position < 1) { - return static_cast((end - start) / 2 * (position * position * position * position) + - start); - } - position -= 2; - return static_cast(-(end - start) / 2 * (position * position * position * position - 2) + - start); - } - } quarticInOut = quarticInOutEasing{}; - - /** - * @ingroup quintic - * @brief Acceelerate initial values with a quintic equation. - */ - static constexpr struct quinticInEasing { - template - static T run(float position, T start, T end) { - return static_cast((end - start) * position * position * position * position * position + start); - } - } quinticIn = quinticInEasing{}; - - /** - * @ingroup quintic - * @brief Deaccelerate ending values with a quintic equation. - */ - static constexpr struct quinticOutEasing { - template - static T run(float position, T start, T end) { - position--; - return static_cast((end - start) * (position * position * position * position * position + 1) + - start); - } - } quinticOut = quinticOutEasing{}; - - /** - * @ingroup quintic - * @brief Acceelerate initial and deaccelerate ending values with a quintic equation. - */ - static constexpr struct quinticInOutEasing { - template - static T run(float position, T start, T end) { - position *= 2; - if (position < 1) { - return static_cast( - (end - start) / 2 * (position * position * position * position * position) + - start); - } - position -= 2; - return static_cast( - (end - start) / 2 * (position * position * position * position * position + 2) + - start); - } - } quinticInOut = quinticInOutEasing{}; - - /** - * @ingroup sinusoidal - * @brief Acceelerate initial values with a sinusoidal equation. - */ - static constexpr struct sinusoidalInEasing { - template - static T run(const float position, T start, T end) { - return static_cast(-(end - start) * cosf(position * static_cast(M_PI) / 2) + (end - start) + start); - } - } sinusoidalIn = sinusoidalInEasing{}; - - /** - * @ingroup sinusoidal - * @brief Deaccelerate ending values with a sinusoidal equation. - */ - static constexpr struct sinusoidalOutEasing { - template - static T run(const float position, T start, T end) { - return static_cast((end - start) * sinf(position * static_cast(M_PI) / 2) + start); - } - } sinusoidalOut = sinusoidalOutEasing{}; - - /** - * @ingroup sinusoidal - * @brief Acceelerate initial and deaccelerate ending values with a sinusoidal equation. - */ - static constexpr struct sinusoidalInOutEasing { - template - static T run(const float position, T start, T end) { - return static_cast(-(end - start) / 2 * (cosf(position * static_cast(M_PI)) - 1) + start); - } - } sinusoidalInOut = sinusoidalInOutEasing{}; - - /** - * @ingroup exponential - * @brief Acceelerate initial values with an exponential equation. - */ - static constexpr struct exponentialInEasing { - template - static T run(const float position, T start, T end) { - return static_cast((end - start) * powf(2, 10 * (position - 1)) + start); - } - } exponentialIn = exponentialInEasing{}; - - /** - * @ingroup exponential - * @brief Deaccelerate ending values with an exponential equation. - */ - static constexpr struct exponentialOutEasing { - template - static T run(const float position, T start, T end) { - return static_cast((end - start) * (-powf(2, -10 * position) + 1) + start); - } - } exponentialOut = exponentialOutEasing{}; - - /** - * @ingroup exponential - * @brief Acceelerate initial and deaccelerate ending values with an exponential equation. - */ - static constexpr struct exponentialInOutEasing { - template - static T run(float position, T start, T end) { - position *= 2; - if (position < 1) { - return static_cast((end - start) / 2 * powf(2, 10 * (position - 1)) + start); - } - --position; - return static_cast((end - start) / 2 * (-powf(2, -10 * position) + 2) + start); - } - } exponentialInOut = exponentialInOutEasing{}; - - /** - * @ingroup circular - * @brief Acceelerate initial values with a circular equation. - */ - static constexpr struct circularInEasing { - template - static T run(const float position, T start, T end) { - return static_cast( -(end - start) * (sqrtf(1 - position * position) - 1) + start ); - } - - template - T operator()(float position, T start, T end) const { - return run(position, start, end); - } - } circularIn = circularInEasing{}; - - /** - * @ingroup circular - * @brief Deaccelerate ending values with a circular equation. - */ - static constexpr struct circularOutEasing { - template - static T run(float position, T start, T end) { - --position; - return static_cast((end - start) * sqrtf(1 - position * position) + start); - } - - template - T operator()(float position, T start, T end) const { - return run(position, start, end); - } - } circularOut = circularOutEasing{}; - - /** - * @ingroup circular - * @brief Acceelerate initial and deaccelerate ending values with a circular equation. - */ - static constexpr struct circularInOutEasing { - template - static T run(float position, T start, T end) { - position *= 2; - if (position < 1) { - return static_cast(-(end - start) / 2 * (sqrtf(1 - position * position) - 1) + start); - } - - position -= 2; - return static_cast((end - start) / 2 * (sqrtf(1 - position * position) + 1) + start); - } - - template - T operator()(float position, T start, T end) const { - return run(position, start, end); - } - } circularInOut = circularInOutEasing{}; - - /** - * @ingroup bounce - * @brief Acceelerate initial values with a "bounce" equation. - */ - static constexpr struct bounceInEasing { - template - static T run(float position, T start, T end) { - return end - start - bounceOut.run(1 - position, T(), (end - start)) + start; - } - - template - T operator()(float position, T start, T end) const { - return run(position, start, end); - } - } bounceIn = bounceInEasing{}; - - /** - * @ingroup bounce - * @brief Deaccelerate ending values with a "bounce" equation. - */ - static constexpr struct bounceOutEasing { - template - static T run(float position, T start, T end) { - T c = end - start; - if (position < 1 / 2.75f) { - return static_cast(c * (7.5625f * position * position) + start); - } - if (position < 2.0f / 2.75f) { - float postFix = position -= (1.5f / 2.75f); - return static_cast(c * (7.5625f * (postFix) * position + .75f) + start); - } - if (position < 2.5f / 2.75f) { - float postFix = position -= (2.25f / 2.75f); - return static_cast(c * (7.5625f * (postFix) * position + .9375f) + start); - } - const float postFix = position -= (2.625f / 2.75f); - return static_cast(c * (7.5625f * postFix * position + .984375f) + start); - } - - template - T operator()(float position, T start, T end) const { - return run(position, start, end); - } - } bounceOut = bounceOutEasing{}; - - /** - * @ingroup bounce - * @brief Acceelerate initial and deaccelerate ending values with a "bounce" equation. - */ - static constexpr struct bounceInOutEasing { - template - static T run(float position, T start, T end) { - if (position < 0.5f) return static_cast(bounceIn.run(position * 2, T(), end - start) * .5f + start); - return static_cast(bounceOut.run(position * 2 - 1, T(), end - start) * .5f + (end - start) * .5f + start); - } - - template - T operator()(float position, T start, T end) const { - return run(position, start, end); - } - } bounceInOut = bounceInOutEasing{}; - - /** - * @ingroup elastic - * @brief Acceelerate initial values with an "elastic" equation. - */ - static constexpr struct elasticInEasing { - template - static T run(float position, T start, T end) { - if (position <= 0.00001f) return start; - if (position >= 0.999f) return end; - const float p = .3f; - auto a = end - start; - const float s = p / 4; - float postFix = - a * powf(2, 10 * (position -= 1)); // this is a fix, again, with post-increment operators - return static_cast(-(postFix * sinf((position - s) * (2 * static_cast(M_PI)) / p)) + start); - } - - template - T operator()(float position, T start, T end) const { - return run(position, start, end); - } - } elasticIn = elasticInEasing{}; - - /** - * @ingroup elastic - * @brief Deaccelerate ending values with an "elastic" equation. - */ - static constexpr struct elasticOutEasing { - template - static T run(const float position, T start, T end) { - if (position <= 0.00001f) return start; - if (position >= 0.999f) return end; - float p = .3f; - auto a = end - start; - float s = p / 4; - return static_cast(a * powf(2, -10 * position) * sinf((position - s) * (2 * static_cast(M_PI)) / p) + end); - } - - template - T operator()(float position, T start, T end) const { - return run(position, start, end); - } - } elasticOut = elasticOutEasing{}; - - /** - * @ingroup elastic - * @brief Acceelerate initial and deaccelerate ending values with an "elastic" equation. - */ - static constexpr struct elasticInOutEasing { - template - static T run(float position, T start, T end) { - if (position <= 0.00001f) return start; - if (position >= 0.999f) return end; - position *= 2; - float p = .3f * 1.5f; - auto a = end - start; - float s = p / 4; - float postFix; - - if (position < 1) { - postFix = a * powf(2, 10 * (position -= 1)); // postIncrement is evil - return static_cast(-0.5f * (postFix * sinf((position - s) * (2 * static_cast(M_PI)) / p)) + start); - } - postFix = a * powf(2, -10 * (position -= 1)); // postIncrement is evil - return static_cast(postFix * sinf((position - s) * (2 * static_cast(M_PI)) / p) * .5f + end); - } - - template - T operator()(float position, T start, T end) const { - return run(position, start, end); - } - } elasticInOut = elasticInOutEasing{}; - - /** - * @ingroup back - * @brief Acceelerate initial values with a "back" equation. - */ - static constexpr struct backInEasing { - template - static T run(float position, T start, T end) { - const float s = 1.70158f; - float postFix = position; - return static_cast((end - start) * postFix * position * ((s + 1) * position - s) + start); - } - - template - T operator()(float position, T start, T end) const { - return run(position, start, end); - } - } backIn = backInEasing{}; - - /** - * @ingroup back - * @brief Deaccelerate ending values with a "back" equation. - */ - static constexpr struct backOutEasing { - template - static T run(float position, T start, T end) { - const float s = 1.70158f; - position -= 1; - return static_cast((end - start) * ((position) * position * ((s + 1) * position + s) + 1) + start); - } - - template - T operator()(float position, T start, T end) const { - return run(position, start, end); - } - } backOut = backOutEasing{}; - - /** - * @ingroup back - * @brief Acceelerate initial and deaccelerate ending values with a "back" equation. - */ - static constexpr struct backInOutEasing { - template - static T run(float position, T start, T end) { - float s = 1.70158f; - float t = position; - auto b = start; - auto c = end - start; - float d = 1; - s *= 1.525f; - if ((t /= d / 2) < 1) return static_cast(c / 2 * (t * t * ((s + 1) * t - s)) + b); - const float postFix = t -= 2; - return static_cast(c / 2 * (postFix * t * ((s + 1) * t + s) + 2) + b); - } +#include "tweeny/easing/stepped.h" +#include "tweeny/easing/def.h" +#include "tweeny/easing/linear.h" +#include "tweeny/easing/quadratic.h" +#include "tweeny/easing/cubic.h" +#include "tweeny/easing/quartic.h" +#include "tweeny/easing/quintic.h" +#include "tweeny/easing/sinusoidal.h" +#include "tweeny/easing/exponential.h" +#include "tweeny/easing/circular.h" +#include "tweeny/easing/bounce.h" +#include "tweeny/easing/elastic.h" +#include "tweeny/easing/back.h" - template - T operator()(float position, T start, T end) const { - return run(position, start, end); - } - } backInOut = backInOutEasing{}; - }; -} #endif //TWEENY_EASING_H diff --git a/include/tweeny/easing/elastic.h b/include/tweeny/easing/elastic.h new file mode 100644 index 0000000..1b0ec6b --- /dev/null +++ b/include/tweeny/easing/elastic.h @@ -0,0 +1,104 @@ +/* +This file is part of the Tweeny library. + +Copyright (c) 2016-2025 Leonardo Guilherme Lucena de Freitas +Copyright (c) 2016 Guilherme R. Costa + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: +# +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ + +#ifndef TWEENY_EASING_ELASTIC_H +#define TWEENY_EASING_ELASTIC_H + +#include + +#ifndef M_PI +#define M_PI 3.14159265358979323846 +#endif + +namespace tweeny::detail { + struct elasticInEasing { + template + static T run(float position, T start, T end) { + if (position <= 0.00001f) return start; + if (position >= 0.999f) return end; + const float p = .3f; + auto a = end - start; + const float s = p / 4; + const float postFix = a * powf(2, 10 * (position -= 1)); + return static_cast(-(postFix * sinf((position - s) * (2 * static_cast(M_PI)) / p)) + start); + } + + template + T operator()(const float position, T start, T end) const { + return run(position, start, end); + } + }; + + struct elasticOutEasing { + template + static T run(const float position, T start, T end) { + if (position <= 0.00001f) return start; + if (position >= 0.999f) return end; + float p = .3f; + auto a = end - start; + float s = p / 4; + return static_cast(a * powf(2, -10 * position) * sinf( + (position - s) * (2 * static_cast(M_PI)) / p + ) + end); + } + + template + T operator()(const float position, T start, T end) const { + return run(position, start, end); + } + }; + + struct elasticInOutEasing { + template + static T run(float position, T start, T end) { + if (position <= 0.00001f) return start; + if (position >= 0.999f) return end; + position *= 2; + const float p = .3f * 1.5f; + auto a = end - start; + const float s = p / 4; + float postFix; + + if (position < 1) { + postFix = a * powf(2, 10 * (position -= 1)); + return static_cast(-0.5f * (postFix * sinf((position - s) * (2 * static_cast(M_PI)) / p)) + start); + } + postFix = a * powf(2, -10 * (position -= 1)); + return static_cast(postFix * sinf((position - s) * (2 * static_cast(M_PI)) / p) * .5f + end); + } + + template + T operator()(const float position, T start, T end) const { + return run(position, start, end); + } + }; +} + +namespace tweeny::easing { + inline constexpr detail::elasticInEasing elasticIn{}; + inline constexpr detail::elasticOutEasing elasticOut{}; + inline constexpr detail::elasticInOutEasing elasticInOut{}; +} + +#endif // TWEENY_EASING_ELASTIC_H diff --git a/include/tweeny/easing/exponential.h b/include/tweeny/easing/exponential.h new file mode 100644 index 0000000..8ca67a1 --- /dev/null +++ b/include/tweeny/easing/exponential.h @@ -0,0 +1,79 @@ +/* +This file is part of the Tweeny library. + +Copyright (c) 2016-2025 Leonardo Guilherme Lucena de Freitas +Copyright (c) 2016 Guilherme R. Costa + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: +# +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ + +#ifndef TWEENY_EASING_EXPONENTIAL_H +#define TWEENY_EASING_EXPONENTIAL_H + +#include + +namespace tweeny::detail { + struct exponentialInEasing { + template + static T run(const float position, T start, T end) { + return static_cast((end - start) * powf(2, 10 * (position - 1)) + start); + } + + template + T operator()(const float position, T start, T end) const { + return run(position, start, end); + } + }; + + struct exponentialOutEasing { + template + static T run(const float position, T start, T end) { + return static_cast((end - start) * (-powf(2, -10 * position) + 1) + start); + } + + template + T operator()(const float position, T start, T end) const { + return run(position, start, end); + } + }; + + struct exponentialInOutEasing { + template + static T run(float position, T start, T end) { + position *= 2; + if (position < 1) { + return static_cast((end - start) / 2 * powf(2, 10 * (position - 1)) + start); + } + --position; + return static_cast((end - start) / 2 * (-powf(2, -10 * position) + 2) + start); + } + + template + T operator()(const float position, T start, T end) const { + return run(position, start, end); + } + }; +} + +namespace tweeny::easing { + inline constexpr detail::exponentialInEasing exponentialIn{}; + inline constexpr detail::exponentialOutEasing exponentialOut{}; + inline constexpr detail::exponentialInOutEasing exponentialInOut{}; +} + +#endif // TWEENY_EASING_EXPONENTIAL_H diff --git a/include/tweeny/easing/linear.h b/include/tweeny/easing/linear.h new file mode 100644 index 0000000..8b8a4f8 --- /dev/null +++ b/include/tweeny/easing/linear.h @@ -0,0 +1,54 @@ +/* +This file is part of the Tweeny library. + +Copyright (c) 2016-2025 Leonardo Guilherme Lucena de Freitas +Copyright (c) 2016 Guilherme R. Costa + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: +# +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ + +#ifndef TWEENY_EASING_LINEAR_H +#define TWEENY_EASING_LINEAR_H + +#include +#include + +namespace tweeny::detail { + struct linearEasing { + template + static std::enable_if_t, T> run(const float position, T start, T end) { + return static_cast(roundf((end - start) * position + start)); + } + + template + static std::enable_if_t, T> run(const float position, T start, T end) { + return static_cast((end - start) * position + start); + } + + template + T operator()(const float position, T start, T end) const { + return run(position, start, end); + } + }; +} + +namespace tweeny::easing { + inline constexpr detail::linearEasing linear{}; +} + +#endif // TWEENY_EASING_LINEAR_H diff --git a/include/tweeny/easing/quadratic.h b/include/tweeny/easing/quadratic.h new file mode 100644 index 0000000..082ddf0 --- /dev/null +++ b/include/tweeny/easing/quadratic.h @@ -0,0 +1,77 @@ +/* +This file is part of the Tweeny library. + +Copyright (c) 2016-2025 Leonardo Guilherme Lucena de Freitas +Copyright (c) 2016 Guilherme R. Costa + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: +# +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ + +#ifndef TWEENY_EASING_QUADRATIC_H +#define TWEENY_EASING_QUADRATIC_H + +namespace tweeny::detail { + struct quadraticInEasing { + template + static T run(const float position, T start, T end) { + return static_cast((end - start) * position * position + start); + } + + template + T operator()(const float position, T start, T end) const { + return run(position, start, end); + } + }; + + struct quadraticOutEasing { + template + static T run(const float position, T start, T end) { + return static_cast((-(end - start)) * position * (position - 2) + start); + } + + template + T operator()(const float position, T start, T end) const { + return run(position, start, end); + } + }; + + struct quadraticInOutEasing { + template + static T run(float position, T start, T end) { + position *= 2; + if (position < 1) { + return static_cast((end - start) / 2 * position * position + start); + } + + --position; + return static_cast(-(end - start) / 2 * (position * (position - 2) - 1) + start); + } + + template + T operator()(const float position, T start, T end) const { + return run(position, start, end); + } + }; +} + +namespace tweeny::easing { + inline constexpr detail::quadraticInEasing quadraticIn{}; + inline constexpr detail::quadraticOutEasing quadraticOut{}; + inline constexpr detail::quadraticInOutEasing quadraticInOut{}; +} +#endif // TWEENY_EASING_QUADRATIC_H diff --git a/include/tweeny/easing/quartic.h b/include/tweeny/easing/quartic.h new file mode 100644 index 0000000..99a60f6 --- /dev/null +++ b/include/tweeny/easing/quartic.h @@ -0,0 +1,78 @@ +/* +This file is part of the Tweeny library. + +Copyright (c) 2016-2025 Leonardo Guilherme Lucena de Freitas +Copyright (c) 2016 Guilherme R. Costa + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: +# +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ + +#ifndef TWEENY_EASING_QUARTIC_H +#define TWEENY_EASING_QUARTIC_H + +namespace tweeny::detail { + struct quarticInEasing { + template + static T run(const float position, T start, T end) { + return static_cast((end - start) * position * position * position * position + start); + } + + template + T operator()(const float position, T start, T end) const { + return run(position, start, end); + } + }; + + struct quarticOutEasing { + template + static T run(float position, T start, T end) { + --position; + return static_cast(-(end - start) * (position * position * position * position - 1) + start); + } + + template + T operator()(const float position, T start, T end) const { + return run(position, start, end); + } + }; + + struct quarticInOutEasing { + template + static T run(float position, T start, T end) { + position *= 2; + if (position < 1) { + return static_cast((end - start) / 2 * (position * position * position * position) + start); + } + position -= 2; + return static_cast(-(end - start) / 2 * (position * position * position * position - 2) + start); + } + + template + T operator()(const float position, T start, T end) const { + return run(position, start, end); + } + }; +} + +namespace tweeny::easing { + inline constexpr detail::quarticInEasing quarticIn{}; + inline constexpr detail::quarticOutEasing quarticOut{}; + inline constexpr detail::quarticInOutEasing quarticInOut{}; +} + +#endif // TWEENY_EASING_QUARTIC_H diff --git a/include/tweeny/easing/quintic.h b/include/tweeny/easing/quintic.h new file mode 100644 index 0000000..b10bc5d --- /dev/null +++ b/include/tweeny/easing/quintic.h @@ -0,0 +1,78 @@ +/* +This file is part of the Tweeny library. + +Copyright (c) 2016-2025 Leonardo Guilherme Lucena de Freitas +Copyright (c) 2016 Guilherme R. Costa + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: +# +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ + +#ifndef TWEENY_EASING_QUINTIC_H +#define TWEENY_EASING_QUINTIC_H + +namespace tweeny::detail { + struct quinticInEasing { + template + static T run(const float position, T start, T end) { + return static_cast((end - start) * position * position * position * position * position + start); + } + + template + T operator()(const float position, T start, T end) const { + return run(position, start, end); + } + }; + + struct quinticOutEasing { + template + static T run(float position, T start, T end) { + position--; + return static_cast((end - start) * (position * position * position * position * position + 1) + start); + } + + template + T operator()(const float position, T start, T end) const { + return run(position, start, end); + } + }; + + struct quinticInOutEasing { + template + static T run(float position, T start, T end) { + position *= 2; + if (position < 1) { + return static_cast((end - start) / 2 * (position * position * position * position * position) + start); + } + position -= 2; + return static_cast((end - start) / 2 * (position * position * position * position * position + 2) + start); + } + + template + T operator()(const float position, T start, T end) const { + return run(position, start, end); + } + }; +} + +namespace tweeny::easing { + inline constexpr detail::quinticInEasing quinticIn{}; + inline constexpr detail::quinticOutEasing quinticOut{}; + inline constexpr detail::quinticInOutEasing quinticInOut{}; +} + +#endif // TWEENY_EASING_QUINTIC_H diff --git a/include/tweeny/easing/sinusoidal.h b/include/tweeny/easing/sinusoidal.h new file mode 100644 index 0000000..65fea5c --- /dev/null +++ b/include/tweeny/easing/sinusoidal.h @@ -0,0 +1,78 @@ +/* +This file is part of the Tweeny library. + +Copyright (c) 2016-2025 Leonardo Guilherme Lucena de Freitas +Copyright (c) 2016 Guilherme R. Costa + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: +# +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ + +#ifndef TWEENY_EASING_SINUSOIDAL_H +#define TWEENY_EASING_SINUSOIDAL_H + +#include + +#ifndef M_PI +#define M_PI 3.14159265358979323846 +#endif + +namespace tweeny::detail { + struct sinusoidalInEasing { + template + static T run(const float position, T start, T end) { + return static_cast(-(end - start) * cosf(position * static_cast(M_PI) / 2) + (end - start) + start); + } + + template + T operator()(const float position, T start, T end) const { + return run(position, start, end); + } + }; + + struct sinusoidalOutEasing { + template + static T run(const float position, T start, T end) { + return static_cast((end - start) * sinf(position * static_cast(M_PI) / 2) + start); + } + + template + T operator()(const float position, T start, T end) const { + return run(position, start, end); + } + }; + + struct sinusoidalInOutEasing { + template + static T run(const float position, T start, T end) { + return static_cast(-(end - start) / 2 * (cosf(position * static_cast(M_PI)) - 1) + start); + } + + template + T operator()(const float position, T start, T end) const { + return run(position, start, end); + } + }; +} + +namespace tweeny::easing { + inline constexpr detail::sinusoidalInEasing sinusoidalIn{}; + inline constexpr detail::sinusoidalOutEasing sinusoidalOut{}; + inline constexpr detail::sinusoidalInOutEasing sinusoidalInOut{}; +} + +#endif // TWEENY_EASING_SINUSOIDAL_H diff --git a/include/tweeny/easing/stepped.h b/include/tweeny/easing/stepped.h new file mode 100644 index 0000000..9e5722b --- /dev/null +++ b/include/tweeny/easing/stepped.h @@ -0,0 +1,46 @@ +/* +This file is part of the Tweeny library. + +Copyright (c) 2016-2025 Leonardo Guilherme Lucena de Freitas +Copyright (c) 2016 Guilherme R. Costa + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: +# +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ + +#ifndef TWEENY_EASING_STEPPED_H +#define TWEENY_EASING_STEPPED_H + +namespace tweeny::detail { + struct steppedEasing { + template + static T run(float /*position*/, T start, T /*end*/) { + return start; + } + + template + T operator()(const float position, T start, T end) const { + return run(position, start, end); + } + }; +} + +namespace tweeny::easing { + inline constexpr detail::steppedEasing stepped{}; +} + +#endif // TWEENY_EASING_STEPPED_H diff --git a/src/tweeny/tween.tcc b/include/tweeny/tween.tcc similarity index 100% rename from src/tweeny/tween.tcc rename to include/tweeny/tween.tcc From 71bbb115e3f80748095d83ef9ac6b55ea3ad541d Mon Sep 17 00:00:00 2001 From: Leonardo Date: Tue, 19 Aug 2025 23:59:03 -0300 Subject: [PATCH 22/58] make `step`, `jump` and `seek` independent of each other --- CMakeLists.txt | 1 + include/tweeny/tween.h | 6 +-- include/tweeny/tween.tcc | 91 ++++++++++++++++++++-------------------- 3 files changed, 49 insertions(+), 49 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index a06f2c4..bd3171a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -61,6 +61,7 @@ target_sources(tweeny INTERFACE BASE_DIRS ${CMAKE_CURRENT_SOURCE_DIR}/include/tweeny FILES + include/tweeny/event.h include/tweeny/tweeny.h include/tweeny/tween.h include/tweeny/tween.tcc diff --git a/include/tweeny/tween.h b/include/tweeny/tween.h index 21a6b42..2d60ca2 100644 --- a/include/tweeny/tween.h +++ b/include/tweeny/tween.h @@ -43,8 +43,8 @@ namespace tweeny { explicit tween(const key_frames_t & key_frames_input); explicit tween(key_frames_t & key_frames_input); - auto seek(uint32_t frame) -> tween_value_t; - auto jump(std::size_t key_frame) -> tween_value_t; + auto seek(uint32_t target_frame) -> tween_value_t; + auto jump(std::size_t target_key_frame) -> tween_value_t; auto step(int32_t frames) -> tween_value_t; private: @@ -52,7 +52,7 @@ namespace tweeny { uint32_t current_frame = 0; tween_value_t current_value; - auto interpolate() -> tween_value_t; + auto render(uint32_t target_frame) -> tween_value_t; auto find_key_frame_index(uint32_t frame) -> std::size_t; }; } diff --git a/include/tweeny/tween.tcc b/include/tweeny/tween.tcc index 3f22536..9a8e801 100644 --- a/include/tweeny/tween.tcc +++ b/include/tweeny/tween.tcc @@ -33,14 +33,6 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. #include "detail/interpolate.h" #include "tweeny/easing/easing.h" -namespace tweeny::detail { - static float clampf(const float v, const float min = 0.0f, const float max = 1.0f) { - if (v < min) return min; - if (v > max) return max; - return v; - } -} - template tweeny::tween::tween(const key_frames_t & key_frames_input) : key_frames(key_frames_input) { } @@ -56,22 +48,19 @@ auto tweeny::tween::find_key_frame_index template -auto tweeny::tween::seek(uint32_t frame) -> tween_value_t { - current_frame = frame; - current_value = interpolate(); +auto tweeny::tween::seek(const uint32_t target_frame) -> tween_value_t { + current_frame = target_frame; + current_value = render(target_frame); return current_value; } template -auto tweeny::tween::jump(std::size_t key_frame) -> tween_value_t { - if (key_frames.empty()) { - return seek(0); - } - if (key_frame >= key_frames.size()) { - key_frame = key_frames.size() - 1; - } - const auto frame = static_cast(key_frames[key_frame].position); - return seek(frame); +auto tweeny::tween::jump(std::size_t target_key_frame) -> tween_value_t { + target_key_frame = std::clamp(target_key_frame, 0, key_frames.size() - 1); + const auto target_frame = static_cast(key_frames[target_key_frame].position); + current_frame = target_frame; + current_value = render(target_frame); + return current_value; } template @@ -84,14 +73,19 @@ auto tweeny::tween::step(const int32_t f } else { target_frame += static_cast(frames); } - return seek(target_frame); + + current_frame = target_frame; + current_value = render(target_frame); + return current_value; } template -auto tweeny::tween::interpolate() -> tween_value_t { +auto tweeny::tween::render(uint32_t target_frame) -> tween_value_t { constexpr std::size_t ValuesCount = sizeof...(RemainingValueTypes) + 1; - const auto as_return_value = [](const auto& val) -> tween_value_t { + if (target_frame == current_frame) return current_value; + + const auto as_return_value = [](const auto & val) -> tween_value_t { if constexpr (ValuesCount == 1) { return std::get<0>(val); } else { @@ -107,39 +101,44 @@ auto tweeny::tween::interpolate() -> twe } } - // If before or at the first key frame, return its value auto & first_key_frame = key_frames.front(); - if (current_frame <= first_key_frame.position) { - const auto & v = first_key_frame.values; - return as_return_value(v); - } + auto & last_key_frame = key_frames.back(); - std::size_t keyframe_index = find_key_frame_index(current_frame); + // note to future self: we don't need to add the frame count + // to the last_key_frame.position because there's no interpolation + // beyond it (duh) + target_frame = std::clamp( + target_frame, + first_key_frame.position, + last_key_frame.position + ); - // If next at or past the last key frame, return its value - auto & last_key_frame = key_frames.back(); - if (keyframe_index + 1 >= key_frames.size()) { - const auto & v = last_key_frame.values; - return as_return_value(v); + if (target_frame <= first_key_frame.position) return as_return_value(first_key_frame.values); + if (target_frame >= last_key_frame.position) return as_return_value(last_key_frame.values); + + std::size_t base_key_key_frame_idx = find_key_frame_index(target_frame); + + if (base_key_key_frame_idx + 1 >= key_frames.size()) { + return as_return_value(last_key_frame.values); } - const key_frame_t & base_key_frame = key_frames[keyframe_index]; - const key_frame_t & next_key_frame = key_frames[keyframe_index + 1]; + const key_frame_t & base_key_frame = key_frames[base_key_key_frame_idx]; + const key_frame_t & next_key_frame = key_frames[base_key_key_frame_idx + 1]; - const int64_t base_pos = base_key_frame.position; - const uint32_t next_pos = next_key_frame.position; - const int64_t current_frame_i64 = current_frame; + const int64_t base_kf_position = base_key_frame.position; + const uint32_t target_kf_position = next_key_frame.position; + const int64_t target_frame_i64 = target_frame; - float tweening_progress = 1.0f; - if (next_pos > base_pos) { - const auto numerator = static_cast(current_frame_i64 - base_pos); - const auto denominator = static_cast(next_pos - base_pos); - tweening_progress = numerator / denominator; + float inbetween_progress = 1.0f; + if (target_kf_position > base_kf_position) { + const auto numerator = static_cast(target_frame_i64 - base_kf_position); + const auto denominator = static_cast(target_kf_position - base_kf_position); + inbetween_progress = numerator / denominator; } - tweening_progress = detail::clampf(tweening_progress); + inbetween_progress = std::clamp(inbetween_progress, 0.0f, 1.0f); auto values = detail::interpolate_values( - tweening_progress, + inbetween_progress, base_key_frame, next_key_frame, std::make_index_sequence{} From afdecd1158a4da337eda29444adffe84e97b8653 Mon Sep 17 00:00:00 2001 From: Leonardo Date: Wed, 20 Aug 2025 01:22:38 -0300 Subject: [PATCH 23/58] add an event system with step listener support Introduced an `event` namespace with the ` step_t ` type and accompanying response enum. Enhanced `tween` class with `on()` for registering `step` listeners. Updated internal `step` logic to trigger callbacks and added tests to validate `step` events. --- include/tweeny/event.h | 51 ++++++++++++++++++++++++++++++++++++++++ include/tweeny/tween.h | 9 +++++++ include/tweeny/tween.tcc | 15 ++++++++++++ src/sandbox.cc | 7 +++++- tests/CMakeLists.txt | 1 + tests/events_step.cpp | 20 ++++++++++++++++ 6 files changed, 102 insertions(+), 1 deletion(-) create mode 100644 include/tweeny/event.h create mode 100644 tests/events_step.cpp diff --git a/include/tweeny/event.h b/include/tweeny/event.h new file mode 100644 index 0000000..2d5413e --- /dev/null +++ b/include/tweeny/event.h @@ -0,0 +1,51 @@ +/* +This file is part of the Tweeny library. + +Copyright (c) 2016-2025 Leonardo Guilherme Lucena de Freitas +Copyright (c) 2016 Guilherme R. Costa + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: +# +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ + +#ifndef TWEENY_EVENT_H +#define TWEENY_EVENT_H + +namespace tweeny::event { + struct sectionIn { + size_t key_frame; + explicit sectionIn(const size_t key_frame_input) : key_frame(key_frame_input) {} + }; + + struct sectionOut { + size_t key_frame; + explicit sectionOut(const size_t key_frame_input) : key_frame(key_frame_input) {} + }; + + inline struct step_t {} step; + inline struct seek_t {} seek; + inline struct jump_t {} jump; + inline struct update_t {} update; + inline struct complete_t {} complete; + + enum class response { + ok = 0, + unsubscribe = 1, + }; +} + +#endif //TWEENY_EVENT_H diff --git a/include/tweeny/tween.h b/include/tweeny/tween.h index 2d60ca2..4896bfc 100644 --- a/include/tweeny/tween.h +++ b/include/tweeny/tween.h @@ -27,9 +27,12 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. #include #include #include +#include +#include #include "detail/key-frame.h" #include "detail/tween-value.h" +#include "event.h" namespace tweeny { template @@ -47,10 +50,16 @@ namespace tweeny { auto jump(std::size_t target_key_frame) -> tween_value_t; auto step(int32_t frames) -> tween_value_t; + template + auto on(event::step_t, Callback&& cb) -> void ; + private: + using step_callback_t = std::function; + key_frames_t key_frames; uint32_t current_frame = 0; tween_value_t current_value; + std::vector step_listeners; auto render(uint32_t target_frame) -> tween_value_t; auto find_key_frame_index(uint32_t frame) -> std::size_t; diff --git a/include/tweeny/tween.tcc b/include/tweeny/tween.tcc index 9a8e801..83d068b 100644 --- a/include/tweeny/tween.tcc +++ b/include/tweeny/tween.tcc @@ -76,9 +76,24 @@ auto tweeny::tween::step(const int32_t f current_frame = target_frame; current_value = render(target_frame); + + // trigger step listeners + for (auto &cb : step_listeners) { + (void)cb(*this); + } + return current_value; } +template +template +auto tweeny::tween::on(event::step_t, Callback && cb) -> void { + using result_t = std::invoke_result_t; + static_assert(std::is_same_v, + "step callback must return tweeny::event::response"); + step_listeners.emplace_back(std::forward(cb)); +} + template auto tweeny::tween::render(uint32_t target_frame) -> tween_value_t { constexpr std::size_t ValuesCount = sizeof...(RemainingValueTypes) + 1; diff --git a/src/sandbox.cc b/src/sandbox.cc index 967ad61..5bdb594 100644 --- a/src/sandbox.cc +++ b/src/sandbox.cc @@ -1,9 +1,14 @@ #include +#include "event.h" #include "tweeny.h" int main() { - const auto builder = tweeny::from(0.0f).to(35.0f).via(tweeny::easing::linear).during(100U); + const auto builder = tweeny + ::from(0.0f) + .to(35.0f) + .via(tweeny::easing::linear) + .during(100U); auto x = builder.build(); const auto v = x.step(1); diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 97419d3..956b7ec 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -3,6 +3,7 @@ find_package(Catch2 3 REQUIRED CONFIG) add_executable(tweeny_tests sanity.cpp + events_step.cpp ) target_compile_features(tweeny_tests PRIVATE cxx_std_17) diff --git a/tests/events_step.cpp b/tests/events_step.cpp new file mode 100644 index 0000000..69e7dcb --- /dev/null +++ b/tests/events_step.cpp @@ -0,0 +1,20 @@ +#include +#include +#include + +TEST_CASE("event::step - callback is invoked on step()", "[event][step]") { + auto t = tweeny::from(0) + .to(10) + .during(10U) + .build(); + + int called = 0; + + t.on(tweeny::event::step, [&](tweeny::tween&) { + ++called; + return tweeny::event::response::ok; + }); + + (void)t.step(1); + REQUIRE(called == 1); +} From ff9a95549db433aa1685e803d6d8a91198486afc Mon Sep 17 00:00:00 2001 From: Leonardo Date: Wed, 20 Aug 2025 01:44:23 -0300 Subject: [PATCH 24/58] add `seek` and `jump` event listener support --- include/tweeny/tween.h | 12 ++++++--- include/tweeny/tween.tcc | 38 +++++++++++++++++++++++---- tests/CMakeLists.txt | 2 +- tests/events.cpp | 55 ++++++++++++++++++++++++++++++++++++++++ tests/events_step.cpp | 20 --------------- 5 files changed, 97 insertions(+), 30 deletions(-) create mode 100644 tests/events.cpp delete mode 100644 tests/events_step.cpp diff --git a/include/tweeny/tween.h b/include/tweeny/tween.h index 4896bfc..194ddc1 100644 --- a/include/tweeny/tween.h +++ b/include/tweeny/tween.h @@ -50,17 +50,21 @@ namespace tweeny { auto jump(std::size_t target_key_frame) -> tween_value_t; auto step(int32_t frames) -> tween_value_t; - template - auto on(event::step_t, Callback&& cb) -> void ; + template auto on(event::step_t, Callback&& cb) -> void ; + template auto on(event::seek_t, Callback&& cb) -> void ; + template auto on(event::jump_t, Callback&& cb) -> void ; private: - using step_callback_t = std::function; + using callback_t = std::function; key_frames_t key_frames; uint32_t current_frame = 0; tween_value_t current_value; - std::vector step_listeners; + std::vector step_listeners; + std::vector seek_listeners; + std::vector jump_listeners; + auto invoke_listeners(const std::vector& listeners) -> void; auto render(uint32_t target_frame) -> tween_value_t; auto find_key_frame_index(uint32_t frame) -> std::size_t; }; diff --git a/include/tweeny/tween.tcc b/include/tweeny/tween.tcc index 83d068b..ac3a6fb 100644 --- a/include/tweeny/tween.tcc +++ b/include/tweeny/tween.tcc @@ -51,15 +51,21 @@ template auto tweeny::tween::seek(const uint32_t target_frame) -> tween_value_t { current_frame = target_frame; current_value = render(target_frame); + + invoke_listeners(seek_listeners); + return current_value; } template auto tweeny::tween::jump(std::size_t target_key_frame) -> tween_value_t { - target_key_frame = std::clamp(target_key_frame, 0, key_frames.size() - 1); + target_key_frame = std::clamp(target_key_frame, static_cast(0), key_frames.size() - 1); const auto target_frame = static_cast(key_frames[target_key_frame].position); current_frame = target_frame; current_value = render(target_frame); + + invoke_listeners(jump_listeners); + return current_value; } @@ -77,10 +83,7 @@ auto tweeny::tween::step(const int32_t f current_frame = target_frame; current_value = render(target_frame); - // trigger step listeners - for (auto &cb : step_listeners) { - (void)cb(*this); - } + invoke_listeners(step_listeners); return current_value; } @@ -94,6 +97,31 @@ auto tweeny::tween::on(event::step_t, Ca step_listeners.emplace_back(std::forward(cb)); } +template +template +auto tweeny::tween::on(event::seek_t, Callback && cb) -> void { + using result_t = std::invoke_result_t; + static_assert(std::is_same_v, + "seek callback must return tweeny::event::response"); + seek_listeners.emplace_back(std::forward(cb)); +} + +template +template +auto tweeny::tween::on(event::jump_t, Callback && cb) -> void { + using result_t = std::invoke_result_t; + static_assert(std::is_same_v, + "jump callback must return tweeny::event::response"); + jump_listeners.emplace_back(std::forward(cb)); +} + +template +auto tweeny::tween::invoke_listeners(const std::vector& listeners) -> void { + for (const auto &cb : listeners) { + (void)cb(*this); + } +} + template auto tweeny::tween::render(uint32_t target_frame) -> tween_value_t { constexpr std::size_t ValuesCount = sizeof...(RemainingValueTypes) + 1; diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 956b7ec..a366662 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -3,7 +3,7 @@ find_package(Catch2 3 REQUIRED CONFIG) add_executable(tweeny_tests sanity.cpp - events_step.cpp + events.cpp ) target_compile_features(tweeny_tests PRIVATE cxx_std_17) diff --git a/tests/events.cpp b/tests/events.cpp new file mode 100644 index 0000000..16691f9 --- /dev/null +++ b/tests/events.cpp @@ -0,0 +1,55 @@ +#include +#include +#include + +TEST_CASE("event::seek - callback is invoked on seek()", "[event][seek]") { + auto t = tweeny::from(0) + .to(10) + .during(10U) + .build(); + + int called = 0; + + t.on(tweeny::event::seek, [&](const auto &) { + ++called; + return tweeny::event::response::ok; + }); + + (void)t.seek(5U); + REQUIRE(called == 1); +} + +TEST_CASE("event::jump - callback is invoked on jump()", "[event][jump]") { + auto t = tweeny::from(0) + .to(10) + .during(10U) + .build(); + + int called = 0; + + t.on(tweeny::event::jump, [&](const auto &) { + ++called; + return tweeny::event::response::ok; + }); + + (void)t.jump(1); + REQUIRE(called == 1); +} + + +TEST_CASE("event::step - callback is invoked on step()", "[event][step]") { + auto t = tweeny::from(0) + .to(10) + .during(10U) + .build(); + + int called = 0; + + t.on(tweeny::event::step, [&](const auto &) { + ++called; + return tweeny::event::response::ok; + }); + + (void)t.step(1); + REQUIRE(called == 1); +} diff --git a/tests/events_step.cpp b/tests/events_step.cpp deleted file mode 100644 index 69e7dcb..0000000 --- a/tests/events_step.cpp +++ /dev/null @@ -1,20 +0,0 @@ -#include -#include -#include - -TEST_CASE("event::step - callback is invoked on step()", "[event][step]") { - auto t = tweeny::from(0) - .to(10) - .during(10U) - .build(); - - int called = 0; - - t.on(tweeny::event::step, [&](tweeny::tween&) { - ++called; - return tweeny::event::response::ok; - }); - - (void)t.step(1); - REQUIRE(called == 1); -} From fcffdc263afa430d7234195ed9fa8f4d9f118ee6 Mon Sep 17 00:00:00 2001 From: Leonardo Date: Wed, 20 Aug 2025 02:21:43 -0300 Subject: [PATCH 25/58] unsubscribe support for event listeners --- include/tweeny/tween.h | 2 +- include/tweeny/tween.tcc | 19 ++++++++++++++++--- tests/events.cpp | 25 +++++++++++++++++++++++++ 3 files changed, 42 insertions(+), 4 deletions(-) diff --git a/include/tweeny/tween.h b/include/tweeny/tween.h index 194ddc1..d78c476 100644 --- a/include/tweeny/tween.h +++ b/include/tweeny/tween.h @@ -64,7 +64,7 @@ namespace tweeny { std::vector seek_listeners; std::vector jump_listeners; - auto invoke_listeners(const std::vector& listeners) -> void; + auto invoke_listeners(std::vector& listeners) -> void; auto render(uint32_t target_frame) -> tween_value_t; auto find_key_frame_index(uint32_t frame) -> std::size_t; }; diff --git a/include/tweeny/tween.tcc b/include/tweeny/tween.tcc index ac3a6fb..13bd8a0 100644 --- a/include/tweeny/tween.tcc +++ b/include/tweeny/tween.tcc @@ -116,9 +116,22 @@ auto tweeny::tween::on(event::jump_t, Ca } template -auto tweeny::tween::invoke_listeners(const std::vector& listeners) -> void { - for (const auto &cb : listeners) { - (void)cb(*this); +auto tweeny::tween::invoke_listeners(std::vector& listeners) -> void { + std::vector to_remove; + to_remove.reserve(listeners.size()); + + for (std::size_t i = 0; i < listeners.size(); ++i) { + const auto resp = listeners[i](*this); + if (resp == event::response::unsubscribe) { + to_remove.push_back(i); + } + } + + if (!to_remove.empty()) { + using diff_t = typename std::vector::difference_type; + for (auto it = to_remove.rbegin(); it != to_remove.rend(); ++it) { + listeners.erase(listeners.begin() + static_cast(*it)); + } } } diff --git a/tests/events.cpp b/tests/events.cpp index 16691f9..fb27efc 100644 --- a/tests/events.cpp +++ b/tests/events.cpp @@ -53,3 +53,28 @@ TEST_CASE("event::step - callback is invoked on step()", "[event][step]") { (void)t.step(1); REQUIRE(called == 1); } + +TEST_CASE("event::step - unsubscribe removes listener", "[event][step][unsubscribe]") { + auto t = tweeny::from(0) + .to(10) + .during(10U) + .build(); + + int called = 0; + + t.on(tweeny::event::step, [&](const auto &) { + ++called; + return tweeny::event::response::unsubscribe; + }); + + t.on(tweeny::event::step, [&](const auto &) { + ++called; + return tweeny::event::response::ok; + }); + + (void)t.step(1); + REQUIRE(called == 2); + + (void)t.step(1); + REQUIRE(called == 3); +} From 351945a5e31ffeb61b5e36c069f7a0878880e352 Mon Sep 17 00:00:00 2001 From: Leonardo Date: Fri, 22 Aug 2025 01:48:50 -0300 Subject: [PATCH 26/58] improve builder with compile-time safety, preventing `build()` without `to()` --- include/tweeny/tweeny.h | 106 ++++++++++++++++++++++++++++++---------- 1 file changed, 80 insertions(+), 26 deletions(-) diff --git a/include/tweeny/tweeny.h b/include/tweeny/tweeny.h index 1561824..3bf798a 100644 --- a/include/tweeny/tweeny.h +++ b/include/tweeny/tweeny.h @@ -27,12 +27,37 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. #include "tween.h" #include +#include #include "detail/tuple-utilities.h" namespace tweeny { + /** + * @brief Primary template for the tween builder type. + * + * Models a fluent builder used to construct tween instances for one or more parts. + * The boolean template parameter encodes the builder state: when false, no to() call + * has been made yet; when true, easing functions and durations can be configured. + * + * @tparam WasToCalled Compile-time flag indicating whether at least one to() call has been made. + * @tparam FirstValue Type of the first tweened component. + * @tparam RemainingValues Types of the remaining tweened components. + */ + template + class tweeny_builder; + template - class tweeny_builder { + /** + * @brief Builder specialization for the stage before the first to() call. + * + * Holds the initial key-frame values and allows adding the next key-frame via to(). + * Calling `to()` transitions the builder to the WasToCalled=true specialization; build() + * can be used to create a tween with the currently collected key-frames. + * + * @tparam FirstValue Type of the first tweened component. + * @tparam RemainingValues Types of the remaining tweened components. + */ + class tweeny_builder { typedef std::vector> key_frames_t; typedef tween tween_t; static size_t constexpr value_count = 1 + sizeof...(RemainingValues); @@ -42,21 +67,61 @@ namespace tweeny { key_frames.emplace_back(firstValue, remainingValues...); } - tweeny_builder & to(const FirstValue & firstValue, const RemainingValues &... remainingValues) { + explicit tweeny_builder(key_frames_t && frames) : key_frames(std::move(frames)) {} + explicit tweeny_builder(const key_frames_t & frames) : key_frames(frames) {} + + tweeny_builder to(const FirstValue & firstValue, const RemainingValues &... remainingValues) & { key_frames.emplace_back(firstValue, remainingValues...); - return *this; + return tweeny_builder(key_frames); + } + + tweeny_builder to(const FirstValue & firstValue, const RemainingValues &... remainingValues) && { + key_frames.emplace_back(firstValue, remainingValues...); + return tweeny_builder(std::move(key_frames)); + } + + private: + key_frames_t key_frames; + }; + + template + /** + * @brief Builder specialization for the stage after at least one to() call. + * + * In this stage, additional key-frames can be appended with to(), easing functions can be + * specified using via(), and per-component or uniform frame counts can be set with during(). + * Finally, build() materializes the configured tween. + * + * @tparam FirstValue Type of the first tweened component. + * @tparam RemainingValues Types of the remaining tweened components. + */ + class tweeny_builder { + typedef std::vector> key_frames_t; + typedef tween tween_t; + static size_t constexpr value_count = 1 + sizeof...(RemainingValues); + + public: + explicit tweeny_builder(FirstValue firstValue, RemainingValues... remainingValues) { + key_frames.emplace_back(firstValue, remainingValues...); + } + + explicit tweeny_builder(key_frames_t && frames) : key_frames(std::move(frames)) {} + explicit tweeny_builder(const key_frames_t & frames) : key_frames(frames) {} + + tweeny_builder to(const FirstValue & firstValue, const RemainingValues &... remainingValues) & { + key_frames.emplace_back(firstValue, remainingValues...); + return tweeny_builder(key_frames); + } + + tweeny_builder to(const FirstValue & firstValue, const RemainingValues &... remainingValues) && { + key_frames.emplace_back(firstValue, remainingValues...); + return tweeny_builder(std::move(key_frames)); } - template tweeny_builder & via(EasingFunctionTypes... easing_functions) { + template + tweeny_builder & via(EasingFunctionTypes... easing_functions) { static_assert(sizeof...(EasingFunctionTypes) == value_count, "via() must have one easing function per tween component"); - - /* HOLD. - * the intention here is to access the “previous” key frame - * after you’ve just pushed a new one with to(...), via(...) configures the easing - * for the segment that starts at the previous frame. - * That is why you see -2 - */ auto & key_frame = key_frames.at(key_frames.size() - 2); key_frame.easing_functions = std::make_tuple(easing_functions...); return *this; @@ -73,17 +138,14 @@ namespace tweeny { tweeny_builder & during(FrameCountsType... frame_counts) { static_assert(sizeof...(FrameCountsType) == value_count, "during() must have one frame count per tween component"); - static_assert((std::is_same_v, uint32_t> && ...), "during() parameters must be of type uint32_t"); - auto & key_frame = key_frames.at(key_frames.size() - 2); std::array frame_counts_array = { frame_counts... }; std::copy( std::begin(frame_counts_array), std::end(frame_counts_array), std::begin(key_frame.tween_frame_counts)); - fix_frame_positions(); return *this; } @@ -95,18 +157,12 @@ namespace tweeny { std::end(key_frame.tween_frame_counts), frame_count ); - fix_frame_positions(); return *this; } - tween_t build() const & { - return tween(key_frames); - } - - tween_t build() && { - return tween(std::move(key_frames)); - } + tween_t build() const & { return tween(key_frames); } + tween_t build() && { return tween(std::move(key_frames)); } private: key_frames_t key_frames; @@ -120,11 +176,9 @@ namespace tweeny { }; template - tweeny_builder from(FirstValue first_value, RemainingValues... remaining_values) { - return tweeny_builder(first_value, remaining_values...); + tweeny_builder from(FirstValue first_value, RemainingValues... remaining_values) { + return tweeny_builder(first_value, remaining_values...); } } -#include "easing/easing.h" - #endif //TWEENY_TWEENY_H From b964ef8de75bf455cd2c3daf62e00e0c68cd459c Mon Sep 17 00:00:00 2001 From: Leonardo Date: Fri, 22 Aug 2025 01:57:19 -0300 Subject: [PATCH 27/58] update tween constructor and reorder frame updates --- include/tweeny/tween.h | 2 +- include/tweeny/tween.tcc | 13 +++++-------- 2 files changed, 6 insertions(+), 9 deletions(-) diff --git a/include/tweeny/tween.h b/include/tweeny/tween.h index d78c476..8ceb29b 100644 --- a/include/tweeny/tween.h +++ b/include/tweeny/tween.h @@ -44,7 +44,7 @@ namespace tweeny { using tween_value_t = detail::tween_value_t; explicit tween(const key_frames_t & key_frames_input); - explicit tween(key_frames_t & key_frames_input); + explicit tween(key_frames_t && key_frames_input); auto seek(uint32_t target_frame) -> tween_value_t; auto jump(std::size_t target_key_frame) -> tween_value_t; diff --git a/include/tweeny/tween.tcc b/include/tweeny/tween.tcc index 13bd8a0..197bf90 100644 --- a/include/tweeny/tween.tcc +++ b/include/tweeny/tween.tcc @@ -34,10 +34,10 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. #include "tweeny/easing/easing.h" template -tweeny::tween::tween(const key_frames_t & key_frames_input) : key_frames(key_frames_input) { } +tweeny::tween::tween(const key_frames_t & key_frames_input) : key_frames(key_frames_input), current_value(render(0)) { } template -tweeny::tween::tween(key_frames_t & key_frames_input) : key_frames(std::move(key_frames_input)) { } +tweeny::tween::tween(key_frames_t && key_frames_input) : key_frames(std::move(key_frames_input)), current_value(render(0)) { } template auto tweeny::tween::find_key_frame_index(uint32_t frame) -> size_t { @@ -49,8 +49,8 @@ auto tweeny::tween::find_key_frame_index template auto tweeny::tween::seek(const uint32_t target_frame) -> tween_value_t { - current_frame = target_frame; current_value = render(target_frame); + current_frame = target_frame; invoke_listeners(seek_listeners); @@ -61,8 +61,8 @@ template auto tweeny::tween::jump(std::size_t target_key_frame) -> tween_value_t { target_key_frame = std::clamp(target_key_frame, static_cast(0), key_frames.size() - 1); const auto target_frame = static_cast(key_frames[target_key_frame].position); - current_frame = target_frame; current_value = render(target_frame); + current_frame = target_frame; invoke_listeners(jump_listeners); @@ -80,8 +80,8 @@ auto tweeny::tween::step(const int32_t f target_frame += static_cast(frames); } - current_frame = target_frame; current_value = render(target_frame); + current_frame = target_frame; invoke_listeners(step_listeners); @@ -160,9 +160,6 @@ auto tweeny::tween::render(uint32_t targ auto & first_key_frame = key_frames.front(); auto & last_key_frame = key_frames.back(); - // note to future self: we don't need to add the frame count - // to the last_key_frame.position because there's no interpolation - // beyond it (duh) target_frame = std::clamp( target_frame, first_key_frame.position, From 6ae52772274c578121bb509f23d1bb3274cd9528 Mon Sep 17 00:00:00 2001 From: Leonardo Date: Thu, 25 Dec 2025 16:35:09 -0300 Subject: [PATCH 28/58] clean up redundant line and correct spacing in headers --- include/tweeny/event.h | 2 +- include/tweeny/tween.h | 2 +- tests/sanity.cpp | 1 - 3 files changed, 2 insertions(+), 3 deletions(-) diff --git a/include/tweeny/event.h b/include/tweeny/event.h index 2d5413e..dff693c 100644 --- a/include/tweeny/event.h +++ b/include/tweeny/event.h @@ -10,7 +10,7 @@ the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: -# + The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. diff --git a/include/tweeny/tween.h b/include/tweeny/tween.h index 8ceb29b..6692178 100644 --- a/include/tweeny/tween.h +++ b/include/tweeny/tween.h @@ -10,7 +10,7 @@ the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: -# + The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. diff --git a/tests/sanity.cpp b/tests/sanity.cpp index ff99846..a9856ca 100644 --- a/tests/sanity.cpp +++ b/tests/sanity.cpp @@ -1,5 +1,4 @@ #include -#include TEST_CASE("sanity - the test framework runs", "[sanity]") { REQUIRE(1 + 1 == 2); From 70572f117793bdacc2b0f2cc42f61cef91d81de5 Mon Sep 17 00:00:00 2001 From: Leonardo Date: Thu, 25 Dec 2025 16:48:29 -0300 Subject: [PATCH 29/58] add `peek` and `progress` methods to `tween` --- include/tweeny/detail/interpolate.h | 31 +++----- include/tweeny/tween.h | 7 +- include/tweeny/tween.tcc | 91 ++++++++++++++++++++++- src/sandbox.cc | 40 +++++++--- tests/CMakeLists.txt | 3 +- tests/peek_and_progress.cpp | 110 ++++++++++++++++++++++++++++ 6 files changed, 245 insertions(+), 37 deletions(-) create mode 100644 tests/peek_and_progress.cpp diff --git a/include/tweeny/detail/interpolate.h b/include/tweeny/detail/interpolate.h index dc4a981..be3d38c 100644 --- a/include/tweeny/detail/interpolate.h +++ b/include/tweeny/detail/interpolate.h @@ -32,11 +32,11 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. #include "easing/easing.h" namespace tweeny::detail { - template + template static auto interpolate_one( float t, - const key_frame & base, - const key_frame & next + const key_frame & base, + const key_frame & next ) -> std::remove_reference_t(base.values))> { const auto & start = std::get(base.values); const auto & end = std::get(next.values); @@ -45,29 +45,16 @@ namespace tweeny::detail { return easing::def(t, start, end); } - template - static auto interpolate_one( - float t, - const key_frame & base, - const key_frame & next - ) -> std::remove_reference_t(base.values))> { - const auto & start = std::get(base.values); - const auto & end = std::get(next.values); - const auto & func = std::get(base.easing_functions); - if (func) return func(t, start, end); - return easing::def(t, start, end); - } - - template + template static auto interpolate_values( float t, - const key_frame & base, - const key_frame & next, + const key_frame & base, + const key_frame & next, std::index_sequence - ) -> typename key_frame::values_t { - using values_t = typename key_frame::values_t; + ) -> typename key_frame::values_t { + using values_t = typename key_frame::values_t; values_t out{}; - ((std::get(out) = interpolate_one(t, base, next)), ...); + ((std::get(out) = interpolate_one(t, base, next)), ...); return out; } } diff --git a/include/tweeny/tween.h b/include/tweeny/tween.h index 6692178..8b3fc69 100644 --- a/include/tweeny/tween.h +++ b/include/tweeny/tween.h @@ -50,6 +50,10 @@ namespace tweeny { auto jump(std::size_t target_key_frame) -> tween_value_t; auto step(int32_t frames) -> tween_value_t; + auto peek() const -> tween_value_t; + auto peek(uint32_t target_frame) const -> tween_value_t; + auto progress() const -> float; + template auto on(event::step_t, Callback&& cb) -> void ; template auto on(event::seek_t, Callback&& cb) -> void ; template auto on(event::jump_t, Callback&& cb) -> void ; @@ -66,7 +70,8 @@ namespace tweeny { auto invoke_listeners(std::vector& listeners) -> void; auto render(uint32_t target_frame) -> tween_value_t; - auto find_key_frame_index(uint32_t frame) -> std::size_t; + auto render(uint32_t target_frame) const -> tween_value_t; + auto find_key_frame_index(uint32_t frame) const -> std::size_t; }; } diff --git a/include/tweeny/tween.tcc b/include/tweeny/tween.tcc index 197bf90..90c35b4 100644 --- a/include/tweeny/tween.tcc +++ b/include/tweeny/tween.tcc @@ -40,7 +40,7 @@ template tweeny::tween::tween(key_frames_t && key_frames_input) : key_frames(std::move(key_frames_input)), current_value(render(0)) { } template -auto tweeny::tween::find_key_frame_index(uint32_t frame) -> size_t { +auto tweeny::tween::find_key_frame_index(uint32_t frame) const -> size_t { std::size_t i = 0; while (i + 1 < key_frames.size() && frame >= key_frames[i + 1].position) { ++i; } return i; @@ -139,8 +139,6 @@ template auto tweeny::tween::render(uint32_t target_frame) -> tween_value_t { constexpr std::size_t ValuesCount = sizeof...(RemainingValueTypes) + 1; - if (target_frame == current_frame) return current_value; - const auto as_return_value = [](const auto & val) -> tween_value_t { if constexpr (ValuesCount == 1) { return std::get<0>(val); @@ -200,4 +198,91 @@ auto tweeny::tween::render(uint32_t targ return as_return_value(values); } +template +auto tweeny::tween::render(uint32_t target_frame) const -> tween_value_t { + constexpr std::size_t ValuesCount = sizeof...(RemainingValueTypes) + 1; + + const auto as_return_value = [](const auto & val) -> tween_value_t { + if constexpr (ValuesCount == 1) { + return std::get<0>(val); + } else { + return val; + } + }; + + if (key_frames.empty()) { + if constexpr (ValuesCount == 1) { + return FirstValueType{}; + } else { + return typename key_frame_t::values_t{}; + } + } + + const auto & first_key_frame = key_frames.front(); + const auto & last_key_frame = key_frames.back(); + + target_frame = std::clamp( + target_frame, + first_key_frame.position, + last_key_frame.position + ); + + if (target_frame <= first_key_frame.position) return as_return_value(first_key_frame.values); + if (target_frame >= last_key_frame.position) return as_return_value(last_key_frame.values); + + std::size_t base_key_key_frame_idx = find_key_frame_index(target_frame); + + if (base_key_key_frame_idx + 1 >= key_frames.size()) { + return as_return_value(last_key_frame.values); + } + + const key_frame_t & base_key_frame = key_frames[base_key_key_frame_idx]; + const key_frame_t & next_key_frame = key_frames[base_key_key_frame_idx + 1]; + + const int64_t base_kf_position = base_key_frame.position; + const uint32_t target_kf_position = next_key_frame.position; + const int64_t target_frame_i64 = target_frame; + + float inbetween_progress = 1.0f; + if (target_kf_position > base_kf_position) { + const auto numerator = static_cast(target_frame_i64 - base_kf_position); + const auto denominator = static_cast(target_kf_position - base_kf_position); + inbetween_progress = numerator / denominator; + } + inbetween_progress = std::clamp(inbetween_progress, 0.0f, 1.0f); + + auto values = detail::interpolate_values( + inbetween_progress, + base_key_frame, + next_key_frame, + std::make_index_sequence{} + ); + + return as_return_value(values); +} + +template +auto tweeny::tween::peek() const -> tween_value_t { + return current_value; +} + +template +auto tweeny::tween::peek(uint32_t target_frame) const -> tween_value_t { + return render(target_frame); +} + +template +auto tweeny::tween::progress() const -> float { + if (key_frames.empty()) return 0.0f; + + const auto & first_key_frame = key_frames.front(); + const auto & last_key_frame = key_frames.back(); + + const uint32_t total_frames = last_key_frame.position - first_key_frame.position; + if (total_frames == 0) return 1.0f; + + const uint32_t current_offset = current_frame - first_key_frame.position; + return std::clamp(static_cast(current_offset) / static_cast(total_frames), 0.0f, 1.0f); +} + #endif //TWEENY_TWEEN_TCC diff --git a/src/sandbox.cc b/src/sandbox.cc index 5bdb594..d2e34ee 100644 --- a/src/sandbox.cc +++ b/src/sandbox.cc @@ -1,19 +1,39 @@ #include - -#include "event.h" #include "tweeny.h" int main() { - const auto builder = tweeny - ::from(0.0f) - .to(35.0f) - .via(tweeny::easing::linear) - .during(100U); - auto x = builder.build(); + // Create a tween from 0 to 100 over 100 frames + auto tween = tweeny::from(0.0f) + .to(100.0f) + .via(tweeny::easing::linear) + .during(100U) + .build(); + + // Demonstrate peek() - query without mutating + printf("Initial value (peek): %f\n", static_cast(tween.peek())); + printf("Progress: %f%%\n", static_cast(tween.progress() * 100.0f)); + + // Peek at frame 50 without changing state + printf("Value at frame 50 (peek): %f\n", static_cast(tween.peek(50U))); + printf("Still at start, progress: %f%%\n", static_cast(tween.progress() * 100.0f)); + + // Step forward + tween.step(25); + printf("\nAfter stepping 25 frames:\n"); + printf("Current value: %f\n", static_cast(tween.peek())); + printf("Progress: %f%%\n", static_cast(tween.progress() * 100.0f)); - const auto v = x.step(1); + // Seek to specific frame + tween.seek(75U); + printf("\nAfter seeking to frame 75:\n"); + printf("Current value: %f\n", static_cast(tween.peek())); + printf("Progress: %f%%\n", static_cast(tween.progress() * 100.0f)); - printf("%f\n", static_cast(v)); + // Complete the tween + tween.seek(100U); + printf("\nAt end:\n"); + printf("Final value: %f\n", static_cast(tween.peek())); + printf("Progress: %f%%\n", static_cast(tween.progress() * 100.0f)); return 0; } diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index a366662..b581a69 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -3,7 +3,8 @@ find_package(Catch2 3 REQUIRED CONFIG) add_executable(tweeny_tests sanity.cpp - events.cpp + events.cpp + peek_and_progress.cpp ) target_compile_features(tweeny_tests PRIVATE cxx_std_17) diff --git a/tests/peek_and_progress.cpp b/tests/peek_and_progress.cpp new file mode 100644 index 0000000..e1ffa12 --- /dev/null +++ b/tests/peek_and_progress.cpp @@ -0,0 +1,110 @@ +#include +#include +#include + +TEST_CASE("peek() - returns current value without mutation", "[peek]") { + auto t = tweeny::from(0) + .to(100) + .during(100U) + .build(); + + REQUIRE(t.peek() == 0); + t.step(50); + REQUIRE(t.peek() == 50); +} + +TEST_CASE("peek(frame) - queries value at arbitrary frame without mutation", "[peek]") { + auto t = tweeny::from(0) + .to(100) + .during(100U) + .build(); + + // Should not change current position + REQUIRE(t.peek(50U) == 50); + REQUIRE(t.peek() == 0); // Still at start + REQUIRE(t.progress() == Catch::Approx(0.0f)); +} + +TEST_CASE("peek(frame) - multi-value tween", "[peek]") { + auto t = tweeny::from(0, 0.0f) + .to(100, 100.0f) + .during(100U) + .build(); + + auto result = t.peek(50U); + REQUIRE(std::get<0>(result) == 50); + REQUIRE(std::get<1>(result) == Catch::Approx(50.0f)); +} + +TEST_CASE("progress() - returns 0.0 at start", "[progress]") { + auto t = tweeny::from(0) + .to(100) + .during(100U) + .build(); + + REQUIRE(t.progress() == Catch::Approx(0.0f)); +} + +TEST_CASE("progress() - returns 1.0 at end", "[progress]") { + auto t = tweeny::from(0) + .to(100) + .during(100U) + .build(); + + t.seek(100U); + REQUIRE(t.progress() == Catch::Approx(1.0f)); +} + +TEST_CASE("progress() - returns 0.5 at midpoint", "[progress]") { + auto t = tweeny::from(0) + .to(100) + .during(100U) + .build(); + + t.step(50); + REQUIRE(t.progress() == Catch::Approx(0.5f)); +} + +TEST_CASE("progress() - multi-point tween", "[progress]") { + auto t = tweeny::from(0) + .to(50).during(50U) + .to(100).during(50U) + .build(); + + REQUIRE(t.progress() == Catch::Approx(0.0f)); + + t.seek(25U); + REQUIRE(t.progress() == Catch::Approx(0.25f)); + + t.seek(50U); + REQUIRE(t.progress() == Catch::Approx(0.5f)); + + t.seek(75U); + REQUIRE(t.progress() == Catch::Approx(0.75f)); + + t.seek(100U); + REQUIRE(t.progress() == Catch::Approx(1.0f)); +} + +TEST_CASE("progress() - handles empty keyframes", "[progress]") { + // Edge case: this shouldn't happen in practice but let's be defensive + auto t = tweeny::from(0) + .to(100) + .during(0U) + .build(); + + REQUIRE(t.progress() == Catch::Approx(1.0f)); +} + +TEST_CASE("peek() and progress() - consistent after stepping", "[peek][progress]") { + auto t = tweeny::from(0) + .to(100) + .during(100U) + .build(); + + for (int i = 0; i <= 100; i += 10) { + t.seek(static_cast(i)); + REQUIRE(t.peek() == i); + REQUIRE(t.progress() == Catch::Approx(static_cast(i) / 100.0f)); + } +} From 6623fc90fe26573fb5cda9fb082c076528805fa7 Mon Sep 17 00:00:00 2001 From: Leonardo Date: Thu, 25 Dec 2025 16:55:52 -0300 Subject: [PATCH 30/58] move easing files to detail namespace --- include/tweeny/{easing => detail}/easing.h | 34 +++++++++---------- include/tweeny/{ => detail}/easing/back.h | 6 ++-- include/tweeny/{ => detail}/easing/bounce.h | 6 ++-- include/tweeny/{ => detail}/easing/circular.h | 6 ++-- include/tweeny/{ => detail}/easing/cubic.h | 6 ++-- include/tweeny/{ => detail}/easing/def.h | 6 ++-- include/tweeny/{ => detail}/easing/elastic.h | 6 ++-- .../tweeny/{ => detail}/easing/exponential.h | 6 ++-- include/tweeny/{ => detail}/easing/linear.h | 6 ++-- .../tweeny/{ => detail}/easing/quadratic.h | 6 ++-- include/tweeny/{ => detail}/easing/quartic.h | 6 ++-- include/tweeny/{ => detail}/easing/quintic.h | 6 ++-- .../tweeny/{ => detail}/easing/sinusoidal.h | 6 ++-- include/tweeny/{ => detail}/easing/stepped.h | 6 ++-- include/tweeny/detail/interpolate.h | 2 +- include/tweeny/detail/key-frame.h | 6 ++-- include/tweeny/detail/tuple-utilities.h | 6 ++-- include/tweeny/detail/tween-value.h | 6 ++-- include/tweeny/detail/value-container.h | 6 ++-- include/tweeny/event.h | 2 ++ include/tweeny/tween.h | 10 +++--- include/tweeny/tween.tcc | 2 +- 22 files changed, 77 insertions(+), 75 deletions(-) rename include/tweeny/{easing => detail}/easing.h (65%) rename include/tweeny/{ => detail}/easing/back.h (96%) rename include/tweeny/{ => detail}/easing/bounce.h (96%) rename include/tweeny/{ => detail}/easing/circular.h (95%) rename include/tweeny/{ => detail}/easing/cubic.h (95%) rename include/tweeny/{ => detail}/easing/def.h (96%) rename include/tweeny/{ => detail}/easing/elastic.h (96%) rename include/tweeny/{ => detail}/easing/exponential.h (95%) rename include/tweeny/{ => detail}/easing/linear.h (94%) rename include/tweeny/{ => detail}/easing/quadratic.h (95%) rename include/tweeny/{ => detail}/easing/quartic.h (95%) rename include/tweeny/{ => detail}/easing/quintic.h (95%) rename include/tweeny/{ => detail}/easing/sinusoidal.h (95%) rename include/tweeny/{ => detail}/easing/stepped.h (92%) diff --git a/include/tweeny/easing/easing.h b/include/tweeny/detail/easing.h similarity index 65% rename from include/tweeny/easing/easing.h rename to include/tweeny/detail/easing.h index ac6ca10..2b45261 100644 --- a/include/tweeny/easing/easing.h +++ b/include/tweeny/detail/easing.h @@ -25,25 +25,25 @@ /** * @file easing.h * The purpose of this file is to list all bundled easings. Each easing is defined - * in its own header under include/tweeny/easing/. Users may include individual + * in its own header under include/tweeny/detail/easing/. Users may include individual * easing headers or this header to get all easings. */ -#ifndef TWEENY_EASING_H -#define TWEENY_EASING_H +#ifndef TWEENY_DETAIL_EASING_H +#define TWEENY_DETAIL_EASING_H -#include "tweeny/easing/stepped.h" -#include "tweeny/easing/def.h" -#include "tweeny/easing/linear.h" -#include "tweeny/easing/quadratic.h" -#include "tweeny/easing/cubic.h" -#include "tweeny/easing/quartic.h" -#include "tweeny/easing/quintic.h" -#include "tweeny/easing/sinusoidal.h" -#include "tweeny/easing/exponential.h" -#include "tweeny/easing/circular.h" -#include "tweeny/easing/bounce.h" -#include "tweeny/easing/elastic.h" -#include "tweeny/easing/back.h" +#include "tweeny/detail/easing/stepped.h" +#include "tweeny/detail/easing/def.h" +#include "tweeny/detail/easing/linear.h" +#include "tweeny/detail/easing/quadratic.h" +#include "tweeny/detail/easing/cubic.h" +#include "tweeny/detail/easing/quartic.h" +#include "tweeny/detail/easing/quintic.h" +#include "tweeny/detail/easing/sinusoidal.h" +#include "tweeny/detail/easing/exponential.h" +#include "tweeny/detail/easing/circular.h" +#include "tweeny/detail/easing/bounce.h" +#include "tweeny/detail/easing/elastic.h" +#include "tweeny/detail/easing/back.h" -#endif //TWEENY_EASING_H +#endif //TWEENY_DETAIL_EASING_H diff --git a/include/tweeny/easing/back.h b/include/tweeny/detail/easing/back.h similarity index 96% rename from include/tweeny/easing/back.h rename to include/tweeny/detail/easing/back.h index 539be58..84356c5 100644 --- a/include/tweeny/easing/back.h +++ b/include/tweeny/detail/easing/back.h @@ -22,8 +22,8 @@ IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -#ifndef TWEENY_EASING_BACK_H -#define TWEENY_EASING_BACK_H +#ifndef TWEENY_DETAIL_EASING_BACK_H +#define TWEENY_DETAIL_EASING_BACK_H namespace tweeny::detail { struct backInEasing { @@ -81,4 +81,4 @@ namespace tweeny::easing { inline constexpr detail::backInOutEasing backInOut{}; } -#endif // TWEENY_EASING_BACK_H +#endif // TWEENY_DETAIL_EASING_BACK_H diff --git a/include/tweeny/easing/bounce.h b/include/tweeny/detail/easing/bounce.h similarity index 96% rename from include/tweeny/easing/bounce.h rename to include/tweeny/detail/easing/bounce.h index fc9e210..4bea4ed 100644 --- a/include/tweeny/easing/bounce.h +++ b/include/tweeny/detail/easing/bounce.h @@ -22,8 +22,8 @@ IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -#ifndef TWEENY_EASING_BOUNCE_H -#define TWEENY_EASING_BOUNCE_H +#ifndef TWEENY_DETAIL_EASING_BOUNCE_H +#define TWEENY_DETAIL_EASING_BOUNCE_H namespace tweeny::detail { struct bounceOutEasing { @@ -84,4 +84,4 @@ namespace tweeny::easing { inline constexpr detail::bounceInOutEasing bounceInOut{}; } -#endif // TWEENY_EASING_BOUNCE_H +#endif // TWEENY_DETAIL_EASING_BOUNCE_H diff --git a/include/tweeny/easing/circular.h b/include/tweeny/detail/easing/circular.h similarity index 95% rename from include/tweeny/easing/circular.h rename to include/tweeny/detail/easing/circular.h index 21141a0..5ae1e5a 100644 --- a/include/tweeny/easing/circular.h +++ b/include/tweeny/detail/easing/circular.h @@ -22,8 +22,8 @@ IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -#ifndef TWEENY_EASING_CIRCULAR_H -#define TWEENY_EASING_CIRCULAR_H +#ifndef TWEENY_DETAIL_EASING_CIRCULAR_H +#define TWEENY_DETAIL_EASING_CIRCULAR_H #include @@ -78,4 +78,4 @@ namespace tweeny::easing { inline constexpr detail::circularInOutEasing circularInOut{}; } -#endif // TWEENY_EASING_CIRCULAR_H +#endif // TWEENY_DETAIL_EASING_CIRCULAR_H diff --git a/include/tweeny/easing/cubic.h b/include/tweeny/detail/easing/cubic.h similarity index 95% rename from include/tweeny/easing/cubic.h rename to include/tweeny/detail/easing/cubic.h index a3cfb6c..948a2d2 100644 --- a/include/tweeny/easing/cubic.h +++ b/include/tweeny/detail/easing/cubic.h @@ -22,8 +22,8 @@ IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -#ifndef TWEENY_EASING_CUBIC_H -#define TWEENY_EASING_CUBIC_H +#ifndef TWEENY_DETAIL_EASING_CUBIC_H +#define TWEENY_DETAIL_EASING_CUBIC_H namespace tweeny::detail { struct cubicInEasing { @@ -75,4 +75,4 @@ namespace tweeny::easing { inline constexpr detail::cubicInOutEasing cubicInOut{}; } -#endif // TWEENY_EASING_CUBIC_H +#endif // TWEENY_DETAIL_EASING_CUBIC_H diff --git a/include/tweeny/easing/def.h b/include/tweeny/detail/easing/def.h similarity index 96% rename from include/tweeny/easing/def.h rename to include/tweeny/detail/easing/def.h index 46cebb0..c5388de 100644 --- a/include/tweeny/easing/def.h +++ b/include/tweeny/detail/easing/def.h @@ -22,8 +22,8 @@ IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -#ifndef TWEENY_EASING_DEF_H -#define TWEENY_EASING_DEF_H +#ifndef TWEENY_DETAIL_EASING_DEF_H +#define TWEENY_DETAIL_EASING_DEF_H #include #include @@ -76,4 +76,4 @@ namespace tweeny::easing { inline constexpr detail::defaultEasing def{}; } -#endif // TWEENY_EASING_DEF_H +#endif // TWEENY_DETAIL_EASING_DEF_H diff --git a/include/tweeny/easing/elastic.h b/include/tweeny/detail/easing/elastic.h similarity index 96% rename from include/tweeny/easing/elastic.h rename to include/tweeny/detail/easing/elastic.h index 1b0ec6b..9f38810 100644 --- a/include/tweeny/easing/elastic.h +++ b/include/tweeny/detail/easing/elastic.h @@ -22,8 +22,8 @@ IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -#ifndef TWEENY_EASING_ELASTIC_H -#define TWEENY_EASING_ELASTIC_H +#ifndef TWEENY_DETAIL_EASING_ELASTIC_H +#define TWEENY_DETAIL_EASING_ELASTIC_H #include @@ -101,4 +101,4 @@ namespace tweeny::easing { inline constexpr detail::elasticInOutEasing elasticInOut{}; } -#endif // TWEENY_EASING_ELASTIC_H +#endif // TWEENY_DETAIL_EASING_ELASTIC_H diff --git a/include/tweeny/easing/exponential.h b/include/tweeny/detail/easing/exponential.h similarity index 95% rename from include/tweeny/easing/exponential.h rename to include/tweeny/detail/easing/exponential.h index 8ca67a1..6ab05db 100644 --- a/include/tweeny/easing/exponential.h +++ b/include/tweeny/detail/easing/exponential.h @@ -22,8 +22,8 @@ IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -#ifndef TWEENY_EASING_EXPONENTIAL_H -#define TWEENY_EASING_EXPONENTIAL_H +#ifndef TWEENY_DETAIL_EASING_EXPONENTIAL_H +#define TWEENY_DETAIL_EASING_EXPONENTIAL_H #include @@ -76,4 +76,4 @@ namespace tweeny::easing { inline constexpr detail::exponentialInOutEasing exponentialInOut{}; } -#endif // TWEENY_EASING_EXPONENTIAL_H +#endif // TWEENY_DETAIL_EASING_EXPONENTIAL_H diff --git a/include/tweeny/easing/linear.h b/include/tweeny/detail/easing/linear.h similarity index 94% rename from include/tweeny/easing/linear.h rename to include/tweeny/detail/easing/linear.h index 8b8a4f8..5a71971 100644 --- a/include/tweeny/easing/linear.h +++ b/include/tweeny/detail/easing/linear.h @@ -22,8 +22,8 @@ IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -#ifndef TWEENY_EASING_LINEAR_H -#define TWEENY_EASING_LINEAR_H +#ifndef TWEENY_DETAIL_EASING_LINEAR_H +#define TWEENY_DETAIL_EASING_LINEAR_H #include #include @@ -51,4 +51,4 @@ namespace tweeny::easing { inline constexpr detail::linearEasing linear{}; } -#endif // TWEENY_EASING_LINEAR_H +#endif // TWEENY_DETAIL_EASING_LINEAR_H diff --git a/include/tweeny/easing/quadratic.h b/include/tweeny/detail/easing/quadratic.h similarity index 95% rename from include/tweeny/easing/quadratic.h rename to include/tweeny/detail/easing/quadratic.h index 082ddf0..46719f8 100644 --- a/include/tweeny/easing/quadratic.h +++ b/include/tweeny/detail/easing/quadratic.h @@ -22,8 +22,8 @@ IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -#ifndef TWEENY_EASING_QUADRATIC_H -#define TWEENY_EASING_QUADRATIC_H +#ifndef TWEENY_DETAIL_EASING_QUADRATIC_H +#define TWEENY_DETAIL_EASING_QUADRATIC_H namespace tweeny::detail { struct quadraticInEasing { @@ -74,4 +74,4 @@ namespace tweeny::easing { inline constexpr detail::quadraticOutEasing quadraticOut{}; inline constexpr detail::quadraticInOutEasing quadraticInOut{}; } -#endif // TWEENY_EASING_QUADRATIC_H +#endif // TWEENY_DETAIL_EASING_QUADRATIC_H diff --git a/include/tweeny/easing/quartic.h b/include/tweeny/detail/easing/quartic.h similarity index 95% rename from include/tweeny/easing/quartic.h rename to include/tweeny/detail/easing/quartic.h index 99a60f6..9dfb100 100644 --- a/include/tweeny/easing/quartic.h +++ b/include/tweeny/detail/easing/quartic.h @@ -22,8 +22,8 @@ IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -#ifndef TWEENY_EASING_QUARTIC_H -#define TWEENY_EASING_QUARTIC_H +#ifndef TWEENY_DETAIL_EASING_QUARTIC_H +#define TWEENY_DETAIL_EASING_QUARTIC_H namespace tweeny::detail { struct quarticInEasing { @@ -75,4 +75,4 @@ namespace tweeny::easing { inline constexpr detail::quarticInOutEasing quarticInOut{}; } -#endif // TWEENY_EASING_QUARTIC_H +#endif // TWEENY_DETAIL_EASING_QUARTIC_H diff --git a/include/tweeny/easing/quintic.h b/include/tweeny/detail/easing/quintic.h similarity index 95% rename from include/tweeny/easing/quintic.h rename to include/tweeny/detail/easing/quintic.h index b10bc5d..0dd2f44 100644 --- a/include/tweeny/easing/quintic.h +++ b/include/tweeny/detail/easing/quintic.h @@ -22,8 +22,8 @@ IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -#ifndef TWEENY_EASING_QUINTIC_H -#define TWEENY_EASING_QUINTIC_H +#ifndef TWEENY_DETAIL_EASING_QUINTIC_H +#define TWEENY_DETAIL_EASING_QUINTIC_H namespace tweeny::detail { struct quinticInEasing { @@ -75,4 +75,4 @@ namespace tweeny::easing { inline constexpr detail::quinticInOutEasing quinticInOut{}; } -#endif // TWEENY_EASING_QUINTIC_H +#endif // TWEENY_DETAIL_EASING_QUINTIC_H diff --git a/include/tweeny/easing/sinusoidal.h b/include/tweeny/detail/easing/sinusoidal.h similarity index 95% rename from include/tweeny/easing/sinusoidal.h rename to include/tweeny/detail/easing/sinusoidal.h index 65fea5c..7497f59 100644 --- a/include/tweeny/easing/sinusoidal.h +++ b/include/tweeny/detail/easing/sinusoidal.h @@ -22,8 +22,8 @@ IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -#ifndef TWEENY_EASING_SINUSOIDAL_H -#define TWEENY_EASING_SINUSOIDAL_H +#ifndef TWEENY_DETAIL_EASING_SINUSOIDAL_H +#define TWEENY_DETAIL_EASING_SINUSOIDAL_H #include @@ -75,4 +75,4 @@ namespace tweeny::easing { inline constexpr detail::sinusoidalInOutEasing sinusoidalInOut{}; } -#endif // TWEENY_EASING_SINUSOIDAL_H +#endif // TWEENY_DETAIL_EASING_SINUSOIDAL_H diff --git a/include/tweeny/easing/stepped.h b/include/tweeny/detail/easing/stepped.h similarity index 92% rename from include/tweeny/easing/stepped.h rename to include/tweeny/detail/easing/stepped.h index 9e5722b..f99aca0 100644 --- a/include/tweeny/easing/stepped.h +++ b/include/tweeny/detail/easing/stepped.h @@ -22,8 +22,8 @@ IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -#ifndef TWEENY_EASING_STEPPED_H -#define TWEENY_EASING_STEPPED_H +#ifndef TWEENY_DETAIL_EASING_STEPPED_H +#define TWEENY_DETAIL_EASING_STEPPED_H namespace tweeny::detail { struct steppedEasing { @@ -43,4 +43,4 @@ namespace tweeny::easing { inline constexpr detail::steppedEasing stepped{}; } -#endif // TWEENY_EASING_STEPPED_H +#endif // TWEENY_DETAIL_EASING_STEPPED_H diff --git a/include/tweeny/detail/interpolate.h b/include/tweeny/detail/interpolate.h index be3d38c..cdd9877 100644 --- a/include/tweeny/detail/interpolate.h +++ b/include/tweeny/detail/interpolate.h @@ -29,7 +29,7 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. #include #include "key-frame.h" -#include "easing/easing.h" +#include "easing.h" namespace tweeny::detail { template diff --git a/include/tweeny/detail/key-frame.h b/include/tweeny/detail/key-frame.h index c45aba9..9b0a1b8 100644 --- a/include/tweeny/detail/key-frame.h +++ b/include/tweeny/detail/key-frame.h @@ -22,8 +22,8 @@ IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -#ifndef TWEENY_KEY_FRAME_H -#define TWEENY_KEY_FRAME_H +#ifndef TWEENY_DETAIL_KEY_FRAME_H +#define TWEENY_DETAIL_KEY_FRAME_H #include @@ -128,4 +128,4 @@ namespace tweeny::detail { }; } -#endif //TWEENY_KEY_FRAME_H +#endif //TWEENY_DETAIL_KEY_FRAME_H diff --git a/include/tweeny/detail/tuple-utilities.h b/include/tweeny/detail/tuple-utilities.h index c1da2d6..52f06a0 100644 --- a/include/tweeny/detail/tuple-utilities.h +++ b/include/tweeny/detail/tuple-utilities.h @@ -22,8 +22,8 @@ IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -#ifndef TWEENY_TUPLE_UTILITIES_H -#define TWEENY_TUPLE_UTILITIES_H +#ifndef TWEENY_DETAIL_TUPLE_UTILITIES_H +#define TWEENY_DETAIL_TUPLE_UTILITIES_H #include #include @@ -58,4 +58,4 @@ namespace tweeny::detail { } } -#endif //TWEENY_TUPLE_UTILITIES_H +#endif //TWEENY_DETAIL_TUPLE_UTILITIES_H diff --git a/include/tweeny/detail/tween-value.h b/include/tweeny/detail/tween-value.h index adde589..57af111 100644 --- a/include/tweeny/detail/tween-value.h +++ b/include/tweeny/detail/tween-value.h @@ -22,8 +22,8 @@ IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -#ifndef TWEENY_TWEEN_VALUE_H -#define TWEENY_TWEEN_VALUE_H +#ifndef TWEENY_DETAIL_TWEEN_VALUE_H +#define TWEENY_DETAIL_TWEEN_VALUE_H #include #include "value-container.h" @@ -62,4 +62,4 @@ namespace tweeny::detail { } -#endif // TWEENY_TWEEN_VALUE_H +#endif // TWEENY_DETAIL_TWEEN_VALUE_H diff --git a/include/tweeny/detail/value-container.h b/include/tweeny/detail/value-container.h index 6c59998..d244a7f 100644 --- a/include/tweeny/detail/value-container.h +++ b/include/tweeny/detail/value-container.h @@ -22,8 +22,8 @@ IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -#ifndef TWEENY_VALUE_CONTAINER_H -#define TWEENY_VALUE_CONTAINER_H +#ifndef TWEENY_DETAIL_VALUE_CONTAINER_H +#define TWEENY_DETAIL_VALUE_CONTAINER_H #include #include @@ -92,4 +92,4 @@ namespace tweeny::detail { >; } -#endif //TWEENY_VALUE_CONTAINER_H +#endif //TWEENY_DETAIL_VALUE_CONTAINER_H diff --git a/include/tweeny/event.h b/include/tweeny/event.h index dff693c..ce05ebe 100644 --- a/include/tweeny/event.h +++ b/include/tweeny/event.h @@ -25,6 +25,8 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. #ifndef TWEENY_EVENT_H #define TWEENY_EVENT_H +#include + namespace tweeny::event { struct sectionIn { size_t key_frame; diff --git a/include/tweeny/tween.h b/include/tweeny/tween.h index 8b3fc69..ea9b0ce 100644 --- a/include/tweeny/tween.h +++ b/include/tweeny/tween.h @@ -24,11 +24,11 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. #ifndef TWEENY_TWEEN_H #define TWEENY_TWEEN_H + #include #include #include #include -#include #include "detail/key-frame.h" #include "detail/tween-value.h" @@ -50,9 +50,9 @@ namespace tweeny { auto jump(std::size_t target_key_frame) -> tween_value_t; auto step(int32_t frames) -> tween_value_t; - auto peek() const -> tween_value_t; - auto peek(uint32_t target_frame) const -> tween_value_t; - auto progress() const -> float; + [[nodiscard]] auto peek() const -> tween_value_t; + [[nodiscard]] auto peek(uint32_t target_frame) const -> tween_value_t; + [[nodiscard]] auto progress() const -> float; template auto on(event::step_t, Callback&& cb) -> void ; template auto on(event::seek_t, Callback&& cb) -> void ; @@ -71,7 +71,7 @@ namespace tweeny { auto invoke_listeners(std::vector& listeners) -> void; auto render(uint32_t target_frame) -> tween_value_t; auto render(uint32_t target_frame) const -> tween_value_t; - auto find_key_frame_index(uint32_t frame) const -> std::size_t; + [[nodiscard]] auto find_key_frame_index(uint32_t frame) const -> std::size_t; }; } diff --git a/include/tweeny/tween.tcc b/include/tweeny/tween.tcc index 90c35b4..2e5ef33 100644 --- a/include/tweeny/tween.tcc +++ b/include/tweeny/tween.tcc @@ -31,7 +31,7 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. #include #include "detail/interpolate.h" -#include "tweeny/easing/easing.h" +#include "detail/easing.h" template tweeny::tween::tween(const key_frames_t & key_frames_input) : key_frames(key_frames_input), current_value(render(0)) { } From 8fbb1b7edd82972d83629cfc0d9785aa98898a8b Mon Sep 17 00:00:00 2001 From: Leonardo Date: Thu, 25 Dec 2025 17:03:13 -0300 Subject: [PATCH 31/58] remove non-const `render` method from `tween` --- include/tweeny/tween.h | 1 - include/tweeny/tween.tcc | 63 ---------------------------------------- 2 files changed, 64 deletions(-) diff --git a/include/tweeny/tween.h b/include/tweeny/tween.h index ea9b0ce..920048d 100644 --- a/include/tweeny/tween.h +++ b/include/tweeny/tween.h @@ -69,7 +69,6 @@ namespace tweeny { std::vector jump_listeners; auto invoke_listeners(std::vector& listeners) -> void; - auto render(uint32_t target_frame) -> tween_value_t; auto render(uint32_t target_frame) const -> tween_value_t; [[nodiscard]] auto find_key_frame_index(uint32_t frame) const -> std::size_t; }; diff --git a/include/tweeny/tween.tcc b/include/tweeny/tween.tcc index 2e5ef33..30c35e3 100644 --- a/include/tweeny/tween.tcc +++ b/include/tweeny/tween.tcc @@ -135,69 +135,6 @@ auto tweeny::tween::invoke_listeners(std } } -template -auto tweeny::tween::render(uint32_t target_frame) -> tween_value_t { - constexpr std::size_t ValuesCount = sizeof...(RemainingValueTypes) + 1; - - const auto as_return_value = [](const auto & val) -> tween_value_t { - if constexpr (ValuesCount == 1) { - return std::get<0>(val); - } else { - return val; - } - }; - - if (key_frames.empty()) { - if constexpr (ValuesCount == 1) { - return FirstValueType{}; - } else { - return typename key_frame_t::values_t{}; - } - } - - auto & first_key_frame = key_frames.front(); - auto & last_key_frame = key_frames.back(); - - target_frame = std::clamp( - target_frame, - first_key_frame.position, - last_key_frame.position - ); - - if (target_frame <= first_key_frame.position) return as_return_value(first_key_frame.values); - if (target_frame >= last_key_frame.position) return as_return_value(last_key_frame.values); - - std::size_t base_key_key_frame_idx = find_key_frame_index(target_frame); - - if (base_key_key_frame_idx + 1 >= key_frames.size()) { - return as_return_value(last_key_frame.values); - } - - const key_frame_t & base_key_frame = key_frames[base_key_key_frame_idx]; - const key_frame_t & next_key_frame = key_frames[base_key_key_frame_idx + 1]; - - const int64_t base_kf_position = base_key_frame.position; - const uint32_t target_kf_position = next_key_frame.position; - const int64_t target_frame_i64 = target_frame; - - float inbetween_progress = 1.0f; - if (target_kf_position > base_kf_position) { - const auto numerator = static_cast(target_frame_i64 - base_kf_position); - const auto denominator = static_cast(target_kf_position - base_kf_position); - inbetween_progress = numerator / denominator; - } - inbetween_progress = std::clamp(inbetween_progress, 0.0f, 1.0f); - - auto values = detail::interpolate_values( - inbetween_progress, - base_key_frame, - next_key_frame, - std::make_index_sequence{} - ); - - return as_return_value(values); -} - template auto tweeny::tween::render(uint32_t target_frame) const -> tween_value_t { constexpr std::size_t ValuesCount = sizeof...(RemainingValueTypes) + 1; From be68a7ce117017106dffa2ca40e3d7145ef8622c Mon Sep 17 00:00:00 2001 From: Leonardo Date: Thu, 25 Dec 2025 17:41:26 -0300 Subject: [PATCH 32/58] migrate easing definitions to tweeny/easing.h --- CMakeLists.txt | 2 +- include/tweeny/detail/easing.h | 49 ------------- include/tweeny/detail/easing/back.h | 6 -- include/tweeny/detail/easing/bounce.h | 6 -- include/tweeny/detail/easing/circular.h | 6 +- include/tweeny/detail/easing/cubic.h | 6 -- include/tweeny/detail/easing/def.h | 4 - include/tweeny/detail/easing/elastic.h | 6 -- include/tweeny/detail/easing/exponential.h | 6 -- include/tweeny/detail/easing/linear.h | 4 - include/tweeny/detail/easing/quadratic.h | 5 -- include/tweeny/detail/easing/quartic.h | 6 -- include/tweeny/detail/easing/quintic.h | 6 -- include/tweeny/detail/easing/sinusoidal.h | 6 -- include/tweeny/detail/easing/stepped.h | 4 - include/tweeny/detail/interpolate.h | 2 +- include/tweeny/easing.h | 85 ++++++++++++++++++++++ include/tweeny/tween.tcc | 2 +- 18 files changed, 89 insertions(+), 122 deletions(-) delete mode 100644 include/tweeny/detail/easing.h create mode 100644 include/tweeny/easing.h diff --git a/CMakeLists.txt b/CMakeLists.txt index bd3171a..ded380e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -68,7 +68,7 @@ target_sources(tweeny INTERFACE include/tweeny/detail/key-frame.h include/tweeny/detail/traits.h include/tweeny/detail/value-container.h - include/tweeny/detail/easing.h + include/tweeny/easing.h include/tweeny/detail/easing-resolve.h ) diff --git a/include/tweeny/detail/easing.h b/include/tweeny/detail/easing.h deleted file mode 100644 index 2b45261..0000000 --- a/include/tweeny/detail/easing.h +++ /dev/null @@ -1,49 +0,0 @@ -/* - This file is part of the Tweeny library. - - Copyright (c) 2016-2025 Leonardo Guilherme Lucena de Freitas - Copyright (c) 2016 Guilherme R. Costa - - Permission is hereby granted, free of charge, to any person obtaining a copy of - this software and associated documentation files (the "Software"), to deal in - the Software without restriction, including without limitation the rights to - use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of - the Software, and to permit persons to whom the Software is furnished to do so, - subject to the following conditions: - # - The above copyright notice and this permission notice shall be included in all - copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS - FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR - COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER - IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -*/ - -/** - * @file easing.h - * The purpose of this file is to list all bundled easings. Each easing is defined - * in its own header under include/tweeny/detail/easing/. Users may include individual - * easing headers or this header to get all easings. - */ - -#ifndef TWEENY_DETAIL_EASING_H -#define TWEENY_DETAIL_EASING_H - -#include "tweeny/detail/easing/stepped.h" -#include "tweeny/detail/easing/def.h" -#include "tweeny/detail/easing/linear.h" -#include "tweeny/detail/easing/quadratic.h" -#include "tweeny/detail/easing/cubic.h" -#include "tweeny/detail/easing/quartic.h" -#include "tweeny/detail/easing/quintic.h" -#include "tweeny/detail/easing/sinusoidal.h" -#include "tweeny/detail/easing/exponential.h" -#include "tweeny/detail/easing/circular.h" -#include "tweeny/detail/easing/bounce.h" -#include "tweeny/detail/easing/elastic.h" -#include "tweeny/detail/easing/back.h" - -#endif //TWEENY_DETAIL_EASING_H diff --git a/include/tweeny/detail/easing/back.h b/include/tweeny/detail/easing/back.h index 84356c5..57058ec 100644 --- a/include/tweeny/detail/easing/back.h +++ b/include/tweeny/detail/easing/back.h @@ -75,10 +75,4 @@ namespace tweeny::detail { }; } -namespace tweeny::easing { - inline constexpr detail::backInEasing backIn{}; - inline constexpr detail::backOutEasing backOut{}; - inline constexpr detail::backInOutEasing backInOut{}; -} - #endif // TWEENY_DETAIL_EASING_BACK_H diff --git a/include/tweeny/detail/easing/bounce.h b/include/tweeny/detail/easing/bounce.h index 4bea4ed..3aab301 100644 --- a/include/tweeny/detail/easing/bounce.h +++ b/include/tweeny/detail/easing/bounce.h @@ -78,10 +78,4 @@ namespace tweeny::detail { }; } -namespace tweeny::easing { - inline constexpr detail::bounceInEasing bounceIn{}; - inline constexpr detail::bounceOutEasing bounceOut{}; - inline constexpr detail::bounceInOutEasing bounceInOut{}; -} - #endif // TWEENY_DETAIL_EASING_BOUNCE_H diff --git a/include/tweeny/detail/easing/circular.h b/include/tweeny/detail/easing/circular.h index 5ae1e5a..fe63713 100644 --- a/include/tweeny/detail/easing/circular.h +++ b/include/tweeny/detail/easing/circular.h @@ -72,10 +72,6 @@ namespace tweeny::detail { }; } -namespace tweeny::easing { - inline constexpr detail::circularInEasing circularIn{}; - inline constexpr detail::circularOutEasing circularOut{}; - inline constexpr detail::circularInOutEasing circularInOut{}; -} + #endif // TWEENY_DETAIL_EASING_CIRCULAR_H diff --git a/include/tweeny/detail/easing/cubic.h b/include/tweeny/detail/easing/cubic.h index 948a2d2..40bff53 100644 --- a/include/tweeny/detail/easing/cubic.h +++ b/include/tweeny/detail/easing/cubic.h @@ -69,10 +69,4 @@ namespace tweeny::detail { }; } -namespace tweeny::easing { - inline constexpr detail::cubicInEasing cubicIn{}; - inline constexpr detail::cubicOutEasing cubicOut{}; - inline constexpr detail::cubicInOutEasing cubicInOut{}; -} - #endif // TWEENY_DETAIL_EASING_CUBIC_H diff --git a/include/tweeny/detail/easing/def.h b/include/tweeny/detail/easing/def.h index c5388de..9c8a44a 100644 --- a/include/tweeny/detail/easing/def.h +++ b/include/tweeny/detail/easing/def.h @@ -72,8 +72,4 @@ namespace tweeny::detail { }; } -namespace tweeny::easing { - inline constexpr detail::defaultEasing def{}; -} - #endif // TWEENY_DETAIL_EASING_DEF_H diff --git a/include/tweeny/detail/easing/elastic.h b/include/tweeny/detail/easing/elastic.h index 9f38810..3fb3c9a 100644 --- a/include/tweeny/detail/easing/elastic.h +++ b/include/tweeny/detail/easing/elastic.h @@ -95,10 +95,4 @@ namespace tweeny::detail { }; } -namespace tweeny::easing { - inline constexpr detail::elasticInEasing elasticIn{}; - inline constexpr detail::elasticOutEasing elasticOut{}; - inline constexpr detail::elasticInOutEasing elasticInOut{}; -} - #endif // TWEENY_DETAIL_EASING_ELASTIC_H diff --git a/include/tweeny/detail/easing/exponential.h b/include/tweeny/detail/easing/exponential.h index 6ab05db..75a9cb0 100644 --- a/include/tweeny/detail/easing/exponential.h +++ b/include/tweeny/detail/easing/exponential.h @@ -70,10 +70,4 @@ namespace tweeny::detail { }; } -namespace tweeny::easing { - inline constexpr detail::exponentialInEasing exponentialIn{}; - inline constexpr detail::exponentialOutEasing exponentialOut{}; - inline constexpr detail::exponentialInOutEasing exponentialInOut{}; -} - #endif // TWEENY_DETAIL_EASING_EXPONENTIAL_H diff --git a/include/tweeny/detail/easing/linear.h b/include/tweeny/detail/easing/linear.h index 5a71971..2446598 100644 --- a/include/tweeny/detail/easing/linear.h +++ b/include/tweeny/detail/easing/linear.h @@ -47,8 +47,4 @@ namespace tweeny::detail { }; } -namespace tweeny::easing { - inline constexpr detail::linearEasing linear{}; -} - #endif // TWEENY_DETAIL_EASING_LINEAR_H diff --git a/include/tweeny/detail/easing/quadratic.h b/include/tweeny/detail/easing/quadratic.h index 46719f8..76b655c 100644 --- a/include/tweeny/detail/easing/quadratic.h +++ b/include/tweeny/detail/easing/quadratic.h @@ -69,9 +69,4 @@ namespace tweeny::detail { }; } -namespace tweeny::easing { - inline constexpr detail::quadraticInEasing quadraticIn{}; - inline constexpr detail::quadraticOutEasing quadraticOut{}; - inline constexpr detail::quadraticInOutEasing quadraticInOut{}; -} #endif // TWEENY_DETAIL_EASING_QUADRATIC_H diff --git a/include/tweeny/detail/easing/quartic.h b/include/tweeny/detail/easing/quartic.h index 9dfb100..b0b79ba 100644 --- a/include/tweeny/detail/easing/quartic.h +++ b/include/tweeny/detail/easing/quartic.h @@ -69,10 +69,4 @@ namespace tweeny::detail { }; } -namespace tweeny::easing { - inline constexpr detail::quarticInEasing quarticIn{}; - inline constexpr detail::quarticOutEasing quarticOut{}; - inline constexpr detail::quarticInOutEasing quarticInOut{}; -} - #endif // TWEENY_DETAIL_EASING_QUARTIC_H diff --git a/include/tweeny/detail/easing/quintic.h b/include/tweeny/detail/easing/quintic.h index 0dd2f44..feb777f 100644 --- a/include/tweeny/detail/easing/quintic.h +++ b/include/tweeny/detail/easing/quintic.h @@ -69,10 +69,4 @@ namespace tweeny::detail { }; } -namespace tweeny::easing { - inline constexpr detail::quinticInEasing quinticIn{}; - inline constexpr detail::quinticOutEasing quinticOut{}; - inline constexpr detail::quinticInOutEasing quinticInOut{}; -} - #endif // TWEENY_DETAIL_EASING_QUINTIC_H diff --git a/include/tweeny/detail/easing/sinusoidal.h b/include/tweeny/detail/easing/sinusoidal.h index 7497f59..156ddc2 100644 --- a/include/tweeny/detail/easing/sinusoidal.h +++ b/include/tweeny/detail/easing/sinusoidal.h @@ -69,10 +69,4 @@ namespace tweeny::detail { }; } -namespace tweeny::easing { - inline constexpr detail::sinusoidalInEasing sinusoidalIn{}; - inline constexpr detail::sinusoidalOutEasing sinusoidalOut{}; - inline constexpr detail::sinusoidalInOutEasing sinusoidalInOut{}; -} - #endif // TWEENY_DETAIL_EASING_SINUSOIDAL_H diff --git a/include/tweeny/detail/easing/stepped.h b/include/tweeny/detail/easing/stepped.h index f99aca0..e00fb61 100644 --- a/include/tweeny/detail/easing/stepped.h +++ b/include/tweeny/detail/easing/stepped.h @@ -39,8 +39,4 @@ namespace tweeny::detail { }; } -namespace tweeny::easing { - inline constexpr detail::steppedEasing stepped{}; -} - #endif // TWEENY_DETAIL_EASING_STEPPED_H diff --git a/include/tweeny/detail/interpolate.h b/include/tweeny/detail/interpolate.h index cdd9877..ec29ba6 100644 --- a/include/tweeny/detail/interpolate.h +++ b/include/tweeny/detail/interpolate.h @@ -29,7 +29,7 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. #include #include "key-frame.h" -#include "easing.h" +#include "../easing.h" namespace tweeny::detail { template diff --git a/include/tweeny/easing.h b/include/tweeny/easing.h new file mode 100644 index 0000000..95911bc --- /dev/null +++ b/include/tweeny/easing.h @@ -0,0 +1,85 @@ +/* + This file is part of the Tweeny library. + + Copyright (c) 2016-2025 Leonardo Guilherme Lucena de Freitas + Copyright (c) 2016 Guilherme R. Costa + + Permission is hereby granted, free of charge, to any person obtaining a copy of + this software and associated documentation files (the "Software"), to deal in + the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ + +/** + * @file easing.h + * The purpose of this file is to list all bundled easings. Each easing is defined + * in its own header under include/tweeny/detail/easing/. Users may include individual + * easing headers or this header to get all easings. + */ + +#ifndef TWEENY_EASING_H +#define TWEENY_EASING_H + +#include "tweeny/detail/easing/back.h" +#include "tweeny/detail/easing/bounce.h" +#include "tweeny/detail/easing/circular.h" +#include "tweeny/detail/easing/cubic.h" +#include "tweeny/detail/easing/def.h" +#include "tweeny/detail/easing/elastic.h" +#include "tweeny/detail/easing/exponential.h" +#include "tweeny/detail/easing/linear.h" +#include "tweeny/detail/easing/quadratic.h" +#include "tweeny/detail/easing/quartic.h" +#include "tweeny/detail/easing/quintic.h" +#include "tweeny/detail/easing/sinusoidal.h" +#include "tweeny/detail/easing/stepped.h" + +namespace tweeny::easing { + inline constexpr detail::backInEasing backIn{}; + inline constexpr detail::backOutEasing backOut{}; + inline constexpr detail::backInOutEasing backInOut{}; + inline constexpr detail::bounceInEasing bounceIn{}; + inline constexpr detail::bounceOutEasing bounceOut{}; + inline constexpr detail::bounceInOutEasing bounceInOut{}; + inline constexpr detail::circularInEasing circularIn{}; + inline constexpr detail::circularOutEasing circularOut{}; + inline constexpr detail::circularInOutEasing circularInOut{}; + inline constexpr detail::cubicInEasing cubicIn{}; + inline constexpr detail::cubicOutEasing cubicOut{}; + inline constexpr detail::cubicInOutEasing cubicInOut{}; + inline constexpr detail::defaultEasing def{}; + inline constexpr detail::elasticInEasing elasticIn{}; + inline constexpr detail::elasticOutEasing elasticOut{}; + inline constexpr detail::elasticInOutEasing elasticInOut{}; + inline constexpr detail::exponentialInEasing exponentialIn{}; + inline constexpr detail::exponentialOutEasing exponentialOut{}; + inline constexpr detail::exponentialInOutEasing exponentialInOut{}; + inline constexpr detail::linearEasing linear{}; + inline constexpr detail::quadraticInEasing quadraticIn{}; + inline constexpr detail::quadraticOutEasing quadraticOut{}; + inline constexpr detail::quadraticInOutEasing quadraticInOut{}; + inline constexpr detail::quarticInEasing quarticIn{}; + inline constexpr detail::quarticOutEasing quarticOut{}; + inline constexpr detail::quarticInOutEasing quarticInOut{}; + inline constexpr detail::quinticInEasing quinticIn{}; + inline constexpr detail::quinticOutEasing quinticOut{}; + inline constexpr detail::quinticInOutEasing quinticInOut{}; + inline constexpr detail::sinusoidalInEasing sinusoidalIn{}; + inline constexpr detail::sinusoidalOutEasing sinusoidalOut{}; + inline constexpr detail::sinusoidalInOutEasing sinusoidalInOut{}; + inline constexpr detail::steppedEasing stepped{}; +} + +#endif //TWEENY_EASING_H diff --git a/include/tweeny/tween.tcc b/include/tweeny/tween.tcc index 30c35e3..937a71a 100644 --- a/include/tweeny/tween.tcc +++ b/include/tweeny/tween.tcc @@ -31,7 +31,7 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. #include #include "detail/interpolate.h" -#include "detail/easing.h" +#include "easing.h" template tweeny::tween::tween(const key_frames_t & key_frames_input) : key_frames(key_frames_input), current_value(render(0)) { } From f9381b7dccda5ce009b24a9201856f48443b6abc Mon Sep 17 00:00:00 2001 From: Leonardo Date: Thu, 25 Dec 2025 17:54:37 -0300 Subject: [PATCH 33/58] document tweeny.h --- include/tweeny/tweeny.h | 152 +++++++++++++++++++++++++++++++++++++++- 1 file changed, 151 insertions(+), 1 deletion(-) diff --git a/include/tweeny/tweeny.h b/include/tweeny/tweeny.h index 3bf798a..9db728f 100644 --- a/include/tweeny/tweeny.h +++ b/include/tweeny/tweeny.h @@ -10,7 +10,7 @@ the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: -# + The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. @@ -22,6 +22,18 @@ IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/** + * @file tweeny.h + * @brief Builder API for creating tweens. + * + * This file provides the fluent builder interface for constructing tween animations. + * The typical workflow is: from() → to() → via() → during() → build() + * + * @code + * auto tween = tweeny::from(0.0f).to(100.0f).via(easing::linear).during(60U).build(); + * @endcode + */ + #ifndef TWEENY_TWEENY_H #define TWEENY_TWEENY_H @@ -70,11 +82,26 @@ namespace tweeny { explicit tweeny_builder(key_frames_t && frames) : key_frames(std::move(frames)) {} explicit tweeny_builder(const key_frames_t & frames) : key_frames(frames) {} + /** + * @brief Adds a target keyframe to the tween. + * + * Specifies the destination value(s) for the animation. After calling this, + * you can configure easing and duration with via() and during(). + * + * @param firstValue Target value for the first component + * @param remainingValues Target values for remaining components (if multi-value tween) + * @return Builder in a configurable state (can call via(), during(), to(), or build()) + * + * @code + * auto t = tweeny::from(0).to(100).during(60U).build(); + * @endcode + */ tweeny_builder to(const FirstValue & firstValue, const RemainingValues &... remainingValues) & { key_frames.emplace_back(firstValue, remainingValues...); return tweeny_builder(key_frames); } + /// @overload tweeny_builder to(const FirstValue & firstValue, const RemainingValues &... remainingValues) && { key_frames.emplace_back(firstValue, remainingValues...); return tweeny_builder(std::move(key_frames)); @@ -108,16 +135,53 @@ namespace tweeny { explicit tweeny_builder(key_frames_t && frames) : key_frames(std::move(frames)) {} explicit tweeny_builder(const key_frames_t & frames) : key_frames(frames) {} + /** + * @brief Adds another keyframe to create multipoint animations. + * + * Call `to()` multiple times to create complex animations with multiple segments, + * each with its own easing and duration. + * + * @param firstValue Target value for the first component + * @param remainingValues Target values for remaining components + * @return Reference to this builder for method chaining + * + * @code + * // Three-point animation: 0 → 50 → 100 + * auto t = tweeny::from(0) + * .to(50).via(easing::quadraticOut).during(30U) + * .to(100).via(easing::bounceOut).during(30U) + * .build(); + * @endcode + */ tweeny_builder to(const FirstValue & firstValue, const RemainingValues &... remainingValues) & { key_frames.emplace_back(firstValue, remainingValues...); return tweeny_builder(key_frames); } + /// @overload tweeny_builder to(const FirstValue & firstValue, const RemainingValues &... remainingValues) && { key_frames.emplace_back(firstValue, remainingValues...); return tweeny_builder(std::move(key_frames)); } + /** + * @brief Specifies per-component easing functions for the last keyframe segment. + * + * Each tween component gets its own easing function. The number of easing + * functions must match the number of tween components (compile-time checked). + * + * @param easing_functions One easing function per tween component + * @return Reference to this builder for method chaining + * + * @code + * // Two components with different easings + * auto t = tweeny::from(0, 0.0f) + * .to(100, 50.0f) + * .via(easing::linear, easing::bounceOut) + * .during(60U) + * .build(); + * @endcode + */ template tweeny_builder & via(EasingFunctionTypes... easing_functions) { static_assert(sizeof...(EasingFunctionTypes) == value_count, @@ -127,6 +191,18 @@ namespace tweeny { return *this; } + /** + * @brief Specifies a single easing function for all components. + * + * Applies the same easing to all tween components. This is the most common usage. + * + * @param easing_function Easing function to apply to all components + * @return Reference to this builder for method chaining + * + * @code + * auto t = tweeny::from(0, 0.0f).to(100, 100.0f).via(easing::quadraticInOut).during(60U).build(); + * @endcode + */ template tweeny_builder & via(EasingFunctionType easing_function) { auto & key_frame = key_frames.at(key_frames.size() - 2); @@ -134,6 +210,21 @@ namespace tweeny { return *this; } + /** + * @brief Specifies per-component frame durations for the last keyframe segment. + * + * Each component can have its own animation duration in frames. The number of + * durations must match the number of components (compile-time checked). + * All parameters must be uint32_t. + * + * @param frame_counts Duration in frames for each component + * @return Reference to this builder for method chaining + * + * @code + * // X animates over 60 frames, Y over 120 frames + * auto t = tweeny::from(0, 0).to(100, 100).during(60U, 120U).build(); + * @endcode + */ template tweeny_builder & during(FrameCountsType... frame_counts) { static_assert(sizeof...(FrameCountsType) == value_count, @@ -150,6 +241,20 @@ namespace tweeny { return *this; } + /** + * @brief Specifies a uniform frame duration for all components. + * + * All tween components will animate over the same number of frames. + * This is the most common usage. + * + * @param frame_count Duration in frames for the animation segment + * @return Reference to this builder for method chaining + * + * @code + * // Animate from 0 to 100 over 60 frames + * auto t = tweeny::from(0, 0).to(100, 100).during(60U).build(); + * @endcode + */ tweeny_builder & during(uint32_t frame_count) { auto & key_frame = key_frames.at(key_frames.size() - 2); std::fill( @@ -161,7 +266,31 @@ namespace tweeny { return *this; } + /** + * @brief Constructs a tween object from the configured keyframes. + * + * Creates a tween with all configured keyframes, easings, and durations. + * The builder can be reused to create multiple tween instances with the same + * configuration or modified further to create variations. + * + * @return A tween object ready for animation + * + * @code + * // Direct use (builder discarded after build) + * auto t1 = tweeny::from(0).to(100).via(easing::linear).during(60U).build(); + * + * // Reusable builder + * auto builder = tweeny::from(0).to(100).via(easing::linear).during(60U); + * auto t2 = builder.build(); // First tween + * auto t3 = builder.build(); // Second tween with same config + * + * // Create variations + * auto t4 = builder.to(200).during(120U).build(); // Extended animation + * @endcode + */ tween_t build() const & { return tween(key_frames); } + + /// @overload tween_t build() && { return tween(std::move(key_frames)); } private: @@ -175,6 +304,27 @@ namespace tweeny { } }; + /** + * @brief Creates a new tween builder starting from the specified value(s). + * + * This is the entry point for creating all tweens. It deduces types automatically + * and supports single values, multiple values, and heterogeneous types. + * + * @param first_value Initial value for the first component + * @param remaining_values Initial values for additional components (optional) + * @return A builder in the initial state (must call to() next) + * + * @code + * // Single value + * auto t1 = tweeny::from(0).to(100).during(60U).build(); + * + * // Multiple homogeneous values + * auto t2 = tweeny::from(0, 0).to(100, 100).during(60U).build(); + * + * // Heterogeneous types + * auto t3 = tweeny::from(0, 0.0f, 0u).to(10, 5.0f, 100u).during(60U).build(); + * @endcode + */ template tweeny_builder from(FirstValue first_value, RemainingValues... remaining_values) { return tweeny_builder(first_value, remaining_values...); From f939558fe96e6dcb15dbf34449ee1c04a4df2465 Mon Sep 17 00:00:00 2001 From: Leonardo Date: Thu, 25 Dec 2025 18:26:10 -0300 Subject: [PATCH 34/58] add complete event listener and update callbacks --- include/tweeny/tween.h | 210 +++++++++++++++++++++++++++++++++++++++ include/tweeny/tween.tcc | 21 ++++ 2 files changed, 231 insertions(+) diff --git a/include/tweeny/tween.h b/include/tweeny/tween.h index 920048d..bc6766e 100644 --- a/include/tweeny/tween.h +++ b/include/tweeny/tween.h @@ -22,6 +22,22 @@ IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/** + * @file tween.h + * @brief Core tween class for frame-based animation. + * + * This file defines the tween class, which represents an animation between keyframes. + * Tweens interpolate values over time using configurable easing functions and support + * event listeners for animation lifecycle events. + * + * @code + * auto t = tweeny::from(0).to(100).during(60U).build(); + * t.step(1); // Advance one frame + * auto val = t.peek(); // Get current value + * float p = t.progress(); // Get completion (0.0-1.0) + * @endcode + */ + #ifndef TWEENY_TWEEN_H #define TWEENY_TWEEN_H @@ -35,29 +51,222 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. #include "event.h" namespace tweeny { + /** + * @brief A tween represents an animation between keyframes. + * + * Tweens interpolate values over discrete frames using configurable easing functions. + * They maintain internal state (current frame and value) and support event listeners + * for animation events. + * + * @tparam FirstValueType Type of the first animated value + * @tparam RemainingValueTypes Types of additional animated values (for multi-value tweens) + * + * @note For single values, tween_value_t is the value type directly. + * For multiple values, tween_value_t is std::tuple + */ template class tween { typedef detail::key_frame key_frame_t; typedef std::vector key_frames_t; public: + /** + * @brief The type returned by navigation and query methods. + * + * For single-value tweens: the value type itself. + * For multi-value tweens: std::tuple of all value types. + */ using tween_value_t = detail::tween_value_t; + /** + * @brief Constructs a tween from keyframes (use builder API instead). + * @internal Users should use tweeny::from() to create tweens. + */ explicit tween(const key_frames_t & key_frames_input); + + /// @overload explicit tween(key_frames_t && key_frames_input); + /** + * @brief Seeks to a specific frame in the animation. + * + * Jumps directly to the target frame, updating the tween's current position and value. + * Clamped to [first_frame, last_frame]. Triggers event::seek listeners. + * + * @param target_frame Absolute frame number to seek to + * @return Interpolated value at the target frame + * + * @code + * auto t = tweeny::from(0).to(100).during(100U).build(); + * t.seek(50U); // Jump to frame 50 (value: 50) + * t.seek(0U); // Jump back to start + * @endcode + */ auto seek(uint32_t target_frame) -> tween_value_t; + + /** + * @brief Jumps to a specific keyframe index. + * + * Moves the tween to the exact position of a keyframe. Useful for resetting to + * known points or implementing discrete state animations. Triggers event::jump listeners. + * + * @param target_key_frame Zero-based keyframe index (clamped to valid range) + * @return Value at the target keyframe + * + * @code + * auto t = tweeny::from(0).to(50).during(30U).to(100).during(30U).build(); + * t.jump(0); // Jump to first keyframe (value: 0) + * t.jump(1); // Jump to second keyframe (value: 50) + * t.jump(2); // Jump to third keyframe (value: 100) + * @endcode + */ auto jump(std::size_t target_key_frame) -> tween_value_t; + + /** + * @brief Advances or rewinds the animation by a frame delta. + * + * Moves the tween forward (positive) or backward (negative) by the specified number + * of frames. Clamped to valid frame range. Triggers event::step listeners. + * + * @param frames Number of frames to move (negative values step backward) + * @return Interpolated value at the new frame + * + * @code + * auto t = tweeny::from(0).to(100).during(100U).build(); + * t.step(10); // Advance 10 frames (value: 10) + * t.step(5); // Advance 5 more frames (value: 15) + * t.step(-3); // Rewind 3 frames (value: 12) + * @endcode + */ auto step(int32_t frames) -> tween_value_t; + /** + * @brief Returns the current interpolated value without changing state. + * + * Non-mutating query of the tween's current value. Does not trigger events + * or modify the tween's position. + * + * @return Current interpolated value + * + * @code + * auto t = tweeny::from(0).to(100).during(100U).build(); + * t.step(25); + * auto val = t.peek(); // Returns 25, doesn't change state + * @endcode + */ [[nodiscard]] auto peek() const -> tween_value_t; + + /** + * @brief Queries the interpolated value at any frame without changing state. + * + * Previews what the value would be at the target frame without moving the tween. + * Useful for scrubbing, previewing, or inspecting the animation curve. + * Does not trigger events. + * + * @param target_frame Frame to query (clamped to valid range) + * @return Interpolated value at the target frame + * + * @code + * auto t = tweeny::from(0).to(100).during(100U).build(); + * auto midpoint = t.peek(50U); // Preview value at frame 50 (returns 50) + * auto start = t.peek(0U); // Preview start value (returns 0) + * // Tween still at frame 0, not moved + * @endcode + */ [[nodiscard]] auto peek(uint32_t target_frame) const -> tween_value_t; + + /** + * @brief Returns the animation completion percentage. + * + * Calculates progress as (current_frame - first_frame) / total_frames. + * Returns a value in the range [0.0, 1.0]. + * + * @return Progress percentage (0.0 = start, 1.0 = complete) + * + * @code + * auto t = tweeny::from(0).to(100).during(100U).build(); + * t.step(25); + * printf("%.0f%% complete\n", t.progress() * 100.0f); // "25% complete" + * + * // Check if animation is done + * if (t.progress() >= 1.0f) { + * printf("Animation complete!\n"); + * } + * @endcode + */ [[nodiscard]] auto progress() const -> float; + /** + * @brief Registers a callback for step() events. + * + * The callback is invoked after each step() call. It receives a reference to + * the tween and must return event::response::ok to continue receiving events, + * or event::response::unsubscribe to auto-remove. + * + * @param cb Callback with signature: event::response(tween&) + * + * @code + * auto t = tweeny::from(0).to(100).during(100U).build(); + * t.on(event::step, [](auto& tween) { + * printf("Stepped to: %d\n", tween.peek()); + * return event::response::ok; + * }); + * t.step(10); // Prints: "Stepped to: 10" + * @endcode + */ template auto on(event::step_t, Callback&& cb) -> void ; + + /** + * @brief Registers a callback for seek() events. + * + * The callback is invoked after each seek() call. Same signature and behavior as step events. + * + * @param cb Callback with signature: event::response(tween&) + * + * @code + * t.on(event::seek, [](auto& tween) { + * printf("Seeked to: %d\n", tween.peek()); + * return event::response::ok; + * }); + * @endcode + */ template auto on(event::seek_t, Callback&& cb) -> void ; + + /** + * @brief Registers a callback for jump() events. + * + * The callback is invoked after each jump() call. Same signature and behavior as step events. + * + * @param cb Callback with signature: event::response(tween&) + * + * @code + * t.on(event::jump, [](auto& tween) { + * printf("Jumped to keyframe\n"); + * return event::response::ok; + * }); + * @endcode + */ template auto on(event::jump_t, Callback&& cb) -> void ; + /** + * @brief Registers a callback for animation completion. + * + * The callback is invoked when the tween reaches the last frame (progress >= 1.0). + * Triggered by step(), seek(), or jump() when they result in completion. + * + * @param cb Callback with signature: event::response(tween&) + * + * @code + * auto t = tweeny::from(0).to(100).during(100U).build(); + * t.on(event::complete, [](auto& tween) { + * printf("Animation complete!\n"); + * return event::response::ok; + * }); + * t.seek(100U); // Triggers complete event + * @endcode + */ + template auto on(event::complete_t, Callback&& cb) -> void ; + private: using callback_t = std::function; @@ -67,6 +276,7 @@ namespace tweeny { std::vector step_listeners; std::vector seek_listeners; std::vector jump_listeners; + std::vector complete_listeners; auto invoke_listeners(std::vector& listeners) -> void; auto render(uint32_t target_frame) const -> tween_value_t; diff --git a/include/tweeny/tween.tcc b/include/tweeny/tween.tcc index 937a71a..545f11d 100644 --- a/include/tweeny/tween.tcc +++ b/include/tweeny/tween.tcc @@ -54,6 +54,10 @@ auto tweeny::tween::seek(const uint32_t invoke_listeners(seek_listeners); + if (progress() >= 1.0f) { + invoke_listeners(complete_listeners); + } + return current_value; } @@ -66,6 +70,10 @@ auto tweeny::tween::jump(std::size_t tar invoke_listeners(jump_listeners); + if (progress() >= 1.0f) { + invoke_listeners(complete_listeners); + } + return current_value; } @@ -85,6 +93,10 @@ auto tweeny::tween::step(const int32_t f invoke_listeners(step_listeners); + if (progress() >= 1.0f) { + invoke_listeners(complete_listeners); + } + return current_value; } @@ -115,6 +127,15 @@ auto tweeny::tween::on(event::jump_t, Ca jump_listeners.emplace_back(std::forward(cb)); } +template +template +auto tweeny::tween::on(event::complete_t, Callback && cb) -> void { + using result_t = std::invoke_result_t; + static_assert(std::is_same_v, + "complete callback must return tweeny::event::response"); + complete_listeners.emplace_back(std::forward(cb)); +} + template auto tweeny::tween::invoke_listeners(std::vector& listeners) -> void { std::vector to_remove; From 77c5bf465cbe5e34a95ae678f587ee501c38aaa5 Mon Sep 17 00:00:00 2001 From: Leonardo Date: Thu, 25 Dec 2025 18:26:26 -0300 Subject: [PATCH 35/58] migrate tests into categorized directories --- tests/CMakeLists.txt | 11 +- tests/events/complete.cpp | 116 ++++++++++++++++++ tests/events/jump.cpp | 19 +++ tests/events/seek.cpp | 19 +++ tests/{events.cpp => events/step.cpp} | 36 ------ tests/tween/jump.cpp | 72 +++++++++++ tests/tween/peek.cpp | 54 ++++++++ .../progress.cpp} | 47 +------ tests/tween/seek.cpp | 81 ++++++++++++ tests/tween/step.cpp | 77 ++++++++++++ 10 files changed, 453 insertions(+), 79 deletions(-) create mode 100644 tests/events/complete.cpp create mode 100644 tests/events/jump.cpp create mode 100644 tests/events/seek.cpp rename tests/{events.cpp => events/step.cpp} (56%) create mode 100644 tests/tween/jump.cpp create mode 100644 tests/tween/peek.cpp rename tests/{peek_and_progress.cpp => tween/progress.cpp} (51%) create mode 100644 tests/tween/seek.cpp create mode 100644 tests/tween/step.cpp diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index b581a69..8835716 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -3,8 +3,15 @@ find_package(Catch2 3 REQUIRED CONFIG) add_executable(tweeny_tests sanity.cpp - events.cpp - peek_and_progress.cpp + tween/peek.cpp + tween/progress.cpp + tween/step.cpp + tween/seek.cpp + tween/jump.cpp + events/step.cpp + events/seek.cpp + events/jump.cpp + events/complete.cpp ) target_compile_features(tweeny_tests PRIVATE cxx_std_17) diff --git a/tests/events/complete.cpp b/tests/events/complete.cpp new file mode 100644 index 0000000..303fdfc --- /dev/null +++ b/tests/events/complete.cpp @@ -0,0 +1,116 @@ +#include +#include + +TEST_CASE("complete event - triggered when reaching end via step", "[event][complete]") { + auto t = tweeny::from(0).to(100).during(100U).build(); + + bool completed = false; + t.on(tweeny::event::complete, [&](auto&) { + completed = true; + return tweeny::event::response::ok; + }); + + // Not complete yet + t.step(50); + REQUIRE_FALSE(completed); + + // Complete now + t.step(50); + REQUIRE(completed); +} + +TEST_CASE("complete event - triggered when reaching end via seek", "[event][complete]") { + auto t = tweeny::from(0).to(100).during(100U).build(); + + bool completed = false; + t.on(tweeny::event::complete, [&](auto&) { + completed = true; + return tweeny::event::response::ok; + }); + + t.seek(100U); + REQUIRE(completed); +} + +TEST_CASE("complete event - triggered when jumping to last keyframe", "[event][complete]") { + auto t = tweeny::from(0).to(50).during(50U).to(100).during(50U).build(); + + bool completed = false; + t.on(tweeny::event::complete, [&](auto&) { + completed = true; + return tweeny::event::response::ok; + }); + + t.jump(2); // Jump to last keyframe + REQUIRE(completed); +} + +TEST_CASE("complete event - not triggered when not at end", "[event][complete]") { + auto t = tweeny::from(0).to(100).during(100U).build(); + + bool completed = false; + t.on(tweeny::event::complete, [&](auto&) { + completed = true; + return tweeny::event::response::ok; + }); + + t.step(50); + REQUIRE_FALSE(completed); + + t.seek(75U); + REQUIRE_FALSE(completed); +} + +TEST_CASE("complete event - can unsubscribe", "[event][complete]") { + auto t = tweeny::from(0).to(100).during(100U).build(); + + int call_count = 0; + t.on(tweeny::event::complete, [&](auto&) { + call_count++; + return tweeny::event::response::unsubscribe; + }); + + t.seek(100U); + REQUIRE(call_count == 1); + + // Reset and complete again - listener should be gone + t.seek(0U); + t.seek(100U); + REQUIRE(call_count == 1); // Should still be 1 +} + +TEST_CASE("complete event - multiple listeners", "[event][complete]") { + auto t = tweeny::from(0).to(100).during(100U).build(); + + int listener1_count = 0; + int listener2_count = 0; + + t.on(tweeny::event::complete, [&](auto&) { + listener1_count++; + return tweeny::event::response::ok; + }); + + t.on(tweeny::event::complete, [&](auto&) { + listener2_count++; + return tweeny::event::response::ok; + }); + + t.step(100); + REQUIRE(listener1_count == 1); + REQUIRE(listener2_count == 1); +} + +TEST_CASE("complete event - triggered on exact completion", "[event][complete]") { + auto t = tweeny::from(0).to(100).during(100U).build(); + + bool completed = false; + t.on(tweeny::event::complete, [&](auto& tween) { + REQUIRE(tween.progress() >= 1.0f); + REQUIRE(tween.peek() == 100); + completed = true; + return tweeny::event::response::ok; + }); + + t.step(100); + REQUIRE(completed); +} diff --git a/tests/events/jump.cpp b/tests/events/jump.cpp new file mode 100644 index 0000000..88e402d --- /dev/null +++ b/tests/events/jump.cpp @@ -0,0 +1,19 @@ +#include +#include + +TEST_CASE("event::jump - callback is invoked on jump()", "[event][jump]") { + auto t = tweeny::from(0) + .to(10) + .during(10U) + .build(); + + int called = 0; + + t.on(tweeny::event::jump, [&](const auto &) { + ++called; + return tweeny::event::response::ok; + }); + + (void)t.jump(1); + REQUIRE(called == 1); +} diff --git a/tests/events/seek.cpp b/tests/events/seek.cpp new file mode 100644 index 0000000..0914096 --- /dev/null +++ b/tests/events/seek.cpp @@ -0,0 +1,19 @@ +#include +#include + +TEST_CASE("event::seek - callback is invoked on seek()", "[event][seek]") { + auto t = tweeny::from(0) + .to(10) + .during(10U) + .build(); + + int called = 0; + + t.on(tweeny::event::seek, [&](const auto &) { + ++called; + return tweeny::event::response::ok; + }); + + (void)t.seek(5U); + REQUIRE(called == 1); +} diff --git a/tests/events.cpp b/tests/events/step.cpp similarity index 56% rename from tests/events.cpp rename to tests/events/step.cpp index fb27efc..0d59d7f 100644 --- a/tests/events.cpp +++ b/tests/events/step.cpp @@ -1,41 +1,5 @@ #include #include -#include - -TEST_CASE("event::seek - callback is invoked on seek()", "[event][seek]") { - auto t = tweeny::from(0) - .to(10) - .during(10U) - .build(); - - int called = 0; - - t.on(tweeny::event::seek, [&](const auto &) { - ++called; - return tweeny::event::response::ok; - }); - - (void)t.seek(5U); - REQUIRE(called == 1); -} - -TEST_CASE("event::jump - callback is invoked on jump()", "[event][jump]") { - auto t = tweeny::from(0) - .to(10) - .during(10U) - .build(); - - int called = 0; - - t.on(tweeny::event::jump, [&](const auto &) { - ++called; - return tweeny::event::response::ok; - }); - - (void)t.jump(1); - REQUIRE(called == 1); -} - TEST_CASE("event::step - callback is invoked on step()", "[event][step]") { auto t = tweeny::from(0) diff --git a/tests/tween/jump.cpp b/tests/tween/jump.cpp new file mode 100644 index 0000000..fee5c81 --- /dev/null +++ b/tests/tween/jump.cpp @@ -0,0 +1,72 @@ +#include +#include + +TEST_CASE("jump() - jumps to keyframe by index", "[tween][jump]") { + auto t = tweeny::from(0) + .to(50).during(50U) + .to(100).during(50U) + .build(); + + auto val = t.jump(0); + REQUIRE(val == 0); + + val = t.jump(1); + REQUIRE(val == 50); + + val = t.jump(2); + REQUIRE(val == 100); +} + +TEST_CASE("jump() - clamped to valid keyframe range", "[tween][jump]") { + auto t = tweeny::from(0) + .to(50).during(50U) + .to(100).during(50U) + .build(); + + t.jump(10); // Beyond last keyframe + REQUIRE(t.peek() == 100); +} + +TEST_CASE("jump() - works with two-point tween", "[tween][jump]") { + auto t = tweeny::from(0) + .to(100) + .during(100U) + .build(); + + t.jump(0); + REQUIRE(t.peek() == 0); + + t.jump(1); + REQUIRE(t.peek() == 100); +} + +TEST_CASE("jump() - multi-value tween", "[tween][jump]") { + auto t = tweeny::from(0, 0.0f) + .to(50, 25.0f).during(50U) + .to(100, 100.0f).during(50U) + .build(); + + auto result = t.jump(1); + REQUIRE(std::get<0>(result) == 50); + REQUIRE(std::get<1>(result) == 25.0f); + + result = t.jump(2); + REQUIRE(std::get<0>(result) == 100); + REQUIRE(std::get<1>(result) == 100.0f); +} + +TEST_CASE("jump() - can jump backward", "[tween][jump]") { + auto t = tweeny::from(0) + .to(50).during(50U) + .to(100).during(50U) + .build(); + + t.jump(2); + REQUIRE(t.peek() == 100); + + t.jump(0); + REQUIRE(t.peek() == 0); + + t.jump(1); + REQUIRE(t.peek() == 50); +} diff --git a/tests/tween/peek.cpp b/tests/tween/peek.cpp new file mode 100644 index 0000000..d3e765f --- /dev/null +++ b/tests/tween/peek.cpp @@ -0,0 +1,54 @@ +#include +#include +#include + +TEST_CASE("peek() - returns current value without mutation", "[tween][peek]") { + auto t = tweeny::from(0) + .to(100) + .during(100U) + .build(); + + REQUIRE(t.peek() == 0); + t.step(50); + REQUIRE(t.peek() == 50); +} + +TEST_CASE("peek(frame) - queries value at arbitrary frame without mutation", "[tween][peek]") { + auto t = tweeny::from(0) + .to(100) + .during(100U) + .build(); + + // Should not change current position + REQUIRE(t.peek(50U) == 50); + REQUIRE(t.peek() == 0); // Still at start + REQUIRE(t.progress() == Catch::Approx(0.0f)); +} + +TEST_CASE("peek(frame) - multi-value tween", "[tween][peek]") { + auto t = tweeny::from(0, 0.0f) + .to(100, 100.0f) + .during(100U) + .build(); + + auto result = t.peek(50U); + REQUIRE(std::get<0>(result) == 50); + REQUIRE(std::get<1>(result) == Catch::Approx(50.0f)); +} + +TEST_CASE("peek() - does not trigger events", "[tween][peek]") { + auto t = tweeny::from(0) + .to(100) + .during(100U) + .build(); + + int call_count = 0; + t.on(tweeny::event::step, [&](auto&) { + call_count++; + return tweeny::event::response::ok; + }); + + (void)t.peek(); + (void)t.peek(50U); + REQUIRE(call_count == 0); +} diff --git a/tests/peek_and_progress.cpp b/tests/tween/progress.cpp similarity index 51% rename from tests/peek_and_progress.cpp rename to tests/tween/progress.cpp index e1ffa12..f739728 100644 --- a/tests/peek_and_progress.cpp +++ b/tests/tween/progress.cpp @@ -2,41 +2,7 @@ #include #include -TEST_CASE("peek() - returns current value without mutation", "[peek]") { - auto t = tweeny::from(0) - .to(100) - .during(100U) - .build(); - - REQUIRE(t.peek() == 0); - t.step(50); - REQUIRE(t.peek() == 50); -} - -TEST_CASE("peek(frame) - queries value at arbitrary frame without mutation", "[peek]") { - auto t = tweeny::from(0) - .to(100) - .during(100U) - .build(); - - // Should not change current position - REQUIRE(t.peek(50U) == 50); - REQUIRE(t.peek() == 0); // Still at start - REQUIRE(t.progress() == Catch::Approx(0.0f)); -} - -TEST_CASE("peek(frame) - multi-value tween", "[peek]") { - auto t = tweeny::from(0, 0.0f) - .to(100, 100.0f) - .during(100U) - .build(); - - auto result = t.peek(50U); - REQUIRE(std::get<0>(result) == 50); - REQUIRE(std::get<1>(result) == Catch::Approx(50.0f)); -} - -TEST_CASE("progress() - returns 0.0 at start", "[progress]") { +TEST_CASE("progress() - returns 0.0 at start", "[tween][progress]") { auto t = tweeny::from(0) .to(100) .during(100U) @@ -45,7 +11,7 @@ TEST_CASE("progress() - returns 0.0 at start", "[progress]") { REQUIRE(t.progress() == Catch::Approx(0.0f)); } -TEST_CASE("progress() - returns 1.0 at end", "[progress]") { +TEST_CASE("progress() - returns 1.0 at end", "[tween][progress]") { auto t = tweeny::from(0) .to(100) .during(100U) @@ -55,7 +21,7 @@ TEST_CASE("progress() - returns 1.0 at end", "[progress]") { REQUIRE(t.progress() == Catch::Approx(1.0f)); } -TEST_CASE("progress() - returns 0.5 at midpoint", "[progress]") { +TEST_CASE("progress() - returns 0.5 at midpoint", "[tween][progress]") { auto t = tweeny::from(0) .to(100) .during(100U) @@ -65,7 +31,7 @@ TEST_CASE("progress() - returns 0.5 at midpoint", "[progress]") { REQUIRE(t.progress() == Catch::Approx(0.5f)); } -TEST_CASE("progress() - multi-point tween", "[progress]") { +TEST_CASE("progress() - multi-point tween", "[tween][progress]") { auto t = tweeny::from(0) .to(50).during(50U) .to(100).during(50U) @@ -86,8 +52,7 @@ TEST_CASE("progress() - multi-point tween", "[progress]") { REQUIRE(t.progress() == Catch::Approx(1.0f)); } -TEST_CASE("progress() - handles empty keyframes", "[progress]") { - // Edge case: this shouldn't happen in practice but let's be defensive +TEST_CASE("progress() - handles zero duration", "[tween][progress]") { auto t = tweeny::from(0) .to(100) .during(0U) @@ -96,7 +61,7 @@ TEST_CASE("progress() - handles empty keyframes", "[progress]") { REQUIRE(t.progress() == Catch::Approx(1.0f)); } -TEST_CASE("peek() and progress() - consistent after stepping", "[peek][progress]") { +TEST_CASE("progress() - consistent with peek", "[tween][progress][peek]") { auto t = tweeny::from(0) .to(100) .during(100U) diff --git a/tests/tween/seek.cpp b/tests/tween/seek.cpp new file mode 100644 index 0000000..cfd0ad2 --- /dev/null +++ b/tests/tween/seek.cpp @@ -0,0 +1,81 @@ +#include +#include + +TEST_CASE("seek() - jumps to target frame", "[tween][seek]") { + auto t = tweeny::from(0) + .to(100) + .during(100U) + .build(); + + auto val = t.seek(50U); + REQUIRE(val == 50); + REQUIRE(t.peek() == 50); +} + +TEST_CASE("seek() - can jump forward and backward", "[tween][seek]") { + auto t = tweeny::from(0) + .to(100) + .during(100U) + .build(); + + t.seek(75U); + REQUIRE(t.peek() == 75); + + t.seek(25U); + REQUIRE(t.peek() == 25); + + t.seek(100U); + REQUIRE(t.peek() == 100); + + t.seek(0U); + REQUIRE(t.peek() == 0); +} + +TEST_CASE("seek() - clamped to valid range", "[tween][seek]") { + auto t = tweeny::from(0) + .to(100) + .during(100U) + .build(); + + t.seek(200U); // Beyond end + REQUIRE(t.peek() == 100); +} + +TEST_CASE("seek() - multi-point tween", "[tween][seek]") { + auto t = tweeny::from(0) + .to(50).during(50U) + .to(100).during(50U) + .build(); + + t.seek(25U); + REQUIRE(t.peek() == 25); + + t.seek(50U); + REQUIRE(t.peek() == 50); + + t.seek(75U); + REQUIRE(t.peek() == 75); +} + +TEST_CASE("seek() - multi-value tween", "[tween][seek]") { + auto t = tweeny::from(0, 100.0f) + .to(100, 0.0f) + .during(100U) + .build(); + + auto result = t.seek(50U); + REQUIRE(std::get<0>(result) == 50); + REQUIRE(std::get<1>(result) == 50.0f); +} + +TEST_CASE("seek() - returns interpolated value", "[tween][seek]") { + auto t = tweeny::from(0) + .to(100) + .during(100U) + .build(); + + for (uint32_t i = 0; i <= 100; i += 10) { + auto val = t.seek(i); + REQUIRE(val == static_cast(i)); + } +} diff --git a/tests/tween/step.cpp b/tests/tween/step.cpp new file mode 100644 index 0000000..768d8e2 --- /dev/null +++ b/tests/tween/step.cpp @@ -0,0 +1,77 @@ +#include +#include + +TEST_CASE("step() - advances by positive delta", "[tween][step]") { + auto t = tweeny::from(0) + .to(100) + .during(100U) + .build(); + + auto val = t.step(10); + REQUIRE(val == 10); + REQUIRE(t.peek() == 10); + + val = t.step(20); + REQUIRE(val == 30); + REQUIRE(t.peek() == 30); +} + +TEST_CASE("step() - rewinds by negative delta", "[tween][step]") { + auto t = tweeny::from(0) + .to(100) + .during(100U) + .build(); + + t.step(50); + REQUIRE(t.peek() == 50); + + t.step(-20); + REQUIRE(t.peek() == 30); + + t.step(-10); + REQUIRE(t.peek() == 20); +} + +TEST_CASE("step() - clamped at start", "[tween][step]") { + auto t = tweeny::from(0) + .to(100) + .during(100U) + .build(); + + t.step(10); + t.step(-100); // Try to go negative + REQUIRE(t.peek() == 0); +} + +TEST_CASE("step() - clamped at end", "[tween][step]") { + auto t = tweeny::from(0) + .to(100) + .during(100U) + .build(); + + t.step(200); // Overshoot + REQUIRE(t.peek() == 100); +} + +TEST_CASE("step() - multi-value tween", "[tween][step]") { + auto t = tweeny::from(0, 0.0f) + .to(100, 50.0f) + .during(100U) + .build(); + + auto result = t.step(50); + REQUIRE(std::get<0>(result) == 50); + REQUIRE(std::get<1>(result) == 25.0f); +} + +TEST_CASE("step() - returns interpolated value", "[tween][step]") { + auto t = tweeny::from(0) + .to(100) + .during(100U) + .build(); + + for (int i = 1; i <= 10; i++) { + auto val = t.step(10); + REQUIRE(val == i * 10); + } +} From c70a86c075ff74ac20f1ce1670b6fee947317e13 Mon Sep 17 00:00:00 2001 From: Leonardo Date: Thu, 25 Dec 2025 19:10:12 -0300 Subject: [PATCH 36/58] update cmake target sources --- CMakeLists.txt | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index ded380e..f03bd7c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -65,11 +65,25 @@ target_sources(tweeny INTERFACE include/tweeny/tweeny.h include/tweeny/tween.h include/tweeny/tween.tcc + include/tweeny/easing.h + include/tweeny/detail/interpolate.h include/tweeny/detail/key-frame.h - include/tweeny/detail/traits.h + include/tweeny/detail/tuple-utilities.h + include/tweeny/detail/tween-value.h include/tweeny/detail/value-container.h - include/tweeny/easing.h - include/tweeny/detail/easing-resolve.h + include/tweeny/detail/easing/back.h + include/tweeny/detail/easing/bounce.h + include/tweeny/detail/easing/circular.h + include/tweeny/detail/easing/cubic.h + include/tweeny/detail/easing/def.h + include/tweeny/detail/easing/elastic.h + include/tweeny/detail/easing/exponential.h + include/tweeny/detail/easing/linear.h + include/tweeny/detail/easing/quadratic.h + include/tweeny/detail/easing/quartic.h + include/tweeny/detail/easing/quintic.h + include/tweeny/detail/easing/sinusoidal.h + include/tweeny/detail/easing/stepped.h ) # Set up install From f0d373c541c7b5b4f41b373ba25cc3c32288d9cc Mon Sep 17 00:00:00 2001 From: Leonardo Date: Thu, 25 Dec 2025 21:05:15 -0300 Subject: [PATCH 37/58] add keyframeEnter and keyframeLeave events --- include/tweeny/event.h | 169 +++++++++++++++++++++++++++++++- include/tweeny/tween.h | 44 +++++++++ include/tweeny/tween.tcc | 68 +++++++++++++ tests/CMakeLists.txt | 2 + tests/events/keyframe_enter.cpp | 147 +++++++++++++++++++++++++++ tests/events/keyframe_leave.cpp | 168 +++++++++++++++++++++++++++++++ 6 files changed, 594 insertions(+), 4 deletions(-) create mode 100644 tests/events/keyframe_enter.cpp create mode 100644 tests/events/keyframe_leave.cpp diff --git a/include/tweeny/event.h b/include/tweeny/event.h index ce05ebe..2c171aa 100644 --- a/include/tweeny/event.h +++ b/include/tweeny/event.h @@ -22,30 +22,191 @@ IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/** + * @file event.h + * @brief Event system for tween animation callbacks. + * + * This file defines event types and response codes for the tween event system. + * Events are triggered during tween operations (step, seek, jump, complete) and + * allow callbacks to react to animation state changes. + * + * @code + * auto t = tweeny::from(0).to(100).during(60U).build(); + * t.on(tweeny::event::step, [](auto& tween) { + * printf("Value: %d\n", tween.peek()); + * return tweeny::event::response::ok; + * }); + * @endcode + */ + #ifndef TWEENY_EVENT_H #define TWEENY_EVENT_H #include +/** + * @brief Event types and response codes for tween callbacks. + * + * This namespace contains tag types for registering event listeners and + * response codes for controlling callback behavior. + */ namespace tweeny::event { - struct sectionIn { + /** + * @brief Event data passed when entering a new keyframe. + * + * This event is triggered when the tween transitions into a new keyframe section, + * providing the index of the keyframe being entered. + * + * @code + * auto t = tweeny::from(0).to(100).during(60U).build(); + * t.on(tweeny::event::keyframeEnter, [](auto& tween, tweeny::event::keyframeEnter evt) { + * std::cout << "Entered keyframe " << evt.key_frame << std::endl; + * return tweeny::event::response::ok; + * }); + * @endcode + */ + struct keyframeEnter { size_t key_frame; - explicit sectionIn(const size_t key_frame_input) : key_frame(key_frame_input) {} + explicit keyframeEnter(const size_t key_frame_input) : key_frame(key_frame_input) {} }; - struct sectionOut { + /** + * @brief Event data passed when leaving a keyframe. + * + * This event is triggered when the tween transitions out of a keyframe section, + * providing the index of the keyframe being exited. + * + * @code + * auto t = tweeny::from(0).to(100).during(60U).build(); + * t.on(tweeny::event::keyframeLeave, [](auto& tween, tweeny::event::keyframeLeave evt) { + * std::cout << "Left keyframe " << evt.key_frame << std::endl; + * return tweeny::event::response::ok; + * }); + * @endcode + */ + struct keyframeLeave { size_t key_frame; - explicit sectionOut(const size_t key_frame_input) : key_frame(key_frame_input) {} + explicit keyframeLeave(const size_t key_frame_input) : key_frame(key_frame_input) {} }; + /** + * @brief Tag type for step events. + * + * Used with tween::on() to register callbacks triggered after step() calls. + * + * @code + * t.on(tweeny::event::step, [](auto& tween) { + * return tweeny::event::response::ok; + * }); + * @endcode + */ inline struct step_t {} step; + + /** + * @brief Tag type for seek events. + * + * Used with tween::on() to register callbacks triggered after seek() calls. + * + * @code + * t.on(tweeny::event::seek, [](auto& tween) { + * return tweeny::event::response::ok; + * }); + * @endcode + */ inline struct seek_t {} seek; + + /** + * @brief Tag type for jump events. + * + * Used with tween::on() to register callbacks triggered after jump() calls. + * + * @code + * t.on(tweeny::event::jump, [](auto& tween) { + * return tweeny::event::response::ok; + * }); + * @endcode + */ inline struct jump_t {} jump; + + /** + * @brief Tag type for update events. + * @internal Currently unused - reserved for future implementation. + */ inline struct update_t {} update; + + /** + * @brief Tag type for completion events. + * + * Used with tween::on() to register callbacks triggered when progress reaches 1.0. + * Fires after step(), seek(), or jump() when the animation completes. + * + * @code + * t.on(tweeny::event::complete, [](auto& tween) { + * printf("Animation done!\n"); + * return tweeny::event::response::ok; + * }); + * @endcode + */ inline struct complete_t {} complete; + /** + * @brief Tag type for the keyframeEnter event. + * + * Use this tag with tween::on() to register a callback that fires when the tween + * enters a new keyframe section. + * + * @code + * auto t = tweeny::from(0).to(100).during(60U).build(); + * t.on(tweeny::event::keyframeEnter, [](auto& tween, tweeny::event::keyframeEnter evt) { + * std::cout << "Entered keyframe " << evt.key_frame << std::endl; + * return tweeny::event::response::ok; + * }); + * @endcode + */ + inline struct keyframeEnter_t {} keyframeEnter; + + /** + * @brief Tag type for the keyframeLeave event. + * + * Use this tag with tween::on() to register a callback that fires when the tween + * leaves a keyframe section. + * + * @code + * auto t = tweeny::from(0).to(100).during(60U).build(); + * t.on(tweeny::event::keyframeLeave, [](auto& tween, tweeny::event::keyframeLeave evt) { + * std::cout << "Left keyframe " << evt.key_frame << std::endl; + * return tweeny::event::response::ok; + * }); + * @endcode + */ + inline struct keyframeLeave_t {} keyframeLeave; + + /** + * @brief Response codes returned by event callbacks. + * + * Controls whether a callback continues receiving events or is automatically removed. + */ enum class response { + /** + * @brief Continue receiving events. + * + * The callback remains registered and will be invoked on future events. + */ ok = 0, + + /** + * @brief Unsubscribe after this callback. + * + * The callback is automatically removed after returning and will not + * receive future events. Useful for one-shot callbacks. + * + * @code + * t.on(tweeny::event::complete, [](auto& tween) { + * printf("Animation done!\n"); + * return tweeny::event::response::unsubscribe; // Remove this callback + * }); + * @endcode + */ unsubscribe = 1, }; } diff --git a/include/tweeny/tween.h b/include/tweeny/tween.h index bc6766e..08bd7cd 100644 --- a/include/tweeny/tween.h +++ b/include/tweeny/tween.h @@ -267,18 +267,62 @@ namespace tweeny { */ template auto on(event::complete_t, Callback&& cb) -> void ; + /** + * @brief Registers a callback for entering a keyframe. + * + * The callback is invoked when the tween transitions into a new keyframe section. + * Receives the tween reference and event data containing the keyframe index. + * + * @param cb Callback with signature: event::response(tween&, event::keyframeEnter) + * + * @code + * auto t = tweeny::from(0).to(50).during(30U).to(100).during(30U).build(); + * t.on(event::keyframeEnter, [](auto& tween, auto evt) { + * printf("Entered keyframe %zu\n", evt.key_frame); + * return event::response::ok; + * }); + * t.step(31); // Triggers: "Entered keyframe 1" + * @endcode + */ + template auto on(event::keyframeEnter_t, Callback&& cb) -> void ; + + /** + * @brief Registers a callback for leaving a keyframe. + * + * The callback is invoked when the tween transitions out of a keyframe section. + * Receives the tween reference and event data containing the keyframe index. + * + * @param cb Callback with signature: event::response(tween&, event::keyframeLeave) + * + * @code + * auto t = tweeny::from(0).to(50).during(30U).to(100).during(30U).build(); + * t.on(event::keyframeLeave, [](auto& tween, auto evt) { + * printf("Left keyframe %zu\n", evt.key_frame); + * return event::response::ok; + * }); + * t.step(31); // Triggers: "Left keyframe 0" + * @endcode + */ + template auto on(event::keyframeLeave_t, Callback&& cb) -> void ; + private: using callback_t = std::function; + using keyframe_enter_callback_t = std::function; + using keyframe_leave_callback_t = std::function; key_frames_t key_frames; uint32_t current_frame = 0; tween_value_t current_value; + std::size_t current_keyframe_index = 0; std::vector step_listeners; std::vector seek_listeners; std::vector jump_listeners; std::vector complete_listeners; + std::vector keyframe_enter_listeners; + std::vector keyframe_leave_listeners; auto invoke_listeners(std::vector& listeners) -> void; + auto invoke_keyframe_listeners(std::size_t old_keyframe_index, std::size_t new_keyframe_index) -> void; auto render(uint32_t target_frame) const -> tween_value_t; [[nodiscard]] auto find_key_frame_index(uint32_t frame) const -> std::size_t; }; diff --git a/include/tweeny/tween.tcc b/include/tweeny/tween.tcc index 545f11d..e91e171 100644 --- a/include/tweeny/tween.tcc +++ b/include/tweeny/tween.tcc @@ -49,9 +49,14 @@ auto tweeny::tween::find_key_frame_index template auto tweeny::tween::seek(const uint32_t target_frame) -> tween_value_t { + const std::size_t old_keyframe_index = current_keyframe_index; + const std::size_t new_keyframe_index = find_key_frame_index(target_frame); + current_value = render(target_frame); current_frame = target_frame; + current_keyframe_index = new_keyframe_index; + invoke_keyframe_listeners(old_keyframe_index, new_keyframe_index); invoke_listeners(seek_listeners); if (progress() >= 1.0f) { @@ -63,11 +68,15 @@ auto tweeny::tween::seek(const uint32_t template auto tweeny::tween::jump(std::size_t target_key_frame) -> tween_value_t { + const std::size_t old_keyframe_index = current_keyframe_index; target_key_frame = std::clamp(target_key_frame, static_cast(0), key_frames.size() - 1); + const auto target_frame = static_cast(key_frames[target_key_frame].position); current_value = render(target_frame); current_frame = target_frame; + current_keyframe_index = target_key_frame; + invoke_keyframe_listeners(old_keyframe_index, target_key_frame); invoke_listeners(jump_listeners); if (progress() >= 1.0f) { @@ -79,6 +88,8 @@ auto tweeny::tween::jump(std::size_t tar template auto tweeny::tween::step(const int32_t frames) -> tween_value_t { + const std::size_t old_keyframe_index = current_keyframe_index; + uint32_t target_frame = current_frame; if (frames < 0) { const auto dec = static_cast(-frames); @@ -88,9 +99,13 @@ auto tweeny::tween::step(const int32_t f target_frame += static_cast(frames); } + const std::size_t new_keyframe_index = find_key_frame_index(target_frame); + current_value = render(target_frame); current_frame = target_frame; + current_keyframe_index = new_keyframe_index; + invoke_keyframe_listeners(old_keyframe_index, new_keyframe_index); invoke_listeners(step_listeners); if (progress() >= 1.0f) { @@ -136,6 +151,24 @@ auto tweeny::tween::on(event::complete_t complete_listeners.emplace_back(std::forward(cb)); } +template +template +auto tweeny::tween::on(event::keyframeEnter_t, Callback && cb) -> void { + using result_t = std::invoke_result_t; + static_assert(std::is_same_v, + "keyframeEnter callback must return tweeny::event::response"); + keyframe_enter_listeners.emplace_back(std::forward(cb)); +} + +template +template +auto tweeny::tween::on(event::keyframeLeave_t, Callback && cb) -> void { + using result_t = std::invoke_result_t; + static_assert(std::is_same_v, + "keyframeLeave callback must return tweeny::event::response"); + keyframe_leave_listeners.emplace_back(std::forward(cb)); +} + template auto tweeny::tween::invoke_listeners(std::vector& listeners) -> void { std::vector to_remove; @@ -156,6 +189,41 @@ auto tweeny::tween::invoke_listeners(std } } +template +auto tweeny::tween::invoke_keyframe_listeners(const std::size_t old_keyframe_index, const std::size_t new_keyframe_index) -> void { + if (old_keyframe_index == new_keyframe_index) return; + + // Leave listeners + { + std::vector to_remove; + const struct event::keyframeLeave evt{old_keyframe_index}; + for (std::size_t i = 0; i < keyframe_leave_listeners.size(); ++i) { + const auto resp = keyframe_leave_listeners[i](*this, evt); + if (resp == event::response::unsubscribe) { + to_remove.push_back(i); + } + } + for (auto it = to_remove.rbegin(); it != to_remove.rend(); ++it) { + keyframe_leave_listeners.erase(keyframe_leave_listeners.begin() + static_cast::difference_type>(*it)); + } + } + + // Enter listeners + { + std::vector to_remove; + const struct event::keyframeEnter evt{new_keyframe_index}; + for (std::size_t i = 0; i < keyframe_enter_listeners.size(); ++i) { + const auto resp = keyframe_enter_listeners[i](*this, evt); + if (resp == event::response::unsubscribe) { + to_remove.push_back(i); + } + } + for (auto it = to_remove.rbegin(); it != to_remove.rend(); ++it) { + keyframe_enter_listeners.erase(keyframe_enter_listeners.begin() + static_cast::difference_type>(*it)); + } + } +} + template auto tweeny::tween::render(uint32_t target_frame) const -> tween_value_t { constexpr std::size_t ValuesCount = sizeof...(RemainingValueTypes) + 1; diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 8835716..ec91155 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -12,6 +12,8 @@ add_executable(tweeny_tests events/seek.cpp events/jump.cpp events/complete.cpp + events/keyframe_enter.cpp + events/keyframe_leave.cpp ) target_compile_features(tweeny_tests PRIVATE cxx_std_17) diff --git a/tests/events/keyframe_enter.cpp b/tests/events/keyframe_enter.cpp new file mode 100644 index 0000000..9220dfc --- /dev/null +++ b/tests/events/keyframe_enter.cpp @@ -0,0 +1,147 @@ +/* +This file is part of the Tweeny library. + +Copyright (c) 2016-2025 Leonardo Guilherme Lucena de Freitas +Copyright (c) 2016 Guilherme R. Costa + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ + +#include +#include "tweeny/tweeny.h" + +TEST_CASE("keyframeEnter event triggers when entering a new keyframe via step") { + auto t = tweeny::from(0).to(50).during(30U).to(100).during(30U).build(); + std::size_t entered_keyframe = 999; + int call_count = 0; + + t.on(tweeny::event::keyframeEnter, [&](auto&, struct tweeny::event::keyframeEnter evt) { + entered_keyframe = evt.key_frame; + call_count++; + return tweeny::event::response::ok; + }); + + t.step(31); + + REQUIRE(call_count == 1); + REQUIRE(entered_keyframe == 1); +} + +TEST_CASE("keyframeEnter event triggers when entering a new keyframe via seek") { + auto t = tweeny::from(0).to(50).during(30U).to(100).during(30U).build(); + std::size_t entered_keyframe = 999; + int call_count = 0; + + t.on(tweeny::event::keyframeEnter, [&](auto&, struct tweeny::event::keyframeEnter evt) { + entered_keyframe = evt.key_frame; + call_count++; + return tweeny::event::response::ok; + }); + + t.seek(35U); + + REQUIRE(call_count == 1); + REQUIRE(entered_keyframe == 1); +} + +TEST_CASE("keyframeEnter event triggers when entering a new keyframe via jump") { + auto t = tweeny::from(0).to(50).during(30U).to(100).during(30U).build(); + std::size_t entered_keyframe = 999; + int call_count = 0; + + t.on(tweeny::event::keyframeEnter, [&](auto&, struct tweeny::event::keyframeEnter evt) { + entered_keyframe = evt.key_frame; + call_count++; + return tweeny::event::response::ok; + }); + + t.jump(1); + + REQUIRE(call_count == 1); + REQUIRE(entered_keyframe == 1); +} + +TEST_CASE("keyframeEnter event does not trigger when staying in the same keyframe") { + auto t = tweeny::from(0).to(50).during(30U).to(100).during(30U).build(); + int call_count = 0; + + t.on(tweeny::event::keyframeEnter, [&](auto&, struct tweeny::event::keyframeEnter) { + call_count++; + return tweeny::event::response::ok; + }); + + t.step(10); + t.step(5); + t.step(10); + + REQUIRE(call_count == 0); +} + +TEST_CASE("keyframeEnter event triggers for the correct keyframe in multi-keyframe tween") { + auto t = tweeny::from(0).to(25).during(10U).to(50).during(10U).to(75).during(10U).to(100).during(10U).build(); + std::vector entered_keyframes; + + t.on(tweeny::event::keyframeEnter, [&](auto&, struct tweeny::event::keyframeEnter evt) { + entered_keyframes.push_back(evt.key_frame); + return tweeny::event::response::ok; + }); + + t.step(11); + t.step(10); + t.step(10); + + REQUIRE(entered_keyframes.size() == 3); + REQUIRE(entered_keyframes[0] == 1); + REQUIRE(entered_keyframes[1] == 2); + REQUIRE(entered_keyframes[2] == 3); +} + +TEST_CASE("keyframeEnter event can unsubscribe") { + auto t = tweeny::from(0).to(50).during(30U).to(100).during(30U).build(); + int call_count = 0; + + t.on(tweeny::event::keyframeEnter, [&](auto&, struct tweeny::event::keyframeEnter) { + call_count++; + return tweeny::event::response::unsubscribe; + }); + + t.step(31); + t.seek(0U); + t.step(31); + + REQUIRE(call_count == 1); +} + +TEST_CASE("keyframeEnter event triggers when stepping backward into a different keyframe") { + auto t = tweeny::from(0).to(50).during(30U).to(100).during(30U).build(); + std::size_t entered_keyframe = 999; + int call_count = 0; + + t.on(tweeny::event::keyframeEnter, [&](auto&, struct tweeny::event::keyframeEnter evt) { + entered_keyframe = evt.key_frame; + call_count++; + return tweeny::event::response::ok; + }); + + t.step(35); + call_count = 0; + t.step(-10); + + REQUIRE(call_count == 1); + REQUIRE(entered_keyframe == 0); +} diff --git a/tests/events/keyframe_leave.cpp b/tests/events/keyframe_leave.cpp new file mode 100644 index 0000000..f989f26 --- /dev/null +++ b/tests/events/keyframe_leave.cpp @@ -0,0 +1,168 @@ +/* +This file is part of the Tweeny library. + +Copyright (c) 2016-2025 Leonardo Guilherme Lucena de Freitas +Copyright (c) 2016 Guilherme R. Costa + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ + +#include +#include "tweeny/tweeny.h" + +TEST_CASE("keyframeLeave event triggers when leaving a keyframe via step") { + auto t = tweeny::from(0).to(50).during(30U).to(100).during(30U).build(); + std::size_t left_keyframe = 999; + int call_count = 0; + + t.on(tweeny::event::keyframeLeave, [&](auto&, struct tweeny::event::keyframeLeave evt) { + left_keyframe = evt.key_frame; + call_count++; + return tweeny::event::response::ok; + }); + + t.step(31); + + REQUIRE(call_count == 1); + REQUIRE(left_keyframe == 0); +} + +TEST_CASE("keyframeLeave event triggers when leaving a keyframe via seek") { + auto t = tweeny::from(0).to(50).during(30U).to(100).during(30U).build(); + std::size_t left_keyframe = 999; + int call_count = 0; + + t.on(tweeny::event::keyframeLeave, [&](auto&, struct tweeny::event::keyframeLeave evt) { + left_keyframe = evt.key_frame; + call_count++; + return tweeny::event::response::ok; + }); + + t.seek(35U); + + REQUIRE(call_count == 1); + REQUIRE(left_keyframe == 0); +} + +TEST_CASE("keyframeLeave event triggers when leaving a keyframe via jump") { + auto t = tweeny::from(0).to(50).during(30U).to(100).during(30U).build(); + std::size_t left_keyframe = 999; + int call_count = 0; + + t.on(tweeny::event::keyframeLeave, [&](auto&, struct tweeny::event::keyframeLeave evt) { + left_keyframe = evt.key_frame; + call_count++; + return tweeny::event::response::ok; + }); + + t.jump(1); + + REQUIRE(call_count == 1); + REQUIRE(left_keyframe == 0); +} + +TEST_CASE("keyframeLeave event does not trigger when staying in the same keyframe") { + auto t = tweeny::from(0).to(50).during(30U).to(100).during(30U).build(); + int call_count = 0; + + t.on(tweeny::event::keyframeLeave, [&](auto&, struct tweeny::event::keyframeLeave) { + call_count++; + return tweeny::event::response::ok; + }); + + t.step(10); + t.step(5); + t.step(10); + + REQUIRE(call_count == 0); +} + +TEST_CASE("keyframeLeave event triggers for the correct keyframe in multi-keyframe tween") { + auto t = tweeny::from(0).to(25).during(10U).to(50).during(10U).to(75).during(10U).to(100).during(10U).build(); + std::vector left_keyframes; + + t.on(tweeny::event::keyframeLeave, [&](auto&, struct tweeny::event::keyframeLeave evt) { + left_keyframes.push_back(evt.key_frame); + return tweeny::event::response::ok; + }); + + t.step(11); + t.step(10); + t.step(10); + + REQUIRE(left_keyframes.size() == 3); + REQUIRE(left_keyframes[0] == 0); + REQUIRE(left_keyframes[1] == 1); + REQUIRE(left_keyframes[2] == 2); +} + +TEST_CASE("keyframeLeave event can unsubscribe") { + auto t = tweeny::from(0).to(50).during(30U).to(100).during(30U).build(); + int call_count = 0; + + t.on(tweeny::event::keyframeLeave, [&](auto&, struct tweeny::event::keyframeLeave) { + call_count++; + return tweeny::event::response::unsubscribe; + }); + + t.step(31); + t.seek(0U); + t.step(31); + + REQUIRE(call_count == 1); +} + +TEST_CASE("keyframeLeave event triggers when stepping backward into a different keyframe") { + auto t = tweeny::from(0).to(50).during(30U).to(100).during(30U).build(); + std::size_t left_keyframe = 999; + int call_count = 0; + + t.on(tweeny::event::keyframeLeave, [&](auto&, struct tweeny::event::keyframeLeave evt) { + left_keyframe = evt.key_frame; + call_count++; + return tweeny::event::response::ok; + }); + + t.step(35); + call_count = 0; + t.step(-10); + + REQUIRE(call_count == 1); + REQUIRE(left_keyframe == 1); +} + +TEST_CASE("keyframeLeave and keyframeEnter events trigger in correct order") { + auto t = tweeny::from(0).to(50).during(30U).to(100).during(30U).build(); + std::vector event_order; + + t.on(tweeny::event::keyframeLeave, [&](auto&, struct tweeny::event::keyframeLeave evt) { + event_order.push_back("leave:" + std::to_string(evt.key_frame)); + return tweeny::event::response::ok; + }); + + t.on(tweeny::event::keyframeEnter, [&](auto&, struct tweeny::event::keyframeEnter evt) { + event_order.push_back("enter:" + std::to_string(evt.key_frame)); + return tweeny::event::response::ok; + }); + + t.step(31); + + REQUIRE(event_order.size() == 2); + REQUIRE(event_order[0] == "leave:0"); + REQUIRE(event_order[1] == "enter:1"); +} From 721a8ee3a078b5ba99203a5d601f9787c1cd1351 Mon Sep 17 00:00:00 2001 From: Leonardo Date: Thu, 25 Dec 2025 21:08:30 -0300 Subject: [PATCH 38/58] reorganize project structure and bump version to 4.0.0 --- CMakeLists.txt | 6 +++--- {doc => src/doc}/CMakeLists.txt | 8 ++++---- {doc => src/doc}/Doxyfile.in | 0 {doc => src/doc}/DoxygenLayout.xml | 0 {doc => src/doc}/MANUAL.dox | 0 {tests => src/tests}/CMakeLists.txt | 0 {tests => src/tests}/events/complete.cpp | 0 {tests => src/tests}/events/jump.cpp | 0 {tests => src/tests}/events/keyframe_enter.cpp | 0 {tests => src/tests}/events/keyframe_leave.cpp | 0 {tests => src/tests}/events/seek.cpp | 0 {tests => src/tests}/events/step.cpp | 0 {tests => src/tests}/sanity.cpp | 0 {tests => src/tests}/tween/jump.cpp | 0 {tests => src/tests}/tween/peek.cpp | 0 {tests => src/tests}/tween/progress.cpp | 0 {tests => src/tests}/tween/seek.cpp | 0 {tests => src/tests}/tween/step.cpp | 0 18 files changed, 7 insertions(+), 7 deletions(-) rename {doc => src/doc}/CMakeLists.txt (85%) rename {doc => src/doc}/Doxyfile.in (100%) rename {doc => src/doc}/DoxygenLayout.xml (100%) rename {doc => src/doc}/MANUAL.dox (100%) rename {tests => src/tests}/CMakeLists.txt (100%) rename {tests => src/tests}/events/complete.cpp (100%) rename {tests => src/tests}/events/jump.cpp (100%) rename {tests => src/tests}/events/keyframe_enter.cpp (100%) rename {tests => src/tests}/events/keyframe_leave.cpp (100%) rename {tests => src/tests}/events/seek.cpp (100%) rename {tests => src/tests}/events/step.cpp (100%) rename {tests => src/tests}/sanity.cpp (100%) rename {tests => src/tests}/tween/jump.cpp (100%) rename {tests => src/tests}/tween/peek.cpp (100%) rename {tests => src/tests}/tween/progress.cpp (100%) rename {tests => src/tests}/tween/seek.cpp (100%) rename {tests => src/tests}/tween/step.cpp (100%) diff --git a/CMakeLists.txt b/CMakeLists.txt index f03bd7c..43d0ab7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -27,7 +27,7 @@ cmake_minimum_required(VERSION 3.23...3.28) cmake_policy(SET CMP0063 NEW) -project(Tweeny LANGUAGES CXX VERSION 3.2.0) +project(Tweeny LANGUAGES CXX VERSION 4.0.0) # Enforce C++17 for targets built in this project set(CMAKE_CXX_STANDARD 17) @@ -94,12 +94,12 @@ install(TARGETS tweeny EXPORT TweenyTargets FILE_SET HEADERS) include(cmake/SetupExports.cmake) if (TWEENY_BUILD_DOCUMENTATION) - add_subdirectory(doc) + add_subdirectory(src/doc) endif () if (TWEENY_BUILD_TESTS) enable_testing() - add_subdirectory(tests) + add_subdirectory(src/tests) endif () if (TWEENY_BUILD_SINGLE_HEADER) diff --git a/doc/CMakeLists.txt b/src/doc/CMakeLists.txt similarity index 85% rename from doc/CMakeLists.txt rename to src/doc/CMakeLists.txt index 68b7b74..3517a62 100644 --- a/doc/CMakeLists.txt +++ b/src/doc/CMakeLists.txt @@ -24,11 +24,11 @@ find_package(Doxygen REQUIRED) -configure_file(${CMAKE_CURRENT_SOURCE_DIR}/Doxyfile.in ${CMAKE_CURRENT_BINARY_DIR}/Doxyfile @ONLY) -file(COPY ${CMAKE_CURRENT_SOURCE_DIR}/../README.md DESTINATION ${CMAKE_CURRENT_BINARY_DIR}) +configure_file(Doxyfile.in ${CMAKE_CURRENT_BINARY_DIR}/Doxyfile @ONLY) +file(COPY ../../README.md DESTINATION ${CMAKE_CURRENT_BINARY_DIR}) file(COPY - ${CMAKE_CURRENT_SOURCE_DIR}/DoxygenLayout.xml - ${CMAKE_CURRENT_SOURCE_DIR}/MANUAL.dox + DoxygenLayout.xml + MANUAL.dox DESTINATION ${CMAKE_CURRENT_BINARY_DIR} ) diff --git a/doc/Doxyfile.in b/src/doc/Doxyfile.in similarity index 100% rename from doc/Doxyfile.in rename to src/doc/Doxyfile.in diff --git a/doc/DoxygenLayout.xml b/src/doc/DoxygenLayout.xml similarity index 100% rename from doc/DoxygenLayout.xml rename to src/doc/DoxygenLayout.xml diff --git a/doc/MANUAL.dox b/src/doc/MANUAL.dox similarity index 100% rename from doc/MANUAL.dox rename to src/doc/MANUAL.dox diff --git a/tests/CMakeLists.txt b/src/tests/CMakeLists.txt similarity index 100% rename from tests/CMakeLists.txt rename to src/tests/CMakeLists.txt diff --git a/tests/events/complete.cpp b/src/tests/events/complete.cpp similarity index 100% rename from tests/events/complete.cpp rename to src/tests/events/complete.cpp diff --git a/tests/events/jump.cpp b/src/tests/events/jump.cpp similarity index 100% rename from tests/events/jump.cpp rename to src/tests/events/jump.cpp diff --git a/tests/events/keyframe_enter.cpp b/src/tests/events/keyframe_enter.cpp similarity index 100% rename from tests/events/keyframe_enter.cpp rename to src/tests/events/keyframe_enter.cpp diff --git a/tests/events/keyframe_leave.cpp b/src/tests/events/keyframe_leave.cpp similarity index 100% rename from tests/events/keyframe_leave.cpp rename to src/tests/events/keyframe_leave.cpp diff --git a/tests/events/seek.cpp b/src/tests/events/seek.cpp similarity index 100% rename from tests/events/seek.cpp rename to src/tests/events/seek.cpp diff --git a/tests/events/step.cpp b/src/tests/events/step.cpp similarity index 100% rename from tests/events/step.cpp rename to src/tests/events/step.cpp diff --git a/tests/sanity.cpp b/src/tests/sanity.cpp similarity index 100% rename from tests/sanity.cpp rename to src/tests/sanity.cpp diff --git a/tests/tween/jump.cpp b/src/tests/tween/jump.cpp similarity index 100% rename from tests/tween/jump.cpp rename to src/tests/tween/jump.cpp diff --git a/tests/tween/peek.cpp b/src/tests/tween/peek.cpp similarity index 100% rename from tests/tween/peek.cpp rename to src/tests/tween/peek.cpp diff --git a/tests/tween/progress.cpp b/src/tests/tween/progress.cpp similarity index 100% rename from tests/tween/progress.cpp rename to src/tests/tween/progress.cpp diff --git a/tests/tween/seek.cpp b/src/tests/tween/seek.cpp similarity index 100% rename from tests/tween/seek.cpp rename to src/tests/tween/seek.cpp diff --git a/tests/tween/step.cpp b/src/tests/tween/step.cpp similarity index 100% rename from tests/tween/step.cpp rename to src/tests/tween/step.cpp From 3e16e8106ef99f421ebd10c036cad7561eeea798 Mon Sep 17 00:00:00 2001 From: Leonardo Date: Thu, 25 Dec 2025 23:02:54 -0300 Subject: [PATCH 39/58] update include paths to match new project structure --- include/tweeny/easing.h | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/include/tweeny/easing.h b/include/tweeny/easing.h index 95911bc..3a3c4ae 100644 --- a/include/tweeny/easing.h +++ b/include/tweeny/easing.h @@ -32,19 +32,19 @@ #ifndef TWEENY_EASING_H #define TWEENY_EASING_H -#include "tweeny/detail/easing/back.h" -#include "tweeny/detail/easing/bounce.h" -#include "tweeny/detail/easing/circular.h" -#include "tweeny/detail/easing/cubic.h" -#include "tweeny/detail/easing/def.h" -#include "tweeny/detail/easing/elastic.h" -#include "tweeny/detail/easing/exponential.h" -#include "tweeny/detail/easing/linear.h" -#include "tweeny/detail/easing/quadratic.h" -#include "tweeny/detail/easing/quartic.h" -#include "tweeny/detail/easing/quintic.h" -#include "tweeny/detail/easing/sinusoidal.h" -#include "tweeny/detail/easing/stepped.h" +#include "detail/easing/back.h" +#include "detail/easing/bounce.h" +#include "detail/easing/circular.h" +#include "detail/easing/cubic.h" +#include "detail/easing/def.h" +#include "detail/easing/elastic.h" +#include "detail/easing/exponential.h" +#include "detail/easing/linear.h" +#include "detail/easing/quadratic.h" +#include "detail/easing/quartic.h" +#include "detail/easing/quintic.h" +#include "detail/easing/sinusoidal.h" +#include "detail/easing/stepped.h" namespace tweeny::easing { inline constexpr detail::backInEasing backIn{}; From 04659bcaa751857381c6cb1fc4a8c00c939caac6 Mon Sep 17 00:00:00 2001 From: Leonardo Date: Thu, 25 Dec 2025 23:03:42 -0300 Subject: [PATCH 40/58] document tweeny::easing namespace and its purpose --- include/tweeny/easing.h | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/include/tweeny/easing.h b/include/tweeny/easing.h index 3a3c4ae..72b1a55 100644 --- a/include/tweeny/easing.h +++ b/include/tweeny/easing.h @@ -46,6 +46,13 @@ #include "detail/easing/sinusoidal.h" #include "detail/easing/stepped.h" +/** + * @namespace tweeny::easing + * @brief Contains all built-in easing functions for controlling animation curves. + * + * Provides 30+ easing functions in In, Out, and InOut variants for creating natural-looking + * animations. See the @ref easings page for detailed descriptions and usage examples. + */ namespace tweeny::easing { inline constexpr detail::backInEasing backIn{}; inline constexpr detail::backOutEasing backOut{}; From c633f4c3f86035c07766f1d0d5d2f5b8170a0a44 Mon Sep 17 00:00:00 2001 From: Leonardo Date: Thu, 25 Dec 2025 23:06:27 -0300 Subject: [PATCH 41/58] document backIn, backOut, and backInOut easing functions --- include/tweeny/easing.h | 109 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 109 insertions(+) diff --git a/include/tweeny/easing.h b/include/tweeny/easing.h index 72b1a55..1682243 100644 --- a/include/tweeny/easing.h +++ b/include/tweeny/easing.h @@ -54,8 +54,117 @@ * animations. See the @ref easings page for detailed descriptions and usage examples. */ namespace tweeny::easing { + /** + * @brief Easing function that creates anticipation by pulling back before moving forward. + * + * The `backIn` easing function begins by moving backwards slightly (overshooting in the + * negative direction), then reverses and accelerates toward the target value. This creates + * an anticipation effect similar to pulling back a slingshot or winding up before a throw. + * + * The backward motion at the start makes this easing particularly effective for animations + * that benefit from telegraphing or anticipation, such as: + * - UI elements that "wind up" before sliding in + * - Character movements that show preparation before action + * - Camera movements that pull back before zooming forward + * + * Mathematically, this easing uses the formula: `c * t * t * ((s + 1) * t - s) + b` + * where `s` controls the overshoot amount (typically 1.70158). + * + * @note The animation will briefly have values less than the start value before proceeding + * to the target. Ensure your animation system can handle values outside the expected range. + * + * @code + * // Create a tween with back-in easing for anticipation effect + * auto tween = tweeny::from(0.0f) + * .to(100.0f) + * .via(easing::backIn) + * .during(60U) + * .build(); + * + * // The animation will dip below 0.0f briefly before accelerating to 100.0f + * @endcode + * + * @see backOut For overshoot at the end instead of the beginning + * @see backInOut For anticipation at the start and overshoot at the end + */ inline constexpr detail::backInEasing backIn{}; + + /** + * @brief Easing function that overshoots the target before settling back. + * + * The `backOut` easing function accelerates toward and past the target value, then pulls + * back to settle at the final position. This creates an overshoot or "spring-back" effect + * that adds liveliness and energy to animations. + * + * This easing is particularly effective for: + * - UI elements that pop into place with energy (buttons, dialogs, notifications) + * - Object arrivals that should feel bouncy and dynamic + * - Emphasizing the end state of an animation + * - Adding personality to mechanical movements + * + * The overshoot creates a more natural, less rigid feel compared to standard easing + * functions. It's widely used in iOS, Android, and Material Design animation patterns + * to make interactions feel more responsive and playful. + * + * Mathematically, this is the inverse of backIn, applied at the end of the transition. + * + * @note The animation will briefly exceed the target value before settling. Ensure your + * rendering or logic can handle values beyond the specified range. + * + * @code + * // Animate a button with overshoot for a lively effect + * auto scale = tweeny::from(0.0f) + * .to(1.0f) + * .via(easing::backOut) + * .during(30U) + * .build(); + * + * // Scale will exceed 1.0f (e.g., 1.1f) before settling back to 1.0f + * @endcode + * + * @see backIn For anticipation at the start instead of overshoot at the end + * @see backInOut For both anticipation and overshoot + * @see elasticOut For a more pronounced oscillating overshoot effect + */ inline constexpr detail::backOutEasing backOut{}; + + /** + * @brief Easing function combining anticipation at the start and overshoot at the end. + * + * The `backInOut` easing function creates a dramatic motion curve by pulling back before + * the start (anticipation), accelerating through the middle, then overshooting past the + * target before settling (spring-back). This combines the effects of both backIn and + * backOut for maximum expressiveness. + * + * This easing creates highly dynamic animations suitable for: + * - Attention-grabbing transitions that need maximum visual interest + * - Character animations requiring wind-up and follow-through + * - Scene transitions with dramatic flair + * - Emphasizing both the start and end states of an animation + * + * The dual overshoot (negative at start, positive at end) makes this one of the most + * expressive easings, but it should be used judiciously as it can feel exaggerated if + * overused. It works best for focal animations rather than ambient motion. + * + * @note The animation will have values outside the start-to-target range at both ends. + * During the first half, values will dip below the start; during the second half, values + * will exceed the target before settling. + * + * @code + * // Create a dramatic page transition + * auto position = tweeny::from(-100.0f) + * .to(100.0f) + * .via(easing::backInOut) + * .during(90U) + * .build(); + * + * // Position will go below -100.0f at start and above 100.0f near end + * @endcode + * + * @see backIn For only anticipation without overshoot + * @see backOut For only overshoot without anticipation + * @see elasticInOut For a more extreme oscillating version + */ inline constexpr detail::backInOutEasing backInOut{}; inline constexpr detail::bounceInEasing bounceIn{}; inline constexpr detail::bounceOutEasing bounceOut{}; From b467b20a61c547da0fd4b966fff8b29d1bdce503 Mon Sep 17 00:00:00 2001 From: Leonardo Date: Thu, 25 Dec 2025 23:06:46 -0300 Subject: [PATCH 42/58] document bounceIn, bounceOut, and bounceInOut easing functions --- include/tweeny/easing.h | 121 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 121 insertions(+) diff --git a/include/tweeny/easing.h b/include/tweeny/easing.h index 1682243..ba79e23 100644 --- a/include/tweeny/easing.h +++ b/include/tweeny/easing.h @@ -166,8 +166,129 @@ namespace tweeny::easing { * @see elasticInOut For a more extreme oscillating version */ inline constexpr detail::backInOutEasing backInOut{}; + + /** + * @brief Bounce easing simulating a ball bouncing with increasing height at the start. + * + * The `bounceIn` easing function creates a bouncing effect before the animation begins, + * like a ball dropped from above that bounces progressively higher before launching into + * the main motion. This is the reverse of bounceOut's natural physics. + * + * Characteristics: + * - Multiple discrete "bounces" at the start + * - Each bounce is higher than the previous + * - Creates anticipation through impact-based motion + * - Less commonly used than bounceOut + * + * This easing works well for: + * - **Landing preparation**: Elements about to drop into place + * - **Impact anticipation**: Building up to a collision + * - **Reverse playback effects**: Bounced animations played backward + * - **Stylized entrances**: Cartoon-style wind-up effects + * + * BounceIn is less intuitive than bounceOut because it reverses natural physics (balls + * don't normally bounce higher each time). Consider backIn for more natural anticipation. + * + * @code + * // Element that bounces before sliding in + * auto position = tweeny::from(0.0f) + * .to(200.0f) + * .via(easing::bounceIn) + * .during(50U) + * .build(); + * @endcode + * + * @see bounceOut For natural ball-drop bouncing at the end + * @see bounceInOut For bouncing at both start and end + * @see backIn For simpler anticipation without discrete impacts + */ inline constexpr detail::bounceInEasing bounceIn{}; + + /** + * @brief Bounce easing simulating a ball dropping and bouncing to rest. + * + * The `bounceOut` easing function creates the classic ball-drop effect where an object + * bounces several times with decreasing height before coming to rest. This mimics real-world + * physics of an inelastic collision and is one of the most recognizable easing patterns. + * + * Characteristics: + * - Multiple discrete bounces with decreasing amplitude + * - Simulates impact and energy loss + * - Creates playful, physical motion + * - Widely recognized and understood by users + * + * This easing excels at: + * - **Object drops**: Items falling into place + * - **Landing animations**: Characters, UI elements touching down + * - **Playful interactions**: Buttons, icons, notifications + * - **Game elements**: Collectibles, power-ups, score displays + * - **Error/success feedback**: Visual confirmation with personality + * - **Cartoon physics**: Exaggerated, entertaining motion + * + * BounceOut is more popular than elasticOut when you want discrete impacts rather than + * smooth oscillation. It conveys weight and physicality better than spring-based easings. + * + * The bouncing creates natural emphasis on the final position, making it excellent for + * drawing attention to where something lands. + * + * @code + * // Notification dropping in from above + * auto y = tweeny::from(-100.0f) + * .to(0.0f) + * .via(easing::bounceOut) + * .during(45U) + * .build(); + * + * // Button that bounces into place + * auto scale = tweeny::from(0.0f) + * .to(1.0f) + * .via(easing::bounceOut) + * .during(40U) + * .build(); + * @endcode + * + * @see bounceIn For inverse bouncing at the start + * @see bounceInOut For bouncing at both ends + * @see elasticOut For smooth spring-like alternative + */ inline constexpr detail::bounceOutEasing bounceOut{}; + + /** + * @brief Bounce easing with bouncing at both start and end. + * + * The `bounceInOut` easing function combines reverse bouncing at the start with natural + * bouncing at the end. The motion bounces with increasing height initially, accelerates + * through the middle, then bounces to rest at the target. + * + * Motion profile: + * - First half: Bounces with increasing amplitude (bounceIn) + * - Midpoint: Smooth transition + * - Second half: Bounces with decreasing amplitude (bounceOut) + * - Creates playful, impact-based motion at both ends + * + * This easing is appropriate for: + * - **Playful transitions**: Fun, energetic scene changes + * - **Game UI**: High-energy, cartoon-style interfaces + * - **Children's applications**: Whimsical, entertaining motion + * - **Attention-grabbing effects**: Elements that need maximum personality + * + * BounceInOut is quite dramatic and can feel excessive for business applications, + * productivity tools, and enterprise software where subtlety is preferred. The dual + * bouncing works best in contexts where playfulness is a design goal. + * + * @code + * // Playful modal transition + * auto scale = tweeny::from(0.0f) + * .to(1.0f) + * .via(easing::bounceInOut) + * .during(60U) + * .build(); + * @endcode + * + * @see bounceIn For bouncing only at the start + * @see bounceOut For bouncing only at the end (more commonly useful) + * @see elasticInOut For spring-like alternative + */ inline constexpr detail::bounceInOutEasing bounceInOut{}; inline constexpr detail::circularInEasing circularIn{}; inline constexpr detail::circularOutEasing circularOut{}; From 39cd2b20fa5563f15d11d662570dce6b4153958e Mon Sep 17 00:00:00 2001 From: Leonardo Date: Thu, 25 Dec 2025 23:07:01 -0300 Subject: [PATCH 43/58] document circularIn, circularOut, and circularInOut easing functions --- include/tweeny/easing.h | 112 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 112 insertions(+) diff --git a/include/tweeny/easing.h b/include/tweeny/easing.h index ba79e23..3481b03 100644 --- a/include/tweeny/easing.h +++ b/include/tweeny/easing.h @@ -290,8 +290,120 @@ namespace tweeny::easing { * @see elasticInOut For spring-like alternative */ inline constexpr detail::bounceInOutEasing bounceInOut{}; + + /** + * @brief Circular easing based on quarter-circle arc for smooth acceleration. + * + * The `circularIn` easing function accelerates following the curve of a quarter circle, + * creating smooth, gradual acceleration. The motion follows the equation `1 - sqrt(1 - t²)`, + * which traces a circular arc. + * + * Characteristics: + * - Smooth, continuous acceleration + * - More gradual than quadratic, less than cubic + * - No sharp transitions in velocity + * - Mathematically elegant curve + * + * This easing is ideal for: + * - **Natural motion**: Movements that need organic feel + * - **Camera movements**: Smooth pans and zooms + * - **Scroll animations**: Gentle acceleration for reading comfort + * - **Subtle UI transitions**: Motion with moderate acceleration + * + * Circular easings strike a balance between the gentle quadratic and the more aggressive + * cubic curves, making them versatile for many contexts. + * + * @code + * // Smooth camera pan + * auto cameraX = tweeny::from(0.0f) + * .to(1000.0f) + * .via(easing::circularIn) + * .during(90U) + * .build(); + * @endcode + * + * @see circularOut For circular deceleration + * @see circularInOut For circular acceleration and deceleration + * @see quadraticIn For gentler acceleration + * @see cubicIn For stronger acceleration + */ inline constexpr detail::circularInEasing circularIn{}; + + /** + * @brief Circular easing based on quarter-circle arc for smooth deceleration. + * + * The `circularOut` easing function decelerates following a circular curve, creating + * smooth, natural-looking motion that settles gently to rest. Popular for UI animations + * requiring moderate deceleration without dramatic emphasis. + * + * Characteristics: + * - Smooth, continuous deceleration + * - Natural feeling without being too soft or aggressive + * - Balanced between gentle and pronounced + * - Widely used in production interfaces + * + * This easing excels at: + * - **Modern UI animations**: Cards, panels, menus + * - **Material Design patterns**: Following Google's motion guidelines + * - **Content transitions**: Page changes, tab switches + * - **Professional applications**: Business and productivity software + * - **General-purpose motion**: Versatile for many animation types + * + * CircularOut works well in most contexts. It's less dramatic than cubic but with + * smoother deceleration than quadratic. + * + * @code + * // Menu panel sliding in + * auto x = tweeny::from(-300.0f) + * .to(0.0f) + * .via(easing::circularOut) + * .during(35U) + * .build(); + * @endcode + * + * @see circularIn For circular acceleration + * @see circularInOut For circular motion at both ends + * @see quadraticOut For gentler alternative + * @see cubicOut For more pronounced alternative + */ inline constexpr detail::circularOutEasing circularOut{}; + + /** + * @brief Circular easing with smooth acceleration and deceleration. + * + * The `circularInOut` easing function uses circular curves for both acceleration and + * deceleration, creating balanced motion suitable for general UI work. This is a + * popular all-purpose easing. + * + * Motion profile: + * - First half: Circular acceleration + * - Midpoint: Maximum velocity + * - Second half: Circular deceleration + * - Creates smooth S-curve + * + * This easing is appropriate for: + * - **All-purpose UI animations**: Buttons, dialogs, drawers + * - **Material Design**: Recommended in Google's motion guidelines + * - **Business applications**: Productivity tools, enterprise apps + * - **Default animation choice**: Safe, versatile motion curve + * + * CircularInOut is often recommended as a starting point for animations because it + * provides polish without being too subtle or too dramatic. + * + * @code + * // Modal dialog appearance + * auto opacity = tweeny::from(0.0f) + * .to(1.0f) + * .via(easing::circularInOut) + * .during(25U) + * .build(); + * @endcode + * + * @see circularIn For only acceleration + * @see circularOut For only deceleration + * @see quadraticInOut For gentler motion + * @see cubicInOut For more dramatic motion + */ inline constexpr detail::circularInOutEasing circularInOut{}; inline constexpr detail::cubicInEasing cubicIn{}; inline constexpr detail::cubicOutEasing cubicOut{}; From 262481eac410318a3147cffa21000e8cae09ffbf Mon Sep 17 00:00:00 2001 From: Leonardo Date: Thu, 25 Dec 2025 23:07:26 -0300 Subject: [PATCH 44/58] document cubic, elastic, and default easing functions --- include/tweeny/easing.h | 251 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 251 insertions(+) diff --git a/include/tweeny/easing.h b/include/tweeny/easing.h index 3481b03..b1abbe5 100644 --- a/include/tweeny/easing.h +++ b/include/tweeny/easing.h @@ -405,11 +405,262 @@ namespace tweeny::easing { * @see cubicInOut For more dramatic motion */ inline constexpr detail::circularInOutEasing circularInOut{}; + + /** + * @brief Cubic polynomial easing (t³) with moderate acceleration. + * + * The `cubicIn` easing function uses a cubic power curve for acceleration, providing + * smooth, noticeable easing that's more pronounced than quadratic but less extreme + * than quartic or exponential. + * + * Characteristics: + * - Polynomial acceleration with power of 3 + * - Balanced between gentle and dramatic + * - Widely used and well-understood + * - Good default for many animation types + * + * This easing is ideal for: + * - **Standard UI animations**: Menus, panels, overlays + * - **Easing beginners**: Easy to understand and predict + * - **General-purpose acceleration**: Versatile for many contexts + * - **Medium-length animations**: 200-500ms durations + * + * CubicIn is a popular choice because it provides clear easing without being subtle + * (like quadratic) or extreme (like quartic/exponential). + * + * Mathematically: `f(t) = t³` + * + * @code + * // Dropdown menu opening + * auto height = tweeny::from(0.0f) + * .to(200.0f) + * .via(easing::cubicIn) + * .during(30U) + * .build(); + * @endcode + * + * @see cubicOut For cubic deceleration + * @see cubicInOut For cubic motion at both ends + * @see quadraticIn For gentler acceleration + * @see quarticIn For stronger acceleration + */ inline constexpr detail::cubicInEasing cubicIn{}; + + /** + * @brief Cubic polynomial easing (t³) with moderate deceleration. + * + * The `cubicOut` easing function provides smooth, balanced deceleration that's popular + * across many design systems. It's pronounced enough to be noticeable but not dramatic. + * + * Characteristics: + * - Smooth, natural-feeling deceleration + * - More pronounced than quadratic, gentler than quartic + * - Industry-standard motion curve + * - Works well for most animation types + * + * This easing excels at: + * - **Web interfaces**: Following CSS animation best practices + * - **Mobile UI**: iOS and Android design patterns + * - **Content animations**: Cards, lists, grids + * - **Default easing choice**: Safe for most situations + * - **User-triggered actions**: Button presses, toggles, switches + * + * CubicOut is one of the most commonly used easings in production interfaces. It + * provides clear polish without drawing excessive attention to the motion itself. + * + * Mathematically: `f(t) = 1 - (1-t)³` + * + * @code + * // Button press feedback + * auto scale = tweeny::from(1.0f) + * .to(0.95f) + * .via(easing::cubicOut) + * .during(10U) + * .build(); + * + * // Card appearing + * auto opacity = tweeny::from(0.0f) + * .to(1.0f) + * .via(easing::cubicOut) + * .during(25U) + * .build(); + * @endcode + * + * @see cubicIn For cubic acceleration + * @see cubicInOut For cubic motion at both ends + * @see quadraticOut For gentler deceleration + * @see quarticOut For stronger deceleration + */ inline constexpr detail::cubicOutEasing cubicOut{}; + + /** + * @brief Cubic polynomial easing with balanced acceleration and deceleration. + * + * The `cubicInOut` easing function combines cubic acceleration and deceleration for + * smooth, professional motion. This is one of the most popular general-purpose easings. + * + * Motion profile: + * - First half: Cubic acceleration (t³) + * - Midpoint: Maximum velocity + * - Second half: Cubic deceleration + * - Creates smooth, balanced S-curve + * + * This easing is appropriate for: + * - **All-purpose animations**: When in doubt, use this + * - **Web standards**: CSS ease-in-out equivalent + * - **Design system defaults**: Common in component libraries + * - **Cross-platform consistency**: Works well everywhere + * - **Medium animations**: 200-500ms sweet spot + * + * CubicInOut is frequently the default easing in design systems and animation libraries + * because it provides clear, professional motion that works for most scenarios. + * + * Mathematically: Combines cubicIn for t < 0.5 and cubicOut for t >= 0.5 + * + * @code + * // Page transition + * auto x = tweeny::from(0.0f) + * .to(1920.0f) + * .via(easing::cubicInOut) + * .during(40U) + * .build(); + * @endcode + * + * @see cubicIn For only acceleration + * @see cubicOut For only deceleration + * @see quadraticInOut For gentler motion + * @see quarticInOut For more dramatic motion + */ inline constexpr detail::cubicInOutEasing cubicInOut{}; + /** + * @brief Default easing function, alias for linear easing. + * + * The `def` easing is a convenience alias for `linear`, providing the same constant-velocity + * interpolation with no acceleration or deceleration. It exists as a semantic indicator that + * the default easing behavior is being explicitly chosen. + * + * Using `def` instead of `linear` can make code intent clearer in contexts where you want to + * explicitly state "use the default behavior" rather than specifically requesting linear motion. + * However, both are functionally identical. + * + * This is the easing applied when no via() call is made in the tween builder. + * + * @code + * // These three tweens are functionally identical: + * auto t1 = tweeny::from(0).to(100).during(60U).build(); // Implicit default + * auto t2 = tweeny::from(0).to(100).via(easing::def).during(60U).build(); + * auto t3 = tweeny::from(0).to(100).via(easing::linear).during(60U).build(); + * @endcode + * + * @see linear For the primary documentation of this easing behavior + */ inline constexpr detail::defaultEasing def{}; + + /** + * @brief Elastic easing with oscillating spring-like motion at the start. + * + * The `elasticIn` easing function creates a spring or elastic band effect that oscillates + * with increasing amplitude before reaching the starting point of the animation. The motion + * resembles pulling back an elastic band that vibrates as tension builds. + * + * The oscillation characteristics: + * - Multiple back-and-forth swings before the main motion begins + * - Amplitude increases as the animation progresses + * - Creates a "winding up" or "charging" effect + * - More pronounced than backIn's single overshoot + * + * This easing is particularly effective for: + * - **Magical or fantasy effects**: Spell charging, energy gathering + * - **Exaggerated cartoon animations**: Extreme anticipation and wind-up + * - **Attention-grabbing entrances**: Elements that need dramatic introduction + * - **Game power-ups**: Visual feedback for charging actions + * - **Playful UI elements**: Whimsical, high-energy interactions + * + * The elastic effect is more dramatic than back easing, making it suitable for contexts + * where strong visual emphasis or entertainment value is desired. It's less appropriate + * for subtle or professional interfaces. + * + * Mathematically, this uses a decaying sine wave with exponential amplitude growth. + * + * @warning This easing creates significant overshoot in both directions. Values will + * oscillate well beyond the start value in both positive and negative directions. Ensure + * your rendering system can handle these extreme values gracefully. + * + * @code + * // Magical charging effect before an action + * auto glow = tweeny::from(0.0f) + * .to(1.0f) + * .via(easing::elasticIn) + * .during(45U) + * .build(); + * + * // The glow will oscillate negative before reaching 1.0f + * // producing a "charging" visual effect + * @endcode + * + * @see elasticOut For spring-like oscillation at the end + * @see elasticInOut For oscillation at both start and end + * @see backIn For a simpler single-overshoot alternative + */ inline constexpr detail::elasticInEasing elasticIn{}; + + /** + * @brief Elastic easing with spring-like oscillation at the end. + * + * The `elasticOut` easing function creates a natural spring or rubber band effect that + * overshoots and oscillates around the target value before settling. This is one of the + * most visually distinctive and playful easings, mimicking real-world elastic physics. + * + * The oscillation characteristics: + * - Overshoots the target value multiple times + * - Amplitude decreases with each oscillation (damped motion) + * - Settles naturally at the final value + * - Creates a bouncy, energetic feel without the hard impacts of bounce easing + * + * This easing excels at: + * - **Playful UI animations**: Buttons, toggles, modal appearances + * - **Game elements**: Power-up notifications, achievement popups, score displays + * - **Cartoon-style motion**: Exaggerated, entertaining character movements + * - **Attention direction**: Drawing eyes to important elements + * - **Spring simulation**: Rubber bands, diving boards, springy objects + * - **Joyful interactions**: Adding personality to standard UI patterns + * + * ElasticOut is widely used in modern mobile UI design to add energy and delight to + * interactions. It's more pronounced than backOut but smoother than bounceOut. + * + * The spring feel makes animations memorable and adds perceived responsiveness, making + * interfaces feel "alive" rather than mechanical. + * + * Mathematically, this uses a decaying sine wave with exponentially decreasing amplitude. + * + * @warning Values will oscillate beyond the target in both directions before settling. + * For example, animating from 0 to 100 might temporarily reach 110, then 95, then 102, + * before settling at 100. Ensure clipping or overflow handling is appropriate. + * + * @code + * // Springy button press feedback + * auto scale = tweeny::from(1.0f) + * .to(1.2f) + * .via(easing::elasticOut) + * .during(40U) + * .build(); + * + * // Scale will overshoot 1.2f (maybe 1.3f) then oscillate + * // down and up before settling at exactly 1.2f + * + * // Modal dialog with playful entrance + * auto opacity = tweeny::from(0.0f) + * .to(1.0f) + * .via(easing::elasticOut) + * .during(50U) + * .build(); + * @endcode + * + * @see elasticIn For spring anticipation at the start + * @see elasticInOut For oscillation at both ends + * @see backOut For a subtler single-overshoot alternative + * @see bounceOut For a similar but impact-based bouncing effect + */ inline constexpr detail::elasticOutEasing elasticOut{}; inline constexpr detail::elasticInOutEasing elasticInOut{}; inline constexpr detail::exponentialInEasing exponentialIn{}; From 4ed364fd80c8002b4ca9ad0ad3a00c828fb94116 Mon Sep 17 00:00:00 2001 From: Leonardo Date: Thu, 25 Dec 2025 23:07:55 -0300 Subject: [PATCH 45/58] document elasticInOut, exponentialIn, and exponentialOut easings --- include/tweeny/easing.h | 159 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 159 insertions(+) diff --git a/include/tweeny/easing.h b/include/tweeny/easing.h index b1abbe5..44653d5 100644 --- a/include/tweeny/easing.h +++ b/include/tweeny/easing.h @@ -662,8 +662,167 @@ namespace tweeny::easing { * @see bounceOut For a similar but impact-based bouncing effect */ inline constexpr detail::elasticOutEasing elasticOut{}; + + /** + * @brief Elastic easing combining spring oscillation at both start and end. + * + * The `elasticInOut` easing function creates dramatic elastic motion with oscillation + * during both the initial acceleration and final deceleration phases. The animation + * winds up with spring-like vibration, accelerates smoothly through the middle, then + * oscillates around the target before settling. + * + * The motion profile: + * - First half: Oscillates with increasing amplitude (elasticIn behavior) + * - Middle: Smooth transition through the midpoint + * - Second half: Oscillates with decreasing amplitude (elasticOut behavior) + * - Creates the most dramatic elastic effect available + * + * This easing is appropriate for: + * - **Hero animations**: Focal transitions that demand attention + * - **Game events**: Critical moments, boss appearances, dramatic reveals + * - **Cartoon physics**: Exaggerated, entertaining motion for stylized visuals + * - **Transitions with flair**: Scene changes that need maximum personality + * - **Experimental UI**: Interfaces prioritizing delight over convention + * + * ElasticInOut is the most expressive elastic variant but also the most extreme. It works + * best for animations that are: + * - Intentionally playful or whimsical + * - The primary focus of user attention + * - Part of a stylized, high-energy aesthetic + * - Not repeated frequently (can become tiresome) + * + * Use sparingly; the dual oscillation can feel excessive for everyday interactions. + * Consider elasticOut or backInOut for a more balanced alternative. + * + * @warning This easing produces extreme value overshoots in both directions throughout + * the animation. During the first half, values oscillate around the start; during the + * second half, around the target. Plan for values far outside the expected range. + * + * @code + * // Dramatic screen transition + * auto position = tweeny::from(0.0f) + * .to(1920.0f) + * .via(easing::elasticInOut) + * .during(90U) + * .build(); + * + * // Position will oscillate around 0.0f at start and 1920.0f at end + * + * // Game title appearing with maximum impact + * auto scale = tweeny::from(0.0f) + * .to(2.0f) + * .via(easing::elasticInOut) + * .during(75U) + * .build(); + * @endcode + * + * @see elasticIn For oscillation only at the start + * @see elasticOut For oscillation only at the end (more commonly useful) + * @see backInOut For a less extreme but still expressive alternative + */ inline constexpr detail::elasticInOutEasing elasticInOut{}; + + /** + * @brief Exponential easing with very slow start and explosive acceleration. + * + * The `exponentialIn` easing function starts extremely slowly and builds to a very rapid + * acceleration toward the end. Based on exponential growth (2^x), this creates one of the + * most dramatic acceleration curves available, with velocity increasing exponentially over time. + * + * Characteristics: + * - Almost no visible motion for the first portion of the animation + * - Sudden, explosive acceleration in the final phase + * - Change rate doubles repeatedly as time progresses + * - Creates extreme contrast between start and end velocity + * + * This easing is ideal for: + * - **Dramatic reveals**: Elements that burst into view + * - **Explosive effects**: Particle systems, energy blasts, explosions + * - **Fade-ins with impact**: Starting invisible and suddenly appearing + * - **Speed-up effects**: Rockets launching, vehicles accelerating + * - **Tension building**: Slow build-up to sudden release + * + * The extreme acceleration curve makes this feel more dramatic than polynomial easings + * (quadratic, cubic, etc.). The motion appears to "explode" into existence rather than + * gradually accelerate. + * + * Use exponentialIn when you want maximum contrast between the patient start and the + * explosive finish. For most UI work, cubic or quartic easings provide sufficient + * acceleration with less extreme behavior. + * + * Mathematically: `f(t) = 2^(10 * (t - 1))` + * + * @code + * // Fade in that suddenly snaps to full visibility + * auto opacity = tweeny::from(0.0f) + * .to(1.0f) + * .via(easing::exponentialIn) + * .during(45U) + * .build(); + * + * // Rocket launch with explosive acceleration + * auto velocity = tweeny::from(0.0f) + * .to(1000.0f) + * .via(easing::exponentialIn) + * .during(120U) + * .build(); + * @endcode + * + * @see exponentialOut For explosive deceleration at the end + * @see exponentialInOut For dramatic acceleration and deceleration + * @see quarticIn For strong but less extreme acceleration + */ inline constexpr detail::exponentialInEasing exponentialIn{}; + + /** + * @brief Exponential easing with explosive start and gradual slow-down. + * + * The `exponentialOut` easing function starts with maximum velocity and decelerates + * exponentially, creating a smooth glide to a stop. The initial burst of speed followed + * by gradual settling creates a powerful, energetic feel. + * + * Characteristics: + * - Immediate, explosive motion at the start + * - Exponentially decreasing velocity + * - Long, smooth deceleration phase + * - Settles gently to final value + * + * This easing excels at: + * - **Quick UI responses**: Instant feedback that settles smoothly + * - **Impact effects**: Objects hitting and settling into place + * - **Momentum-based motion**: Thrown objects, swipe gestures + * - **Energetic entrances**: Elements that burst in with energy + * - **Modern UI patterns**: iOS-style animations with quick start + * + * ExponentialOut is popular in modern mobile design because it provides immediate + * visual feedback (the fast start) while ending smoothly. Users perceive the interface + * as highly responsive due to the instant motion. + * + * The long tail of deceleration gives animations a "quality feel" - nothing stops + * abruptly. This is gentler on the eyes than linear or even cubic deceleration. + * + * Mathematically: `f(t) = 1 - 2^(-10 * t)` + * + * @code + * // Responsive drawer that slides out quickly then settles + * auto position = tweeny::from(-300.0f) + * .to(0.0f) + * .via(easing::exponentialOut) + * .during(35U) + * .build(); + * + * // Notification that pops in with energy + * auto scale = tweeny::from(0.0f) + * .to(1.0f) + * .via(easing::exponentialOut) + * .during(25U) + * .build(); + * @endcode + * + * @see exponentialIn For explosive acceleration at the start + * @see exponentialInOut For explosive motion at both ends + * @see quarticOut For strong but less extreme deceleration + */ inline constexpr detail::exponentialOutEasing exponentialOut{}; inline constexpr detail::exponentialInOutEasing exponentialInOut{}; inline constexpr detail::linearEasing linear{}; From c4653b91d2e94bb8361598b9d2ee66ae70b20b20 Mon Sep 17 00:00:00 2001 From: Leonardo Date: Thu, 25 Dec 2025 23:08:25 -0300 Subject: [PATCH 46/58] document exponentialInOut, linear, and quadratic easings --- include/tweeny/easing.h | 217 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 217 insertions(+) diff --git a/include/tweeny/easing.h b/include/tweeny/easing.h index 44653d5..c90788c 100644 --- a/include/tweeny/easing.h +++ b/include/tweeny/easing.h @@ -824,10 +824,227 @@ namespace tweeny::easing { * @see quarticOut For strong but less extreme deceleration */ inline constexpr detail::exponentialOutEasing exponentialOut{}; + + /** + * @brief Exponential easing with extreme acceleration and deceleration. + * + * The `exponentialInOut` easing function combines explosive acceleration in the first + * half with explosive deceleration in the second half. This creates one of the most + * dramatic, high-energy motion curves available, with rapid changes at both ends and + * smooth motion through the middle. + * + * Motion profile: + * - First half: Exponential acceleration from near-zero velocity + * - Midpoint: Maximum velocity + * - Second half: Exponential deceleration to rest + * - Creates extreme S-curve shape + * + * This easing is appropriate for: + * - **High-impact transitions**: Scene changes, screen swipes + * - **Dramatic animations**: Hero elements, focal content + * - **Fast-paced interfaces**: Games, action-oriented apps + * - **Attention-grabbing motion**: Elements that need maximum visibility + * - **Strong transitions**: For elements moving significant distances or longer durations (500ms+) + * + * ExponentialInOut creates a powerful sense of momentum and energy. The motion feels + * purposeful and confident. However, the extreme acceleration can be disorienting if + * overused or applied to large elements. + * + * Best practices: + * - Use for animations under 1 second duration + * - Apply to elements that move short to medium distances + * - Reserve for important, infrequent transitions + * - Consider quarticInOut or quinticInOut as less extreme alternatives + * + * Mathematically: Combines exponentialIn for t < 0.5 and exponentialOut for t >= 0.5 + * + * @code + * // Page transition with explosive motion + * auto position = tweeny::from(0.0f) + * .to(1920.0f) + * .via(easing::exponentialInOut) + * .during(40U) + * .build(); + * + * // Modal dialog with dramatic appearance + * auto opacity = tweeny::from(0.0f) + * .to(1.0f) + * .via(easing::exponentialInOut) + * .during(30U) + * .build(); + * @endcode + * + * @see exponentialIn For only explosive acceleration + * @see exponentialOut For only explosive deceleration + * @see quinticInOut For slightly less extreme alternative + */ inline constexpr detail::exponentialInOutEasing exponentialInOut{}; + /** + * @brief Linear easing function with constant velocity throughout the animation. + * + * The `linear` easing function produces uniform motion with no acceleration or deceleration. + * The interpolation progresses at a constant rate from start to finish, creating mechanical, + * predictable movement. + * + * Linear easing is characterized by: + * - Constant velocity: the rate of change never varies + * - No ease-in or ease-out: motion starts and stops abruptly + * - Simple mathematical relationship: output = start + (end - start) * progress + * - Predictable timing: halfway through time means halfway through distance + * + * This easing is appropriate for: + * - **Mechanical objects**: Conveyor belts, pistons, automated systems + * - **Progress indicators**: Loading bars, timers, countdowns + * - **Continuous loops**: Rotating objects, scrolling backgrounds + * - **Data visualization**: Graph animations where consistency is important + * - **Debug and testing**: Predictable behavior for verification + * + * Linear easing is generally **not recommended** for most UI animations because: + * - Lacks the natural feel of acceleration/deceleration + * - Abrupt starts and stops can feel jarring + * - Missing visual polish that easing provides + * - Human perception expects objects to ease into and out of motion + * + * However, it serves as the foundation for all other easing functions and is essential + * when mechanical precision is more important than natural motion feel. + * + * Mathematically, this implements: `f(t) = t` where t is normalized progress [0, 1]. + * + * @code + * // Constant velocity scrolling background + * auto scroll = tweeny::from(0.0f) + * .to(1000.0f) + * .via(easing::linear) + * .during(600U) + * .build(); + * + * // Progress indicator that matches time exactly + * auto progress = tweeny::from(0) + * .to(100) + * .via(easing::linear) + * .during(100U) + * .build(); + * + * // At frame 50, progress will be exactly 50 + * @endcode + * + * @note Linear is the default easing when no via() is specified, though using def is + * more explicit in code. + * + * @see def Alias for linear easing + * @see quadraticInOut For a gentle alternative with ease-in and ease-out + * @see sinusoidalInOut For smooth, natural-feeling motion + */ inline constexpr detail::linearEasing linear{}; + + /** + * @brief Quadratic polynomial easing (t²) with gentle acceleration. + * + * The `quadraticIn` easing function uses a squared power curve for acceleration, + * providing the gentlest polynomial easing. It's more subtle than cubic but still + * provides noticeable easing. + * + * Characteristics: + * - Gentle, smooth acceleration + * - Polynomial with power of 2 + * - Subtle but perceptible easing + * - Good for beginners and subtle animations + * + * This easing is ideal for: + * - **Subtle UI motion**: When easing should be felt but not seen + * - **Quick animations**: Short durations where gentle curves work best + * - **Minimal designs**: Interfaces prioritizing restraint + * - **Learning easings**: Easy to understand and predict + * + * Mathematically: `f(t) = t²` + * + * @code + * // Gentle fade in + * auto opacity = tweeny::from(0.0f) + * .to(1.0f) + * .via(easing::quadraticIn) + * .during(20U) + * .build(); + * @endcode + * + * @see quadraticOut For gentle deceleration + * @see quadraticInOut For gentle motion at both ends + * @see cubicIn For more pronounced acceleration + */ inline constexpr detail::quadraticInEasing quadraticIn{}; + + /** + * @brief Quadratic polynomial easing (t²) with gentle deceleration. + * + * The `quadraticOut` easing function provides subtle, smooth deceleration. It's the + * gentlest polynomial easing, perfect when you want polish without drama. + * + * Characteristics: + * - Gentle, smooth deceleration + * - Subtle but professional feel + * - Never feels too slow or too fast + * - Safe choice for any context + * + * This easing excels at: + * - **Subtle UI polish**: Adding refinement without drawing attention + * - **Fast animations**: 100-200ms durations + * - **Minimal interfaces**: Clean, understated design systems + * - **Accessibility-friendly**: Gentle motion reduces disorientation + * - **Background animations**: Motion that shouldn't distract + * + * QuadraticOut is great when you want animations to stay out of the way. It's less + * dramatic than cubic but smoother than linear due to gradual deceleration. + * + * Mathematically: `f(t) = 1 - (1-t)²` + * + * @code + * // Subtle tooltip appearance + * auto opacity = tweeny::from(0.0f) + * .to(1.0f) + * .via(easing::quadraticOut) + * .during(15U) + * .build(); + * @endcode + * + * @see quadraticIn For gentle acceleration + * @see quadraticInOut For gentle motion at both ends + * @see cubicOut For more pronounced deceleration + */ inline constexpr detail::quadraticOutEasing quadraticOut{}; + + /** + * @brief Quadratic polynomial easing with gentle acceleration and deceleration. + * + * The `quadraticInOut` easing function provides the most subtle polynomial S-curve. + * Perfect for animations that need easing but should remain understated. + * + * Motion profile: + * - First half: Gentle acceleration (t²) + * - Midpoint: Maximum velocity + * - Second half: Gentle deceleration + * - Creates subtle S-curve + * + * This easing is appropriate for: + * - **Minimal design systems**: Understated, refined motion + * - **Fast animations**: Under 200ms where gentle curves shine + * - **Accessibility**: Motion-sensitive users + * - **Background motion**: Animations that shouldn't dominate + * + * Mathematically: Combines quadraticIn for t < 0.5 and quadraticOut for t >= 0.5 + * + * @code + * // Subtle menu transition + * auto x = tweeny::from(0.0f) + * .to(100.0f) + * .via(easing::quadraticInOut) + * .during(20U) + * .build(); + * @endcode + * + * @see quadraticIn For only gentle acceleration + * @see quadraticOut For only gentle deceleration + * @see cubicInOut For more pronounced motion + */ inline constexpr detail::quadraticInOutEasing quadraticInOut{}; inline constexpr detail::quarticInEasing quarticIn{}; inline constexpr detail::quarticOutEasing quarticOut{}; From d23a03d3b4b7a9b80fd63f5d9da505f08ee36187 Mon Sep 17 00:00:00 2001 From: Leonardo Date: Thu, 25 Dec 2025 23:08:39 -0300 Subject: [PATCH 47/58] document quarticIn, quarticOut, and quarticInOut easings --- include/tweeny/easing.h | 108 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 108 insertions(+) diff --git a/include/tweeny/easing.h b/include/tweeny/easing.h index c90788c..8ce6728 100644 --- a/include/tweeny/easing.h +++ b/include/tweeny/easing.h @@ -1046,8 +1046,116 @@ namespace tweeny::easing { * @see cubicInOut For more pronounced motion */ inline constexpr detail::quadraticInOutEasing quadraticInOut{}; + + /** + * @brief Quartic polynomial easing (t⁴) with strong acceleration. + * + * The `quarticIn` easing function uses a power-4 curve for acceleration, creating + * pronounced easing that's stronger than cubic but less extreme than exponential. + * + * Characteristics: + * - Strong acceleration curve + * - Polynomial with power of 4 + * - Very slow start, rapid finish + * - More dramatic than cubic + * + * This easing is ideal for: + * - **Dramatic entrances**: Elements that burst into view + * - **Emphasis**: Drawing attention to motion start + * - **Game animations**: Action-oriented interfaces + * - **Long animations**: Over 500ms where strong curves work + * + * Mathematically: `f(t) = t⁴` + * + * @code + * // Panel sliding in with emphasis + * auto x = tweeny::from(-400.0f) + * .to(0.0f) + * .via(easing::quarticIn) + * .during(50U) + * .build(); + * @endcode + * + * @see quarticOut For strong deceleration + * @see quarticInOut For strong motion at both ends + * @see cubicIn For gentler acceleration + * @see quinticIn For even stronger acceleration + */ inline constexpr detail::quarticInEasing quarticIn{}; + + /** + * @brief Quartic polynomial easing (t⁴) with strong deceleration. + * + * The `quarticOut` easing function provides pronounced deceleration that's popular + * for animations needing clear emphasis without being as extreme as exponential. + * + * Characteristics: + * - Strong deceleration curve + * - Smooth, extended slowdown + * - Emphasizes final position + * - Balanced between cubic and exponential + * + * This easing excels at: + * - **Emphasized arrivals**: Elements settling with impact + * - **Current design patterns**: Strong deceleration suitable for emphasized arrivals + * - **Medium-length animations**: 300-600ms sweet spot + * - **Purposeful motion**: Clear acceleration and deceleration + * + * QuarticOut provides more "oomph" than cubic while remaining smooth and + * professional. It's a good choice when cubic feels too subtle. + * + * Mathematically: `f(t) = 1 - (1-t)⁴` + * + * @code + * // Card sliding into place with emphasis + * auto y = tweeny::from(200.0f) + * .to(0.0f) + * .via(easing::quarticOut) + * .during(40U) + * .build(); + * @endcode + * + * @see quarticIn For strong acceleration + * @see quarticInOut For strong motion at both ends + * @see cubicOut For gentler deceleration + * @see quinticOut For even stronger deceleration + */ inline constexpr detail::quarticOutEasing quarticOut{}; + + /** + * @brief Quartic polynomial easing with strong acceleration and deceleration. + * + * The `quarticInOut` easing function combines strong acceleration and deceleration + * for confident, purposeful motion. More dramatic than cubic, less extreme than quintic. + * + * Motion profile: + * - First half: Strong acceleration (t⁴) + * - Midpoint: Maximum velocity + * - Second half: Strong deceleration + * - Creates pronounced S-curve + * + * This easing is appropriate for: + * - **Emphasized transitions**: Strong acceleration and deceleration + * - **Current design patterns**: Strong deceleration curves + * - **Important animations**: Focal transitions + * - **Medium durations**: 300-600ms range + * + * Mathematically: Combines quarticIn for t < 0.5 and quarticOut for t >= 0.5 + * + * @code + * // Page transition with impact + * auto x = tweeny::from(0.0f) + * .to(1920.0f) + * .via(easing::quarticInOut) + * .during(45U) + * .build(); + * @endcode + * + * @see quarticIn For only strong acceleration + * @see quarticOut For only strong deceleration + * @see cubicInOut For gentler motion + * @see quinticInOut For more dramatic motion + */ inline constexpr detail::quarticInOutEasing quarticInOut{}; inline constexpr detail::quinticInEasing quinticIn{}; inline constexpr detail::quinticOutEasing quinticOut{}; From d5f0a2e57110c118b8788ba31e7ef1a13ae7a6fd Mon Sep 17 00:00:00 2001 From: Leonardo Date: Thu, 25 Dec 2025 23:08:54 -0300 Subject: [PATCH 48/58] document quinticIn, quinticOut, and quinticInOut easings --- include/tweeny/easing.h | 109 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 109 insertions(+) diff --git a/include/tweeny/easing.h b/include/tweeny/easing.h index 8ce6728..ceed672 100644 --- a/include/tweeny/easing.h +++ b/include/tweeny/easing.h @@ -1157,8 +1157,117 @@ namespace tweeny::easing { * @see quinticInOut For more dramatic motion */ inline constexpr detail::quarticInOutEasing quarticInOut{}; + + /** + * @brief Quintic polynomial easing (t⁵) with very strong acceleration. + * + * The `quinticIn` easing function uses a power-5 curve, creating the strongest + * polynomial acceleration. It's more extreme than quartic but less than exponential. + * + * Characteristics: + * - Very strong acceleration + * - Polynomial with power of 5 + * - Extremely slow start + * - Dramatic finish + * + * This easing is ideal for: + * - **Maximum polynomial emphasis**: Strongest polynomial option + * - **Dramatic effects**: Hero animations, focal transitions + * - **Long animations**: Over 600ms durations + * - **Alternative to exponential**: Slightly less extreme + * + * Mathematically: `f(t) = t⁵` + * + * @code + * // Dramatic hero section reveal + * auto scale = tweeny::from(0.0f) + * .to(1.0f) + * .via(easing::quinticIn) + * .during(60U) + * .build(); + * @endcode + * + * @see quinticOut For very strong deceleration + * @see quinticInOut For very strong motion at both ends + * @see quarticIn For slightly gentler acceleration + * @see exponentialIn For even more extreme acceleration + */ inline constexpr detail::quinticInEasing quinticIn{}; + + /** + * @brief Quintic polynomial easing (t⁵) with very strong deceleration. + * + * The `quinticOut` easing function provides the strongest polynomial deceleration, + * creating smooth but dramatic slowdown. Popular for animations with long durations + * (500ms+) requiring strong deceleration. + * + * Characteristics: + * - Very strong deceleration + * - Long, smooth tail + * - Very strong deceleration with extended slowdown phase + * - Strongest polynomial option + * + * This easing excels at: + * - **Applications prioritizing strong visual emphasis**: Galleries, portfolios, marketing sites + * - **Animations with extended durations**: Requiring maximum polynomial deceleration + * - **Longer animations**: 500ms+ durations + * - **Emphasized arrivals**: Strong focus on end state + * + * QuinticOut provides the strongest polynomial deceleration while maintaining a + * smooth, continuous curve. + * + * Mathematically: `f(t) = 1 - (1-t)⁵` + * + * @code + * // Premium modal entrance + * auto opacity = tweeny::from(0.0f) + * .to(1.0f) + * .via(easing::quinticOut) + * .during(45U) + * .build(); + * @endcode + * + * @see quinticIn For very strong acceleration + * @see quinticInOut For very strong motion at both ends + * @see quarticOut For slightly gentler deceleration + * @see exponentialOut For even more extreme deceleration + */ inline constexpr detail::quinticOutEasing quinticOut{}; + + /** + * @brief Quintic polynomial easing with very strong acceleration and deceleration. + * + * The `quinticInOut` easing function provides the strongest polynomial S-curve, + * creating powerful motion with maximum polynomial emphasis. + * + * Motion profile: + * - First half: Very strong acceleration (t⁵) + * - Midpoint: Maximum velocity + * - Second half: Very strong deceleration + * - Creates dramatic S-curve + * + * This easing is appropriate for: + * - **Applications prioritizing strong visual emphasis**: Galleries, portfolios, marketing sites + * - **Animations requiring maximum polynomial emphasis**: At both acceleration and deceleration phases + * - **Important transitions**: Focal, memorable moments + * - **Longer animations**: 500ms+ durations + * + * Mathematically: Combines quinticIn for t < 0.5 and quinticOut for t >= 0.5 + * + * @code + * // Premium screen transition + * auto x = tweeny::from(0.0f) + * .to(1920.0f) + * .via(easing::quinticInOut) + * .during(50U) + * .build(); + * @endcode + * + * @see quinticIn For only very strong acceleration + * @see quinticOut For only very strong deceleration + * @see quarticInOut For gentler motion + * @see exponentialInOut For more extreme motion + */ inline constexpr detail::quinticInOutEasing quinticInOut{}; inline constexpr detail::sinusoidalInEasing sinusoidalIn{}; inline constexpr detail::sinusoidalOutEasing sinusoidalOut{}; From ca272fba23fd970bdb418b269d05031f38b91dee Mon Sep 17 00:00:00 2001 From: Leonardo Date: Thu, 25 Dec 2025 23:09:09 -0300 Subject: [PATCH 49/58] document sinusoidalIn, sinusoidalOut, sinusoidalInOut, and stepped easings --- include/tweeny/easing.h | 160 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 160 insertions(+) diff --git a/include/tweeny/easing.h b/include/tweeny/easing.h index ceed672..9c63357 100644 --- a/include/tweeny/easing.h +++ b/include/tweeny/easing.h @@ -1269,9 +1269,169 @@ namespace tweeny::easing { * @see exponentialInOut For more extreme motion */ inline constexpr detail::quinticInOutEasing quinticInOut{}; + + /** + * @brief Sinusoidal easing based on sine wave for smooth acceleration. + * + * The `sinusoidalIn` easing function uses a sine curve for acceleration, creating + * extremely smooth, natural motion. Based on trigonometric functions rather than + * polynomials. + * + * Characteristics: + * - Very smooth acceleration + * - Natural, organic feel + * - No abrupt velocity changes + * - Gentle but noticeable + * + * This easing is ideal for: + * - **Natural motion**: Organic, flowing animations + * - **Smooth camera moves**: Pans, zooms, orbits + * - **Elegant transitions**: Refined, sophisticated feel + * - **Accessible animations**: Gentle on motion sensitivity + * + * Mathematically: `f(t) = 1 - cos(t * π/2)` + * + * @code + * // Smooth camera pan + * auto x = tweeny::from(0.0f) + * .to(1000.0f) + * .via(easing::sinusoidalIn) + * .during(60U) + * .build(); + * @endcode + * + * @see sinusoidalOut For smooth deceleration + * @see sinusoidalInOut For smooth motion at both ends + * @see quadraticIn For similar gentleness + */ inline constexpr detail::sinusoidalInEasing sinusoidalIn{}; + + /** + * @brief Sinusoidal easing based on sine wave for smooth deceleration. + * + * The `sinusoidalOut` easing function uses a sine curve for deceleration, creating + * extremely smooth, natural motion that settles gently. + * + * Characteristics: + * - Very smooth deceleration + * - Natural, flowing motion + * - Gentle slowdown + * - Mathematically elegant + * + * This easing excels at: + * - **Natural UI motion**: Smooth, organic feel + * - **Continuous animations**: Loops, cycles, repeated motion + * - **Gentle transitions**: Calm, relaxed interfaces + * - **Accessible design**: Motion-sensitivity friendly + * + * SinusoidalOut is excellent when you want smoothness above all else. It's + * gentler than quadratic while still providing clear easing. + * + * Mathematically: `f(t) = sin(t * π/2)` + * + * @code + * // Smooth fade in + * auto opacity = tweeny::from(0.0f) + * .to(1.0f) + * .via(easing::sinusoidalOut) + * .during(30U) + * .build(); + * @endcode + * + * @see sinusoidalIn For smooth acceleration + * @see sinusoidalInOut For smooth motion at both ends + * @see quadraticOut For similar gentleness + */ inline constexpr detail::sinusoidalOutEasing sinusoidalOut{}; + + /** + * @brief Sinusoidal easing with smooth acceleration and deceleration. + * + * The `sinusoidalInOut` easing function uses sine curves for both acceleration + * and deceleration, creating the smoothest possible S-curve motion. + * + * Motion profile: + * - First half: Smooth sine-based acceleration + * - Midpoint: Maximum velocity + * - Second half: Smooth sine-based deceleration + * - Creates extremely smooth S-curve + * + * This easing is appropriate for: + * - **Natural, organic motion**: Smoothest easing available + * - **Continuous loops**: Seamless repeated animations + * - **Calm interfaces**: Relaxed, gentle design systems + * - **Accessible animations**: Minimal motion stress + * + * SinusoidalInOut is the smoothest InOut easing, making it perfect when + * fluid, natural motion is the priority. + * + * Mathematically: `f(t) = (1 - cos(t * π)) / 2` + * + * @code + * // Ultra-smooth transition + * auto x = tweeny::from(0.0f) + * .to(100.0f) + * .via(easing::sinusoidalInOut) + * .during(40U) + * .build(); + * @endcode + * + * @see sinusoidalIn For only smooth acceleration + * @see sinusoidalOut For only smooth deceleration + * @see quadraticInOut For similar gentleness + */ inline constexpr detail::sinusoidalInOutEasing sinusoidalInOut{}; + + /** + * @brief Stepped easing that holds the start value until the keyframe completes. + * + * The `stepped` easing function returns the starting value throughout the entire + * duration of the keyframe segment, only jumping to the target value when moving + * to the next keyframe. This creates instant transitions between keyframes without + * any interpolation within each segment. + * + * Characteristics: + * - No interpolation within keyframe segments + * - Holds start value until keyframe ends + * - Instant jumps between keyframes + * - Creates discrete, stair-step motion + * + * This easing is ideal for: + * - **Discrete state transitions**: Values that shouldn't interpolate smoothly + * - **Keyframe-based animations**: Step through distinct poses or states + * - **Frame-by-frame effects**: Hold each frame without blending + * - **Boolean-like values**: Properties that need instant changes + * - **Sprite switching**: Change sprites at keyframe boundaries + * - **Cut transitions**: Instant changes without fading + * + * Stepped is fundamentally different from other easings because it eliminates + * interpolation entirely within each keyframe segment, creating hard cuts between + * animation states. + * + * @code + * // Hold value at each keyframe, jump instantly between them + * auto value = tweeny::from(0) + * .to(10).via(easing::stepped).during(100U) + * .to(20).via(easing::stepped).during(100U) + * .to(30).via(easing::stepped).during(100U) + * .build(); + * + * // During frames 0-99: returns 0 + * // During frames 100-199: returns 10 + * // During frames 200-299: returns 20 + * // At frame 300: returns 30 + * + * // Useful for sprite animation indices + * auto spriteIndex = tweeny::from(0) + * .to(1).via(easing::stepped).during(10U) + * .to(2).via(easing::stepped).during(10U) + * .to(3).via(easing::stepped).during(10U) + * .build(); + * // Holds each sprite index for 10 frames, then instantly switches + * @endcode + * + * @see linear For smooth constant-velocity interpolation + */ inline constexpr detail::steppedEasing stepped{}; } From 6fa330a313958e762dbf6dc4e2e36847371d515a Mon Sep 17 00:00:00 2001 From: Leonardo Date: Thu, 25 Dec 2025 23:10:13 -0300 Subject: [PATCH 50/58] establish mainpage, update input paths, and refine doxygen layout --- include/tweeny/event.h | 1 + include/tweeny/tweeny.h | 8 ++ src/doc/CMakeLists.txt | 24 +++-- src/doc/Doxyfile.in | 8 +- src/doc/DoxygenLayout.xml | 4 +- src/doc/mainpage.dox | 141 +++++++++++++++++++++++++++++ src/doc/{MANUAL.dox => manual.dox} | 0 7 files changed, 171 insertions(+), 15 deletions(-) create mode 100644 src/doc/mainpage.dox rename src/doc/{MANUAL.dox => manual.dox} (100%) diff --git a/include/tweeny/event.h b/include/tweeny/event.h index 2c171aa..7a83323 100644 --- a/include/tweeny/event.h +++ b/include/tweeny/event.h @@ -45,6 +45,7 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. #include /** + * @namespace tweeny::event * @brief Event types and response codes for tween callbacks. * * This namespace contains tag types for registering event listeners and diff --git a/include/tweeny/tweeny.h b/include/tweeny/tweeny.h index 9db728f..755acf4 100644 --- a/include/tweeny/tweeny.h +++ b/include/tweeny/tweeny.h @@ -43,6 +43,14 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. #include "detail/tuple-utilities.h" +/** + * @namespace tweeny + * @brief Contains all public API types and functions for creating and managing tweens. + * + * This namespace provides the builder pattern API (from(), tweeny_builder), the main + * tween class template, easing functions, and event types. All user-facing functionality + * is contained within this namespace to avoid naming conflicts. + */ namespace tweeny { /** * @brief Primary template for the tween builder type. diff --git a/src/doc/CMakeLists.txt b/src/doc/CMakeLists.txt index 3517a62..531eb95 100644 --- a/src/doc/CMakeLists.txt +++ b/src/doc/CMakeLists.txt @@ -25,17 +25,23 @@ find_package(Doxygen REQUIRED) configure_file(Doxyfile.in ${CMAKE_CURRENT_BINARY_DIR}/Doxyfile @ONLY) -file(COPY ../../README.md DESTINATION ${CMAKE_CURRENT_BINARY_DIR}) -file(COPY - DoxygenLayout.xml - MANUAL.dox - DESTINATION - ${CMAKE_CURRENT_BINARY_DIR} + +set(DOC_FILES + "${CMAKE_CURRENT_SOURCE_DIR}/mainpage.dox" + "${CMAKE_CURRENT_SOURCE_DIR}/manual.dox" + "${CMAKE_CURRENT_SOURCE_DIR}/easings.dox" + "${CMAKE_CURRENT_SOURCE_DIR}/DoxygenLayout.xml" + "${CMAKE_CURRENT_SOURCE_DIR}/../../README.md" ) + add_custom_target(doc ALL - ${DOXYGEN_EXECUTABLE} ${CMAKE_CURRENT_BINARY_DIR}/Doxyfile - WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} - COMMENT "Generating API documentation with Doxygen" VERBATIM) + COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_CURRENT_SOURCE_DIR}/../../README.md ${CMAKE_CURRENT_BINARY_DIR}/ + COMMAND ${CMAKE_COMMAND} -E copy_if_different ${DOC_FILES} ${CMAKE_CURRENT_BINARY_DIR}/ + COMMAND ${DOXYGEN_EXECUTABLE} ${CMAKE_CURRENT_BINARY_DIR}/Doxyfile + WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} + DEPENDS ${DOC_FILES} Doxyfile.in + COMMENT "Generating API documentation with Doxygen" VERBATIM +) include(GNUInstallDirs) install(DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/html DESTINATION ${CMAKE_INSTALL_DOCDIR}) diff --git a/src/doc/Doxyfile.in b/src/doc/Doxyfile.in index d6cea5d..7948b4d 100644 --- a/src/doc/Doxyfile.in +++ b/src/doc/Doxyfile.in @@ -765,7 +765,7 @@ WARN_LOGFILE = # spaces. See also FILE_PATTERNS and EXTENSION_MAPPING # Note: If this tag is empty the current directory is searched. -INPUT = MANUAL.dox @CMAKE_CURRENT_SOURCE_DIR@/../include +INPUT = mainpage.dox manual.dox @CMAKE_CURRENT_SOURCE_DIR@/../../include # This tag can be used to specify the character encoding of the source files # that doxygen parses. Internally doxygen uses the UTF-8 encoding. Doxygen uses @@ -790,13 +790,13 @@ INPUT_ENCODING = UTF-8 # *.m, *.markdown, *.md, *.mm, *.dox, *.py, *.f90, *.f, *.for, *.tcl, *.vhd, # *.vhdl, *.ucf, *.qsf, *.as and *.js. -FILE_PATTERNS = +FILE_PATTERNS = *.h *.hpp *.tcc *.dox # The RECURSIVE tag can be used to specify whether or not subdirectories should # be searched for input files as well. # The default value is: NO. -RECURSIVE = NO +RECURSIVE = YES # The EXCLUDE tag can be used to specify files and/or directories that should be # excluded from the INPUT source files. This way you can easily exclude a @@ -838,7 +838,7 @@ EXCLUDE_SYMBOLS = # that contain example code fragments that are included (see the \include # command). -EXAMPLE_PATH = @CMAKE_CURRENT_SOURCE_DIR@/../examples +# EXAMPLE_PATH = @CMAKE_CURRENT_SOURCE_DIR@/../examples # If the value of the EXAMPLE_PATH tag contains directories, you can use the # EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp and diff --git a/src/doc/DoxygenLayout.xml b/src/doc/DoxygenLayout.xml index 193ca63..d0630fb 100644 --- a/src/doc/DoxygenLayout.xml +++ b/src/doc/DoxygenLayout.xml @@ -3,9 +3,9 @@ - + - + diff --git a/src/doc/mainpage.dox b/src/doc/mainpage.dox new file mode 100644 index 0000000..1286f53 --- /dev/null +++ b/src/doc/mainpage.dox @@ -0,0 +1,141 @@ +/** +@mainpage Tweeny + +Tweeny is a modern C++ inbetweening library designed for creating complex animations +for games and other interactive software. It provides a type-safe, fluent API for +declaring interpolations (tweens) of any numeric type that supports arithmetic operations. + +@section features Key Features + +- **Type-safe and modern**: Leverages C++17 features for compile-time type checking +- **Fluent builder API**: Intuitive method chaining for tween creation +- **Multi-value tweens**: Animate multiple values simultaneously (e.g., RGB colors, 3D positions) +- **Heterogeneous types**: Mix different numeric types in a single tween +- **Rich easing library**: Includes 30+ built-in easing functions +- **Keyframe animations**: Create complex multi-segment animations +- **Event system**: React to tween lifecycle events (start, step, end, keyframes) +- **Header-only**: Simple integration with no linking required +- **Zero dependencies**: Only requires a C++17 compiler + +@section quickstart Quick Start + +@code +#include +using tweeny::easing; + +int main() { + // Simple tween from 0 to 100 over 60 frames + auto tween = tweeny::from(0).to(100).during(60U).build(); + + // Step through the animation + for (int i = 0; i < 60; i++) { + int value = tween.step(); + // Use value... + } + + // Multi-value tween (e.g., for RGB color) + auto color = tweeny::from(255, 0, 0) + .to(0, 255, 0) + .during(120U) + .build(); + + // Tween with easing + auto smooth = tweeny::from(0.0f) + .to(100.0f) + .via(easing::quadraticInOut) + .during(60U) + .build(); + + // Multi-segment animation with keyframes + auto complex = tweeny::from(0) + .to(50).via(easing::linear).during(30U) + .to(100).via(easing::bounceOut).during(30U) + .build(); + + return 0; +} +@endcode + +@section api API Reference + +The most important parts of the API are: + +- tweeny::from() - Entry point for creating new tweens +- tweeny::tween - Main tween class with animation control methods +- tweeny::tweeny_builder - Fluent builder for constructing tweens +- tweeny::easing namespace - Collection of easing functions +- tweeny::event - Event types for tween lifecycle callbacks + +Visit the modules page for a complete list of easing functions. + +@section examples Common Patterns + +@subsection ex_basic Basic Animation +@code +auto tween = tweeny::from(0.0f).to(1.0f).during(60U).build(); +while (!tween.ended()) { + float alpha = tween.step(); + // Render with alpha... +} +@endcode + +@subsection ex_seek Seeking and Direction +@code +auto tween = tweeny::from(0).to(100).during(100U).build(); + +// Jump to specific frame +tween.seek(50U); + +// Jump to percentage +tween.seek(0.75f); // 75% through the animation + +// Reverse direction +tween.backward().step(10); +@endcode + +@subsection ex_events Event Listeners +@code +auto tween = tweeny::from(0).to(100).during(60U).build(); + +// Listen for step events +tween.onStep([](int value, float progress) { + std::cout << "Value: " << value << ", Progress: " << progress << std::endl; + return false; // Return true to cancel the tween +}); + +// Listen for keyframe transitions +tween.onKeyframeEnter([](int from, int to) { + std::cout << "Entering keyframe from " << from << " to " << to << std::endl; +}); +@endcode + +@subsection ex_multivalue Multi-Value Tweens +@code +// Animate RGB color +auto color = tweeny::from(255, 0, 0).to(0, 255, 0).during(120U).build(); +auto [r, g, b] = color.step(); + +// Different easing per component +auto position = tweeny::from(0.0f, 0.0f) + .to(100.0f, 50.0f) + .via(easing::linear, easing::bounceOut) + .during(60U) + .build(); + +// Different duration per component +auto mixed = tweeny::from(0, 0) + .to(100, 200) + .during(60U, 120U) // X completes in 60 frames, Y in 120 + .build(); +@endcode + +@section resources Resources + +- GitHub Repository +- Demo Applications +- Easing Function Cheat Sheet + +@section license License + +Tweeny is licensed under the MIT License. See the LICENSE file in the repository for details. +*/ diff --git a/src/doc/MANUAL.dox b/src/doc/manual.dox similarity index 100% rename from src/doc/MANUAL.dox rename to src/doc/manual.dox From a46889d492726223444430a0f80a78087c654c42 Mon Sep 17 00:00:00 2001 From: Leonardo Date: Fri, 26 Dec 2025 00:08:16 -0300 Subject: [PATCH 51/58] update manual and examples to reflect builder pattern --- include/tweeny/tweeny.h | 28 ++- src/doc/CMakeLists.txt | 1 - src/doc/mainpage.dox | 28 +-- src/doc/manual.dox | 395 +++++++++++++++++++++++++++------------- 4 files changed, 305 insertions(+), 147 deletions(-) diff --git a/include/tweeny/tweeny.h b/include/tweeny/tweeny.h index 755acf4..1a9f9e8 100644 --- a/include/tweeny/tweeny.h +++ b/include/tweeny/tweeny.h @@ -119,17 +119,17 @@ namespace tweeny { key_frames_t key_frames; }; - template /** - * @brief Builder specialization for the stage after at least one to() call. - * - * In this stage, additional key-frames can be appended with to(), easing functions can be - * specified using via(), and per-component or uniform frame counts can be set with during(). - * Finally, build() materializes the configured tween. - * - * @tparam FirstValue Type of the first tweened component. - * @tparam RemainingValues Types of the remaining tweened components. - */ + * @brief Builder specialization for the stage after at least one to() call. + * + * In this stage, additional key-frames can be appended with to(), easing functions can be + * specified using via(), and per-component or uniform frame counts can be set with during(). + * Finally, build() materializes the configured tween. + * + * @tparam FirstValue Type of the first tweened component. + * @tparam RemainingValues Types of the remaining tweened components. + */ + template class tweeny_builder { typedef std::vector> key_frames_t; typedef tween tween_t; @@ -160,6 +160,8 @@ namespace tweeny { * .to(100).via(easing::bounceOut).during(30U) * .build(); * @endcode + * + * @anchor builder_to */ tweeny_builder to(const FirstValue & firstValue, const RemainingValues &... remainingValues) & { key_frames.emplace_back(firstValue, remainingValues...); @@ -210,6 +212,8 @@ namespace tweeny { * @code * auto t = tweeny::from(0, 0.0f).to(100, 100.0f).via(easing::quadraticInOut).during(60U).build(); * @endcode + * + * @anchor builder_via */ template tweeny_builder & via(EasingFunctionType easing_function) { @@ -232,6 +236,8 @@ namespace tweeny { * // X animates over 60 frames, Y over 120 frames * auto t = tweeny::from(0, 0).to(100, 100).during(60U, 120U).build(); * @endcode + * + * @anchor builder_during */ template tweeny_builder & during(FrameCountsType... frame_counts) { @@ -295,6 +301,8 @@ namespace tweeny { * // Create variations * auto t4 = builder.to(200).during(120U).build(); // Extended animation * @endcode + * + * @anchor builder_build */ tween_t build() const & { return tween(key_frames); } diff --git a/src/doc/CMakeLists.txt b/src/doc/CMakeLists.txt index 531eb95..d5dd5eb 100644 --- a/src/doc/CMakeLists.txt +++ b/src/doc/CMakeLists.txt @@ -29,7 +29,6 @@ configure_file(Doxyfile.in ${CMAKE_CURRENT_BINARY_DIR}/Doxyfile @ONLY) set(DOC_FILES "${CMAKE_CURRENT_SOURCE_DIR}/mainpage.dox" "${CMAKE_CURRENT_SOURCE_DIR}/manual.dox" - "${CMAKE_CURRENT_SOURCE_DIR}/easings.dox" "${CMAKE_CURRENT_SOURCE_DIR}/DoxygenLayout.xml" "${CMAKE_CURRENT_SOURCE_DIR}/../../README.md" ) diff --git a/src/doc/mainpage.dox b/src/doc/mainpage.dox index 1286f53..e1195c2 100644 --- a/src/doc/mainpage.dox +++ b/src/doc/mainpage.dox @@ -56,6 +56,8 @@ int main() { } @endcode +Visit the manual for a more in-depth explanation of the library. + @section api API Reference The most important parts of the API are: @@ -66,31 +68,30 @@ The most important parts of the API are: - tweeny::easing namespace - Collection of easing functions - tweeny::event - Event types for tween lifecycle callbacks -Visit the modules page for a complete list of easing functions. @section examples Common Patterns @subsection ex_basic Basic Animation @code auto tween = tweeny::from(0.0f).to(1.0f).during(60U).build(); -while (!tween.ended()) { - float alpha = tween.step(); +while (tween.progress() < 1.0f) { + float alpha = tween.step(1); // Render with alpha... } @endcode -@subsection ex_seek Seeking and Direction +@subsection ex_seek Seeking and Jumping @code auto tween = tweeny::from(0).to(100).during(100U).build(); // Jump to specific frame tween.seek(50U); -// Jump to percentage -tween.seek(0.75f); // 75% through the animation +// Step backward (negative delta) +tween.step(-10); -// Reverse direction -tween.backward().step(10); +// Jump to a keyframe by index +tween.jump(0); // Jump back to first keyframe @endcode @subsection ex_events Event Listeners @@ -98,14 +99,15 @@ tween.backward().step(10); auto tween = tweeny::from(0).to(100).during(60U).build(); // Listen for step events -tween.onStep([](int value, float progress) { - std::cout << "Value: " << value << ", Progress: " << progress << std::endl; - return false; // Return true to cancel the tween +tween.on(tweeny::event::step, [](auto& tween) { + std::cout << "Value: " << tween.peek() << ", Progress: " << tween.progress() << std::endl; + return tweeny::event::response::ok; }); // Listen for keyframe transitions -tween.onKeyframeEnter([](int from, int to) { - std::cout << "Entering keyframe from " << from << " to " << to << std::endl; +tween.on(tweeny::event::keyframeEnter, [](auto& tween, auto evt) { + std::cout << "Entered keyframe " << evt.key_frame << std::endl; + return tweeny::event::response::ok; }); @endcode diff --git a/src/doc/manual.dox b/src/doc/manual.dox index 62ff779..03e4eb4 100644 --- a/src/doc/manual.dox +++ b/src/doc/manual.dox @@ -4,235 +4,384 @@ namespace tweeny { This document is the manual for Tweeny. It walks you through all the important steps when creating and controlling tweens. - @section creating Creating a tween + @section creating Creating Tweens - Tweeny can interpolate a single value, a set of values and a set of values with different types. For each of these cases, you create a tween using the same method: tweeny::from. The argument types you pass to it defines the tween itself which in turn defines argument number and types for tween::to, tween::during and tween::via, as well as the callback argument types. + @subsection builder_intro The Builder Pattern - Here is how you create a tween: + Tweeny uses a fluent builder API to create tweens. The tweeny::from function returns a **builder object**, not a tween directly. + You configure the interpolation using the builder's methods (\ref builder_to "to()", \ref builder_via "via()", + \ref builder_during "during()") and then call \ref builder_build "build()" to create + the actual tween object. @code - // Creates a single-valued tween - auto tween = tweeny::from(0); + // tweeny::from returns a builder + auto builder = tweeny::from(0); - // Creates a multi-valued tween - auto tween2 = tweeny::from(0, 1, 2); + // Configure the builder (methods modify the builder and return a reference) + builder.to(100).during(60U); - // Creates a multi-value heterogeneous tween - auto tween3 = tweeny::from(0, 'a', 1.0f); + // Build the tween + auto tween = builder.build(); @endcode - @section points Adding tween points - - A tween without a target value does nothing. To add a point to a tween, use the method tween::to. Since tweeny::from returns the tween instance itself, you can chain both calls, forming the following: + Most commonly, you'll chain all calls together in a single expression: @code - // For a single value - auto tween = tweeny::from(0).to(100); + auto tween = tweeny::from(0).to(100).during(60U).build(); + @endcode - // For multiple values - auto tween = tweeny::from(0, 'a').to(10, 'z'); + @note **Important:** Unlike Tweeny 3.x, you must explicitly call `build()` to create a tween. The builder + can be reused and modified to create variations. + @code + auto builder = tweeny::from(0).to(100).during(60U); + auto tween1 = builder.build(); // First tween (0→100 in 60 frames) + builder.to(200).during(120U); // Add another keyframe + auto tween2 = builder.build(); // New tween (0→100→200) @endcode - Notice how arguments to tween::to were of the same type of the arguments to tweeny::from. + Once built, a tween's **keyframes, durations, and easing functions are immutable**. However, the tween's + current state (frame position and value) changes as you navigate it with `step()`, `seek()`, or `jump()`. - @section durations Specifying durations + @subsection value_types Value Types - A tween needs a duration between each point. This duration unit is a unsigned integer that can represent anything you want. Usually I use it as milliseconds (this is relevant when stepping a tween). To specify the duration, call tween::during **after** tween::to: + Tweeny can interpolate single values, multiple values, or values of different types. The types you pass to tweeny::from + determine the tween's type signature, which affects all subsequent builder methods and the tween's return values: @code - auto tween = tweeny::from(0).to(100).during(100); + // Single value tween + auto t1 = tweeny + ::from(0) + .to(100) + .during(60U) + .build(); + + // Multi-value tween (homogeneous) + auto t2 = tweeny + ::from(0, 0, 0) + .to(255, 128, 64) + .during(60U) + .build(); + + // Multi-value heterogeneous tween + auto t3 = tweeny + ::from(0, 'a', 1.0f) + .to(10, 'z', 5.0f) + .during(60U) + .build(); @endcode - When you have a multivalued tween, you can either pass one single duration for all the values or specify a value for each point: + @subsection from_to From and To + + Every \ref tween needs at least a starting point and an ending point. tweeny::from specifies the starting values, + and you must call `to()` at least once to specify target values. **This requirement is enforced at compile time** - if you + try to build a tween without calling `to()`, you'll get a compilation error. @code - auto tween = tweeny::from(0, 1, 2).to(3, 4, 5).during(50, 100, 200); + // This won't compile - no to() called + // auto tween = tweeny::from(0).during(60U).build(); // ERROR! + + // This is correct + auto tween = tweeny::from(0).to(100).during(60U).build(); @endcode - The total duration from the starting point `(0, 1, 2)` to the next point `(3, 4, 5)` is 200, but each value will reach their target at different times. + The number and types of arguments to `to()` must match those passed to `from()`: + + @code + // Single value: one argument + auto t1 = tweeny::from(0).to(100).during(60U).build(); + + // Two values: two arguments of matching types + auto t2 = tweeny::from(0, 'a').to(100, 'z').during(60U).build(); + + // Wrong number of arguments - won't compile + // auto t3 = tweeny::from(0, 0).to(100).build(); // ERROR! + @endcode + + @subsection during Duration + + Every interpolation segment needs a duration. The `during()` method specifies how many units (typically frames or milliseconds) + the interpolation should take to reach the target values. The duration is always an unsigned 32-bit integer (`uint32_t`). + + @code + auto tween = tweeny::from(0).to(100).during(60U).build(); + @endcode - To specify the same time for each value, pass a single value for tween::during: + For multi-value tweens, you can specify either: + - A single duration that applies to all values + - Individual durations for each value (must match the number of values) @code - auto tween = tweeny::from(0, 1, 2).to(3, 4, 5).during(200); + // Same duration for all values (60 frames) + auto t1 = tweeny::from(0, 0, 0).to(100, 200, 300).during(60U).build(); + + // Different durations per value + auto t2 = tweeny::from(0, 0, 0).to(100, 200, 300).during(30U, 60U, 90U).build(); @endcode - @section easings Changing easing functions + When using per-value durations, the total interpolation length is determined by the **longest** duration. In the example above, + the first value reaches its target at frame 30, the second at frame 60, and the third at frame 90. The interpolation is + complete when all values have reached their targets (at frame 90). + + @subsection via Easing Functions - Easing functions control the interpolation values between each point. Given a percentage `p`, initial value `a` and final value `b`, a easing function will return a value between `a` and `b` corresponding to that `p`. For instance, the common implementation of a linear easing function is the following: + Easing functions control **how** values interpolate between keyframes. They take a progress value (0.0 to 1.0), a start value, + and an end value, then return the interpolated value at that progress. For example, a linear easing is simply: @code int linear(float p, int a, int b) { - return (b-a)*p + a; + return static_cast((b - a) * p + a); } @endcode - By default, tweens use a linear easing function to interpolate their values. You can change that, though: Tweeny has 30 easing functions that you can specify using tween::via function. + By default, tweens use `easing::linear`. You can change this with the `via()` method, which must be called **after** `to()`. + Tweeny includes \ref tweeny::easing "30+ built-in easing functions": @code - auto tween = tweeny::from(0).to(10).during(100).via(tweeny::easing::circularInOut); + auto tween = tweeny::from(0).to(100).during(60U).via(tweeny::easing::quadraticInOut).build(); @endcode - The same rules of tween::during applies here: if you have multi-valued tweens, you can specify a different easing for each one or use the same for all of them: + Like `during()`, you can specify easings per-value or use the same for all values: @code using tweeny::easing; - auto tween = tweeny::from(0, 1, 2).to(3, 4, 5).during(200).via(easing::exponentialIn, easing::exponentialInOut, easing::backOut); + + // Same easing for all values + auto t1 = tweeny::from(0, 0, 0).to(100, 200, 300).during(60U) + .via(easing::bounceOut) + .build(); + + // Different easing per value + auto t2 = tweeny::from(0, 0, 0).to(100, 200, 300).during(60U) + .via( + easing::linear, + easing::quadraticOut, + easing::bounceOut + ) + .build(); @endcode - For a list of all available easings, consult the modules page. - http://easings.net has a nice visualization of those easing curves. + See tweeny::easing namespace documentation for all available easings, or visit http://easings.net for visualizations. + + @subsubsection custom_easing Custom Easing Functions - You can specify custom easing functions if a different behavior is needed, by passing any callable type to tween::via conforming to the T ease(float p, T begin, T end) - prototype and returning the corresponding value. + You can provide custom easing functions as any callable matching the signature `T(float, T, T)`: @code - auto tween = tweeny::from(0).to(100).during(100).via([](float p, int a, int b) { return (b-a)*p + a; } ); + auto tween = tweeny::from(0).to(100).during(60U) + .via([](float p, int a, int b) { + return static_cast((b - a) * p * p + a); // Quadratic + }) + .build(); @endcode - To a multi type tween, you need to make sure that easing arguments conform to their type: + For heterogeneous tweens, each easing must match its corresponding value type: @code - auto tween = tweeny::from(0, 1.0f).to(100, 200.0f).during(100) - .via([](float p, int a, int b) { return (b-a)*p + a; }, [](float p, float a, float b) { return (b-a)*p + a; }); + auto tween = tweeny::from(0, 1.0f).to(100, 200.0f).during(60U) + .via([](float p, int a, int b) { return (b - a) * p + a; }, + [](float p, float a, float b) { return (b - a) * p + a; }) + .build(); @endcode - Beware that when using integral types most easing functions will not round but truncate their results. This leads to strange behaviors such as - the tween reaching its final value only when it its 100% but staying in its initial value for a long percentage portion. You can use floating point values - and round them to obtain smoother results. easing::linear does this by default for integral types, but other easings don't. - - @section multipoint Multi point tweens + @note Most easing functions truncate (not round) when returning integral types. This can cause interpolations to appear + "stuck" at the start value for a while. Use floating-point types and round manually for smoother results, or use + `easing::linear` which handles this correctly for integers. - Tweens can have multiple points: a sequence of values that will be reached in order. For instance, you might want to start from 0, reach - 100 during 500ms through a easing::linear easing, then reach 200 during 100ms through a easing::circularOut easing. + @subsection multipoint Multi-Point Animations - To allow for that, each call to tween::to adds a new tweening point. Calls to tween::during and tween::via always refer to the last added - point: + You can create complex interpolations by chaining multiple keyframes together. Each call to `to()` adds a new keyframe, and subsequent + calls to `during()` and `via()` configure that specific segment: @code auto tween = tweeny::from(0) - .to(100).during(500) // 0 to 100 during 500 - .to(200).during(100).via(easing::circularOut); // 100 to 200 during 100 via circularOut + .to(100).during(500U) // 0 → 100 (linear, 500 frames) + .to(200).during(100U).via(easing::bounceOut) // 100 → 200 (bounce, 100 frames) + .to(50).during(200U).via(easing::backInOut) // 200 → 50 (back, 200 frames) + .build(); @endcode - Stepping and seeking works transparently for the user, regardless of how many tween points there are. This means that Tweeny will - automatically manage switching from one point to another when using tween::step and tween::seek. + The resulting tween seamlessly transitions through all keyframes. Navigation methods like `step()` and `seek()` work transparently + across keyframe boundaries - Tweeny automatically handles transitions between segments. - @section interpolating Stepping, seeking and jumping. + @section navigation Navigating Tweens - After setting up points, durations and easings, a tween is ready to interpolate. There are three main ways to do that and we are going - to cover them in this section. + Once built, a tween can be navigated in three ways: stepping, seeking, and jumping. - The first one is **stepping**. It is used to move the tween forward by a delta amount, which can be either specified in duration units - or percentage. Stepping is particularly useful when you are in a event/rendering loop and has access to the delta time between frames: + @subsection step Stepping + + **Stepping** moves the tween by a relative amount (delta). This is the primary method for frame-by-frame interpolation in game loops: @code - auto tween = tweeny::from(0).to(100).during(1000); - while (!done) { - tween.step(dt); - } + auto tween = tweeny::from(0).to(100).during(1000U).build(); + while (tween.progress() < 1.0f) { + int value = tween.step(1); // Advance by 1 frame + // Use value... + } @endcode - Passing a integral quantity (integers) to tween::step will step it in duration units. Passing a float value will step it by - a percentage (ranging from 0.0f to 1.0f). + `step()` accepts a signed 32-bit integer (`int32_t`). Positive values move forward, negative values move backward: + + @code + tween.step(10); // Move forward 10 frames + tween.step(-5); // Move backward 5 frames + @endcode - You can set a tween to go backwards, so that it steps in reverse. To to that, use tween::backward: each tween::step call will decrease - a tween time until it reaches 0. To make it go forward again, use tween::forward. Tween direction makes no difference when seeking or jumping. + @subsection seek Seeking - The second one is **seeking**. Seeking is useful if you need the tween to move to a specific point in time or percentage. + **Seeking** jumps to an absolute frame position. Useful for scrubbing or jumping to specific points: @code - auto tween = tweeny::from(0).to(100).during(1000); - tween.seek(0.5f); + auto tween = tweeny::from(0).to(100).during(1000U).build(); + tween.seek(500U); // Jump to frame 500 (50% complete) + tween.seek(0U); // Jump back to start @endcode - The same value type rules of tween::step applies: a float value means a percentage and an integral value means duration. + `seek()` accepts an unsigned 32-bit integer (`uint32_t`) representing the absolute frame number. Values are clamped to the + valid range `[0, total_duration]`. + + @subsection jump Jumping to Keyframes - The third one is **jumping**. Jumping is useful to seek to a specific tween point, when you have @ref multipoint . + **Jumping** moves directly to a keyframe by its index (0-based). This is useful for multi-point interpolations: @code - auto tween = tweeny::from(0).to(100).during(100).to(200).during(100); - tween.jump(1); + auto tween = tweeny::from(0).to(100).during(100U).to(200).during(100U).build(); + tween.jump(0); // Jump to keyframe 0 (value: 0, frame: 0) + tween.jump(1); // Jump to keyframe 1 (value: 100, frame: 100) + tween.jump(2); // Jump to keyframe 2 (value: 200, frame: 200) @endcode - tween::step, tween::seek and tween::jump both returns the result of their action. The return type varies according to the - tween type according to these rules: + @subsection return_values Return Values - - If the tween has a single value, it will yield that value directly: + All navigation methods (`step()`, `seek()`, `jump()`) return the current interpolated value(s): + + - **Single-value tweens** return the value directly: @code - auto tween = tweeny::from(0).to(100).during(100); + auto tween = tweeny::from(0).to(100).during(100U).build(); int value = tween.step(10); @endcode - - If the tween has multiple values of the same type, it will yield an array with those values: + - **Multi-value tweens** return a tuple (use structured bindings): @code - auto tween = tweeny::from(0, 1).to(2, 3).during(100); - std::array v = tween.step(10); + auto tween = tweeny::from(0, 0).to(100, 200).during(100U).build(); + auto [x, y] = tween.step(10); @endcode - - If the tween has multiple types, it will return a tuple: + - **Heterogeneous tweens** also return a tuple: @code - auto tween = tweeny::from(0, 1.0f).to(2, 3.0f).during(100); - std::tuple v = tween.step(10); + auto tween = tweeny::from(0, 1.0f).to(100, 5.0f).during(100U).build(); + auto [i, f] = tween.step(10); @endcode - @section callbacks Callbacks + @subsection peek Peeking Values - Tweeny lets you specify seeking and stepping callbacks so that actions can executed in specific points - (e.g, playing a sound when a tween reaches a frame). tween::onStep add a function that will be called whenever a tween - steps whereas tween::onSeek will add a function to be called when it seeks. Although stepping is resolved - in terms of seeking, tween::step it will not trigger seek callbacks. + Use `peek()` to query the current value without modifying the tween's state: - Callbacks can be of three different types: + @code + auto tween = tweeny::from(0).to(100).during(100U).build(); + tween.step(50); + int current = tween.peek(); // Returns 50, doesn't change state + int preview = tween.peek(75U); // Preview value at frame 75, doesn't move tween + @endcode - - Accept the tween and its current values. Useful if you want access to tween values and need to control the tween instance: - @code - bool stepped(tween & t, int x, int y); - auto tween = tweeny::from(0, 0).to(100, 200).during(100).onStep(stepped); - @endcode + @section events Event System - - Accept only a tween, useful if the values itself are not interesting but you need to control tween behavior: - @code - bool stepped(tween & t); - auto tween = tweeny::from(0, 0).to(100, 200).during(100).onStep(stepped); - @endcode + Tweeny provides an event system for reacting to interpolation lifecycle events. Register callbacks using the `on()` method with + an event type tag. - - Accept only tween values, if you just want tween values: - @code - bool stepped(int x, int y); - auto tween = tweeny::from(0, 0).to(100, 200).during(100).onStep(stepped); - @endcode + @subsection event_types Event Types + + Available event types: + - `event::step` - Triggered after each `step()` call + - `event::seek` - Triggered after each `seek()` call + - `event::jump` - Triggered after each `jump()` call + - `event::complete` - Triggered when the interpolation reaches the end (progress >= 1.0) + - `event::keyframeEnter` - Triggered when transitioning into a new keyframe segment + - `event::keyframeLeave` - Triggered when transitioning out of a keyframe segment - The return type of a callback is always boolean. If it returns true, it will be *dismissed* and - removed from the callback list. Returning false keeps the callback in the queue: + @subsection basic_callbacks Basic Callbacks + + Most callbacks receive a reference to the tween and return an `event::response`: @code - bool stepped(int x, int y) { - printf("x: %d, y: %d\n", x, y); - if (x == y) return true; - return false; - } - auto tween = tweeny::from(0, 0).to(100, 200).during(100).onStep(stepped); + auto tween = tweeny::from(0, 0).to(100, 200).during(100U).build(); + + tween.on(tweeny::event::step, [](auto& t) { + auto [x, y] = t.peek(); + printf("Position: (%d, %d), Progress: %.2f\n", x, y, t.progress()); + return tweeny::event::response::ok; + }); + + // Completion callback + tween.on(tweeny::event::complete, [](auto& t) { + printf("Animation finished!\n"); + return tweeny::event::response::ok; + }); + @endcode + + @subsection keyframe_callbacks Keyframe Callbacks + + Keyframe events receive additional data through an event struct: + + @code + auto tween = tweeny::from(0).to(50).during(50U).to(100).during(50U).build(); + + tween.on(tweeny::event::keyframeEnter, [](auto& t, auto evt) { + printf("Entering keyframe %zu\n", evt.key_frame); + return tweeny::event::response::ok; + }); + + tween.on(tweeny::event::keyframeLeave, [](auto& t, auto evt) { + printf("Leaving keyframe %zu\n", evt.key_frame); + return tweeny::event::response::ok; + }); @endcode - All callable types can be used as a callback, as long as they conform to the interface. + @subsection callback_lifetime Callback Lifetime + + The return value controls whether a callback stays registered: + + - `event::response::ok` - Keep receiving events (default behavior) + - `event::response::unsubscribe` - Remove callback after this invocation (one-shot) + + @code + int step_count = 0; + tween.on(tweeny::event::step, [&](auto& t) { + step_count++; + if (step_count >= 10) { + printf("Unsubscribing after 10 steps\n"); + return tweeny::event::response::unsubscribe; + } + return tweeny::event::response::ok; + }); + @endcode + + @subsection callable_types Callable Types + + Any callable matching the required signature can be used - lambdas, function pointers, functors, etc: @code - struct ftor { - bool operator()(int x, int y) { return false; } + // Lambda (most common) + tween.on(tweeny::event::step, [](auto& t) { + return tweeny::event::response::ok; + }); + + // Function + auto my_callback = [](tweeny::tween& t) { + return tweeny::event::response::ok; }; - auto tween = tweeny::from(0, 0).to(100, 200).during(100); - tween.onStep([](int, int) { return false; }); // lambdas - tween.onStep(ftor()); // functors + tween.on(tweeny::event::seek, my_callback); @endcode - The @ref loop has some nice ways of using callbacks. + @section summary Summary -
+ This manual covered the essential aspects of using Tweeny: + - Creating tweens with the fluent builder API (`from()`, `to()`, `via()`, `during()`, `build()`) + - Navigating interpolations (`step()`, `seek()`, `jump()`, `peek()`) + - Responding to interpolation events with callbacks - This covers all the basics steps of using Tweeny. There is more to learn though, take a look at the demo repository to see - more. Consult the API of the @ref tween class to see all its methods and more examples within. + For more examples, see the demo repository. + For complete API documentation, consult the tweeny::tween class reference. - I hope you have fun using Tweeny. + Enjoy using Tweeny! */ } From a4581b3d031dd743e0976a9abbce9b2a40dd174e Mon Sep 17 00:00:00 2001 From: Leonardo Date: Fri, 26 Dec 2025 00:08:35 -0300 Subject: [PATCH 52/58] add links to visualize easing functions at easings.net --- include/tweeny/easing.h | 32 +++++++++++++++++++++++++++++++- 1 file changed, 31 insertions(+), 1 deletion(-) diff --git a/include/tweeny/easing.h b/include/tweeny/easing.h index 9c63357..474ca4d 100644 --- a/include/tweeny/easing.h +++ b/include/tweeny/easing.h @@ -51,7 +51,7 @@ * @brief Contains all built-in easing functions for controlling animation curves. * * Provides 30+ easing functions in In, Out, and InOut variants for creating natural-looking - * animations. See the @ref easings page for detailed descriptions and usage examples. + * animations. */ namespace tweeny::easing { /** @@ -86,6 +86,7 @@ namespace tweeny::easing { * * @see backOut For overshoot at the end instead of the beginning * @see backInOut For anticipation at the start and overshoot at the end + * @see Visualize at https://easings.net/#easeInBack */ inline constexpr detail::backInEasing backIn{}; @@ -125,6 +126,7 @@ namespace tweeny::easing { * @see backIn For anticipation at the start instead of overshoot at the end * @see backInOut For both anticipation and overshoot * @see elasticOut For a more pronounced oscillating overshoot effect + * @see Visualize at https://easings.net/#easeOutBack */ inline constexpr detail::backOutEasing backOut{}; @@ -164,6 +166,7 @@ namespace tweeny::easing { * @see backIn For only anticipation without overshoot * @see backOut For only overshoot without anticipation * @see elasticInOut For a more extreme oscillating version + * @see Visualize at https://easings.net/#easeInOutBack */ inline constexpr detail::backInOutEasing backInOut{}; @@ -201,6 +204,7 @@ namespace tweeny::easing { * @see bounceOut For natural ball-drop bouncing at the end * @see bounceInOut For bouncing at both start and end * @see backIn For simpler anticipation without discrete impacts + * @see Visualize at https://easings.net/#easeInBounce */ inline constexpr detail::bounceInEasing bounceIn{}; @@ -250,6 +254,7 @@ namespace tweeny::easing { * @see bounceIn For inverse bouncing at the start * @see bounceInOut For bouncing at both ends * @see elasticOut For smooth spring-like alternative + * @see Visualize at https://easings.net/#easeOutBounce */ inline constexpr detail::bounceOutEasing bounceOut{}; @@ -288,6 +293,7 @@ namespace tweeny::easing { * @see bounceIn For bouncing only at the start * @see bounceOut For bouncing only at the end (more commonly useful) * @see elasticInOut For spring-like alternative + * @see Visualize at https://easings.net/#easeInOutBounce */ inline constexpr detail::bounceInOutEasing bounceInOut{}; @@ -326,6 +332,7 @@ namespace tweeny::easing { * @see circularInOut For circular acceleration and deceleration * @see quadraticIn For gentler acceleration * @see cubicIn For stronger acceleration + * @see Visualize at https://easings.net/#easeInCirc */ inline constexpr detail::circularInEasing circularIn{}; @@ -365,6 +372,7 @@ namespace tweeny::easing { * @see circularInOut For circular motion at both ends * @see quadraticOut For gentler alternative * @see cubicOut For more pronounced alternative + * @see Visualize at https://easings.net/#easeOutCirc */ inline constexpr detail::circularOutEasing circularOut{}; @@ -403,6 +411,7 @@ namespace tweeny::easing { * @see circularOut For only deceleration * @see quadraticInOut For gentler motion * @see cubicInOut For more dramatic motion + * @see Visualize at https://easings.net/#easeInOutCirc */ inline constexpr detail::circularInOutEasing circularInOut{}; @@ -443,6 +452,7 @@ namespace tweeny::easing { * @see cubicInOut For cubic motion at both ends * @see quadraticIn For gentler acceleration * @see quarticIn For stronger acceleration + * @see Visualize at https://easings.net/#easeInCubic */ inline constexpr detail::cubicInEasing cubicIn{}; @@ -490,6 +500,7 @@ namespace tweeny::easing { * @see cubicInOut For cubic motion at both ends * @see quadraticOut For gentler deceleration * @see quarticOut For stronger deceleration + * @see Visualize at https://easings.net/#easeOutCubic */ inline constexpr detail::cubicOutEasing cubicOut{}; @@ -530,6 +541,7 @@ namespace tweeny::easing { * @see cubicOut For only deceleration * @see quadraticInOut For gentler motion * @see quarticInOut For more dramatic motion + * @see Visualize at https://easings.net/#easeInOutCubic */ inline constexpr detail::cubicInOutEasing cubicInOut{}; /** @@ -601,6 +613,7 @@ namespace tweeny::easing { * @see elasticOut For spring-like oscillation at the end * @see elasticInOut For oscillation at both start and end * @see backIn For a simpler single-overshoot alternative + * @see Visualize at https://easings.net/#easeInElastic */ inline constexpr detail::elasticInEasing elasticIn{}; @@ -660,6 +673,7 @@ namespace tweeny::easing { * @see elasticInOut For oscillation at both ends * @see backOut For a subtler single-overshoot alternative * @see bounceOut For a similar but impact-based bouncing effect + * @see Visualize at https://easings.net/#easeOutElastic */ inline constexpr detail::elasticOutEasing elasticOut{}; @@ -719,6 +733,7 @@ namespace tweeny::easing { * @see elasticIn For oscillation only at the start * @see elasticOut For oscillation only at the end (more commonly useful) * @see backInOut For a less extreme but still expressive alternative + * @see Visualize at https://easings.net/#easeInOutElastic */ inline constexpr detail::elasticInOutEasing elasticInOut{}; @@ -771,6 +786,7 @@ namespace tweeny::easing { * @see exponentialOut For explosive deceleration at the end * @see exponentialInOut For dramatic acceleration and deceleration * @see quarticIn For strong but less extreme acceleration + * @see Visualize at https://easings.net/#easeInExpo */ inline constexpr detail::exponentialInEasing exponentialIn{}; @@ -822,6 +838,7 @@ namespace tweeny::easing { * @see exponentialIn For explosive acceleration at the start * @see exponentialInOut For explosive motion at both ends * @see quarticOut For strong but less extreme deceleration + * @see Visualize at https://easings.net/#easeOutExpo */ inline constexpr detail::exponentialOutEasing exponentialOut{}; @@ -877,6 +894,7 @@ namespace tweeny::easing { * @see exponentialIn For only explosive acceleration * @see exponentialOut For only explosive deceleration * @see quinticInOut For slightly less extreme alternative + * @see Visualize at https://easings.net/#easeInOutExpo */ inline constexpr detail::exponentialInOutEasing exponentialInOut{}; /** @@ -970,6 +988,7 @@ namespace tweeny::easing { * @see quadraticOut For gentle deceleration * @see quadraticInOut For gentle motion at both ends * @see cubicIn For more pronounced acceleration + * @see Visualize at https://easings.net/#easeInQuad */ inline constexpr detail::quadraticInEasing quadraticIn{}; @@ -1009,6 +1028,7 @@ namespace tweeny::easing { * @see quadraticIn For gentle acceleration * @see quadraticInOut For gentle motion at both ends * @see cubicOut For more pronounced deceleration + * @see Visualize at https://easings.net/#easeOutQuad */ inline constexpr detail::quadraticOutEasing quadraticOut{}; @@ -1044,6 +1064,7 @@ namespace tweeny::easing { * @see quadraticIn For only gentle acceleration * @see quadraticOut For only gentle deceleration * @see cubicInOut For more pronounced motion + * @see Visualize at https://easings.net/#easeInOutQuad */ inline constexpr detail::quadraticInOutEasing quadraticInOut{}; @@ -1080,6 +1101,7 @@ namespace tweeny::easing { * @see quarticInOut For strong motion at both ends * @see cubicIn For gentler acceleration * @see quinticIn For even stronger acceleration + * @see Visualize at https://easings.net/#easeInQuart */ inline constexpr detail::quarticInEasing quarticIn{}; @@ -1119,6 +1141,7 @@ namespace tweeny::easing { * @see quarticInOut For strong motion at both ends * @see cubicOut For gentler deceleration * @see quinticOut For even stronger deceleration + * @see Visualize at https://easings.net/#easeOutQuart */ inline constexpr detail::quarticOutEasing quarticOut{}; @@ -1155,6 +1178,7 @@ namespace tweeny::easing { * @see quarticOut For only strong deceleration * @see cubicInOut For gentler motion * @see quinticInOut For more dramatic motion + * @see Visualize at https://easings.net/#easeInOutQuart */ inline constexpr detail::quarticInOutEasing quarticInOut{}; @@ -1191,6 +1215,7 @@ namespace tweeny::easing { * @see quinticInOut For very strong motion at both ends * @see quarticIn For slightly gentler acceleration * @see exponentialIn For even more extreme acceleration + * @see Visualize at https://easings.net/#easeInQuint */ inline constexpr detail::quinticInEasing quinticIn{}; @@ -1231,6 +1256,7 @@ namespace tweeny::easing { * @see quinticInOut For very strong motion at both ends * @see quarticOut For slightly gentler deceleration * @see exponentialOut For even more extreme deceleration + * @see Visualize at https://easings.net/#easeOutQuint */ inline constexpr detail::quinticOutEasing quinticOut{}; @@ -1267,6 +1293,7 @@ namespace tweeny::easing { * @see quinticOut For only very strong deceleration * @see quarticInOut For gentler motion * @see exponentialInOut For more extreme motion + * @see Visualize at https://easings.net/#easeInOutQuint */ inline constexpr detail::quinticInOutEasing quinticInOut{}; @@ -1303,6 +1330,7 @@ namespace tweeny::easing { * @see sinusoidalOut For smooth deceleration * @see sinusoidalInOut For smooth motion at both ends * @see quadraticIn For similar gentleness + * @see Visualize at https://easings.net/#easeInSine */ inline constexpr detail::sinusoidalInEasing sinusoidalIn{}; @@ -1341,6 +1369,7 @@ namespace tweeny::easing { * @see sinusoidalIn For smooth acceleration * @see sinusoidalInOut For smooth motion at both ends * @see quadraticOut For similar gentleness + * @see Visualize at https://easings.net/#easeOutSine */ inline constexpr detail::sinusoidalOutEasing sinusoidalOut{}; @@ -1379,6 +1408,7 @@ namespace tweeny::easing { * @see sinusoidalIn For only smooth acceleration * @see sinusoidalOut For only smooth deceleration * @see quadraticInOut For similar gentleness + * @see Visualize at https://easings.net/#easeInOutSine */ inline constexpr detail::sinusoidalInOutEasing sinusoidalInOut{}; From bd3c2f49b491f18d5929512954cef3a6d2e4d819 Mon Sep 17 00:00:00 2001 From: Leonardo Date: Fri, 26 Dec 2025 00:15:31 -0300 Subject: [PATCH 53/58] clarify manual wording and improve formatting for consistency --- include/tweeny/event.h | 2 ++ src/doc/manual.dox | 14 +++++++------- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/include/tweeny/event.h b/include/tweeny/event.h index 7a83323..34cfcd9 100644 --- a/include/tweeny/event.h +++ b/include/tweeny/event.h @@ -100,6 +100,8 @@ namespace tweeny::event { * return tweeny::event::response::ok; * }); * @endcode + * + * @anchor step */ inline struct step_t {} step; diff --git a/src/doc/manual.dox b/src/doc/manual.dox index 03e4eb4..977e3f3 100644 --- a/src/doc/manual.dox +++ b/src/doc/manual.dox @@ -201,7 +201,7 @@ namespace tweeny { @endcode The resulting tween seamlessly transitions through all keyframes. Navigation methods like `step()` and `seek()` work transparently - across keyframe boundaries - Tweeny automatically handles transitions between segments. + across keyframe boundaries. @section navigation Navigating Tweens @@ -209,7 +209,7 @@ namespace tweeny { @subsection step Stepping - **Stepping** moves the tween by a relative amount (delta). This is the primary method for frame-by-frame interpolation in game loops: + @b Stepping moves the tween by a relative amount (delta). This is the primary method for frame-by-frame interpolation in game loops: @code auto tween = tweeny::from(0).to(100).during(1000U).build(); @@ -219,7 +219,7 @@ namespace tweeny { } @endcode - `step()` accepts a signed 32-bit integer (`int32_t`). Positive values move forward, negative values move backward: + step() accepts a signed 32-bit integer (`int32_t`). Positive values move forward, negative values move backward: @code tween.step(10); // Move forward 10 frames @@ -228,7 +228,7 @@ namespace tweeny { @subsection seek Seeking - **Seeking** jumps to an absolute frame position. Useful for scrubbing or jumping to specific points: + @b Seeking jumps to an absolute frame position. Useful for scrubbing or jumping to specific points: @code auto tween = tweeny::from(0).to(100).during(1000U).build(); @@ -236,12 +236,12 @@ namespace tweeny { tween.seek(0U); // Jump back to start @endcode - `seek()` accepts an unsigned 32-bit integer (`uint32_t`) representing the absolute frame number. Values are clamped to the + seek() accepts an unsigned 32-bit integer (`uint32_t`) representing the absolute frame number. Values are clamped to the valid range `[0, total_duration]`. @subsection jump Jumping to Keyframes - **Jumping** moves directly to a keyframe by its index (0-based). This is useful for multi-point interpolations: + @b Jumping moves directly to a keyframe by its index (0-based). This is useful for multi-point interpolations: @code auto tween = tweeny::from(0).to(100).during(100U).to(200).during(100U).build(); @@ -285,7 +285,7 @@ namespace tweeny { @section events Event System - Tweeny provides an event system for reacting to interpolation lifecycle events. Register callbacks using the `on()` method with + Tweeny provides an event system for reacting to interpolation lifecycle events. Register callbacks using the tween::on() method with an event type tag. @subsection event_types Event Types From 8130d549630a5f61f4857d2ae10fee8467a8a1f9 Mon Sep 17 00:00:00 2001 From: Leonardo Date: Fri, 26 Dec 2025 00:27:09 -0300 Subject: [PATCH 54/58] update event documentation and clarify usage examples --- include/tweeny/event.h | 242 +++++++++++++++++++++++------------------ 1 file changed, 137 insertions(+), 105 deletions(-) diff --git a/include/tweeny/event.h b/include/tweeny/event.h index 34cfcd9..fb200cb 100644 --- a/include/tweeny/event.h +++ b/include/tweeny/event.h @@ -28,7 +28,8 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. * * This file defines event types and response codes for the tween event system. * Events are triggered during tween operations (step, seek, jump, complete) and - * allow callbacks to react to animation state changes. + * allow callbacks to react to animation state changes. Tag types are defined in + * detail/event.h and instantiated here with documentation. * * @code * auto t = tweeny::from(0).to(100).during(60U).build(); @@ -42,176 +43,207 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. #ifndef TWEENY_EVENT_H #define TWEENY_EVENT_H -#include +#include "detail/event.h" /** * @namespace tweeny::event * @brief Event types and response codes for tween callbacks. * * This namespace contains tag types for registering event listeners and - * response codes for controlling callback behavior. + * response codes for controlling callback behavior. Use these tags with + * tween::on() to register callbacks for animation lifecycle events. */ namespace tweeny::event { /** - * @brief Event data passed when entering a new keyframe. + * @brief Event triggered after each step() call. * - * This event is triggered when the tween transitions into a new keyframe section, - * providing the index of the keyframe being entered. + * Use this event to react to frame-by-frame progression of an animation. + * The callback receives a reference to the tween and can query its current + * state using peek() or progress(). + * + * Common use cases: + * - Updating visual properties every frame + * - Logging animation progress + * - Synchronizing with other animations + * - Implementing custom timing logic * * @code - * auto t = tweeny::from(0).to(100).during(60U).build(); - * t.on(tweeny::event::keyframeEnter, [](auto& tween, tweeny::event::keyframeEnter evt) { - * std::cout << "Entered keyframe " << evt.key_frame << std::endl; + * auto tween = tweeny::from(0).to(100).during(60U).build(); + * tween.on(tweeny::event::step, [](auto& t) { + * printf("Current value: %d\n", t.peek()); * return tweeny::event::response::ok; * }); + * + * tween.step(1); // Triggers the callback * @endcode + * + * @see seek For events when jumping to specific frames + * @see complete For events when animation finishes */ - struct keyframeEnter { - size_t key_frame; - explicit keyframeEnter(const size_t key_frame_input) : key_frame(key_frame_input) {} - }; + inline constexpr detail::event::step_t step{}; /** - * @brief Event data passed when leaving a keyframe. + * @brief Event triggered after each seek() call. * - * This event is triggered when the tween transitions out of a keyframe section, - * providing the index of the keyframe being exited. + * Use this event to react when the tween jumps to a specific frame position. + * Unlike step events, seek events fire regardless of the direction or distance + * of the movement. + * + * Common use cases: + * - Scrubbing through animations + * - Reacting to timeline jumps + * - Updating state after non-linear navigation + * - Synchronizing with external timeline controls * * @code - * auto t = tweeny::from(0).to(100).during(60U).build(); - * t.on(tweeny::event::keyframeLeave, [](auto& tween, tweeny::event::keyframeLeave evt) { - * std::cout << "Left keyframe " << evt.key_frame << std::endl; + * auto tween = tweeny::from(0).to(100).during(100U).build(); + * tween.on(tweeny::event::seek, [](auto& t) { + * printf("Seeked to frame with value: %d\n", t.peek()); * return tweeny::event::response::ok; * }); + * + * tween.seek(50U); // Triggers the callback * @endcode + * + * @see step For frame-by-frame progression events + * @see jump For keyframe-specific navigation */ - struct keyframeLeave { - size_t key_frame; - explicit keyframeLeave(const size_t key_frame_input) : key_frame(key_frame_input) {} - }; + inline constexpr detail::event::seek_t seek{}; /** - * @brief Tag type for step events. + * @brief Event triggered after each jump() call. + * + * Use this event to react when the tween jumps to a specific keyframe by index. + * This is particularly useful for multi-point animations where keyframes represent + * distinct animation phases. * - * Used with tween::on() to register callbacks triggered after step() calls. + * Common use cases: + * - Transitioning between animation states + * - Triggering phase-specific logic + * - Resetting to specific animation checkpoints + * - Implementing state machines * * @code - * t.on(tweeny::event::step, [](auto& tween) { + * auto tween = tweeny::from(0).to(50).during(30U).to(100).during(30U).build(); + * tween.on(tweeny::event::jump, [](auto& t) { + * printf("Jumped to keyframe\n"); * return tweeny::event::response::ok; * }); + * + * tween.jump(1); // Jump to keyframe 1, triggers callback * @endcode * - * @anchor step + * @see keyframeEnter For detecting keyframe transitions during playback + * @see seek For arbitrary frame jumps */ - inline struct step_t {} step; + inline constexpr detail::event::jump_t jump{}; /** - * @brief Tag type for seek events. + * @brief Event triggered when the animation is at completion. * - * Used with tween::on() to register callbacks triggered after seek() calls. + * Fires whenever progress() is >= 1.0 (100%) after a step(), seek(), or jump() call. + * This means the callback will fire **every time** the tween is navigated to a completed + * state, not just the first time. * - * @code - * t.on(tweeny::event::seek, [](auto& tween) { - * return tweeny::event::response::ok; - * }); - * @endcode - */ - inline struct seek_t {} seek; - - /** - * @brief Tag type for jump events. + * Common use cases: + * - Cleaning up resources after animation + * - Chaining animations sequentially + * - Triggering completion callbacks + * - Transitioning to next state + * - Playing sound effects or particle effects at end * - * Used with tween::on() to register callbacks triggered after jump() calls. + * @note The callback fires every time the tween is at completion (progress >= 1.0). + * If you step to completion, then back, then forward to completion again, it will + * fire both times. Use response::unsubscribe if you want one-shot behavior. * * @code - * t.on(tweeny::event::jump, [](auto& tween) { - * return tweeny::event::response::ok; + * auto tween = tweeny::from(0).to(100).during(60U).build(); + * tween.on(tweeny::event::complete, [](auto& t) { + * printf("Animation at completion: %d\n", t.peek()); + * return tweeny::event::response::unsubscribe; // One-shot callback * }); + * + * tween.step(60); // Fires complete callback + * tween.step(-10); // Now progress < 1.0 + * tween.step(10); // Would fire again, but we unsubscribed * @endcode + * + * @see step For frame-by-frame events during animation + * @see response::unsubscribe For one-shot completion handlers */ - inline struct jump_t {} jump; - - /** - * @brief Tag type for update events. - * @internal Currently unused - reserved for future implementation. - */ - inline struct update_t {} update; + inline constexpr detail::event::complete_t complete{}; /** - * @brief Tag type for completion events. + * @brief Event triggered when entering a new keyframe segment. * - * Used with tween::on() to register callbacks triggered when progress reaches 1.0. - * Fires after step(), seek(), or jump() when the animation completes. + * Fires when the tween transitions into a new keyframe section during playback. + * The callback receives both the tween reference and a keyframeEnter struct + * containing the keyframe index. * - * @code - * t.on(tweeny::event::complete, [](auto& tween) { - * printf("Animation done!\n"); - * return tweeny::event::response::ok; - * }); - * @endcode - */ - inline struct complete_t {} complete; - - /** - * @brief Tag type for the keyframeEnter event. + * Common use cases: + * - Triggering phase-specific animations + * - Playing transition sounds + * - Updating UI to reflect animation phase + * - Synchronizing multi-part animations + * - Implementing animation state machines * - * Use this tag with tween::on() to register a callback that fires when the tween - * enters a new keyframe section. + * @note Keyframe indices are 0-based. The first keyframe is index 0, second is 1, etc. * * @code - * auto t = tweeny::from(0).to(100).during(60U).build(); - * t.on(tweeny::event::keyframeEnter, [](auto& tween, tweeny::event::keyframeEnter evt) { - * std::cout << "Entered keyframe " << evt.key_frame << std::endl; + * auto tween = tweeny::from(0) + * .to(50).during(30U) + * .to(100).during(30U) + * .build(); + * + * tween.on(tweeny::event::keyframeEnter, [](auto& t, tweeny::event::keyframeEnter evt) { + * printf("Entering keyframe %zu\n", evt.key_frame); * return tweeny::event::response::ok; * }); + * + * tween.step(31); // Triggers: "Entering keyframe 1" * @endcode + * + * @see keyframeLeave For detecting when leaving a keyframe + * @see keyframeEnter For the event data struct + * @see jump For manually jumping to keyframes */ - inline struct keyframeEnter_t {} keyframeEnter; + inline constexpr detail::event::keyframeEnter_t keyframeEnter{}; /** - * @brief Tag type for the keyframeLeave event. + * @brief Event triggered when leaving a keyframe segment. + * + * Fires when the tween transitions out of a keyframe section during playback. + * The callback receives both the tween reference and a keyframeLeave struct + * containing the keyframe index being exited. + * + * Common use cases: + * - Cleaning up phase-specific resources + * - Stopping phase-specific effects + * - Logging animation progression + * - Implementing phase exit handlers + * - Coordinating complex multi-segment animations * - * Use this tag with tween::on() to register a callback that fires when the tween - * leaves a keyframe section. + * @note Keyframe indices are 0-based. Leaving keyframe 0 means transitioning + * from the first to the second segment. * * @code - * auto t = tweeny::from(0).to(100).during(60U).build(); - * t.on(tweeny::event::keyframeLeave, [](auto& tween, tweeny::event::keyframeLeave evt) { - * std::cout << "Left keyframe " << evt.key_frame << std::endl; + * auto tween = tweeny::from(0) + * .to(50).during(30U) + * .to(100).during(30U) + * .build(); + * + * tween.on(tweeny::event::keyframeLeave, [](auto& t, tweeny::event::keyframeLeave evt) { + * printf("Leaving keyframe %zu\n", evt.key_frame); * return tweeny::event::response::ok; * }); + * + * tween.step(31); // Triggers: "Leaving keyframe 0" * @endcode - */ - inline struct keyframeLeave_t {} keyframeLeave; - - /** - * @brief Response codes returned by event callbacks. * - * Controls whether a callback continues receiving events or is automatically removed. + * @see keyframeEnter For detecting when entering a keyframe + * @see keyframeLeave For the event data struct */ - enum class response { - /** - * @brief Continue receiving events. - * - * The callback remains registered and will be invoked on future events. - */ - ok = 0, - - /** - * @brief Unsubscribe after this callback. - * - * The callback is automatically removed after returning and will not - * receive future events. Useful for one-shot callbacks. - * - * @code - * t.on(tweeny::event::complete, [](auto& tween) { - * printf("Animation done!\n"); - * return tweeny::event::response::unsubscribe; // Remove this callback - * }); - * @endcode - */ - unsubscribe = 1, - }; + inline constexpr detail::event::keyframeLeave_t keyframeLeave{}; } #endif //TWEENY_EVENT_H From 5a77b8e8ecc7cef33957799b599c960ebe46f122 Mon Sep 17 00:00:00 2001 From: Leonardo Date: Fri, 26 Dec 2025 00:56:55 -0300 Subject: [PATCH 55/58] add update event type, tests, and handlers --- include/tweeny/detail/event.h | 120 ++++++++++++++++++++++ include/tweeny/event.h | 34 +++++++ include/tweeny/tween.h | 35 +++++-- include/tweeny/tween.tcc | 28 +++-- include/tweeny/tweeny.h | 2 + src/tests/CMakeLists.txt | 1 + src/tests/events/complete.cpp | 14 +-- src/tests/events/keyframe_enter.cpp | 14 +-- src/tests/events/keyframe_leave.cpp | 16 +-- src/tests/events/update.cpp | 153 ++++++++++++++++++++++++++++ 10 files changed, 380 insertions(+), 37 deletions(-) create mode 100644 include/tweeny/detail/event.h create mode 100644 src/tests/events/update.cpp diff --git a/include/tweeny/detail/event.h b/include/tweeny/detail/event.h new file mode 100644 index 0000000..44dd29f --- /dev/null +++ b/include/tweeny/detail/event.h @@ -0,0 +1,120 @@ +/* +This file is part of the Tweeny library. + +Copyright (c) 2016-2025 Leonardo Guilherme Lucena de Freitas +Copyright (c) 2016 Guilherme R. Costa + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ + +#ifndef TWEENY_DETAIL_EVENT_H +#define TWEENY_DETAIL_EVENT_H + +#include + +namespace tweeny::detail::event { + /** + * @brief Tag type for step events. + * @internal Implementation detail - users should use tweeny::event::step + */ + struct step_t {}; + + /** + * @brief Tag type for seek events. + * @internal Implementation detail - users should use tweeny::event::seek + */ + struct seek_t {}; + + /** + * @brief Tag type for jump events. + * @internal Implementation detail - users should use tweeny::event::jump + */ + struct jump_t {}; + + /** + * @brief Tag type for completion events. + * @internal Implementation detail - users should use tweeny::event::complete + */ + struct complete_t {}; + + /** + * @brief Tag type for keyframeEnter events. + * @internal Implementation detail - users should use tweeny::event::keyframeEnter + */ + struct keyframeEnter_t {}; + + /** + * @brief Tag type for keyframeLeave events. + * @internal Implementation detail - users should use tweeny::event::keyframeLeave + */ + struct keyframeLeave_t {}; + + /** + * @brief Tag type for update events. + * @internal Implementation detail - users should use tweeny::event::update + */ + struct update_t {}; +} + +namespace tweeny::event { + /** + * @brief Event data passed when entering a new keyframe. + * + * This event is triggered when the tween transitions into a new keyframe section, + * providing the index of the keyframe being entered. + */ + struct keyframeEnter { + size_t key_frame; + explicit keyframeEnter(const size_t key_frame_input) : key_frame(key_frame_input) {} + }; + + /** + * @brief Event data passed when leaving a keyframe. + * + * This event is triggered when the tween transitions out of a keyframe section, + * providing the index of the keyframe being exited. + */ + struct keyframeLeave { + size_t key_frame; + explicit keyframeLeave(const size_t key_frame_input) : key_frame(key_frame_input) {} + }; + + /** + * @brief Response codes returned by event callbacks. + * + * Controls whether a callback continues receiving events or is automatically removed. + */ + enum class response { + /** + * @brief Continue receiving events. + * + * The callback remains registered and will be invoked on future events. + */ + ok = 0, + + /** + * @brief Unsubscribe after this callback. + * + * The callback is automatically removed after returning and will not + * receive future events. Useful for one-shot callbacks. + */ + unsubscribe = 1, + }; +} + +#endif // TWEENY_DETAIL_EVENT_H diff --git a/include/tweeny/event.h b/include/tweeny/event.h index fb200cb..2342f2c 100644 --- a/include/tweeny/event.h +++ b/include/tweeny/event.h @@ -244,6 +244,40 @@ namespace tweeny::event { * @see keyframeLeave For the event data struct */ inline constexpr detail::event::keyframeLeave_t keyframeLeave{}; + + /** + * @brief Event triggered whenever the tween position changes. + * + * Fires after any step(), seek(), or jump() call. This is a convenience event + * that consolidates all position update events into a single callback point. + * + * Common use cases: + * - Updating visuals regardless of how the tween changed + * - Monitoring all tween changes from one place + * - Logging every position update + * - Synchronizing with external systems + * - Implementing universal change handlers + * + * @note This event fires **after** the specific event (step, seek, or jump) and + * **before** the complete event if applicable. + * + * @code + * auto tween = tweeny::from(0).to(100).during(60U).build(); + * tween.on(tweeny::event::update, [](auto& t) { + * printf("Tween updated to: %d\n", t.peek()); + * return tweeny::event::response::ok; + * }); + * + * tween.step(10); // Triggers update + * tween.seek(50U); // Triggers update + * tween.jump(1); // Triggers update + * @endcode + * + * @see step For frame-by-frame updates + * @see seek For arbitrary frame jumps + * @see jump For keyframe jumps + */ + inline constexpr detail::event::update_t update{}; } #endif //TWEENY_EVENT_H diff --git a/include/tweeny/tween.h b/include/tweeny/tween.h index 08bd7cd..a0f4d46 100644 --- a/include/tweeny/tween.h +++ b/include/tweeny/tween.h @@ -48,7 +48,7 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. #include "detail/key-frame.h" #include "detail/tween-value.h" -#include "event.h" +#include "detail/event.h" namespace tweeny { /** @@ -214,7 +214,7 @@ namespace tweeny { * t.step(10); // Prints: "Stepped to: 10" * @endcode */ - template auto on(event::step_t, Callback&& cb) -> void ; + template auto on(detail::event::step_t, Callback&& cb) -> void ; /** * @brief Registers a callback for seek() events. @@ -230,7 +230,7 @@ namespace tweeny { * }); * @endcode */ - template auto on(event::seek_t, Callback&& cb) -> void ; + template auto on(detail::event::seek_t, Callback&& cb) -> void ; /** * @brief Registers a callback for jump() events. @@ -246,7 +246,7 @@ namespace tweeny { * }); * @endcode */ - template auto on(event::jump_t, Callback&& cb) -> void ; + template auto on(detail::event::jump_t, Callback&& cb) -> void ; /** * @brief Registers a callback for animation completion. @@ -265,7 +265,7 @@ namespace tweeny { * t.seek(100U); // Triggers complete event * @endcode */ - template auto on(event::complete_t, Callback&& cb) -> void ; + template auto on(detail::event::complete_t, Callback&& cb) -> void ; /** * @brief Registers a callback for entering a keyframe. @@ -284,7 +284,7 @@ namespace tweeny { * t.step(31); // Triggers: "Entered keyframe 1" * @endcode */ - template auto on(event::keyframeEnter_t, Callback&& cb) -> void ; + template auto on(detail::event::keyframeEnter_t, Callback&& cb) -> void ; /** * @brief Registers a callback for leaving a keyframe. @@ -303,7 +303,27 @@ namespace tweeny { * t.step(31); // Triggers: "Left keyframe 0" * @endcode */ - template auto on(event::keyframeLeave_t, Callback&& cb) -> void ; + template auto on(detail::event::keyframeLeave_t, Callback&& cb) -> void ; + + /** + * @brief Registers a callback for any position update (step, seek, or jump). + * + * The callback is invoked after step(), seek(), or jump() calls. This is a convenience + * event for monitoring all position changes from a single callback. + * + * @param cb Callback with signature: event::response(tween&) + * + * @code + * auto t = tweeny::from(0).to(100).during(60U).build(); + * t.on(event::update, [](auto& tween) { + * printf("Tween updated to: %d\n", tween.peek()); + * return event::response::ok; + * }); + * t.step(10); // Triggers update + * t.seek(50U); // Triggers update + * @endcode + */ + template auto on(detail::event::update_t, Callback&& cb) -> void ; private: using callback_t = std::function; @@ -318,6 +338,7 @@ namespace tweeny { std::vector seek_listeners; std::vector jump_listeners; std::vector complete_listeners; + std::vector update_listeners; std::vector keyframe_enter_listeners; std::vector keyframe_leave_listeners; diff --git a/include/tweeny/tween.tcc b/include/tweeny/tween.tcc index e91e171..4aa94c7 100644 --- a/include/tweeny/tween.tcc +++ b/include/tweeny/tween.tcc @@ -58,6 +58,7 @@ auto tweeny::tween::seek(const uint32_t invoke_keyframe_listeners(old_keyframe_index, new_keyframe_index); invoke_listeners(seek_listeners); + invoke_listeners(update_listeners); if (progress() >= 1.0f) { invoke_listeners(complete_listeners); @@ -78,6 +79,7 @@ auto tweeny::tween::jump(std::size_t tar invoke_keyframe_listeners(old_keyframe_index, target_key_frame); invoke_listeners(jump_listeners); + invoke_listeners(update_listeners); if (progress() >= 1.0f) { invoke_listeners(complete_listeners); @@ -107,6 +109,7 @@ auto tweeny::tween::step(const int32_t f invoke_keyframe_listeners(old_keyframe_index, new_keyframe_index); invoke_listeners(step_listeners); + invoke_listeners(update_listeners); if (progress() >= 1.0f) { invoke_listeners(complete_listeners); @@ -117,7 +120,7 @@ auto tweeny::tween::step(const int32_t f template template -auto tweeny::tween::on(event::step_t, Callback && cb) -> void { +auto tweeny::tween::on(detail::event::step_t, Callback && cb) -> void { using result_t = std::invoke_result_t; static_assert(std::is_same_v, "step callback must return tweeny::event::response"); @@ -126,7 +129,7 @@ auto tweeny::tween::on(event::step_t, Ca template template -auto tweeny::tween::on(event::seek_t, Callback && cb) -> void { +auto tweeny::tween::on(detail::event::seek_t, Callback && cb) -> void { using result_t = std::invoke_result_t; static_assert(std::is_same_v, "seek callback must return tweeny::event::response"); @@ -135,7 +138,7 @@ auto tweeny::tween::on(event::seek_t, Ca template template -auto tweeny::tween::on(event::jump_t, Callback && cb) -> void { +auto tweeny::tween::on(detail::event::jump_t, Callback && cb) -> void { using result_t = std::invoke_result_t; static_assert(std::is_same_v, "jump callback must return tweeny::event::response"); @@ -144,7 +147,7 @@ auto tweeny::tween::on(event::jump_t, Ca template template -auto tweeny::tween::on(event::complete_t, Callback && cb) -> void { +auto tweeny::tween::on(detail::event::complete_t, Callback && cb) -> void { using result_t = std::invoke_result_t; static_assert(std::is_same_v, "complete callback must return tweeny::event::response"); @@ -153,8 +156,8 @@ auto tweeny::tween::on(event::complete_t template template -auto tweeny::tween::on(event::keyframeEnter_t, Callback && cb) -> void { - using result_t = std::invoke_result_t; +auto tweeny::tween::on(detail::event::keyframeEnter_t, Callback && cb) -> void { + using result_t = std::invoke_result_t; static_assert(std::is_same_v, "keyframeEnter callback must return tweeny::event::response"); keyframe_enter_listeners.emplace_back(std::forward(cb)); @@ -162,13 +165,22 @@ auto tweeny::tween::on(event::keyframeEn template template -auto tweeny::tween::on(event::keyframeLeave_t, Callback && cb) -> void { - using result_t = std::invoke_result_t; +auto tweeny::tween::on(detail::event::keyframeLeave_t, Callback && cb) -> void { + using result_t = std::invoke_result_t; static_assert(std::is_same_v, "keyframeLeave callback must return tweeny::event::response"); keyframe_leave_listeners.emplace_back(std::forward(cb)); } +template +template +auto tweeny::tween::on(detail::event::update_t, Callback && cb) -> void { + using result_t = std::invoke_result_t; + static_assert(std::is_same_v, + "update callback must return tweeny::event::response"); + update_listeners.emplace_back(std::forward(cb)); +} + template auto tweeny::tween::invoke_listeners(std::vector& listeners) -> void { std::vector to_remove; diff --git a/include/tweeny/tweeny.h b/include/tweeny/tweeny.h index 1a9f9e8..aa77f38 100644 --- a/include/tweeny/tweeny.h +++ b/include/tweeny/tweeny.h @@ -42,6 +42,8 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. #include #include "detail/tuple-utilities.h" +#include "event.h" +#include "easing.h" /** * @namespace tweeny diff --git a/src/tests/CMakeLists.txt b/src/tests/CMakeLists.txt index ec91155..667e2e2 100644 --- a/src/tests/CMakeLists.txt +++ b/src/tests/CMakeLists.txt @@ -14,6 +14,7 @@ add_executable(tweeny_tests events/complete.cpp events/keyframe_enter.cpp events/keyframe_leave.cpp + events/update.cpp ) target_compile_features(tweeny_tests PRIVATE cxx_std_17) diff --git a/src/tests/events/complete.cpp b/src/tests/events/complete.cpp index 303fdfc..b26a7a9 100644 --- a/src/tests/events/complete.cpp +++ b/src/tests/events/complete.cpp @@ -1,7 +1,7 @@ #include #include -TEST_CASE("complete event - triggered when reaching end via step", "[event][complete]") { +TEST_CASE("event::complete - triggered when reaching end via step", "[event][complete]") { auto t = tweeny::from(0).to(100).during(100U).build(); bool completed = false; @@ -19,7 +19,7 @@ TEST_CASE("complete event - triggered when reaching end via step", "[event][comp REQUIRE(completed); } -TEST_CASE("complete event - triggered when reaching end via seek", "[event][complete]") { +TEST_CASE("event::complete - triggered when reaching end via seek", "[event][complete]") { auto t = tweeny::from(0).to(100).during(100U).build(); bool completed = false; @@ -32,7 +32,7 @@ TEST_CASE("complete event - triggered when reaching end via seek", "[event][comp REQUIRE(completed); } -TEST_CASE("complete event - triggered when jumping to last keyframe", "[event][complete]") { +TEST_CASE("event::complete - triggered when jumping to last keyframe", "[event][complete]") { auto t = tweeny::from(0).to(50).during(50U).to(100).during(50U).build(); bool completed = false; @@ -45,7 +45,7 @@ TEST_CASE("complete event - triggered when jumping to last keyframe", "[event][c REQUIRE(completed); } -TEST_CASE("complete event - not triggered when not at end", "[event][complete]") { +TEST_CASE("event::complete - not triggered when not at end", "[event][complete]") { auto t = tweeny::from(0).to(100).during(100U).build(); bool completed = false; @@ -61,7 +61,7 @@ TEST_CASE("complete event - not triggered when not at end", "[event][complete]") REQUIRE_FALSE(completed); } -TEST_CASE("complete event - can unsubscribe", "[event][complete]") { +TEST_CASE("event::complete - can unsubscribe", "[event][complete]") { auto t = tweeny::from(0).to(100).during(100U).build(); int call_count = 0; @@ -79,7 +79,7 @@ TEST_CASE("complete event - can unsubscribe", "[event][complete]") { REQUIRE(call_count == 1); // Should still be 1 } -TEST_CASE("complete event - multiple listeners", "[event][complete]") { +TEST_CASE("event::complete - multiple listeners", "[event][complete]") { auto t = tweeny::from(0).to(100).during(100U).build(); int listener1_count = 0; @@ -100,7 +100,7 @@ TEST_CASE("complete event - multiple listeners", "[event][complete]") { REQUIRE(listener2_count == 1); } -TEST_CASE("complete event - triggered on exact completion", "[event][complete]") { +TEST_CASE("event::complete - triggered on exact completion", "[event][complete]") { auto t = tweeny::from(0).to(100).during(100U).build(); bool completed = false; diff --git a/src/tests/events/keyframe_enter.cpp b/src/tests/events/keyframe_enter.cpp index 9220dfc..cdd23c2 100644 --- a/src/tests/events/keyframe_enter.cpp +++ b/src/tests/events/keyframe_enter.cpp @@ -25,7 +25,7 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. #include #include "tweeny/tweeny.h" -TEST_CASE("keyframeEnter event triggers when entering a new keyframe via step") { +TEST_CASE("event::keyframeEnter - triggers when entering a new keyframe via step", "[event][keyframeEnter]") { auto t = tweeny::from(0).to(50).during(30U).to(100).during(30U).build(); std::size_t entered_keyframe = 999; int call_count = 0; @@ -42,7 +42,7 @@ TEST_CASE("keyframeEnter event triggers when entering a new keyframe via step") REQUIRE(entered_keyframe == 1); } -TEST_CASE("keyframeEnter event triggers when entering a new keyframe via seek") { +TEST_CASE("event::keyframeEnter - triggers when entering a new keyframe via seek", "[event][keyframeEnter]") { auto t = tweeny::from(0).to(50).during(30U).to(100).during(30U).build(); std::size_t entered_keyframe = 999; int call_count = 0; @@ -59,7 +59,7 @@ TEST_CASE("keyframeEnter event triggers when entering a new keyframe via seek") REQUIRE(entered_keyframe == 1); } -TEST_CASE("keyframeEnter event triggers when entering a new keyframe via jump") { +TEST_CASE("event::keyframeEnter - triggers when entering a new keyframe via jump", "[event][keyframeEnter]") { auto t = tweeny::from(0).to(50).during(30U).to(100).during(30U).build(); std::size_t entered_keyframe = 999; int call_count = 0; @@ -76,7 +76,7 @@ TEST_CASE("keyframeEnter event triggers when entering a new keyframe via jump") REQUIRE(entered_keyframe == 1); } -TEST_CASE("keyframeEnter event does not trigger when staying in the same keyframe") { +TEST_CASE("event::keyframeEnter - does not trigger when staying in the same keyframe", "[event][keyframeEnter]") { auto t = tweeny::from(0).to(50).during(30U).to(100).during(30U).build(); int call_count = 0; @@ -92,7 +92,7 @@ TEST_CASE("keyframeEnter event does not trigger when staying in the same keyfram REQUIRE(call_count == 0); } -TEST_CASE("keyframeEnter event triggers for the correct keyframe in multi-keyframe tween") { +TEST_CASE("event::keyframeEnter - triggers for the correct keyframe in multi-keyframe tween", "[event][keyframeEnter]") { auto t = tweeny::from(0).to(25).during(10U).to(50).during(10U).to(75).during(10U).to(100).during(10U).build(); std::vector entered_keyframes; @@ -111,7 +111,7 @@ TEST_CASE("keyframeEnter event triggers for the correct keyframe in multi-keyfra REQUIRE(entered_keyframes[2] == 3); } -TEST_CASE("keyframeEnter event can unsubscribe") { +TEST_CASE("event::keyframeEnter - can unsubscribe", "[event][keyframeEnter]") { auto t = tweeny::from(0).to(50).during(30U).to(100).during(30U).build(); int call_count = 0; @@ -127,7 +127,7 @@ TEST_CASE("keyframeEnter event can unsubscribe") { REQUIRE(call_count == 1); } -TEST_CASE("keyframeEnter event triggers when stepping backward into a different keyframe") { +TEST_CASE("event::keyframeEnter - triggers when stepping backward into a different keyframe", "[event][keyframeEnter]") { auto t = tweeny::from(0).to(50).during(30U).to(100).during(30U).build(); std::size_t entered_keyframe = 999; int call_count = 0; diff --git a/src/tests/events/keyframe_leave.cpp b/src/tests/events/keyframe_leave.cpp index f989f26..39f997e 100644 --- a/src/tests/events/keyframe_leave.cpp +++ b/src/tests/events/keyframe_leave.cpp @@ -25,7 +25,7 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. #include #include "tweeny/tweeny.h" -TEST_CASE("keyframeLeave event triggers when leaving a keyframe via step") { +TEST_CASE("event::keyframeLeave - triggers when leaving a keyframe via step", "[event][keyframeLeave]") { auto t = tweeny::from(0).to(50).during(30U).to(100).during(30U).build(); std::size_t left_keyframe = 999; int call_count = 0; @@ -42,7 +42,7 @@ TEST_CASE("keyframeLeave event triggers when leaving a keyframe via step") { REQUIRE(left_keyframe == 0); } -TEST_CASE("keyframeLeave event triggers when leaving a keyframe via seek") { +TEST_CASE("event::keyframeLeave - triggers when leaving a keyframe via seek", "[event][keyframeLeave]") { auto t = tweeny::from(0).to(50).during(30U).to(100).during(30U).build(); std::size_t left_keyframe = 999; int call_count = 0; @@ -59,7 +59,7 @@ TEST_CASE("keyframeLeave event triggers when leaving a keyframe via seek") { REQUIRE(left_keyframe == 0); } -TEST_CASE("keyframeLeave event triggers when leaving a keyframe via jump") { +TEST_CASE("event::keyframeLeave - triggers when leaving a keyframe via jump", "[event][keyframeLeave]") { auto t = tweeny::from(0).to(50).during(30U).to(100).during(30U).build(); std::size_t left_keyframe = 999; int call_count = 0; @@ -76,7 +76,7 @@ TEST_CASE("keyframeLeave event triggers when leaving a keyframe via jump") { REQUIRE(left_keyframe == 0); } -TEST_CASE("keyframeLeave event does not trigger when staying in the same keyframe") { +TEST_CASE("event::keyframeLeave - does not trigger when staying in the same keyframe", "[event][keyframeLeave]") { auto t = tweeny::from(0).to(50).during(30U).to(100).during(30U).build(); int call_count = 0; @@ -92,7 +92,7 @@ TEST_CASE("keyframeLeave event does not trigger when staying in the same keyfram REQUIRE(call_count == 0); } -TEST_CASE("keyframeLeave event triggers for the correct keyframe in multi-keyframe tween") { +TEST_CASE("event::keyframeLeave - triggers for the correct keyframe in multi-keyframe tween", "[event][keyframeLeave]") { auto t = tweeny::from(0).to(25).during(10U).to(50).during(10U).to(75).during(10U).to(100).during(10U).build(); std::vector left_keyframes; @@ -111,7 +111,7 @@ TEST_CASE("keyframeLeave event triggers for the correct keyframe in multi-keyfra REQUIRE(left_keyframes[2] == 2); } -TEST_CASE("keyframeLeave event can unsubscribe") { +TEST_CASE("event::keyframeLeave - can unsubscribe", "[event][keyframeLeave]") { auto t = tweeny::from(0).to(50).during(30U).to(100).during(30U).build(); int call_count = 0; @@ -127,7 +127,7 @@ TEST_CASE("keyframeLeave event can unsubscribe") { REQUIRE(call_count == 1); } -TEST_CASE("keyframeLeave event triggers when stepping backward into a different keyframe") { +TEST_CASE("event::keyframeLeave - triggers when stepping backward into a different keyframe", "[event][keyframeLeave]") { auto t = tweeny::from(0).to(50).during(30U).to(100).during(30U).build(); std::size_t left_keyframe = 999; int call_count = 0; @@ -146,7 +146,7 @@ TEST_CASE("keyframeLeave event triggers when stepping backward into a different REQUIRE(left_keyframe == 1); } -TEST_CASE("keyframeLeave and keyframeEnter events trigger in correct order") { +TEST_CASE("event::keyframeLeave - leave and enter events trigger in correct order", "[event][keyframeLeave]") { auto t = tweeny::from(0).to(50).during(30U).to(100).during(30U).build(); std::vector event_order; diff --git a/src/tests/events/update.cpp b/src/tests/events/update.cpp new file mode 100644 index 0000000..b416bbb --- /dev/null +++ b/src/tests/events/update.cpp @@ -0,0 +1,153 @@ +#include +#include + +TEST_CASE("event::update - callback is invoked on step()", "[event][update]") { + auto t = tweeny::from(0) + .to(10) + .during(10U) + .build(); + + int called = 0; + + t.on(tweeny::event::update, [&](const auto &) { + ++called; + return tweeny::event::response::ok; + }); + + (void)t.step(1); + REQUIRE(called == 1); +} + +TEST_CASE("event::update - callback is invoked on seek()", "[event][update]") { + auto t = tweeny::from(0) + .to(10) + .during(10U) + .build(); + + int called = 0; + + t.on(tweeny::event::update, [&](const auto &) { + ++called; + return tweeny::event::response::ok; + }); + + (void)t.seek(5U); + REQUIRE(called == 1); +} + +TEST_CASE("event::update - callback is invoked on jump()", "[event][update]") { + auto t = tweeny::from(0) + .to(10) + .during(10U) + .build(); + + int called = 0; + + t.on(tweeny::event::update, [&](const auto &) { + ++called; + return tweeny::event::response::ok; + }); + + (void)t.jump(1); + REQUIRE(called == 1); +} + +TEST_CASE("event::update - callback fires after specific event", "[event][update]") { + auto t = tweeny::from(0) + .to(10) + .during(10U) + .build(); + + int step_order = 0; + int update_order = 0; + int counter = 0; + + t.on(tweeny::event::step, [&](const auto &) { + step_order = ++counter; + return tweeny::event::response::ok; + }); + + t.on(tweeny::event::update, [&](const auto &) { + update_order = ++counter; + return tweeny::event::response::ok; + }); + + (void)t.step(1); + REQUIRE(step_order == 1); + REQUIRE(update_order == 2); +} + +TEST_CASE("event::update - callback fires before complete event", "[event][update]") { + auto t = tweeny::from(0) + .to(10) + .during(10U) + .build(); + + int update_order = 0; + int complete_order = 0; + int counter = 0; + + t.on(tweeny::event::update, [&](const auto &) { + update_order = ++counter; + return tweeny::event::response::ok; + }); + + t.on(tweeny::event::complete, [&](const auto &) { + complete_order = ++counter; + return tweeny::event::response::ok; + }); + + (void)t.step(10); + REQUIRE(update_order == 1); + REQUIRE(complete_order == 2); +} + +TEST_CASE("event::update - unsubscribe removes listener", "[event][update]") { + auto t = tweeny::from(0) + .to(10) + .during(10U) + .build(); + + int called = 0; + + t.on(tweeny::event::update, [&](const auto &) { + ++called; + return tweeny::event::response::unsubscribe; + }); + + t.on(tweeny::event::update, [&](const auto &) { + ++called; + return tweeny::event::response::ok; + }); + + (void)t.step(1); + REQUIRE(called == 2); + + (void)t.step(1); + REQUIRE(called == 3); +} + +TEST_CASE("event::update - fires on all update types in sequence", "[event][update]") { + auto t = tweeny::from(0) + .to(10) + .during(10U) + .to(20) + .during(10U) + .build(); + + int called = 0; + + t.on(tweeny::event::update, [&](const auto &) { + ++called; + return tweeny::event::response::ok; + }); + + (void)t.step(1); + REQUIRE(called == 1); + + (void)t.seek(5U); + REQUIRE(called == 2); + + (void)t.jump(1); + REQUIRE(called == 3); +} From ae05b9a6429eb298ec0f676c1705d8a3873b59fb Mon Sep 17 00:00:00 2001 From: Leonardo Date: Fri, 26 Dec 2025 01:49:18 -0300 Subject: [PATCH 56/58] add v3-to-v4 migration guide and update documentation --- src/doc/CMakeLists.txt | 1 + src/doc/Doxyfile.in | 2 +- src/doc/mainpage.dox | 2 + src/doc/manual.dox | 12 +- src/doc/v3_to_v4.dox | 389 +++++++++++++++++++++++++++++++++++++++++ 5 files changed, 396 insertions(+), 10 deletions(-) create mode 100644 src/doc/v3_to_v4.dox diff --git a/src/doc/CMakeLists.txt b/src/doc/CMakeLists.txt index d5dd5eb..dd24aba 100644 --- a/src/doc/CMakeLists.txt +++ b/src/doc/CMakeLists.txt @@ -29,6 +29,7 @@ configure_file(Doxyfile.in ${CMAKE_CURRENT_BINARY_DIR}/Doxyfile @ONLY) set(DOC_FILES "${CMAKE_CURRENT_SOURCE_DIR}/mainpage.dox" "${CMAKE_CURRENT_SOURCE_DIR}/manual.dox" + "${CMAKE_CURRENT_SOURCE_DIR}/v3_to_v4.dox" "${CMAKE_CURRENT_SOURCE_DIR}/DoxygenLayout.xml" "${CMAKE_CURRENT_SOURCE_DIR}/../../README.md" ) diff --git a/src/doc/Doxyfile.in b/src/doc/Doxyfile.in index 7948b4d..36cbdda 100644 --- a/src/doc/Doxyfile.in +++ b/src/doc/Doxyfile.in @@ -765,7 +765,7 @@ WARN_LOGFILE = # spaces. See also FILE_PATTERNS and EXTENSION_MAPPING # Note: If this tag is empty the current directory is searched. -INPUT = mainpage.dox manual.dox @CMAKE_CURRENT_SOURCE_DIR@/../../include +INPUT = mainpage.dox manual.dox v3_to_v4.dox @CMAKE_CURRENT_SOURCE_DIR@/../../include # This tag can be used to specify the character encoding of the source files # that doxygen parses. Internally doxygen uses the UTF-8 encoding. Doxygen uses diff --git a/src/doc/mainpage.dox b/src/doc/mainpage.dox index e1195c2..df92b83 100644 --- a/src/doc/mainpage.dox +++ b/src/doc/mainpage.dox @@ -19,6 +19,8 @@ declaring interpolations (tweens) of any numeric type that supports arithmetic o @section quickstart Quick Start +@note Coming from Tweeny 3.x? Check out the @ref v3_to_v4 "migration guide"! + @code #include using tweeny::easing; diff --git a/src/doc/manual.dox b/src/doc/manual.dox index 977e3f3..4280f8a 100644 --- a/src/doc/manual.dox +++ b/src/doc/manual.dox @@ -4,6 +4,8 @@ namespace tweeny { This document is the manual for Tweeny. It walks you through all the important steps when creating and controlling tweens. +@note Coming from Tweeny 3.x? Check out the @ref v3_to_v4 "migration guide"! + @section creating Creating Tweens @subsection builder_intro The Builder Pattern @@ -372,15 +374,7 @@ namespace tweeny { tween.on(tweeny::event::seek, my_callback); @endcode - @section summary Summary - - This manual covered the essential aspects of using Tweeny: - - Creating tweens with the fluent builder API (`from()`, `to()`, `via()`, `during()`, `build()`) - - Navigating interpolations (`step()`, `seek()`, `jump()`, `peek()`) - - Responding to interpolation events with callbacks - - For more examples, see the demo repository. - For complete API documentation, consult the tweeny::tween class reference. + @section done Done! Enjoy using Tweeny! */ diff --git a/src/doc/v3_to_v4.dox b/src/doc/v3_to_v4.dox new file mode 100644 index 0000000..9079e99 --- /dev/null +++ b/src/doc/v3_to_v4.dox @@ -0,0 +1,389 @@ +namespace tweeny { +/** + @page v3_to_v4 Migrating from Tweeny 3.x to 4.x + + This guide helps you migrate code from Tweeny 3.x to 4.x. While the core concepts remain the same, + version 4 introduces significant API changes centered around a builder pattern and a new event system. + + @section overview_changes Overview of Changes + + The main changes in Tweeny 4.x are: + - **Builder Pattern:** tweeny::from now returns a builder; you must call `build()` to create a tween + - **Immutable Tweens:** Once built, keyframes, durations, and easings cannot be changed + - **New Event System:** Callbacks are now registered via `on()` with event types instead of `onStep()`/`onSeek()` + - **Type System Changes:** Duration and step parameters are now strongly typed (`uint32_t` and `int32_t`) + - **Return Value Changes:** Multi-value tweens now return tuples instead of arrays + - **New peek() Method:** Query values without changing tween state + - **Direction API Removed:** `forward()`/`backward()` removed; use negative steps instead + + @section builder_pattern The Builder Pattern + + @subsection builder_basic Basic Usage + + @b Tweeny 3.x: + @code + auto tween = tweeny::from(0).to(100).during(100); + @endcode + + @b Tweeny 4.x: + @code + auto tween = tweeny::from(0).to(100).during(100U).build(); + @endcode + + The key difference: you must explicitly call `build()` to create the tween. The builder is reusable: + + @code + auto builder = tweeny::from(0).to(100).during(60U); + auto tween1 = builder.build(); // First tween + builder.to(200).during(120U); // Add keyframe + auto tween2 = builder.build(); // Different tween (0→100→200) + @endcode + + @subsection builder_compile_time Compile-Time Safety + + Tweeny 4.x enforces that you call `to()` at least once before building: + + @b Tweeny 3.x: (would create invalid tween) + @code + auto tween = tweeny::from(0).during(100); // Creates tween with no target + @endcode + + @b Tweeny 4.x: (compilation error) + @code + // auto tween = tweeny::from(0).during(100U).build(); // ERROR: no to() called + auto tween = tweeny::from(0).to(100).during(100U).build(); // OK + @endcode + + @section type_changes Type System Changes + + @subsection duration_types Duration Types + + @b Tweeny 3.x: Durations could be any unsigned integer type + @code + auto tween = tweeny::from(0).to(100).during(100); // int literal + @endcode + + @b Tweeny 4.x: Durations must be `uint32_t` (use the `U` suffix) + @code + auto tween = tweeny::from(0).to(100).during(100U).build(); // uint32_t + @endcode + + @subsection step_types Step Types + + @b Tweeny 3.x: step() accepted floats (percentage) or integers (duration) + @code + tween.step(0.5f); // Step by 50% + tween.step(10); // Step by 10 units + @endcode + + @b Tweeny 4.x: step() only accepts `int32_t` (duration), no percentage mode + @code + tween.step(10); // Step forward by 10 frames + tween.step(-5); // Step backward by 5 frames + @endcode + + @subsection seek_types Seek Types + + @b Tweeny 3.x: seek() accepted floats (percentage) or integers (absolute position) + @code + tween.seek(0.5f); // Seek to 50% + tween.seek(500); // Seek to frame 500 + @endcode + + @b Tweeny 4.x: seek() only accepts `uint32_t` (absolute frame position) + @code + tween.seek(500U); // Seek to frame 500 + + // For percentage, calculate manually: + tween.seek(static_cast(tween.total_duration() * 0.5f)); + @endcode + + @subsection return_values Return Values + + @b Tweeny 3.x: Multi-value tweens returned `std::array` + @code + auto tween = tweeny::from(0, 0).to(100, 200).during(100); + std::array values = tween.step(10); + int x = values[0]; + int y = values[1]; + @endcode + + @b Tweeny 4.x: Multi-value tweens return tuples (use structured bindings) + @code + auto tween = tweeny::from(0, 0).to(100, 200).during(100U).build(); + auto [x, y] = tween.step(10); + @endcode + + @section direction_changes Direction Changes + + @b Tweeny 3.x: Used `forward()` and `backward()` to control direction + @code + tween.backward(); + tween.step(10); // Steps backward + tween.forward(); + tween.step(10); // Steps forward + @endcode + + @b Tweeny 4.x: Use signed integers with step() + @code + tween.step(-10); // Steps backward by 10 + tween.step(10); // Steps forward by 10 + @endcode + + @section peek_method The peek() Method + + Tweeny 4.x introduces `peek()` to query values without mutating state. + + @b Tweeny 3.x: No direct equivalent; you had to step and track state manually + @code + auto tween = tweeny::from(0).to(100).during(100); + tween.seek(50); + // Get current value by stepping 0 (awkward) + int value = tween.step(0); + @endcode + + @b Tweeny 4.x: Use peek() for non-mutating queries + @code + auto tween = tweeny::from(0).to(100).during(100U).build(); + tween.seek(50U); + + int current = tween.peek(); // Get current value without changing state + int preview = tween.peek(75U); // Preview value at frame 75 without seeking + @endcode + + @section event_system Event System Changes + + The callback system has been completely redesigned around an event-based architecture. + + @subsection callback_registration Registration + + @b Tweeny 3.x: Used `onStep()` and `onSeek()` methods + @code + auto tween = tweeny::from(0, 0).to(100, 200).during(100); + + // Step callback accepting values + tween.onStep([](int x, int y) { + printf("Position: (%d, %d)\n", x, y); + return false; // false = keep callback + }); + + // Step callback accepting tween reference + tween.onStep([](auto& t) { + return false; + }); + + // Step callback accepting both + tween.onStep([](auto& t, int x, int y) { + return false; + }); + + // Seek callback + tween.onSeek([](int x, int y) { + return false; + }); + @endcode + + @b Tweeny 4.x: Use `on()` with event type tags + @code + auto tween = tweeny::from(0, 0).to(100, 200).during(100U).build(); + + // Step callback (receives tween reference) + tween.on(tweeny::event::step, [](auto& t) { + auto [x, y] = t.peek(); + printf("Position: (%d, %d)\n", x, y); + return tweeny::event::response::ok; // Keep callback + }); + + // Seek callback + tween.on(tweeny::event::seek, [](auto& t) { + return tweeny::event::response::ok; + }); + + // Jump callback (new in v4) + tween.on(tweeny::event::jump, [](auto& t) { + return tweeny::event::response::ok; + }); + @endcode + + @subsection callback_return_values Callback Return Values + + @b Tweeny 3.x: Returned `bool` + @code + return true; // Remove callback (one-shot) + return false; // Keep callback + @endcode + + @b Tweeny 4.x: Returns `event::response` enum + @code + return tweeny::event::response::unsubscribe; // Remove callback + return tweeny::event::response::ok; // Keep callback + @endcode + + @subsection new_events New Event Types + + Tweeny 4.x introduces several new event types: + + @code + // Triggered when interpolation completes (reaches 100%) + tween.on(tweeny::event::complete, [](auto& t) { + printf("Animation finished!\n"); + return tweeny::event::response::ok; + }); + + // Triggered when entering a new keyframe segment + tween.on(tweeny::event::keyframeEnter, [](auto& t, auto evt) { + printf("Entering keyframe %zu\n", evt.key_frame); + return tweeny::event::response::ok; + }); + + // Triggered when leaving a keyframe segment + tween.on(tweeny::event::keyframeLeave, [](auto& t, auto evt) { + printf("Leaving keyframe %zu\n", evt.key_frame); + return tweeny::event::response::ok; + }); + @endcode + + @subsection callback_signatures Callback Signature Changes + + Tweeny 3.x callbacks could receive interpolated values directly. Tweeny 4.x callbacks always receive + a tween reference; use `peek()` to get values: + + @b Tweeny 3.x: + @code + tween.onStep([](int x, int y) { // Values passed directly + printf("x=%d, y=%d\n", x, y); + return false; + }); + @endcode + + @b Tweeny 4.x: + @code + tween.on(tweeny::event::step, [](auto& t) { // Tween reference only + auto [x, y] = t.peek(); // Explicitly peek values + printf("x=%d, y=%d\n", x, y); + return tweeny::event::response::ok; + }); + @endcode + + @section migration_checklist Migration Checklist + + Use this checklist to migrate your code: + + 1. **Add `.build()` calls** + - Find all `tweeny::from(...)` chains + - Add `.build()` at the end to create the tween + + 2. **Update duration literals** + - Change `during(100)` to `during(100U)` + - Ensure all duration values use `uint32_t` + + 3. **Update step() calls** + - Remove percentage-based stepping (convert to frame counts) + - Use negative values for backward stepping instead of `backward()` + + 4. **Update seek() calls** + - Remove percentage-based seeking (calculate frame from percentage manually) + - Add `U` suffix to all seek values: `seek(500)` → `seek(500U)` + + 5. **Replace array destructuring with tuple destructuring** + - Change `std::array v = tween.step(...)` to `auto [v1, v2, ...] = tween.step(...)` + + 6. **Update callbacks** + - Replace `onStep(callback)` with `on(event::step, callback)` + - Replace `onSeek(callback)` with `on(event::seek, callback)` + - Change callback signatures to accept `auto& t` parameter + - Use `t.peek()` to get values inside callbacks + - Change `return true/false` to `return event::response::unsubscribe/ok` + + 7. **Remove forward()/backward() calls** + - Replace with signed step values + + 8. **Use peek() for non-mutating queries** + - Replace `step(0)` patterns with `peek()` + - Use `peek(frame)` to preview values at different positions + + 9. **Consider new event types** + - Add `event::complete` callbacks for animation completion + - Add `event::keyframeEnter`/`event::keyframeLeave` for multi-point tweens + + @section example_migration Complete Migration Example + + @b Tweeny 3.x: + @code + #include "tweeny.h" + + auto tween = tweeny::from(0, 0, 255) + .to(640, 480, 0) + .during(2000) + .via(tweeny::easing::backOut); + + tween.onStep([](int x, int y, int alpha) { + draw_sprite(x, y, alpha); + return false; + }); + + tween.onSeek([](int x, int y, int alpha) { + printf("Seeked to: %d, %d, %d\n", x, y, alpha); + return false; + }); + + // Animation loop + while (tween.progress() < 1.0f) { + tween.step(delta_time); + render(); + } + + // Reverse + tween.backward(); + while (tween.progress() > 0.0f) { + tween.step(delta_time); + render(); + } + @endcode + + @b Tweeny 4.x: + @code + #include "tweeny.h" + + auto tween = tweeny::from(0, 0, 255) + .to(640, 480, 0) + .during(2000U) + .via(tweeny::easing::backOut) + .build(); // <-- Must call build() + + tween.on(tweeny::event::step, [](auto& t) { + auto [x, y, alpha] = t.peek(); // <-- Use peek() to get values + draw_sprite(x, y, alpha); + return tweeny::event::response::ok; // <-- New return type + }); + + tween.on(tweeny::event::seek, [](auto& t) { + auto [x, y, alpha] = t.peek(); + printf("Seeked to: %d, %d, %d\n", x, y, alpha); + return tweeny::event::response::ok; + }); + + // Completion event (new in v4) + tween.on(tweeny::event::complete, [](auto& t) { + printf("Animation complete!\n"); + return tweeny::event::response::ok; + }); + + // Animation loop + while (tween.progress() < 1.0f) { + tween.step(delta_time); // delta_time is int32_t + render(); + } + + // Reverse (use negative steps instead of backward()) + while (tween.progress() > 0.0f) { + tween.step(-delta_time); // <-- Negative value steps backward + render(); + } + @endcode + + @section migration_done Done! + + For detailed information on the new API, see the @ref manual. +*/ +} From 5a48eefefd0d6f452273fafc23e78b11139e19c3 Mon Sep 17 00:00:00 2001 From: Leonardo Date: Fri, 26 Dec 2025 01:57:43 -0300 Subject: [PATCH 57/58] update link text for easing visualizations in mainpage doc --- src/doc/mainpage.dox | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/doc/mainpage.dox b/src/doc/mainpage.dox index df92b83..291416c 100644 --- a/src/doc/mainpage.dox +++ b/src/doc/mainpage.dox @@ -136,8 +136,7 @@ auto mixed = tweeny::from(0, 0) @section resources Resources - GitHub Repository -- Demo Applications -- Easing Function Cheat Sheet +- Easing visualizations, a very useful tool @section license License From 547eec226ddd8c941c7f7744f63fa2d1708debf1 Mon Sep 17 00:00:00 2001 From: Leonardo Date: Fri, 26 Dec 2025 02:18:07 -0300 Subject: [PATCH 58/58] remove sandbox example code and adjust CMake configuration --- .gitignore | 1 + CMakeLists.txt | 5 +++-- src/sandbox.cc | 39 --------------------------------------- 3 files changed, 4 insertions(+), 41 deletions(-) delete mode 100644 src/sandbox.cc diff --git a/.gitignore b/.gitignore index f3aff03..d9136a8 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ .idea cmake-build-* build +src/sandbox.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 43d0ab7..e4830ad 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -106,8 +106,9 @@ if (TWEENY_BUILD_SINGLE_HEADER) include(cmake/GenerateSingleHeader.cmake) endif () -if (TWEENY_BUILD_SANDBOX) - add_executable(sandbox src/sandbox.cc) +# Check for sandbox file existence and build if found +if (EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/src/sandbox.cpp") + add_executable(sandbox src/sandbox.cpp) target_link_libraries(sandbox tweeny) # Enable very strict warnings and treat warnings as errors for the sandbox target diff --git a/src/sandbox.cc b/src/sandbox.cc deleted file mode 100644 index d2e34ee..0000000 --- a/src/sandbox.cc +++ /dev/null @@ -1,39 +0,0 @@ -#include -#include "tweeny.h" - -int main() { - // Create a tween from 0 to 100 over 100 frames - auto tween = tweeny::from(0.0f) - .to(100.0f) - .via(tweeny::easing::linear) - .during(100U) - .build(); - - // Demonstrate peek() - query without mutating - printf("Initial value (peek): %f\n", static_cast(tween.peek())); - printf("Progress: %f%%\n", static_cast(tween.progress() * 100.0f)); - - // Peek at frame 50 without changing state - printf("Value at frame 50 (peek): %f\n", static_cast(tween.peek(50U))); - printf("Still at start, progress: %f%%\n", static_cast(tween.progress() * 100.0f)); - - // Step forward - tween.step(25); - printf("\nAfter stepping 25 frames:\n"); - printf("Current value: %f\n", static_cast(tween.peek())); - printf("Progress: %f%%\n", static_cast(tween.progress() * 100.0f)); - - // Seek to specific frame - tween.seek(75U); - printf("\nAfter seeking to frame 75:\n"); - printf("Current value: %f\n", static_cast(tween.peek())); - printf("Progress: %f%%\n", static_cast(tween.progress() * 100.0f)); - - // Complete the tween - tween.seek(100U); - printf("\nAt end:\n"); - printf("Final value: %f\n", static_cast(tween.peek())); - printf("Progress: %f%%\n", static_cast(tween.progress() * 100.0f)); - - return 0; -}