#pragma once #include "Easing.h" #include "TimeWarp.h" //---------------------------------------------------------------------- // EASING HELPERS //---------------------------------------------------------------------- namespace EasingFunc { // typedef float (*eFunc)(float); // тип easing-функции, как у Вас // Статические данные для обёртки static const TimeWarp* g_warp = nullptr; static eFunc g_baseEasing = nullptr; // Обёртка с модификатором времени (вызывается Tween'ом) inline float _easingWithWarp(float t) { if (!g_warp || !g_baseEasing) return t; float warpedT = g_warp->apply(t); return g_baseEasing(warpedT); } // Установить текущий модификатор времени inline eFunc withTimeWarp(eFunc easing, const TimeWarp& warp) { g_warp = &warp; g_baseEasing = easing; return _easingWithWarp; } }; // namespace EasingFunc //---------------------------------------------------------------------- // LISTENER‑ИНТЕРФЕЙС //---------------------------------------------------------------------- class Tween; // forward class TweenListener { public: virtual void onTweenFinished(Tween& tween) = 0; virtual void onTweenUpdate(Tween& tween) = 0; virtual ~TweenListener() = default; }; //---------------------------------------------------------------------- // TWEEN //---------------------------------------------------------------------- class Tween { private: static inline Tween* head = nullptr; static inline Tween* last = nullptr; static inline uint32_t frameRateTimer; Tween* next; TweenListener* listener = nullptr; public: Tween(uint16_t fps = 30) { setFps(fps); if (Tween::head == nullptr) { Tween::head = this; } if (last != nullptr) { last->next = this; } last = this; } static void tick() { Tween* current = Tween::head; while (current != nullptr) { current->update(); current = current->next; } } void setListener(TweenListener* l) { listener = l; } TweenListener* getListener() const { return listener; } /////// private: uint16_t frameTime; float dt; uint32_t oldMillis; float from; float to; float duration; EasingFunc::eFunc easing = nullptr; float progress; bool isPlayingF; bool triggerLastTick = false; // Флаг последнего тика public: float current; void dtTick() { uint32_t loopStartTime = millis(); dt = (loopStartTime - oldMillis) / 1000.0; oldMillis = loopStartTime; } void update() { if (millis() - frameRateTimer > frameTime) { uint32_t now = millis(); dt = (now - oldMillis) / 1000.0; oldMillis = now; if (!isPlayingF && triggerLastTick) { triggerLastTick = false; current = to; if (listener != nullptr) listener->onTweenFinished(*this); return; } if (!isPlayingF) return; progress = constrain(progress + dt, 0, duration); float normProgress = constrain(progress / duration, 0, 1); current = Tween::lerp(from, to, easing(normProgress)); if (progress >= duration) { current = to; triggerLastTick = true; isPlayingF = false; } if (listener != nullptr) listener->onTweenUpdate(*this); frameRateTimer = millis(); } } float getProgress() { return constrain(progress / duration, 0, 1); } void start(float from_, float to_, uint16_t durationMs, EasingFunc::eFunc easing_ = nullptr) { // если easing не задан, используем линейную easing = (easing_ != nullptr) ? easing_ : EasingFunc::easeLinear; from = from_; to = to_; duration = durationMs / 1000.0f; progress = 0; current = from; oldMillis = millis(); isPlayingF = true; triggerLastTick = false; } void resetToStart() { progress = 0; } void stop() { isPlayingF = false; current = to; triggerLastTick = true; } bool isPlaying() const { return isPlayingF || triggerLastTick; // Учитываем последний тик } void setFps(uint16_t fps){ frameTime = 1000 / fps; } float getFrom() const { return from; } float getTo() const { return to; } static float integrateEasing(EasingFunc::eFunc easing, int steps = 100) { float sum = 0; for (int i = 0; i <= steps; ++i) { float t = i / (float)steps; sum += easing(t); } return sum / (steps + 1); // Среднее значение функции } static float lerp(float a, float b, float t) { return a + (b - a) * t; } private: // int clamp(int value, int a, int b) { // if (a > b) { // a ^= b; // b ^= a; // a ^= b; // } // if (value < a) return a; // else if (value > b) return b; // else return value; // } }; //---------------------------------------------------------------------- // ANIMATION CHAIN //---------------------------------------------------------------------- class AnimationChain : public TweenListener { public: using FromFunc = std::function; struct Anim { float from; FromFunc fromFunc = nullptr; float to; uint16_t duration; // мс EasingFunc::eFunc easing; }; private: std::vector animations; Tween tween; // единственный Tween size_t currentIndex = 0; bool chainPlaying = false; float* currentUserPtr = nullptr; void launchCurrent() { const Anim& a = animations[currentIndex]; tween.start(a.fromFunc ? a.fromFunc() : a.from, a.to, a.duration, a.easing); } public: float current = 0; // актуальное значение наружу explicit AnimationChain(uint16_t fps = 30) : tween(fps) { tween.setListener(this); // подписываемся } //------------------------------------------------------------------ // API ЦЕПОЧКИ //------------------------------------------------------------------ void addAnim(float from, float to, uint16_t duration, EasingFunc::eFunc easing = nullptr) { animations.push_back({from, {}, to, duration, easing}); } void addAnim(FromFunc fromF, float to, uint16_t duration, EasingFunc::eFunc easing = nullptr) { animations.push_back({0, fromF, to, duration, easing}); } void clear() { animations.clear(); } void start() { if (animations.empty()) return; currentIndex = 0; chainPlaying = true; launchCurrent(); Serial.printf("Chain start [%d]\n", currentIndex); } void stop() { chainPlaying = false; tween.stop(); } void setCustomParam(float* val) { currentUserPtr = val; } void resetCustomParam() { currentUserPtr = nullptr; } //------------------------------------------------------------------ // STATE //------------------------------------------------------------------ bool isChainPlaying() const { return chainPlaying; } bool isAnimPlaying(size_t index) const { return chainPlaying && index == currentIndex && tween.isPlaying(); } size_t getCurrentIndex() const { return currentIndex; } // void setCurrentIndex(size_t index){ currentIndex = index; } private: //------------------------------------------------------------------ // LISTENER CALLBACK //------------------------------------------------------------------ void onTweenFinished(Tween& /*t*/) override { ++currentIndex; if (currentIndex < animations.size()) { Serial.printf("Chain next [%d]\n", currentIndex); launchCurrent(); } else { Serial.printf("Chain stop [x]\n", currentIndex); chainPlaying = false; // вся цепочка закончилась } } void onTweenUpdate(Tween& /*t*/) override { if (chainPlaying) { current = tween.current; // даём наружу актуальное значение if(currentUserPtr != nullptr) *currentUserPtr = current; } else { current = *currentUserPtr; } } };