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