3 // Copyright (c) 2014 Samsung Electronics Co., Ltd.
5 // Licensed under the Flora License, Version 1.0 (the License);
6 // you may not use this file except in compliance with the License.
7 // You may obtain a copy of the License at
9 // http://floralicense.org/license/
11 // Unless required by applicable law or agreed to in writing, software
12 // distributed under the License is distributed on an AS IS BASIS,
13 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 // See the License for the specific language governing permissions and
15 // limitations under the License.
18 #include <dali-toolkit/internal/controls/scrollable/scroll-view/scroll-view-effect-impl.h>
19 #include <dali-toolkit/internal/controls/scrollable/scroll-view/scroll-view-slide-effect-impl.h>
23 typedef Dali::Toolkit::Internal::ScrollSlideInfo ScrollSlideInfo;
24 typedef Dali::Toolkit::Internal::ScrollSlideInfoPtr ScrollSlideInfoPtr;
36 * ScrollSlideInfo structure contains
37 * common info that is shared amongst the constraints applied to Actors.
38 * The constraints + effect all share ownership of this info struct.
39 * The info is written to by the ScrollSlideInfoUpdate constraint. While
40 * the other constraints read from by the other constraints. Due to the order
41 * in which the constraints are applied, all constraints will get the current
42 * property values for these properties.
43 * The advantage of doing this is that:
44 * A) Constraints are not restricted by the 6 property limit. to function.
45 * B) Properties which rarely change or only change when another property changes
46 * (e.g. time), such as scroll position, scroll domain, size, wrap mode don't need
47 * to be checked for each constraint to apply.
49 class ScrollSlideInfo : public RefObject
64 virtual ~ScrollSlideInfo()
70 Vector3 mScrollPosition;
71 Vector3 mEffectReference;
73 Vector3 mScrollPositionMin;
74 Vector3 mScrollPositionMax;
80 } // namespace Internal
82 } // namespace Toolkit
87 namespace // unnamed namespace
90 const float SLIDEEFFECT_ANIMATION_MAX_TIME = 60.0f; ///< Animation time (every time finishes, checks if it needs to go again)
91 const float COMPLETION_START_DURATION = 0.25f; ///< Maximum time for completion of effect after scroll-view initially completes (due to delay effect)
92 const float COMPLETION_END_DURATION = 5.0f; ///< Maximum time for completion of effect after scroll-view initially completes (due to delay effect)
93 const float ANIMATION_BLEND_COEFFICIENT = 0.05f; ///< Animation blending coefficient (blends between target value e.g. 5% and current value 9%)
94 const float INV_ANIMATION_BLEND_COEFFICIENT = (1.0f - ANIMATION_BLEND_COEFFICIENT);
95 const float DEFAULT_MAX_DELAY_DURATION = 0.25f; ///< Default maximum delay duration of the effect after scroll completes is 0.25f
96 const float EFFECT_SNAP_GROW_DURATION = 0.33f; ///< Take 1/3rd of a second for the snap effect property to grow
97 const float EFFECT_SNAP_DECAY_DURATION = 0.667f; ///< Take 2/3rds of a second for the snap effect property to decay
100 * Gets a property index. If the property doesn't already exist, then
101 * it will create the property.
102 * @param[in] handle The handle that owns or will own the property
103 * @param[in] name The name for this property
104 * @param[in] propertyValue The initial value for this property
105 * @return The property index for this property is returned.
107 Property::Index SafeRegisterProperty( Handle& handle, const std::string& name, Property::Value propertyValue )
109 Property::Index index = handle.GetPropertyIndex( name );
111 if(index == Property::INVALID_INDEX)
113 index = handle.RegisterProperty( name, propertyValue );
120 * Re-scales input value x from x0 - x1, to linearly map
121 * over the values y0 - y1. Values outside of this range
122 * will also conform to the trend (gradient) set.
123 * @param[in] y0 output minimum bound
124 * @param[in] y1 output maximum bound
125 * @param[in] x input X value
126 * @param[in] x0 (optional) input minimum bound (default 0.0)
127 * @param[in] x1 (optional) input maximum bound (default 1.0)
128 * @return The result of the mapping is returned.
130 float Mix(float y0, float y1, float x, float x0 = 0.0f, float x1 = 1.0f)
132 return y0 + (y1 - y0) * (x - x0) / (x1-x0);
136 * Returns the value of x chasing target.
137 * returns a value of x which is closer to target.
138 * but limited by maxDelta. #
143 * result is 30.0f (x is 20.0f units closer to target)
144 * However, if x is already within maxDelta units
145 * of target, x will equal target.
150 * result is 50.0f (x was already within 20.0f units of target)
152 float Chase( float x, float target, float maxDelta )
154 float delta = target - x;
158 x = std::min( x + maxDelta, target );
162 x = std::max( x - maxDelta, target );
168 // constraints ////////////////////////////////////////////////////////////////
171 * ScrollSlideInfoUpdate
173 * Info constraint updates an info struct with property info,
174 * so that constraints can use this instead of having it passed through
177 struct ScrollSlideInfoUpdate
180 * Constraint constructor
182 ScrollSlideInfoUpdate(ScrollSlideInfoPtr scrollInfo)
183 : mScrollSlideInfo(scrollInfo)
188 * @param[in] current The current value
189 * @param[in] scrollPositionProperty The scroll-view's position property (SCROLL_POSITION_PROPERTY_NAME)
190 * @param[in] effectReferenceProperty The point in the scroll-view where the user touched the screen
191 * @param[in] effectTimeProperty The current timer. Starting from 0.0 when scroll animation/dragging
192 * commences. Ensures that constraint is applied and processed every frame (to achieve the delay effect)
193 * @param[in] sizeProperty The size of the ScrollView.
194 * @return The new position of this Actor.
196 float operator()(const float& current,
197 const PropertyInput& scrollPositionProperty,
198 const PropertyInput& effectReferenceProperty,
199 const PropertyInput& scrollSizeProperty,
200 const PropertyInput& scrollPositionMinProperty,
201 const PropertyInput& scrollPositionMaxProperty,
202 const PropertyInput& scrollWrapProperty)
204 mScrollSlideInfo->mScrollPosition = scrollPositionProperty.GetVector3();
205 mScrollSlideInfo->mEffectReference = effectReferenceProperty.GetVector3();
206 mScrollSlideInfo->mScrollSize = scrollSizeProperty.GetVector3();
207 mScrollSlideInfo->mScrollPositionMin = scrollPositionMinProperty.GetVector3();
208 mScrollSlideInfo->mScrollPositionMax = scrollPositionMaxProperty.GetVector3();
209 mScrollSlideInfo->mScrollWrap = scrollWrapProperty.GetBoolean();
216 ScrollSlideInfoPtr mScrollSlideInfo;
222 * ScrollSlidePositionConstraint
224 * Position constraint adjusts the position of the Actors
225 * based on their parent page's position relative to the middle of the screen.
226 * When at middle of the screen the position is not altered.
227 * When one screen away from middle the position is rotated about it's origin + mAnchor
229 struct ScrollSlidePositionConstraint
232 * Constraint constructor
234 ScrollSlidePositionConstraint(ScrollSlideInfoPtr scrollInfo, float delayMin, float delayMax)
235 : mScrollSlideInfo(scrollInfo),
236 mScrollPosition(scrollInfo->mScrollPosition),
244 * @param[in] current The current position
245 * @param[in] scrollPositionProperty The scroll-view's position property (SCROLL_POSITION_PROPERTY_NAME)
246 * @param[in] effectReferenceProperty The point in the scroll-view where the user touched the screen
247 * @param[in] effectTimeProperty The current timer. Starting from 0.0 when scroll animation/dragging
248 * commences. Ensures that constraint is applied and processed every frame (to achieve the delay effect)
249 * @param[in] sizeProperty The size of the ScrollView.
250 * @return The new position of this Actor.
252 Vector3 operator()(const Vector3& current,
253 const PropertyInput& pagePositionProperty,
254 const PropertyInput& effectTimeProperty,
255 const PropertyInput& deltaPositionProperty,
256 const PropertyInput& snapProperty)
258 const float complete = snapProperty.GetFloat();
259 bool activate = (complete > Math::MACHINE_EPSILON_1);
260 const Vector3& pagePosition = pagePositionProperty.GetVector3();
261 const Vector3& scrollPosition = mScrollSlideInfo->mScrollPosition;
263 // Get position of page.
264 Vector2 relativePosition(pagePosition + scrollPosition);
266 // short circuit: for orthognal view and when the blending has been deactivated.
268 (fabsf(relativePosition.x) < Math::MACHINE_EPSILON_1) &&
269 (fabsf(relativePosition.y) < Math::MACHINE_EPSILON_1) )
271 Vector3 actorPosition(current + scrollPosition);
272 return actorPosition;
275 const Vector3& referencePoint = mScrollSlideInfo->mEffectReference;
276 const Vector3& scrollSize = mScrollSlideInfo->mScrollSize;
277 const Vector3& deltaPosition = deltaPositionProperty.GetVector3();
279 // 1. Determine the relative position of the actor from the scrolling reference point.
280 // (the further away from the reference, the longer the delay should be)
281 const Vector3& min = mScrollSlideInfo->mScrollPositionMin;
282 const Vector3& max = mScrollSlideInfo->mScrollPositionMax;
284 relativePosition.y = (pagePosition.y + current.y - referencePoint.y) / scrollSize.height;
286 // Smoothen the relativePosition value by averaging with mRelativePosition (avoids sudden jerk when
287 // user touche different points)
288 float shortestDirection = ShortestDistanceInDomain(mRelativePosition.y, relativePosition.y, min.y, max.y);
289 mRelativePosition.y += activate ? shortestDirection * ANIMATION_BLEND_COEFFICIENT : shortestDirection;
291 // f represents this absolute distance. Get as a relative distance and inverse exponential
292 // (as delay equation has an exponential effect i.e. the closer delayFactor to 1.0f,
293 // the longer the delay would appear exponentially)
294 float f = fabsf(mRelativePosition.y) * complete;
295 f = std::min(f, 1.0f);
296 f = 1.0f - powf(ANIMATION_BLEND_COEFFICIENT, f);
298 // at center delay factor is mDelayMin, at maximum (1.0) it is mDelayMax
299 f = Mix(mDelayMin, mDelayMax, f);
301 // 2. Now that f (delay factor) has been determined for this Actor,
302 // move mScrollPosition towards the actual scroll position, at rate determined by f.
303 float shortest = ShortestDistanceInDomain(mScrollPosition.x, WrapInDomain(scrollPosition.x, -min.x, -max.x), min.x, max.x);
304 mScrollPosition.x += activate ? shortest * (1.0f-f) : shortest;
305 mScrollPosition.x = WrapInDomain(mScrollPosition.x, -min.x, -max.x);
306 mScrollPosition.y = scrollPosition.y;
308 Vector3 actorPosition(current + pagePosition + mScrollPosition);
310 // Get position of actor.
311 bool wrap = mScrollSlideInfo->mScrollWrap;
315 if(fabsf(min.x - max.x) > Math::MACHINE_EPSILON_1)
317 // WRAP X (based on the position of the right side)
318 actorPosition.x = WrapInDomain(actorPosition.x + scrollSize.x, min.x, max.x) - scrollSize.width;
322 const float targetRelativePositionX = (referencePoint.x + deltaPosition.x);
324 float blend(Mix(1.0f, ANIMATION_BLEND_COEFFICIENT, 1.0f - (1.0f - complete) * (1.0f - complete) ));
325 float invBlend(1.0f - blend);
327 mRelativePosition.x = activate ? mRelativePosition.x * invBlend + targetRelativePositionX * blend : targetRelativePositionX;
328 mRelativePosition.x = Chase( mRelativePosition.x, targetRelativePositionX, 1.0f );
330 relativePosition.x = (actorPosition.x - mRelativePosition.x) / scrollSize.width;
332 float difference = fabsf(ShortestDistanceInDomain(mScrollPosition.x, scrollPosition.x, -max.x, -min.x));
333 mAverageSpeed = activate ? mAverageSpeed * invBlend + difference * blend : 0.0f;
335 actorPosition.x += relativePosition.x * mAverageSpeed;
337 return actorPosition - pagePosition;
342 ScrollSlideInfoPtr mScrollSlideInfo;
343 Vector3 mScrollPosition; ///< The current scroll position
344 float mDelayMin; ///< Minimum delay rate (at closest position to touch)
345 float mDelayMax; ///< Maximum delay rate (at furthest position from touch - 1 page away)
346 Vector2 mRelativePosition;
347 float mAverageSpeed; ///< The Average speed of the Actor (proportional to mScrollPosition - scrollPosition)
352 * ScrollSlideScaleConstraint
355 struct ScrollSlideScaleConstraint
358 * Constraint constructor
360 ScrollSlideScaleConstraint()
365 * @param[in] current The current position
366 * @param[in] scrollPositionProperty The scroll-view's position property (SCROLL_POSITION_PROPERTY_NAME)
367 * @param[in] effectReferenceProperty The point in the scroll-view where the user touched the screen
368 * @param[in] effectTimeProperty The current timer. Starting from 0.0 when scroll animation/dragging
369 * commences. Ensures that constraint is applied and processed every frame (to achieve the delay effect)
370 * @param[in] sizeProperty The size of the ScrollView.
371 * @return The new position of this Actor.
373 Vector3 operator()(const Vector3& current,
374 const PropertyInput& snapProperty)
376 float scale = 1.0f + snapProperty.GetFloat() * 0.008f;
377 return Vector3( current.x * scale, current.y * scale, current.z);
383 * Applies the slide constraints to the child actor for overshoot effect.
385 * @param[in] scrollView The ScrollView containing the pages.
386 * @param[in] child The child to be affected with the slide effect.
387 * @param[in] angleSwing The maximum amount the child actor should
388 * rotate in radians for each axis (X and Y) as the page is scrolled.
389 * move for each axis (X and Y) as the page is scrolled.
391 void ApplyScrollSlideConstraints(ScrollSlideInfoPtr scrollSlideInfo,
392 Toolkit::ScrollView scrollView,
397 // Apply constraints to these actors //
398 Constraint constraint = Constraint::New<Vector3>( Actor::POSITION,
399 ParentSource(Actor::POSITION),
400 Source(scrollView, scrollView.GetPropertyIndex( Toolkit::ScrollViewSlideEffect::EFFECT_TIME ) ),
401 Source(scrollView, scrollView.GetPropertyIndex( Toolkit::ScrollView::SCROLL_POSITION_DELTA_PROPERTY_NAME ) ),
402 Source(scrollView, scrollView.GetPropertyIndex( Toolkit::ScrollViewSlideEffect::EFFECT_ACTIVE ) ),
403 ScrollSlidePositionConstraint(scrollSlideInfo, delayMin, delayMax) );
404 constraint.SetRemoveAction( Constraint::Discard );
405 child.ApplyConstraint( constraint );
407 constraint = Constraint::New<Vector3>( Actor::SCALE,
408 Source(scrollView, scrollView.GetPropertyIndex( Toolkit::ScrollViewSlideEffect::EFFECT_ACTIVE ) ),
409 ScrollSlideScaleConstraint() );
410 constraint.SetRemoveAction( Constraint::Discard );
411 child.ApplyConstraint( constraint );
414 } // unnamed namespace
425 ScrollViewSlideEffect::ScrollViewSlideEffect()
426 : mScrollSlideInfo(new ScrollSlideInfo()),
427 mPropertyTime(Property::INVALID_INDEX),
428 mPropertyReference(Property::INVALID_INDEX),
429 mPropertyActive(Property::INVALID_INDEX),
430 mDelayReferenceOffset(Vector3::ZERO),
431 mMaxDelayDuration(DEFAULT_MAX_DELAY_DURATION)
435 ScrollViewSlideEffect::~ScrollViewSlideEffect()
439 bool ScrollViewSlideEffect::GetSlideDirection() const
441 return mScrollSlideInfo->mVertical;
444 void ScrollViewSlideEffect::SetSlideDirection(bool vertical)
446 mScrollSlideInfo->mVertical = vertical;
449 const Vector3& ScrollViewSlideEffect::GetDelayReferenceOffset() const
451 return mDelayReferenceOffset;
454 void ScrollViewSlideEffect::SetDelayReferenceOffset(const Vector3& offset)
456 mDelayReferenceOffset = offset;
459 float ScrollViewSlideEffect::GetMaxDelayDuration() const
461 return mMaxDelayDuration;
464 void ScrollViewSlideEffect::SetMaxDelayDuration(float duration)
466 mMaxDelayDuration = duration;
469 void ScrollViewSlideEffect::ApplyToActor(Actor child,
473 ApplyScrollSlideConstraints( mScrollSlideInfo,
480 void ScrollViewSlideEffect::OnAttach(Toolkit::ScrollView& scrollView)
482 mScrollSlideInfo->mScrollPosition = scrollView.GetProperty<Vector3>( scrollView.GetPropertyIndex( Toolkit::ScrollView::SCROLL_POSITION_PROPERTY_NAME ) );
483 mScrollSlideInfo->mScrollSize = scrollView.GetProperty<Vector3>( Actor::SIZE );
484 mScrollSlideInfo->mScrollPositionMin = scrollView.GetProperty<Vector3>( scrollView.GetPropertyIndex( Toolkit::ScrollView::SCROLL_POSITION_MIN_PROPERTY_NAME ) );
485 mScrollSlideInfo->mScrollPositionMax = scrollView.GetProperty<Vector3>( scrollView.GetPropertyIndex( Toolkit::ScrollView::SCROLL_POSITION_MAX_PROPERTY_NAME ) );
486 mScrollSlideInfo->mScrollWrap = scrollView.GetProperty<bool>( scrollView.GetPropertyIndex( Toolkit::ScrollView::SCROLL_WRAP_PROPERTY_NAME ) );
487 mScrollSlideInfo->mVertical = false;
489 // Create effect-time property if not already created.
490 if(mPropertyTime == Property::INVALID_INDEX)
492 mPropertyTime = SafeRegisterProperty( scrollView, Toolkit::ScrollViewSlideEffect::EFFECT_TIME, 0.0f );
493 mPropertyReference = SafeRegisterProperty( scrollView, Toolkit::ScrollViewSlideEffect::EFFECT_REFERENCE, Vector3::ZERO );
494 mPropertyActive = SafeRegisterProperty( scrollView, Toolkit::ScrollViewSlideEffect::EFFECT_ACTIVE, 0.0f );
497 // Create constraint to update ScrollSlideInfo
498 // Doesn't matter what this is applied to and on what property.
499 // Just needs to update mScrollSlideInfo values as properties change.
500 // The minor constraints (applied to the Actors) can use this mScrollSlideInfo.
501 Constraint constraint = Constraint::New<float>( mPropertyTime,
502 Source(scrollView, scrollView.GetPropertyIndex( Toolkit::ScrollView::SCROLL_POSITION_PROPERTY_NAME ) ),
503 Source(scrollView, scrollView.GetPropertyIndex( Toolkit::ScrollViewSlideEffect::EFFECT_REFERENCE ) ),
504 Source(scrollView, Actor::SIZE),
505 Source(scrollView, scrollView.GetPropertyIndex( Toolkit::ScrollView::SCROLL_POSITION_MIN_PROPERTY_NAME ) ),
506 Source(scrollView, scrollView.GetPropertyIndex( Toolkit::ScrollView::SCROLL_POSITION_MAX_PROPERTY_NAME ) ),
507 Source(scrollView, scrollView.GetPropertyIndex( Toolkit::ScrollView::SCROLL_WRAP_PROPERTY_NAME ) ),
508 ScrollSlideInfoUpdate(mScrollSlideInfo) );
509 constraint.SetRemoveAction( Constraint::Discard );
510 mInfoUpdateConstraint = scrollView.ApplyConstraint( constraint );
512 // Connect to the scroll view signals
513 scrollView.ScrollStartedSignal().Connect(this, &ScrollViewSlideEffect::OnScrollStart);
514 scrollView.SnapStartedSignal().Connect(this, &ScrollViewSlideEffect::OnScrollSnapStarted);
515 scrollView.TouchedSignal().Connect(this, &ScrollViewSlideEffect::OnScrollTouched);
517 AttachActor(scrollView);
520 bool ScrollViewSlideEffect::OnScrollTouched(Actor actor, const TouchEvent& event)
522 // Ignore events with multiple-touch points
523 if (event.GetPointCount() != 1)
528 if (event.GetPoint(0).state == TouchPoint::Down)
530 const TouchPoint& point = event.GetPoint(0);
531 Vector3 touchPosition(point.local - Stage::GetCurrent().GetSize() * 0.5f);
533 Vector3 scrollPosition = GetScrollView().GetCurrentScrollPosition();
534 GetScrollView().SetProperty(mPropertyReference, scrollPosition + touchPosition + mDelayReferenceOffset);
540 void ScrollViewSlideEffect::OnDetach(Toolkit::ScrollView& scrollView)
542 scrollView.ScrollStartedSignal().Disconnect(this, &ScrollViewSlideEffect::OnScrollStart);
543 scrollView.SnapStartedSignal().Disconnect(this, &ScrollViewSlideEffect::OnScrollSnapStarted);
544 scrollView.TouchedSignal().Disconnect(this, &ScrollViewSlideEffect::OnScrollTouched);
545 scrollView.RemoveConstraint( mInfoUpdateConstraint );
549 mAnimation.FinishedSignal().Disconnect(this, &ScrollViewSlideEffect::OnAnimationFinished);
556 mAnimationSnap.FinishedSignal().Disconnect(this, &ScrollViewSlideEffect::OnAnimationSnapFinished);
557 mAnimationSnap.Clear();
558 mAnimationSnap.Reset();
562 void ScrollViewSlideEffect::AttachActor(Actor actor)
567 void ScrollViewSlideEffect::DetachActor(Actor actor)
569 // TODO: remove the specific constraint defined in AttachActor (and possibly
570 // unregister property) - neither functionality exists in Dali.
573 void ScrollViewSlideEffect::ContinueAnimation(float endTime)
575 // continue animating
578 mAnimation.FinishedSignal().Disconnect(this, &ScrollViewSlideEffect::OnAnimationFinished);
582 Actor scrollView = GetScrollView();
584 mAnimation = Animation::New(SLIDEEFFECT_ANIMATION_MAX_TIME);
585 mAnimation.AnimateTo( Property(scrollView, mPropertyTime), endTime, AlphaFunctions::Linear );
586 mAnimation.FinishedSignal().Connect(this, &ScrollViewSlideEffect::OnAnimationFinished);
590 void ScrollViewSlideEffect::OnScrollStart( const Vector3& position )
592 Actor scrollView = GetScrollView();
593 GetScrollView().SetProperty(mPropertyTime, 0.0f);
595 ContinueAnimation(SLIDEEFFECT_ANIMATION_MAX_TIME);
599 mAnimationSnap.FinishedSignal().Disconnect(this, &ScrollViewSlideEffect::OnAnimationSnapFinished);
600 mAnimationSnap.Clear();
603 mAnimationSnap = Animation::New( EFFECT_SNAP_GROW_DURATION );
604 mAnimationSnap.AnimateTo( Property(scrollView, mPropertyActive), 1.0f, AlphaFunctions::Linear );
605 mAnimationSnap.FinishedSignal().Connect(this, &ScrollViewSlideEffect::OnAnimationSnapFinished);
606 mAnimationSnap.Play();
609 void ScrollViewSlideEffect::OnScrollSnapStarted(const Toolkit::ScrollView::SnapEvent& event)
613 mAnimationSnap.Clear();
616 Actor scrollView = GetScrollView();
617 mAnimationSnap = Animation::New(EFFECT_SNAP_DECAY_DURATION );
618 mAnimationSnap.AnimateTo( Property(scrollView, mPropertyActive), 0.0f, AlphaFunctions::Linear );
619 mAnimationSnap.FinishedSignal().Connect(this, &ScrollViewSlideEffect::OnAnimationSnapFinished);
620 mAnimationSnap.Play();
623 void ScrollViewSlideEffect::OnAnimationSnapFinished( Animation& animation )
625 mAnimationSnap.FinishedSignal().Disconnect(this, &ScrollViewSlideEffect::OnAnimationSnapFinished);
626 mAnimationSnap.Clear();
628 // stop time animation
631 mAnimation.FinishedSignal().Disconnect(this, &ScrollViewSlideEffect::OnAnimationFinished);
636 void ScrollViewSlideEffect::OnAnimationFinished( Animation& animation )
638 // still unstable, so continue animating.
639 // TODO: Requires an instability check to ensure time animation finishes when delay is
640 // less noticeable. i.e. all present scrollPositions are approx the same as mScrollPosition in constraints.
641 // best solution for this is to switch to a single history vector of scroll position, and compare if
642 // position has not deviated >= 0.5 pixel for the past 1 second.
643 float endTime = GetScrollView().GetProperty<float>(mPropertyTime) + SLIDEEFFECT_ANIMATION_MAX_TIME;
644 ContinueAnimation(endTime);
647 } // namespace Internal
649 } // namespace Toolkit