A tool to animate a pair of values (x,y)

Hi,
I came up with this little tool because I needed to create animation for a few couple of x,y values (and ComponentAnimator wasn’t meeting my requirements) … I get so much out of this forum that I thought “why not to share this”, maybe someone on this forum might find it handy. Surely it can be improved but it works and I find it quite useful :slightly_smiling_face:.
It uses Timer internally, you can attach a lambda function to onStep to have a function called on each step of the animation.
Here it is:

template <typename T>
    struct XYAnimator : public Timer
    {
        XYAnimator()
        {        }
        XYAnimator(T startX, T endX, T startY, T endY, int animationDurationInMilliseconds)
        {
            startX = startX;
            endX = endX;
            startY = startY;
            endY = endY;
            figureOutDirections();
            ms = animationDurationInMilliseconds;
        }
        void calculateStepsSize()
        {
            stepX = (((endX - startX) / ms) * 1000 ) / hz ;
            stepY = (((endY - startY) / ms) * 1000 ) / hz ;
        }
        void timerCallback() override
        {            
            if(onStep)onStep();
            if ((increaseX && currentX < endX) || (!increaseX && currentX > endX)) currentX += stepX;
            if ((increaseY && currentY < endX) || (!increaseY && currentY > endX)) currentY += stepY;
            
            if ( ((increaseX && currentX >= endX) || (!increaseX && currentX <= endX)) && ((increaseY && currentY >= endY) || (!increaseY && currentY <= endY)))
            {
                stopTimer();
                if (onEnd) onEnd();
            }
        }
        void figureOutDirections(){increaseX=(startX<endX)?true:false; increaseY=(startY<endY)?true:false;}
        void start()
        {
            currentX = startX;
            currentY = startY;
            if(onStart)onStart();
            startTimerHz(hz);
        }
        XYAnimator& setHz(int timerFrequencyHz){hz = timerFrequencyHz; calculateStepsSize(); return *this;}
        XYAnimator& setDurationInMilliseconds(int milliseconds){ms = milliseconds; calculateStepsSize(); return *this;}
        XYAnimator& setStartValues(T x, T y)
        {startX = x; startY = y; figureOutDirections(); calculateStepsSize(); return *this; }
        XYAnimator& setEndValues(T x, T y)
        {endX = x; endY = y; figureOutDirections(); calculateStepsSize(); return *this; }
        XYAnimator& setStartEndValues(T xStart, T yStart, T xEnd, T yEnd)
        { setStartValues(xStart, yStart); setEndValues(xEnd, yEnd); figureOutDirections(); return *this; }
        bool isAnimating() {return isTimerRunning();}
        T startX, endX, startY, endY, stepX, stepY, currentX, currentY;
        int ms {150};
        
        int hz {60};
        bool increaseX, increaseY;
        std::function<void()> onStart;
        std::function<void()> onEnd;
        std::function<void()> onStep;
    };

Any suggestion is much appreciated. For example, even if I am not a pro I can see that all those if statements in the callback could turn into a more elegant and probably more efficient code somehow…

3 Likes

You may be able to steal some ideas from the JUCE animation controller that I put together: https://artandlogic.com/2019/09/friz-and-the-illusion-of-life/

7 Likes

Hello @bgporter , i really enjoyed reading that post, i am also a fan of animation, having been (or being?) an animator myself, before focusing more into sound and music composition. I like how you have implemented curves! It is one thing i would like to implement as well (an old good “bounce” effect sometimes is needed), elaborating more on the idea of the little widget i posted. Many “what if” that passed through my mind seem to be part of what you have realized: sine/cos lfo-like patterns for example.
I will certainly have a look at the demo application.
I think it would be nice to have a way to “draw” the animation curve…mmm🤔
Thanks for your input and link.

The hand-drawn easing curves are (as all good programming textbooks would do it) left as an exercise for the reader.

1 Like

Hey guys,

I saw this thread in the morning whilst I was still half asleep and it has occupied some spare brain cycles in the back of my head all day now.

I’m looking to build some kind of system of managing animations, and I’m (still) pretty n00b at this sort of stuff.

I think I read in @bgporter’s blog, that his animation system is able to avoid any runtime overhead if not being used. And I was kind of surprised. I would imagine that a system that does (optional) animation, at least requires somewhere in the draw loop, to check if any animation needs to be applied.

I’m building this in Rust (which I think is GREAT btw, and definitely worth starting a thread over and we should have a separate discussion), so I’m trying to wrap my head around this conceptually.

If you can guide me that would be of great benefit.

  • Should Animator be a struct (Rust doesn’t really have ‘classes’) that lives inside of every thing I want to animate, and gets called by the indivual element’s draw function, prior to being drawn,

  • or should it live outside and manage a vector of ‘jobs’ whose side effect is to mutate the data it points to. Which gets run in the global update loop before the global draw loop?

I’m beginning to think of programming, less about the syntax of code, and more about the concepts behind them, which is really encouraging and cool. But it’s also my first time really thinking like this.

So any guidance from the more experienced folk, such as yourself Brett, is greatly valued.

Thanks.

  1. My system is timer-based, and if there’s no animation in effect, the timer is killed until another one is launched.

  2. In the system I built, the animator is a separate controller that operates on other objects in the system (and it doesn’t care what those other objects are, since the coupling is purely in terms of lambdas). The less logic you require to be built inside of objects to be controlled (ideally none!) the more widely usable your control system is. If you’re animating e.g. the location or size of JUCE components, the already extant setBounds() is all you need. Small pieces loosely coupled will always reduce your headaches down the road.

That being said – different software will have different needs that lead to different designs; my first instinct at this point is probably not going to be a mixin class that imbues some other object with knowledge that it’s animatable, but I can imagine that design and the arguments that led to it.

Right, I’ve just spent the day building my Animator, and it works, but it’s fundamentally just job scheduling, with ‘working’ and ‘complete’ states that happen to hold Animation jobs. But the animation jobs themselves are defined inside the Animator so that it knows what to do, you just have to pass it a pointer to the data you want it to mutate. I like the idea of passing it a lambda though, but then how does the Animator know when the job is complete and to remove it from its list of jobs to process?

In my system, an Animation contains 1 or more AnimatedValue objects that each generate their next value when called, and each have an internal state of either processing or complete (and completion may be because it’s run for a pre-defined amount of time, or if the current value is within a tolerance of the desired endpoint, or something else that no one has implemented yet). When all of the values in the animation have reached their completed state, the animation as a whole is by definition complete as well.