[dali_2.3.19] Merge branch 'devel/master'
[platform/core/uifw/dali-toolkit.git] / dali-toolkit / internal / controls / scrollable / scroll-view / scroll-view-impl.cpp
1 /*
2  * Copyright (c) 2022 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::INFO, "%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 Dali::Vector2 GetPosition(Dali::Actor actor)
534 {
535   Vector2 screenPosition          = actor.GetProperty<Vector2>(Actor::Property::SCREEN_POSITION);
536   Vector3 size                    = actor.GetCurrentProperty<Vector3>(Actor::Property::SIZE) * actor.GetCurrentProperty<Vector3>(Actor::Property::WORLD_SCALE);
537   bool    positionUsesAnchorPoint = actor.GetProperty<bool>(Actor::Property::POSITION_USES_ANCHOR_POINT);
538   Vector3 anchorPointOffSet       = size * (positionUsesAnchorPoint ? actor.GetCurrentProperty<Vector3>(Actor::Property::ANCHOR_POINT) : AnchorPoint::TOP_LEFT);
539   Vector2 position                = Vector2(screenPosition.x - anchorPointOffSet.x, screenPosition.y - anchorPointOffSet.y);
540
541   return position;
542 }
543
544 } // unnamed namespace
545
546 namespace Dali
547 {
548 namespace Toolkit
549 {
550 namespace Internal
551 {
552 namespace
553 {
554 BaseHandle Create()
555 {
556   return Toolkit::ScrollView::New();
557 }
558
559 // Setup properties, signals and actions using the type-registry.
560 DALI_TYPE_REGISTRATION_BEGIN(Toolkit::ScrollView, Toolkit::Scrollable, Create)
561
562 DALI_PROPERTY_REGISTRATION(Toolkit, ScrollView, "wrapEnabled", BOOLEAN, WRAP_ENABLED)
563 DALI_PROPERTY_REGISTRATION(Toolkit, ScrollView, "panningEnabled", BOOLEAN, PANNING_ENABLED)
564 DALI_PROPERTY_REGISTRATION(Toolkit, ScrollView, "axisAutoLockEnabled", BOOLEAN, AXIS_AUTO_LOCK_ENABLED)
565 DALI_PROPERTY_REGISTRATION(Toolkit, ScrollView, "wheelScrollDistanceStep", VECTOR2, WHEEL_SCROLL_DISTANCE_STEP)
566 DALI_PROPERTY_REGISTRATION(Toolkit, ScrollView, "scrollMode", MAP, SCROLL_MODE)
567
568 DALI_ANIMATABLE_PROPERTY_REGISTRATION(Toolkit, ScrollView, "scrollPosition", VECTOR2, SCROLL_POSITION)
569 DALI_ANIMATABLE_PROPERTY_REGISTRATION(Toolkit, ScrollView, "scrollPrePosition", VECTOR2, SCROLL_PRE_POSITION)
570 DALI_ANIMATABLE_PROPERTY_COMPONENT_REGISTRATION(Toolkit, ScrollView, "scrollPrePositionX", SCROLL_PRE_POSITION_X, SCROLL_PRE_POSITION, 0)
571 DALI_ANIMATABLE_PROPERTY_COMPONENT_REGISTRATION(Toolkit, ScrollView, "scrollPrePositionY", SCROLL_PRE_POSITION_Y, SCROLL_PRE_POSITION, 1)
572 DALI_ANIMATABLE_PROPERTY_REGISTRATION(Toolkit, ScrollView, "scrollPrePositionMax", VECTOR2, SCROLL_PRE_POSITION_MAX)
573 DALI_ANIMATABLE_PROPERTY_COMPONENT_REGISTRATION(Toolkit, ScrollView, "scrollPrePositionMaxX", SCROLL_PRE_POSITION_MAX_X, SCROLL_PRE_POSITION_MAX, 0)
574 DALI_ANIMATABLE_PROPERTY_COMPONENT_REGISTRATION(Toolkit, ScrollView, "scrollPrePositionMaxY", SCROLL_PRE_POSITION_MAX_Y, SCROLL_PRE_POSITION_MAX, 1)
575 DALI_ANIMATABLE_PROPERTY_REGISTRATION(Toolkit, ScrollView, "overshootX", FLOAT, OVERSHOOT_X)
576 DALI_ANIMATABLE_PROPERTY_REGISTRATION(Toolkit, ScrollView, "overshootY", FLOAT, OVERSHOOT_Y)
577 DALI_ANIMATABLE_PROPERTY_REGISTRATION(Toolkit, ScrollView, "scrollFinal", VECTOR2, SCROLL_FINAL)
578 DALI_ANIMATABLE_PROPERTY_COMPONENT_REGISTRATION(Toolkit, ScrollView, "scrollFinalX", SCROLL_FINAL_X, SCROLL_FINAL, 0)
579 DALI_ANIMATABLE_PROPERTY_COMPONENT_REGISTRATION(Toolkit, ScrollView, "scrollFinalY", SCROLL_FINAL_Y, SCROLL_FINAL, 1)
580 DALI_ANIMATABLE_PROPERTY_REGISTRATION(Toolkit, ScrollView, "wrap", BOOLEAN, WRAP)
581 DALI_ANIMATABLE_PROPERTY_REGISTRATION(Toolkit, ScrollView, "panning", BOOLEAN, PANNING)
582 DALI_ANIMATABLE_PROPERTY_REGISTRATION(Toolkit, ScrollView, "scrolling", BOOLEAN, SCROLLING)
583 DALI_ANIMATABLE_PROPERTY_REGISTRATION(Toolkit, ScrollView, "scrollDomainSize", VECTOR2, SCROLL_DOMAIN_SIZE)
584 DALI_ANIMATABLE_PROPERTY_COMPONENT_REGISTRATION(Toolkit, ScrollView, "scrollDomainSizeX", SCROLL_DOMAIN_SIZE_X, SCROLL_DOMAIN_SIZE, 0)
585 DALI_ANIMATABLE_PROPERTY_COMPONENT_REGISTRATION(Toolkit, ScrollView, "scrollDomainSizeY", SCROLL_DOMAIN_SIZE_Y, SCROLL_DOMAIN_SIZE, 1)
586 DALI_ANIMATABLE_PROPERTY_REGISTRATION(Toolkit, ScrollView, "scrollDomainOffset", VECTOR2, SCROLL_DOMAIN_OFFSET)
587 DALI_ANIMATABLE_PROPERTY_REGISTRATION(Toolkit, ScrollView, "scrollPositionDelta", VECTOR2, SCROLL_POSITION_DELTA)
588 DALI_ANIMATABLE_PROPERTY_REGISTRATION(Toolkit, ScrollView, "startPagePosition", VECTOR3, START_PAGE_POSITION)
589
590 DALI_SIGNAL_REGISTRATION(Toolkit, ScrollView, "valueChanged", SIGNAL_SNAP_STARTED)
591
592 DALI_TYPE_REGISTRATION_END()
593
594 } // namespace
595
596 ///////////////////////////////////////////////////////////////////////////////////////////////////
597 // ScrollView
598 ///////////////////////////////////////////////////////////////////////////////////////////////////
599
600 Dali::Toolkit::ScrollView ScrollView::New()
601 {
602   // Create the implementation
603   ScrollViewPtr scrollView(new ScrollView());
604
605   // Pass ownership to CustomActor via derived handle
606   Dali::Toolkit::ScrollView handle(*scrollView);
607
608   // Second-phase init of the implementation
609   // This can only be done after the CustomActor connection has been made...
610   scrollView->Initialize();
611
612   return handle;
613 }
614
615 ScrollView::ScrollView()
616 : ScrollBase(ControlBehaviour(DISABLE_STYLE_CHANGE_SIGNALS)), // Enable size negotiation
617   mTouchDownTime(0u),
618   mGestureStackDepth(0),
619   mScrollStateFlags(0),
620   mLockAxis(LockPossible),
621   mScrollUpdateDistance(DEFAULT_SCROLL_UPDATE_DISTANCE),
622   mMaxOvershoot(DEFAULT_MAX_OVERSHOOT, DEFAULT_MAX_OVERSHOOT),
623   mUserMaxOvershoot(DEFAULT_MAX_OVERSHOOT, DEFAULT_MAX_OVERSHOOT),
624   mSnapOvershootDuration(DEFAULT_SNAP_OVERSHOOT_DURATION),
625   mSnapOvershootAlphaFunction(AlphaFunction::EASE_OUT),
626   mSnapDuration(DEFAULT_SLOW_SNAP_ANIMATION_DURATION),
627   mSnapAlphaFunction(AlphaFunction::EASE_OUT),
628   mMinFlickDistance(DEFAULT_MIN_FLICK_DISTANCE),
629   mFlickSpeedThreshold(DEFAULT_MIN_FLICK_SPEED_THRESHOLD),
630   mFlickDuration(DEFAULT_FAST_SNAP_ANIMATION_DURATION),
631   mFlickAlphaFunction(AlphaFunction::EASE_OUT),
632   mAxisAutoLockGradient(DEFAULT_AXIS_AUTO_LOCK_GRADIENT),
633   mFrictionCoefficient(DEFAULT_FRICTION_COEFFICIENT),
634   mFlickSpeedCoefficient(DEFAULT_FLICK_SPEED_COEFFICIENT),
635   mMaxFlickSpeed(DEFAULT_MAX_FLICK_SPEED),
636   mWheelScrollDistanceStep(Vector2::ZERO),
637   mInAccessibilityPan(false),
638   mScrolling(false),
639   mScrollInterrupted(false),
640   mPanning(false),
641   mSensitive(true),
642   mTouchDownTimeoutReached(false),
643   mActorAutoSnapEnabled(false),
644   mAutoResizeContainerEnabled(false),
645   mWrapMode(false),
646   mAxisAutoLock(false),
647   mAlterChild(false),
648   mDefaultMaxOvershoot(true),
649   mCanScrollHorizontal(true),
650   mCanScrollVertical(true),
651   mTransientScrollBar(true)
652 {
653 }
654
655 void ScrollView::OnInitialize()
656 {
657   Actor self = Self();
658
659   // Internal Actor, used to hide actors from enumerations.
660   // Also actors added to Internal actor appear as overlays e.g. ScrollBar components.
661   mInternalActor = Actor::New();
662   self.Add(mInternalActor);
663
664   mInternalActor.SetProperty(Actor::Property::PARENT_ORIGIN, ParentOrigin::CENTER);
665   mInternalActor.SetProperty(Actor::Property::ANCHOR_POINT, AnchorPoint::CENTER);
666   mInternalActor.SetResizePolicy(ResizePolicy::FILL_TO_PARENT, Dimension::ALL_DIMENSIONS);
667
668   mAlterChild = true;
669
670   mScrollPostPosition = mScrollPrePosition = Vector2::ZERO;
671
672   mWheelScrollDistanceStep = Stage::GetCurrent().GetSize() * DEFAULT_WHEEL_SCROLL_DISTANCE_STEP_PROPORTION;
673
674   mGestureStackDepth = 0;
675
676   self.TouchedSignal().Connect(this, &ScrollView::OnTouch);
677   EnableGestureDetection(GestureType::Value(GestureType::PAN));
678
679   // By default we'll allow the user to freely drag the scroll view,
680   // while disabling the other rulers.
681   RulerPtr ruler = new DefaultRuler();
682   mRulerX        = ruler;
683   mRulerY        = ruler;
684
685   self.SetProperty(Toolkit::Scrollable::Property::CAN_SCROLL_VERTICAL, mCanScrollVertical);
686   self.SetProperty(Toolkit::Scrollable::Property::CAN_SCROLL_HORIZONTAL, mCanScrollHorizontal);
687
688   ScrollViewPropertyHandler::UpdatePropertyDomain(*this);
689   mConstraints.SetInternalConstraints(*this);
690
691   // Connect wheel event
692   self.WheelEventSignal().Connect(this, &ScrollView::OnWheelEvent);
693
694   self.SetProperty(DevelControl::Property::ACCESSIBILITY_ROLE, Dali::Accessibility::Role::SCROLL_PANE);
695 }
696
697 DevelControl::ControlAccessible* ScrollView::CreateAccessibleObject()
698 {
699   return new ScrollViewAccessible(Self());
700 }
701
702 void ScrollView::OnSceneConnection(int depth)
703 {
704   DALI_LOG_SCROLL_STATE("[0x%X]", this);
705
706   if(mSensitive)
707   {
708     SetScrollSensitive(false);
709     SetScrollSensitive(true);
710   }
711
712   if(IsOvershootEnabled())
713   {
714     // try and make sure property notifications are set
715     EnableScrollOvershoot(true);
716   }
717
718   ScrollBase::OnSceneConnection(depth);
719 }
720
721 void ScrollView::OnSceneDisconnection()
722 {
723   DALI_LOG_SCROLL_STATE("[0x%X]", this);
724
725   StopAnimation();
726
727   ScrollBase::OnSceneDisconnection();
728 }
729
730 ScrollView::~ScrollView()
731 {
732   DALI_LOG_SCROLL_STATE("[0x%X]", this);
733 }
734
735 void ScrollView::ApplyEffect(Toolkit::ScrollViewEffect effect)
736 {
737   Dali::Toolkit::ScrollView self = Dali::Toolkit::ScrollView::DownCast(Self());
738
739   // Assertion check to ensure effect doesn't already exist in this scrollview
740   bool effectAlreadyExistsInScrollView(false);
741   for(ScrollViewEffectIter iter = mEffects.begin(); iter != mEffects.end(); ++iter)
742   {
743     if(*iter == effect)
744     {
745       effectAlreadyExistsInScrollView = true;
746       break;
747     }
748   }
749
750   DALI_ASSERT_ALWAYS(!effectAlreadyExistsInScrollView);
751
752   // add effect to effects list
753   mEffects.push_back(effect);
754
755   // invoke Attachment request to ScrollView first
756   GetImpl(effect).Attach(self);
757 }
758
759 void ScrollView::RemoveEffect(Toolkit::ScrollViewEffect effect)
760 {
761   Dali::Toolkit::ScrollView self = Dali::Toolkit::ScrollView::DownCast(Self());
762
763   // remove effect from effects list
764   bool effectExistedInScrollView(false);
765   for(ScrollViewEffectIter iter = mEffects.begin(); iter != mEffects.end(); ++iter)
766   {
767     if(*iter == effect)
768     {
769       mEffects.erase(iter);
770       effectExistedInScrollView = true;
771       break;
772     }
773   }
774
775   // Assertion check to ensure effect existed.
776   DALI_ASSERT_ALWAYS(effectExistedInScrollView);
777
778   // invoke Detachment request to ScrollView last
779   GetImpl(effect).Detach(self);
780 }
781
782 void ScrollView::RemoveAllEffects()
783 {
784   Dali::Toolkit::ScrollView self = Dali::Toolkit::ScrollView::DownCast(Self());
785
786   for(ScrollViewEffectIter effectIter = mEffects.begin(); effectIter != mEffects.end(); ++effectIter)
787   {
788     Toolkit::ScrollViewEffect effect = *effectIter;
789
790     // invoke Detachment request to ScrollView last
791     GetImpl(effect).Detach(self);
792   }
793
794   mEffects.clear();
795 }
796
797 void ScrollView::ApplyConstraintToChildren(Constraint constraint)
798 {
799   ApplyConstraintToBoundActors(constraint);
800 }
801
802 void ScrollView::RemoveConstraintsFromChildren()
803 {
804   RemoveConstraintsFromBoundActors();
805 }
806
807 void ScrollView::SetRulerX(RulerPtr ruler)
808 {
809   mRulerX = ruler;
810
811   ScrollViewPropertyHandler::UpdatePropertyDomain(*this);
812   mConstraints.UpdateMainInternalConstraint(*this);
813 }
814
815 void ScrollView::SetRulerY(RulerPtr ruler)
816 {
817   mRulerY = ruler;
818
819   ScrollViewPropertyHandler::UpdatePropertyDomain(*this);
820   mConstraints.UpdateMainInternalConstraint(*this);
821 }
822
823 void ScrollView::SetScrollSensitive(bool sensitive)
824 {
825   Actor              self = Self();
826   PanGestureDetector panGesture(GetPanGestureDetector());
827
828   DALI_LOG_SCROLL_STATE("[0x%X] sensitive: before:[%d] setting[%d]", this, int(mSensitive), int(sensitive));
829
830   if((!mSensitive) && (sensitive))
831   {
832     mSensitive = sensitive;
833     panGesture.Attach(self);
834   }
835   else if((mSensitive) && (!sensitive))
836   {
837     DALI_LOG_SCROLL_STATE("[0x%X] BEFORE: panning:[%d]", this, int(mPanning));
838
839     // while the scroll view is panning, the state needs to be reset.
840     if(mPanning)
841     {
842       PanGesture cancelGesture = DevelPanGesture::New(GestureState::CANCELLED);
843       OnPan(cancelGesture);
844     }
845
846     panGesture.Detach(self);
847     mSensitive = sensitive;
848
849     mGestureStackDepth = 0;
850     DALI_LOG_SCROLL_STATE("[0x%X] AFTER: panning:[%d]", this, int(mPanning));
851   }
852 }
853
854 void ScrollView::SetMaxOvershoot(float overshootX, float overshootY)
855 {
856   mMaxOvershoot.x      = overshootX;
857   mMaxOvershoot.y      = overshootY;
858   mUserMaxOvershoot    = mMaxOvershoot;
859   mDefaultMaxOvershoot = false;
860   mConstraints.UpdateMainInternalConstraint(*this);
861 }
862
863 bool ScrollView::GetActorAutoSnap()
864 {
865   return mActorAutoSnapEnabled;
866 }
867
868 void ScrollView::SetAutoResize(bool enable)
869 {
870   mAutoResizeContainerEnabled = enable;
871   // TODO: This needs a lot of issues to be addressed before working.
872 }
873
874 void ScrollView::SetWrapMode(bool enable)
875 {
876   mWrapMode = enable;
877   Self().SetProperty(Toolkit::ScrollView::Property::WRAP, enable);
878 }
879
880 void ScrollView::SetAxisAutoLock(bool enable)
881 {
882   mAxisAutoLock = enable;
883   mConstraints.UpdateMainInternalConstraint(*this);
884 }
885
886 void ScrollView::SetAxisAutoLockGradient(float gradient)
887 {
888   DALI_ASSERT_DEBUG(gradient >= 0.0f && gradient <= 1.0f);
889   mAxisAutoLockGradient = gradient;
890   mConstraints.UpdateMainInternalConstraint(*this);
891 }
892
893 void ScrollView::SetFrictionCoefficient(float friction)
894 {
895   DALI_ASSERT_DEBUG(friction > 0.0f);
896   mFrictionCoefficient = friction;
897 }
898
899 unsigned int ScrollView::GetCurrentPage() const
900 {
901   // in case animation is currently taking place.
902   Vector2 position = GetPropertyPosition();
903
904   Actor        self           = Self();
905   unsigned int page           = 0;
906   unsigned int pagesPerVolume = 1;
907   unsigned int volume         = 0;
908
909   // if rulerX is enabled, then get page count (columns)
910   page           = mRulerX->GetPageFromPosition(-position.x, mWrapMode);
911   volume         = mRulerY->GetPageFromPosition(-position.y, mWrapMode);
912   pagesPerVolume = mRulerX->GetTotalPages();
913
914   return volume * pagesPerVolume + page;
915 }
916
917 Vector2 ScrollView::GetCurrentScrollPosition() const
918 {
919   return -GetPropertyPosition();
920 }
921
922 void ScrollView::TransformTo(const Vector2& position,
923                              DirectionBias  horizontalBias,
924                              DirectionBias  verticalBias)
925 {
926   TransformTo(position, mSnapDuration, mSnapAlphaFunction, horizontalBias, verticalBias);
927 }
928
929 void ScrollView::TransformTo(const Vector2& position, float duration, AlphaFunction alpha, DirectionBias horizontalBias, DirectionBias verticalBias)
930 {
931   // If this is called while the timer is running, then cancel it
932   StopTouchDownTimer();
933
934   Actor self(Self());
935
936   // Guard against destruction during signal emission
937   // Note that Emit() methods are called indirectly e.g. from within ScrollView::AnimateTo()
938   Toolkit::ScrollView handle(GetOwner());
939
940   DALI_LOG_SCROLL_STATE("[0x%X] pos[%.2f,%.2f], duration[%.2f] bias[%d, %d]",
941                         this,
942                         position.x,
943                         position.y,
944                         duration,
945                         int(horizontalBias),
946                         int(verticalBias));
947
948   Vector2 currentScrollPosition = GetCurrentScrollPosition();
949   self.SetProperty(Toolkit::ScrollView::Property::START_PAGE_POSITION, Vector3(currentScrollPosition));
950
951   if(mScrolling) // are we interrupting a current scroll?
952   {
953     // set mScrolling to false, in case user has code that interrogates mScrolling Getter() in complete.
954     mScrolling = false;
955     DALI_LOG_SCROLL_STATE("[0x%X] mScrollCompletedSignal 1 [%.2f, %.2f]", this, currentScrollPosition.x, currentScrollPosition.y);
956     mScrollCompletedSignal.Emit(currentScrollPosition);
957   }
958
959   if(mPanning) // are we interrupting a current pan?
960   {
961     DALI_LOG_SCROLL_STATE("[0x%X] Interrupting Pan, set to false", this);
962     mPanning           = false;
963     mGestureStackDepth = 0;
964     self.SetProperty(Toolkit::ScrollView::Property::PANNING, false);
965
966     if(mConstraints.mScrollMainInternalPrePositionConstraint)
967     {
968       mConstraints.mScrollMainInternalPrePositionConstraint.Remove();
969     }
970   }
971
972   self.SetProperty(Toolkit::ScrollView::Property::SCROLLING, true);
973   mScrolling = true;
974
975   DALI_LOG_SCROLL_STATE("[0x%X] mScrollStartedSignal 1 [%.2f, %.2f]", this, currentScrollPosition.x, currentScrollPosition.y);
976   mScrollStartedSignal.Emit(currentScrollPosition);
977   bool animating = AnimateTo(-position,
978                              Vector2::ONE * duration,
979                              alpha,
980                              true,
981                              horizontalBias,
982                              verticalBias,
983                              SNAP);
984
985   if(!animating)
986   {
987     // if not animating, then this pan has completed right now.
988     self.SetProperty(Toolkit::ScrollView::Property::SCROLLING, false);
989     mScrolling = false;
990
991     // If we have no duration, then in the next update frame, we will be at the position specified as we just set.
992     // In this scenario, we cannot return the currentScrollPosition as this is out-of-date and should instead return the requested final position
993     Vector2 completedPosition(currentScrollPosition);
994     if(duration <= Math::MACHINE_EPSILON_10)
995     {
996       completedPosition = position;
997     }
998
999     DALI_LOG_SCROLL_STATE("[0x%X] mScrollCompletedSignal 2 [%.2f, %.2f]", this, completedPosition.x, completedPosition.y);
1000     SetScrollUpdateNotification(false);
1001     mScrollCompletedSignal.Emit(completedPosition);
1002   }
1003 }
1004
1005 void ScrollView::ScrollTo(const Vector2& position)
1006 {
1007   ScrollTo(position, mSnapDuration);
1008 }
1009
1010 void ScrollView::ScrollTo(const Vector2& position, float duration)
1011 {
1012   ScrollTo(position, duration, DIRECTION_BIAS_NONE, DIRECTION_BIAS_NONE);
1013 }
1014
1015 void ScrollView::ScrollTo(const Vector2& position, float duration, AlphaFunction alpha)
1016 {
1017   ScrollTo(position, duration, alpha, DIRECTION_BIAS_NONE, DIRECTION_BIAS_NONE);
1018 }
1019
1020 void ScrollView::ScrollTo(const Vector2& position, float duration, DirectionBias horizontalBias, DirectionBias verticalBias)
1021 {
1022   ScrollTo(position, duration, mSnapAlphaFunction, horizontalBias, verticalBias);
1023 }
1024
1025 void ScrollView::ScrollTo(const Vector2& position, float duration, AlphaFunction alpha, DirectionBias horizontalBias, DirectionBias verticalBias)
1026 {
1027   DALI_LOG_SCROLL_STATE("[0x%X] position[%.2f, %.2f] duration[%.2f], bias[%d, %d]", this, position.x, position.y, duration, int(horizontalBias), int(verticalBias));
1028   TransformTo(position, duration, alpha, horizontalBias, verticalBias);
1029 }
1030
1031 void ScrollView::ScrollTo(unsigned int page)
1032 {
1033   ScrollTo(page, mSnapDuration);
1034 }
1035
1036 void ScrollView::ScrollTo(unsigned int page, float duration, DirectionBias bias)
1037 {
1038   Vector2      position;
1039   unsigned int volume;
1040   unsigned int libraries;
1041
1042   // The position to scroll to is continuous and linear
1043   // unless a domain has been enabled on the X axis.
1044   // or if WrapMode has been enabled.
1045   bool carryX = mRulerX->GetDomain().enabled | mWrapMode;
1046   bool carryY = mRulerY->GetDomain().enabled | mWrapMode;
1047
1048   position.x = mRulerX->GetPositionFromPage(page, volume, carryX);
1049   position.y = mRulerY->GetPositionFromPage(volume, libraries, carryY);
1050
1051   ScrollTo(position, duration, bias, bias);
1052 }
1053
1054 void ScrollView::ScrollTo(Actor& actor)
1055 {
1056   ScrollTo(actor, mSnapDuration);
1057 }
1058
1059 void ScrollView::ScrollTo(Actor& actor, float duration)
1060 {
1061   DALI_ASSERT_ALWAYS(actor.GetParent() == Self());
1062
1063   Actor   self        = Self();
1064   Vector3 size        = self.GetCurrentProperty<Vector3>(Actor::Property::SIZE);
1065   Vector3 position    = actor.GetCurrentProperty<Vector3>(Actor::Property::POSITION);
1066   Vector2 prePosition = GetPropertyPrePosition();
1067   position.GetVectorXY() -= prePosition;
1068
1069   ScrollTo(Vector2(position.x - size.width * 0.5f, position.y - size.height * 0.5f), duration);
1070 }
1071
1072 Actor ScrollView::FindClosestActor()
1073 {
1074   Actor   self = Self();
1075   Vector3 size = self.GetCurrentProperty<Vector3>(Actor::Property::SIZE);
1076
1077   return FindClosestActorToPosition(Vector3(size.width * 0.5f, size.height * 0.5f, 0.0f));
1078 }
1079
1080 Actor ScrollView::FindClosestActorToPosition(const Vector3& position, FindDirection dirX, FindDirection dirY, FindDirection dirZ)
1081 {
1082   return ::FindClosestActorToPosition(Self(), mInternalActor, position, dirX, dirY, dirZ);
1083 }
1084
1085 bool ScrollView::ScrollToSnapPoint()
1086 {
1087   DALI_LOG_SCROLL_STATE("[0x%X]", this);
1088   Vector2 stationaryVelocity = Vector2(0.0f, 0.0f);
1089   return SnapWithVelocity(stationaryVelocity);
1090 }
1091
1092 bool ScrollView::SnapWithVelocity(Vector2 velocity)
1093 {
1094   Vector2       positionSnap     = mScrollPrePosition;
1095   Vector2       positionDuration = Vector2::ONE * mSnapDuration;
1096   AlphaFunction alphaFunction    = mSnapAlphaFunction;
1097   bool          isFlick;
1098   bool          isFreeFlick;
1099
1100   ::SnapWithVelocity(*this, mRulerX, mRulerY, mLockAxis, velocity, mMaxOvershoot, positionSnap, positionDuration, alphaFunction, mInAccessibilityPan, isFlick, isFreeFlick);
1101
1102   bool animating = AnimateTo(positionSnap, positionDuration, alphaFunction, false, DIRECTION_BIAS_NONE, DIRECTION_BIAS_NONE, isFlick || isFreeFlick ? FLICK : SNAP);
1103
1104   return animating;
1105 }
1106
1107 void ScrollView::StopAnimation(void)
1108 {
1109   // Clear Snap animation if exists.
1110   StopAnimation(mInternalXAnimation);
1111   StopAnimation(mInternalYAnimation);
1112   mScrollStateFlags = 0;
1113   // remove scroll animation flags
1114   HandleStoppedAnimation();
1115 }
1116
1117 void ScrollView::StopAnimation(Animation& animation)
1118 {
1119   if(animation)
1120   {
1121     animation.Stop();
1122     animation.Reset();
1123   }
1124 }
1125
1126 bool ScrollView::AnimateTo(const Vector2& position, const Vector2& positionDuration, AlphaFunction alpha, bool findShortcuts, DirectionBias horizontalBias, DirectionBias verticalBias, SnapType snapType)
1127 {
1128   // Here we perform an animation on a number of properties (depending on which have changed)
1129   // The animation is applied to all ScrollBases
1130   Actor self            = Self();
1131   mScrollTargetPosition = position;
1132   float totalDuration   = 0.0f;
1133
1134   bool positionChanged = (mScrollTargetPosition != mScrollPostPosition);
1135
1136   if(positionChanged)
1137   {
1138     totalDuration = std::max(totalDuration, positionDuration.x);
1139     totalDuration = std::max(totalDuration, positionDuration.y);
1140   }
1141   else
1142   {
1143     // try to animate for a frame, on some occasions update will be changing scroll value while event side thinks it hasnt changed
1144     totalDuration   = 0.01f;
1145     positionChanged = true;
1146   }
1147
1148   StopAnimation();
1149
1150   // Position Delta ///////////////////////////////////////////////////////
1151   if(positionChanged)
1152   {
1153     mConstraints.UpdateMainInternalConstraint(*this);
1154     if(mWrapMode && findShortcuts)
1155     {
1156       // In Wrap Mode, the shortest distance is a little less intuitive...
1157       const RulerDomain rulerDomainX = mRulerX->GetDomain();
1158       const RulerDomain rulerDomainY = mRulerY->GetDomain();
1159
1160       if(mRulerX->IsEnabled())
1161       {
1162         float dir               = VectorInDomain(-mScrollPrePosition.x, -mScrollTargetPosition.x, rulerDomainX.min, rulerDomainX.max, horizontalBias);
1163         mScrollTargetPosition.x = mScrollPrePosition.x + -dir;
1164       }
1165
1166       if(mRulerY->IsEnabled())
1167       {
1168         float dir               = VectorInDomain(-mScrollPrePosition.y, -mScrollTargetPosition.y, rulerDomainY.min, rulerDomainY.max, verticalBias);
1169         mScrollTargetPosition.y = mScrollPrePosition.y + -dir;
1170       }
1171     }
1172
1173     // note we have two separate animations for X & Y, this deals with sliding diagonally and hitting
1174     // a horizonal/vertical wall.delay
1175     AnimateInternalXTo(mScrollTargetPosition.x, positionDuration.x, alpha);
1176     AnimateInternalYTo(mScrollTargetPosition.y, positionDuration.y, alpha);
1177
1178     if(!(mScrollStateFlags & SCROLL_ANIMATION_FLAGS))
1179     {
1180       DALI_LOG_SCROLL_STATE("[0x%X] Setting SCROLL_PRE_POSITION To[%.2f, %.2f]", this, mScrollTargetPosition.x, mScrollTargetPosition.y);
1181       self.SetProperty(Toolkit::ScrollView::Property::SCROLL_PRE_POSITION, mScrollTargetPosition);
1182       mScrollPrePosition  = mScrollTargetPosition;
1183       mScrollPostPosition = mScrollTargetPosition;
1184       WrapPosition(mScrollPostPosition);
1185     }
1186
1187     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);
1188     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);
1189   }
1190
1191   SetScrollUpdateNotification(true);
1192
1193   // Always send a snap event when AnimateTo is called.
1194   Toolkit::ScrollView::SnapEvent snapEvent;
1195   snapEvent.type     = snapType;
1196   snapEvent.position = -mScrollTargetPosition;
1197   snapEvent.duration = totalDuration;
1198
1199   DALI_LOG_SCROLL_STATE("[0x%X] mSnapStartedSignal [%.2f, %.2f]", this, snapEvent.position.x, snapEvent.position.y);
1200   mSnapStartedSignal.Emit(snapEvent);
1201
1202   return (mScrollStateFlags & SCROLL_ANIMATION_FLAGS) != 0;
1203 }
1204
1205 void ScrollView::EnableScrollOvershoot(bool enable)
1206 {
1207   if(enable)
1208   {
1209     if(!mOvershootIndicator)
1210     {
1211       mOvershootIndicator = ScrollOvershootIndicator::New();
1212     }
1213
1214     mOvershootIndicator->AttachToScrollable(*this);
1215   }
1216   else
1217   {
1218     mMaxOvershoot = mUserMaxOvershoot;
1219
1220     if(mOvershootIndicator)
1221     {
1222       mOvershootIndicator->DetachFromScrollable(*this);
1223     }
1224   }
1225
1226   mConstraints.UpdateMainInternalConstraint(*this);
1227 }
1228
1229 void ScrollView::AddOverlay(Actor actor)
1230 {
1231   actor.SetProperty(Actor::Property::DRAW_MODE, DrawMode::OVERLAY_2D);
1232   mInternalActor.Add(actor);
1233 }
1234
1235 void ScrollView::RemoveOverlay(Actor actor)
1236 {
1237   mInternalActor.Remove(actor);
1238 }
1239
1240 void ScrollView::SetOvershootSize(const Vector2& size)
1241 {
1242   mOvershootSize = size;
1243   if(IsOvershootEnabled() && mOvershootIndicator)
1244   {
1245     mOvershootIndicator->AttachToScrollable(*this);
1246   }
1247 }
1248
1249 void ScrollView::SetOvershootEffectColor(const Vector4& color)
1250 {
1251   mOvershootEffectColor = color;
1252   if(mOvershootIndicator)
1253   {
1254     mOvershootIndicator->SetOvershootEffectColor(color);
1255   }
1256 }
1257
1258 void ScrollView::SetScrollingDirection(Radian direction, Radian threshold)
1259 {
1260   PanGestureDetector panGesture(GetPanGestureDetector());
1261
1262   // First remove just in case we have some set, then add.
1263   panGesture.RemoveDirection(direction);
1264   panGesture.AddDirection(direction, threshold);
1265 }
1266
1267 void ScrollView::RemoveScrollingDirection(Radian direction)
1268 {
1269   PanGestureDetector panGesture(GetPanGestureDetector());
1270   panGesture.RemoveDirection(direction);
1271 }
1272
1273 Toolkit::ScrollView::SnapStartedSignalType& ScrollView::SnapStartedSignal()
1274 {
1275   return mSnapStartedSignal;
1276 }
1277
1278 bool ScrollView::ScrollViewAccessible::ScrollToChild(Actor child)
1279 {
1280   auto scrollView = Dali::Toolkit::ScrollView::DownCast(Self());
1281   if(Toolkit::GetImpl(scrollView).FindClosestActor() == child)
1282   {
1283     return false;
1284   }
1285
1286   auto childPosition = GetPosition(child);
1287   auto selfPosition  = GetPosition(Self());
1288
1289   scrollView.ScrollTo(childPosition - selfPosition, scrollView.GetScrollFlickDuration());
1290
1291   return true;
1292 }
1293
1294 void ScrollView::FindAndUnbindActor(Actor child)
1295 {
1296   UnbindActor(child);
1297 }
1298
1299 Vector2 ScrollView::GetPropertyPrePosition() const
1300 {
1301   Vector2 position = Self().GetCurrentProperty<Vector2>(Toolkit::ScrollView::Property::SCROLL_PRE_POSITION);
1302   WrapPosition(position);
1303   return position;
1304 }
1305
1306 Vector2 ScrollView::GetPropertyPosition() const
1307 {
1308   Vector2 position = Self().GetCurrentProperty<Vector2>(Toolkit::ScrollView::Property::SCROLL_POSITION);
1309   WrapPosition(position);
1310
1311   return position;
1312 }
1313
1314 void ScrollView::HandleStoppedAnimation()
1315 {
1316   SetScrollUpdateNotification(false);
1317 }
1318
1319 void ScrollView::HandleSnapAnimationFinished()
1320 {
1321   // Emit Signal that scrolling has completed.
1322   mScrolling = false;
1323   Actor self = Self();
1324   self.SetProperty(Toolkit::ScrollView::Property::SCROLLING, false);
1325
1326   Vector2 deltaPosition(mScrollPrePosition);
1327
1328   UpdateLocalScrollProperties();
1329   WrapPosition(mScrollPrePosition);
1330   DALI_LOG_SCROLL_STATE("[0x%X] Setting SCROLL_PRE_POSITION To[%.2f, %.2f]", this, mScrollPrePosition.x, mScrollPrePosition.y);
1331   self.SetProperty(Toolkit::ScrollView::Property::SCROLL_PRE_POSITION, mScrollPrePosition);
1332
1333   Vector2 currentScrollPosition = GetCurrentScrollPosition();
1334   DALI_LOG_SCROLL_STATE("[0x%X] mScrollCompletedSignal 3 current[%.2f, %.2f], mScrollTargetPosition[%.2f, %.2f]", this, currentScrollPosition.x, currentScrollPosition.y, -mScrollTargetPosition.x, -mScrollTargetPosition.y);
1335   mScrollCompletedSignal.Emit(currentScrollPosition);
1336
1337   mDomainOffset += deltaPosition - mScrollPostPosition;
1338   self.SetProperty(Toolkit::ScrollView::Property::SCROLL_DOMAIN_OFFSET, mDomainOffset);
1339   HandleStoppedAnimation();
1340 }
1341
1342 void ScrollView::SetScrollUpdateNotification(bool enabled)
1343 {
1344   Actor self = Self();
1345   if(mScrollXUpdateNotification)
1346   {
1347     // disconnect now to avoid a notification before removed from update thread
1348     mScrollXUpdateNotification.NotifySignal().Disconnect(this, &ScrollView::OnScrollUpdateNotification);
1349     self.RemovePropertyNotification(mScrollXUpdateNotification);
1350     mScrollXUpdateNotification.Reset();
1351   }
1352   if(enabled && !mScrollUpdatedSignal.Empty())
1353   {
1354     // Only set up the notification when the application has connected to the updated signal
1355     mScrollXUpdateNotification = self.AddPropertyNotification(Toolkit::ScrollView::Property::SCROLL_POSITION, 0, StepCondition(mScrollUpdateDistance, 0.0f));
1356     mScrollXUpdateNotification.NotifySignal().Connect(this, &ScrollView::OnScrollUpdateNotification);
1357   }
1358   if(mScrollYUpdateNotification)
1359   {
1360     // disconnect now to avoid a notification before removed from update thread
1361     mScrollYUpdateNotification.NotifySignal().Disconnect(this, &ScrollView::OnScrollUpdateNotification);
1362     self.RemovePropertyNotification(mScrollYUpdateNotification);
1363     mScrollYUpdateNotification.Reset();
1364   }
1365   if(enabled && !mScrollUpdatedSignal.Empty())
1366   {
1367     // Only set up the notification when the application has connected to the updated signal
1368     mScrollYUpdateNotification = self.AddPropertyNotification(Toolkit::ScrollView::Property::SCROLL_POSITION, 1, StepCondition(mScrollUpdateDistance, 0.0f));
1369     mScrollYUpdateNotification.NotifySignal().Connect(this, &ScrollView::OnScrollUpdateNotification);
1370   }
1371 }
1372
1373 void ScrollView::OnScrollUpdateNotification(Dali::PropertyNotification& source)
1374 {
1375   // Guard against destruction during signal emission
1376   Toolkit::ScrollView handle(GetOwner());
1377
1378   Vector2 currentScrollPosition = GetCurrentScrollPosition();
1379   mScrollUpdatedSignal.Emit(currentScrollPosition);
1380 }
1381
1382 bool ScrollView::DoConnectSignal(BaseObject* object, ConnectionTrackerInterface* tracker, const std::string& signalName, FunctorDelegate* functor)
1383 {
1384   Dali::BaseHandle handle(object);
1385
1386   bool                connected(true);
1387   Toolkit::ScrollView view = Toolkit::ScrollView::DownCast(handle);
1388
1389   if(0 == strcmp(signalName.c_str(), SIGNAL_SNAP_STARTED))
1390   {
1391     view.SnapStartedSignal().Connect(tracker, functor);
1392   }
1393   else
1394   {
1395     // signalName does not match any signal
1396     connected = false;
1397   }
1398
1399   return connected;
1400 }
1401
1402 void ScrollView::OnSizeAnimation(Animation& animation, const Vector3& targetSize)
1403 {
1404   // need to update domain properties for new size
1405   ScrollViewPropertyHandler::UpdatePropertyDomain(*this);
1406 }
1407
1408 void ScrollView::OnSizeSet(const Vector3& size)
1409 {
1410   // need to update domain properties for new size
1411   if(mDefaultMaxOvershoot)
1412   {
1413     mUserMaxOvershoot.x = size.x * 0.5f;
1414     mUserMaxOvershoot.y = size.y * 0.5f;
1415     if(!IsOvershootEnabled())
1416     {
1417       mMaxOvershoot = mUserMaxOvershoot;
1418     }
1419   }
1420   ScrollViewPropertyHandler::UpdatePropertyDomain(*this);
1421   mConstraints.UpdateMainInternalConstraint(*this);
1422   if(IsOvershootEnabled())
1423   {
1424     mOvershootIndicator->Reset();
1425   }
1426
1427   ScrollBase::OnSizeSet(size);
1428 }
1429
1430 void ScrollView::OnChildAdd(Actor& child)
1431 {
1432   ScrollBase::OnChildAdd(child);
1433
1434   Dali::Toolkit::ScrollBar scrollBar = Dali::Toolkit::ScrollBar::DownCast(child);
1435   if(scrollBar)
1436   {
1437     mScrollBar = scrollBar;
1438     scrollBar.SetProperty(Dali::Actor::Property::NAME, "ScrollBar");
1439
1440     mInternalActor.Add(scrollBar);
1441     if(scrollBar.GetScrollDirection() == Toolkit::ScrollBar::HORIZONTAL)
1442     {
1443       scrollBar.SetScrollPropertySource(Self(),
1444                                         Toolkit::ScrollView::Property::SCROLL_PRE_POSITION_X,
1445                                         Toolkit::Scrollable::Property::SCROLL_POSITION_MIN_X,
1446                                         Toolkit::ScrollView::Property::SCROLL_PRE_POSITION_MAX_X,
1447                                         Toolkit::ScrollView::Property::SCROLL_DOMAIN_SIZE_X);
1448     }
1449     else
1450     {
1451       scrollBar.SetScrollPropertySource(Self(),
1452                                         Toolkit::ScrollView::Property::SCROLL_PRE_POSITION_Y,
1453                                         Toolkit::Scrollable::Property::SCROLL_POSITION_MIN_Y,
1454                                         Toolkit::ScrollView::Property::SCROLL_PRE_POSITION_MAX_Y,
1455                                         Toolkit::ScrollView::Property::SCROLL_DOMAIN_SIZE_Y);
1456     }
1457
1458     if(mTransientScrollBar)
1459     {
1460       // Show the scroll-indicator for a brief period
1461       Property::Map emptyMap;
1462       scrollBar.DoAction("ShowTransientIndicator", emptyMap);
1463     }
1464   }
1465   else if(mAlterChild)
1466   {
1467     BindActor(child);
1468   }
1469 }
1470
1471 void ScrollView::OnChildRemove(Actor& child)
1472 {
1473   // TODO: Actor needs a RemoveConstraint method to take out an individual constraint.
1474   UnbindActor(child);
1475
1476   ScrollBase::OnChildRemove(child);
1477 }
1478
1479 void ScrollView::StartTouchDownTimer()
1480 {
1481   if(!mTouchDownTimer)
1482   {
1483     mTouchDownTimer = Timer::New(TOUCH_DOWN_TIMER_INTERVAL);
1484     mTouchDownTimer.TickSignal().Connect(this, &ScrollView::OnTouchDownTimeout);
1485   }
1486
1487   mTouchDownTimer.Start();
1488 }
1489
1490 void ScrollView::StopTouchDownTimer()
1491 {
1492   if(mTouchDownTimer)
1493   {
1494     mTouchDownTimer.Stop();
1495   }
1496 }
1497
1498 bool ScrollView::OnTouchDownTimeout()
1499 {
1500   DALI_LOG_SCROLL_STATE("[0x%X]", this);
1501
1502   mTouchDownTimeoutReached = true;
1503
1504   unsigned int currentScrollStateFlags(mScrollStateFlags); // Cleared in StopAnimation so keep local copy for comparison
1505   if(currentScrollStateFlags & (SCROLL_ANIMATION_FLAGS | SNAP_ANIMATION_FLAGS))
1506   {
1507     DALI_LOG_SCROLL_STATE("[0x%X] Scrolling Or snapping flags set, stopping animation", this);
1508
1509     StopAnimation();
1510     if(currentScrollStateFlags & SCROLL_ANIMATION_FLAGS)
1511     {
1512       DALI_LOG_SCROLL_STATE("[0x%X] Scrolling flags set, emitting signal", this);
1513
1514       mScrollInterrupted = true;
1515       // reset domain offset as scrolling from original plane.
1516       mDomainOffset = Vector2::ZERO;
1517       Self().SetProperty(Toolkit::ScrollView::Property::SCROLL_DOMAIN_OFFSET, Vector2::ZERO);
1518
1519       UpdateLocalScrollProperties();
1520       Vector2 currentScrollPosition = GetCurrentScrollPosition();
1521       DALI_LOG_SCROLL_STATE("[0x%X] mScrollCompletedSignal 4 [%.2f, %.2f]", this, currentScrollPosition.x, currentScrollPosition.y);
1522       mScrollCompletedSignal.Emit(currentScrollPosition);
1523     }
1524   }
1525
1526   return false;
1527 }
1528
1529 bool ScrollView::OnTouch(Actor actor, const TouchEvent& touch)
1530 {
1531   if(!mSensitive)
1532   {
1533     DALI_LOG_SCROLL_STATE("[0x%X], Not Sensitive, ignoring", this);
1534
1535     // Ignore this touch event, if scrollview is insensitive.
1536     return false;
1537   }
1538
1539   // Ignore events with multiple-touch points
1540   if(touch.GetPointCount() != 1)
1541   {
1542     DALI_LOG_SCROLL_STATE("[0x%X], multiple touch, ignoring", this);
1543
1544     return false;
1545   }
1546
1547   const PointState::Type pointState = touch.GetState(0);
1548   if(pointState == PointState::DOWN)
1549   {
1550     DALI_LOG_SCROLL_STATE("[0x%X] Down", this);
1551
1552     if(mGestureStackDepth == 0)
1553     {
1554       mTouchDownTime = touch.GetTime();
1555
1556       // This allows time for a pan-gesture to start, to avoid breaking snap-animation behavior with fast flicks.
1557       // If touch-down does not become a pan (after timeout interval), then snap-animation can be interrupted.
1558       mTouchDownTimeoutReached = false;
1559       mScrollInterrupted       = false;
1560       StartTouchDownTimer();
1561     }
1562   }
1563   else if((pointState == PointState::UP) ||
1564           ((pointState == PointState::INTERRUPTED) && (touch.GetHitActor(0) == Self())))
1565   {
1566     DALI_LOG_SCROLL_STATE("[0x%X] %s", this, ((pointState == PointState::UP) ? "Up" : "Interrupted"));
1567
1568     StopTouchDownTimer();
1569
1570     // if the user touches and releases without enough movement to go
1571     // into a gesture state, then we should snap to nearest point.
1572     // otherwise our scroll could be stopped (interrupted) half way through an animation.
1573     if(mGestureStackDepth == 0 && mTouchDownTimeoutReached)
1574     {
1575       if((pointState == PointState::INTERRUPTED) ||
1576          ((touch.GetTime() - mTouchDownTime) >= MINIMUM_TIME_BETWEEN_DOWN_AND_UP_FOR_RESET))
1577       {
1578         // Reset the velocity only if down was received a while ago
1579         mLastVelocity = Vector2(0.0f, 0.0f);
1580       }
1581
1582       UpdateLocalScrollProperties();
1583       // Only finish the transform if scrolling was interrupted on down or if we are scrolling
1584       if(mScrollInterrupted || mScrolling)
1585       {
1586         DALI_LOG_SCROLL_STATE("[0x%X] Calling FinishTransform", this);
1587
1588         FinishTransform();
1589       }
1590     }
1591     mTouchDownTimeoutReached = false;
1592     mScrollInterrupted       = false;
1593   }
1594
1595   return false;
1596 }
1597
1598 bool ScrollView::OnWheelEvent(Actor actor, const WheelEvent& event)
1599 {
1600   if(!mSensitive)
1601   {
1602     // Ignore this wheel event, if scrollview is insensitive.
1603     return false;
1604   }
1605
1606   Vector2 targetScrollPosition = GetPropertyPosition();
1607
1608   if(mRulerX->IsEnabled() && !mRulerY->IsEnabled())
1609   {
1610     // If only the ruler in the X axis is enabled, scroll in the X axis.
1611     if(mRulerX->GetType() == Ruler::FREE)
1612     {
1613       // Free panning mode
1614       targetScrollPosition.x += event.GetDelta() * mWheelScrollDistanceStep.x;
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());
1622     }
1623   }
1624   else
1625   {
1626     // If the ruler in the Y axis is enabled, scroll in the Y axis.
1627     if(mRulerY->GetType() == Ruler::FREE)
1628     {
1629       // Free panning mode
1630       targetScrollPosition.y += event.GetDelta() * mWheelScrollDistanceStep.y;
1631       ClampPosition(targetScrollPosition);
1632       ScrollTo(-targetScrollPosition);
1633     }
1634     else if(!mScrolling)
1635     {
1636       // Snap mode, only respond to the event when the previous snap animation is finished.
1637       ScrollTo(GetCurrentPage() - event.GetDelta() * mRulerX->GetTotalPages());
1638     }
1639   }
1640
1641   return true;
1642 }
1643
1644 void ScrollView::ResetScrolling()
1645 {
1646   Actor self = Self();
1647   self.GetCurrentProperty(Toolkit::ScrollView::Property::SCROLL_POSITION).Get(mScrollPostPosition);
1648   mScrollPrePosition = mScrollPostPosition;
1649   DALI_LOG_SCROLL_STATE("[0x%X] Setting SCROLL_PRE_POSITION To[%.2f, %.2f]", this, mScrollPostPosition.x, mScrollPostPosition.y);
1650   self.SetProperty(Toolkit::ScrollView::Property::SCROLL_PRE_POSITION, mScrollPostPosition);
1651 }
1652
1653 void ScrollView::UpdateLocalScrollProperties()
1654 {
1655   Actor self = Self();
1656   self.GetCurrentProperty(Toolkit::ScrollView::Property::SCROLL_PRE_POSITION).Get(mScrollPrePosition);
1657   self.GetCurrentProperty(Toolkit::ScrollView::Property::SCROLL_POSITION).Get(mScrollPostPosition);
1658 }
1659
1660 // private functions
1661
1662 void ScrollView::PreAnimatedScrollSetup()
1663 {
1664   // SCROLL_PRE_POSITION is our unclamped property with wrapping
1665   // SCROLL_POSITION is our final scroll position after clamping
1666
1667   Actor self = Self();
1668
1669   Vector2 deltaPosition(mScrollPostPosition);
1670   WrapPosition(mScrollPostPosition);
1671   mDomainOffset += deltaPosition - mScrollPostPosition;
1672   Self().SetProperty(Toolkit::ScrollView::Property::SCROLL_DOMAIN_OFFSET, mDomainOffset);
1673
1674   if(mScrollStateFlags & SCROLL_X_STATE_MASK)
1675   {
1676     // already performing animation on internal x position
1677     StopAnimation(mInternalXAnimation);
1678   }
1679
1680   if(mScrollStateFlags & SCROLL_Y_STATE_MASK)
1681   {
1682     // already performing animation on internal y position
1683     StopAnimation(mInternalYAnimation);
1684   }
1685
1686   mScrollStateFlags = 0;
1687
1688   // Update Actor position with this wrapped value.
1689 }
1690
1691 void ScrollView::FinaliseAnimatedScroll()
1692 {
1693   // TODO - common animation finishing code in here
1694 }
1695
1696 void ScrollView::AnimateInternalXTo(float position, float duration, AlphaFunction alpha)
1697 {
1698   StopAnimation(mInternalXAnimation);
1699
1700   if(duration > Math::MACHINE_EPSILON_10)
1701   {
1702     Actor self = Self();
1703     DALI_LOG_SCROLL_STATE("[0x%X], Animating from[%.2f] to[%.2f]", this, self.GetCurrentProperty(Toolkit::ScrollView::Property::SCROLL_PRE_POSITION).Get<Vector2>().x, position);
1704     mInternalXAnimation = Animation::New(duration);
1705     DALI_LOG_SCROLL_STATE("[0x%X], mInternalXAnimation[0x%X]", this, mInternalXAnimation.GetObjectPtr());
1706     mInternalXAnimation.FinishedSignal().Connect(this, &ScrollView::OnScrollAnimationFinished);
1707     mInternalXAnimation.AnimateTo(Property(self, Toolkit::ScrollView::Property::SCROLL_PRE_POSITION, 0), position, alpha, TimePeriod(duration));
1708     mInternalXAnimation.Play();
1709
1710     // erase current state flags
1711     mScrollStateFlags &= ~SCROLL_X_STATE_MASK;
1712     // add internal animation state flag
1713     mScrollStateFlags |= AnimatingInternalX;
1714   }
1715 }
1716
1717 void ScrollView::AnimateInternalYTo(float position, float duration, AlphaFunction alpha)
1718 {
1719   StopAnimation(mInternalYAnimation);
1720
1721   if(duration > Math::MACHINE_EPSILON_10)
1722   {
1723     Actor self = Self();
1724     DALI_LOG_SCROLL_STATE("[0x%X], Animating from[%.2f] to[%.2f]", this, self.GetCurrentProperty(Toolkit::ScrollView::Property::SCROLL_PRE_POSITION).Get<Vector2>().y, position);
1725     mInternalYAnimation = Animation::New(duration);
1726     DALI_LOG_SCROLL_STATE("[0x%X], mInternalYAnimation[0x%X]", this, mInternalYAnimation.GetObjectPtr());
1727     mInternalYAnimation.FinishedSignal().Connect(this, &ScrollView::OnScrollAnimationFinished);
1728     mInternalYAnimation.AnimateTo(Property(self, Toolkit::ScrollView::Property::SCROLL_PRE_POSITION, 1), position, alpha, TimePeriod(duration));
1729     mInternalYAnimation.Play();
1730
1731     // erase current state flags
1732     mScrollStateFlags &= ~SCROLL_Y_STATE_MASK;
1733     // add internal animation state flag
1734     mScrollStateFlags |= AnimatingInternalY;
1735   }
1736 }
1737
1738 void ScrollView::OnScrollAnimationFinished(Animation& source)
1739 {
1740   // Guard against destruction during signal emission
1741   // Note that ScrollCompletedSignal is emitted from HandleSnapAnimationFinished()
1742   Toolkit::ScrollView handle(GetOwner());
1743
1744   bool scrollingFinished = false;
1745
1746   // update our local scroll positions
1747   UpdateLocalScrollProperties();
1748
1749   if(source == mInternalXAnimation)
1750   {
1751     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);
1752
1753     if(!(mScrollStateFlags & AnimatingInternalY))
1754     {
1755       scrollingFinished = true;
1756     }
1757     mInternalXAnimation.Reset();
1758     // wrap pre scroll x position and set it
1759     if(mWrapMode)
1760     {
1761       const RulerDomain rulerDomain = mRulerX->GetDomain();
1762       mScrollPrePosition.x          = -WrapInDomain(-mScrollPrePosition.x, rulerDomain.min, rulerDomain.max);
1763       DALI_LOG_SCROLL_STATE("[0x%X] Setting SCROLL_PRE_POSITION To[%.2f, %.2f]", this, mScrollPrePosition.x, mScrollPrePosition.y);
1764       handle.SetProperty(Toolkit::ScrollView::Property::SCROLL_PRE_POSITION, mScrollPrePosition);
1765     }
1766     SnapInternalXTo(mScrollPostPosition.x);
1767   }
1768
1769   if(source == mInternalYAnimation)
1770   {
1771     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);
1772
1773     if(!(mScrollStateFlags & AnimatingInternalX))
1774     {
1775       scrollingFinished = true;
1776     }
1777     mInternalYAnimation.Reset();
1778     if(mWrapMode)
1779     {
1780       // wrap pre scroll y position and set it
1781       const RulerDomain rulerDomain = mRulerY->GetDomain();
1782       mScrollPrePosition.y          = -WrapInDomain(-mScrollPrePosition.y, rulerDomain.min, rulerDomain.max);
1783       DALI_LOG_SCROLL_STATE("[0x%X] Setting SCROLL_PRE_POSITION To[%.2f, %.2f]", this, mScrollPrePosition.x, mScrollPrePosition.y);
1784       handle.SetProperty(Toolkit::ScrollView::Property::SCROLL_PRE_POSITION, mScrollPrePosition);
1785     }
1786     SnapInternalYTo(mScrollPostPosition.y);
1787   }
1788
1789   DALI_LOG_SCROLL_STATE("[0x%X] scrollingFinished[%d] Animation[0x%X]", this, scrollingFinished, source.GetObjectPtr());
1790
1791   if(scrollingFinished)
1792   {
1793     HandleSnapAnimationFinished();
1794   }
1795 }
1796
1797 void ScrollView::OnSnapInternalPositionFinished(Animation& source)
1798 {
1799   Actor self = Self();
1800   UpdateLocalScrollProperties();
1801   if(source == mInternalXAnimation)
1802   {
1803     DALI_LOG_SCROLL_STATE("[0x%X] Finished X PostPosition Animation", this);
1804
1805     // clear internal x animation flags
1806     mScrollStateFlags &= ~SCROLL_X_STATE_MASK;
1807     mInternalXAnimation.Reset();
1808     WrapPosition(mScrollPrePosition);
1809   }
1810   if(source == mInternalYAnimation)
1811   {
1812     DALI_LOG_SCROLL_STATE("[0x%X] Finished Y PostPosition Animation", this);
1813
1814     mScrollStateFlags &= ~SCROLL_Y_STATE_MASK;
1815     mInternalYAnimation.Reset();
1816     WrapPosition(mScrollPrePosition);
1817   }
1818 }
1819
1820 void ScrollView::SnapInternalXTo(float position)
1821 {
1822   Actor self = Self();
1823
1824   StopAnimation(mInternalXAnimation);
1825
1826   // erase current state flags
1827   mScrollStateFlags &= ~SCROLL_X_STATE_MASK;
1828
1829   // if internal x not equal to inputed parameter, animate it
1830   float duration = std::min(fabsf((position - mScrollPrePosition.x) / mMaxOvershoot.x) * mSnapOvershootDuration, mSnapOvershootDuration);
1831   DALI_LOG_SCROLL_STATE("[0x%X] duration[%.2f]", this, duration);
1832   if(duration > Math::MACHINE_EPSILON_1)
1833   {
1834     DALI_LOG_SCROLL_STATE("[0x%X] Starting X Snap Animation to[%.2f]", this, position);
1835
1836     mInternalXAnimation = Animation::New(duration);
1837     mInternalXAnimation.FinishedSignal().Connect(this, &ScrollView::OnSnapInternalPositionFinished);
1838     mInternalXAnimation.AnimateTo(Property(self, Toolkit::ScrollView::Property::SCROLL_PRE_POSITION, 0), position);
1839     mInternalXAnimation.Play();
1840
1841     // add internal animation state flag
1842     mScrollStateFlags |= SnappingInternalX;
1843   }
1844 }
1845
1846 void ScrollView::SnapInternalYTo(float position)
1847 {
1848   Actor self = Self();
1849
1850   StopAnimation(mInternalYAnimation);
1851
1852   // erase current state flags
1853   mScrollStateFlags &= ~SCROLL_Y_STATE_MASK;
1854
1855   // if internal y not equal to inputed parameter, animate it
1856   float duration = std::min(fabsf((position - mScrollPrePosition.y) / mMaxOvershoot.y) * mSnapOvershootDuration, mSnapOvershootDuration);
1857   DALI_LOG_SCROLL_STATE("[0x%X] duration[%.2f]", this, duration);
1858   if(duration > Math::MACHINE_EPSILON_1)
1859   {
1860     DALI_LOG_SCROLL_STATE("[0x%X] Starting Y Snap Animation to[%.2f]", this, position);
1861
1862     mInternalYAnimation = Animation::New(duration);
1863     mInternalYAnimation.FinishedSignal().Connect(this, &ScrollView::OnSnapInternalPositionFinished);
1864     mInternalYAnimation.AnimateTo(Property(self, Toolkit::ScrollView::Property::SCROLL_PRE_POSITION, 1), position);
1865     mInternalYAnimation.Play();
1866
1867     // add internal animation state flag
1868     mScrollStateFlags |= SnappingInternalY;
1869   }
1870 }
1871
1872 void ScrollView::GestureStarted()
1873 {
1874   // we handle the first gesture.
1875   // if we're currently doing a gesture and receive another
1876   // we continue and combine the effects of the gesture instead of reseting.
1877   if(mGestureStackDepth++ == 0)
1878   {
1879     Actor self = Self();
1880     StopTouchDownTimer();
1881     StopAnimation();
1882     mPanDelta     = Vector2::ZERO;
1883     mLastVelocity = Vector2::ZERO;
1884     if(!mScrolling)
1885     {
1886       mLockAxis = LockPossible;
1887     }
1888
1889     if(mScrollStateFlags & SCROLL_X_STATE_MASK)
1890     {
1891       StopAnimation(mInternalXAnimation);
1892     }
1893     if(mScrollStateFlags & SCROLL_Y_STATE_MASK)
1894     {
1895       StopAnimation(mInternalYAnimation);
1896     }
1897     mScrollStateFlags = 0;
1898
1899     if(mScrolling) // are we interrupting a current scroll?
1900     {
1901       // set mScrolling to false, in case user has code that interrogates mScrolling Getter() in complete.
1902       mScrolling = false;
1903       // send negative scroll position since scroll internal scroll position works as an offset for actors,
1904       // give applications the position within the domain from the scroll view's anchor position
1905       DALI_LOG_SCROLL_STATE("[0x%X] mScrollCompletedSignal 5 [%.2f, %.2f]", this, -mScrollPostPosition.x, -mScrollPostPosition.y);
1906       mScrollCompletedSignal.Emit(-mScrollPostPosition);
1907     }
1908   }
1909 }
1910
1911 void ScrollView::GestureContinuing(const Vector2& panDelta)
1912 {
1913   mPanDelta.x += panDelta.x;
1914   mPanDelta.y += panDelta.y;
1915
1916   // Save the velocity, there is a bug in PanGesture
1917   // Whereby the GestureState::FINISHED's velocity is either:
1918   // NaN (due to time delta of zero between the last two events)
1919   // or 0 (due to position being the same between the last two events)
1920
1921   // Axis Auto Lock - locks the panning to the horizontal or vertical axis if the pan
1922   // appears mostly horizontal or mostly vertical respectively.
1923   if(mAxisAutoLock)
1924   {
1925     mLockAxis = GetLockAxis(mPanDelta, mLockAxis, mAxisAutoLockGradient);
1926   } // end if mAxisAutoLock
1927 }
1928
1929 // TODO: Upgrade to use a more powerful gesture detector (one that supports multiple touches on pan - so works as pan and flick gesture)
1930 // BUG: GestureState::FINISHED doesn't always return velocity on release (due to
1931 // timeDelta between last two events being 0 sometimes, or posiiton being the same)
1932 void ScrollView::OnPan(const PanGesture& gesture)
1933 {
1934   // Guard against destruction during signal emission
1935   // Note that Emit() methods are called indirectly e.g. from within ScrollView::OnGestureEx()
1936   Actor self(Self());
1937
1938   if(!mSensitive)
1939   {
1940     DALI_LOG_SCROLL_STATE("[0x%X] Pan Ignored, Insensitive", this);
1941
1942     // If another callback on the same original signal disables sensitivity,
1943     // this callback will still be called, so we must suppress it.
1944     return;
1945   }
1946
1947   // translate Gesture input to get useful data...
1948   switch(gesture.GetState())
1949   {
1950     case GestureState::STARTED:
1951     {
1952       DALI_LOG_SCROLL_STATE("[0x%X] Pan Started", this);
1953       const Vector2& position = gesture.GetPosition();
1954       mPanStartPosition       = position - gesture.GetDisplacement();
1955       UpdateLocalScrollProperties();
1956       GestureStarted();
1957       mPanning = true;
1958       self.SetProperty(Toolkit::ScrollView::Property::PANNING, true);
1959       self.SetProperty(Toolkit::ScrollView::Property::START_PAGE_POSITION, Vector3(position.x, position.y, 0.0f));
1960
1961       mConstraints.UpdateMainInternalConstraint(*this);
1962       Toolkit::ScrollBar scrollBar = mScrollBar.GetHandle();
1963       if(scrollBar && mTransientScrollBar)
1964       {
1965         Vector3                     size         = Self().GetCurrentProperty<Vector3>(Actor::Property::SIZE);
1966         const Toolkit::RulerDomain& rulerDomainX = mRulerX->GetDomain();
1967         const Toolkit::RulerDomain& rulerDomainY = mRulerY->GetDomain();
1968
1969         if((rulerDomainX.max > size.width) || (rulerDomainY.max > size.height))
1970         {
1971           scrollBar.ShowIndicator();
1972         }
1973       }
1974       break;
1975     }
1976
1977     case GestureState::CONTINUING:
1978     {
1979       if(mPanning)
1980       {
1981         DALI_LOG_SCROLL_STATE("[0x%X] Pan Continuing", this);
1982         GestureContinuing(gesture.GetScreenDisplacement());
1983       }
1984       else
1985       {
1986         // If we do not think we are panning, then we should not do anything here
1987         return;
1988       }
1989       break;
1990     }
1991
1992     case GestureState::FINISHED:
1993     case GestureState::CANCELLED:
1994     {
1995       if(mPanning)
1996       {
1997         DALI_LOG_SCROLL_STATE("[0x%X] Pan %s", this, ((gesture.GetState() == GestureState::FINISHED) ? "Finished" : "Cancelled"));
1998
1999         UpdateLocalScrollProperties();
2000         mLastVelocity = gesture.GetVelocity();
2001         mPanning      = false;
2002         self.SetProperty(Toolkit::ScrollView::Property::PANNING, false);
2003
2004         if(mConstraints.mScrollMainInternalPrePositionConstraint)
2005         {
2006           mConstraints.mScrollMainInternalPrePositionConstraint.Remove();
2007         }
2008
2009         Toolkit::ScrollBar scrollBar = mScrollBar.GetHandle();
2010         if(scrollBar && mTransientScrollBar)
2011         {
2012           scrollBar.HideIndicator();
2013         }
2014       }
2015       else
2016       {
2017         // If we do not think we are panning, then we should not do anything here
2018         return;
2019       }
2020       break;
2021     }
2022
2023     case GestureState::POSSIBLE:
2024     case GestureState::CLEAR:
2025     {
2026       // Nothing to do, not needed.
2027       break;
2028     }
2029
2030   } // end switch(gesture.state)
2031
2032   OnGestureEx(gesture.GetState());
2033 }
2034
2035 void ScrollView::OnGestureEx(GestureState state)
2036 {
2037   // call necessary signals for application developer
2038
2039   if(state == GestureState::STARTED)
2040   {
2041     Vector2 currentScrollPosition = GetCurrentScrollPosition();
2042     Self().SetProperty(Toolkit::ScrollView::Property::SCROLLING, true);
2043     mScrolling = true;
2044     DALI_LOG_SCROLL_STATE("[0x%X] mScrollStartedSignal 2 [%.2f, %.2f]", this, currentScrollPosition.x, currentScrollPosition.y);
2045     mScrollStartedSignal.Emit(currentScrollPosition);
2046   }
2047   else if((state == GestureState::FINISHED) ||
2048           (state == GestureState::CANCELLED)) // Finished/default
2049   {
2050     // when all the gestures have finished, we finish the transform.
2051     // so if a user decides to pan (1 gesture), and then pan+zoom (2 gestures)
2052     // then stop panning (back to 1 gesture), and then stop zooming (0 gestures).
2053     // this is the point we end, and perform necessary snapping.
2054     mGestureStackDepth--;
2055     if(mGestureStackDepth == 0)
2056     {
2057       // no flick if we have not exceeded min flick distance
2058       if((fabsf(mPanDelta.x) < mMinFlickDistance.x) && (fabsf(mPanDelta.y) < mMinFlickDistance.y))
2059       {
2060         // reset flick velocity
2061         mLastVelocity = Vector2::ZERO;
2062       }
2063       FinishTransform();
2064     }
2065     else
2066     {
2067       DALI_LOG_SCROLL_STATE("[0x%X] mGestureStackDepth[%d]", this, mGestureStackDepth);
2068     }
2069   }
2070 }
2071
2072 void ScrollView::FinishTransform()
2073 {
2074   // at this stage internal x and x scroll position should have followed prescroll position exactly
2075   Actor self = Self();
2076
2077   PreAnimatedScrollSetup();
2078
2079   // convert pixels/millisecond to pixels per second
2080   bool animating = SnapWithVelocity(mLastVelocity * 1000.0f);
2081
2082   if(!animating)
2083   {
2084     // if not animating, then this pan has completed right now.
2085     SetScrollUpdateNotification(false);
2086     mScrolling = false;
2087     Self().SetProperty(Toolkit::ScrollView::Property::SCROLLING, false);
2088
2089     if(fabs(mScrollPrePosition.x - mScrollTargetPosition.x) > Math::MACHINE_EPSILON_10)
2090     {
2091       SnapInternalXTo(mScrollTargetPosition.x);
2092     }
2093     if(fabs(mScrollPrePosition.y - mScrollTargetPosition.y) > Math::MACHINE_EPSILON_10)
2094     {
2095       SnapInternalYTo(mScrollTargetPosition.y);
2096     }
2097     Vector2 currentScrollPosition = GetCurrentScrollPosition();
2098     DALI_LOG_SCROLL_STATE("[0x%X] mScrollCompletedSignal 6 [%.2f, %.2f]", this, currentScrollPosition.x, currentScrollPosition.y);
2099     mScrollCompletedSignal.Emit(currentScrollPosition);
2100   }
2101 }
2102
2103 bool ScrollView::OnAccessibilityPan(PanGesture gesture)
2104 {
2105   // Keep track of whether this is an AccessibilityPan
2106   mInAccessibilityPan = true;
2107   OnPan(gesture);
2108   mInAccessibilityPan = false;
2109
2110   return true;
2111 }
2112
2113 void ScrollView::ClampPosition(Vector2& position) const
2114 {
2115   ClampState2D clamped;
2116   ClampPosition(position, clamped);
2117 }
2118
2119 void ScrollView::ClampPosition(Vector2& position, ClampState2D& clamped) const
2120 {
2121   Vector3 size = Self().GetCurrentProperty<Vector3>(Actor::Property::SIZE);
2122
2123   ::ClampPosition(size, mRulerX, mRulerY, position, clamped);
2124 }
2125
2126 void ScrollView::WrapPosition(Vector2& position) const
2127 {
2128   if(mWrapMode)
2129   {
2130     const RulerDomain rulerDomainX = mRulerX->GetDomain();
2131     const RulerDomain rulerDomainY = mRulerY->GetDomain();
2132
2133     if(mRulerX->IsEnabled())
2134     {
2135       position.x = -WrapInDomain(-position.x, rulerDomainX.min, rulerDomainX.max);
2136     }
2137
2138     if(mRulerY->IsEnabled())
2139     {
2140       position.y = -WrapInDomain(-position.y, rulerDomainY.min, rulerDomainY.max);
2141     }
2142   }
2143 }
2144
2145 void ScrollView::SetProperty(BaseObject* object, Property::Index index, const Property::Value& value)
2146 {
2147   ScrollViewPropertyHandler::Set(object, index, value);
2148 }
2149
2150 Property::Value ScrollView::GetProperty(BaseObject* object, Property::Index index)
2151 {
2152   return ScrollViewPropertyHandler::Get(object, index);
2153 }
2154
2155 ScrollView::LockAxis GetLockAxis(const Vector2& panDelta, ScrollView::LockAxis currentLockAxis, float lockGradient)
2156 {
2157   if(panDelta.LengthSquared() > AUTOLOCK_AXIS_MINIMUM_DISTANCE2 &&
2158      currentLockAxis == ScrollView::LockPossible)
2159   {
2160     float dx = fabsf(panDelta.x);
2161     float dy = fabsf(panDelta.y);
2162     if(dx * lockGradient >= dy)
2163     {
2164       // 0.36:1 gradient to the horizontal (deviate < 20 degrees)
2165       currentLockAxis = ScrollView::LockVertical;
2166     }
2167     else if(dy * lockGradient > dx)
2168     {
2169       // 0.36:1 gradient to the vertical (deviate < 20 degrees)
2170       currentLockAxis = ScrollView::LockHorizontal;
2171     }
2172     else
2173     {
2174       currentLockAxis = ScrollView::LockNone;
2175     }
2176   }
2177   return currentLockAxis;
2178 }
2179
2180 } // namespace Internal
2181
2182 } // namespace Toolkit
2183
2184 } // namespace Dali