2473439853027fd315ace94cfe2f458d1b117c54
[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::LogMessage(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 AlphaFunction ScrollView::GetScrollSnapAlphaFunction() const
722 {
723   return mSnapAlphaFunction;
724 }
725
726 void ScrollView::SetScrollSnapAlphaFunction(AlphaFunction alpha)
727 {
728   mSnapAlphaFunction = alpha;
729 }
730
731 AlphaFunction ScrollView::GetScrollFlickAlphaFunction() const
732 {
733   return mFlickAlphaFunction;
734 }
735
736 void ScrollView::SetScrollFlickAlphaFunction(AlphaFunction alpha)
737 {
738   mFlickAlphaFunction = alpha;
739 }
740
741 float ScrollView::GetScrollSnapDuration() const
742 {
743   return mSnapDuration;
744 }
745
746 void ScrollView::SetScrollSnapDuration(float time)
747 {
748   mSnapDuration = time;
749 }
750
751 float ScrollView::GetScrollFlickDuration() const
752 {
753   return mFlickDuration;
754 }
755
756 void ScrollView::SetScrollFlickDuration(float time)
757 {
758   mFlickDuration = time;
759 }
760
761 void ScrollView::ApplyEffect(Toolkit::ScrollViewEffect effect)
762 {
763   Dali::Toolkit::ScrollView self = Dali::Toolkit::ScrollView::DownCast(Self());
764
765   // Assertion check to ensure effect doesn't already exist in this scrollview
766   bool effectAlreadyExistsInScrollView(false);
767   for(ScrollViewEffectIter iter = mEffects.begin(); iter != mEffects.end(); ++iter)
768   {
769     if(*iter == effect)
770     {
771       effectAlreadyExistsInScrollView = true;
772       break;
773     }
774   }
775
776   DALI_ASSERT_ALWAYS(!effectAlreadyExistsInScrollView);
777
778   // add effect to effects list
779   mEffects.push_back(effect);
780
781   // invoke Attachment request to ScrollView first
782   GetImpl(effect).Attach(self);
783 }
784
785 void ScrollView::RemoveEffect(Toolkit::ScrollViewEffect effect)
786 {
787   Dali::Toolkit::ScrollView self = Dali::Toolkit::ScrollView::DownCast(Self());
788
789   // remove effect from effects list
790   bool effectExistedInScrollView(false);
791   for(ScrollViewEffectIter iter = mEffects.begin(); iter != mEffects.end(); ++iter)
792   {
793     if(*iter == effect)
794     {
795       mEffects.erase(iter);
796       effectExistedInScrollView = true;
797       break;
798     }
799   }
800
801   // Assertion check to ensure effect existed.
802   DALI_ASSERT_ALWAYS(effectExistedInScrollView);
803
804   // invoke Detachment request to ScrollView last
805   GetImpl(effect).Detach(self);
806 }
807
808 void ScrollView::RemoveAllEffects()
809 {
810   Dali::Toolkit::ScrollView self = Dali::Toolkit::ScrollView::DownCast(Self());
811
812   for(ScrollViewEffectIter effectIter = mEffects.begin(); effectIter != mEffects.end(); ++effectIter)
813   {
814     Toolkit::ScrollViewEffect effect = *effectIter;
815
816     // invoke Detachment request to ScrollView last
817     GetImpl(effect).Detach(self);
818   }
819
820   mEffects.clear();
821 }
822
823 void ScrollView::ApplyConstraintToChildren(Constraint constraint)
824 {
825   ApplyConstraintToBoundActors(constraint);
826 }
827
828 void ScrollView::RemoveConstraintsFromChildren()
829 {
830   RemoveConstraintsFromBoundActors();
831 }
832
833 const RulerPtr ScrollView::GetRulerX() const
834 {
835   return mRulerX;
836 }
837
838 const RulerPtr ScrollView::GetRulerY() const
839 {
840   return mRulerY;
841 }
842
843 void ScrollView::SetRulerX(RulerPtr ruler)
844 {
845   mRulerX = ruler;
846
847   UpdatePropertyDomain();
848   mConstraints.UpdateMainInternalConstraint(*this);
849 }
850
851 void ScrollView::SetRulerY(RulerPtr ruler)
852 {
853   mRulerY = ruler;
854
855   UpdatePropertyDomain();
856   mConstraints.UpdateMainInternalConstraint(*this);
857 }
858
859 void ScrollView::UpdatePropertyDomain()
860 {
861   Actor   self                  = Self();
862   Vector3 size                  = self.GetTargetSize();
863   Vector2 min                   = mMinScroll;
864   Vector2 max                   = mMaxScroll;
865   bool    scrollPositionChanged = false;
866   bool    domainChanged         = false;
867
868   bool canScrollVertical   = false;
869   bool canScrollHorizontal = false;
870   UpdateLocalScrollProperties();
871   if(mRulerX->IsEnabled())
872   {
873     const Toolkit::RulerDomain& rulerDomain = mRulerX->GetDomain();
874     if(fabsf(min.x - rulerDomain.min) > Math::MACHINE_EPSILON_100 || fabsf(max.x - rulerDomain.max) > Math::MACHINE_EPSILON_100)
875     {
876       domainChanged = true;
877       min.x         = rulerDomain.min;
878       max.x         = rulerDomain.max;
879
880       // make sure new scroll value is within new domain
881       if(mScrollPrePosition.x < min.x || mScrollPrePosition.x > max.x)
882       {
883         scrollPositionChanged = true;
884         mScrollPrePosition.x  = Clamp(mScrollPrePosition.x, -(max.x - size.x), -min.x);
885       }
886     }
887     if((fabsf(rulerDomain.max - rulerDomain.min) - size.x) > Math::MACHINE_EPSILON_100)
888     {
889       canScrollHorizontal = true;
890     }
891   }
892   else if(fabs(min.x) > Math::MACHINE_EPSILON_100 || fabs(max.x) > Math::MACHINE_EPSILON_100)
893   {
894     // need to reset to 0
895     domainChanged       = true;
896     min.x               = 0.0f;
897     max.x               = 0.0f;
898     canScrollHorizontal = false;
899   }
900
901   if(mRulerY->IsEnabled())
902   {
903     const Toolkit::RulerDomain& rulerDomain = mRulerY->GetDomain();
904     if(fabsf(min.y - rulerDomain.min) > Math::MACHINE_EPSILON_100 || fabsf(max.y - rulerDomain.max) > Math::MACHINE_EPSILON_100)
905     {
906       domainChanged = true;
907       min.y         = rulerDomain.min;
908       max.y         = rulerDomain.max;
909
910       // make sure new scroll value is within new domain
911       if(mScrollPrePosition.y < min.y || mScrollPrePosition.y > max.y)
912       {
913         scrollPositionChanged = true;
914         mScrollPrePosition.y  = Clamp(mScrollPrePosition.y, -(max.y - size.y), -min.y);
915       }
916     }
917     if((fabsf(rulerDomain.max - rulerDomain.min) - size.y) > Math::MACHINE_EPSILON_100)
918     {
919       canScrollVertical = true;
920     }
921   }
922   else if(fabs(min.y) > Math::MACHINE_EPSILON_100 || fabs(max.y) > Math::MACHINE_EPSILON_100)
923   {
924     // need to reset to 0
925     domainChanged     = true;
926     min.y             = 0.0f;
927     max.y             = 0.0f;
928     canScrollVertical = false;
929   }
930
931   // avoid setting properties if possible, otherwise this will cause an entire update as well as triggering constraints using each property we update
932   if(mCanScrollVertical != canScrollVertical)
933   {
934     mCanScrollVertical = canScrollVertical;
935     self.SetProperty(Toolkit::Scrollable::Property::CAN_SCROLL_VERTICAL, canScrollVertical);
936   }
937   if(mCanScrollHorizontal != canScrollHorizontal)
938   {
939     mCanScrollHorizontal = canScrollHorizontal;
940     self.SetProperty(Toolkit::Scrollable::Property::CAN_SCROLL_HORIZONTAL, canScrollHorizontal);
941   }
942   if(scrollPositionChanged)
943   {
944     DALI_LOG_SCROLL_STATE("[0x%X] Domain Changed, setting SCROLL_PRE_POSITION To[%.2f, %.2f]", this, mScrollPrePosition.x, mScrollPrePosition.y);
945     self.SetProperty(Toolkit::ScrollView::Property::SCROLL_PRE_POSITION, mScrollPrePosition);
946   }
947   if(domainChanged)
948   {
949     mMinScroll = min;
950     mMaxScroll = max;
951     self.SetProperty(Toolkit::Scrollable::Property::SCROLL_POSITION_MIN, mMinScroll);
952     self.SetProperty(Toolkit::Scrollable::Property::SCROLL_POSITION_MAX, mMaxScroll);
953   }
954 }
955
956 bool ScrollView::GetScrollSensitive()
957 {
958   return mSensitive;
959 }
960
961 void ScrollView::SetScrollSensitive(bool sensitive)
962 {
963   Actor              self = Self();
964   PanGestureDetector panGesture(GetPanGestureDetector());
965
966   DALI_LOG_SCROLL_STATE("[0x%X] sensitive: before:[%d] setting[%d]", this, int(mSensitive), int(sensitive));
967
968   if((!mSensitive) && (sensitive))
969   {
970     mSensitive = sensitive;
971     panGesture.Attach(self);
972   }
973   else if((mSensitive) && (!sensitive))
974   {
975     DALI_LOG_SCROLL_STATE("[0x%X] BEFORE: panning:[%d]", this, int(mPanning));
976
977     // while the scroll view is panning, the state needs to be reset.
978     if(mPanning)
979     {
980       PanGesture cancelGesture = DevelPanGesture::New(GestureState::CANCELLED);
981       OnPan(cancelGesture);
982     }
983
984     panGesture.Detach(self);
985     mSensitive = sensitive;
986
987     mGestureStackDepth = 0;
988     DALI_LOG_SCROLL_STATE("[0x%X] AFTER: panning:[%d]", this, int(mPanning));
989   }
990 }
991
992 void ScrollView::SetMaxOvershoot(float overshootX, float overshootY)
993 {
994   mMaxOvershoot.x      = overshootX;
995   mMaxOvershoot.y      = overshootY;
996   mUserMaxOvershoot    = mMaxOvershoot;
997   mDefaultMaxOvershoot = false;
998   mConstraints.UpdateMainInternalConstraint(*this);
999 }
1000
1001 void ScrollView::SetSnapOvershootAlphaFunction(AlphaFunction alpha)
1002 {
1003   mSnapOvershootAlphaFunction = alpha;
1004 }
1005
1006 float ScrollView::GetSnapOvershootDuration()
1007 {
1008   return mSnapOvershootDuration;
1009 }
1010
1011 void ScrollView::SetSnapOvershootDuration(float duration)
1012 {
1013   mSnapOvershootDuration = duration;
1014 }
1015
1016 bool ScrollView::GetActorAutoSnap()
1017 {
1018   return mActorAutoSnapEnabled;
1019 }
1020
1021 void ScrollView::SetActorAutoSnap(bool enable)
1022 {
1023   mActorAutoSnapEnabled = enable;
1024 }
1025
1026 void ScrollView::SetAutoResize(bool enable)
1027 {
1028   mAutoResizeContainerEnabled = enable;
1029   // TODO: This needs a lot of issues to be addressed before working.
1030 }
1031
1032 bool ScrollView::GetWrapMode() const
1033 {
1034   return mWrapMode;
1035 }
1036
1037 void ScrollView::SetWrapMode(bool enable)
1038 {
1039   mWrapMode = enable;
1040   Self().SetProperty(Toolkit::ScrollView::Property::WRAP, enable);
1041 }
1042
1043 int ScrollView::GetScrollUpdateDistance() const
1044 {
1045   return mScrollUpdateDistance;
1046 }
1047
1048 void ScrollView::SetScrollUpdateDistance(int distance)
1049 {
1050   mScrollUpdateDistance = distance;
1051 }
1052
1053 bool ScrollView::GetAxisAutoLock() const
1054 {
1055   return mAxisAutoLock;
1056 }
1057
1058 void ScrollView::SetAxisAutoLock(bool enable)
1059 {
1060   mAxisAutoLock = enable;
1061   mConstraints.UpdateMainInternalConstraint(*this);
1062 }
1063
1064 float ScrollView::GetAxisAutoLockGradient() const
1065 {
1066   return mAxisAutoLockGradient;
1067 }
1068
1069 void ScrollView::SetAxisAutoLockGradient(float gradient)
1070 {
1071   DALI_ASSERT_DEBUG(gradient >= 0.0f && gradient <= 1.0f);
1072   mAxisAutoLockGradient = gradient;
1073   mConstraints.UpdateMainInternalConstraint(*this);
1074 }
1075
1076 float ScrollView::GetFrictionCoefficient() const
1077 {
1078   return mFrictionCoefficient;
1079 }
1080
1081 void ScrollView::SetFrictionCoefficient(float friction)
1082 {
1083   DALI_ASSERT_DEBUG(friction > 0.0f);
1084   mFrictionCoefficient = friction;
1085 }
1086
1087 float ScrollView::GetFlickSpeedCoefficient() const
1088 {
1089   return mFlickSpeedCoefficient;
1090 }
1091
1092 void ScrollView::SetFlickSpeedCoefficient(float speed)
1093 {
1094   mFlickSpeedCoefficient = speed;
1095 }
1096
1097 Vector2 ScrollView::GetMinimumDistanceForFlick() const
1098 {
1099   return mMinFlickDistance;
1100 }
1101
1102 void ScrollView::SetMinimumDistanceForFlick(const Vector2& distance)
1103 {
1104   mMinFlickDistance = distance;
1105 }
1106
1107 float ScrollView::GetMinimumSpeedForFlick() const
1108 {
1109   return mFlickSpeedThreshold;
1110 }
1111
1112 void ScrollView::SetMinimumSpeedForFlick(float speed)
1113 {
1114   mFlickSpeedThreshold = speed;
1115 }
1116
1117 float ScrollView::GetMaxFlickSpeed() const
1118 {
1119   return mMaxFlickSpeed;
1120 }
1121
1122 void ScrollView::SetMaxFlickSpeed(float speed)
1123 {
1124   mMaxFlickSpeed = speed;
1125 }
1126
1127 void ScrollView::SetWheelScrollDistanceStep(Vector2 step)
1128 {
1129   mWheelScrollDistanceStep = step;
1130 }
1131
1132 Vector2 ScrollView::GetWheelScrollDistanceStep() const
1133 {
1134   return mWheelScrollDistanceStep;
1135 }
1136
1137 unsigned int ScrollView::GetCurrentPage() const
1138 {
1139   // in case animation is currently taking place.
1140   Vector2 position = GetPropertyPosition();
1141
1142   Actor        self           = Self();
1143   unsigned int page           = 0;
1144   unsigned int pagesPerVolume = 1;
1145   unsigned int volume         = 0;
1146
1147   // if rulerX is enabled, then get page count (columns)
1148   page           = mRulerX->GetPageFromPosition(-position.x, mWrapMode);
1149   volume         = mRulerY->GetPageFromPosition(-position.y, mWrapMode);
1150   pagesPerVolume = mRulerX->GetTotalPages();
1151
1152   return volume * pagesPerVolume + page;
1153 }
1154
1155 Vector2 ScrollView::GetCurrentScrollPosition() const
1156 {
1157   return -GetPropertyPosition();
1158 }
1159
1160 void ScrollView::TransformTo(const Vector2& position,
1161                              DirectionBias  horizontalBias,
1162                              DirectionBias  verticalBias)
1163 {
1164   TransformTo(position, mSnapDuration, mSnapAlphaFunction, horizontalBias, verticalBias);
1165 }
1166
1167 void ScrollView::TransformTo(const Vector2& position, float duration, AlphaFunction alpha, DirectionBias horizontalBias, DirectionBias verticalBias)
1168 {
1169   // If this is called while the timer is running, then cancel it
1170   StopTouchDownTimer();
1171
1172   Actor self(Self());
1173
1174   // Guard against destruction during signal emission
1175   // Note that Emit() methods are called indirectly e.g. from within ScrollView::AnimateTo()
1176   Toolkit::ScrollView handle(GetOwner());
1177
1178   DALI_LOG_SCROLL_STATE("[0x%X] pos[%.2f,%.2f], duration[%.2f] bias[%d, %d]",
1179                         this,
1180                         position.x,
1181                         position.y,
1182                         duration,
1183                         int(horizontalBias),
1184                         int(verticalBias));
1185
1186   Vector2 currentScrollPosition = GetCurrentScrollPosition();
1187   self.SetProperty(Toolkit::ScrollView::Property::START_PAGE_POSITION, Vector3(currentScrollPosition));
1188
1189   if(mScrolling) // are we interrupting a current scroll?
1190   {
1191     // set mScrolling to false, in case user has code that interrogates mScrolling Getter() in complete.
1192     mScrolling = false;
1193     DALI_LOG_SCROLL_STATE("[0x%X] mScrollCompletedSignal 1 [%.2f, %.2f]", this, currentScrollPosition.x, currentScrollPosition.y);
1194     mScrollCompletedSignal.Emit(currentScrollPosition);
1195   }
1196
1197   if(mPanning) // are we interrupting a current pan?
1198   {
1199     DALI_LOG_SCROLL_STATE("[0x%X] Interrupting Pan, set to false", this);
1200     mPanning           = false;
1201     mGestureStackDepth = 0;
1202     self.SetProperty(Toolkit::ScrollView::Property::PANNING, false);
1203
1204     if(mConstraints.mScrollMainInternalPrePositionConstraint)
1205     {
1206       mConstraints.mScrollMainInternalPrePositionConstraint.Remove();
1207     }
1208   }
1209
1210   self.SetProperty(Toolkit::ScrollView::Property::SCROLLING, true);
1211   mScrolling = true;
1212
1213   DALI_LOG_SCROLL_STATE("[0x%X] mScrollStartedSignal 1 [%.2f, %.2f]", this, currentScrollPosition.x, currentScrollPosition.y);
1214   mScrollStartedSignal.Emit(currentScrollPosition);
1215   bool animating = AnimateTo(-position,
1216                              Vector2::ONE * duration,
1217                              alpha,
1218                              true,
1219                              horizontalBias,
1220                              verticalBias,
1221                              SNAP);
1222
1223   if(!animating)
1224   {
1225     // if not animating, then this pan has completed right now.
1226     self.SetProperty(Toolkit::ScrollView::Property::SCROLLING, false);
1227     mScrolling = false;
1228
1229     // If we have no duration, then in the next update frame, we will be at the position specified as we just set.
1230     // In this scenario, we cannot return the currentScrollPosition as this is out-of-date and should instead return the requested final position
1231     Vector2 completedPosition(currentScrollPosition);
1232     if(duration <= Math::MACHINE_EPSILON_10)
1233     {
1234       completedPosition = position;
1235     }
1236
1237     DALI_LOG_SCROLL_STATE("[0x%X] mScrollCompletedSignal 2 [%.2f, %.2f]", this, completedPosition.x, completedPosition.y);
1238     SetScrollUpdateNotification(false);
1239     mScrollCompletedSignal.Emit(completedPosition);
1240   }
1241 }
1242
1243 void ScrollView::ScrollTo(const Vector2& position)
1244 {
1245   ScrollTo(position, mSnapDuration);
1246 }
1247
1248 void ScrollView::ScrollTo(const Vector2& position, float duration)
1249 {
1250   ScrollTo(position, duration, DIRECTION_BIAS_NONE, DIRECTION_BIAS_NONE);
1251 }
1252
1253 void ScrollView::ScrollTo(const Vector2& position, float duration, AlphaFunction alpha)
1254 {
1255   ScrollTo(position, duration, alpha, DIRECTION_BIAS_NONE, DIRECTION_BIAS_NONE);
1256 }
1257
1258 void ScrollView::ScrollTo(const Vector2& position, float duration, DirectionBias horizontalBias, DirectionBias verticalBias)
1259 {
1260   ScrollTo(position, duration, mSnapAlphaFunction, horizontalBias, verticalBias);
1261 }
1262
1263 void ScrollView::ScrollTo(const Vector2& position, float duration, AlphaFunction alpha, DirectionBias horizontalBias, DirectionBias verticalBias)
1264 {
1265   DALI_LOG_SCROLL_STATE("[0x%X] position[%.2f, %.2f] duration[%.2f], bias[%d, %d]", this, position.x, position.y, duration, int(horizontalBias), int(verticalBias));
1266   TransformTo(position, duration, alpha, horizontalBias, verticalBias);
1267 }
1268
1269 void ScrollView::ScrollTo(unsigned int page)
1270 {
1271   ScrollTo(page, mSnapDuration);
1272 }
1273
1274 void ScrollView::ScrollTo(unsigned int page, float duration, DirectionBias bias)
1275 {
1276   Vector2      position;
1277   unsigned int volume;
1278   unsigned int libraries;
1279
1280   // The position to scroll to is continuous and linear
1281   // unless a domain has been enabled on the X axis.
1282   // or if WrapMode has been enabled.
1283   bool carryX = mRulerX->GetDomain().enabled | mWrapMode;
1284   bool carryY = mRulerY->GetDomain().enabled | mWrapMode;
1285
1286   position.x = mRulerX->GetPositionFromPage(page, volume, carryX);
1287   position.y = mRulerY->GetPositionFromPage(volume, libraries, carryY);
1288
1289   ScrollTo(position, duration, bias, bias);
1290 }
1291
1292 void ScrollView::ScrollTo(Actor& actor)
1293 {
1294   ScrollTo(actor, mSnapDuration);
1295 }
1296
1297 void ScrollView::ScrollTo(Actor& actor, float duration)
1298 {
1299   DALI_ASSERT_ALWAYS(actor.GetParent() == Self());
1300
1301   Actor   self        = Self();
1302   Vector3 size        = self.GetCurrentProperty<Vector3>(Actor::Property::SIZE);
1303   Vector3 position    = actor.GetCurrentProperty<Vector3>(Actor::Property::POSITION);
1304   Vector2 prePosition = GetPropertyPrePosition();
1305   position.GetVectorXY() -= prePosition;
1306
1307   ScrollTo(Vector2(position.x - size.width * 0.5f, position.y - size.height * 0.5f), duration);
1308 }
1309
1310 Actor ScrollView::FindClosestActor()
1311 {
1312   Actor   self = Self();
1313   Vector3 size = self.GetCurrentProperty<Vector3>(Actor::Property::SIZE);
1314
1315   return FindClosestActorToPosition(Vector3(size.width * 0.5f, size.height * 0.5f, 0.0f));
1316 }
1317
1318 Actor ScrollView::FindClosestActorToPosition(const Vector3& position, FindDirection dirX, FindDirection dirY, FindDirection dirZ)
1319 {
1320   return ::FindClosestActorToPosition(Self(), mInternalActor, position, dirX, dirY, dirZ);
1321 }
1322
1323 bool ScrollView::ScrollToSnapPoint()
1324 {
1325   DALI_LOG_SCROLL_STATE("[0x%X]", this);
1326   Vector2 stationaryVelocity = Vector2(0.0f, 0.0f);
1327   return SnapWithVelocity(stationaryVelocity);
1328 }
1329
1330 bool ScrollView::SnapWithVelocity(Vector2 velocity)
1331 {
1332   Vector2       positionSnap     = mScrollPrePosition;
1333   Vector2       positionDuration = Vector2::ONE * mSnapDuration;
1334   AlphaFunction alphaFunction    = mSnapAlphaFunction;
1335   bool          isFlick;
1336   bool          isFreeFlick;
1337
1338   ::SnapWithVelocity(*this, mRulerX, mRulerY, mLockAxis, velocity, mMaxOvershoot, positionSnap, positionDuration, alphaFunction, mInAccessibilityPan, isFlick, isFreeFlick);
1339
1340   bool animating = AnimateTo(positionSnap, positionDuration, alphaFunction, false, DIRECTION_BIAS_NONE, DIRECTION_BIAS_NONE, isFlick || isFreeFlick ? FLICK : SNAP);
1341
1342   return animating;
1343 }
1344
1345 void ScrollView::StopAnimation(void)
1346 {
1347   // Clear Snap animation if exists.
1348   StopAnimation(mInternalXAnimation);
1349   StopAnimation(mInternalYAnimation);
1350   mScrollStateFlags = 0;
1351   // remove scroll animation flags
1352   HandleStoppedAnimation();
1353 }
1354
1355 void ScrollView::StopAnimation(Animation& animation)
1356 {
1357   if(animation)
1358   {
1359     animation.Stop();
1360     animation.Reset();
1361   }
1362 }
1363
1364 bool ScrollView::AnimateTo(const Vector2& position, const Vector2& positionDuration, AlphaFunction alpha, bool findShortcuts, DirectionBias horizontalBias, DirectionBias verticalBias, SnapType snapType)
1365 {
1366   // Here we perform an animation on a number of properties (depending on which have changed)
1367   // The animation is applied to all ScrollBases
1368   Actor self            = Self();
1369   mScrollTargetPosition = position;
1370   float totalDuration   = 0.0f;
1371
1372   bool positionChanged = (mScrollTargetPosition != mScrollPostPosition);
1373
1374   if(positionChanged)
1375   {
1376     totalDuration = std::max(totalDuration, positionDuration.x);
1377     totalDuration = std::max(totalDuration, positionDuration.y);
1378   }
1379   else
1380   {
1381     // try to animate for a frame, on some occasions update will be changing scroll value while event side thinks it hasnt changed
1382     totalDuration   = 0.01f;
1383     positionChanged = true;
1384   }
1385
1386   StopAnimation();
1387
1388   // Position Delta ///////////////////////////////////////////////////////
1389   if(positionChanged)
1390   {
1391     mConstraints.UpdateMainInternalConstraint(*this);
1392     if(mWrapMode && findShortcuts)
1393     {
1394       // In Wrap Mode, the shortest distance is a little less intuitive...
1395       const RulerDomain rulerDomainX = mRulerX->GetDomain();
1396       const RulerDomain rulerDomainY = mRulerY->GetDomain();
1397
1398       if(mRulerX->IsEnabled())
1399       {
1400         float dir               = VectorInDomain(-mScrollPrePosition.x, -mScrollTargetPosition.x, rulerDomainX.min, rulerDomainX.max, horizontalBias);
1401         mScrollTargetPosition.x = mScrollPrePosition.x + -dir;
1402       }
1403
1404       if(mRulerY->IsEnabled())
1405       {
1406         float dir               = VectorInDomain(-mScrollPrePosition.y, -mScrollTargetPosition.y, rulerDomainY.min, rulerDomainY.max, verticalBias);
1407         mScrollTargetPosition.y = mScrollPrePosition.y + -dir;
1408       }
1409     }
1410
1411     // note we have two separate animations for X & Y, this deals with sliding diagonally and hitting
1412     // a horizonal/vertical wall.delay
1413     AnimateInternalXTo(mScrollTargetPosition.x, positionDuration.x, alpha);
1414     AnimateInternalYTo(mScrollTargetPosition.y, positionDuration.y, alpha);
1415
1416     if(!(mScrollStateFlags & SCROLL_ANIMATION_FLAGS))
1417     {
1418       DALI_LOG_SCROLL_STATE("[0x%X] Setting SCROLL_PRE_POSITION To[%.2f, %.2f]", this, mScrollTargetPosition.x, mScrollTargetPosition.y);
1419       self.SetProperty(Toolkit::ScrollView::Property::SCROLL_PRE_POSITION, mScrollTargetPosition);
1420       mScrollPrePosition  = mScrollTargetPosition;
1421       mScrollPostPosition = mScrollTargetPosition;
1422       WrapPosition(mScrollPostPosition);
1423     }
1424
1425     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);
1426     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);
1427   }
1428
1429   SetScrollUpdateNotification(true);
1430
1431   // Always send a snap event when AnimateTo is called.
1432   Toolkit::ScrollView::SnapEvent snapEvent;
1433   snapEvent.type     = snapType;
1434   snapEvent.position = -mScrollTargetPosition;
1435   snapEvent.duration = totalDuration;
1436
1437   DALI_LOG_SCROLL_STATE("[0x%X] mSnapStartedSignal [%.2f, %.2f]", this, snapEvent.position.x, snapEvent.position.y);
1438   mSnapStartedSignal.Emit(snapEvent);
1439
1440   return (mScrollStateFlags & SCROLL_ANIMATION_FLAGS) != 0;
1441 }
1442
1443 void ScrollView::EnableScrollOvershoot(bool enable)
1444 {
1445   if(enable)
1446   {
1447     if(!mOvershootIndicator)
1448     {
1449       mOvershootIndicator = ScrollOvershootIndicator::New();
1450     }
1451
1452     mOvershootIndicator->AttachToScrollable(*this);
1453   }
1454   else
1455   {
1456     mMaxOvershoot = mUserMaxOvershoot;
1457
1458     if(mOvershootIndicator)
1459     {
1460       mOvershootIndicator->DetachFromScrollable(*this);
1461     }
1462   }
1463
1464   mConstraints.UpdateMainInternalConstraint(*this);
1465 }
1466
1467 void ScrollView::AddOverlay(Actor actor)
1468 {
1469   actor.SetProperty(Actor::Property::DRAW_MODE, DrawMode::OVERLAY_2D);
1470   mInternalActor.Add(actor);
1471 }
1472
1473 void ScrollView::RemoveOverlay(Actor actor)
1474 {
1475   mInternalActor.Remove(actor);
1476 }
1477
1478 void ScrollView::SetOvershootSize(const Vector2& size)
1479 {
1480   mOvershootSize = size;
1481   if(IsOvershootEnabled() && mOvershootIndicator)
1482   {
1483     mOvershootIndicator->AttachToScrollable(*this);
1484   }
1485 }
1486
1487 void ScrollView::SetOvershootEffectColor(const Vector4& color)
1488 {
1489   mOvershootEffectColor = color;
1490   if(mOvershootIndicator)
1491   {
1492     mOvershootIndicator->SetOvershootEffectColor(color);
1493   }
1494 }
1495
1496 void ScrollView::SetScrollingDirection(Radian direction, Radian threshold)
1497 {
1498   PanGestureDetector panGesture(GetPanGestureDetector());
1499
1500   // First remove just in case we have some set, then add.
1501   panGesture.RemoveDirection(direction);
1502   panGesture.AddDirection(direction, threshold);
1503 }
1504
1505 void ScrollView::RemoveScrollingDirection(Radian direction)
1506 {
1507   PanGestureDetector panGesture(GetPanGestureDetector());
1508   panGesture.RemoveDirection(direction);
1509 }
1510
1511 Toolkit::ScrollView::SnapStartedSignalType& ScrollView::SnapStartedSignal()
1512 {
1513   return mSnapStartedSignal;
1514 }
1515
1516 bool ScrollView::AccessibleImpl::ScrollToChild(Actor child)
1517 {
1518   auto scrollView = Dali::Toolkit::ScrollView::DownCast(Self());
1519   if(Toolkit::GetImpl(scrollView).FindClosestActor() == child)
1520   {
1521     return false;
1522   }
1523
1524   // FIXME: ScrollTo does not work (snaps back to original position)
1525   scrollView.ScrollTo(child, scrollView.GetScrollFlickDuration());
1526   return true;
1527 }
1528
1529 void ScrollView::FindAndUnbindActor(Actor child)
1530 {
1531   UnbindActor(child);
1532 }
1533
1534 Vector2 ScrollView::GetPropertyPrePosition() const
1535 {
1536   Vector2 position = Self().GetCurrentProperty<Vector2>(Toolkit::ScrollView::Property::SCROLL_PRE_POSITION);
1537   WrapPosition(position);
1538   return position;
1539 }
1540
1541 Vector2 ScrollView::GetPropertyPosition() const
1542 {
1543   Vector2 position = Self().GetCurrentProperty<Vector2>(Toolkit::ScrollView::Property::SCROLL_POSITION);
1544   WrapPosition(position);
1545
1546   return position;
1547 }
1548
1549 void ScrollView::HandleStoppedAnimation()
1550 {
1551   SetScrollUpdateNotification(false);
1552 }
1553
1554 void ScrollView::HandleSnapAnimationFinished()
1555 {
1556   // Emit Signal that scrolling has completed.
1557   mScrolling = false;
1558   Actor self = Self();
1559   self.SetProperty(Toolkit::ScrollView::Property::SCROLLING, false);
1560
1561   Vector2 deltaPosition(mScrollPrePosition);
1562
1563   UpdateLocalScrollProperties();
1564   WrapPosition(mScrollPrePosition);
1565   DALI_LOG_SCROLL_STATE("[0x%X] Setting SCROLL_PRE_POSITION To[%.2f, %.2f]", this, mScrollPrePosition.x, mScrollPrePosition.y);
1566   self.SetProperty(Toolkit::ScrollView::Property::SCROLL_PRE_POSITION, mScrollPrePosition);
1567
1568   Vector2 currentScrollPosition = GetCurrentScrollPosition();
1569   DALI_LOG_SCROLL_STATE("[0x%X] mScrollCompletedSignal 3 current[%.2f, %.2f], mScrollTargetPosition[%.2f, %.2f]", this, currentScrollPosition.x, currentScrollPosition.y, -mScrollTargetPosition.x, -mScrollTargetPosition.y);
1570   mScrollCompletedSignal.Emit(currentScrollPosition);
1571
1572   mDomainOffset += deltaPosition - mScrollPostPosition;
1573   self.SetProperty(Toolkit::ScrollView::Property::SCROLL_DOMAIN_OFFSET, mDomainOffset);
1574   HandleStoppedAnimation();
1575 }
1576
1577 void ScrollView::SetScrollUpdateNotification(bool enabled)
1578 {
1579   Actor self = Self();
1580   if(mScrollXUpdateNotification)
1581   {
1582     // disconnect now to avoid a notification before removed from update thread
1583     mScrollXUpdateNotification.NotifySignal().Disconnect(this, &ScrollView::OnScrollUpdateNotification);
1584     self.RemovePropertyNotification(mScrollXUpdateNotification);
1585     mScrollXUpdateNotification.Reset();
1586   }
1587   if(enabled && !mScrollUpdatedSignal.Empty())
1588   {
1589     // Only set up the notification when the application has connected to the updated signal
1590     mScrollXUpdateNotification = self.AddPropertyNotification(Toolkit::ScrollView::Property::SCROLL_POSITION, 0, StepCondition(mScrollUpdateDistance, 0.0f));
1591     mScrollXUpdateNotification.NotifySignal().Connect(this, &ScrollView::OnScrollUpdateNotification);
1592   }
1593   if(mScrollYUpdateNotification)
1594   {
1595     // disconnect now to avoid a notification before removed from update thread
1596     mScrollYUpdateNotification.NotifySignal().Disconnect(this, &ScrollView::OnScrollUpdateNotification);
1597     self.RemovePropertyNotification(mScrollYUpdateNotification);
1598     mScrollYUpdateNotification.Reset();
1599   }
1600   if(enabled && !mScrollUpdatedSignal.Empty())
1601   {
1602     // Only set up the notification when the application has connected to the updated signal
1603     mScrollYUpdateNotification = self.AddPropertyNotification(Toolkit::ScrollView::Property::SCROLL_POSITION, 1, StepCondition(mScrollUpdateDistance, 0.0f));
1604     mScrollYUpdateNotification.NotifySignal().Connect(this, &ScrollView::OnScrollUpdateNotification);
1605   }
1606 }
1607
1608 void ScrollView::OnScrollUpdateNotification(Dali::PropertyNotification& source)
1609 {
1610   // Guard against destruction during signal emission
1611   Toolkit::ScrollView handle(GetOwner());
1612
1613   Vector2 currentScrollPosition = GetCurrentScrollPosition();
1614   mScrollUpdatedSignal.Emit(currentScrollPosition);
1615 }
1616
1617 bool ScrollView::DoConnectSignal(BaseObject* object, ConnectionTrackerInterface* tracker, const std::string& signalName, FunctorDelegate* functor)
1618 {
1619   Dali::BaseHandle handle(object);
1620
1621   bool                connected(true);
1622   Toolkit::ScrollView view = Toolkit::ScrollView::DownCast(handle);
1623
1624   if(0 == strcmp(signalName.c_str(), SIGNAL_SNAP_STARTED))
1625   {
1626     view.SnapStartedSignal().Connect(tracker, functor);
1627   }
1628   else
1629   {
1630     // signalName does not match any signal
1631     connected = false;
1632   }
1633
1634   return connected;
1635 }
1636
1637 void ScrollView::OnSizeAnimation(Animation& animation, const Vector3& targetSize)
1638 {
1639   // need to update domain properties for new size
1640   UpdatePropertyDomain();
1641 }
1642
1643 void ScrollView::OnSizeSet(const Vector3& size)
1644 {
1645   // need to update domain properties for new size
1646   if(mDefaultMaxOvershoot)
1647   {
1648     mUserMaxOvershoot.x = size.x * 0.5f;
1649     mUserMaxOvershoot.y = size.y * 0.5f;
1650     if(!IsOvershootEnabled())
1651     {
1652       mMaxOvershoot = mUserMaxOvershoot;
1653     }
1654   }
1655   UpdatePropertyDomain();
1656   mConstraints.UpdateMainInternalConstraint(*this);
1657   if(IsOvershootEnabled())
1658   {
1659     mOvershootIndicator->Reset();
1660   }
1661
1662   ScrollBase::OnSizeSet(size);
1663 }
1664
1665 void ScrollView::OnChildAdd(Actor& child)
1666 {
1667   ScrollBase::OnChildAdd(child);
1668
1669   Dali::Toolkit::ScrollBar scrollBar = Dali::Toolkit::ScrollBar::DownCast(child);
1670   if(scrollBar)
1671   {
1672     mScrollBar = scrollBar;
1673     scrollBar.SetProperty(Dali::Actor::Property::NAME, "ScrollBar");
1674
1675     mInternalActor.Add(scrollBar);
1676     if(scrollBar.GetScrollDirection() == Toolkit::ScrollBar::HORIZONTAL)
1677     {
1678       scrollBar.SetScrollPropertySource(Self(),
1679                                         Toolkit::ScrollView::Property::SCROLL_PRE_POSITION_X,
1680                                         Toolkit::Scrollable::Property::SCROLL_POSITION_MIN_X,
1681                                         Toolkit::ScrollView::Property::SCROLL_PRE_POSITION_MAX_X,
1682                                         Toolkit::ScrollView::Property::SCROLL_DOMAIN_SIZE_X);
1683     }
1684     else
1685     {
1686       scrollBar.SetScrollPropertySource(Self(),
1687                                         Toolkit::ScrollView::Property::SCROLL_PRE_POSITION_Y,
1688                                         Toolkit::Scrollable::Property::SCROLL_POSITION_MIN_Y,
1689                                         Toolkit::ScrollView::Property::SCROLL_PRE_POSITION_MAX_Y,
1690                                         Toolkit::ScrollView::Property::SCROLL_DOMAIN_SIZE_Y);
1691     }
1692
1693     if(mTransientScrollBar)
1694     {
1695       // Show the scroll-indicator for a brief period
1696       Property::Map emptyMap;
1697       scrollBar.DoAction("ShowTransientIndicator", emptyMap);
1698     }
1699   }
1700   else if(mAlterChild)
1701   {
1702     BindActor(child);
1703   }
1704 }
1705
1706 void ScrollView::OnChildRemove(Actor& child)
1707 {
1708   // TODO: Actor needs a RemoveConstraint method to take out an individual constraint.
1709   UnbindActor(child);
1710
1711   ScrollBase::OnChildRemove(child);
1712 }
1713
1714 void ScrollView::StartTouchDownTimer()
1715 {
1716   if(!mTouchDownTimer)
1717   {
1718     mTouchDownTimer = Timer::New(TOUCH_DOWN_TIMER_INTERVAL);
1719     mTouchDownTimer.TickSignal().Connect(this, &ScrollView::OnTouchDownTimeout);
1720   }
1721
1722   mTouchDownTimer.Start();
1723 }
1724
1725 void ScrollView::StopTouchDownTimer()
1726 {
1727   if(mTouchDownTimer)
1728   {
1729     mTouchDownTimer.Stop();
1730   }
1731 }
1732
1733 bool ScrollView::OnTouchDownTimeout()
1734 {
1735   DALI_LOG_SCROLL_STATE("[0x%X]", this);
1736
1737   mTouchDownTimeoutReached = true;
1738
1739   unsigned int currentScrollStateFlags(mScrollStateFlags); // Cleared in StopAnimation so keep local copy for comparison
1740   if(currentScrollStateFlags & (SCROLL_ANIMATION_FLAGS | SNAP_ANIMATION_FLAGS))
1741   {
1742     DALI_LOG_SCROLL_STATE("[0x%X] Scrolling Or snapping flags set, stopping animation", this);
1743
1744     StopAnimation();
1745     if(currentScrollStateFlags & SCROLL_ANIMATION_FLAGS)
1746     {
1747       DALI_LOG_SCROLL_STATE("[0x%X] Scrolling flags set, emitting signal", this);
1748
1749       mScrollInterrupted = true;
1750       // reset domain offset as scrolling from original plane.
1751       mDomainOffset = Vector2::ZERO;
1752       Self().SetProperty(Toolkit::ScrollView::Property::SCROLL_DOMAIN_OFFSET, Vector2::ZERO);
1753
1754       UpdateLocalScrollProperties();
1755       Vector2 currentScrollPosition = GetCurrentScrollPosition();
1756       DALI_LOG_SCROLL_STATE("[0x%X] mScrollCompletedSignal 4 [%.2f, %.2f]", this, currentScrollPosition.x, currentScrollPosition.y);
1757       mScrollCompletedSignal.Emit(currentScrollPosition);
1758     }
1759   }
1760
1761   return false;
1762 }
1763
1764 bool ScrollView::OnTouch(Actor actor, const TouchEvent& touch)
1765 {
1766   if(!mSensitive)
1767   {
1768     DALI_LOG_SCROLL_STATE("[0x%X], Not Sensitive, ignoring", this);
1769
1770     // Ignore this touch event, if scrollview is insensitive.
1771     return false;
1772   }
1773
1774   // Ignore events with multiple-touch points
1775   if(touch.GetPointCount() != 1)
1776   {
1777     DALI_LOG_SCROLL_STATE("[0x%X], multiple touch, ignoring", this);
1778
1779     return false;
1780   }
1781
1782   const PointState::Type pointState = touch.GetState(0);
1783   if(pointState == PointState::DOWN)
1784   {
1785     DALI_LOG_SCROLL_STATE("[0x%X] Down", this);
1786
1787     if(mGestureStackDepth == 0)
1788     {
1789       mTouchDownTime = touch.GetTime();
1790
1791       // This allows time for a pan-gesture to start, to avoid breaking snap-animation behavior with fast flicks.
1792       // If touch-down does not become a pan (after timeout interval), then snap-animation can be interrupted.
1793       mTouchDownTimeoutReached = false;
1794       mScrollInterrupted       = false;
1795       StartTouchDownTimer();
1796     }
1797   }
1798   else if((pointState == PointState::UP) ||
1799           ((pointState == PointState::INTERRUPTED) && (touch.GetHitActor(0) == Self())))
1800   {
1801     DALI_LOG_SCROLL_STATE("[0x%X] %s", this, ((pointState == PointState::UP) ? "Up" : "Interrupted"));
1802
1803     StopTouchDownTimer();
1804
1805     // if the user touches and releases without enough movement to go
1806     // into a gesture state, then we should snap to nearest point.
1807     // otherwise our scroll could be stopped (interrupted) half way through an animation.
1808     if(mGestureStackDepth == 0 && mTouchDownTimeoutReached)
1809     {
1810       if((pointState == PointState::INTERRUPTED) ||
1811          ((touch.GetTime() - mTouchDownTime) >= MINIMUM_TIME_BETWEEN_DOWN_AND_UP_FOR_RESET))
1812       {
1813         // Reset the velocity only if down was received a while ago
1814         mLastVelocity = Vector2(0.0f, 0.0f);
1815       }
1816
1817       UpdateLocalScrollProperties();
1818       // Only finish the transform if scrolling was interrupted on down or if we are scrolling
1819       if(mScrollInterrupted || mScrolling)
1820       {
1821         DALI_LOG_SCROLL_STATE("[0x%X] Calling FinishTransform", this);
1822
1823         FinishTransform();
1824       }
1825     }
1826     mTouchDownTimeoutReached = false;
1827     mScrollInterrupted       = false;
1828   }
1829
1830   return false;
1831 }
1832
1833 bool ScrollView::OnWheelEvent(Actor actor, const WheelEvent& event)
1834 {
1835   if(!mSensitive)
1836   {
1837     // Ignore this wheel event, if scrollview is insensitive.
1838     return false;
1839   }
1840
1841   Vector2 targetScrollPosition = GetPropertyPosition();
1842
1843   if(mRulerX->IsEnabled() && !mRulerY->IsEnabled())
1844   {
1845     // If only the ruler in the X axis is enabled, scroll in the X axis.
1846     if(mRulerX->GetType() == Ruler::FREE)
1847     {
1848       // Free panning mode
1849       targetScrollPosition.x += event.GetDelta() * mWheelScrollDistanceStep.x;
1850       ClampPosition(targetScrollPosition);
1851       ScrollTo(-targetScrollPosition);
1852     }
1853     else if(!mScrolling)
1854     {
1855       // Snap mode, only respond to the event when the previous snap animation is finished.
1856       ScrollTo(GetCurrentPage() - event.GetDelta());
1857     }
1858   }
1859   else
1860   {
1861     // If the ruler in the Y axis is enabled, scroll in the Y axis.
1862     if(mRulerY->GetType() == Ruler::FREE)
1863     {
1864       // Free panning mode
1865       targetScrollPosition.y += event.GetDelta() * mWheelScrollDistanceStep.y;
1866       ClampPosition(targetScrollPosition);
1867       ScrollTo(-targetScrollPosition);
1868     }
1869     else if(!mScrolling)
1870     {
1871       // Snap mode, only respond to the event when the previous snap animation is finished.
1872       ScrollTo(GetCurrentPage() - event.GetDelta() * mRulerX->GetTotalPages());
1873     }
1874   }
1875
1876   return true;
1877 }
1878
1879 void ScrollView::ResetScrolling()
1880 {
1881   Actor self = Self();
1882   self.GetCurrentProperty(Toolkit::ScrollView::Property::SCROLL_POSITION).Get(mScrollPostPosition);
1883   mScrollPrePosition = mScrollPostPosition;
1884   DALI_LOG_SCROLL_STATE("[0x%X] Setting SCROLL_PRE_POSITION To[%.2f, %.2f]", this, mScrollPostPosition.x, mScrollPostPosition.y);
1885   self.SetProperty(Toolkit::ScrollView::Property::SCROLL_PRE_POSITION, mScrollPostPosition);
1886 }
1887
1888 void ScrollView::UpdateLocalScrollProperties()
1889 {
1890   Actor self = Self();
1891   self.GetCurrentProperty(Toolkit::ScrollView::Property::SCROLL_PRE_POSITION).Get(mScrollPrePosition);
1892   self.GetCurrentProperty(Toolkit::ScrollView::Property::SCROLL_POSITION).Get(mScrollPostPosition);
1893 }
1894
1895 // private functions
1896
1897 void ScrollView::PreAnimatedScrollSetup()
1898 {
1899   // SCROLL_PRE_POSITION is our unclamped property with wrapping
1900   // SCROLL_POSITION is our final scroll position after clamping
1901
1902   Actor self = Self();
1903
1904   Vector2 deltaPosition(mScrollPostPosition);
1905   WrapPosition(mScrollPostPosition);
1906   mDomainOffset += deltaPosition - mScrollPostPosition;
1907   Self().SetProperty(Toolkit::ScrollView::Property::SCROLL_DOMAIN_OFFSET, mDomainOffset);
1908
1909   if(mScrollStateFlags & SCROLL_X_STATE_MASK)
1910   {
1911     // already performing animation on internal x position
1912     StopAnimation(mInternalXAnimation);
1913   }
1914
1915   if(mScrollStateFlags & SCROLL_Y_STATE_MASK)
1916   {
1917     // already performing animation on internal y position
1918     StopAnimation(mInternalYAnimation);
1919   }
1920
1921   mScrollStateFlags = 0;
1922
1923   // Update Actor position with this wrapped value.
1924 }
1925
1926 void ScrollView::FinaliseAnimatedScroll()
1927 {
1928   // TODO - common animation finishing code in here
1929 }
1930
1931 void ScrollView::AnimateInternalXTo(float position, float duration, AlphaFunction alpha)
1932 {
1933   StopAnimation(mInternalXAnimation);
1934
1935   if(duration > Math::MACHINE_EPSILON_10)
1936   {
1937     Actor self = Self();
1938     DALI_LOG_SCROLL_STATE("[0x%X], Animating from[%.2f] to[%.2f]", this, self.GetCurrentProperty(Toolkit::ScrollView::Property::SCROLL_PRE_POSITION).Get<Vector2>().x, position);
1939     mInternalXAnimation = Animation::New(duration);
1940     DALI_LOG_SCROLL_STATE("[0x%X], mInternalXAnimation[0x%X]", this, mInternalXAnimation.GetObjectPtr());
1941     mInternalXAnimation.FinishedSignal().Connect(this, &ScrollView::OnScrollAnimationFinished);
1942     mInternalXAnimation.AnimateTo(Property(self, Toolkit::ScrollView::Property::SCROLL_PRE_POSITION, 0), position, alpha, TimePeriod(duration));
1943     mInternalXAnimation.Play();
1944
1945     // erase current state flags
1946     mScrollStateFlags &= ~SCROLL_X_STATE_MASK;
1947     // add internal animation state flag
1948     mScrollStateFlags |= AnimatingInternalX;
1949   }
1950 }
1951
1952 void ScrollView::AnimateInternalYTo(float position, float duration, AlphaFunction alpha)
1953 {
1954   StopAnimation(mInternalYAnimation);
1955
1956   if(duration > Math::MACHINE_EPSILON_10)
1957   {
1958     Actor self = Self();
1959     DALI_LOG_SCROLL_STATE("[0x%X], Animating from[%.2f] to[%.2f]", this, self.GetCurrentProperty(Toolkit::ScrollView::Property::SCROLL_PRE_POSITION).Get<Vector2>().y, position);
1960     mInternalYAnimation = Animation::New(duration);
1961     DALI_LOG_SCROLL_STATE("[0x%X], mInternalYAnimation[0x%X]", this, mInternalYAnimation.GetObjectPtr());
1962     mInternalYAnimation.FinishedSignal().Connect(this, &ScrollView::OnScrollAnimationFinished);
1963     mInternalYAnimation.AnimateTo(Property(self, Toolkit::ScrollView::Property::SCROLL_PRE_POSITION, 1), position, alpha, TimePeriod(duration));
1964     mInternalYAnimation.Play();
1965
1966     // erase current state flags
1967     mScrollStateFlags &= ~SCROLL_Y_STATE_MASK;
1968     // add internal animation state flag
1969     mScrollStateFlags |= AnimatingInternalY;
1970   }
1971 }
1972
1973 void ScrollView::OnScrollAnimationFinished(Animation& source)
1974 {
1975   // Guard against destruction during signal emission
1976   // Note that ScrollCompletedSignal is emitted from HandleSnapAnimationFinished()
1977   Toolkit::ScrollView handle(GetOwner());
1978
1979   bool scrollingFinished = false;
1980
1981   // update our local scroll positions
1982   UpdateLocalScrollProperties();
1983
1984   if(source == mInternalXAnimation)
1985   {
1986     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);
1987
1988     if(!(mScrollStateFlags & AnimatingInternalY))
1989     {
1990       scrollingFinished = true;
1991     }
1992     mInternalXAnimation.Reset();
1993     // wrap pre scroll x position and set it
1994     if(mWrapMode)
1995     {
1996       const RulerDomain rulerDomain = mRulerX->GetDomain();
1997       mScrollPrePosition.x          = -WrapInDomain(-mScrollPrePosition.x, rulerDomain.min, rulerDomain.max);
1998       DALI_LOG_SCROLL_STATE("[0x%X] Setting SCROLL_PRE_POSITION To[%.2f, %.2f]", this, mScrollPrePosition.x, mScrollPrePosition.y);
1999       handle.SetProperty(Toolkit::ScrollView::Property::SCROLL_PRE_POSITION, mScrollPrePosition);
2000     }
2001     SnapInternalXTo(mScrollPostPosition.x);
2002   }
2003
2004   if(source == mInternalYAnimation)
2005   {
2006     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);
2007
2008     if(!(mScrollStateFlags & AnimatingInternalX))
2009     {
2010       scrollingFinished = true;
2011     }
2012     mInternalYAnimation.Reset();
2013     if(mWrapMode)
2014     {
2015       // wrap pre scroll y position and set it
2016       const RulerDomain rulerDomain = mRulerY->GetDomain();
2017       mScrollPrePosition.y          = -WrapInDomain(-mScrollPrePosition.y, rulerDomain.min, rulerDomain.max);
2018       DALI_LOG_SCROLL_STATE("[0x%X] Setting SCROLL_PRE_POSITION To[%.2f, %.2f]", this, mScrollPrePosition.x, mScrollPrePosition.y);
2019       handle.SetProperty(Toolkit::ScrollView::Property::SCROLL_PRE_POSITION, mScrollPrePosition);
2020     }
2021     SnapInternalYTo(mScrollPostPosition.y);
2022   }
2023
2024   DALI_LOG_SCROLL_STATE("[0x%X] scrollingFinished[%d] Animation[0x%X]", this, scrollingFinished, source.GetObjectPtr());
2025
2026   if(scrollingFinished)
2027   {
2028     HandleSnapAnimationFinished();
2029   }
2030 }
2031
2032 void ScrollView::OnSnapInternalPositionFinished(Animation& source)
2033 {
2034   Actor self = Self();
2035   UpdateLocalScrollProperties();
2036   if(source == mInternalXAnimation)
2037   {
2038     DALI_LOG_SCROLL_STATE("[0x%X] Finished X PostPosition Animation", this);
2039
2040     // clear internal x animation flags
2041     mScrollStateFlags &= ~SCROLL_X_STATE_MASK;
2042     mInternalXAnimation.Reset();
2043     WrapPosition(mScrollPrePosition);
2044   }
2045   if(source == mInternalYAnimation)
2046   {
2047     DALI_LOG_SCROLL_STATE("[0x%X] Finished Y PostPosition Animation", this);
2048
2049     mScrollStateFlags &= ~SCROLL_Y_STATE_MASK;
2050     mInternalYAnimation.Reset();
2051     WrapPosition(mScrollPrePosition);
2052   }
2053 }
2054
2055 void ScrollView::SnapInternalXTo(float position)
2056 {
2057   Actor self = Self();
2058
2059   StopAnimation(mInternalXAnimation);
2060
2061   // erase current state flags
2062   mScrollStateFlags &= ~SCROLL_X_STATE_MASK;
2063
2064   // if internal x not equal to inputed parameter, animate it
2065   float duration = std::min(fabsf((position - mScrollPrePosition.x) / mMaxOvershoot.x) * mSnapOvershootDuration, mSnapOvershootDuration);
2066   DALI_LOG_SCROLL_STATE("[0x%X] duration[%.2f]", this, duration);
2067   if(duration > Math::MACHINE_EPSILON_1)
2068   {
2069     DALI_LOG_SCROLL_STATE("[0x%X] Starting X Snap Animation to[%.2f]", this, position);
2070
2071     mInternalXAnimation = Animation::New(duration);
2072     mInternalXAnimation.FinishedSignal().Connect(this, &ScrollView::OnSnapInternalPositionFinished);
2073     mInternalXAnimation.AnimateTo(Property(self, Toolkit::ScrollView::Property::SCROLL_PRE_POSITION, 0), position);
2074     mInternalXAnimation.Play();
2075
2076     // add internal animation state flag
2077     mScrollStateFlags |= SnappingInternalX;
2078   }
2079 }
2080
2081 void ScrollView::SnapInternalYTo(float position)
2082 {
2083   Actor self = Self();
2084
2085   StopAnimation(mInternalYAnimation);
2086
2087   // erase current state flags
2088   mScrollStateFlags &= ~SCROLL_Y_STATE_MASK;
2089
2090   // if internal y not equal to inputed parameter, animate it
2091   float duration = std::min(fabsf((position - mScrollPrePosition.y) / mMaxOvershoot.y) * mSnapOvershootDuration, mSnapOvershootDuration);
2092   DALI_LOG_SCROLL_STATE("[0x%X] duration[%.2f]", this, duration);
2093   if(duration > Math::MACHINE_EPSILON_1)
2094   {
2095     DALI_LOG_SCROLL_STATE("[0x%X] Starting Y Snap Animation to[%.2f]", this, position);
2096
2097     mInternalYAnimation = Animation::New(duration);
2098     mInternalYAnimation.FinishedSignal().Connect(this, &ScrollView::OnSnapInternalPositionFinished);
2099     mInternalYAnimation.AnimateTo(Property(self, Toolkit::ScrollView::Property::SCROLL_PRE_POSITION, 1), position);
2100     mInternalYAnimation.Play();
2101
2102     // add internal animation state flag
2103     mScrollStateFlags |= SnappingInternalY;
2104   }
2105 }
2106
2107 void ScrollView::GestureStarted()
2108 {
2109   // we handle the first gesture.
2110   // if we're currently doing a gesture and receive another
2111   // we continue and combine the effects of the gesture instead of reseting.
2112   if(mGestureStackDepth++ == 0)
2113   {
2114     Actor self = Self();
2115     StopTouchDownTimer();
2116     StopAnimation();
2117     mPanDelta     = Vector2::ZERO;
2118     mLastVelocity = Vector2::ZERO;
2119     if(!mScrolling)
2120     {
2121       mLockAxis = LockPossible;
2122     }
2123
2124     if(mScrollStateFlags & SCROLL_X_STATE_MASK)
2125     {
2126       StopAnimation(mInternalXAnimation);
2127     }
2128     if(mScrollStateFlags & SCROLL_Y_STATE_MASK)
2129     {
2130       StopAnimation(mInternalYAnimation);
2131     }
2132     mScrollStateFlags = 0;
2133
2134     if(mScrolling) // are we interrupting a current scroll?
2135     {
2136       // set mScrolling to false, in case user has code that interrogates mScrolling Getter() in complete.
2137       mScrolling = false;
2138       // send negative scroll position since scroll internal scroll position works as an offset for actors,
2139       // give applications the position within the domain from the scroll view's anchor position
2140       DALI_LOG_SCROLL_STATE("[0x%X] mScrollCompletedSignal 5 [%.2f, %.2f]", this, -mScrollPostPosition.x, -mScrollPostPosition.y);
2141       mScrollCompletedSignal.Emit(-mScrollPostPosition);
2142     }
2143   }
2144 }
2145
2146 void ScrollView::GestureContinuing(const Vector2& panDelta)
2147 {
2148   mPanDelta.x += panDelta.x;
2149   mPanDelta.y += panDelta.y;
2150
2151   // Save the velocity, there is a bug in PanGesture
2152   // Whereby the GestureState::FINISHED's velocity is either:
2153   // NaN (due to time delta of zero between the last two events)
2154   // or 0 (due to position being the same between the last two events)
2155
2156   // Axis Auto Lock - locks the panning to the horizontal or vertical axis if the pan
2157   // appears mostly horizontal or mostly vertical respectively.
2158   if(mAxisAutoLock)
2159   {
2160     mLockAxis = GetLockAxis(mPanDelta, mLockAxis, mAxisAutoLockGradient);
2161   } // end if mAxisAutoLock
2162 }
2163
2164 // TODO: Upgrade to use a more powerful gesture detector (one that supports multiple touches on pan - so works as pan and flick gesture)
2165 // BUG: GestureState::FINISHED doesn't always return velocity on release (due to
2166 // timeDelta between last two events being 0 sometimes, or posiiton being the same)
2167 void ScrollView::OnPan(const PanGesture& gesture)
2168 {
2169   // Guard against destruction during signal emission
2170   // Note that Emit() methods are called indirectly e.g. from within ScrollView::OnGestureEx()
2171   Actor self(Self());
2172
2173   if(!mSensitive)
2174   {
2175     DALI_LOG_SCROLL_STATE("[0x%X] Pan Ignored, Insensitive", this);
2176
2177     // If another callback on the same original signal disables sensitivity,
2178     // this callback will still be called, so we must suppress it.
2179     return;
2180   }
2181
2182   // translate Gesture input to get useful data...
2183   switch(gesture.GetState())
2184   {
2185     case GestureState::STARTED:
2186     {
2187       DALI_LOG_SCROLL_STATE("[0x%X] Pan Started", this);
2188       const Vector2& position = gesture.GetPosition();
2189       mPanStartPosition       = position - gesture.GetDisplacement();
2190       UpdateLocalScrollProperties();
2191       GestureStarted();
2192       mPanning = true;
2193       self.SetProperty(Toolkit::ScrollView::Property::PANNING, true);
2194       self.SetProperty(Toolkit::ScrollView::Property::START_PAGE_POSITION, Vector3(position.x, position.y, 0.0f));
2195
2196       mConstraints.UpdateMainInternalConstraint(*this);
2197       Toolkit::ScrollBar scrollBar = mScrollBar.GetHandle();
2198       if(scrollBar && mTransientScrollBar)
2199       {
2200         Vector3                     size         = Self().GetCurrentProperty<Vector3>(Actor::Property::SIZE);
2201         const Toolkit::RulerDomain& rulerDomainX = mRulerX->GetDomain();
2202         const Toolkit::RulerDomain& rulerDomainY = mRulerY->GetDomain();
2203
2204         if((rulerDomainX.max > size.width) || (rulerDomainY.max > size.height))
2205         {
2206           scrollBar.ShowIndicator();
2207         }
2208       }
2209       break;
2210     }
2211
2212     case GestureState::CONTINUING:
2213     {
2214       if(mPanning)
2215       {
2216         DALI_LOG_SCROLL_STATE("[0x%X] Pan Continuing", this);
2217         GestureContinuing(gesture.GetScreenDisplacement());
2218       }
2219       else
2220       {
2221         // If we do not think we are panning, then we should not do anything here
2222         return;
2223       }
2224       break;
2225     }
2226
2227     case GestureState::FINISHED:
2228     case GestureState::CANCELLED:
2229     {
2230       if(mPanning)
2231       {
2232         DALI_LOG_SCROLL_STATE("[0x%X] Pan %s", this, ((gesture.GetState() == GestureState::FINISHED) ? "Finished" : "Cancelled"));
2233
2234         UpdateLocalScrollProperties();
2235         mLastVelocity = gesture.GetVelocity();
2236         mPanning      = false;
2237         self.SetProperty(Toolkit::ScrollView::Property::PANNING, false);
2238
2239         if(mConstraints.mScrollMainInternalPrePositionConstraint)
2240         {
2241           mConstraints.mScrollMainInternalPrePositionConstraint.Remove();
2242         }
2243
2244         Toolkit::ScrollBar scrollBar = mScrollBar.GetHandle();
2245         if(scrollBar && mTransientScrollBar)
2246         {
2247           scrollBar.HideIndicator();
2248         }
2249       }
2250       else
2251       {
2252         // If we do not think we are panning, then we should not do anything here
2253         return;
2254       }
2255       break;
2256     }
2257
2258     case GestureState::POSSIBLE:
2259     case GestureState::CLEAR:
2260     {
2261       // Nothing to do, not needed.
2262       break;
2263     }
2264
2265   } // end switch(gesture.state)
2266
2267   OnGestureEx(gesture.GetState());
2268 }
2269
2270 void ScrollView::OnGestureEx(GestureState state)
2271 {
2272   // call necessary signals for application developer
2273
2274   if(state == GestureState::STARTED)
2275   {
2276     Vector2 currentScrollPosition = GetCurrentScrollPosition();
2277     Self().SetProperty(Toolkit::ScrollView::Property::SCROLLING, true);
2278     mScrolling = true;
2279     DALI_LOG_SCROLL_STATE("[0x%X] mScrollStartedSignal 2 [%.2f, %.2f]", this, currentScrollPosition.x, currentScrollPosition.y);
2280     mScrollStartedSignal.Emit(currentScrollPosition);
2281   }
2282   else if((state == GestureState::FINISHED) ||
2283           (state == GestureState::CANCELLED)) // Finished/default
2284   {
2285     // when all the gestures have finished, we finish the transform.
2286     // so if a user decides to pan (1 gesture), and then pan+zoom (2 gestures)
2287     // then stop panning (back to 1 gesture), and then stop zooming (0 gestures).
2288     // this is the point we end, and perform necessary snapping.
2289     mGestureStackDepth--;
2290     if(mGestureStackDepth == 0)
2291     {
2292       // no flick if we have not exceeded min flick distance
2293       if((fabsf(mPanDelta.x) < mMinFlickDistance.x) && (fabsf(mPanDelta.y) < mMinFlickDistance.y))
2294       {
2295         // reset flick velocity
2296         mLastVelocity = Vector2::ZERO;
2297       }
2298       FinishTransform();
2299     }
2300     else
2301     {
2302       DALI_LOG_SCROLL_STATE("[0x%X] mGestureStackDepth[%d]", this, mGestureStackDepth);
2303     }
2304   }
2305 }
2306
2307 void ScrollView::FinishTransform()
2308 {
2309   // at this stage internal x and x scroll position should have followed prescroll position exactly
2310   Actor self = Self();
2311
2312   PreAnimatedScrollSetup();
2313
2314   // convert pixels/millisecond to pixels per second
2315   bool animating = SnapWithVelocity(mLastVelocity * 1000.0f);
2316
2317   if(!animating)
2318   {
2319     // if not animating, then this pan has completed right now.
2320     SetScrollUpdateNotification(false);
2321     mScrolling = false;
2322     Self().SetProperty(Toolkit::ScrollView::Property::SCROLLING, false);
2323
2324     if(fabs(mScrollPrePosition.x - mScrollTargetPosition.x) > Math::MACHINE_EPSILON_10)
2325     {
2326       SnapInternalXTo(mScrollTargetPosition.x);
2327     }
2328     if(fabs(mScrollPrePosition.y - mScrollTargetPosition.y) > Math::MACHINE_EPSILON_10)
2329     {
2330       SnapInternalYTo(mScrollTargetPosition.y);
2331     }
2332     Vector2 currentScrollPosition = GetCurrentScrollPosition();
2333     DALI_LOG_SCROLL_STATE("[0x%X] mScrollCompletedSignal 6 [%.2f, %.2f]", this, currentScrollPosition.x, currentScrollPosition.y);
2334     mScrollCompletedSignal.Emit(currentScrollPosition);
2335   }
2336 }
2337
2338 Vector2 ScrollView::GetOvershoot(Vector2& position) const
2339 {
2340   Vector3 size = Self().GetCurrentProperty<Vector3>(Actor::Property::SIZE);
2341   Vector2 overshoot;
2342
2343   const RulerDomain rulerDomainX = mRulerX->GetDomain();
2344   const RulerDomain rulerDomainY = mRulerY->GetDomain();
2345
2346   if(mRulerX->IsEnabled() && rulerDomainX.enabled)
2347   {
2348     const float left  = rulerDomainX.min - position.x;
2349     const float right = size.width - rulerDomainX.max - position.x;
2350     if(left < 0)
2351     {
2352       overshoot.x = left;
2353     }
2354     else if(right > 0)
2355     {
2356       overshoot.x = right;
2357     }
2358   }
2359
2360   if(mRulerY->IsEnabled() && rulerDomainY.enabled)
2361   {
2362     const float top    = rulerDomainY.min - position.y;
2363     const float bottom = size.height - rulerDomainY.max - position.y;
2364     if(top < 0)
2365     {
2366       overshoot.y = top;
2367     }
2368     else if(bottom > 0)
2369     {
2370       overshoot.y = bottom;
2371     }
2372   }
2373
2374   return overshoot;
2375 }
2376
2377 bool ScrollView::OnAccessibilityPan(PanGesture gesture)
2378 {
2379   // Keep track of whether this is an AccessibilityPan
2380   mInAccessibilityPan = true;
2381   OnPan(gesture);
2382   mInAccessibilityPan = false;
2383
2384   return true;
2385 }
2386
2387 void ScrollView::ClampPosition(Vector2& position) const
2388 {
2389   ClampState2D clamped;
2390   ClampPosition(position, clamped);
2391 }
2392
2393 void ScrollView::ClampPosition(Vector2& position, ClampState2D& clamped) const
2394 {
2395   Vector3 size = Self().GetCurrentProperty<Vector3>(Actor::Property::SIZE);
2396
2397   ::ClampPosition(size, mRulerX, mRulerY, position, clamped);
2398 }
2399
2400 void ScrollView::WrapPosition(Vector2& position) const
2401 {
2402   if(mWrapMode)
2403   {
2404     const RulerDomain rulerDomainX = mRulerX->GetDomain();
2405     const RulerDomain rulerDomainY = mRulerY->GetDomain();
2406
2407     if(mRulerX->IsEnabled())
2408     {
2409       position.x = -WrapInDomain(-position.x, rulerDomainX.min, rulerDomainX.max);
2410     }
2411
2412     if(mRulerY->IsEnabled())
2413     {
2414       position.y = -WrapInDomain(-position.y, rulerDomainY.min, rulerDomainY.max);
2415     }
2416   }
2417 }
2418
2419 void ScrollView::SetProperty(BaseObject* object, Property::Index index, const Property::Value& value)
2420 {
2421   Toolkit::ScrollView scrollView = Toolkit::ScrollView::DownCast(Dali::BaseHandle(object));
2422
2423   if(scrollView)
2424   {
2425     ScrollView& scrollViewImpl(GetImpl(scrollView));
2426     switch(index)
2427     {
2428       case Toolkit::ScrollView::Property::WRAP_ENABLED:
2429       {
2430         scrollViewImpl.SetWrapMode(value.Get<bool>());
2431         break;
2432       }
2433       case Toolkit::ScrollView::Property::PANNING_ENABLED:
2434       {
2435         scrollViewImpl.SetScrollSensitive(value.Get<bool>());
2436         break;
2437       }
2438       case Toolkit::ScrollView::Property::AXIS_AUTO_LOCK_ENABLED:
2439       {
2440         scrollViewImpl.SetAxisAutoLock(value.Get<bool>());
2441         break;
2442       }
2443       case Toolkit::ScrollView::Property::WHEEL_SCROLL_DISTANCE_STEP:
2444       {
2445         scrollViewImpl.SetWheelScrollDistanceStep(value.Get<Vector2>());
2446         break;
2447       }
2448       case Toolkit::ScrollView::Property::SCROLL_MODE:
2449       {
2450         const Property::Map* map = value.GetMap();
2451         if(map)
2452         {
2453           scrollViewImpl.SetScrollMode(*map);
2454         }
2455       }
2456     }
2457   }
2458 }
2459
2460 Property::Value ScrollView::GetProperty(BaseObject* object, Property::Index index)
2461 {
2462   Property::Value value;
2463
2464   Toolkit::ScrollView scrollView = Toolkit::ScrollView::DownCast(Dali::BaseHandle(object));
2465
2466   if(scrollView)
2467   {
2468     ScrollView& scrollViewImpl(GetImpl(scrollView));
2469     switch(index)
2470     {
2471       case Toolkit::ScrollView::Property::WRAP_ENABLED:
2472       {
2473         value = scrollViewImpl.GetWrapMode();
2474         break;
2475       }
2476       case Toolkit::ScrollView::Property::PANNING_ENABLED:
2477       {
2478         value = scrollViewImpl.GetScrollSensitive();
2479         break;
2480       }
2481       case Toolkit::ScrollView::Property::AXIS_AUTO_LOCK_ENABLED:
2482       {
2483         value = scrollViewImpl.GetAxisAutoLock();
2484         break;
2485       }
2486       case Toolkit::ScrollView::Property::WHEEL_SCROLL_DISTANCE_STEP:
2487       {
2488         value = scrollViewImpl.GetWheelScrollDistanceStep();
2489         break;
2490       }
2491     }
2492   }
2493
2494   return value;
2495 }
2496
2497 void ScrollView::SetScrollMode(const Property::Map& scrollModeMap)
2498 {
2499   Toolkit::RulerPtr rulerX, rulerY;
2500
2501   // Check the scroll mode in the X axis
2502   bool             xAxisScrollEnabled = true;
2503   Property::Value* valuePtr           = scrollModeMap.Find(Toolkit::ScrollMode::X_AXIS_SCROLL_ENABLED, "xAxisScrollEnabled");
2504   if(valuePtr && valuePtr->GetType() == Property::BOOLEAN)
2505   {
2506     valuePtr->Get(xAxisScrollEnabled);
2507   }
2508
2509   if(!xAxisScrollEnabled)
2510   {
2511     // Default ruler and disabled
2512     rulerX = new Toolkit::DefaultRuler();
2513     rulerX->Disable();
2514   }
2515   else
2516   {
2517     valuePtr                  = scrollModeMap.Find(Toolkit::ScrollMode::X_AXIS_SNAP_TO_INTERVAL, "xAxisSnapToInterval");
2518     float xAxisSnapToInterval = 0.0f;
2519     if(valuePtr && valuePtr->Get(xAxisSnapToInterval))
2520     {
2521       // Fixed ruler and enabled
2522       rulerX = new Toolkit::FixedRuler(xAxisSnapToInterval);
2523     }
2524     else
2525     {
2526       // Default ruler and enabled
2527       rulerX = new Toolkit::DefaultRuler();
2528     }
2529
2530     valuePtr                  = scrollModeMap.Find(Toolkit::ScrollMode::X_AXIS_SCROLL_BOUNDARY, "xAxisScrollBoundary");
2531     float xAxisScrollBoundary = 0.0f;
2532     if(valuePtr && valuePtr->Get(xAxisScrollBoundary))
2533     {
2534       // By default ruler domain is disabled unless set
2535       rulerX->SetDomain(Toolkit::RulerDomain(0, xAxisScrollBoundary, true));
2536     }
2537   }
2538
2539   // Check the scroll mode in the Y axis
2540   bool yAxisScrollEnabled = true;
2541   valuePtr                = scrollModeMap.Find(Toolkit::ScrollMode::Y_AXIS_SCROLL_ENABLED, "yAxisScrollEnabled");
2542   if(valuePtr && valuePtr->GetType() == Property::BOOLEAN)
2543   {
2544     valuePtr->Get(yAxisScrollEnabled);
2545   }
2546
2547   if(!yAxisScrollEnabled)
2548   {
2549     // Default ruler and disabled
2550     rulerY = new Toolkit::DefaultRuler();
2551     rulerY->Disable();
2552   }
2553   else
2554   {
2555     valuePtr                  = scrollModeMap.Find(Toolkit::ScrollMode::Y_AXIS_SNAP_TO_INTERVAL, "yAxisSnapToInterval");
2556     float yAxisSnapToInterval = 0.0f;
2557     if(valuePtr && valuePtr->Get(yAxisSnapToInterval))
2558     {
2559       // Fixed ruler and enabled
2560       rulerY = new Toolkit::FixedRuler(yAxisSnapToInterval);
2561     }
2562     else
2563     {
2564       // Default ruler and enabled
2565       rulerY = new Toolkit::DefaultRuler();
2566     }
2567
2568     valuePtr                  = scrollModeMap.Find(Toolkit::ScrollMode::Y_AXIS_SCROLL_BOUNDARY, "yAxisScrollBoundary");
2569     float yAxisScrollBoundary = 0.0f;
2570     if(valuePtr && valuePtr->Get(yAxisScrollBoundary))
2571     {
2572       // By default ruler domain is disabled unless set
2573       rulerY->SetDomain(Toolkit::RulerDomain(0, yAxisScrollBoundary, true));
2574     }
2575   }
2576
2577   SetRulerX(rulerX);
2578   SetRulerY(rulerY);
2579 }
2580
2581 ScrollView::LockAxis GetLockAxis(const Vector2& panDelta, ScrollView::LockAxis currentLockAxis, float lockGradient)
2582 {
2583   if(panDelta.LengthSquared() > AUTOLOCK_AXIS_MINIMUM_DISTANCE2 &&
2584      currentLockAxis == ScrollView::LockPossible)
2585   {
2586     float dx = fabsf(panDelta.x);
2587     float dy = fabsf(panDelta.y);
2588     if(dx * lockGradient >= dy)
2589     {
2590       // 0.36:1 gradient to the horizontal (deviate < 20 degrees)
2591       currentLockAxis = ScrollView::LockVertical;
2592     }
2593     else if(dy * lockGradient > dx)
2594     {
2595       // 0.36:1 gradient to the vertical (deviate < 20 degrees)
2596       currentLockAxis = ScrollView::LockHorizontal;
2597     }
2598     else
2599     {
2600       currentLockAxis = ScrollView::LockNone;
2601     }
2602   }
2603   return currentLockAxis;
2604 }
2605
2606 } // namespace Internal
2607
2608 } // namespace Toolkit
2609
2610 } // namespace Dali