Implement Animation PlayAfter() API
[platform/core/uifw/dali-core.git] / dali / internal / update / animation / scene-graph-animation.cpp
index 29093b9..9ce8ef0 100644 (file)
@@ -1,18 +1,19 @@
-//
-// Copyright (c) 2014 Samsung Electronics Co., Ltd.
-//
-// Licensed under the Flora License, Version 1.0 (the License);
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://floralicense.org/license/
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an AS IS BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-//
+/*
+ * Copyright (c) 2017 Samsung Electronics Co., Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
 
 // CLASS HEADER
 #include <dali/internal/update/animation/scene-graph-animation.h>
 #include <cmath> // fmod
 
 // INTERNAL INCLUDES
+#include <dali/internal/common/memory-pool-object-allocator.h>
 #include <dali/internal/render/common/performance-monitor.h>
+#include <dali/public-api/math/math-utils.h>
+namespace //Unnamed namespace
+{
+//Memory pool used to allocate new animations. Memory used by this pool will be released when shutting down DALi
+Dali::Internal::MemoryPoolObjectAllocator<Dali::Internal::SceneGraph::Animation> gAnimationMemoryPool;
+
+inline void WrapInPlayRange( float& elapsed, const Dali::Vector2& playRangeSeconds)
+{
+  if( elapsed > playRangeSeconds.y )
+  {
+    elapsed = playRangeSeconds.x + fmodf((elapsed-playRangeSeconds.x), (playRangeSeconds.y-playRangeSeconds.x));
+  }
+  else if( elapsed < playRangeSeconds.x )
+  {
+    elapsed = playRangeSeconds.y - fmodf( (playRangeSeconds.x - elapsed), (playRangeSeconds.y-playRangeSeconds.x) );
+  }
+}
 
-using namespace std;
+/// Compares the end times of the animators and if the end time is less, then it is moved earlier in the list. If end times are the same, then no change.
+bool CompareAnimatorEndTimes( const Dali::Internal::SceneGraph::AnimatorBase* lhs, const Dali::Internal::SceneGraph::AnimatorBase* rhs )
+{
+  return ( ( lhs->GetIntervalDelay() + lhs->GetDuration() ) < ( rhs->GetIntervalDelay() + rhs->GetDuration() ) );
+}
+
+} // unnamed namespace
 
 namespace Dali
 {
@@ -34,19 +59,25 @@ namespace Internal
 namespace SceneGraph
 {
 
-float DefaultAlphaFunc(float progress)
+Animation* Animation::New( float durationSeconds, float speedFactor, const Vector2& playRange, int loopCount, EndAction endAction, EndAction disconnectAction )
 {
-  return progress; // linear
+  return new ( gAnimationMemoryPool.AllocateRawThreadSafe() ) Animation( durationSeconds, speedFactor, playRange, loopCount, endAction, disconnectAction );
 }
 
-Animation::Animation(float durationSeconds, bool isLooping, Dali::Animation::EndAction endAction, Dali::Animation::EndAction destroyAction)
-: mDurationSeconds(durationSeconds),
-  mLooping(isLooping),
+Animation::Animation( float durationSeconds, float speedFactor, const Vector2& playRange, int loopCount, Dali::Animation::EndAction endAction, Dali::Animation::EndAction disconnectAction )
+: mPlayRange( playRange ),
+  mDurationSeconds( durationSeconds ),
+  mDelaySeconds( 0.0f ),
+  mElapsedSeconds( playRange.x*mDurationSeconds ),
+  mSpeedFactor( speedFactor ),
+  mProgressMarker( 0.0f ),
+  mPlayedCount( 0 ),
+  mLoopCount(loopCount),
+  mCurrentLoop(0),
   mEndAction(endAction),
-  mDestroyAction(destroyAction),
+  mDisconnectAction(disconnectAction),
   mState(Stopped),
-  mElapsedSeconds(0.0f),
-  mPlayCount(0)
+  mProgressReachedSignalRequired( false )
 {
 }
 
@@ -54,16 +85,29 @@ Animation::~Animation()
 {
 }
 
-void Animation::SetDuration(float durationSeconds)
+void Animation::operator delete( void* ptr )
 {
-  DALI_ASSERT_DEBUG(durationSeconds > 0.0f);
+  gAnimationMemoryPool.FreeThreadSafe( static_cast<Animation*>( ptr ) );
+}
 
+void Animation::SetDuration(float durationSeconds)
+{
   mDurationSeconds = durationSeconds;
 }
 
-void Animation::SetLooping(bool looping)
+void Animation::SetProgressNotification( float progress )
+{
+  mProgressMarker = progress;
+  if ( mProgressMarker > 0.0f )
+  {
+    mProgressReachedSignalRequired = true;
+  }
+}
+
+void Animation::SetLoopCount(int loopCount)
 {
-  mLooping = looping;
+  mLoopCount = loopCount;
+  mCurrentLoop = 0;
 }
 
 void Animation::SetEndAction(Dali::Animation::EndAction action)
@@ -71,14 +115,85 @@ void Animation::SetEndAction(Dali::Animation::EndAction action)
   mEndAction = action;
 }
 
-void Animation::SetDestroyAction(Dali::Animation::EndAction action)
+void Animation::SetDisconnectAction(Dali::Animation::EndAction action)
 {
-  mDestroyAction = action;
+  if ( mDisconnectAction != action )
+  {
+    mDisconnectAction = action;
+
+    for ( AnimatorIter iter = mAnimators.Begin(), endIter = mAnimators.End(); iter != endIter; ++iter )
+    {
+      (*iter)->SetDisconnectAction( action );
+    }
+  }
+}
+
+void Animation::SetPlayRange( const Vector2& range )
+{
+  mPlayRange = range;
+
+  // Make sure mElapsedSeconds is within the new range
+
+  if( mState == Stopped )
+  {
+    // Ensure that the animation starts at the right place
+    mElapsedSeconds = mPlayRange.x * mDurationSeconds;
+  }
+  else
+  {
+    // If already past the end of the range, but before end of duration, then clamp will
+    // ensure that the animation stops on the next update.
+    // If not yet at the start of the range, clamping will jump to the start
+    mElapsedSeconds = Dali::Clamp(mElapsedSeconds, mPlayRange.x*mDurationSeconds , mPlayRange.y*mDurationSeconds );
+  }
 }
 
 void Animation::Play()
 {
+  // Sort according to end time with earlier end times coming first, if the end time is the same, then the animators are not moved
+  std::stable_sort( mAnimators.Begin(), mAnimators.End(), CompareAnimatorEndTimes );
+
   mState = Playing;
+
+  if ( mSpeedFactor < 0.0f && mElapsedSeconds <= mPlayRange.x*mDurationSeconds )
+  {
+    mElapsedSeconds = mPlayRange.y * mDurationSeconds;
+  }
+
+  SetAnimatorsActive( true );
+
+  mCurrentLoop = 0;
+}
+
+void Animation::PlayFrom( float progress )
+{
+  // If the animation is already playing this has no effect
+  // Progress is guaranteed to be in range.
+  if( mState != Playing )
+  {
+    mElapsedSeconds = progress * mDurationSeconds;
+    mState = Playing;
+
+    SetAnimatorsActive( true );
+  }
+}
+
+void Animation::PlayAfter( float delaySeconds )
+{
+  if( mState != Playing )
+  {
+    mDelaySeconds = delaySeconds;
+    mState = Playing;
+
+    if ( mSpeedFactor < 0.0f && mElapsedSeconds <= mPlayRange.x*mDurationSeconds )
+    {
+      mElapsedSeconds = mPlayRange.y * mDurationSeconds;
+    }
+
+    SetAnimatorsActive( true );
+
+    mCurrentLoop = 0;
+  }
 }
 
 void Animation::Pause()
@@ -89,6 +204,31 @@ void Animation::Pause()
   }
 }
 
+void Animation::Bake(BufferIndex bufferIndex, EndAction action)
+{
+  if( action == Dali::Animation::BakeFinal )
+  {
+    if( mSpeedFactor > 0.0f )
+    {
+      mElapsedSeconds = mPlayRange.y*mDurationSeconds + Math::MACHINE_EPSILON_1; // Force animation to reach it's end
+    }
+    else
+    {
+      mElapsedSeconds = mPlayRange.x*mDurationSeconds - Math::MACHINE_EPSILON_1; //Force animation to reach it's beginning
+    }
+  }
+
+  UpdateAnimators( bufferIndex, true/*bake the final result*/, true /*animation finished*/ );
+}
+
+void Animation::SetAnimatorsActive( bool active )
+{
+  for ( AnimatorIter iter = mAnimators.Begin(), endIter = mAnimators.End(); iter != endIter; ++iter )
+  {
+    (*iter)->SetActive( active );
+  }
+}
+
 bool Animation::Stop(BufferIndex bufferIndex)
 {
   bool animationFinished(false);
@@ -97,16 +237,23 @@ bool Animation::Stop(BufferIndex bufferIndex)
   {
     animationFinished = true; // The actor-thread should be notified of this
 
-    if (mEndAction == Dali::Animation::Bake)
+    if( mEndAction != Dali::Animation::Discard )
+    {
+      Bake( bufferIndex, mEndAction );
+
+      // Animators are automatically set to inactive in Bake
+    }
+    else
     {
-      UpdateAnimators(bufferIndex, true/*bake the final result*/);
+      SetAnimatorsActive( false );
     }
 
     // The animation has now been played to completion
-    ++mPlayCount;
+    ++mPlayedCount;
+    mCurrentLoop = 0;
   }
 
-  mElapsedSeconds = 0.0f;
+  mElapsedSeconds = mPlayRange.x*mDurationSeconds;
   mState = Stopped;
 
   return animationFinished;
@@ -116,95 +263,173 @@ void Animation::OnDestroy(BufferIndex bufferIndex)
 {
   if (mState == Playing || mState == Paused)
   {
-    if (mDestroyAction == Dali::Animation::Bake)
+    if (mEndAction != Dali::Animation::Discard)
     {
-      UpdateAnimators(bufferIndex, true/*bake the final result*/);
+      Bake( bufferIndex, mEndAction );
+
+      // Animators are automatically set to inactive in Bake
+    }
+    else
+    {
+      SetAnimatorsActive( false );
     }
   }
 
   mState = Destroyed;
 }
 
-void Animation::AddAnimator( AnimatorBase* animator, PropertyOwner* propertyOwner )
+void Animation::AddAnimator( OwnerPointer<AnimatorBase>& animator )
 {
-  animator->Attach( propertyOwner );
+  animator->ConnectToSceneGraph();
+  animator->SetDisconnectAction( mDisconnectAction );
 
-  mAnimators.PushBack( animator );
+  mAnimators.PushBack( animator.Release() );
 }
 
-bool Animation::Update(BufferIndex bufferIndex, float elapsedSeconds)
+void Animation::Update(BufferIndex bufferIndex, float elapsedSeconds, bool& looped, bool& finished, bool& progressReached )
 {
+  looped = false;
+  finished = false;
+
   if (mState == Stopped || mState == Destroyed)
   {
     // Short circuit when animation isn't running
-    return false;
+    return;
   }
 
   // The animation must still be applied when Paused/Stopping
   if (mState == Playing)
   {
-    mElapsedSeconds += elapsedSeconds;
-  }
-
-  if (mLooping)
-  {
-    if (mElapsedSeconds > mDurationSeconds)
+    // If there is delay time before Animation starts, wait the Animation until mDelaySeconds.
+    if( mDelaySeconds > 0)
     {
-      mElapsedSeconds = fmod(mElapsedSeconds, mDurationSeconds);
+      mDelaySeconds = mDelaySeconds - ( elapsedSeconds * mSpeedFactor );
     }
-  }
+    else
+    {
+      mElapsedSeconds += elapsedSeconds * mSpeedFactor;
 
-  const bool animationFinished(mState == Playing && mElapsedSeconds > mDurationSeconds);
+      if ( mProgressReachedSignalRequired && ( mElapsedSeconds >= mProgressMarker ) )
+      {
+        // The application should be notified by NotificationManager, in another thread
+        progressReached = true;
+        mProgressReachedSignalRequired = false;
+      }
+    }
+  }
 
-  UpdateAnimators(bufferIndex, animationFinished && (mEndAction == Dali::Animation::Bake));
+  Vector2 playRangeSeconds = mPlayRange * mDurationSeconds;
 
-  if (animationFinished)
+  if( 0 == mLoopCount )
   {
-    // The animation has now been played to completion
-    ++mPlayCount;
+    // loop forever
+    WrapInPlayRange(mElapsedSeconds, playRangeSeconds);
+
+    UpdateAnimators(bufferIndex, false, false );
 
-    mElapsedSeconds = 0.0f;
-    mState = Stopped;
+    // don't increment mPlayedCount as event loop tracks this to indicate animation finished (end of all loops)
   }
+  else if( mCurrentLoop < mLoopCount - 1) // '-1' here so last loop iteration uses play once below
+  {
+    // looping
+    looped =  (mState == Playing                                                 &&
+               (( mSpeedFactor > 0.0f && mElapsedSeconds > playRangeSeconds.y )  ||
+                ( mSpeedFactor < 0.0f && mElapsedSeconds < playRangeSeconds.x )) );
 
-  return animationFinished;
-}
+    WrapInPlayRange( mElapsedSeconds, playRangeSeconds );
 
-void Animation::UpdateAnimators(BufferIndex bufferIndex, bool bake)
-{
-  for ( AnimatorIter iter = mAnimators.Begin(); iter != mAnimators.End(); )
+    UpdateAnimators(bufferIndex, false, false );
+
+    if(looped)
+    {
+      ++mCurrentLoop;
+      mProgressReachedSignalRequired = mProgressMarker > 0.0f;
+      // don't increment mPlayedCount until the finished final loop
+    }
+  }
+  else
   {
-    // If an animator is not successfully applied, then it has been orphaned
-    bool applied(true);
+    // playing once (and last mCurrentLoop)
+    finished = (mState == Playing                                                 &&
+                (( mSpeedFactor > 0.0f && mElapsedSeconds > playRangeSeconds.y )  ||
+                 ( mSpeedFactor < 0.0f && mElapsedSeconds < playRangeSeconds.x )) );
 
-    AnimatorBase *animator = *iter;
-    const float initialDelay(animator->GetInitialDelay());
+    // update with bake if finished
+    UpdateAnimators(bufferIndex, finished && (mEndAction != Dali::Animation::Discard), finished );
 
-    if (mElapsedSeconds >= initialDelay)
+    if(finished)
     {
-      // Calculate a progress specific to each individual animator
-      float progress(1.0f);
-      const float animatorDuration = animator->GetDuration();
-      if (animatorDuration > 0.0f) // animators can be "immediate"
+      // The animation has now been played to completion
+      ++mPlayedCount;
+
+      // loop iterations come to this else branch for their final iterations
+      if( mCurrentLoop < mLoopCount)
       {
-        progress = min(1.0f, (mElapsedSeconds - initialDelay) / animatorDuration);
+        ++mCurrentLoop;
+        DALI_ASSERT_DEBUG(mCurrentLoop == mLoopCount);
       }
 
-      applied = animator->Update(bufferIndex, progress, bake);
+      mProgressReachedSignalRequired = mProgressMarker > 0.0f;
+      mElapsedSeconds = playRangeSeconds.x;
+      mState = Stopped;
     }
+  }
+}
 
