[AT-SPI] fix a crash on scroll-view
[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   // child can be one of descendants
1274   // find direct child of ScrollView to avoid the ASSERT in ScrollTo
1275   auto parent = child.GetParent();
1276   while (parent && parent != Self())
1277   {
1278     child = parent;
1279     parent = child.GetParent();
1280   }
1281   if (!parent)
1282   {
1283     return false;
1284   }
1285
1286   // FIXME: ScrollTo does not work (snaps back to original position)
1287   scrollView.ScrollTo(child, scrollView.GetScrollFlickDuration());
1288   return true;
1289 }
1290
1291 void ScrollView::FindAndUnbindActor(Actor child)
1292 {
1293   UnbindActor(child);
1294 }
1295
1296 Vector2 ScrollView::GetPropertyPrePosition() const
1297 {
1298   Vector2 position = Self().GetCurrentProperty<Vector2>(Toolkit::ScrollView::Property::SCROLL_PRE_POSITION);
1299   WrapPosition(position);
1300   return position;
1301 }
1302
1303 Vector2 ScrollView::GetPropertyPosition() const
1304 {
1305   Vector2 position = Self().GetCurrentProperty<Vector2>(Toolkit::ScrollView::Property::SCROLL_POSITION);
1306   WrapPosition(position);
1307
1308   return position;
1309 }
1310
1311 void ScrollView::HandleStoppedAnimation()
1312 {
1313   SetScrollUpdateNotification(false);
1314 }
1315
1316 void ScrollView::HandleSnapAnimationFinished()
1317 {
1318   // Emit Signal that scrolling has completed.
1319   mScrolling = false;
1320   Actor self = Self();
1321   self.SetProperty(Toolkit::ScrollView::Property::SCROLLING, false);
1322
1323   Vector2 deltaPosition(mScrollPrePosition);
1324
1325   UpdateLocalScrollProperties();
1326   WrapPosition(mScrollPrePosition);
1327   DALI_LOG_SCROLL_STATE("[0x%X] Setting SCROLL_PRE_POSITION To[%.2f, %.2f]", this, mScrollPrePosition.x, mScrollPrePosition.y);
1328   self.SetProperty(Toolkit::ScrollView::Property::SCROLL_PRE_POSITION, mScrollPrePosition);
1329
1330   Vector2 currentScrollPosition = GetCurrentScrollPosition();
1331   DALI_LOG_SCROLL_STATE("[0x%X] mScrollCompletedSignal 3 current[%.2f, %.2f], mScrollTargetPosition[%.2f, %.2f]", this, currentScrollPosition.x, currentScrollPosition.y, -mScrollTargetPosition.x, -mScrollTargetPosition.y);
1332   mScrollCompletedSignal.Emit(currentScrollPosition);
1333
1334   mDomainOffset += deltaPosition - mScrollPostPosition;
1335   self.SetProperty(Toolkit::ScrollView::Property::SCROLL_DOMAIN_OFFSET, mDomainOffset);
1336   HandleStoppedAnimation();
1337 }
1338
1339 void ScrollView::SetScrollUpdateNotification(bool enabled)
1340 {
1341   Actor self = Self();
1342   if(mScrollXUpdateNotification)
1343   {
1344     // disconnect now to avoid a notification before removed from update thread
1345     mScrollXUpdateNotification.NotifySignal().Disconnect(this, &ScrollView::OnScrollUpdateNotification);
1346     self.RemovePropertyNotification(mScrollXUpdateNotification);
1347     mScrollXUpdateNotification.Reset();
1348   }
1349   if(enabled && !mScrollUpdatedSignal.Empty())
1350   {
1351     // Only set up the notification when the application has connected to the updated signal
1352     mScrollXUpdateNotification = self.AddPropertyNotification(Toolkit::ScrollView::Property::SCROLL_POSITION, 0, StepCondition(mScrollUpdateDistance, 0.0f));
1353     mScrollXUpdateNotification.NotifySignal().Connect(this, &ScrollView::OnScrollUpdateNotification);
1354   }
1355   if(mScrollYUpdateNotification)
1356   {
1357     // disconnect now to avoid a notification before removed from update thread
1358     mScrollYUpdateNotification.NotifySignal().Disconnect(this, &ScrollView::OnScrollUpdateNotification);
1359     self.RemovePropertyNotification(mScrollYUpdateNotification);
1360     mScrollYUpdateNotification.Reset();
1361   }
1362   if(enabled && !mScrollUpdatedSignal.Empty())
1363   {
1364     // Only set up the notification when the application has connected to the updated signal
1365     mScrollYUpdateNotification = self.AddPropertyNotification(Toolkit::ScrollView::Property::SCROLL_POSITION, 1, StepCondition(mScrollUpdateDistance, 0.0f));
1366     mScrollYUpdateNotification.NotifySignal().Connect(this, &ScrollView::OnScrollUpdateNotification);
1367   }
1368 }
1369
1370 void ScrollView::OnScrollUpdateNotification(Dali::PropertyNotification& source)
1371 {
1372   // Guard against destruction during signal emission
1373   Toolkit::ScrollView handle(GetOwner());
1374
1375   Vector2 currentScrollPosition = GetCurrentScrollPosition();
1376   mScrollUpdatedSignal.Emit(currentScrollPosition);
1377 }
1378
1379 bool ScrollView::DoConnectSignal(BaseObject* object, ConnectionTrackerInterface* tracker, const std::string& signalName, FunctorDelegate* functor)
1380 {
1381   Dali::BaseHandle handle(object);
1382
1383   bool                connected(true);
1384   Toolkit::ScrollView view = Toolkit::ScrollView::DownCast(handle);
1385
1386   if(0 == strcmp(signalName.c_str(), SIGNAL_SNAP_STARTED))
1387   {
1388     view.SnapStartedSignal().Connect(tracker, functor);
1389   }
1390   else
1391   {
1392     // signalName does not match any signal
1393     connected = false;
1394   }
1395
1396   return connected;
1397 }
1398
1399 void ScrollView::OnSizeAnimation(Animation& animation, const Vector3& targetSize)
1400 {
1401   // need to update domain properties for new size
1402   ScrollViewPropertyHandler::UpdatePropertyDomain(*this);
1403 }
1404
1405 void ScrollView::OnSizeSet(const Vector3& size)
1406 {
1407   // need to update domain properties for new size
1408   if(mDefaultMaxOvershoot)
1409   {
1410     mUserMaxOvershoot.x = size.x * 0.5f;
1411     mUserMaxOvershoot.y = size.y * 0.5f;
1412     if(!IsOvershootEnabled())
1413     {
1414       mMaxOvershoot = mUserMaxOvershoot;
1415     }
1416   }
1417   ScrollViewPropertyHandler::UpdatePropertyDomain(*this);
1418   mConstraints.UpdateMainInternalConstraint(*this);
1419   if(IsOvershootEnabled())
1420   {
1421     mOvershootIndicator->Reset();
1422   }
1423
1424   ScrollBase::OnSizeSet(size);
1425 }
1426
1427 void ScrollView::OnChildAdd(Actor& child)
1428 {
1429   ScrollBase::OnChildAdd(child);
1430
1431   Dali::Toolkit::ScrollBar scrollBar = Dali::Toolkit::ScrollBar::DownCast(child);
1432   if(scrollBar)
1433   {
1434     mScrollBar = scrollBar;
1435     scrollBar.SetProperty(Dali::Actor::Property::NAME, "ScrollBar");
1436
1437     mInternalActor.Add(scrollBar);
1438     if(scrollBar.GetScrollDirection() == Toolkit::ScrollBar::HORIZONTAL)
1439     {
1440       scrollBar.SetScrollPropertySource(Self(),
1441                                         Toolkit::ScrollView::Property::SCROLL_PRE_POSITION_X,
1442                                         Toolkit::Scrollable::Property::SCROLL_POSITION_MIN_X,
1443                                         Toolkit::ScrollView::Property::SCROLL_PRE_POSITION_MAX_X,
1444                                         Toolkit::ScrollView::Property::SCROLL_DOMAIN_SIZE_X);
1445     }
1446     else
1447     {
1448       scrollBar.SetScrollPropertySource(Self(),
1449                                         Toolkit::ScrollView::Property::SCROLL_PRE_POSITION_Y,
1450                                         Toolkit::Scrollable::Property::SCROLL_POSITION_MIN_Y,
1451                                         Toolkit::ScrollView::Property::SCROLL_PRE_POSITION_MAX_Y,
1452                                         Toolkit::ScrollView::Property::SCROLL_DOMAIN_SIZE_Y);
1453     }
1454
1455     if(mTransientScrollBar)
1456     {
1457       // Show the scroll-indicator for a brief period
1458       Property::Map emptyMap;
1459       scrollBar.DoAction("ShowTransientIndicator", emptyMap);
1460     }
1461   }
1462   else if(mAlterChild)
1463   {
1464     BindActor(child);
1465   }
1466 }
1467
1468 void ScrollView::OnChildRemove(Actor& child)
1469 {
1470   // TODO: Actor needs a RemoveConstraint method to take out an individual constraint.
1471   UnbindActor(child);
1472
1473   ScrollBase::OnChildRemove(child);
1474 }
1475
1476 void ScrollView::StartTouchDownTimer()
1477 {
1478   if(!mTouchDownTimer)
1479   {
1480     mTouchDownTimer = Timer::New(TOUCH_DOWN_TIMER_INTERVAL);
1481     mTouchDownTimer.TickSignal().Connect(this, &ScrollView::OnTouchDownTimeout);
1482   }
1483
1484   mTouchDownTimer.Start();
1485 }
1486
1487 void ScrollView::StopTouchDownTimer()
1488 {
1489   if(mTouchDownTimer)
1490   {
1491     mTouchDownTimer.Stop();
1492   }
1493 }
1494
1495 bool ScrollView::OnTouchDownTimeout()
1496 {
1497   DALI_LOG_SCROLL_STATE("[0x%X]", this);
1498
1499   mTouchDownTimeoutReached = true;
1500
1501   unsigned int currentScrollStateFlags(mScrollStateFlags); // Cleared in StopAnimation so keep local copy for comparison
1502   if(currentScrollStateFlags & (SCROLL_ANIMATION_FLAGS | SNAP_ANIMATION_FLAGS))
1503   {
1504     DALI_LOG_SCROLL_STATE("[0x%X] Scrolling Or snapping flags set, stopping animation", this);
1505
1506     StopAnimation();
1507     if(currentScrollStateFlags & SCROLL_ANIMATION_FLAGS)
1508     {
1509       DALI_LOG_SCROLL_STATE("[0x%X] Scrolling flags set, emitting signal", this);
1510
1511       mScrollInterrupted = true;
1512       // reset domain offset as scrolling from original plane.
1513       mDomainOffset = Vector2::ZERO;
1514       Self().SetProperty(Toolkit::ScrollView::Property::SCROLL_DOMAIN_OFFSET, Vector2::ZERO);
1515
1516       UpdateLocalScrollProperties();
1517       Vector2 currentScrollPosition = GetCurrentScrollPosition();
1518       DALI_LOG_SCROLL_STATE("[0x%X] mScrollCompletedSignal 4 [%.2f, %.2f]", this, currentScrollPosition.x, currentScrollPosition.y);
1519       mScrollCompletedSignal.Emit(currentScrollPosition);
1520     }
1521   }
1522
1523   return false;
1524 }
1525
1526 bool ScrollView::OnTouch(Actor actor, const TouchEvent& touch)
1527 {
1528   if(!mSensitive)
1529   {
1530     DALI_LOG_SCROLL_STATE("[0x%X], Not Sensitive, ignoring", this);
1531
1532     // Ignore this touch event, if scrollview is insensitive.
1533     return false;
1534   }
1535
1536   // Ignore events with multiple-touch points
1537   if(touch.GetPointCount() != 1)
1538   {
1539     DALI_LOG_SCROLL_STATE("[0x%X], multiple touch, ignoring", this);
1540
1541     return false;
1542   }
1543
1544   const PointState::Type pointState = touch.GetState(0);
1545   if(pointState == PointState::DOWN)
1546   {
1547     DALI_LOG_SCROLL_STATE("[0x%X] Down", this);
1548
1549     if(mGestureStackDepth == 0)
1550     {
1551       mTouchDownTime = touch.GetTime();
1552
1553       // This allows time for a pan-gesture to start, to avoid breaking snap-animation behavior with fast flicks.
1554       // If touch-down does not become a pan (after timeout interval), then snap-animation can be interrupted.
1555       mTouchDownTimeoutReached = false;
1556       mScrollInterrupted       = false;
1557       StartTouchDownTimer();
1558     }
1559   }
1560   else if((pointState == PointState::UP) ||
1561           ((pointState == PointState::INTERRUPTED) && (touch.GetHitActor(0) == Self())))
1562   {
1563     DALI_LOG_SCROLL_STATE("[0x%X] %s", this, ((pointState == PointState::UP) ? "Up" : "Interrupted"));
1564
1565     StopTouchDownTimer();
1566
1567     // if the user touches and releases without enough movement to go
1568     // into a gesture state, then we should snap to nearest point.
1569     // otherwise our scroll could be stopped (interrupted) half way through an animation.
1570     if(mGestureStackDepth == 0 && mTouchDownTimeoutReached)
1571     {
1572       if((pointState == PointState::INTERRUPTED) ||
1573          ((touch.GetTime() - mTouchDownTime) >= MINIMUM_TIME_BETWEEN_DOWN_AND_UP_FOR_RESET))
1574       {
1575         // Reset the velocity only if down was received a while ago
1576         mLastVelocity = Vector2(0.0f, 0.0f);
1577       }
1578
1579       UpdateLocalScrollProperties();
1580       // Only finish the transform if scrolling was interrupted on down or if we are scrolling
1581       if(mScrollInterrupted || mScrolling)
1582       {
1583         DALI_LOG_SCROLL_STATE("[0x%X] Calling FinishTransform", this);
1584
1585         FinishTransform();
1586       }
1587     }
1588     mTouchDownTimeoutReached = false;
1589     mScrollInterrupted       = false;
1590   }
1591
1592   return false;
1593 }
1594
1595 bool ScrollView::OnWheelEvent(Actor actor, const WheelEvent& event)
1596 {
1597   if(!mSensitive)
1598   {
1599     // Ignore this wheel event, if scrollview is insensitive.
1600     return false;
1601   }
1602
1603   Vector2 targetScrollPosition = GetPropertyPosition();
1604
1605   if(mRulerX->IsEnabled() && !mRulerY->IsEnabled())
1606   {
1607     // If only the ruler in the X axis is enabled, scroll in the X axis.
1608     if(mRulerX->GetType() == Ruler::FREE)
1609     {
1610       // Free panning mode
1611       targetScrollPosition.x += event.GetDelta() * mWheelScrollDistanceStep.x;
1612       ClampPosition(targetScrollPosition);
1613       ScrollTo(-targetScrollPosition);
1614     }
1615     else if(!mScrolling)
1616     {
1617       // Snap mode, only respond to the event when the previous snap animation is finished.
1618       ScrollTo(GetCurrentPage() - event.GetDelta());
1619     }
1620   }
1621   else
1622   {
1623     // If the ruler in the Y axis is enabled, scroll in the Y axis.
1624     if(mRulerY->GetType() == Ruler::FREE)
1625     {
1626       // Free panning mode
1627       targetScrollPosition.y += event.GetDelta() * mWheelScrollDistanceStep.y;
1628       ClampPosition(targetScrollPosition);
1629       ScrollTo(-targetScrollPosition);
1630     }
1631     else if(!mScrolling)
1632     {
1633       // Snap mode, only respond to the event when the previous snap animation is finished.
1634       ScrollTo(GetCurrentPage() - event.GetDelta() * mRulerX->GetTotalPages());
1635     }
1636   }
1637
1638   return true;
1639 }
1640
1641 void ScrollView::ResetScrolling()
1642 {
1643   Actor self = Self();
1644   self.GetCurrentProperty(Toolkit::ScrollView::Property::SCROLL_POSITION).Get(mScrollPostPosition);
1645   mScrollPrePosition = mScrollPostPosition;
1646   DALI_LOG_SCROLL_STATE("[0x%X] Setting SCROLL_PRE_POSITION To[%.2f, %.2f]", this, mScrollPostPosition.x, mScrollPostPosition.y);
1647   self.SetProperty(Toolkit::ScrollView::Property::SCROLL_PRE_POSITION, mScrollPostPosition);
1648 }
1649
1650 void ScrollView::UpdateLocalScrollProperties()
1651 {
1652   Actor self = Self();
1653   self.GetCurrentProperty(Toolkit::ScrollView::Property::SCROLL_PRE_POSITION).Get(mScrollPrePosition);
1654   self.GetCurrentProperty(Toolkit::ScrollView::Property::SCROLL_POSITION).Get(mScrollPostPosition);
1655 }
1656
1657 // private functions
1658
1659 void ScrollView::PreAnimatedScrollSetup()
1660 {
1661   // SCROLL_PRE_POSITION is our unclamped property with wrapping
1662   // SCROLL_POSITION is our final scroll position after clamping
1663
1664   Actor self = Self();
1665
1666   Vector2 deltaPosition(mScrollPostPosition);
1667   WrapPosition(mScrollPostPosition);
1668   mDomainOffset += deltaPosition - mScrollPostPosition;
1669   Self().SetProperty(Toolkit::ScrollView::Property::SCROLL_DOMAIN_OFFSET, mDomainOffset);
1670
1671   if(mScrollStateFlags & SCROLL_X_STATE_MASK)
1672   {
1673     // already performing animation on internal x position
1674     StopAnimation(mInternalXAnimation);
1675   }
1676
1677   if(mScrollStateFlags & SCROLL_Y_STATE_MASK)
1678   {
1679     // already performing animation on internal y position
1680     StopAnimation(mInternalYAnimation);
1681   }
1682
1683   mScrollStateFlags = 0;
1684
1685   // Update Actor position with this wrapped value.
1686 }
1687
1688 void ScrollView::FinaliseAnimatedScroll()
1689 {
1690   // TODO - common animation finishing code in here
1691 }
1692
1693 void ScrollView::AnimateInternalXTo(float position, float duration, AlphaFunction alpha)
1694 {
1695   StopAnimation(mInternalXAnimation);
1696
1697   if(duration > Math::MACHINE_EPSILON_10)
1698   {
1699     Actor self = Self();
1700     DALI_LOG_SCROLL_STATE("[0x%X], Animating from[%.2f] to[%.2f]", this, self.GetCurrentProperty(Toolkit::ScrollView::Property::SCROLL_PRE_POSITION).Get<Vector2>().x, position);
1701     mInternalXAnimation = Animation::New(duration);
1702     DALI_LOG_SCROLL_STATE("[0x%X], mInternalXAnimation[0x%X]", this, mInternalXAnimation.GetObjectPtr());
1703     mInternalXAnimation.FinishedSignal().Connect(this, &ScrollView::OnScrollAnimationFinished);
1704     mInternalXAnimation.AnimateTo(Property(self, Toolkit::ScrollView::Property::SCROLL_PRE_POSITION, 0), position, alpha, TimePeriod(duration));
1705     mInternalXAnimation.Play();
1706
1707     // erase current state flags
1708     mScrollStateFlags &= ~SCROLL_X_STATE_MASK;
1709     // add internal animation state flag
1710     mScrollStateFlags |= AnimatingInternalX;
1711   }
1712 }
1713
1714 void ScrollView::AnimateInternalYTo(float position, float duration, AlphaFunction alpha)
1715 {
1716   StopAnimation(mInternalYAnimation);
1717
1718   if(duration > Math::MACHINE_EPSILON_10)
1719   {
1720     Actor self = Self();
1721     DALI_LOG_SCROLL_STATE("[0x%X], Animating from[%.2f] to[%.2f]", this, self.GetCurrentProperty(Toolkit::ScrollView::Property::SCROLL_PRE_POSITION).Get<Vector2>().y, position);
1722     mInternalYAnimation = Animation::New(duration);
1723     DALI_LOG_SCROLL_STATE("[0x%X], mInternalYAnimation[0x%X]", this, mInternalYAnimation.GetObjectPtr());
1724     mInternalYAnimation.FinishedSignal().Connect(this, &ScrollView::OnScrollAnimationFinished);
1725     mInternalYAnimation.AnimateTo(Property(self, Toolkit::ScrollView::Property::SCROLL_PRE_POSITION, 1), position, alpha, TimePeriod(duration));
1726     mInternalYAnimation.Play();
1727
1728     // erase current state flags
1729     mScrollStateFlags &= ~SCROLL_Y_STATE_MASK;
1730     // add internal animation state flag
1731     mScrollStateFlags |= AnimatingInternalY;
1732   }
1733 }
1734
1735 void ScrollView::OnScrollAnimationFinished(Animation& source)
1736 {
1737   // Guard against destruction during signal emission
1738   // Note that ScrollCompletedSignal is emitted from HandleSnapAnimationFinished()
1739   Toolkit::ScrollView handle(GetOwner());
1740
1741   bool scrollingFinished = false;
1742
1743   // update our local scroll positions
1744   UpdateLocalScrollProperties();
1745
1746   if(source == mInternalXAnimation)
1747   {
1748     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);
1749
1750     if(!(mScrollStateFlags & AnimatingInternalY))
1751     {
1752       scrollingFinished = true;
1753     }
1754     mInternalXAnimation.Reset();
1755     // wrap pre scroll x position and set it
1756     if(mWrapMode)
1757     {
1758       const RulerDomain rulerDomain = mRulerX->GetDomain();
1759       mScrollPrePosition.x          = -WrapInDomain(-mScrollPrePosition.x, rulerDomain.min, rulerDomain.max);
1760       DALI_LOG_SCROLL_STATE("[0x%X] Setting SCROLL_PRE_POSITION To[%.2f, %.2f]", this, mScrollPrePosition.x, mScrollPrePosition.y);
1761       handle.SetProperty(Toolkit::ScrollView::Property::SCROLL_PRE_POSITION, mScrollPrePosition);
1762     }
1763     SnapInternalXTo(mScrollPostPosition.x);
1764   }
1765
1766   if(source == mInternalYAnimation)
1767   {
1768     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);
1769
1770     if(!(mScrollStateFlags & AnimatingInternalX))
1771     {
1772       scrollingFinished = true;
1773     }
1774     mInternalYAnimation.Reset();
1775     if(mWrapMode)
1776     {
1777       // wrap pre scroll y position and set it
1778       const RulerDomain rulerDomain = mRulerY->GetDomain();
1779       mScrollPrePosition.y          = -WrapInDomain(-mScrollPrePosition.y, rulerDomain.min, rulerDomain.max);
1780       DALI_LOG_SCROLL_STATE("[0x%X] Setting SCROLL_PRE_POSITION To[%.2f, %.2f]", this, mScrollPrePosition.x, mScrollPrePosition.y);
1781       handle.SetProperty(Toolkit::ScrollView::Property::SCROLL_PRE_POSITION, mScrollPrePosition);
1782     }
1783     SnapInternalYTo(mScrollPostPosition.y);
1784   }
1785
1786   DALI_LOG_SCROLL_STATE("[0x%X] scrollingFinished[%d] Animation[0x%X]", this, scrollingFinished, source.GetObjectPtr());
1787
1788   if(scrollingFinished)
1789   {
1790     HandleSnapAnimationFinished();
1791   }
1792 }
1793
1794 void ScrollView::OnSnapInternalPositionFinished(Animation& source)
1795 {
1796   Actor self = Self();
1797   UpdateLocalScrollProperties();
1798   if(source == mInternalXAnimation)
1799   {
1800     DALI_LOG_SCROLL_STATE("[0x%X] Finished X PostPosition Animation", this);
1801
1802     // clear internal x animation flags
1803     mScrollStateFlags &= ~SCROLL_X_STATE_MASK;
1804     mInternalXAnimation.Reset();
1805     WrapPosition(mScrollPrePosition);
1806   }
1807   if(source == mInternalYAnimation)
1808   {
1809     DALI_LOG_SCROLL_STATE("[0x%X] Finished Y PostPosition Animation", this);
1810
1811     mScrollStateFlags &= ~SCROLL_Y_STATE_MASK;
1812     mInternalYAnimation.Reset();
1813     WrapPosition(mScrollPrePosition);
1814   }
1815 }
1816
1817 void ScrollView::SnapInternalXTo(float position)
1818 {
1819   Actor self = Self();
1820
1821   StopAnimation(mInternalXAnimation);
1822
1823   // erase current state flags
1824   mScrollStateFlags &= ~SCROLL_X_STATE_MASK;
1825
1826   // if internal x not equal to inputed parameter, animate it
1827   float duration = std::min(fabsf((position - mScrollPrePosition.x) / mMaxOvershoot.x) * mSnapOvershootDuration, mSnapOvershootDuration);
1828   DALI_LOG_SCROLL_STATE("[0x%X] duration[%.2f]", this, duration);
1829   if(duration > Math::MACHINE_EPSILON_1)
1830   {
1831     DALI_LOG_SCROLL_STATE("[0x%X] Starting X Snap Animation to[%.2f]", this, position);
1832
1833     mInternalXAnimation = Animation::New(duration);
1834     mInternalXAnimation.FinishedSignal().Connect(this, &ScrollView::OnSnapInternalPositionFinished);
1835     mInternalXAnimation.AnimateTo(Property(self, Toolkit::ScrollView::Property::SCROLL_PRE_POSITION, 0), position);
1836     mInternalXAnimation.Play();
1837
1838     // add internal animation state flag
1839     mScrollStateFlags |= SnappingInternalX;
1840   }
1841 }
1842
1843 void ScrollView::SnapInternalYTo(float position)
1844 {
1845   Actor self = Self();
1846
1847   StopAnimation(mInternalYAnimation);
1848
1849   // erase current state flags
1850   mScrollStateFlags &= ~SCROLL_Y_STATE_MASK;
1851
1852   // if internal y not equal to inputed parameter, animate it
1853   float duration = std::min(fabsf((position - mScrollPrePosition.y) / mMaxOvershoot.y) * mSnapOvershootDuration, mSnapOvershootDuration);
1854   DALI_LOG_SCROLL_STATE("[0x%X] duration[%.2f]", this, duration);
1855   if(duration > Math::MACHINE_EPSILON_1)
1856   {
1857     DALI_LOG_SCROLL_STATE("[0x%X] Starting Y Snap Animation to[%.2f]", this, position);
1858
1859     mInternalYAnimation = Animation::New(duration);
1860     mInternalYAnimation.FinishedSignal().Connect(this, &ScrollView::OnSnapInternalPositionFinished);
1861     mInternalYAnimation.AnimateTo(Property(self, Toolkit::ScrollView::Property::SCROLL_PRE_POSITION, 1), position);
1862     mInternalYAnimation.Play();
1863
1864     // add internal animation state flag
1865     mScrollStateFlags |= SnappingInternalY;
1866   }
1867 }
1868
1869 void ScrollView::GestureStarted()
1870 {
1871   // we handle the first gesture.
1872   // if we're currently doing a gesture and receive another
1873   // we continue and combine the effects of the gesture instead of reseting.
1874   if(mGestureStackDepth++ == 0)
1875   {
1876     Actor self = Self();
1877     StopTouchDownTimer();
1878     StopAnimation();
1879     mPanDelta     = Vector2::ZERO;
1880     mLastVelocity = Vector2::ZERO;
1881     if(!mScrolling)
1882     {
1883       mLockAxis = LockPossible;
1884     }
1885
1886     if(mScrollStateFlags & SCROLL_X_STATE_MASK)
1887     {
1888       StopAnimation(mInternalXAnimation);
1889     }
1890     if(mScrollStateFlags & SCROLL_Y_STATE_MASK)
1891     {
1892       StopAnimation(mInternalYAnimation);
1893     }
1894     mScrollStateFlags = 0;
1895
1896     if(mScrolling) // are we interrupting a current scroll?
1897     {
1898       // set mScrolling to false, in case user has code that interrogates mScrolling Getter() in complete.
1899       mScrolling = false;
1900       // send negative scroll position since scroll internal scroll position works as an offset for actors,
1901       // give applications the position within the domain from the scroll view's anchor position
1902       DALI_LOG_SCROLL_STATE("[0x%X] mScrollCompletedSignal 5 [%.2f, %.2f]", this, -mScrollPostPosition.x, -mScrollPostPosition.y);
1903       mScrollCompletedSignal.Emit(-mScrollPostPosition);
1904     }
1905   }
1906 }
1907
1908 void ScrollView::GestureContinuing(const Vector2& panDelta)
1909 {
1910   mPanDelta.x += panDelta.x;
1911   mPanDelta.y += panDelta.y;
1912
1913   // Save the velocity, there is a bug in PanGesture
1914   // Whereby the GestureState::FINISHED's velocity is either:
1915   // NaN (due to time delta of zero between the last two events)
1916   // or 0 (due to position being the same between the last two events)
1917
1918   // Axis Auto Lock - locks the panning to the horizontal or vertical axis if the pan
1919   // appears mostly horizontal or mostly vertical respectively.
1920   if(mAxisAutoLock)
1921   {
1922     mLockAxis = GetLockAxis(mPanDelta, mLockAxis, mAxisAutoLockGradient);
1923   } // end if mAxisAutoLock
1924 }
1925
1926 // TODO: Upgrade to use a more powerful gesture detector (one that supports multiple touches on pan - so works as pan and flick gesture)
1927 // BUG: GestureState::FINISHED doesn't always return velocity on release (due to
1928 // timeDelta between last two events being 0 sometimes, or posiiton being the same)
1929 void ScrollView::OnPan(const PanGesture& gesture)
1930 {
1931   // Guard against destruction during signal emission
1932   // Note that Emit() methods are called indirectly e.g. from within ScrollView::OnGestureEx()
1933   Actor self(Self());
1934
1935   if(!mSensitive)
1936   {
1937     DALI_LOG_SCROLL_STATE("[0x%X] Pan Ignored, Insensitive", this);
1938
1939     // If another callback on the same original signal disables sensitivity,
1940     // this callback will still be called, so we must suppress it.
1941     return;
1942   }
1943
1944   // translate Gesture input to get useful data...
1945   switch(gesture.GetState())
1946   {
1947     case GestureState::STARTED:
1948     {
1949       DALI_LOG_SCROLL_STATE("[0x%X] Pan Started", this);
1950       const Vector2& position = gesture.GetPosition();
1951       mPanStartPosition       = position - gesture.GetDisplacement();
1952       UpdateLocalScrollProperties();
1953       GestureStarted();
1954       mPanning = true;
1955       self.SetProperty(Toolkit::ScrollView::Property::PANNING, true);
1956       self.SetProperty(Toolkit::ScrollView::Property::START_PAGE_POSITION, Vector3(position.x, position.y, 0.0f));
1957
1958       mConstraints.UpdateMainInternalConstraint(*this);
1959       Toolkit::ScrollBar scrollBar = mScrollBar.GetHandle();
1960       if(scrollBar && mTransientScrollBar)
1961       {
1962         Vector3                     size         = Self().GetCurrentProperty<Vector3>(Actor::Property::SIZE);
1963         const Toolkit::RulerDomain& rulerDomainX = mRulerX->GetDomain();
1964         const Toolkit::RulerDomain& rulerDomainY = mRulerY->GetDomain();
1965
1966         if((rulerDomainX.max > size.width) || (rulerDomainY.max > size.height))
1967         {
1968           scrollBar.ShowIndicator();
1969         }
1970       }
1971       break;
1972     }
1973
1974     case GestureState::CONTINUING:
1975     {
1976       if(mPanning)
1977       {
1978         DALI_LOG_SCROLL_STATE("[0x%X] Pan Continuing", this);
1979         GestureContinuing(gesture.GetScreenDisplacement());
1980       }
1981       else
1982       {
1983         // If we do not think we are panning, then we should not do anything here
1984         return;
1985       }
1986       break;
1987     }
1988
1989     case GestureState::FINISHED:
1990     case GestureState::CANCELLED:
1991     {
1992       if(mPanning)
1993       {
1994         DALI_LOG_SCROLL_STATE("[0x%X] Pan %s", this, ((gesture.GetState() == GestureState::FINISHED) ? "Finished" : "Cancelled"));
1995
1996         UpdateLocalScrollProperties();
1997         mLastVelocity = gesture.GetVelocity();
1998         mPanning      = false;
1999         self.SetProperty(Toolkit::ScrollView::Property::PANNING, false);
2000
2001         if(mConstraints.mScrollMainInternalPrePositionConstraint)
2002         {
2003           mConstraints.mScrollMainInternalPrePositionConstraint.Remove();
2004         }
2005
2006         Toolkit::ScrollBar scrollBar = mScrollBar.GetHandle();
2007         if(scrollBar && mTransientScrollBar)
2008         {
2009           scrollBar.HideIndicator();
2010         }
2011       }
2012       else
2013       {
2014         // If we do not think we are panning, then we should not do anything here
2015         return;
2016       }
2017       break;
2018     }
2019
2020     case GestureState::POSSIBLE:
2021     case GestureState::CLEAR:
2022     {
2023       // Nothing to do, not needed.
2024       break;
2025     }
2026
2027   } // end switch(gesture.state)
2028
2029   OnGestureEx(gesture.GetState());
2030 }
2031
2032 void ScrollView::OnGestureEx(GestureState state)
2033 {
2034   // call necessary signals for application developer
2035
2036   if(state == GestureState::STARTED)
2037   {
2038     Vector2 currentScrollPosition = GetCurrentScrollPosition();
2039     Self().SetProperty(Toolkit::ScrollView::Property::SCROLLING, true);
2040     mScrolling = true;
2041     DALI_LOG_SCROLL_STATE("[0x%X] mScrollStartedSignal 2 [%.2f, %.2f]", this, currentScrollPosition.x, currentScrollPosition.y);
2042     mScrollStartedSignal.Emit(currentScrollPosition);
2043   }
2044   else if((state == GestureState::FINISHED) ||
2045           (state == GestureState::CANCELLED)) // Finished/default
2046   {
2047     // when all the gestures have finished, we finish the transform.
2048     // so if a user decides to pan (1 gesture), and then pan+zoom (2 gestures)
2049     // then stop panning (back to 1 gesture), and then stop zooming (0 gestures).
2050     // this is the point we end, and perform necessary snapping.
2051     mGestureStackDepth--;
2052     if(mGestureStackDepth == 0)
2053     {
2054       // no flick if we have not exceeded min flick distance
2055       if((fabsf(mPanDelta.x) < mMinFlickDistance.x) && (fabsf(mPanDelta.y) < mMinFlickDistance.y))
2056       {
2057         // reset flick velocity
2058         mLastVelocity = Vector2::ZERO;
2059       }
2060       FinishTransform();
2061     }
2062     else
2063     {
2064       DALI_LOG_SCROLL_STATE("[0x%X] mGestureStackDepth[%d]", this, mGestureStackDepth);
2065     }
2066   }
2067 }
2068
2069 void ScrollView::FinishTransform()
2070 {
2071   // at this stage internal x and x scroll position should have followed prescroll position exactly
2072   Actor self = Self();
2073
2074   PreAnimatedScrollSetup();
2075
2076   // convert pixels/millisecond to pixels per second
2077   bool animating = SnapWithVelocity(mLastVelocity * 1000.0f);
2078
2079   if(!animating)
2080   {
2081     // if not animating, then this pan has completed right now.
2082     SetScrollUpdateNotification(false);
2083     mScrolling = false;
2084     Self().SetProperty(Toolkit::ScrollView::Property::SCROLLING, false);
2085
2086     if(fabs(mScrollPrePosition.x - mScrollTargetPosition.x) > Math::MACHINE_EPSILON_10)
2087     {
2088       SnapInternalXTo(mScrollTargetPosition.x);
2089     }
2090     if(fabs(mScrollPrePosition.y - mScrollTargetPosition.y) > Math::MACHINE_EPSILON_10)
2091     {
2092       SnapInternalYTo(mScrollTargetPosition.y);
2093     }
2094     Vector2 currentScrollPosition = GetCurrentScrollPosition();
2095     DALI_LOG_SCROLL_STATE("[0x%X] mScrollCompletedSignal 6 [%.2f, %.2f]", this, currentScrollPosition.x, currentScrollPosition.y);
2096     mScrollCompletedSignal.Emit(currentScrollPosition);
2097   }
2098 }
2099
2100 bool ScrollView::OnAccessibilityPan(PanGesture gesture)
2101 {
2102   // Keep track of whether this is an AccessibilityPan
2103   mInAccessibilityPan = true;
2104   OnPan(gesture);
2105   mInAccessibilityPan = false;
2106
2107   return true;
2108 }
2109
2110 void ScrollView::ClampPosition(Vector2& position) const
2111 {
2112   ClampState2D clamped;
2113   ClampPosition(position, clamped);
2114 }
2115
2116 void ScrollView::ClampPosition(Vector2& position, ClampState2D& clamped) const
2117 {
2118   Vector3 size = Self().GetCurrentProperty<Vector3>(Actor::Property::SIZE);
2119
2120   ::ClampPosition(size, mRulerX, mRulerY, position, clamped);
2121 }
2122
2123 void ScrollView::WrapPosition(Vector2& position) const
2124 {
2125   if(mWrapMode)
2126   {
2127     const RulerDomain rulerDomainX = mRulerX->GetDomain();
2128     const RulerDomain rulerDomainY = mRulerY->GetDomain();
2129
2130     if(mRulerX->IsEnabled())
2131     {
2132       position.x = -WrapInDomain(-position.x, rulerDomainX.min, rulerDomainX.max);
2133     }
2134
2135     if(mRulerY->IsEnabled())
2136     {
2137       position.y = -WrapInDomain(-position.y, rulerDomainY.min, rulerDomainY.max);
2138     }
2139   }
2140 }
2141
2142 void ScrollView::SetProperty(BaseObject* object, Property::Index index, const Property::Value& value)
2143 {
2144   ScrollViewPropertyHandler::Set(object, index, value);
2145 }
2146
2147 Property::Value ScrollView::GetProperty(BaseObject* object, Property::Index index)
2148 {
2149   return ScrollViewPropertyHandler::Get(object, index);
2150 }
2151
2152 ScrollView::LockAxis GetLockAxis(const Vector2& panDelta, ScrollView::LockAxis currentLockAxis, float lockGradient)
2153 {
2154   if(panDelta.LengthSquared() > AUTOLOCK_AXIS_MINIMUM_DISTANCE2 &&
2155      currentLockAxis == ScrollView::LockPossible)
2156   {
2157     float dx = fabsf(panDelta.x);
2158     float dy = fabsf(panDelta.y);
2159     if(dx * lockGradient >= dy)
2160     {
2161       // 0.36:1 gradient to the horizontal (deviate < 20 degrees)
2162       currentLockAxis = ScrollView::LockVertical;
2163     }
2164     else if(dy * lockGradient > dx)
2165     {
2166       // 0.36:1 gradient to the vertical (deviate < 20 degrees)
2167       currentLockAxis = ScrollView::LockHorizontal;
2168     }
2169     else
2170     {
2171       currentLockAxis = ScrollView::LockNone;
2172     }
2173   }
2174   return currentLockAxis;
2175 }
2176
2177 } // namespace Internal
2178
2179 } // namespace Toolkit
2180
2181 } // namespace Dali