2 * Copyright (c) 2014 Samsung Electronics Co., Ltd.
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
19 #include <dali/internal/event/animation/animation-impl.h>
22 #include <dali/public-api/actors/actor.h>
23 #include <dali/public-api/animation/alpha-functions.h>
24 #include <dali/public-api/animation/time-period.h>
25 #include <dali/public-api/common/dali-common.h>
26 #include <dali/public-api/object/type-registry.h>
27 #include <dali/public-api/math/vector2.h>
28 #include <dali/public-api/math/radian.h>
29 #include <dali/internal/event/actors/actor-impl.h>
30 #include <dali/internal/event/animation/animation-playlist.h>
31 #include <dali/internal/event/animation/animator-connector.h>
32 #include <dali/internal/event/common/notification-manager.h>
33 #include <dali/internal/event/common/property-helper.h>
34 #include <dali/internal/event/common/stage-impl.h>
35 #include <dali/internal/event/common/thread-local-storage.h>
36 #include <dali/internal/event/effects/shader-effect-impl.h>
37 #include <dali/internal/update/manager/update-manager.h>
39 using Dali::Internal::SceneGraph::UpdateManager;
40 using Dali::Internal::SceneGraph::AnimatorBase;
41 using Dali::Internal::SceneGraph::Shader;
49 static bool SHOW_VALUE = true;
50 static bool HIDE_VALUE = false;
57 const char* const SIGNAL_FINISHED = "finished";
61 const char* const ACTION_PLAY = "play";
62 const char* const ACTION_STOP = "stop";
63 const char* const ACTION_PAUSE = "pause";
67 return Dali::Animation::New(0.f);
70 TypeRegistration mType( typeid( Dali::Animation ), typeid( Dali::BaseHandle ), Create );
72 SignalConnectorType signalConnector1( mType, SIGNAL_FINISHED, &Animation::DoConnectSignal );
74 TypeAction action1( mType, ACTION_PLAY, &Animation::DoAction );
75 TypeAction action2( mType, ACTION_STOP, &Animation::DoAction );
76 TypeAction action3( mType, ACTION_PAUSE, &Animation::DoAction );
78 const Dali::Animation::EndAction DEFAULT_END_ACTION( Dali::Animation::Bake );
79 const Dali::Animation::EndAction DEFAULT_DISCONNECT_ACTION( Dali::Animation::BakeFinal );
80 const Dali::Animation::Interpolation DEFAULT_INTERPOLATION( Dali::Animation::Linear );
85 AnimationPtr Animation::New(float durationSeconds)
87 Stage* stage = Stage::GetCurrent();
89 AnimationPlaylist& playlist = stage->GetAnimationPlaylist();
91 if( durationSeconds < 0.0f )
93 DALI_LOG_WARNING("duration should be greater than 0.0f.\n");
94 durationSeconds = 0.0f;
97 AnimationPtr animation = new Animation( *stage, playlist, durationSeconds, DEFAULT_END_ACTION, DEFAULT_DISCONNECT_ACTION, Dali::AlphaFunctions::Linear );
99 // Second-phase construction
100 animation->Initialize();
105 Animation::Animation( EventThreadServices& eventThreadServices, AnimationPlaylist& playlist, float durationSeconds, EndAction endAction, EndAction disconnectAction, AlphaFunction defaultAlpha )
106 : mEventThreadServices( eventThreadServices ),
107 mPlaylist( playlist ),
109 mNotificationCount( 0 ),
110 mFinishedCallback( NULL ),
111 mFinishedCallbackObject( NULL ),
112 mDurationSeconds( durationSeconds ),
115 mPlayRange( Vector2(0.0f,1.0f)),
116 mEndAction( endAction ),
117 mDisconnectAction( disconnectAction ),
118 mDefaultAlpha( defaultAlpha )
122 void Animation::Initialize()
124 // Connect to the animation playlist
125 mPlaylist.AnimationCreated( *this );
132 Animation::~Animation()
134 // Guard to allow handle destruction after Core has been destroyed
135 if ( Stage::IsInstalled() )
137 // Disconnect from the animation playlist
138 mPlaylist.AnimationDestroyed( *this );
140 DestroySceneObject();
146 void Animation::CreateSceneObject()
148 DALI_ASSERT_DEBUG( mAnimation == NULL );
150 // Create a new animation, temporarily owned
151 SceneGraph::Animation* animation = SceneGraph::Animation::New( mDurationSeconds, mSpeedFactor, mPlayRange, mIsLooping, mEndAction, mDisconnectAction );
153 // Keep a const pointer to the animation.
154 mAnimation = animation;
156 // Transfer animation ownership to the update manager through a message
157 AddAnimationMessage( mEventThreadServices.GetUpdateManager(), animation );
160 void Animation::DestroySceneObject()
162 if ( mAnimation != NULL )
164 // Remove animation using a message to the update manager
165 RemoveAnimationMessage( mEventThreadServices.GetUpdateManager(), *mAnimation );
170 void Animation::SetDuration(float seconds)
174 DALI_LOG_WARNING("duration should be greater than 0.0f.\n");
178 // Cache for public getters
179 mDurationSeconds = seconds;
181 // mAnimation is being used in a separate thread; queue a message to set the value
182 SetDurationMessage( mEventThreadServices, *mAnimation, seconds );
185 float Animation::GetDuration() const
187 // This is not animatable; the cached value is up-to-date.
188 return mDurationSeconds;
191 void Animation::SetLooping(bool looping)
193 // Cache for public getters
194 mIsLooping = looping;
196 // mAnimation is being used in a separate thread; queue a message to set the value
197 SetLoopingMessage( mEventThreadServices, *mAnimation, looping );
200 bool Animation::IsLooping() const
202 // This is not animatable; the cached value is up-to-date.
206 void Animation::SetEndAction(EndAction action)
208 // Cache for public getters
211 // mAnimation is being used in a separate thread; queue a message to set the value
212 SetEndActionMessage( mEventThreadServices, *mAnimation, action );
215 Dali::Animation::EndAction Animation::GetEndAction() const
217 // This is not animatable; the cached value is up-to-date.
221 void Animation::SetDisconnectAction(EndAction action)
223 // Cache for public getters
224 mDisconnectAction = action;
226 // mAnimation is being used in a separate thread; queue a message to set the value
227 SetDisconnectActionMessage( mEventThreadServices, *mAnimation, action );
230 Dali::Animation::EndAction Animation::GetDisconnectAction() const
232 // This is not animatable; the cached value is up-to-date.
233 return mDisconnectAction;
236 void Animation::Play()
238 // Update the current playlist
239 mPlaylist.OnPlay( *this );
241 // mAnimation is being used in a separate thread; queue a Play message
242 PlayAnimationMessage( mEventThreadServices, *mAnimation );
245 void Animation::PlayFrom( float progress )
247 if( progress >= mPlayRange.x && progress <= mPlayRange.y )
249 // Update the current playlist
250 mPlaylist.OnPlay( *this );
252 // mAnimation is being used in a separate thread; queue a Play message
253 PlayAnimationFromMessage( mEventThreadServices, *mAnimation, progress );
257 void Animation::Pause()
259 // mAnimation is being used in a separate thread; queue a Pause message
260 PauseAnimationMessage( mEventThreadServices, *mAnimation );
263 void Animation::Stop()
265 // mAnimation is being used in a separate thread; queue a Stop message
266 StopAnimationMessage( mEventThreadServices.GetUpdateManager(), *mAnimation );
269 void Animation::Clear()
271 DALI_ASSERT_DEBUG(mAnimation);
273 // Remove all the connectors
276 // Replace the old scene-object with a new one
277 DestroySceneObject();
280 // Reset the notification count, since the new scene-object has never been played
281 mNotificationCount = 0;
283 // Update the current playlist
284 mPlaylist.OnClear( *this );
287 void Animation::AnimateBy(Property& target, Property::Value& relativeValue)
289 AnimateBy(target, relativeValue, AlphaFunctions::Default, mDurationSeconds);
292 void Animation::AnimateBy(Property& target, Property::Value& relativeValue, AlphaFunction alpha)
294 AnimateBy(target, relativeValue, alpha, mDurationSeconds);
297 void Animation::AnimateBy(Property& target, Property::Value& relativeValue, TimePeriod period)
299 AnimateBy(target, relativeValue, AlphaFunctions::Default, period);
302 void Animation::AnimateBy(Property& target, Property::Value& relativeValue, AlphaFunction alpha, TimePeriod period)
304 Object& object = dynamic_cast<Object&>( GetImplementation(target.object) );
306 ExtendDuration( period );
308 switch ( relativeValue.GetType() )
310 case Property::BOOLEAN:
312 AddAnimatorConnector( AnimatorConnector<bool>::New( object,
313 target.propertyIndex,
314 target.componentIndex,
315 new AnimateByBoolean(relativeValue.Get<bool>()),
321 case Property::FLOAT:
323 AddAnimatorConnector( AnimatorConnector<float>::New( object,
324 target.propertyIndex,
325 target.componentIndex,
326 new AnimateByFloat(relativeValue.Get<float>()),
332 case Property::INTEGER:
334 AddAnimatorConnector( AnimatorConnector<int>::New( object,
335 target.propertyIndex,
336 target.componentIndex,
337 new AnimateByInteger(relativeValue.Get<int>()),
343 case Property::VECTOR2:
345 AddAnimatorConnector( AnimatorConnector<Vector2>::New( object,
346 target.propertyIndex,
347 target.componentIndex,
348 new AnimateByVector2(relativeValue.Get<Vector2>()),
354 case Property::VECTOR3:
356 AddAnimatorConnector( AnimatorConnector<Vector3>::New( object,
357 target.propertyIndex,
358 target.componentIndex,
359 new AnimateByVector3(relativeValue.Get<Vector3>()),
365 case Property::VECTOR4:
367 AddAnimatorConnector( AnimatorConnector<Vector4>::New( object,
368 target.propertyIndex,
369 target.componentIndex,
370 new AnimateByVector4(relativeValue.Get<Vector4>()),
376 case Property::ROTATION:
378 AngleAxis angleAxis = relativeValue.Get<AngleAxis>();
380 AddAnimatorConnector( AnimatorConnector<Quaternion>::New( object,
381 target.propertyIndex,
382 target.componentIndex,
383 new RotateByAngleAxis(angleAxis.angle, angleAxis.axis),
390 DALI_ASSERT_ALWAYS( false && "Property type enumeration out of bounds" ); // should never come here
395 void Animation::AnimateTo(Property& target, Property::Value& destinationValue)
397 AnimateTo(target, destinationValue, AlphaFunctions::Default, mDurationSeconds);
400 void Animation::AnimateTo(Property& target, Property::Value& destinationValue, AlphaFunction alpha)
402 AnimateTo(target, destinationValue, alpha, mDurationSeconds);
405 void Animation::AnimateTo(Property& target, Property::Value& destinationValue, TimePeriod period)
407 AnimateTo(target, destinationValue, AlphaFunctions::Default, period);
410 void Animation::AnimateTo(Property& target, Property::Value& destinationValue, AlphaFunction alpha, TimePeriod period)
412 Object& object = dynamic_cast<Object&>( GetImplementation(target.object) );
414 AnimateTo( object, target.propertyIndex, target.componentIndex, destinationValue, alpha, period );
417 void Animation::AnimateTo(Object& targetObject, Property::Index targetPropertyIndex, int componentIndex, Property::Value& destinationValue, AlphaFunction alpha, TimePeriod period)
419 Property::Type type = targetObject.GetPropertyType(targetPropertyIndex);
420 if(componentIndex != Property::INVALID_COMPONENT_INDEX)
422 if( type == Property::VECTOR2
423 || type == Property::VECTOR3
424 || type == Property::VECTOR4 )
426 type = Property::FLOAT;
429 DALI_ASSERT_ALWAYS( type == destinationValue.GetType() && "DestinationValue does not match Target Property type" );
431 ExtendDuration( period );
433 switch (destinationValue.GetType())
435 case Property::BOOLEAN:
437 AddAnimatorConnector( AnimatorConnector<bool>::New( targetObject,
440 new AnimateToBoolean( destinationValue.Get<bool>() ),
446 case Property::FLOAT:
448 AddAnimatorConnector( AnimatorConnector<float>::New( targetObject,
451 new AnimateToFloat( destinationValue.Get<float>() ),
457 case Property::INTEGER:
459 AddAnimatorConnector( AnimatorConnector<int>::New( targetObject,
462 new AnimateToInteger( destinationValue.Get<int>() ),
468 case Property::VECTOR2:
470 AddAnimatorConnector( AnimatorConnector<Vector2>::New( targetObject,
473 new AnimateToVector2( destinationValue.Get<Vector2>() ),
479 case Property::VECTOR3:
481 if ( Dali::Actor::Property::SIZE == targetPropertyIndex )
483 // Test whether this is actually an Actor
484 Actor* maybeActor = dynamic_cast<Actor*>( &targetObject );
487 // Notify the actor that its size is being animated
488 maybeActor->NotifySizeAnimation( *this, destinationValue.Get<Vector3>() );
492 AddAnimatorConnector( AnimatorConnector<Vector3>::New( targetObject,
495 new AnimateToVector3( destinationValue.Get<Vector3>() ),
501 case Property::VECTOR4:
503 AddAnimatorConnector( AnimatorConnector<Vector4>::New( targetObject,
506 new AnimateToVector4( destinationValue.Get<Vector4>() ),
512 case Property::ROTATION:
514 AddAnimatorConnector( AnimatorConnector<Quaternion>::New( targetObject,
517 new RotateToQuaternion( destinationValue.Get<Quaternion>() ),
524 DALI_ASSERT_ALWAYS( false && "Property type enumeration out of bounds" ); // should never come here
529 void Animation::AnimateBetween(Property target, const KeyFrames& keyFrames)
531 AnimateBetween(target, keyFrames, mDefaultAlpha, mDurationSeconds, DEFAULT_INTERPOLATION );
534 void Animation::AnimateBetween(Property target, const KeyFrames& keyFrames, Interpolation interpolation )
536 AnimateBetween(target, keyFrames, mDefaultAlpha, mDurationSeconds, interpolation );
539 void Animation::AnimateBetween(Property target, const KeyFrames& keyFrames, TimePeriod period)
541 AnimateBetween(target, keyFrames, mDefaultAlpha, period, DEFAULT_INTERPOLATION);
544 void Animation::AnimateBetween(Property target, const KeyFrames& keyFrames, TimePeriod period, Interpolation interpolation)
546 AnimateBetween(target, keyFrames, mDefaultAlpha, period, interpolation);
549 void Animation::AnimateBetween(Property target, const KeyFrames& keyFrames, AlphaFunction alpha)
551 AnimateBetween(target, keyFrames, alpha, mDurationSeconds, DEFAULT_INTERPOLATION);
554 void Animation::AnimateBetween(Property target, const KeyFrames& keyFrames, AlphaFunction alpha, Interpolation interpolation)
556 AnimateBetween(target, keyFrames, alpha, mDurationSeconds, interpolation);
559 void Animation::AnimateBetween(Property target, const KeyFrames& keyFrames, AlphaFunction alpha, TimePeriod period)
561 AnimateBetween(target, keyFrames, alpha, period, DEFAULT_INTERPOLATION);
564 void Animation::AnimateBetween(Property target, const KeyFrames& keyFrames, AlphaFunction alpha, TimePeriod period, Interpolation interpolation)
566 Object& object = dynamic_cast<Object&>( GetImplementation(target.object) );
568 ExtendDuration( period );
570 switch(keyFrames.GetType())
572 case Dali::Property::BOOLEAN:
574 const KeyFrameBoolean* kf;
575 GetSpecialization(keyFrames, kf);
576 KeyFrameBooleanPtr kfCopy = KeyFrameBoolean::Clone(*kf);
577 AddAnimatorConnector( AnimatorConnector<bool>::New( object,
578 target.propertyIndex,
579 target.componentIndex,
580 new KeyFrameBooleanFunctor(kfCopy),
586 case Dali::Property::FLOAT:
588 const KeyFrameNumber* kf;
589 GetSpecialization(keyFrames, kf);
590 KeyFrameNumberPtr kfCopy = KeyFrameNumber::Clone(*kf);
591 AddAnimatorConnector( AnimatorConnector<float>::New( object,
592 target.propertyIndex,
593 target.componentIndex,
594 new KeyFrameNumberFunctor(kfCopy,interpolation),
600 case Dali::Property::INTEGER:
602 const KeyFrameInteger* kf;
603 GetSpecialization(keyFrames, kf);
604 KeyFrameIntegerPtr kfCopy = KeyFrameInteger::Clone(*kf);
605 AddAnimatorConnector( AnimatorConnector<int>::New( object,
606 target.propertyIndex,
607 target.componentIndex,
608 new KeyFrameIntegerFunctor(kfCopy,interpolation),
614 case Dali::Property::VECTOR2:
616 const KeyFrameVector2* kf;
617 GetSpecialization(keyFrames, kf);
618 KeyFrameVector2Ptr kfCopy = KeyFrameVector2::Clone(*kf);
619 AddAnimatorConnector( AnimatorConnector<Vector2>::New( object,
620 target.propertyIndex,
621 target.componentIndex,
622 new KeyFrameVector2Functor(kfCopy,interpolation),
628 case Dali::Property::VECTOR3:
630 const KeyFrameVector3* kf;
631 GetSpecialization(keyFrames, kf);
632 KeyFrameVector3Ptr kfCopy = KeyFrameVector3::Clone(*kf);
633 AddAnimatorConnector( AnimatorConnector<Vector3>::New( object,
634 target.propertyIndex,
635 target.componentIndex,
636 new KeyFrameVector3Functor(kfCopy,interpolation),
642 case Dali::Property::VECTOR4:
644 const KeyFrameVector4* kf;
645 GetSpecialization(keyFrames, kf);
646 KeyFrameVector4Ptr kfCopy = KeyFrameVector4::Clone(*kf);
647 AddAnimatorConnector( AnimatorConnector<Vector4>::New( object,
648 target.propertyIndex,
649 target.componentIndex,
650 new KeyFrameVector4Functor(kfCopy,interpolation),
656 case Dali::Property::ROTATION:
658 const KeyFrameQuaternion* kf;
659 GetSpecialization(keyFrames, kf);
660 KeyFrameQuaternionPtr kfCopy = KeyFrameQuaternion::Clone(*kf);
661 AddAnimatorConnector( AnimatorConnector<Quaternion>::New( object,
662 target.propertyIndex,
663 target.componentIndex,
664 new KeyFrameQuaternionFunctor(kfCopy),
670 default: // not all property types are animateable
675 bool Animation::HasFinished()
677 bool hasFinished(false);
678 const int playCount(mAnimation->GetPlayCount());
680 // If the play count has been incremented, then another notification is required
681 if (playCount > mNotificationCount)
683 // Note that only one signal is emitted, if the animation has been played repeatedly
684 mNotificationCount = playCount;
692 Dali::Animation::AnimationSignalType& Animation::FinishedSignal()
694 return mFinishedSignal;
697 void Animation::EmitSignalFinish()
699 if ( !mFinishedSignal.Empty() )
701 Dali::Animation handle( this );
702 mFinishedSignal.Emit( handle );
705 // This callback is used internally, to avoid the overhead of using a signal.
706 if ( mFinishedCallback )
708 mFinishedCallback( mFinishedCallbackObject );
712 bool Animation::DoConnectSignal( BaseObject* object, ConnectionTrackerInterface* tracker, const std::string& signalName, FunctorDelegate* functor )
714 bool connected( true );
715 Animation* animation = dynamic_cast<Animation*>(object);
717 if ( 0 == strcmp( signalName.c_str(), SIGNAL_FINISHED ) )
719 animation->FinishedSignal().Connect( tracker, functor );
723 // signalName does not match any signal
730 void Animation::SetFinishedCallback( FinishedCallback callback, Object* object )
732 mFinishedCallback = callback;
733 mFinishedCallbackObject = object;
736 void Animation::AddAnimatorConnector( AnimatorConnectorBase* connector )
738 DALI_ASSERT_DEBUG( NULL != connector );
740 connector->SetParent(*this);
742 mConnectors.PushBack( connector );
745 void Animation::Animate( Actor& actor, const Path& path, const Vector3& forward )
747 Animate( actor, path, forward, mDefaultAlpha, TimePeriod(0.0f,GetDuration()) );
750 void Animation::Animate( Actor& actor, const Path& path, const Vector3& forward, AlphaFunction alpha )
752 Animate( actor, path, forward, alpha, TimePeriod(0.0f,GetDuration()) );
755 void Animation::Animate( Actor& actor, const Path& path, const Vector3& forward, TimePeriod period )
757 Animate( actor, path, forward, mDefaultAlpha, period );
760 void Animation::Animate( Actor& actor, const Path& path, const Vector3& forward, AlphaFunction alpha, TimePeriod period)
762 ExtendDuration( period );
764 PathPtr pathCopy = Path::Clone(path);
767 AddAnimatorConnector( AnimatorConnector<Vector3>::New( actor,
768 Dali::Actor::Property::POSITION,
769 Property::INVALID_COMPONENT_INDEX,
770 new PathPositionFunctor( pathCopy ),
774 //If forward is zero, PathRotationFunctor will always return the unit quaternion
775 if( forward != Vector3::ZERO )
778 AddAnimatorConnector( AnimatorConnector<Quaternion>::New( actor,
779 Dali::Actor::Property::ORIENTATION,
780 Property::INVALID_COMPONENT_INDEX,
781 new PathRotationFunctor( pathCopy, forward ),
787 void Animation::Show(Actor& actor, float delaySeconds)
789 ExtendDuration( TimePeriod(delaySeconds, 0) );
791 AddAnimatorConnector( AnimatorConnector<bool>::New( actor,
792 Dali::Actor::Property::VISIBLE,
793 Property::INVALID_COMPONENT_INDEX,
794 new AnimateToBoolean(SHOW_VALUE),
795 AlphaFunctions::Default,
796 TimePeriod(delaySeconds, 0.0f/*immediate*/) ) );
799 void Animation::Hide(Actor& actor, float delaySeconds)
801 ExtendDuration( TimePeriod(delaySeconds, 0) );
803 AddAnimatorConnector( AnimatorConnector<bool>::New( actor,
804 Dali::Actor::Property::VISIBLE,
805 Property::INVALID_COMPONENT_INDEX,
806 new AnimateToBoolean(HIDE_VALUE),
807 AlphaFunctions::Default,
808 TimePeriod(delaySeconds, 0.0f/*immediate*/) ) );
811 bool Animation::DoAction( BaseObject* object, const std::string& actionName, const std::vector<Property::Value>& attributes )
814 Animation* animation = dynamic_cast<Animation*>( object );
818 if( 0 == strcmp( actionName.c_str(), ACTION_PLAY ) )
820 if( attributes.size() > 0 )
822 animation->SetDuration( attributes[0].Get<float>() );
828 else if( 0 == strcmp( actionName.c_str(), ACTION_STOP ) )
833 else if( 0 == strcmp( actionName.c_str(), ACTION_PAUSE ) )
843 void Animation::SetCurrentProgress(float progress)
845 if( mAnimation && progress >= mPlayRange.x && progress <= mPlayRange.y )
847 // mAnimation is being used in a separate thread; queue a message to set the current progress
848 SetCurrentProgressMessage( mEventThreadServices, *mAnimation, progress );
852 float Animation::GetCurrentProgress()
856 return mAnimation->GetCurrentProgress();
862 void Animation::ExtendDuration( const TimePeriod& timePeriod )
864 float duration = timePeriod.delaySeconds + timePeriod.durationSeconds;
866 if( duration > mDurationSeconds )
868 SetDuration( duration );
872 void Animation::SetSpeedFactor( float factor )
876 mSpeedFactor = factor;
877 SetSpeedFactorMessage( mEventThreadServices, *mAnimation, factor );
881 float Animation::GetSpeedFactor() const
886 void Animation::SetPlayRange( const Vector2& range)
888 //Make sure the range specified is between 0.0 and 1.0
889 if( range.x >= 0.0f && range.x <= 1.0f && range.y >= 0.0f && range.y <= 1.0f )
891 Vector2 orderedRange( range );
892 //If the range is not in order swap values
893 if( range.x > range.y )
895 orderedRange = Vector2(range.y, range.x);
898 // Cache for public getters
899 mPlayRange = orderedRange;
901 // mAnimation is being used in a separate thread; queue a message to set play range
902 SetPlayRangeMessage( mEventThreadServices, *mAnimation, orderedRange );
906 Vector2 Animation::GetPlayRange() const
912 } // namespace Internal