Ejemplo n.º 1
0
void AnimationBase::fireAnimationEventsIfNeeded()
{
    if (!m_compAnim)
        return;

    // If we are waiting for the delay time to expire and it has, go to the next state
    if (m_animState != AnimationStateStartWaitTimer && m_animState != AnimationStateLooping && m_animState != AnimationStateEnding)
        return;

    // We have to make sure to keep a ref to the this pointer, because it could get destroyed
    // during an animation callback that might get called. Since the owner is a CompositeAnimation
    // and it ref counts this object, we will keep a ref to that instead. That way the AnimationBase
    // can still access the resources of its CompositeAnimation as needed.
    Ref<AnimationBase> protect(*this);
    Ref<CompositeAnimation> protectCompositeAnimation(*m_compAnim);
    
    // Check for start timeout
    if (m_animState == AnimationStateStartWaitTimer) {
        if (beginAnimationUpdateTime() - m_requestedStartTime >= m_animation->delay())
            updateStateMachine(AnimationStateInputStartTimerFired, 0);
        return;
    }
    
    double elapsedDuration = beginAnimationUpdateTime() - m_startTime;
    // FIXME: we need to ensure that elapsedDuration is never < 0. If it is, this suggests that
    // we had a recalcStyle() outside of beginAnimationUpdate()/endAnimationUpdate().
    // Also check in getTimeToNextEvent().
    elapsedDuration = max(elapsedDuration, 0.0);
    
    // Check for end timeout
    if (m_totalDuration >= 0 && elapsedDuration >= m_totalDuration) {
        // We may still be in AnimationStateLooping if we've managed to skip a
        // whole iteration, in which case we should jump to the end state.
        LOG(Animations, "%p AnimationState %s -> Ending", this, nameForState(m_animState));
        m_animState = AnimationStateEnding;

        // Fire an end event
        updateStateMachine(AnimationStateInputEndTimerFired, m_totalDuration);
    } else {
        // Check for iteration timeout
        if (m_nextIterationDuration < 0) {
            // Hasn't been set yet, set it
            double durationLeft = m_animation->duration() - fmod(elapsedDuration, m_animation->duration());
            m_nextIterationDuration = elapsedDuration + durationLeft;
        }
        
        if (elapsedDuration >= m_nextIterationDuration) {
            // Set to the next iteration
            double previous = m_nextIterationDuration;
            double durationLeft = m_animation->duration() - fmod(elapsedDuration, m_animation->duration());
            m_nextIterationDuration = elapsedDuration + durationLeft;
            
            // Send the event
            updateStateMachine(AnimationStateInputLoopTimerFired, previous);
        }
    }
}
Ejemplo n.º 2
0
double AnimationBase::getElapsedTime() const
{
#if ENABLE(CSS_ANIMATIONS_LEVEL_2)
    if (m_animation->trigger() && m_animation->trigger()->isScrollAnimationTrigger()) {
        ScrollAnimationTrigger& scrollTrigger = downcast<ScrollAnimationTrigger>(*m_animation->trigger().get());
        if (scrollTrigger.hasEndValue() && m_object) {
            float offset = m_compositeAnimation->animationController().scrollPosition();
            float startValue = scrollTrigger.startValue().value();
            if (offset < startValue)
                return 0;
            float endValue = scrollTrigger.endValue().value();
            if (offset > endValue)
                return m_animation->duration();
            return m_animation->duration() * (offset - startValue) / (endValue - startValue);
        }
    }
#endif

    if (paused())
        return m_pauseTime - m_startTime;
    if (m_startTime <= 0)
        return 0;
    if (postActive() || fillingForwards())
        return m_totalDuration;

    return beginAnimationUpdateTime() - m_startTime;
}
Ejemplo n.º 3
0
double AnimationBase::timeToNextService()
{
    // Returns the time at which next service is required. -1 means no service is required. 0 means 
    // service is required now, and > 0 means service is required that many seconds in the future.
    if (paused() || isNew() || m_animationState == AnimationState::FillingForwards)
        return -1;
    
    if (m_animationState == AnimationState::StartWaitTimer) {
#if ENABLE(CSS_ANIMATIONS_LEVEL_2)
        if (m_animation->trigger()->isScrollAnimationTrigger()) {
            if (m_object) {
                float currentScrollOffset = m_object->view().frameView().scrollOffsetForFixedPosition().height().toFloat();
                ScrollAnimationTrigger& scrollTrigger = downcast<ScrollAnimationTrigger>(*m_animation->trigger().get());
                if (currentScrollOffset >= scrollTrigger.startValue().value() && (!scrollTrigger.hasEndValue() || currentScrollOffset <= scrollTrigger.endValue().value()))
                    return 0;
            }
            return -1;
        }
#endif
        double timeFromNow = m_animation->delay() - (beginAnimationUpdateTime() - m_requestedStartTime);
        return std::max(timeFromNow, 0.0);
    }
    
    fireAnimationEventsIfNeeded();
        
    // In all other cases, we need service right away.
    return 0;
}
void AnimationControllerPrivate::endAnimationUpdate()
{
    ASSERT(m_beginAnimationUpdateCount > 0);
    if (m_beginAnimationUpdateCount == 1) {
        styleAvailable();
        if (!m_waitingForAsyncStartNotification)
            startTimeResponse(beginAnimationUpdateTime());
    }
    --m_beginAnimationUpdateCount;
}
Ejemplo n.º 5
0
double AnimationBase::getElapsedTime() const
{
    if (paused())    
        return m_pauseTime - m_startTime;
    if (m_startTime <= 0)
        return 0;
    if (postActive())
        return 1;

    return beginAnimationUpdateTime() - m_startTime;
}
Ejemplo n.º 6
0
double AnimationBase::getElapsedTime() const
{
    ASSERT(!postActive());
    if (paused())
        return m_pauseTime - m_startTime;
    if (m_startTime <= 0)
        return 0;

    double elapsedTime = beginAnimationUpdateTime() - m_startTime;
    // It's possible for the start time to be ahead of the last update time
    // if the compositor has just sent notification for the start of an
    // accelerated animation.
    return max(elapsedTime, 0.0);
}
Ejemplo n.º 7
0
double AnimationBase::timeToNextService()
{
    // Returns the time at which next service is required. -1 means no service is required. 0 means 
    // service is required now, and > 0 means service is required that many seconds in the future.
    if (paused() || isNew() || m_animState == AnimationStateFillingForwards)
        return -1;
    
    if (m_animState == AnimationStateStartWaitTimer) {
        double timeFromNow = m_animation->delay() - (beginAnimationUpdateTime() - m_requestedStartTime);
        return max(timeFromNow, 0.0);
    }
    
    fireAnimationEventsIfNeeded();
        
    // In all other cases, we need service right away.
    return 0;
}
Ejemplo n.º 8
0
void AnimationBase::freezeAtTime(double t)
{
    if (!m_compAnim)
        return;

    if (!m_startTime) {
        // If we haven't started yet, make it as if we started.
        m_animState = AnimationStateStartWaitResponse;
        onAnimationStartResponse(beginAnimationUpdateTime());
    }

    ASSERT(m_startTime);        // if m_startTime is zero, we haven't started yet, so we'll get a bad pause time.
    if (t <= m_animation->delay())
        m_pauseTime = m_startTime;
    else
        m_pauseTime = m_startTime + t - m_animation->delay();

    if (m_object && m_object->isComposited())
        toRenderBoxModelObject(m_object)->suspendAnimations(m_pauseTime);
}
Ejemplo n.º 9
0
void AnimationBase::getTimeToNextEvent(double& time, bool& isLooping) const
{
    // Decide when the end or loop event needs to fire
    const double elapsedDuration = max(beginAnimationUpdateTime() - m_startTime, 0.0);
    double durationLeft = 0;
    double nextIterationTime = m_totalDuration;

    if (m_totalDuration < 0 || elapsedDuration < m_totalDuration) {
        durationLeft = m_animation->duration() > 0 ? (m_animation->duration() - fmod(elapsedDuration, m_animation->duration())) : 0;
        nextIterationTime = elapsedDuration + durationLeft;
    }
    
    if (m_totalDuration < 0 || nextIterationTime < m_totalDuration) {
        // We are not at the end yet
        ASSERT(nextIterationTime > 0);
        isLooping = true;
    } else {
        // We are at the end
        isLooping = false;
    }
    
    time = durationLeft;
}
Ejemplo n.º 10
0
void AnimationControllerPrivate::endAnimationUpdate()
{
    styleAvailable();
    if (!m_waitingForAsyncStartNotification)
        startTimeResponse(beginAnimationUpdateTime());
}
Ejemplo n.º 11
0
void AnimationBase::updateStateMachine(AnimStateInput input, double param)
{
    if (!m_compAnim)
        return;

    // If we get AnimationStateInputRestartAnimation then we force a new animation, regardless of state.
    if (input == AnimationStateInputMakeNew) {
        if (m_animState == AnimationStateStartWaitStyleAvailable)
            m_compAnim->animationController()->removeFromAnimationsWaitingForStyle(this);
        LOG(Animations, "%p AnimationState %s -> New", this, nameForState(m_animState));
        m_animState = AnimationStateNew;
        m_startTime = 0;
        m_pauseTime = -1;
        m_requestedStartTime = 0;
        m_nextIterationDuration = -1;
        endAnimation();
        return;
    }

    if (input == AnimationStateInputRestartAnimation) {
        if (m_animState == AnimationStateStartWaitStyleAvailable)
            m_compAnim->animationController()->removeFromAnimationsWaitingForStyle(this);
        LOG(Animations, "%p AnimationState %s -> New", this, nameForState(m_animState));
        m_animState = AnimationStateNew;
        m_startTime = 0;
        m_pauseTime = -1;
        m_requestedStartTime = 0;
        m_nextIterationDuration = -1;
        endAnimation();

        if (!paused())
            updateStateMachine(AnimationStateInputStartAnimation, -1);
        return;
    }

    if (input == AnimationStateInputEndAnimation) {
        if (m_animState == AnimationStateStartWaitStyleAvailable)
            m_compAnim->animationController()->removeFromAnimationsWaitingForStyle(this);
        LOG(Animations, "%p AnimationState %s -> Done", this, nameForState(m_animState));
        m_animState = AnimationStateDone;
        endAnimation();
        return;
    }

    if (input == AnimationStateInputPauseOverride) {
        if (m_animState == AnimationStateStartWaitResponse) {
            // If we are in AnimationStateStartWaitResponse, the animation will get canceled before 
            // we get a response, so move to the next state.
            endAnimation();
            updateStateMachine(AnimationStateInputStartTimeSet, beginAnimationUpdateTime());
        }
        return;
    }

    if (input == AnimationStateInputResumeOverride) {
        if (m_animState == AnimationStateLooping || m_animState == AnimationStateEnding) {
            // Start the animation
            startAnimation(beginAnimationUpdateTime() - m_startTime);
        }
        return;
    }

    // Execute state machine
    switch (m_animState) {
        case AnimationStateNew:
            ASSERT(input == AnimationStateInputStartAnimation || input == AnimationStateInputPlayStateRunning || input == AnimationStateInputPlayStatePaused);
            if (input == AnimationStateInputStartAnimation || input == AnimationStateInputPlayStateRunning) {
                m_requestedStartTime = beginAnimationUpdateTime();
                LOG(Animations, "%p AnimationState %s -> StartWaitTimer", this, nameForState(m_animState));
                m_animState = AnimationStateStartWaitTimer;
            } else {
                // We are pausing before we even started.
                LOG(Animations, "%p AnimationState %s -> AnimationStatePausedNew", this, nameForState(m_animState));
                m_animState = AnimationStatePausedNew;
            }
            break;
        case AnimationStateStartWaitTimer:
            ASSERT(input == AnimationStateInputStartTimerFired || input == AnimationStateInputPlayStatePaused);

            if (input == AnimationStateInputStartTimerFired) {
                ASSERT(param >= 0);
                // Start timer has fired, tell the animation to start and wait for it to respond with start time
                LOG(Animations, "%p AnimationState %s -> StartWaitStyleAvailable", this, nameForState(m_animState));
                m_animState = AnimationStateStartWaitStyleAvailable;
                m_compAnim->animationController()->addToAnimationsWaitingForStyle(this);

                // Trigger a render so we can start the animation
                if (m_object && m_object->element())
                    m_compAnim->animationController()->addElementChangeToDispatch(*m_object->element());
            } else {
                ASSERT(!paused());
                // We're waiting for the start timer to fire and we got a pause. Cancel the timer, pause and wait
                m_pauseTime = beginAnimationUpdateTime();
                LOG(Animations, "%p AnimationState %s -> PausedWaitTimer", this, nameForState(m_animState));
                m_animState = AnimationStatePausedWaitTimer;
            }
            break;
        case AnimationStateStartWaitStyleAvailable:
            ASSERT(input == AnimationStateInputStyleAvailable || input == AnimationStateInputPlayStatePaused);

            if (input == AnimationStateInputStyleAvailable) {
                // Start timer has fired, tell the animation to start and wait for it to respond with start time
                LOG(Animations, "%p AnimationState %s -> StartWaitResponse", this, nameForState(m_animState));
                m_animState = AnimationStateStartWaitResponse;

                overrideAnimations();

                // Start the animation
                if (overridden()) {
                    // We won't try to start accelerated animations if we are overridden and
                    // just move on to the next state.
                    LOG(Animations, "%p AnimationState %s -> StartWaitResponse", this, nameForState(m_animState));
                    m_animState = AnimationStateStartWaitResponse;
                    m_isAccelerated = false;
                    updateStateMachine(AnimationStateInputStartTimeSet, beginAnimationUpdateTime());
                } else {
                    double timeOffset = 0;
                    // If the value for 'animation-delay' is negative then the animation appears to have started in the past.
                    if (m_animation->delay() < 0)
                        timeOffset = -m_animation->delay();
                    bool started = startAnimation(timeOffset);

                    m_compAnim->animationController()->addToAnimationsWaitingForStartTimeResponse(this, started);
                    m_isAccelerated = started;
                }
            } else {
                // We're waiting for the style to be available and we got a pause. Pause and wait
                m_pauseTime = beginAnimationUpdateTime();
                LOG(Animations, "%p AnimationState %s -> PausedWaitStyleAvailable", this, nameForState(m_animState));
                m_animState = AnimationStatePausedWaitStyleAvailable;
            }
            break;
        case AnimationStateStartWaitResponse:
            ASSERT(input == AnimationStateInputStartTimeSet || input == AnimationStateInputPlayStatePaused);

            if (input == AnimationStateInputStartTimeSet) {
                ASSERT(param >= 0);
                // We have a start time, set it, unless the startTime is already set
                if (m_startTime <= 0) {
                    m_startTime = param;
                    // If the value for 'animation-delay' is negative then the animation appears to have started in the past.
                    if (m_animation->delay() < 0)
                        m_startTime += m_animation->delay();
                }

                // Now that we know the start time, fire the start event.
                onAnimationStart(0); // The elapsedTime is 0.

                // Decide whether to go into looping or ending state
                goIntoEndingOrLoopingState();

                // Dispatch updateStyleIfNeeded so we can start the animation
                if (m_object && m_object->element())
                    m_compAnim->animationController()->addElementChangeToDispatch(*m_object->element());
            } else {
                // We are pausing while waiting for a start response. Cancel the animation and wait. When 
                // we unpause, we will act as though the start timer just fired
                m_pauseTime = beginAnimationUpdateTime();
                pauseAnimation(beginAnimationUpdateTime() - m_startTime);
                LOG(Animations, "%p AnimationState %s -> PausedWaitResponse", this, nameForState(m_animState));
                m_animState = AnimationStatePausedWaitResponse;
            }
            break;
        case AnimationStateLooping:
            ASSERT(input == AnimationStateInputLoopTimerFired || input == AnimationStateInputPlayStatePaused);

            if (input == AnimationStateInputLoopTimerFired) {
                ASSERT(param >= 0);
                // Loop timer fired, loop again or end.
                onAnimationIteration(param);

                // Decide whether to go into looping or ending state
                goIntoEndingOrLoopingState();
            } else {
                // We are pausing while running. Cancel the animation and wait
                m_pauseTime = beginAnimationUpdateTime();
                pauseAnimation(beginAnimationUpdateTime() - m_startTime);
                LOG(Animations, "%p AnimationState %s -> PausedRun", this, nameForState(m_animState));
                m_animState = AnimationStatePausedRun;
            }
            break;
        case AnimationStateEnding:
#if !LOG_DISABLED
            if (input != AnimationStateInputEndTimerFired && input != AnimationStateInputPlayStatePaused)
                LOG_ERROR("State is AnimationStateEnding, but input is not AnimationStateInputEndTimerFired or AnimationStateInputPlayStatePaused. It is %d.", input);
#endif
            if (input == AnimationStateInputEndTimerFired) {

                ASSERT(param >= 0);
                // End timer fired, finish up
                onAnimationEnd(param);

                LOG(Animations, "%p AnimationState %s -> Done", this, nameForState(m_animState));
                m_animState = AnimationStateDone;
                
                if (m_object) {
                    if (m_animation->fillsForwards()) {
                        LOG(Animations, "%p AnimationState %s -> FillingForwards", this, nameForState(m_animState));
                        m_animState = AnimationStateFillingForwards;
                    } else
                        resumeOverriddenAnimations();

                    // Fire off another style change so we can set the final value
                    if (m_object->element())
                        m_compAnim->animationController()->addElementChangeToDispatch(*m_object->element());
                }
            } else {
                // We are pausing while running. Cancel the animation and wait
                m_pauseTime = beginAnimationUpdateTime();
                pauseAnimation(beginAnimationUpdateTime() - m_startTime);
                LOG(Animations, "%p AnimationState %s -> PausedRun", this, nameForState(m_animState));
                m_animState = AnimationStatePausedRun;
            }
            // |this| may be deleted here
            break;
        case AnimationStatePausedWaitTimer:
            ASSERT(input == AnimationStateInputPlayStateRunning);
            ASSERT(paused());
            // Update the times
            m_startTime += beginAnimationUpdateTime() - m_pauseTime;
            m_pauseTime = -1;

            // we were waiting for the start timer to fire, go back and wait again
            LOG(Animations, "%p AnimationState %s -> New", this, nameForState(m_animState));
            m_animState = AnimationStateNew;
            updateStateMachine(AnimationStateInputStartAnimation, 0);
            break;
        case AnimationStatePausedNew:
        case AnimationStatePausedWaitResponse:
        case AnimationStatePausedWaitStyleAvailable:
        case AnimationStatePausedRun:
            // We treat these two cases the same. The only difference is that, when we are in
            // AnimationStatePausedWaitResponse, we don't yet have a valid startTime, so we send 0 to startAnimation.
            // When the AnimationStateInputStartTimeSet comes in and we were in AnimationStatePausedRun, we will notice
            // that we have already set the startTime and will ignore it.
            ASSERT(input == AnimationStateInputPlayStateRunning || input == AnimationStateInputStartTimeSet || input == AnimationStateInputStyleAvailable || input == AnimationStateInputStartAnimation);
            ASSERT(paused());

            if (input == AnimationStateInputPlayStateRunning) {
                if (m_animState == AnimationStatePausedNew) {
                    // We were paused before we even started, and now we're supposed
                    // to start, so jump back to the New state and reset.
                    LOG(Animations, "%p AnimationState %s -> AnimationStateNew", this, nameForState(m_animState));
                    m_animState = AnimationStateNew;
                    updateStateMachine(input, param);
                    break;
                }

                // Update the times
                if (m_animState == AnimationStatePausedRun)
                    m_startTime += beginAnimationUpdateTime() - m_pauseTime;
                else
                    m_startTime = 0;
                m_pauseTime = -1;

                if (m_animState == AnimationStatePausedWaitStyleAvailable) {
                    LOG(Animations, "%p AnimationState %s -> StartWaitStyleAvailable", this, nameForState(m_animState));
                    m_animState = AnimationStateStartWaitStyleAvailable;
                } else {
                    // We were either running or waiting for a begin time response from the animation.
                    // Either way we need to restart the animation (possibly with an offset if we
                    // had already been running) and wait for it to start.
                    LOG(Animations, "%p AnimationState %s -> StartWaitResponse", this, nameForState(m_animState));
                    m_animState = AnimationStateStartWaitResponse;

                    // Start the animation
                    if (overridden()) {
                        // We won't try to start accelerated animations if we are overridden and
                        // just move on to the next state.
                        updateStateMachine(AnimationStateInputStartTimeSet, beginAnimationUpdateTime());
                        m_isAccelerated = true;
                    } else {
                        bool started = startAnimation(beginAnimationUpdateTime() - m_startTime);
                        m_compAnim->animationController()->addToAnimationsWaitingForStartTimeResponse(this, started);
                        m_isAccelerated = started;
                    }
                }
                break;
            }
            
            if (input == AnimationStateInputStartTimeSet) {
                ASSERT(m_animState == AnimationStatePausedWaitResponse);
                
                // We are paused but we got the callback that notifies us that an accelerated animation started.
                // We ignore the start time and just move into the paused-run state.
                LOG(Animations, "%p AnimationState %s -> PausedRun", this, nameForState(m_animState));
                m_animState = AnimationStatePausedRun;
                ASSERT(m_startTime == 0);
                m_startTime = param;
                m_pauseTime += m_startTime;
                break;
            }

            ASSERT(m_animState == AnimationStatePausedWaitStyleAvailable);
            // We are paused but we got the callback that notifies us that style has been updated.
            // We move to the AnimationStatePausedWaitResponse state
            LOG(Animations, "%p AnimationState %s -> PausedWaitResponse", this, nameForState(m_animState));
            m_animState = AnimationStatePausedWaitResponse;
            overrideAnimations();
            break;
        case AnimationStateFillingForwards:
        case AnimationStateDone:
            // We're done. Stay in this state until we are deleted
            break;
    }
}
Ejemplo n.º 12
0
void AnimationBase::fireAnimationEventsIfNeeded()
{
    if (!m_compositeAnimation)
        return;

    // If we are waiting for the delay time to expire and it has, go to the next state
    if (m_animationState != AnimationState::StartWaitTimer && m_animationState != AnimationState::Looping && m_animationState != AnimationState::Ending)
        return;

    // We have to make sure to keep a ref to the this pointer, because it could get destroyed
    // during an animation callback that might get called. Since the owner is a CompositeAnimation
    // and it ref counts this object, we will keep a ref to that instead. That way the AnimationBase
    // can still access the resources of its CompositeAnimation as needed.
    Ref<AnimationBase> protect(*this);
    Ref<CompositeAnimation> protectCompositeAnimation(*m_compositeAnimation);
    
    // Check for start timeout
    if (m_animationState == AnimationState::StartWaitTimer) {
#if ENABLE(CSS_ANIMATIONS_LEVEL_2)
        if (m_animation->trigger() && m_animation->trigger()->isScrollAnimationTrigger()) {
            if (m_object) {
                float offset = m_compositeAnimation->animationController().scrollPosition();
                ScrollAnimationTrigger& scrollTrigger = downcast<ScrollAnimationTrigger>(*m_animation->trigger().get());
                if (offset > scrollTrigger.startValue().value())
                    updateStateMachine(AnimationStateInput::StartTimerFired, 0);
            }

            return;
        }
#endif
        if (beginAnimationUpdateTime() - m_requestedStartTime >= m_animation->delay())
            updateStateMachine(AnimationStateInput::StartTimerFired, 0);
        return;
    }

    double elapsedDuration = beginAnimationUpdateTime() - m_startTime;
#if ENABLE(CSS_ANIMATIONS_LEVEL_2)
    // If we are a triggered animation that depends on scroll, our elapsed
    // time is determined by the scroll position.
    if (m_animation->trigger() && m_animation->trigger()->isScrollAnimationTrigger())
        elapsedDuration = getElapsedTime();
#endif

    // FIXME: we need to ensure that elapsedDuration is never < 0. If it is, this suggests that
    // we had a recalcStyle() outside of beginAnimationUpdate()/endAnimationUpdate().
    // Also check in getTimeToNextEvent().
    elapsedDuration = std::max(elapsedDuration, 0.0);
    
    // Check for end timeout
    if (m_totalDuration >= 0 && elapsedDuration >= m_totalDuration) {
        // We may still be in AnimationState::Looping if we've managed to skip a
        // whole iteration, in which case we should jump to the end state.
        LOG(Animations, "%p AnimationState %s -> Ending", this, nameForState(m_animationState));
        m_animationState = AnimationState::Ending;

        // Fire an end event
        updateStateMachine(AnimationStateInput::EndTimerFired, m_totalDuration);
    } else {
        // Check for iteration timeout
        if (m_nextIterationDuration < 0) {
            // Hasn't been set yet, set it
            double durationLeft = m_animation->duration() - fmod(elapsedDuration, m_animation->duration());
            m_nextIterationDuration = elapsedDuration + durationLeft;
        }
        
        if (elapsedDuration >= m_nextIterationDuration) {
            // Set to the next iteration
            double previous = m_nextIterationDuration;
            double durationLeft = m_animation->duration() - fmod(elapsedDuration, m_animation->duration());
            m_nextIterationDuration = elapsedDuration + durationLeft;
            
            // Send the event
            updateStateMachine(AnimationStateInput::LoopTimerFired, previous);
        }
    }
}