diff --git a/Tween.h b/Tween.h index d7f2a03..e60ca71 100644 --- a/Tween.h +++ b/Tween.h @@ -2,6 +2,9 @@ #include "Easing.h" #include "TimeWarp.h" +//---------------------------------------------------------------------- +// EASING HELPERS +//---------------------------------------------------------------------- namespace EasingFunc { // typedef float (*eFunc)(float); // тип easing-функции, как у Вас @@ -26,6 +29,21 @@ namespace EasingFunc { }; // 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; @@ -33,6 +51,8 @@ private: static inline uint32_t frameRateTimer; Tween* next; + TweenListener* listener = nullptr; + public: Tween(uint16_t fps = 30) { setFps(fps); @@ -53,6 +73,9 @@ public: } } + void setListener(TweenListener* l) { listener = l; } + TweenListener* getListener() const { return listener; } + /////// private: @@ -86,10 +109,11 @@ public: if (!isPlayingF && triggerLastTick) { triggerLastTick = false; current = to; + if (listener != nullptr) listener->onTweenFinished(*this); return; } - if (!isPlayingF || easing == nullptr) return; + if (!isPlayingF) return; progress = constrain(progress + dt, 0, duration); float normProgress = constrain(progress / duration, 0, 1); @@ -102,6 +126,8 @@ public: isPlayingF = false; } + if (listener != nullptr) listener->onTweenUpdate(*this); + frameRateTimer = millis(); } } @@ -110,20 +136,15 @@ public: return constrain(progress / duration, 0, 1); } - void start(float from, float to, uint16_t duration, EasingFunc::eFunc easing) { - current = from; - if (from == to) { - current = to; - isPlayingF = false; - triggerLastTick = true; - return; - } + void start(float from_, float to_, uint16_t durationMs, EasingFunc::eFunc easing_ = nullptr) { + // если easing не задан, используем линейную + easing = (easing_ != nullptr) ? easing_ : EasingFunc::easeLinear; - this->from = from; - this->to = to; - this->duration = duration / 1000.0; - this->easing = easing; + from = from_; + to = to_; + duration = durationMs / 1000.0f; progress = 0; + current = from; oldMillis = millis(); isPlayingF = true; triggerLastTick = false; @@ -178,4 +199,112 @@ private: // else if (value > b) return b; // else return value; // } -}; \ No newline at end of file +}; + + + +//---------------------------------------------------------------------- +// 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; + } + } + }; + \ No newline at end of file