712cb17b53360ff58971299ef42dc7f2f9632e66
[platform/core/uifw/dali-toolkit.git] / dali-toolkit / internal / controls / scrollable / scroll-view / scroll-view-impl.cpp
1 /*
2  * Copyright (c) 2021 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-toolkit/internal/controls/scrollable/scroll-view/scroll-view-impl.h>
20
21 // EXTERNAL INCLUDES
22 #include <dali/devel-api/actors/actor-devel.h>
23 #include <dali/devel-api/common/stage.h>
24 #include <dali/devel-api/events/pan-gesture-devel.h>
25 #include <dali/devel-api/object/property-helper-devel.h>
26 #include <dali/integration-api/debug.h>
27 #include <dali/public-api/animation/constraints.h>
28 #include <dali/public-api/events/touch-event.h>
29 #include <dali/public-api/events/wheel-event.h>
30 #include <dali/public-api/object/property-map.h>
31 #include <dali/public-api/object/type-registry-helper.h>
32 #include <dali/public-api/object/type-registry.h>
33 #include <cstring> // for strcmp
34
35 // INTERNAL INCLUDES
36 #include <dali-toolkit/devel-api/controls/scroll-bar/scroll-bar.h>
37 #include <dali-toolkit/internal/controls/scrollable/scroll-view/scroll-overshoot-indicator-impl.h>
38 #include <dali-toolkit/internal/controls/scrollable/scroll-view/scroll-view-effect-impl.h>
39 #include <dali-toolkit/internal/controls/scrollable/scroll-view/scroll-view-impl-property-handler.h>
40 #include <dali-toolkit/public-api/controls/scrollable/scroll-view/scroll-mode.h>
41 #include <dali-toolkit/public-api/controls/scrollable/scroll-view/scroll-view-constraints.h>
42 #include <dali-toolkit/public-api/controls/scrollable/scroll-view/scroll-view.h>
43
44 //#define ENABLED_SCROLL_STATE_LOGGING
45
46 #ifdef ENABLED_SCROLL_STATE_LOGGING
47 #define DALI_LOG_SCROLL_STATE(format, ...) Dali::Integration::Log::LogMessageWithFunctionLine(Dali::Integration::Log::DebugInfo, "%s:%d " format "\n", __PRETTY_FUNCTION__, __LINE__, ##__VA_ARGS__)
48 #else
49 #define DALI_LOG_SCROLL_STATE(format, ...)
50 #endif
51
52 // TODO: Change to two class system:
53 // 1. DraggableActor (is an actor which can be dragged anywhere, can be set to range using the ruler)
54 // 2. ScrollView (contains a draggable actor that can a) be dragged in the negative X, and Y domain, b) has a hitArea for touches)
55 // TODO: external components (page and status overlays).
56 // TODO: Orientation.
57 // TODO: upgrade Vector2/3 to support returning Unit vectors, normals, & cross product (dot product is already provided)
58
59 namespace
60 {
61 using namespace Dali;
62
63 constexpr float DEFAULT_SLOW_SNAP_ANIMATION_DURATION(0.5f);  ///< Default Drag-Release animation time.
64 constexpr float DEFAULT_FAST_SNAP_ANIMATION_DURATION(0.25f); ///< Default Drag-Flick animation time.
65 constexpr float DEFAULT_SNAP_OVERSHOOT_DURATION(0.5f);       ///< Default Overshoot snapping animation time.
66 constexpr float DEFAULT_MAX_OVERSHOOT(100.0f);               ///< Default maximum allowed overshoot in pixels
67
68 constexpr float DEFAULT_AXIS_AUTO_LOCK_GRADIENT(0.36f); ///< Default Axis-AutoLock gradient threshold. default is 0.36:1 (20 degrees)
69 constexpr float DEFAULT_FRICTION_COEFFICIENT(1.0f);     ///< Default Friction Co-efficient. (in stage diagonals per second)
70 constexpr float DEFAULT_FLICK_SPEED_COEFFICIENT(1.0f);  ///< Default Flick speed coefficient (multiples input touch velocity)
71 constexpr float DEFAULT_MAX_FLICK_SPEED(3.0f);          ///< Default Maximum flick speed. (in stage diagonals per second)
72
73 constexpr Dali::Vector2 DEFAULT_MIN_FLICK_DISTANCE(30.0f, 30.0f);  ///< minimum distance for pan before flick allowed
74 constexpr float         DEFAULT_MIN_FLICK_SPEED_THRESHOLD(500.0f); ///< Minimum pan speed required for flick in pixels/s
75
76 constexpr float FREE_FLICK_SPEED_THRESHOLD      = 200.0f; ///< Free-Flick threshold in pixels/ms
77 constexpr float AUTOLOCK_AXIS_MINIMUM_DISTANCE2 = 100.0f; ///< Auto-lock axis after minimum distance squared.
78 constexpr float FLICK_ORTHO_ANGLE_RANGE         = 75.0f;  ///< degrees. (if >45, then supports diagonal flicking)
79
80 constexpr Dali::Vector2 DEFAULT_WHEEL_SCROLL_DISTANCE_STEP_PROPORTION(0.17f, 0.1f); ///< The step of horizontal scroll distance in the proportion of stage size for each wheel event received.
81
82 constexpr unsigned long MINIMUM_TIME_BETWEEN_DOWN_AND_UP_FOR_RESET(150u);
83 constexpr float         TOUCH_DOWN_TIMER_INTERVAL = 100.0f;
84 constexpr float         DEFAULT_SCROLL_UPDATE_DISTANCE(30.0f); ///< Default distance to travel in pixels for scroll update signal
85
86 const std::string INTERNAL_MAX_POSITION_PROPERTY_NAME("internalMaxPosition");
87
88 // Helpers ////////////////////////////////////////////////////////////////////////////////////////
89
90 /**
91  * Find the vector (distance) from (a) to (b)
92  * in domain (start) to (end)
93  * (\ / start)               (\ / end)
94  *   |-a                 b<----|
95  *
96  * @note assumes both (a) and (b) are already with the domain
97  * (start) to (end)
98  *
99  * @param[in] a the current point
100  * @param[in] b the target point
101  * @param[in] start the start of the domain
102  * @param[in] end the end of the domain
103  * @param[in] bias whether to only take the right direction or the left direction,
104  * or the shortest direction.
105  * @return the shortest direction and distance
106  */
107 float VectorInDomain(float a, float b, float start, float end, Dali::Toolkit::DirectionBias bias)
108 {
109   if(bias == Dali::Toolkit::DIRECTION_BIAS_NONE)
110   {
111     return Dali::ShortestDistanceInDomain(a, b, start, end);
112   }
113   //  (a-start + end-b)
114   float size = end - start;
115   float vect = b - a;
116
117   if(vect > 0)
118   {
119     // +ve vector
120     if(bias == Dali::Toolkit::DIRECTION_BIAS_RIGHT) // going right, take the vector.
121     {
122       return vect;
123     }
124     else
125     {
126       float aRight = a + size;
127       return b - aRight;
128     }
129   }
130   else
131   {
132     // -ve vector
133     if(bias == Dali::Toolkit::DIRECTION_BIAS_LEFT) // going left, take the vector.
134     {
135       return vect;
136     }
137     else
138     {
139       float aLeft = a - size;
140       return b - aLeft;
141     }
142   }
143 }
144
145 /**
146  * Returns the position of the anchor within actor
147  *
148  * @param actor The Actor
149  * @param anchor The Anchor point of interest.
150  * @return The position of the Anchor
151  */
152 Dali::Vector3 GetPositionOfAnchor(Dali::Actor& actor, const Dali::Vector3& anchor)
153 {
154   Dali::Vector3 childPosition = actor.GetCurrentProperty<Dali::Vector3>(Dali::Actor::Property::POSITION);
155   Dali::Vector3 childAnchor   = -actor.GetCurrentProperty<Dali::Vector3>(Dali::Actor::Property::ANCHOR_POINT) + anchor;
156   Dali::Vector3 childSize     = actor.GetCurrentProperty<Dali::Vector3>(Dali::Actor::Property::SIZE);
157
158   return childPosition + childAnchor * childSize;
159 }
160
161 /**
162  * Returns the closest actor to the given position
163  * @param[in] actor The scrollview actor
164  * @param[in] internalActor The internal actor (to ignore)
165  * @param[in] position The given position
166  * @param[in] dirX Direction to search in
167  * @param[in] dirY Direction to search in
168  * @param[in] dirZ Direction to search in
169  * @return the closest child actor
170  */
171 using FindDirection = Dali::Toolkit::Internal::ScrollView::FindDirection;
172
173 Actor FindClosestActorToPosition(
174   CustomActor actor, Actor internalActor, const Vector3& position, FindDirection dirX, FindDirection dirY, FindDirection dirZ)
175 {
176   Actor   closestChild;
177   float   closestDistance2 = 0.0f;
178   Vector3 actualPosition   = position;
179
180   unsigned int numChildren = actor.GetChildCount();
181
182   for(unsigned int i = 0; i < numChildren; ++i)
183   {
184     Actor child = actor.GetChildAt(i);
185
186     if(internalActor == child) // ignore internal actor.
187     {
188       continue;
189     }
190
191     Vector3 childPosition = GetPositionOfAnchor(child, AnchorPoint::CENTER);
192
193     Vector3 delta = childPosition - actualPosition;
194
195     // X-axis checking (only find Actors to the [dirX] of actualPosition)
196     if(dirX > FindDirection::All) // != All,None
197     {
198       FindDirection deltaH = delta.x > 0 ? FindDirection::Right : FindDirection::Left;
199       if(dirX != deltaH)
200       {
201         continue;
202       }
203     }
204
205     // Y-axis checking (only find Actors to the [dirY] of actualPosition)
206     if(dirY > FindDirection::All) // != All,None
207     {
208       FindDirection deltaV = delta.y > 0 ? FindDirection::Down : FindDirection::Up;
209       if(dirY != deltaV)
210       {
211         continue;
212       }
213     }
214
215     // Z-axis checking (only find Actors to the [dirZ] of actualPosition)
216     if(dirZ > FindDirection::All) // != All,None
217     {
218       FindDirection deltaV = delta.y > 0 ? FindDirection::In : FindDirection::Out;
219       if(dirZ != deltaV)
220       {
221         continue;
222       }
223     }
224
225     // compare child to closest child in terms of distance.
226     float distance2 = 0.0f;
227
228     // distance2 = the Square of the relevant dimensions of delta
229     if(dirX != FindDirection::None)
230     {
231       distance2 += delta.x * delta.x;
232     }
233
234     if(dirY != FindDirection::None)
235     {
236       distance2 += delta.y * delta.y;
237     }
238
239     if(dirZ != FindDirection::None)
240     {
241       distance2 += delta.z * delta.z;
242     }
243
244     if(closestChild) // Next time.
245     {
246       if(distance2 < closestDistance2)
247       {
248         closestChild     = child;
249         closestDistance2 = distance2;
250       }
251     }
252     else // First time.
253     {
254       closestChild     = child;
255       closestDistance2 = distance2;
256     }
257   }
258
259   return closestChild;
260 }
261
262 // AlphaFunctions /////////////////////////////////////////////////////////////////////////////////
263
264 /**
265  * ConstantDecelerationAlphaFunction
266  * Newtoninan distance for constant deceleration
267  * v = 1 - t, s = t - 1/2 t^2
268  * when t = 0, s = 0.0 (min distance)
269  * when t = 1, s = 0.5 (max distance)
270  * progress = s / (max-min) = 2t - t^2
271  *
272  * @param[in] offset The input progress
273  * @return The output progress
274  */
275 float ConstantDecelerationAlphaFunction(float progress)
276 {
277   return progress * 2.0f - progress * progress;
278 }
279
280 /**
281  * Clamp a position
282  * @param[in] size The size to clamp to
283  * @param[in] rulerX The horizontal ruler
284  * @param[in] rulerY The vertical ruler
285  * @param[in,out] position The position to clamp
286  * @param[out] clamped the clamped state
287  */
288 void ClampPosition(const Vector3& size, Dali::Toolkit::RulerPtr rulerX, Dali::Toolkit::RulerPtr rulerY, Vector2& position, Dali::Toolkit::ClampState2D& clamped)
289 {
290   position.x = -rulerX->Clamp(-position.x, size.width, 1.0f, clamped.x);  // NOTE: X & Y rulers think in -ve coordinate system.
291   position.y = -rulerY->Clamp(-position.y, size.height, 1.0f, clamped.y); // That is scrolling RIGHT (e.g. 100.0, 0.0) means moving LEFT.
292 }
293
294 /**
295  * TODO: In situations where axes are different (X snap, Y free)
296  * Each axis should really have their own independent animation (time and equation)
297  * Consider, X axis snapping to nearest grid point (EaseOut over fixed time)
298  * Consider, Y axis simulating physics to arrive at a point (Physics equation over variable time)
299  * Currently, the axes have been split however, they both use the same EaseOut equation.
300  *
301  * @param[in] scrollView The main scrollview
302  * @param[in] rulerX The X ruler
303  * @param[in] rulerY The Y ruler
304  * @param[in] lockAxis Which axis (if any) is locked.
305  * @param[in] velocity Current pan velocity
306  * @param[in] maxOvershoot Maximum overshoot
307  * @param[in] inAcessibilityPan True if we are currently panning with accessibility
308  * @param[out] positionSnap The target position of snap animation
309  * @param[out] positionDuration The duration of the snap animation
310  * @param[out] alphaFunction The snap animation alpha function
311  * @param[out] isFlick if we are flicking or not
312  * @param[out] isFreeFlick if we are free flicking or not
313  */
314 void SnapWithVelocity(
315   Dali::Toolkit::Internal::ScrollView&          scrollView,
316   Dali::Toolkit::RulerPtr                       rulerX,
317   Dali::Toolkit::RulerPtr                       rulerY,
318   Dali::Toolkit::Internal::ScrollView::LockAxis lockAxis,
319   Vector2                                       velocity,
320   Vector2                                       maxOvershoot,
321   Vector2&                                      positionSnap,
322   Vector2&                                      positionDuration,
323   AlphaFunction&                                alphaFunction,
324   bool                                          inAccessibilityPan,
325   bool&                                         isFlick,
326   bool&                                         isFreeFlick)
327 {
328   // Animator takes over now, touches are assumed not to interfere.
329   // And if touches do interfere, then we'll stop animation, update PrePosition
330   // to current mScroll's properties, and then resume.
331   // Note: For Flicking this may work a bit different...
332
333   float         angle      = atan2(velocity.y, velocity.x);
334   float         speed2     = velocity.LengthSquared();
335   float         biasX      = 0.5f;
336   float         biasY      = 0.5f;
337   FindDirection horizontal = FindDirection::None;
338   FindDirection vertical   = FindDirection::None;
339
340   using LockAxis = Dali::Toolkit::Internal::ScrollView::LockAxis;
341
342   // orthoAngleRange = Angle tolerance within the Exact N,E,S,W direction
343   // that will be accepted as a general N,E,S,W flick direction.
344
345   const float orthoAngleRange      = FLICK_ORTHO_ANGLE_RANGE * M_PI / 180.0f;
346   const float flickSpeedThreshold2 = scrollView.GetMinimumSpeedForFlick() * scrollView.GetMinimumSpeedForFlick();
347
348   // Flick logic X Axis
349
350   if(rulerX->IsEnabled() && lockAxis != LockAxis::LockHorizontal)
351   {
352     horizontal = FindDirection::All;
353
354     if(speed2 > flickSpeedThreshold2 || // exceeds flick threshold
355        inAccessibilityPan)              // With AccessibilityPan its easier to move between snap positions
356     {
357       if((angle >= -orthoAngleRange) && (angle < orthoAngleRange)) // Swiping East
358       {
359         biasX = 0.0f, horizontal = FindDirection::Left;
360
361         // This guards against an error where no movement occurs, due to the flick finishing
362         // before the update-thread has advanced mScrollPostPosition past the the previous snap point.
363         positionSnap.x += 1.0f;
364       }
365       else if((angle >= M_PI - orthoAngleRange) || (angle < -M_PI + orthoAngleRange)) // Swiping West
366       {
367         biasX = 1.0f, horizontal = FindDirection::Right;
368
369         // This guards against an error where no movement occurs, due to the flick finishing
370         // before the update-thread has advanced mScrollPostPosition past the the previous snap point.
371         positionSnap.x -= 1.0f;
372       }
373     }
374   }
375
376   // Flick logic Y Axis
377
378   if(rulerY->IsEnabled() && lockAxis != LockAxis::LockVertical)
379   {
380     vertical = FindDirection::All;
381
382     if(speed2 > flickSpeedThreshold2 || // exceeds flick threshold
383        inAccessibilityPan)              // With AccessibilityPan its easier to move between snap positions
384     {
385       if((angle >= M_PI_2 - orthoAngleRange) && (angle < M_PI_2 + orthoAngleRange)) // Swiping South
386       {
387         biasY = 0.0f, vertical = FindDirection::Up;
388       }
389       else if((angle >= -M_PI_2 - orthoAngleRange) && (angle < -M_PI_2 + orthoAngleRange)) // Swiping North
390       {
391         biasY = 1.0f, vertical = FindDirection::Down;
392       }
393     }
394   }
395
396   // isFlick: Whether this gesture is a flick or not.
397   isFlick = (horizontal != FindDirection::All || vertical != FindDirection::All);
398   // isFreeFlick: Whether this gesture is a flick under free panning criteria.
399   isFreeFlick = velocity.LengthSquared() > (FREE_FLICK_SPEED_THRESHOLD * FREE_FLICK_SPEED_THRESHOLD);
400
401   if(isFlick || isFreeFlick)
402   {
403     positionDuration = Vector2::ONE * scrollView.GetScrollFlickDuration();
404     alphaFunction    = scrollView.GetScrollFlickAlphaFunction();
405   }
406
407   // Calculate next positionSnap ////////////////////////////////////////////////////////////
408
409   if(scrollView.GetActorAutoSnap())
410   {
411     Vector3 size = scrollView.Self().GetCurrentProperty<Vector3>(Actor::Property::SIZE);
412
413     Actor child = scrollView.FindClosestActorToPosition(Vector3(size.width * 0.5f, size.height * 0.5f, 0.0f), horizontal, vertical);
414
415     if(!child && isFlick)
416     {
417       // If we conducted a direction limited search and found no actor, then just snap to the closest actor.
418       child = scrollView.FindClosestActorToPosition(Vector3(size.width * 0.5f, size.height * 0.5f, 0.0f));
419     }
420
421     if(child)
422     {
423       Vector2 position = scrollView.Self().GetCurrentProperty<Vector2>(Toolkit::ScrollView::Property::SCROLL_POSITION);
424
425       // Get center-point of the Actor.
426       Vector3 childPosition = GetPositionOfAnchor(child, AnchorPoint::CENTER);
427
428       if(rulerX->IsEnabled())
429       {
430         positionSnap.x = position.x - childPosition.x + size.width * 0.5f;
431       }
432       if(rulerY->IsEnabled())
433       {
434         positionSnap.y = position.y - childPosition.y + size.height * 0.5f;
435       }
436     }
437   }
438
439   Vector2 startPosition = positionSnap;
440   positionSnap.x        = -rulerX->Snap(-positionSnap.x, biasX); // NOTE: X & Y rulers think in -ve coordinate system.
441   positionSnap.y        = -rulerY->Snap(-positionSnap.y, biasY); // That is scrolling RIGHT (e.g. 100.0, 0.0) means moving LEFT.
442
443   Dali::Toolkit::ClampState2D clamped;
444   Vector3                     size = scrollView.Self().GetCurrentProperty<Vector3>(Actor::Property::SIZE);
445   Vector2                     clampDelta(Vector2::ZERO);
446   ClampPosition(size, rulerX, rulerY, positionSnap, clamped);
447
448   if((rulerX->GetType() == Dali::Toolkit::Ruler::FREE || rulerY->GetType() == Dali::Toolkit::Ruler::FREE) &&
449      isFreeFlick && !scrollView.GetActorAutoSnap())
450   {
451     // Calculate target position based on velocity of flick.
452
453     // a = Deceleration (Set to diagonal stage length * friction coefficient)
454     // u = Initial Velocity (Flick velocity)
455     // v = 0 (Final Velocity)
456     // t = Time (Velocity / Deceleration)
457     Vector2 stageSize   = Stage::GetCurrent().GetSize();
458     float   stageLength = Vector3(stageSize.x, stageSize.y, 0.0f).Length();
459     float   a           = (stageLength * scrollView.GetFrictionCoefficient());
460     Vector3 u           = Vector3(velocity.x, velocity.y, 0.0f) * scrollView.GetFlickSpeedCoefficient();
461     float   speed       = u.Length();
462     u /= speed;
463
464     // TODO: Change this to a decay function. (faster you flick, the slower it should be)
465     speed = std::min(speed, stageLength * scrollView.GetMaxFlickSpeed());
466     u *= speed;
467     alphaFunction = ConstantDecelerationAlphaFunction;
468
469     float t = speed / a;
470
471     if(rulerX->IsEnabled() && rulerX->GetType() == Dali::Toolkit::Ruler::FREE)
472     {
473       positionSnap.x += t * u.x * 0.5f;
474     }
475
476     if(rulerY->IsEnabled() && rulerY->GetType() == Dali::Toolkit::Ruler::FREE)
477     {
478       positionSnap.y += t * u.y * 0.5f;
479     }
480
481     clampDelta = positionSnap;
482     ClampPosition(size, rulerX, rulerY, positionSnap, clamped);
483
484     if((positionSnap - startPosition).LengthSquared() > Math::MACHINE_EPSILON_0)
485     {
486       clampDelta -= positionSnap;
487       clampDelta.x = clampDelta.x > 0.0f ? std::min(clampDelta.x, maxOvershoot.x) : std::max(clampDelta.x, -maxOvershoot.x);
488       clampDelta.y = clampDelta.y > 0.0f ? std::min(clampDelta.y, maxOvershoot.y) : std::max(clampDelta.y, -maxOvershoot.y);
489     }
490     else
491     {
492       clampDelta = Vector2::ZERO;
493     }
494
495     // If Axis is Free and has velocity, then calculate time taken
496     // to reach target based on velocity in axis.
497     if(rulerX->IsEnabled() && rulerX->GetType() == Dali::Toolkit::Ruler::FREE)
498     {
499       float deltaX = fabsf(startPosition.x - positionSnap.x);
500
501       if(fabsf(u.x) > Math::MACHINE_EPSILON_1)
502       {
503         positionDuration.x = fabsf(deltaX / u.x);
504       }
505       else
506       {
507         positionDuration.x = 0;
508       }
509     }
510
511     if(rulerY->IsEnabled() && rulerY->GetType() == Dali::Toolkit::Ruler::FREE)
512     {
513       float deltaY = fabsf(startPosition.y - positionSnap.y);
514
515       if(fabsf(u.y) > Math::MACHINE_EPSILON_1)
516       {
517         positionDuration.y = fabsf(deltaY / u.y);
518       }
519       else
520       {
521         positionDuration.y = 0;
522       }
523     }
524   }
525
526   if(scrollView.IsOvershootEnabled())
527   {
528     // Scroll to the end of the overshoot only when overshoot is enabled.
529     positionSnap += clampDelta;
530   }
531 }
532
533 } // unnamed namespace
534
535 namespace Dali
536 {
537 namespace Toolkit
538 {
539 namespace Internal
540 {
541 namespace
542 {
543 BaseHandle Create()
544 {
545   return Toolkit::ScrollView::New();
546 }
547
548 // Setup properties, signals and actions using the type-registry.
549 DALI_TYPE_REGISTRATION_BEGIN(Toolkit::ScrollView, Toolkit::Scrollable, Create)
550
551 DALI_PROPERTY_REGISTRATION(Toolkit, ScrollView, "wrapEnabled", BOOLEAN, WRAP_ENABLED)
552 DALI_PROPERTY_REGISTRATION(Toolkit, ScrollView, "panningEnabled", BOOLEAN, PANNING_ENABLED)
553 DALI_PROPERTY_REGISTRATION(Toolkit, ScrollView, "axisAutoLockEnabled", BOOLEAN, AXIS_AUTO_LOCK_ENABLED)
554 DALI_PROPERTY_REGISTRATION(Toolkit, ScrollView, "wheelScrollDistanceStep", VECTOR2, WHEEL_SCROLL_DISTANCE_STEP)
555 DALI_PROPERTY_REGISTRATION(Toolkit, ScrollView, "scrollMode", MAP, SCROLL_MODE)
556
557 DALI_ANIMATABLE_PROPERTY_REGISTRATION(Toolkit, ScrollView, "scrollPosition", VECTOR2, SCROLL_POSITION)
558 DALI_ANIMATABLE_PROPERTY_REGISTRATION(Toolkit, ScrollView, "scrollPrePosition", VECTOR2, SCROLL_PRE_POSITION)
559 DALI_ANIMATABLE_PROPERTY_COMPONENT_REGISTRATION(Toolkit, ScrollView, "scrollPrePositionX", SCROLL_PRE_POSITION_X, SCROLL_PRE_POSITION, 0)
560 DALI_ANIMATABLE_PROPERTY_COMPONENT_REGISTRATION(Toolkit, ScrollView, "scrollPrePositionY", SCROLL_PRE_POSITION_Y, SCROLL_PRE_POSITION, 1)
561 DALI_ANIMATABLE_PROPERTY_REGISTRATION(Toolkit, ScrollView, "scrollPrePositionMax", VECTOR2, SCROLL_PRE_POSITION_MAX)
562 DALI_ANIMATABLE_PROPERTY_COMPONENT_REGISTRATION(Toolkit, ScrollView, "scrollPrePositionMaxX", SCROLL_PRE_POSITION_MAX_X, SCROLL_PRE_POSITION_MAX, 0)
563 DALI_ANIMATABLE_PROPERTY_COMPONENT_REGISTRATION(Toolkit, ScrollView, "scrollPrePositionMaxY", SCROLL_PRE_POSITION_MAX_Y, SCROLL_PRE_POSITION_MAX, 1)
564 DALI_ANIMATABLE_PROPERTY_REGISTRATION(Toolkit, ScrollView, "overshootX", FLOAT, OVERSHOOT_X)
565 DALI_ANIMATABLE_PROPERTY_REGISTRATION(Toolkit, ScrollView, "overshootY", FLOAT, OVERSHOOT_Y)
566 DALI_ANIMATABLE_PROPERTY_REGISTRATION(Toolkit, ScrollView, "scrollFinal", VECTOR2, SCROLL_FINAL)
567 DALI_ANIMATABLE_PROPERTY_COMPONENT_REGISTRATION(Toolkit, ScrollView, "scrollFinalX", SCROLL_FINAL_X, SCROLL_FINAL, 0)
568 DALI_ANIMATABLE_PROPERTY_COMPONENT_REGISTRATION(Toolkit, ScrollView, "scrollFinalY", SCROLL_FINAL_Y, SCROLL_FINAL, 1)
569 DALI_ANIMATABLE_PROPERTY_REGISTRATION(Toolkit, ScrollView, "wrap", BOOLEAN, WRAP)
570 DALI_ANIMATABLE_PROPERTY_REGISTRATION(Toolkit, ScrollView, "panning", BOOLEAN, PANNING)
571 DALI_ANIMATABLE_PROPERTY_REGISTRATION(Toolkit, ScrollView, "scrolling", BOOLEAN, SCROLLING)
572 DALI_ANIMATABLE_PROPERTY_REGISTRATION(Toolkit, ScrollView, "scrollDomainSize", VECTOR2, SCROLL_DOMAIN_SIZE)
573 DALI_ANIMATABLE_PROPERTY_COMPONENT_REGISTRATION(Toolkit, ScrollView, "scrollDomainSizeX", SCROLL_DOMAIN_SIZE_X, SCROLL_DOMAIN_SIZE, 0)
574 DALI_ANIMATABLE_PROPERTY_COMPONENT_REGISTRATION(Toolkit, ScrollView, "scrollDomainSizeY", SCROLL_DOMAIN_SIZE_Y, SCROLL_DOMAIN_SIZE, 1)
575 DALI_ANIMATABLE_PROPERTY_REGISTRATION(Toolkit, ScrollView, "scrollDomainOffset", VECTOR2, SCROLL_DOMAIN_OFFSET)
576 DALI_ANIMATABLE_PROPERTY_REGISTRATION(Toolkit, ScrollView, "scrollPositionDelta", VECTOR2, SCROLL_POSITION_DELTA)
577 DALI_ANIMATABLE_PROPERTY_REGISTRATION(Toolkit, ScrollView, "startPagePosition", VECTOR3, START_PAGE_POSITION)
578
579 DALI_SIGNAL_REGISTRATION(Toolkit, ScrollView, "valueChanged", SIGNAL_SNAP_STARTED)
580
581 DALI_TYPE_REGISTRATION_END()
582
583 } // namespace
584
585 ///////////////////////////////////////////////////////////////////////////////////////////////////
586 // ScrollView
587 ///////////////////////////////////////////////////////////////////////////////////////////////////
588
589 Dali::Toolkit::ScrollView ScrollView::New()
590 {
591   // Create the implementation
592   ScrollViewPtr scrollView(new ScrollView());
593
594   // Pass ownership to CustomActor via derived handle
595   Dali::Toolkit::ScrollView handle(*scrollView);
596
597   // Second-phase init of the implementation
598   // This can only be done after the CustomActor connection has been made...
599   scrollView->Initialize();
600
601   return handle;
602 }
603
604 ScrollView::ScrollView()
605 : ScrollBase(ControlBehaviour(DISABLE_STYLE_CHANGE_SIGNALS)), // Enable size negotiation
606   mTouchDownTime(0u),
607   mGestureStackDepth(0),
608   mScrollStateFlags(0),
609   mLockAxis(LockPossible),
610   mScrollUpdateDistance(DEFAULT_SCROLL_UPDATE_DISTANCE),
611   mMaxOvershoot(DEFAULT_MAX_OVERSHOOT, DEFAULT_MAX_OVERSHOOT),
612   mUserMaxOvershoot(DEFAULT_MAX_OVERSHOOT, DEFAULT_MAX_OVERSHOOT),
613   mSnapOvershootDuration(DEFAULT_SNAP_OVERSHOOT_DURATION),
614   mSnapOvershootAlphaFunction(AlphaFunction::EASE_OUT),
615   mSnapDuration(DEFAULT_SLOW_SNAP_ANIMATION_DURATION),
616   mSnapAlphaFunction(AlphaFunction::EASE_OUT),
617   mMinFlickDistance(DEFAULT_MIN_FLICK_DISTANCE),
618   mFlickSpeedThreshold(DEFAULT_MIN_FLICK_SPEED_THRESHOLD),
619   mFlickDuration(DEFAULT_FAST_SNAP_ANIMATION_DURATION),
620   mFlickAlphaFunction(AlphaFunction::EASE_OUT),
621   mAxisAutoLockGradient(DEFAULT_AXIS_AUTO_LOCK_GRADIENT),
622   mFrictionCoefficient(DEFAULT_FRICTION_COEFFICIENT),
623   mFlickSpeedCoefficient(DEFAULT_FLICK_SPEED_COEFFICIENT),
624   mMaxFlickSpeed(DEFAULT_MAX_FLICK_SPEED),
625   mWheelScrollDistanceStep(Vector2::ZERO),
626   mInAccessibilityPan(false),
627   mScrolling(false),
628   mScrollInterrupted(false),
629   mPanning(false),
630   mSensitive(true),
631   mTouchDownTimeoutReached(false),
632   mActorAutoSnapEnabled(false),
633   mAutoResizeContainerEnabled(false),
634   mWrapMode(false),
635   mAxisAutoLock(false),
636   mAlterChild(false),
637   mDefaultMaxOvershoot(true),
638   mCanScrollHorizontal(true),
639   mCanScrollVertical(true),
640   mTransientScrollBar(true)
641 {
642 }
643
644 void ScrollView::OnInitialize()
645 {
646   Actor self = Self();
647
648   // Internal Actor, used to hide actors from enumerations.
649   // Also actors added to Internal actor appear as overlays e.g. ScrollBar components.
650   mInternalActor = Actor::New();
651   self.Add(mInternalActor);
652
653   mInternalActor.SetProperty(Actor::Property::PARENT_ORIGIN, ParentOrigin::CENTER);
654   mInternalActor.SetProperty(Actor::Property::ANCHOR_POINT, AnchorPoint::CENTER);
655   mInternalActor.SetResizePolicy(ResizePolicy::FILL_TO_PARENT, Dimension::ALL_DIMENSIONS);
656
657   mAlterChild = true;
658
659   mScrollPostPosition = mScrollPrePosition = Vector2::ZERO;
660
661   mWheelScrollDistanceStep = Stage::GetCurrent().GetSize() * DEFAULT_WHEEL_SCROLL_DISTANCE_STEP_PROPORTION;
662
663   mGestureStackDepth = 0;
664
665   self.TouchedSignal().Connect(this, &ScrollView::OnTouch);
666   EnableGestureDetection(GestureType::Value(GestureType::PAN));
667
668   // By default we'll allow the user to freely drag the scroll view,
669   // while disabling the other rulers.
670   RulerPtr ruler = new DefaultRuler();
671   mRulerX        = ruler;
672   mRulerY        = ruler;
673
674   self.SetProperty(Toolkit::Scrollable::Property::CAN_SCROLL_VERTICAL, mCanScrollVertical);
675   self.SetProperty(Toolkit::Scrollable::Property::CAN_SCROLL_HORIZONTAL, mCanScrollHorizontal);
676
677   ScrollViewPropertyHandler::UpdatePropertyDomain(*this);
678   mConstraints.SetInternalConstraints(*this);
679
680   // Connect wheel event
681   self.WheelEventSignal().Connect(this, &ScrollView::OnWheelEvent);
682
683   DevelControl::SetAccessibilityConstructor(Self(), [](Dali::Actor actor) {
684     return std::unique_ptr<Dali::Accessibility::Accessible>(
685       new AccessibleImpl(actor, Dali::Accessibility::Role::SCROLL_PANE));
686   });
687 }
688
689 void ScrollView::OnSceneConnection(int depth)
690 {
691   DALI_LOG_SCROLL_STATE("[0x%X]", this);
692
693   if(mSensitive)
694   {
695     SetScrollSensitive(false);
696     SetScrollSensitive(true);
697   }
698
699   if(IsOvershootEnabled())
700   {
701     // try and make sure property notifications are set
702     EnableScrollOvershoot(true);
703   }
704
705   ScrollBase::OnSceneConnection(depth);
706 }
707
708 void ScrollView::OnSceneDisconnection()
709 {
710   DALI_LOG_SCROLL_STATE("[0x%X]", this);
711
712   StopAnimation();
713
714   ScrollBase::OnSceneDisconnection();
715 }
716
717 ScrollView::~ScrollView()
718 {
719   DALI_LOG_SCROLL_STATE("[0x%X]", this);
720 }
721
722 void ScrollView::ApplyEffect(Toolkit::ScrollViewEffect effect)
723 {
724   Dali::Toolkit::ScrollView self = Dali::Toolkit::ScrollView::DownCast(Self());
725
726   // Assertion check to ensure effect doesn't already exist in this scrollview
727   bool effectAlreadyExistsInScrollView(false);
728   for(ScrollViewEffectIter iter = mEffects.begin(); iter != mEffects.end(); ++iter)
729   {
730     if(*iter == effect)
731     {
732       effectAlreadyExistsInScrollView = true;
733       break;
734     }
735   }
736
737   DALI_ASSERT_ALWAYS(!effectAlreadyExistsInScrollView);
738
739   // add effect to effects list
740   mEffects.push_back(effect);
741
742   // invoke Attachment request to ScrollView first
743   GetImpl(effect).Attach(self);
744 }
745
746 void ScrollView::RemoveEffect(Toolkit::ScrollViewEffect effect)
747 {
748   Dali::Toolkit::ScrollView self = Dali::Toolkit::ScrollView::DownCast(Self());
749
750   // remove effect from effects list
751   bool effectExistedInScrollView(false);
752   for(ScrollViewEffectIter iter = mEffects.begin(); iter != mEffects.end(); ++iter)
753   {
754     if(*iter == effect)
755     {
756       mEffects.erase(iter);
757       effectExistedInScrollView = true;
758       break;
759     }
760   }
761
762   // Assertion check to ensure effect existed.
763   DALI_ASSERT_ALWAYS(effectExistedInScrollView);
764
765   // invoke Detachment request to ScrollView last
766   GetImpl(effect).Detach(self);
767 }
768
769 void ScrollView::RemoveAllEffects()
770 {
771   Dali::Toolkit::ScrollView self = Dali::Toolkit::ScrollView::DownCast(Self());
772
773   for(ScrollViewEffectIter effectIter = mEffects.begin(); effectIter != mEffects.end(); ++effectIter)
774   {
775     Toolkit::ScrollViewEffect effect = *effectIter;
776
777     // invoke Detachment request to ScrollView last
778     GetImpl(effect).Detach(self);
779   }
780
781   mEffects.clear();
782 }
783
784 void ScrollView::ApplyConstraintToChildren(Constraint constraint)
785 {
786   ApplyConstraintToBoundActors(constraint);
787 }
788
789 void ScrollView::RemoveConstraintsFromChildren()
790 {
791   RemoveConstraintsFromBoundActors();
792 }
793
794 void ScrollView::SetRulerX(RulerPtr ruler)
795 {
796   mRulerX = ruler;
797
798   ScrollViewPropertyHandler::UpdatePropertyDomain(*this);
799   mConstraints.UpdateMainInternalConstraint(*this);
800 }
801
802 void ScrollView::SetRulerY(RulerPtr ruler)
803 {
804   mRulerY = ruler;
805
806   ScrollViewPropertyHandler::UpdatePropertyDomain(*this);
807   mConstraints.UpdateMainInternalConstraint(*this);
808 }
809
810 void ScrollView::SetScrollSensitive(bool sensitive)
811 {
812   Actor              self = Self();
813   PanGestureDetector panGesture(GetPanGestureDetector());
814
815   DALI_LOG_SCROLL_STATE("[0x%X] sensitive: before:[%d] setting[%d]", this, int(mSensitive), int(sensitive));
816
817   if((!mSensitive) && (sensitive))
818   {
819     mSensitive = sensitive;
820     panGesture.Attach(self);
821   }
822   else if((mSensitive) && (!sensitive))
823   {
824     DALI_LOG_SCROLL_STATE("[0x%X] BEFORE: panning:[%d]", this, int(mPanning));
825
826     // while the scroll view is panning, the state needs to be reset.
827     if(mPanning)
828     {
829       PanGesture cancelGesture = DevelPanGesture::New(GestureState::CANCELLED);
830       OnPan(cancelGesture);
831     }
832
833     panGesture.Detach(self);
834     mSensitive = sensitive;
835
836     mGestureStackDepth = 0;
837     DALI_LOG_SCROLL_STATE("[0x%X] AFTER: panning:[%d]", this, int(mPanning));
838   }
839 }
840
841 void ScrollView::SetMaxOvershoot(float overshootX, float overshootY)
842 {
843   mMaxOvershoot.x      = overshootX;
844   mMaxOvershoot.y      = overshootY;
845   mUserMaxOvershoot    = mMaxOvershoot;
846   mDefaultMaxOvershoot = false;
847   mConstraints.UpdateMainInternalConstraint(*this);
848 }
849
850 bool ScrollView::GetActorAutoSnap()
851 {
852   return mActorAutoSnapEnabled;
853 }
854
855 void ScrollView::SetAutoResize(bool enable)
856 {
857   mAutoResizeContainerEnabled = enable;
858   // TODO: This needs a lot of issues to be addressed before working.
859 }
860
861 void ScrollView::SetWrapMode(bool enable)
862 {
863   mWrapMode = enable;
864   Self().SetProperty(Toolkit::ScrollView::Property::WRAP, enable);
865 }
866
867 void ScrollView::SetAxisAutoLock(bool enable)
868 {
869   mAxisAutoLock = enable;
870   mConstraints.UpdateMainInternalConstraint(*this);
871 }
872
873 void ScrollView::SetAxisAutoLockGradient(float gradient)
874 {
875   DALI_ASSERT_DEBUG(gradient >= 0.0f && gradient <= 1.0f);
876   mAxisAutoLockGradient = gradient;
877   mConstraints.UpdateMainInternalConstraint(*this);
878 }
879
880 void ScrollView::SetFrictionCoefficient(float friction)
881 {
882   DALI_ASSERT_DEBUG(friction > 0.0f);
883   mFrictionCoefficient = friction;
884 }
885
886 unsigned int ScrollView::GetCurrentPage() const
887 {
888   // in case animation is currently taking place.
889   Vector2 position = GetPropertyPosition();
890
891   Actor        self           = Self();
892   unsigned int page           = 0;
893   unsigned int pagesPerVolume = 1;
894   unsigned int volume         = 0;
895
896   // if rulerX is enabled, then get page count (columns)
897   page           = mRulerX->GetPageFromPosition(-position.x, mWrapMode);
898   volume         = mRulerY->GetPageFromPosition(-position.y, mWrapMode);
899   pagesPerVolume = mRulerX->GetTotalPages();
900
901   return volume * pagesPerVolume + page;
902 }
903
904 Vector2 ScrollView::GetCurrentScrollPosition() const
905 {
906   return -GetPropertyPosition();
907 }
908
909 void ScrollView::TransformTo(const Vector2& position,
910                              DirectionBias  horizontalBias,
911                              DirectionBias  verticalBias)
912 {
913   TransformTo(position, mSnapDuration, mSnapAlphaFunction, horizontalBias, verticalBias);
914 }
915
916 void ScrollView::TransformTo(const Vector2& position, float duration, AlphaFunction alpha, DirectionBias horizontalBias, DirectionBias verticalBias)
917 {
918   // If this is called while the timer is running, then cancel it
919   StopTouchDownTimer();
920
921   Actor self(Self());
922
923   // Guard against destruction during signal emission
924   // Note that Emit() methods are called indirectly e.g. from within ScrollView::AnimateTo()
925   Toolkit::ScrollView handle(GetOwner());
926
927   DALI_LOG_SCROLL_STATE("[0x%X] pos[%.2f,%.2f], duration[%.2f] bias[%d, %d]",
928                         this,
929                         position.x,
930                         position.y,
931                         duration,
932                         int(horizontalBias),
933                         int(verticalBias));
934
935   Vector2 currentScrollPosition = GetCurrentScrollPosition();
936   self.SetProperty(Toolkit::ScrollView::Property::START_PAGE_POSITION, Vector3(currentScrollPosition));
937
938   if(mScrolling) // are we interrupting a current scroll?
939   {
940     // set mScrolling to false, in case user has code that interrogates mScrolling Getter() in complete.
941     mScrolling = false;
942     DALI_LOG_SCROLL_STATE("[0x%X] mScrollCompletedSignal 1 [%.2f, %.2f]", this, currentScrollPosition.x, currentScrollPosition.y);
943     mScrollCompletedSignal.Emit(currentScrollPosition);
944   }
945
946   if(mPanning) // are we interrupting a current pan?
947   {
948     DALI_LOG_SCROLL_STATE("[0x%X] Interrupting Pan, set to false", this);
949     mPanning           = false;
950     mGestureStackDepth = 0;
951     self.SetProperty(Toolkit::ScrollView::Property::PANNING, false);
952
953     if(mConstraints.mScrollMainInternalPrePositionConstraint)
954     {
955       mConstraints.mScrollMainInternalPrePositionConstraint.Remove();
956     }
957   }
958
959   self.SetProperty(Toolkit::ScrollView::Property::SCROLLING, true);
960   mScrolling = true;
961
962   DALI_LOG_SCROLL_STATE("[0x%X] mScrollStartedSignal 1 [%.2f, %.2f]", this, currentScrollPosition.x, currentScrollPosition.y);
963   mScrollStartedSignal.Emit(currentScrollPosition);
964   bool animating = AnimateTo(-position,
965                              Vector2::ONE * duration,
966                              alpha,
967                              true,
968                              horizontalBias,
969                              verticalBias,
970                              SNAP);
971
972   if(!animating)
973   {
974     // if not animating, then this pan has completed right now.
975     self.SetProperty(Toolkit::ScrollView::Property::SCROLLING, false);
976     mScrolling = false;
977
978     // If we have no duration, then in the next update frame, we will be at the position specified as we just set.
979     // In this scenario, we cannot return the currentScrollPosition as this is out-of-date and should instead return the requested final position
980     Vector2 completedPosition(currentScrollPosition);
981     if(duration <= Math::MACHINE_EPSILON_10)
982     {
983       completedPosition = position;
984     }
985
986     DALI_LOG_SCROLL_STATE("[0x%X] mScrollCompletedSignal 2 [%.2f, %.2f]", this, completedPosition.x, completedPosition.y);
987     SetScrollUpdateNotification(false);
988     mScrollCompletedSignal.Emit(completedPosition);
989   }
990 }
991
992 void ScrollView::ScrollTo(const Vector2& position)
993 {
994   ScrollTo(position, mSnapDuration);
995 }
996
997 void ScrollView::ScrollTo(const Vector2& position, float duration)
998 {
999   ScrollTo(position, duration, DIRECTION_BIAS_NONE, DIRECTION_BIAS_NONE);
1000 }
1001
1002 void ScrollView::ScrollTo(const Vector2& position, float duration, AlphaFunction alpha)
1003 {
1004   ScrollTo(position, duration, alpha, DIRECTION_BIAS_NONE, DIRECTION_BIAS_NONE);
1005 }
1006
1007 void ScrollView::ScrollTo(const Vector2& position, float duration, DirectionBias horizontalBias, DirectionBias verticalBias)
1008 {
1009   ScrollTo(position, duration, mSnapAlphaFunction, horizontalBias, verticalBias);
1010 }
1011
1012 void ScrollView::ScrollTo(const Vector2& position, float duration, AlphaFunction alpha, DirectionBias horizontalBias, DirectionBias verticalBias)
1013 {
1014   DALI_LOG_SCROLL_STATE("[0x%X] position[%.2f, %.2f] duration[%.2f], bias[%d, %d]", this, position.x, position.y, duration, int(horizontalBias), int(verticalBias));
1015   TransformTo(position, duration, alpha, horizontalBias, verticalBias);
1016 }
1017
1018 void ScrollView::ScrollTo(unsigned int page)
1019 {
1020   ScrollTo(page, mSnapDuration);
1021 }
1022
1023 void ScrollView::ScrollTo(unsigned int page, float duration, DirectionBias bias)
1024 {
1025   Vector2      position;
1026   unsigned int volume;
1027   unsigned int libraries;
1028
1029   // The position to scroll to is continuous and linear
1030   // unless a domain has been enabled on the X axis.
1031   // or if WrapMode has been enabled.
1032   bool carryX = mRulerX->GetDomain().enabled | mWrapMode;
1033   bool carryY = mRulerY->GetDomain().enabled | mWrapMode;
1034
1035   position.x = mRulerX->GetPositionFromPage(page, volume, carryX);
1036   position.y = mRulerY->GetPositionFromPage(volume, libraries, carryY);
1037
1038   ScrollTo(position, duration, bias, bias);
1039 }
1040
1041 void ScrollView::ScrollTo(Actor& actor)
1042 {
1043   ScrollTo(actor, mSnapDuration);
1044 }
1045
1046 void ScrollView::ScrollTo(Actor& actor, float duration)
1047 {
1048   DALI_ASSERT_ALWAYS(actor.GetParent() == Self());
1049
1050   Actor   self        = Self();
1051   Vector3 size        = self.GetCurrentProperty<Vector3>(Actor::Property::SIZE);
1052   Vector3 position    = actor.GetCurrentProperty<Vector3>(Actor::Property::POSITION);
1053   Vector2 prePosition = GetPropertyPrePosition();
1054   position.GetVectorXY() -= prePosition;
1055
1056   ScrollTo(Vector2(position.x - size.width * 0.5f, position.y - size.height * 0.5f), duration);
1057 }
1058
1059 Actor ScrollView::FindClosestActor()
1060 {
1061   Actor   self = Self();
1062   Vector3 size = self.GetCurrentProperty<Vector3>(Actor::Property::SIZE);
1063
1064   return FindClosestActorToPosition(Vector3(size.width * 0.5f, size.height * 0.5f, 0.0f));
1065 }
1066
1067 Actor ScrollView::FindClosestActorToPosition(const Vector3& position, FindDirection dirX, FindDirection dirY, FindDirection dirZ)
1068 {
1069   return ::FindClosestActorToPosition(Self(), mInternalActor, position, dirX, dirY, dirZ);
1070 }
1071
1072 bool ScrollView::ScrollToSnapPoint()
1073 {
1074   DALI_LOG_SCROLL_STATE("[0x%X]", this);
1075   Vector2 stationaryVelocity = Vector2(0.0f, 0.0f);
1076   return SnapWithVelocity(stationaryVelocity);
1077 }
1078
1079 bool ScrollView::SnapWithVelocity(Vector2 velocity)
1080 {
1081   Vector2       positionSnap     = mScrollPrePosition;
1082   Vector2       positionDuration = Vector2::ONE * mSnapDuration;
1083   AlphaFunction alphaFunction    = mSnapAlphaFunction;
1084   bool          isFlick;
1085   bool          isFreeFlick;
1086
1087   ::SnapWithVelocity(*this, mRulerX, mRulerY, mLockAxis, velocity, mMaxOvershoot, positionSnap, positionDuration, alphaFunction, mInAccessibilityPan, isFlick, isFreeFlick);
1088
1089   bool animating = AnimateTo(positionSnap, positionDuration, alphaFunction, false, DIRECTION_BIAS_NONE, DIRECTION_BIAS_NONE, isFlick || isFreeFlick ? FLICK : SNAP);
1090
1091   return animating;
1092 }
1093
1094 void ScrollView::StopAnimation(void)
1095 {
1096   // Clear Snap animation if exists.
1097   StopAnimation(mInternalXAnimation);
1098   StopAnimation(mInternalYAnimation);
1099   mScrollStateFlags = 0;
1100   // remove scroll animation flags
1101   HandleStoppedAnimation();
1102 }
1103
1104 void ScrollView::StopAnimation(Animation& animation)
1105 {
1106   if(animation)
1107   {
1108     animation.Stop();
1109     animation.Reset();
1110   }
1111 }
1112
1113 bool ScrollView::AnimateTo(const Vector2& position, const Vector2& positionDuration, AlphaFunction alpha, bool findShortcuts, DirectionBias horizontalBias, DirectionBias verticalBias, SnapType snapType)
1114 {
1115   // Here we perform an animation on a number of properties (depending on which have changed)
1116   // The animation is applied to all ScrollBases
1117   Actor self            = Self();
1118   mScrollTargetPosition = position;
1119   float totalDuration   = 0.0f;
1120
1121   bool positionChanged = (mScrollTargetPosition != mScrollPostPosition);
1122
1123   if(positionChanged)
1124   {
1125     totalDuration = std::max(totalDuration, positionDuration.x);
1126     totalDuration = std::max(totalDuration, positionDuration.y);
1127   }
1128   else
1129   {
1130     // try to animate for a frame, on some occasions update will be changing scroll value while event side thinks it hasnt changed
1131     totalDuration   = 0.01f;
1132     positionChanged = true;
1133   }
1134
1135   StopAnimation();
1136
1137   // Position Delta ///////////////////////////////////////////////////////
1138   if(positionChanged)
1139   {
1140     mConstraints.UpdateMainInternalConstraint(*this);
1141     if(mWrapMode && findShortcuts)
1142     {
1143       // In Wrap Mode, the shortest distance is a little less intuitive...
1144       const RulerDomain rulerDomainX = mRulerX->GetDomain();
1145       const RulerDomain rulerDomainY = mRulerY->GetDomain();
1146
1147       if(mRulerX->IsEnabled())
1148       {
1149         float dir               = VectorInDomain(-mScrollPrePosition.x, -mScrollTargetPosition.x, rulerDomainX.min, rulerDomainX.max, horizontalBias);
1150         mScrollTargetPosition.x = mScrollPrePosition.x + -dir;
1151       }
1152
1153       if(mRulerY->IsEnabled())
1154       {
1155         float dir               = VectorInDomain(-mScrollPrePosition.y, -mScrollTargetPosition.y, rulerDomainY.min, rulerDomainY.max, verticalBias);
1156         mScrollTargetPosition.y = mScrollPrePosition.y + -dir;
1157       }
1158     }
1159
1160     // note we have two separate animations for X & Y, this deals with sliding diagonally and hitting
1161     // a horizonal/vertical wall.delay
1162     AnimateInternalXTo(mScrollTargetPosition.x, positionDuration.x, alpha);
1163     AnimateInternalYTo(mScrollTargetPosition.y, positionDuration.y, alpha);
1164
1165     if(!(mScrollStateFlags & SCROLL_ANIMATION_FLAGS))
1166     {
1167       DALI_LOG_SCROLL_STATE("[0x%X] Setting SCROLL_PRE_POSITION To[%.2f, %.2f]", this, mScrollTargetPosition.x, mScrollTargetPosition.y);
1168       self.SetProperty(Toolkit::ScrollView::Property::SCROLL_PRE_POSITION, mScrollTargetPosition);
1169       mScrollPrePosition  = mScrollTargetPosition;
1170       mScrollPostPosition = mScrollTargetPosition;
1171       WrapPosition(mScrollPostPosition);
1172     }
1173
1174     DALI_LOG_SCROLL_STATE("[0x%X] position-changed, mScrollTargetPosition[%.2f, %.2f], mScrollPrePosition[%.2f, %.2f], mScrollPostPosition[%.2f, %.2f]", this, mScrollTargetPosition.x, mScrollTargetPosition.y, mScrollPrePosition.x, mScrollPrePosition.y, mScrollPostPosition.x, mScrollPostPosition.y);
1175     DALI_LOG_SCROLL_STATE("[0x%X] SCROLL_PRE_POSITION[%.2f, %.2f], SCROLL_POSITION[%.2f, %.2f]", this, self.GetCurrentProperty(Toolkit::ScrollView::Property::SCROLL_PRE_POSITION).Get<Vector2>().x, self.GetCurrentProperty(Toolkit::ScrollView::Property::SCROLL_PRE_POSITION).Get<Vector2>().y, self.GetCurrentProperty(Toolkit::ScrollView::Property::SCROLL_POSITION).Get<Vector2>().x, self.GetCurrentProperty(Toolkit::ScrollView::Property::SCROLL_POSITION).Get<Vector2>().y);
1176   }
1177
1178   SetScrollUpdateNotification(true);
1179
1180   // Always send a snap event when AnimateTo is called.
1181   Toolkit::ScrollView::SnapEvent snapEvent;
1182   snapEvent.type     = snapType;
1183   snapEvent.position = -mScrollTargetPosition;
1184   snapEvent.duration = totalDuration;
1185
1186   DALI_LOG_SCROLL_STATE("[0x%X] mSnapStartedSignal [%.2f, %.2f]", this, snapEvent.position.x, snapEvent.position.y);
1187   mSnapStartedSignal.Emit(snapEvent);
1188
1189   return (mScrollStateFlags & SCROLL_ANIMATION_FLAGS) != 0;
1190 }
1191
1192 void ScrollView::EnableScrollOvershoot(bool enable)
1193 {
1194   if(enable)
1195   {
1196     if(!mOvershootIndicator)
1197     {
1198       mOvershootIndicator = ScrollOvershootIndicator::New();
1199     }
1200
1201     mOvershootIndicator->AttachToScrollable(*this);
1202   }
1203   else
1204   {
1205     mMaxOvershoot = mUserMaxOvershoot;
1206
1207     if(mOvershootIndicator)
1208     {
1209       mOvershootIndicator->DetachFromScrollable(*this);
1210     }
1211   }
1212
1213   mConstraints.UpdateMainInternalConstraint(*this);
1214 }
1215
1216 void ScrollView::AddOverlay(Actor actor)
1217 {
1218   actor.SetProperty(Actor::Property::DRAW_MODE, DrawMode::OVERLAY_2D);
1219   mInternalActor.Add(actor);
1220 }
1221
1222 void ScrollView::RemoveOverlay(Actor actor)
1223 {
1224   mInternalActor.Remove(actor);
1225 }
1226
1227 void ScrollView::SetOvershootSize(const Vector2& size)
1228 {
1229   mOvershootSize = size;
1230   if(IsOvershootEnabled() && mOvershootIndicator)
1231   {
1232     mOvershootIndicator->AttachToScrollable(*this);
1233   }
1234 }
1235
1236 void ScrollView::SetOvershootEffectColor(const Vector4& color)
1237 {
1238   mOvershootEffectColor = color;
1239   if(mOvershootIndicator)
1240   {
1241     mOvershootIndicator->SetOvershootEffectColor(color);
1242   }
1243 }
1244
1245 void ScrollView::SetScrollingDirection(Radian direction, Radian threshold)
1246 {
1247   PanGestureDetector panGesture(GetPanGestureDetector());
1248
1249   // First remove just in case we have some set, then add.
1250   panGesture.RemoveDirection(direction);
1251   panGesture.AddDirection(direction, threshold);
1252 }
1253
1254 void ScrollView::RemoveScrollingDirection(Radian direction)
1255 {
1256   PanGestureDetector panGesture(GetPanGestureDetector());
1257   panGesture.RemoveDirection(direction);
1258 }
1259
1260 Toolkit::ScrollView::SnapStartedSignalType& ScrollView::SnapStartedSignal()
1261 {
1262   return mSnapStartedSignal;
1263 }
1264
1265 bool ScrollView::AccessibleImpl::ScrollToChild(Actor child)
1266 {
1267   auto scrollView = Dali::Toolkit::ScrollView::DownCast(Self());
1268   if(Toolkit::GetImpl(scrollView).FindClosestActor() == child)
1269   {
1270     return false;
1271   }
1272
1273   // FIXME: ScrollTo does not work (snaps back to original position)
1274   scrollView.ScrollTo(child, scrollView.GetScrollFlickDuration());
1275   return true;
1276 }
1277
1278 void ScrollView::FindAndUnbindActor(Actor child)
1279 {
1280   UnbindActor(child);
1281 }
1282
1283 Vector2 ScrollView::GetPropertyPrePosition() const
1284 {
1285   Vector2 position = Self().GetCurrentProperty<Vector2>(Toolkit::ScrollView::Property::SCROLL_PRE_POSITION);
1286   WrapPosition(position);
1287   return position;
1288 }
1289
1290 Vector2 ScrollView::GetPropertyPosition() const
1291 {
1292   Vector2 position = Self().GetCurrentProperty<Vector2>(Toolkit::ScrollView::Property::SCROLL_POSITION);
1293   WrapPosition(position);
1294
1295   return position;
1296 }
1297
1298 void ScrollView::HandleStoppedAnimation()
1299 {
1300   SetScrollUpdateNotification(false);
1301 }
1302
1303 void ScrollView::HandleSnapAnimationFinished()
1304 {
1305   // Emit Signal that scrolling has completed.
1306   mScrolling = false;
1307   Actor self = Self();
1308   self.SetProperty(Toolkit::ScrollView::Property::SCROLLING, false);
1309
1310   Vector2 deltaPosition(mScrollPrePosition);
1311
1312   UpdateLocalScrollProperties();
1313   WrapPosition(mScrollPrePosition);
1314   DALI_LOG_SCROLL_STATE("[0x%X] Setting SCROLL_PRE_POSITION To[%.2f, %.2f]", this, mScrollPrePosition.x, mScrollPrePosition.y);
1315   self.SetProperty(Toolkit::ScrollView::Property::SCROLL_PRE_POSITION, mScrollPrePosition);
1316
1317   Vector2 currentScrollPosition = GetCurrentScrollPosition();
1318   DALI_LOG_SCROLL_STATE("[0x%X] mScrollCompletedSignal 3 current[%.2f, %.2f], mScrollTargetPosition[%.2f, %.2f]", this, currentScrollPosition.x, currentScrollPosition.y, -mScrollTargetPosition.x, -mScrollTargetPosition.y);
1319   mScrollCompletedSignal.Emit(currentScrollPosition);
1320
1321   mDomainOffset += deltaPosition - mScrollPostPosition;
1322   self.SetProperty(Toolkit::ScrollView::Property::SCROLL_DOMAIN_OFFSET, mDomainOffset);
1323   HandleStoppedAnimation();
1324 }
1325
1326 void ScrollView::SetScrollUpdateNotification(bool enabled)
1327 {
1328   Actor self = Self();
1329   if(mScrollXUpdateNotification)
1330   {
1331     // disconnect now to avoid a notification before removed from update thread
1332     mScrollXUpdateNotification.NotifySignal().Disconnect(this, &ScrollView::OnScrollUpdateNotification);
1333     self.RemovePropertyNotification(mScrollXUpdateNotification);
1334     mScrollXUpdateNotification.Reset();
1335   }
1336   if(enabled && !mScrollUpdatedSignal.Empty())
1337   {
1338     // Only set up the notification when the application has connected to the updated signal
1339     mScrollXUpdateNotification = self.AddPropertyNotification(Toolkit::ScrollView::Property::SCROLL_POSITION, 0, StepCondition(mScrollUpdateDistance, 0.0f));
1340     mScrollXUpdateNotification.NotifySignal().Connect(this, &ScrollView::OnScrollUpdateNotification);
1341   }
1342   if(mScrollYUpdateNotification)
1343   {
1344     // disconnect now to avoid a notification before removed from update thread
1345     mScrollYUpdateNotification.NotifySignal().Disconnect(this, &ScrollView::OnScrollUpdateNotification);
1346     self.RemovePropertyNotification(mScrollYUpdateNotification);
1347     mScrollYUpdateNotification.Reset();
1348   }
1349   if(enabled && !mScrollUpdatedSignal.Empty())
1350   {
1351     // Only set up the notification when the application has connected to the updated signal
1352     mScrollYUpdateNotification = self.AddPropertyNotification(Toolkit::ScrollView::Property::SCROLL_POSITION, 1, StepCondition(mScrollUpdateDistance, 0.0f));
1353     mScrollYUpdateNotification.NotifySignal().Connect(this, &ScrollView::OnScrollUpdateNotification);
1354   }
1355 }
1356
1357 void ScrollView::OnScrollUpdateNotification(Dali::PropertyNotification& source)
1358 {
1359   // Guard against destruction during signal emission
1360   Toolkit::ScrollView handle(GetOwner());
1361
1362   Vector2 currentScrollPosition = GetCurrentScrollPosition();
1363   mScrollUpdatedSignal.Emit(currentScrollPosition);
1364 }
1365
1366 bool ScrollView::DoConnectSignal(BaseObject* object, ConnectionTrackerInterface* tracker, const std::string& signalName, FunctorDelegate* functor)
1367 {
1368   Dali::BaseHandle handle(object);
1369
1370   bool                connected(true);
1371   Toolkit::ScrollView view = Toolkit::ScrollView::DownCast(handle);
1372
1373   if(0 == strcmp(signalName.c_str(), SIGNAL_SNAP_STARTED))
1374   {
1375     view.SnapStartedSignal().Connect(tracker, functor);
1376   }
1377   else
1378   {
1379     // signalName does not match any signal
1380     connected = false;
1381   }
1382
1383   return connected;
1384 }
1385
1386 void ScrollView::OnSizeAnimation(Animation& animation, const Vector3& targetSize)
1387 {
1388   // need to update domain properties for new size
1389   ScrollViewPropertyHandler::UpdatePropertyDomain(*this);
1390 }
1391
1392 void ScrollView::OnSizeSet(const Vector3& size)
1393 {
1394   // need to update domain properties for new size
1395   if(mDefaultMaxOvershoot)
1396   {
1397     mUserMaxOvershoot.x = size.x * 0.5f;
1398     mUserMaxOvershoot.y = size.y * 0.5f;
1399     if(!IsOvershootEnabled())
1400     {
1401       mMaxOvershoot = mUserMaxOvershoot;
1402     }
1403   }
1404   ScrollViewPropertyHandler::UpdatePropertyDomain(*this);
1405   mConstraints.UpdateMainInternalConstraint(*this);
1406   if(IsOvershootEnabled())
1407   {
1408     mOvershootIndicator->Reset();
1409   }
1410
1411   ScrollBase::OnSizeSet(size);
1412 }
1413
1414 void ScrollView::OnChildAdd(Actor& child)
1415 {
1416   ScrollBase::OnChildAdd(child);
1417
1418   Dali::Toolkit::ScrollBar scrollBar = Dali::Toolkit::ScrollBar::DownCast(child);
1419   if(scrollBar)
1420   {
1421     mScrollBar = scrollBar;
1422     scrollBar.SetProperty(Dali::Actor::Property::NAME, "ScrollBar");
1423
1424     mInternalActor.Add(scrollBar);
1425     if(scrollBar.GetScrollDirection() == Toolkit::ScrollBar::HORIZONTAL)
1426     {
1427       scrollBar.SetScrollPropertySource(Self(),
1428                                         Toolkit::ScrollView::Property::SCROLL_PRE_POSITION_X,
1429                                         Toolkit::Scrollable::Property::SCROLL_POSITION_MIN_X,
1430                                         Toolkit::ScrollView::Property::SCROLL_PRE_POSITION_MAX_X,
1431                                         Toolkit::ScrollView::Property::SCROLL_DOMAIN_SIZE_X);
1432     }
1433     else
1434     {
1435       scrollBar.SetScrollPropertySource(Self(),
1436                                         Toolkit::ScrollView::Property::SCROLL_PRE_POSITION_Y,
1437                                         Toolkit::Scrollable::Property::SCROLL_POSITION_MIN_Y,
1438                                         Toolkit::ScrollView::Property::SCROLL_PRE_POSITION_MAX_Y,
1439                                         Toolkit::ScrollView::Property::SCROLL_DOMAIN_SIZE_Y);
1440     }
1441
1442     if(mTransientScrollBar)
1443     {
1444       // Show the scroll-indicator for a brief period
1445       Property::Map emptyMap;
1446       scrollBar.DoAction("ShowTransientIndicator", emptyMap);
1447     }
1448   }
1449   else if(mAlterChild)
1450   {
1451     BindActor(child);
1452   }
1453 }
1454
1455 void ScrollView::OnChildRemove(Actor& child)
1456 {
1457   // TODO: Actor needs a RemoveConstraint method to take out an individual constraint.
1458   UnbindActor(child);
1459
1460   ScrollBase::OnChildRemove(child);
1461 }
1462
1463 void ScrollView::StartTouchDownTimer()
1464 {
1465   if(!mTouchDownTimer)
1466   {
1467     mTouchDownTimer = Timer::New(TOUCH_DOWN_TIMER_INTERVAL);
1468     mTouchDownTimer.TickSignal().Connect(this, &ScrollView::OnTouchDownTimeout);
1469   }
1470
1471   mTouchDownTimer.Start();
1472 }
1473
1474 void ScrollView::StopTouchDownTimer()
1475 {
1476   if(mTouchDownTimer)
1477   {
1478     mTouchDownTimer.Stop();
1479   }
1480 }
1481
1482 bool ScrollView::OnTouchDownTimeout()
1483 {
1484   DALI_LOG_SCROLL_STATE("[0x%X]", this);
1485
1486   mTouchDownTimeoutReached = true;
1487
1488   unsigned int currentScrollStateFlags(mScrollStateFlags); // Cleared in StopAnimation so keep local copy for comparison
1489   if(currentScrollStateFlags & (SCROLL_ANIMATION_FLAGS | SNAP_ANIMATION_FLAGS))
1490   {
1491     DALI_LOG_SCROLL_STATE("[0x%X] Scrolling Or snapping flags set, stopping animation", this);
1492
1493     StopAnimation();
1494     if(currentScrollStateFlags & SCROLL_ANIMATION_FLAGS)
1495     {
1496       DALI_LOG_SCROLL_STATE("[0x%X] Scrolling flags set, emitting signal", this);
1497
1498       mScrollInterrupted = true;
1499       // reset domain offset as scrolling from original plane.
1500       mDomainOffset = Vector2::ZERO;
1501       Self().SetProperty(Toolkit::ScrollView::Property::SCROLL_DOMAIN_OFFSET, Vector2::ZERO);
1502
1503       UpdateLocalScrollProperties();
1504       Vector2 currentScrollPosition = GetCurrentScrollPosition();
1505       DALI_LOG_SCROLL_STATE("[0x%X] mScrollCompletedSignal 4 [%.2f, %.2f]", this, currentScrollPosition.x, currentScrollPosition.y);
1506       mScrollCompletedSignal.Emit(currentScrollPosition);
1507     }
1508   }
1509
1510   return false;
1511 }
1512
1513 bool ScrollView::OnTouch(Actor actor, const TouchEvent& touch)
1514 {
1515   if(!mSensitive)
1516   {
1517     DALI_LOG_SCROLL_STATE("[0x%X], Not Sensitive, ignoring", this);
1518
1519     // Ignore this touch event, if scrollview is insensitive.
1520     return false;
1521   }
1522
1523   // Ignore events with multiple-touch points
1524   if(touch.GetPointCount() != 1)
1525   {
1526     DALI_LOG_SCROLL_STATE("[0x%X], multiple touch, ignoring", this);
1527
1528     return false;
1529   }
1530
1531   const PointState::Type pointState = touch.GetState(0);
1532   if(pointState == PointState::DOWN)
1533   {
1534     DALI_LOG_SCROLL_STATE("[0x%X] Down", this);
1535
1536     if(mGestureStackDepth == 0)
1537     {
1538       mTouchDownTime = touch.GetTime();
1539
1540       // This allows time for a pan-gesture to start, to avoid breaking snap-animation behavior with fast flicks.
1541       // If touch-down does not become a pan (after timeout interval), then snap-animation can be interrupted.
1542       mTouchDownTimeoutReached = false;
1543       mScrollInterrupted       = false;
1544       StartTouchDownTimer();
1545     }
1546   }
1547   else if((pointState == PointState::UP) ||
1548           ((pointState == PointState::INTERRUPTED) && (touch.GetHitActor(0) == Self())))
1549   {
1550     DALI_LOG_SCROLL_STATE("[0x%X] %s", this, ((pointState == PointState::UP) ? "Up" : "Interrupted"));
1551
1552     StopTouchDownTimer();
1553
1554     // if the user touches and releases without enough movement to go
1555     // into a gesture state, then we should snap to nearest point.
1556     // otherwise our scroll could be stopped (interrupted) half way through an animation.
1557     if(mGestureStackDepth == 0 && mTouchDownTimeoutReached)
1558     {
1559       if((pointState == PointState::INTERRUPTED) ||
1560          ((touch.GetTime() - mTouchDownTime) >= MINIMUM_TIME_BETWEEN_DOWN_AND_UP_FOR_RESET))
1561       {
1562         // Reset the velocity only if down was received a while ago
1563         mLastVelocity = Vector2(0.0f, 0.0f);
1564       }
1565
1566       UpdateLocalScrollProperties();
1567       // Only finish the transform if scrolling was interrupted on down or if we are scrolling
1568       if(mScrollInterrupted || mScrolling)
1569       {
1570         DALI_LOG_SCROLL_STATE("[0x%X] Calling FinishTransform", this);
1571
1572         FinishTransform();
1573       }
1574     }
1575     mTouchDownTimeoutReached = false;
1576     mScrollInterrupted       = false;
1577   }
1578
1579   return false;
1580 }
1581
1582 bool ScrollView::OnWheelEvent(Actor actor, const WheelEvent& event)
1583 {
1584   if(!mSensitive)
1585   {
1586     // Ignore this wheel event, if scrollview is insensitive.
1587     return false;
1588   }
1589
1590   Vector2 targetScrollPosition = GetPropertyPosition();
1591
1592   if(mRulerX->IsEnabled() && !mRulerY->IsEnabled())
1593   {
1594     // If only the ruler in the X axis is enabled, scroll in the X axis.
1595     if(mRulerX->GetType() == Ruler::FREE)
1596     {
1597       // Free panning mode
1598       targetScrollPosition.x += event.GetDelta() * mWheelScrollDistanceStep.x;
1599       ClampPosition(targetScrollPosition);
1600       ScrollTo(-targetScrollPosition);
1601     }
1602     else if(!mScrolling)
1603     {
1604       // Snap mode, only respond to the event when the previous snap animation is finished.
1605       ScrollTo(GetCurrentPage() - event.GetDelta());
1606     }
1607   }
1608   else
1609   {
1610     // If the ruler in the Y axis is enabled, scroll in the Y axis.
1611     if(mRulerY->GetType() == Ruler::FREE)
1612     {
1613       // Free panning mode
1614       targetScrollPosition.y += event.GetDelta() * mWheelScrollDistanceStep.y;
1615       ClampPosition(targetScrollPosition);
1616       ScrollTo(-targetScrollPosition);
1617     }
1618     else if(!mScrolling)
1619     {
1620       // Snap mode, only respond to the event when the previous snap animation is finished.
1621       ScrollTo(GetCurrentPage() - event.GetDelta() * mRulerX->GetTotalPages());
1622     }
1623   }
1624
1625   return true;
1626 }
1627
1628 void ScrollView::ResetScrolling()
1629 {
1630   Actor self = Self();
1631   self.GetCurrentProperty(Toolkit::ScrollView::Property::SCROLL_POSITION).Get(mScrollPostPosition);
1632   mScrollPrePosition = mScrollPostPosition;
1633   DALI_LOG_SCROLL_STATE("[0x%X] Setting SCROLL_PRE_POSITION To[%.2f, %.2f]", this, mScrollPostPosition.x, mScrollPostPosition.y);
1634   self.SetProperty(Toolkit::ScrollView::Property::SCROLL_PRE_POSITION, mScrollPostPosition);
1635 }
1636
1637 void ScrollView::UpdateLocalScrollProperties()
1638 {
1639   Actor self = Self();
1640   self.GetCurrentProperty(Toolkit::ScrollView::Property::SCROLL_PRE_POSITION).Get(mScrollPrePosition);
1641   self.GetCurrentProperty(Toolkit::ScrollView::Property::SCROLL_POSITION).Get(mScrollPostPosition);
1642 }
1643
1644 // private functions
1645
1646 void ScrollView::PreAnimatedScrollSetup()
1647 {
1648   // SCROLL_PRE_POSITION is our unclamped property with wrapping
1649   // SCROLL_POSITION is our final scroll position after clamping
1650
1651   Actor self = Self();
1652
1653   Vector2 deltaPosition(mScrollPostPosition);
1654   WrapPosition(mScrollPostPosition);
1655   mDomainOffset += deltaPosition - mScrollPostPosition;
1656   Self().SetProperty(Toolkit::ScrollView::Property::SCROLL_DOMAIN_OFFSET, mDomainOffset);
1657
1658   if(mScrollStateFlags & SCROLL_X_STATE_MASK)
1659   {
1660     // already performing animation on internal x position
1661     StopAnimation(mInternalXAnimation);
1662   }
1663
1664   if(mScrollStateFlags & SCROLL_Y_STATE_MASK)
1665   {
1666     // already performing animation on internal y position
1667     StopAnimation(mInternalYAnimation);
1668   }
1669
1670   mScrollStateFlags = 0;
1671
1672   // Update Actor position with this wrapped value.
1673 }
1674
1675 void ScrollView::FinaliseAnimatedScroll()
1676 {
1677   // TODO - common animation finishing code in here
1678 }
1679
1680 void ScrollView::AnimateInternalXTo(float position, float duration, AlphaFunction alpha)
1681 {
1682   StopAnimation(mInternalXAnimation);
1683
1684   if(duration > Math::MACHINE_EPSILON_10)
1685   {
1686     Actor self = Self();
1687     DALI_LOG_SCROLL_STATE("[0x%X], Animating from[%.2f] to[%.2f]", this, self.GetCurrentProperty(Toolkit::ScrollView::Property::SCROLL_PRE_POSITION).Get<Vector2>().x, position);
1688     mInternalXAnimation = Animation::New(duration);
1689     DALI_LOG_SCROLL_STATE("[0x%X], mInternalXAnimation[0x%X]", this, mInternalXAnimation.GetObjectPtr());
1690     mInternalXAnimation.FinishedSignal().Connect(this, &ScrollView::OnScrollAnimationFinished);
1691     mInternalXAnimation.AnimateTo(Property(self, Toolkit::ScrollView::Property::SCROLL_PRE_POSITION, 0), position, alpha, TimePeriod(duration));
1692     mInternalXAnimation.Play();
1693
1694     // erase current state flags
1695     mScrollStateFlags &= ~SCROLL_X_STATE_MASK;
1696     // add internal animation state flag
1697     mScrollStateFlags |= AnimatingInternalX;
1698   }
1699 }
1700
1701 void ScrollView::AnimateInternalYTo(float position, float duration, AlphaFunction alpha)
1702 {
1703   StopAnimation(mInternalYAnimation);
1704
1705   if(duration > Math::MACHINE_EPSILON_10)
1706   {
1707     Actor self = Self();
1708     DALI_LOG_SCROLL_STATE("[0x%X], Animating from[%.2f] to[%.2f]", this, self.GetCurrentProperty(Toolkit::ScrollView::Property::SCROLL_PRE_POSITION).Get<Vector2>().y, position);
1709     mInternalYAnimation = Animation::New(duration);
1710     DALI_LOG_SCROLL_STATE("[0x%X], mInternalYAnimation[0x%X]", this, mInternalYAnimation.GetObjectPtr());
1711     mInternalYAnimation.FinishedSignal().Connect(this, &ScrollView::OnScrollAnimationFinished);
1712     mInternalYAnimation.AnimateTo(Property(self, Toolkit::ScrollView::Property::SCROLL_PRE_POSITION, 1), position, alpha, TimePeriod(duration));
1713     mInternalYAnimation.Play();
1714
1715     // erase current state flags
1716     mScrollStateFlags &= ~SCROLL_Y_STATE_MASK;
1717     // add internal animation state flag
1718     mScrollStateFlags |= AnimatingInternalY;
1719   }
1720 }
1721
1722 void ScrollView::OnScrollAnimationFinished(Animation& source)
1723 {
1724   // Guard against destruction during signal emission
1725   // Note that ScrollCompletedSignal is emitted from HandleSnapAnimationFinished()
1726   Toolkit::ScrollView handle(GetOwner());
1727
1728   bool scrollingFinished = false;
1729
1730   // update our local scroll positions
1731   UpdateLocalScrollProperties();
1732
1733   if(source == mInternalXAnimation)
1734   {
1735     DALI_LOG_SCROLL_STATE("[0x%X] mInternalXAnimation[0x%X], expected[%.2f], actual[%.2f], post[%.2f]", this, mInternalXAnimation.GetObjectPtr(), mScrollTargetPosition.x, Self().GetCurrentProperty(SCROLL_PRE_POSITION).Get<Vector2>().x, mScrollPostPosition.x);
1736
1737     if(!(mScrollStateFlags & AnimatingInternalY))
1738     {
1739       scrollingFinished = true;
1740     }
1741     mInternalXAnimation.Reset();
1742     // wrap pre scroll x position and set it
1743     if(mWrapMode)
1744     {
1745       const RulerDomain rulerDomain = mRulerX->GetDomain();
1746       mScrollPrePosition.x          = -WrapInDomain(-mScrollPrePosition.x, rulerDomain.min, rulerDomain.max);
1747       DALI_LOG_SCROLL_STATE("[0x%X] Setting SCROLL_PRE_POSITION To[%.2f, %.2f]", this, mScrollPrePosition.x, mScrollPrePosition.y);
1748       handle.SetProperty(Toolkit::ScrollView::Property::SCROLL_PRE_POSITION, mScrollPrePosition);
1749     }
1750     SnapInternalXTo(mScrollPostPosition.x);
1751   }
1752
1753   if(source == mInternalYAnimation)
1754   {
1755     DALI_LOG_SCROLL_STATE("[0x%X] mInternalYAnimation[0x%X], expected[%.2f], actual[%.2f], post[%.2f]", this, mInternalYAnimation.GetObjectPtr(), mScrollTargetPosition.y, DevelHandle::GetProperty(Self(), SCROLL_PRE_POSITION).Get<Vector2>().y, mScrollPostPosition.y);
1756
1757     if(!(mScrollStateFlags & AnimatingInternalX))
1758     {
1759       scrollingFinished = true;
1760     }
1761     mInternalYAnimation.Reset();
1762     if(mWrapMode)
1763     {
1764       // wrap pre scroll y position and set it
1765       const RulerDomain rulerDomain = mRulerY->GetDomain();
1766       mScrollPrePosition.y          = -WrapInDomain(-mScrollPrePosition.y, rulerDomain.min, rulerDomain.max);
1767       DALI_LOG_SCROLL_STATE("[0x%X] Setting SCROLL_PRE_POSITION To[%.2f, %.2f]", this, mScrollPrePosition.x, mScrollPrePosition.y);
1768       handle.SetProperty(Toolkit::ScrollView::Property::SCROLL_PRE_POSITION, mScrollPrePosition);
1769     }
1770     SnapInternalYTo(mScrollPostPosition.y);
1771   }
1772
1773   DALI_LOG_SCROLL_STATE("[0x%X] scrollingFinished[%d] Animation[0x%X]", this, scrollingFinished, source.GetObjectPtr());
1774
1775   if(scrollingFinished)
1776   {
1777     HandleSnapAnimationFinished();
1778   }
1779 }
1780
1781 void ScrollView::OnSnapInternalPositionFinished(Animation& source)
1782 {
1783   Actor self = Self();
1784   UpdateLocalScrollProperties();
1785   if(source == mInternalXAnimation)
1786   {
1787     DALI_LOG_SCROLL_STATE("[0x%X] Finished X PostPosition Animation", this);
1788
1789     // clear internal x animation flags
1790     mScrollStateFlags &= ~SCROLL_X_STATE_MASK;
1791     mInternalXAnimation.Reset();
1792     WrapPosition(mScrollPrePosition);
1793   }
1794   if(source == mInternalYAnimation)
1795   {
1796     DALI_LOG_SCROLL_STATE("[0x%X] Finished Y PostPosition Animation", this);
1797
1798     mScrollStateFlags &= ~SCROLL_Y_STATE_MASK;
1799     mInternalYAnimation.Reset();
1800     WrapPosition(mScrollPrePosition);
1801   }
1802 }
1803
1804 void ScrollView::SnapInternalXTo(float position)
1805 {
1806   Actor self = Self();
1807
1808   StopAnimation(mInternalXAnimation);
1809
1810   // erase current state flags
1811   mScrollStateFlags &= ~SCROLL_X_STATE_MASK;
1812
1813   // if internal x not equal to inputed parameter, animate it
1814   float duration = std::min(fabsf((position - mScrollPrePosition.x) / mMaxOvershoot.x) * mSnapOvershootDuration, mSnapOvershootDuration);
1815   DALI_LOG_SCROLL_STATE("[0x%X] duration[%.2f]", this, duration);
1816   if(duration > Math::MACHINE_EPSILON_1)
1817   {
1818     DALI_LOG_SCROLL_STATE("[0x%X] Starting X Snap Animation to[%.2f]", this, position);
1819
1820     mInternalXAnimation = Animation::New(duration);
1821     mInternalXAnimation.FinishedSignal().Connect(this, &ScrollView::OnSnapInternalPositionFinished);
1822     mInternalXAnimation.AnimateTo(Property(self, Toolkit::ScrollView::Property::SCROLL_PRE_POSITION, 0), position);
1823     mInternalXAnimation.Play();
1824
1825     // add internal animation state flag
1826     mScrollStateFlags |= SnappingInternalX;
1827   }
1828 }
1829
1830 void ScrollView::SnapInternalYTo(float position)
1831 {
1832   Actor self = Self();
1833
1834   StopAnimation(mInternalYAnimation);
1835
1836   // erase current state flags
1837   mScrollStateFlags &= ~SCROLL_Y_STATE_MASK;
1838
1839   // if internal y not equal to inputed parameter, animate it
1840   float duration = std::min(fabsf((position - mScrollPrePosition.y) / mMaxOvershoot.y) * mSnapOvershootDuration, mSnapOvershootDuration);
1841   DALI_LOG_SCROLL_STATE("[0x%X] duration[%.2f]", this, duration);
1842   if(duration > Math::MACHINE_EPSILON_1)
1843   {
1844     DALI_LOG_SCROLL_STATE("[0x%X] Starting Y Snap Animation to[%.2f]", this, position);
1845
1846     mInternalYAnimation = Animation::New(duration);
1847     mInternalYAnimation.FinishedSignal().Connect(this, &ScrollView::OnSnapInternalPositionFinished);
1848     mInternalYAnimation.AnimateTo(Property(self, Toolkit::ScrollView::Property::SCROLL_PRE_POSITION, 1), position);
1849     mInternalYAnimation.Play();
1850
1851     // add internal animation state flag
1852     mScrollStateFlags |= SnappingInternalY;
1853   }
1854 }
1855
1856 void ScrollView::GestureStarted()
1857 {
1858   // we handle the first gesture.
1859   // if we're currently doing a gesture and receive another
1860   // we continue and combine the effects of the gesture instead of reseting.
1861   if(mGestureStackDepth++ == 0)
1862   {
1863     Actor self = Self();
1864     StopTouchDownTimer();
1865     StopAnimation();
1866     mPanDelta     = Vector2::ZERO;
1867     mLastVelocity = Vector2::ZERO;
1868     if(!mScrolling)
1869     {
1870       mLockAxis = LockPossible;
1871     }
1872
1873     if(mScrollStateFlags & SCROLL_X_STATE_MASK)
1874     {
1875       StopAnimation(mInternalXAnimation);
1876     }
1877     if(mScrollStateFlags & SCROLL_Y_STATE_MASK)
1878     {
1879       StopAnimation(mInternalYAnimation);
1880     }
1881     mScrollStateFlags = 0;
1882
1883     if(mScrolling) // are we interrupting a current scroll?
1884     {
1885       // set mScrolling to false, in case user has code that interrogates mScrolling Getter() in complete.
1886       mScrolling = false;
1887       // send negative scroll position since scroll internal scroll position works as an offset for actors,
1888       // give applications the position within the domain from the scroll view's anchor position
1889       DALI_LOG_SCROLL_STATE("[0x%X] mScrollCompletedSignal 5 [%.2f, %.2f]", this, -mScrollPostPosition.x, -mScrollPostPosition.y);
1890       mScrollCompletedSignal.Emit(-mScrollPostPosition);
1891     }
1892   }
1893 }
1894
1895 void ScrollView::GestureContinuing(const Vector2& panDelta)
1896 {
1897   mPanDelta.x += panDelta.x;
1898   mPanDelta.y += panDelta.y;
1899
1900   // Save the velocity, there is a bug in PanGesture
1901   // Whereby the GestureState::FINISHED's velocity is either:
1902   // NaN (due to time delta of zero between the last two events)
1903   // or 0 (due to position being the same between the last two events)
1904
1905   // Axis Auto Lock - locks the panning to the horizontal or vertical axis if the pan
1906   // appears mostly horizontal or mostly vertical respectively.
1907   if(mAxisAutoLock)
1908   {
1909     mLockAxis = GetLockAxis(mPanDelta, mLockAxis, mAxisAutoLockGradient);
1910   } // end if mAxisAutoLock
1911 }
1912
1913 // TODO: Upgrade to use a more powerful gesture detector (one that supports multiple touches on pan - so works as pan and flick gesture)
1914 // BUG: GestureState::FINISHED doesn't always return velocity on release (due to
1915 // timeDelta between last two events being 0 sometimes, or posiiton being the same)
1916 void ScrollView::OnPan(const PanGesture& gesture)
1917 {
1918   // Guard against destruction during signal emission
1919   // Note that Emit() methods are called indirectly e.g. from within ScrollView::OnGestureEx()
1920   Actor self(Self());
1921
1922   if(!mSensitive)
1923   {
1924     DALI_LOG_SCROLL_STATE("[0x%X] Pan Ignored, Insensitive", this);
1925
1926     // If another callback on the same original signal disables sensitivity,
1927     // this callback will still be called, so we must suppress it.
1928     return;
1929   }
1930
1931   // translate Gesture input to get useful data...
1932   switch(gesture.GetState())
1933   {
1934     case GestureState::STARTED:
1935     {
1936       DALI_LOG_SCROLL_STATE("[0x%X] Pan Started", this);
1937       const Vector2& position = gesture.GetPosition();
1938       mPanStartPosition       = position - gesture.GetDisplacement();
1939       UpdateLocalScrollProperties();
1940       GestureStarted();
1941       mPanning = true;
1942       self.SetProperty(Toolkit::ScrollView::Property::PANNING, true);
1943       self.SetProperty(Toolkit::ScrollView::Property::START_PAGE_POSITION, Vector3(position.x, position.y, 0.0f));
1944
1945       mConstraints.UpdateMainInternalConstraint(*this);
1946       Toolkit::ScrollBar scrollBar = mScrollBar.GetHandle();
1947       if(scrollBar && mTransientScrollBar)
1948       {
1949         Vector3                     size         = Self().GetCurrentProperty<Vector3>(Actor::Property::SIZE);
1950         const Toolkit::RulerDomain& rulerDomainX = mRulerX->GetDomain();
1951         const Toolkit::RulerDomain& rulerDomainY = mRulerY->GetDomain();
1952
1953         if((rulerDomainX.max > size.width) || (rulerDomainY.max > size.height))
1954         {
1955           scrollBar.ShowIndicator();
1956         }
1957       }
1958       break;
1959     }
1960
1961     case GestureState::CONTINUING:
1962     {
1963       if(mPanning)
1964       {
1965         DALI_LOG_SCROLL_STATE("[0x%X] Pan Continuing", this);
1966         GestureContinuing(gesture.GetScreenDisplacement());
1967       }
1968       else
1969       {
1970         // If we do not think we are panning, then we should not do anything here
1971         return;
1972       }
1973       break;
1974     }
1975
1976     case GestureState::FINISHED:
1977     case GestureState::CANCELLED:
1978     {
1979       if(mPanning)
1980       {
1981         DALI_LOG_SCROLL_STATE("[0x%X] Pan %s", this, ((gesture.GetState() == GestureState::FINISHED) ? "Finished" : "Cancelled"));
1982
1983         UpdateLocalScrollProperties();
1984         mLastVelocity = gesture.GetVelocity();
1985         mPanning      = false;
1986         self.SetProperty(Toolkit::ScrollView::Property::PANNING, false);
1987
1988         if(mConstraints.mScrollMainInternalPrePositionConstraint)
1989         {
1990           mConstraints.mScrollMainInternalPrePositionConstraint.Remove();
1991         }
1992
1993         Toolkit::ScrollBar scrollBar = mScrollBar.GetHandle();
1994         if(scrollBar && mTransientScrollBar)
1995         {
1996           scrollBar.HideIndicator();
1997         }
1998       }
1999       else
2000       {
2001         // If we do not think we are panning, then we should not do anything here
2002         return;
2003       }
2004       break;
2005     }
2006
2007     case GestureState::POSSIBLE:
2008     case GestureState::CLEAR:
2009     {
2010       // Nothing to do, not needed.
2011       break;
2012     }
2013
2014   } // end switch(gesture.state)
2015
2016   OnGestureEx(gesture.GetState());
2017 }
2018
2019 void ScrollView::OnGestureEx(GestureState state)
2020 {
2021   // call necessary signals for application developer
2022
2023   if(state == GestureState::STARTED)
2024   {
2025     Vector2 currentScrollPosition = GetCurrentScrollPosition();
2026     Self().SetProperty(Toolkit::ScrollView::Property::SCROLLING, true);
2027     mScrolling = true;
2028     DALI_LOG_SCROLL_STATE("[0x%X] mScrollStartedSignal 2 [%.2f, %.2f]", this, currentScrollPosition.x, currentScrollPosition.y);
2029     mScrollStartedSignal.Emit(currentScrollPosition);
2030   }
2031   else if((state == GestureState::FINISHED) ||
2032           (state == GestureState::CANCELLED)) // Finished/default
2033   {
2034     // when all the gestures have finished, we finish the transform.
2035     // so if a user decides to pan (1 gesture), and then pan+zoom (2 gestures)
2036     // then stop panning (back to 1 gesture), and then stop zooming (0 gestures).
2037     // this is the point we end, and perform necessary snapping.
2038     mGestureStackDepth--;
2039     if(mGestureStackDepth == 0)
2040     {
2041       // no flick if we have not exceeded min flick distance
2042       if((fabsf(mPanDelta.x) < mMinFlickDistance.x) && (fabsf(mPanDelta.y) < mMinFlickDistance.y))
2043       {
2044         // reset flick velocity
2045         mLastVelocity = Vector2::ZERO;
2046       }
2047       FinishTransform();
2048     }
2049     else
2050     {
2051       DALI_LOG_SCROLL_STATE("[0x%X] mGestureStackDepth[%d]", this, mGestureStackDepth);
2052     }
2053   }
2054 }
2055
2056 void ScrollView::FinishTransform()
2057 {
2058   // at this stage internal x and x scroll position should have followed prescroll position exactly
2059   Actor self = Self();
2060
2061   PreAnimatedScrollSetup();
2062
2063   // convert pixels/millisecond to pixels per second
2064   bool animating = SnapWithVelocity(mLastVelocity * 1000.0f);
2065
2066   if(!animating)
2067   {
2068     // if not animating, then this pan has completed right now.
2069     SetScrollUpdateNotification(false);
2070     mScrolling = false;
2071     Self().SetProperty(Toolkit::ScrollView::Property::SCROLLING, false);
2072
2073     if(fabs(mScrollPrePosition.x - mScrollTargetPosition.x) > Math::MACHINE_EPSILON_10)
2074     {
2075       SnapInternalXTo(mScrollTargetPosition.x);
2076     }
2077     if(fabs(mScrollPrePosition.y - mScrollTargetPosition.y) > Math::MACHINE_EPSILON_10)
2078     {
2079       SnapInternalYTo(mScrollTargetPosition.y);
2080     }
2081     Vector2 currentScrollPosition = GetCurrentScrollPosition();
2082     DALI_LOG_SCROLL_STATE("[0x%X] mScrollCompletedSignal 6 [%.2f, %.2f]", this, currentScrollPosition.x, currentScrollPosition.y);
2083     mScrollCompletedSignal.Emit(currentScrollPosition);
2084   }
2085 }
2086
2087 bool ScrollView::OnAccessibilityPan(PanGesture gesture)
2088 {
2089   // Keep track of whether this is an AccessibilityPan
2090   mInAccessibilityPan = true;
2091   OnPan(gesture);
2092   mInAccessibilityPan = false;
2093
2094   return true;
2095 }
2096
2097 void ScrollView::ClampPosition(Vector2& position) const
2098 {
2099   ClampState2D clamped;
2100   ClampPosition(position, clamped);
2101 }
2102
2103 void ScrollView::ClampPosition(Vector2& position, ClampState2D& clamped) const
2104 {
2105   Vector3 size = Self().GetCurrentProperty<Vector3>(Actor::Property::SIZE);
2106
2107   ::ClampPosition(size, mRulerX, mRulerY, position, clamped);
2108 }
2109
2110 void ScrollView::WrapPosition(Vector2& position) const
2111 {
2112   if(mWrapMode)
2113   {
2114     const RulerDomain rulerDomainX = mRulerX->GetDomain();
2115     const RulerDomain rulerDomainY = mRulerY->GetDomain();
2116
2117     if(mRulerX->IsEnabled())
2118     {
2119       position.x = -WrapInDomain(-position.x, rulerDomainX.min, rulerDomainX.max);
2120     }
2121
2122     if(mRulerY->IsEnabled())
2123     {
2124       position.y = -WrapInDomain(-position.y, rulerDomainY.min, rulerDomainY.max);
2125     }
2126   }
2127 }
2128
2129 void ScrollView::SetProperty(BaseObject* object, Property::Index index, const Property::Value& value)
2130 {
2131   ScrollViewPropertyHandler::Set(object, index, value);
2132 }
2133
2134 Property::Value ScrollView::GetProperty(BaseObject* object, Property::Index index)
2135 {
2136   return ScrollViewPropertyHandler::Get(object, index);
2137 }
2138
2139 ScrollView::LockAxis GetLockAxis(const Vector2& panDelta, ScrollView::LockAxis currentLockAxis, float lockGradient)
2140 {
2141   if(panDelta.LengthSquared() > AUTOLOCK_AXIS_MINIMUM_DISTANCE2 &&
2142      currentLockAxis == ScrollView::LockPossible)
2143   {
2144     float dx = fabsf(panDelta.x);
2145     float dy = fabsf(panDelta.y);
2146     if(dx * lockGradient >= dy)
2147     {
2148       // 0.36:1 gradient to the horizontal (deviate < 20 degrees)
2149       currentLockAxis = ScrollView::LockVertical;
2150     }
2151     else if(dy * lockGradient > dx)
2152     {
2153       // 0.36:1 gradient to the vertical (deviate < 20 degrees)
2154       currentLockAxis = ScrollView::LockHorizontal;
2155     }
2156     else
2157     {
2158       currentLockAxis = ScrollView::LockNone;
2159     }
2160   }
2161   return currentLockAxis;
2162 }
2163
2164 } // namespace Internal
2165
2166 } // namespace Toolkit
2167
2168 } // namespace Dali