Implement Animation PlayAfter() API
[platform/core/uifw/dali-core.git] / dali / internal / update / animation / scene-graph-animation.cpp
1 /*
2  * Copyright (c) 2017 Samsung Electronics Co., Ltd.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  *
16  */
17
18 // CLASS HEADER
19 #include <dali/internal/update/animation/scene-graph-animation.h>
20
21 // EXTERNAL INCLUDES
22 #include <cmath> // fmod
23
24 // INTERNAL INCLUDES
25 #include <dali/internal/common/memory-pool-object-allocator.h>
26 #include <dali/internal/render/common/performance-monitor.h>
27 #include <dali/public-api/math/math-utils.h>
28 namespace //Unnamed namespace
29 {
30 //Memory pool used to allocate new animations. Memory used by this pool will be released when shutting down DALi
31 Dali::Internal::MemoryPoolObjectAllocator<Dali::Internal::SceneGraph::Animation> gAnimationMemoryPool;
32
33 inline void WrapInPlayRange( float& elapsed, const Dali::Vector2& playRangeSeconds)
34 {
35   if( elapsed > playRangeSeconds.y )
36   {
37     elapsed = playRangeSeconds.x + fmodf((elapsed-playRangeSeconds.x), (playRangeSeconds.y-playRangeSeconds.x));
38   }
39   else if( elapsed < playRangeSeconds.x )
40   {
41     elapsed = playRangeSeconds.y - fmodf( (playRangeSeconds.x - elapsed), (playRangeSeconds.y-playRangeSeconds.x) );
42   }
43 }
44
45 /// 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.
46 bool CompareAnimatorEndTimes( const Dali::Internal::SceneGraph::AnimatorBase* lhs, const Dali::Internal::SceneGraph::AnimatorBase* rhs )
47 {
48   return ( ( lhs->GetIntervalDelay() + lhs->GetDuration() ) < ( rhs->GetIntervalDelay() + rhs->GetDuration() ) );
49 }
50
51 } // unnamed namespace
52
53 namespace Dali
54 {
55
56 namespace Internal
57 {
58
59 namespace SceneGraph
60 {
61
62 Animation* Animation::New( float durationSeconds, float speedFactor, const Vector2& playRange, int loopCount, EndAction endAction, EndAction disconnectAction )
63 {
64   return new ( gAnimationMemoryPool.AllocateRawThreadSafe() ) Animation( durationSeconds, speedFactor, playRange, loopCount, endAction, disconnectAction );
65 }
66
67 Animation::Animation( float durationSeconds, float speedFactor, const Vector2& playRange, int loopCount, Dali::Animation::EndAction endAction, Dali::Animation::EndAction disconnectAction )
68 : mPlayRange( playRange ),
69   mDurationSeconds( durationSeconds ),
70   mDelaySeconds( 0.0f ),
71   mElapsedSeconds( playRange.x*mDurationSeconds ),
72   mSpeedFactor( speedFactor ),
73   mProgressMarker( 0.0f ),
74   mPlayedCount( 0 ),
75   mLoopCount(loopCount),
76   mCurrentLoop(0),
77   mEndAction(endAction),
78   mDisconnectAction(disconnectAction),
79   mState(Stopped),
80   mProgressReachedSignalRequired( false )
81 {
82 }
83
84 Animation::~Animation()
85 {
86 }
87
88 void Animation::operator delete( void* ptr )
89 {
90   gAnimationMemoryPool.FreeThreadSafe( static_cast<Animation*>( ptr ) );
91 }
92
93 void Animation::SetDuration(float durationSeconds)
94 {
95   mDurationSeconds = durationSeconds;
96 }
97
98 void Animation::SetProgressNotification( float progress )
99 {
100   mProgressMarker = progress;
101   if ( mProgressMarker > 0.0f )
102   {
103     mProgressReachedSignalRequired = true;
104   }
105 }
106
107 void Animation::SetLoopCount(int loopCount)
108 {
109   mLoopCount = loopCount;
110   mCurrentLoop = 0;
111 }
112
113 void Animation::SetEndAction(Dali::Animation::EndAction action)
114 {
115   mEndAction = action;
116 }
117
118 void Animation::SetDisconnectAction(Dali::Animation::EndAction action)
119 {
120   if ( mDisconnectAction != action )
121   {
122     mDisconnectAction = action;
123
124     for ( AnimatorIter iter = mAnimators.Begin(), endIter = mAnimators.End(); iter != endIter; ++iter )
125     {
126       (*iter)->SetDisconnectAction( action );
127     }
128   }
129 }
130
131 void Animation::SetPlayRange( const Vector2& range )
132 {
133   mPlayRange = range;
134
135   // Make sure mElapsedSeconds is within the new range
136
137   if( mState == Stopped )
138   {
139     // Ensure that the animation starts at the right place
140     mElapsedSeconds = mPlayRange.x * mDurationSeconds;
141   }
142   else
143   {
144     // If already past the end of the range, but before end of duration, then clamp will
145     // ensure that the animation stops on the next update.
146     // If not yet at the start of the range, clamping will jump to the start
147     mElapsedSeconds = Dali::Clamp(mElapsedSeconds, mPlayRange.x*mDurationSeconds , mPlayRange.y*mDurationSeconds );
148   }
149 }
150
151 void Animation::Play()
152 {
153   // Sort according to end time with earlier end times coming first, if the end time is the same, then the animators are not moved
154   std::stable_sort( mAnimators.Begin(), mAnimators.End(), CompareAnimatorEndTimes );
155
156   mState = Playing;
157
158   if ( mSpeedFactor < 0.0f && mElapsedSeconds <= mPlayRange.x*mDurationSeconds )
159   {
160     mElapsedSeconds = mPlayRange.y * mDurationSeconds;
161   }
162
163   SetAnimatorsActive( true );
164
165   mCurrentLoop = 0;
166 }
167
168 void Animation::PlayFrom( float progress )
169 {
170   // If the animation is already playing this has no effect
171   // Progress is guaranteed to be in range.
172   if( mState != Playing )
173   {
174     mElapsedSeconds = progress * mDurationSeconds;
175     mState = Playing;
176
177     SetAnimatorsActive( true );
178   }
179 }
180
181 void Animation::PlayAfter( float delaySeconds )
182 {
183   if( mState != Playing )
184   {
185     mDelaySeconds = delaySeconds;
186     mState = Playing;
187
188     if ( mSpeedFactor < 0.0f && mElapsedSeconds <= mPlayRange.x*mDurationSeconds )
189     {
190       mElapsedSeconds = mPlayRange.y * mDurationSeconds;
191     }
192
193     SetAnimatorsActive( true );
194
195     mCurrentLoop = 0;
196   }
197 }
198
199 void Animation::Pause()
200 {
201   if (mState == Playing)
202   {
203     mState = Paused;
204   }
205 }
206
207 void Animation::Bake(BufferIndex bufferIndex, EndAction action)
208 {
209   if( action == Dali::Animation::BakeFinal )
210   {
211     if( mSpeedFactor > 0.0f )
212     {
213       mElapsedSeconds = mPlayRange.y*mDurationSeconds + Math::MACHINE_EPSILON_1; // Force animation to reach it's end
214     }
215     else
216     {
217       mElapsedSeconds = mPlayRange.x*mDurationSeconds - Math::MACHINE_EPSILON_1; //Force animation to reach it's beginning
218     }
219   }
220
221   UpdateAnimators( bufferIndex, true/*bake the final result*/, true /*animation finished*/ );
222 }
223
224 void Animation::SetAnimatorsActive( bool active )
225 {
226   for ( AnimatorIter iter = mAnimators.Begin(), endIter = mAnimators.End(); iter != endIter; ++iter )
227   {
228     (*iter)->SetActive( active );
229   }
230 }
231
232 bool Animation::Stop(BufferIndex bufferIndex)
233 {
234   bool animationFinished(false);
235
236   if (mState == Playing || mState == Paused)
237   {
238     animationFinished = true; // The actor-thread should be notified of this
239
240     if( mEndAction != Dali::Animation::Discard )
241     {
242       Bake( bufferIndex, mEndAction );
243
244       // Animators are automatically set to inactive in Bake
245     }
246     else
247     {
248       SetAnimatorsActive( false );
249     }
250
251     // The animation has now been played to completion
252     ++mPlayedCount;
253     mCurrentLoop = 0;
254   }
255
256   mElapsedSeconds = mPlayRange.x*mDurationSeconds;
257   mState = Stopped;
258
259   return animationFinished;
260 }
261
262 void Animation::OnDestroy(BufferIndex bufferIndex)
263 {
264   if (mState == Playing || mState == Paused)
265   {
266     if (mEndAction != Dali::Animation::Discard)
267     {
268       Bake( bufferIndex, mEndAction );
269
270       // Animators are automatically set to inactive in Bake
271     }
272     else
273     {
274       SetAnimatorsActive( false );
275     }
276   }
277
278   mState = Destroyed;
279 }
280
281 void Animation::AddAnimator( OwnerPointer<AnimatorBase>& animator )
282 {
283   animator->ConnectToSceneGraph();
284   animator->SetDisconnectAction( mDisconnectAction );
285
286   mAnimators.PushBack( animator.Release() );
287 }
288
289 void Animation::Update(BufferIndex bufferIndex, float elapsedSeconds, bool& looped, bool& finished, bool& progressReached )
290 {
291   looped = false;
292   finished = false;
293
294   if (mState == Stopped || mState == Destroyed)
295   {
296     // Short circuit when animation isn't running
297     return;
298   }
299
300   // The animation must still be applied when Paused/Stopping
301   if (mState == Playing)
302   {
303     // If there is delay time before Animation starts, wait the Animation until mDelaySeconds.
304     if( mDelaySeconds > 0)
305     {
306       mDelaySeconds = mDelaySeconds - ( elapsedSeconds * mSpeedFactor );
307     }
308     else
309     {
310       mElapsedSeconds += elapsedSeconds * mSpeedFactor;
311
312       if ( mProgressReachedSignalRequired && ( mElapsedSeconds >= mProgressMarker ) )
313       {
314         // The application should be notified by NotificationManager, in another thread
315         progressReached = true;
316         mProgressReachedSignalRequired = false;
317       }
318     }
319   }
320
321   Vector2 playRangeSeconds = mPlayRange * mDurationSeconds;
322
323   if( 0 == mLoopCount )
324   {
325     // loop forever
326     WrapInPlayRange(mElapsedSeconds, playRangeSeconds);
327
328     UpdateAnimators(bufferIndex, false, false );
329
330     // don't increment mPlayedCount as event loop tracks this to indicate animation finished (end of all loops)
331   }
332   else if( mCurrentLoop < mLoopCount - 1) // '-1' here so last loop iteration uses play once below
333   {
334     // looping
335     looped =  (mState == Playing                                                 &&
336                (( mSpeedFactor > 0.0f && mElapsedSeconds > playRangeSeconds.y )  ||
337                 ( mSpeedFactor < 0.0f && mElapsedSeconds < playRangeSeconds.x )) );
338
339     WrapInPlayRange( mElapsedSeconds, playRangeSeconds );
340
341     UpdateAnimators(bufferIndex, false, false );
342
343     if(looped)
344     {
345       ++mCurrentLoop;
346       mProgressReachedSignalRequired = mProgressMarker > 0.0f;
347       // don't increment mPlayedCount until the finished final loop
348     }
349   }
350   else
351   {
352     // playing once (and last mCurrentLoop)
353     finished = (mState == Playing                                                 &&
354                 (( mSpeedFactor > 0.0f && mElapsedSeconds > playRangeSeconds.y )  ||
355                  ( mSpeedFactor < 0.0f && mElapsedSeconds < playRangeSeconds.x )) );
356
357     // update with bake if finished
358     UpdateAnimators(bufferIndex, finished && (mEndAction != Dali::Animation::Discard), finished );
359
360     if(finished)
361     {
362       // The animation has now been played to completion
363       ++mPlayedCount;
364
365       // loop iterations come to this else branch for their final iterations
366       if( mCurrentLoop < mLoopCount)
367       {
368         ++mCurrentLoop;
369         DALI_ASSERT_DEBUG(mCurrentLoop == mLoopCount);
370       }
371
372       mProgressReachedSignalRequired = mProgressMarker > 0.0f;
373       mElapsedSeconds = playRangeSeconds.x;
374       mState = Stopped;
375     }
376   }
377 }
378
379 void Animation::UpdateAnimators( BufferIndex bufferIndex, bool bake, bool animationFinished )
380 {
381   const Vector2 playRange( mPlayRange * mDurationSeconds );
382   float elapsedSecondsClamped = Clamp( mElapsedSeconds, playRange.x, playRange.y );
383
384   //Loop through all animators
385   bool applied(true);
386   for ( AnimatorIter iter = mAnimators.Begin(); iter != mAnimators.End(); )
387   {
388     AnimatorBase *animator = *iter;
389
390     if( animator->Orphan() )
391     {
392       //Remove animators whose PropertyOwner has been destroyed
393       iter = mAnimators.Erase(iter);
394     }
395     else
396     {
397       if( animator->IsEnabled() )
398       {
399         const float intervalDelay( animator->GetIntervalDelay() );
400
401         if( elapsedSecondsClamped >= intervalDelay )
402         {
403           // Calculate a progress specific to each individual animator
404           float progress(1.0f);
405           const float animatorDuration = animator->GetDuration();
406           if (animatorDuration > 0.0f) // animators can be "immediate"
407           {
408             progress = Clamp((elapsedSecondsClamped - intervalDelay) / animatorDuration, 0.0f , 1.0f );
409           }
410           animator->Update(bufferIndex, progress, bake);
411         }
412         applied = true;
413       }
414       else
415       {
416         applied = false;
417       }
418
419       if ( animationFinished )
420       {
421         animator->SetActive( false );
422       }
423
424       if (applied)
425       {
426         INCREASE_COUNTER(PerformanceMonitor::ANIMATORS_APPLIED);
427       }
428
429       ++iter;
430     }
431   }
432
433 }
434
435 } // namespace SceneGraph
436
437 } // namespace Internal
438
439 } // namespace Dali