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