FR: ComponentAnimator lambda callbacks

Right now, ComponentAnimator relies on sendChangeMessage() to notify listeners when it starts animating, and when it stops animating.

it would be pretty useful if we could attach a lambda callback for each component that is being animated.

the AnimationTask class can just have one member added and initialized in the constructor:

class ComponentAnimator::AnimationTask
{
public:
    AnimationTask (Component* c, std::function<void()> stopCallback) noexcept  : 
        component (c), 
        stopAnimationCallback( std::move(stopCallback) )
        {}
//...snip
    std::function<void()> stopAnimationCallback;

and for when it starts:

void ComponentAnimator::animateComponent (Component* const component,
                                          const Rectangle<int>& finalBounds,
                                          const float finalAlpha,
                                          const int millisecondsToSpendMoving,
                                          const bool useProxyComponent,
                                          const double startSpeed,
                                          const double endSpeed,
                                          std::function<void()> startAnimationCallback,
                                          std::function<void()> stopAnimationCallback)
{
    // the speeds must be 0 or greater!
    jassert (startSpeed >= 0 && endSpeed >= 0);

    if (component != nullptr)
    {
        auto* at = findTaskFor (component);

        if (at == nullptr)
        {
            at = new AnimationTask (component, stopAnimationCallback);
            tasks.add (at);
            sendChangeMessage();

            if( startAnimationCallback ) startAnimationCallback();
        }

        at->reset (finalBounds, finalAlpha, millisecondsToSpendMoving,
                   useProxyComponent, startSpeed, endSpeed);

        if (! isTimerRunning())
        {
            lastTime = Time::getMillisecondCounter();
            startTimerHz (50);
        }
    }
}

something like this for when it stops being animated:

void ComponentAnimator::timerCallback()
{
    auto timeNow = Time::getMillisecondCounter();

    if (lastTime == 0)
        lastTime = timeNow;

    auto elapsed = (int) (timeNow - lastTime);

    for (auto* task : Array<AnimationTask*> (tasks.begin(), tasks.size()))
    {
        if (tasks.contains (task) && ! task->useTimeslice (elapsed))
        {
            std::function<void()> callback;
            if( task->stopAnimationCallback ) callback = task->stopAnimationCallback;

            tasks.removeObject (task);          
            sendChangeMessage();
            if( callback ) callback();
        }
    }

    lastTime = timeNow;

    if (tasks.size() == 0)
        stopTimer();
}

This would be a much cleaner way to do stuff when animations start or end, compared to inheriting from ChangeListener, and checking if the ChangeBroadcaster was the Desktop::getInstance().getAnimator() in the changeListenerCallback.

5 Likes

There are a few places the API could be updated to use newer C++ features now that our set of supported platforms is slowly getting younger. This is one of them! We’ll be doing a tidy up sometime after the next big release.

2 Likes

bump! Any news of this being added to the development tip?

No news sorry. It’s in our backlog but I’m afraid it’s low priority, so estimating when it might get done is very difficult.

@matkatmusic that would be very cool, and I believe that you can speed up things if you provide your changes as a pull request in github: at that point it will probably be only a few code reviews away from being integrated

1 Like

bump!

I’m doing this until there is an official addition:

struct ComponentAnimatorLambdas : ChangeListener
{
    ComponentAnimatorLambdas()
    {
        animator.addChangeListener(this);
    }
    ~ComponentAnimatorLambdas()
    {
        animator.removeChangeListener(this);
    }
    
    /*
     the changeListener callback is called after a task for a component is created, and added to the tasks[] container
     it is also called after a task is removed from the tasks[] container
     ComponentAnimator::isAnimating(Component*) searches the tasks to see if there is a task for the provided component
     we can leverage this as long as we store the component being animated locally.
     */
    void changeListenerCallback(ChangeBroadcaster* source) override
    {
        if( animator.isAnimating(currentTarget) )
        {
            if( onAnimationStart )
                onAnimationStart();
        }
        else
        {
            //animation has ended.  the animation task has been removed from the tasks[]
            if( onAnimationEnd() )
            {
                onAnimationEnd();
            }
            currentTarget = nullptr;
        }
    }
    
    void animateComponent (Component* const component,
                           const Rectangle<int>& finalBounds,
                           const float finalAlpha,
                           const int millisecondsToSpendMoving,
                           const bool useProxyComponent,
                           const double startSpeed,
                           const double endSpeed)
    {
        currentTarget = component;
        animator.animatComponent(component,
                                 finalBounds,
                                 finalAlpha,
                                 millisecondsToSpendMoving,
                                 useProxyComponent,
                                 startSpeed,
                                 endSpeed);
    }
    
    void fadeIn (Component* component, int millisecondsToTake)
    {
        if (component != nullptr && ! (component->isVisible() && component->getAlpha() == 1.0f))
        {
            component->setAlpha (0.0f);
            component->setVisible (true);
            animateComponent (component, component->getBounds(), 1.0f, millisecondsToTake, false, 1.0, 1.0);
        }
    }
    
    void fadeOut (Component* component, int millisecondsToTake)
    {
        if (component != nullptr)
        {
            if (component->isShowing() && millisecondsToTake > 0)
                animateComponent (component, component->getBounds(), 0.0f, millisecondsToTake, true, 1.0, 1.0);
            
            component->setVisible (false);
        }
    }
    std::function<void()> onAnimationStart;
    std::function<void()> onAnimationEnd();
private:
    Component* currentTarget = nullptr;
    ComponentAnimator animator;
    JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(ComponentAnimatorLambdas)
};