-    // Animators are automatically removed, when orphaned from animatable scene objects.
-    if (!applied)
+void Animation::UpdateAnimators( BufferIndex bufferIndex, bool bake, bool animationFinished )
+{
+  const Vector2 playRange( mPlayRange * mDurationSeconds );
+  float elapsedSecondsClamped = Clamp( mElapsedSeconds, playRange.x, playRange.y );
+
+  //Loop through all animators
+  bool applied(true);
+  for ( AnimatorIter iter = mAnimators.Begin(); iter != mAnimators.End(); )
+  {
+    AnimatorBase *animator = *iter;
+
+    if( animator->Orphan() )
     {
+      //Remove animators whose PropertyOwner has been destroyed
       iter = mAnimators.Erase(iter);
     }
     else
     {
-      ++iter;
+      if( animator->IsEnabled() )
+      {
+        const float intervalDelay( animator->GetIntervalDelay() );
+
+        if( elapsedSecondsClamped >= intervalDelay )
+        {
+          // Calculate a progress specific to each individual animator
+          float progress(1.0f);
+          const float animatorDuration = animator->GetDuration();
+          if (animatorDuration > 0.0f) // animators can be "immediate"
+          {
+            progress = Clamp((elapsedSecondsClamped - intervalDelay) / animatorDuration, 0.0f , 1.0f );
+          }
+          animator->Update(bufferIndex, progress, bake);
+        }
+        applied = true;
+      }
+      else
+      {
+        applied = false;
+      }
 
-      INCREASE_COUNTER(PerformanceMonitor::ANIMATORS_APPLIED);
+      if ( animationFinished )
+      {
+        animator->SetActive( false );
+      }
+
+      if (applied)
+      {
+        INCREASE_COUNTER(PerformanceMonitor::ANIMATORS_APPLIED);
+      }
+
+      ++iter;
     }
   }
+
 }
 
 } // namespace SceneGraph