[NUI] Add PageFlickThreshold and ScrollAlphaFunction (#1790)
[platform/core/csapi/tizenfx.git] / src / Tizen.NUI.Components / Controls / ScrollableBase.cs
1 /* Copyright (c) 2020 Samsung Electronics Co., Ltd.
2  *
3  * Licensed under the Apache License, Version 2.0 (the "License");
4  * you may not use this file except in compliance with the License.
5  * You may obtain a copy of the License at
6  *
7  * http://www.apache.org/licenses/LICENSE-2.0
8  *
9  * Unless required by applicable law or agreed to in writing, software
10  * distributed under the License is distributed on an "AS IS" BASIS,
11  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12  * See the License for the specific language governing permissions and
13  * limitations under the License.
14  *
15  */
16 using System;
17 using Tizen.NUI.BaseComponents;
18 using System.Collections.Generic;
19 using System.ComponentModel;
20 using System.Diagnostics;
21 using System.Runtime.InteropServices;
22
23 namespace Tizen.NUI.Components
24 {
25     /// <summary>
26     /// ScrollEventArgs is a class to record scroll event arguments which will sent to user.
27     /// </summary>
28     /// This may be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API
29     [EditorBrowsable(EditorBrowsableState.Never)]
30     public class ScrollEventArgs : EventArgs
31     {
32         Position position;
33
34         /// <summary>
35         /// Default constructor.
36         /// </summary>
37         /// <param name="position">Current scroll position</param>
38         /// This may be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API
39         public ScrollEventArgs(Position position)
40         {
41             this.position = position;
42         }
43
44         /// <summary>
45         /// [Draft] Current scroll position.
46         /// </summary>
47         /// This may be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API
48         [EditorBrowsable(EditorBrowsableState.Never)]
49         public Position Position
50         {
51             get
52             {
53                 return position;
54             }
55         }
56     }
57
58     /// <summary>
59     /// [Draft] This class provides a View that can scroll a single View with a layout. This View can be a nest of Views.
60     /// </summary>
61     /// This may be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API.
62     [EditorBrowsable(EditorBrowsableState.Never)]
63     public class ScrollableBase : Control
64     {
65         static bool LayoutDebugScrollableBase = false; // Debug flag
66         private Direction mScrollingDirection = Direction.Vertical;
67         private bool mScrollEnabled = true;
68         private int mPageWidth = 0;
69
70         private class ScrollableBaseCustomLayout : LayoutGroup
71         {
72             protected override void OnMeasure(MeasureSpecification widthMeasureSpec, MeasureSpecification heightMeasureSpec)
73             {
74                 Extents padding = Padding;
75                 float totalHeight = padding.Top + padding.Bottom;
76                 float totalWidth = padding.Start + padding.End;
77
78                 MeasuredSize.StateType childWidthState = MeasuredSize.StateType.MeasuredSizeOK;
79                 MeasuredSize.StateType childHeightState = MeasuredSize.StateType.MeasuredSizeOK;
80
81                 Direction scrollingDirection = Direction.Vertical;
82                 ScrollableBase scrollableBase = this.Owner as ScrollableBase;
83                 if (scrollableBase)
84                 {
85                     scrollingDirection = scrollableBase.ScrollingDirection;
86                 }
87
88                 // measure child, should be a single scrolling child
89                 foreach (LayoutItem childLayout in LayoutChildren)
90                 {
91                     if (childLayout != null)
92                     {
93                         // Get size of child
94                         // Use an Unspecified MeasureSpecification mode so scrolling child is not restricted to it's parents size in Height (for vertical scrolling)
95                         // or Width for horizontal scrolling
96                         MeasureSpecification unrestrictedMeasureSpec = new MeasureSpecification(heightMeasureSpec.Size, MeasureSpecification.ModeType.Unspecified);
97
98                         if (scrollingDirection == Direction.Vertical)
99                         {
100                             MeasureChildWithMargins(childLayout, widthMeasureSpec, new LayoutLength(0), unrestrictedMeasureSpec, new LayoutLength(0));  // Height unrestricted by parent
101                         }
102                         else
103                         {
104                             MeasureChildWithMargins(childLayout, unrestrictedMeasureSpec, new LayoutLength(0), heightMeasureSpec, new LayoutLength(0));  // Width unrestricted by parent
105                         }
106
107                         float childWidth = childLayout.MeasuredWidth.Size.AsDecimal();
108                         float childHeight = childLayout.MeasuredHeight.Size.AsDecimal();
109
110                         // Determine the width and height needed by the children using their given position and size.
111                         // Children could overlap so find the left most and right most child.
112                         Position2D childPosition = childLayout.Owner.Position2D;
113                         float childLeft = childPosition.X;
114                         float childTop = childPosition.Y;
115
116                         // Store current width and height needed to contain all children.
117                         Extents childMargin = childLayout.Margin;
118                         totalWidth = childWidth + childMargin.Start + childMargin.End;
119                         totalHeight = childHeight + childMargin.Top + childMargin.Bottom;
120
121                         if (childLayout.MeasuredWidth.State == MeasuredSize.StateType.MeasuredSizeTooSmall)
122                         {
123                             childWidthState = MeasuredSize.StateType.MeasuredSizeTooSmall;
124                         }
125                         if (childLayout.MeasuredHeight.State == MeasuredSize.StateType.MeasuredSizeTooSmall)
126                         {
127                             childHeightState = MeasuredSize.StateType.MeasuredSizeTooSmall;
128                         }
129                     }
130                 }
131
132
133                 MeasuredSize widthSizeAndState = ResolveSizeAndState(new LayoutLength(totalWidth + Padding.Start + Padding.End), widthMeasureSpec, MeasuredSize.StateType.MeasuredSizeOK);
134                 MeasuredSize heightSizeAndState = ResolveSizeAndState(new LayoutLength(totalHeight + Padding.Top + Padding.Bottom), heightMeasureSpec, MeasuredSize.StateType.MeasuredSizeOK);
135                 totalWidth = widthSizeAndState.Size.AsDecimal();
136                 totalHeight = heightSizeAndState.Size.AsDecimal();
137
138                 // Ensure layout respects it's given minimum size
139                 totalWidth = Math.Max(totalWidth, SuggestedMinimumWidth.AsDecimal());
140                 totalHeight = Math.Max(totalHeight, SuggestedMinimumHeight.AsDecimal());
141
142                 widthSizeAndState.State = childWidthState;
143                 heightSizeAndState.State = childHeightState;
144
145                 SetMeasuredDimensions(ResolveSizeAndState(new LayoutLength(totalWidth + Padding.Start + Padding.End), widthMeasureSpec, childWidthState),
146                                        ResolveSizeAndState(new LayoutLength(totalHeight + Padding.Top + Padding.Bottom), heightMeasureSpec, childHeightState));
147
148                 // Size of ScrollableBase is changed. Change Page width too.
149                 scrollableBase.mPageWidth = (int)MeasuredWidth.Size.AsRoundedValue();
150             }
151
152             protected override void OnLayout(bool changed, LayoutLength left, LayoutLength top, LayoutLength right, LayoutLength bottom)
153             {
154                 foreach (LayoutItem childLayout in LayoutChildren)
155                 {
156                     if (childLayout != null)
157                     {
158                         LayoutLength childWidth = childLayout.MeasuredWidth.Size;
159                         LayoutLength childHeight = childLayout.MeasuredHeight.Size;
160
161                         Position2D childPosition = childLayout.Owner.Position2D;
162                         Extents padding = Padding;
163                         Extents childMargin = childLayout.Margin;
164
165                         LayoutLength childLeft = new LayoutLength(childPosition.X + childMargin.Start + padding.Start);
166                         LayoutLength childTop = new LayoutLength(childPosition.Y + childMargin.Top + padding.Top);
167
168                         childLayout.Layout(childLeft, childTop, childLeft + childWidth, childTop + childHeight);
169                     }
170                 }
171             }
172         } //  ScrollableBaseCustomLayout
173
174         /// <summary>
175         /// The direction axis to scroll.
176         /// </summary>
177         /// <since_tizen> 6 </since_tizen>
178         /// This may be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API.
179         [EditorBrowsable(EditorBrowsableState.Never)]
180         public enum Direction
181         {
182             /// <summary>
183             /// Horizontal axis.
184             /// </summary>
185             /// <since_tizen> 6 </since_tizen>
186             Horizontal,
187
188             /// <summary>
189             /// Vertical axis.
190             /// </summary>
191             /// <since_tizen> 6 </since_tizen>
192             Vertical
193         }
194
195         /// <summary>
196         /// [Draft] Scrolling direction mode.
197         /// Default is Vertical scrolling.
198         /// </summary>
199         /// This may be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API
200         [EditorBrowsable(EditorBrowsableState.Never)]
201         public Direction ScrollingDirection
202         {
203             get
204             {
205                 return mScrollingDirection;
206             }
207             set
208             {
209                 if (value != mScrollingDirection)
210                 {
211                     mScrollingDirection = value;
212                     mPanGestureDetector.RemoveDirection(value == Direction.Horizontal ?
213                         PanGestureDetector.DirectionVertical : PanGestureDetector.DirectionHorizontal);
214                     mPanGestureDetector.AddDirection(value == Direction.Horizontal ?
215                         PanGestureDetector.DirectionHorizontal : PanGestureDetector.DirectionVertical);
216
217                     ContentContainer.WidthSpecification = mScrollingDirection == Direction.Vertical ?
218                         LayoutParamPolicies.MatchParent : LayoutParamPolicies.WrapContent;
219                     ContentContainer.HeightSpecification = mScrollingDirection == Direction.Vertical ?
220                         LayoutParamPolicies.WrapContent : LayoutParamPolicies.MatchParent;
221                 }
222             }
223         }
224
225         /// <summary>
226         /// [Draft] Enable or disable scrolling.
227         /// </summary>
228         /// This may be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API
229         [EditorBrowsable(EditorBrowsableState.Never)]
230         public bool ScrollEnabled
231         {
232             get
233             {
234                 return mScrollEnabled;
235             }
236             set
237             {
238                 if (value != mScrollEnabled)
239                 {
240                     mScrollEnabled = value;
241                     if (mScrollEnabled)
242                     {
243                         mPanGestureDetector.Detected += OnPanGestureDetected;
244                     }
245                     else
246                     {
247                         mPanGestureDetector.Detected -= OnPanGestureDetected;
248                     }
249                 }
250             }
251         }
252
253         /// <summary>
254         /// [Draft] Pages mode, enables moving to the next or return to current page depending on pan displacement.
255         /// Default is false.
256         /// </summary>
257         /// This may be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API
258         [EditorBrowsable(EditorBrowsableState.Never)]
259         public bool SnapToPage { set; get; } = false;
260
261         /// <summary>
262         /// [Draft] Get current page.
263         /// Working propery with SnapToPage property.
264         /// </summary>
265         /// This may be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API
266         [EditorBrowsable(EditorBrowsableState.Never)]
267         public int CurrentPage { get; private set; } = 0;
268
269         /// <summary>
270         /// [Draft] Duration of scroll animation.
271         /// </summary>
272         /// This may be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API
273         [EditorBrowsable(EditorBrowsableState.Never)]
274
275         public int ScrollDuration { set; get; } = 125;
276         /// <summary>
277         /// [Draft] Scroll Available area.
278         /// </summary>
279         /// This may be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API
280         [EditorBrowsable(EditorBrowsableState.Never)]
281         public Vector2 ScrollAvailableArea { set; get; }
282
283         /// <summary>
284         /// An event emitted when user starts dragging ScrollableBase, user can subscribe or unsubscribe to this event handler.<br />
285         /// </summary>
286         /// This may be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API
287         [EditorBrowsable(EditorBrowsableState.Never)]
288         public event EventHandler<ScrollEventArgs> ScrollDragStarted;
289
290         /// <summary>
291         /// An event emitted when user stops dragging ScrollableBase, user can subscribe or unsubscribe to this event handler.<br />
292         /// </summary>
293         /// This may be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API
294         [EditorBrowsable(EditorBrowsableState.Never)]
295         public event EventHandler<ScrollEventArgs> ScrollDragEnded;
296
297
298         /// <summary>
299         /// An event emitted when the scrolling slide animation starts, user can subscribe or unsubscribe to this event handler.<br />
300         /// </summary>
301         /// This may be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API
302         [EditorBrowsable(EditorBrowsableState.Never)]
303         public event EventHandler<ScrollEventArgs> ScrollAnimationStarted;
304
305         /// <summary>
306         /// An event emitted when the scrolling slide animation ends, user can subscribe or unsubscribe to this event handler.<br />
307         /// </summary>
308         /// This may be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API
309         [EditorBrowsable(EditorBrowsableState.Never)]
310         public event EventHandler<ScrollEventArgs> ScrollAnimationEnded;
311
312
313         /// <summary>
314         /// An event emitted when scrolling, user can subscribe or unsubscribe to this event handler.<br />
315         /// </summary>
316         /// This may be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API
317         [EditorBrowsable(EditorBrowsableState.Never)]
318         public event EventHandler<ScrollEventArgs> Scrolling;
319
320
321         /// <summary>
322         /// Scrollbar for ScrollableBase.<br />
323         /// </summary>
324         /// This may be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API
325         [EditorBrowsable(EditorBrowsableState.Never)]
326         public ScrollbarBase Scrollbar
327         {
328             get
329             {
330                 return scrollBar;
331             }
332             set
333             {
334                 if (scrollBar)
335                 {
336                     scrollBar.Unparent();
337                 }
338
339                 scrollBar = value;
340                 scrollBar.Name = "ScrollBar";
341                 base.Add(scrollBar);
342
343                 if (hideScrollbar)
344                 {
345                     scrollBar.Hide();
346                 }
347                 else
348                 {
349                     scrollBar.Show();
350                 }
351
352                 SetScrollbar();
353             }
354         }
355
356         /// <summary>
357         /// [Draft] Always hide Scrollbar.
358         /// </summary>
359         /// This may be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API
360         [EditorBrowsable(EditorBrowsableState.Never)]
361         public bool HideScrollBar
362         {
363             get
364             {
365                 return hideScrollbar;
366             }
367             set
368             {
369                 hideScrollbar = value;
370
371                 if (scrollBar)
372                 {
373                     if (value)
374                     {
375                         scrollBar.Hide();
376                     }
377                     else
378                     {
379                         scrollBar.Show();
380                     }
381                 }
382             }
383         }
384
385         /// <summary>
386         /// Container which has content of ScrollableBase.
387         /// </summary>
388         [EditorBrowsable(EditorBrowsableState.Never)]
389         public View ContentContainer { get; private set; }
390
391         /// <summary>
392         /// Set the layout on this View. Replaces any existing Layout.
393         /// </summary>
394         [EditorBrowsable(EditorBrowsableState.Never)]
395         public new LayoutItem Layout
396         {
397             get
398             {
399                 return ContentContainer.Layout;
400             }
401             set
402             {
403                 ContentContainer.Layout = value;
404                 if (ContentContainer.Layout != null)
405                 {
406                     ContentContainer.Layout.SetPositionByLayout = false;
407                 }
408             }
409         }
410
411         /// <summary>
412         /// List of children of Container.
413         /// </summary>
414         [EditorBrowsable(EditorBrowsableState.Never)]
415         public new List<View> Children
416         {
417             get
418             {
419                 return ContentContainer.Children;
420             }
421         }
422
423         /// <summary>
424         /// Deceleration rate of scrolling by finger.
425         /// Rate should be 0 < rate < 1.
426         /// </summary>
427         [EditorBrowsable(EditorBrowsableState.Never)]
428         public float DecelerationRate
429         {
430             get
431             {
432                 return decelerationRate;
433             }
434             set
435             {
436                 decelerationRate = value;
437                 logValueOfDeceleration = (float)Math.Log(value);
438             }
439         }
440
441         /// <summary>
442         /// Threashold not to go infinit at the end of scrolling animation.
443         /// </summary>
444         [EditorBrowsable(EditorBrowsableState.Never)]
445         public float DecelerationThreshold { get; set; } = 0.1f;
446
447         /// <summary>
448         /// Page will be changed when velocity of panning is over threshold.
449         /// </summary>
450         [EditorBrowsable(EditorBrowsableState.Never)]
451         public float PageFlickThreshold { get; set; } = 0.4f;
452
453         /// <summary>
454         /// Alphafunction for scroll animation.
455         /// </summary>
456         [EditorBrowsable(EditorBrowsableState.Never)]
457         public AlphaFunction ScrollAlphaFunction { get; set; } = new AlphaFunction(AlphaFunction.BuiltinFunctions.Linear);
458
459         private bool hideScrollbar = true;
460         private float maxScrollDistance;
461         private float childTargetPosition = 0.0f;
462         private PanGestureDetector mPanGestureDetector;
463         private View mInterruptTouchingChild;
464         private ScrollbarBase scrollBar;
465         private bool scrolling = false;
466         private float ratioOfScreenWidthToCompleteScroll = 0.5f;
467         private float totalDisplacementForPan = 0.0f;
468         private Size previousContainerSize = new Size();
469         private PropertyNotification propertyNotification;
470         private float noticeAnimationEndBeforePosition = 0.0f;
471         private bool readyToNotice = false;
472         // Let's consider more whether this needs to be set as protected.
473         public float NoticeAnimationEndBeforePosition { get => noticeAnimationEndBeforePosition; set => noticeAnimationEndBeforePosition = value; }
474
475
476         // Let's consider more whether this needs to be set as protected.
477         private float finalTargetPosition;
478
479         private Animation scrollAnimation;
480         // Declare user alpha function delegate
481         [UnmanagedFunctionPointer(CallingConvention.StdCall)]
482         private delegate float UserAlphaFunctionDelegate(float progress);
483         private UserAlphaFunctionDelegate customScrollAlphaFunction;
484         private float velocityOfLastPan = 0.0f;
485         private float panAnimationDuration = 0.0f;
486         private float panAnimationDelta = 0.0f;
487         private float logValueOfDeceleration = 0.0f;
488         private float decelerationRate = 0.0f;
489
490         /// <summary>
491         /// [Draft] Constructor
492         /// </summary>
493         /// This may be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API
494         [EditorBrowsable(EditorBrowsableState.Never)]
495         public ScrollableBase() : base()
496         {
497             DecelerationRate = 0.998f;
498
499             base.Layout = new ScrollableBaseCustomLayout();
500             mPanGestureDetector = new PanGestureDetector();
501             mPanGestureDetector.Attach(this);
502             mPanGestureDetector.AddDirection(PanGestureDetector.DirectionVertical);
503             mPanGestureDetector.Detected += OnPanGestureDetected;
504
505             ClippingMode = ClippingModeType.ClipChildren;
506
507             //Default Scrolling child
508             ContentContainer = new View()
509             {
510                 WidthSpecification = ScrollingDirection == Direction.Vertical ? LayoutParamPolicies.MatchParent : LayoutParamPolicies.WrapContent,
511                 HeightSpecification = ScrollingDirection == Direction.Vertical ? LayoutParamPolicies.WrapContent : LayoutParamPolicies.MatchParent,
512                 Layout = new AbsoluteLayout() { SetPositionByLayout = false },
513             };
514             ContentContainer.Relayout += OnScrollingChildRelayout;
515             propertyNotification = ContentContainer.AddPropertyNotification("position", PropertyCondition.Step(1.0f));
516             propertyNotification.Notified += OnPropertyChanged;
517             base.Add(ContentContainer);
518
519             //Interrupt touching when panning is started
520             mInterruptTouchingChild = new View()
521             {
522                 Size = new Size(Window.Instance.WindowSize),
523                 BackgroundColor = Color.Transparent,
524             };
525             mInterruptTouchingChild.TouchEvent += OnIterruptTouchingChildTouched;
526             Scrollbar = new Scrollbar();
527         }
528
529         private bool OnIterruptTouchingChildTouched(object source, View.TouchEventArgs args)
530         {
531             if (args.Touch.GetState(0) == PointStateType.Down)
532             {
533                 if (scrolling && !SnapToPage)
534                 {
535                     StopScroll();
536                 }
537             }
538             return true;
539         }
540
541         private void OnPropertyChanged(object source, PropertyNotification.NotifyEventArgs args)
542         {
543             OnScroll();
544         }
545
546         /// <summary>
547         /// Called after a child has been added to the owning view.
548         /// </summary>
549         /// <param name="view">The child which has been added.</param>
550         /// This may be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API
551         [EditorBrowsable(EditorBrowsableState.Never)]
552         public override void Add(View view)
553         {
554             ContentContainer.Add(view);
555         }
556
557         /// <summary>
558         /// Called after a child has been removed from the owning view.
559         /// </summary>
560         /// <param name="view">The child which has been removed.</param>
561         /// This may be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API
562         [EditorBrowsable(EditorBrowsableState.Never)]
563         public override void Remove(View view)
564         {
565             if (SnapToPage && CurrentPage == Children.IndexOf(view) && CurrentPage == Children.Count - 1)
566             {
567                 // Target View is current page and also last child.
568                 // CurrentPage should be changed to previous page.
569                 CurrentPage = Math.Max(0, CurrentPage - 1);
570                 ScrollToIndex(CurrentPage);
571             }
572
573             ContentContainer.Remove(view);
574         }
575
576         private void OnScrollingChildRelayout(object source, EventArgs args)
577         {
578             // Size is changed. Calculate maxScrollDistance.
579             bool isSizeChanged = previousContainerSize.Width != ContentContainer.Size.Width || previousContainerSize.Height != ContentContainer.Size.Height;
580
581             if (isSizeChanged)
582             {
583                 maxScrollDistance = CalculateMaximumScrollDistance();
584                 SetScrollbar();
585             }
586
587             previousContainerSize = ContentContainer.Size;
588         }
589
590         /// <summary>
591         /// The composition of a Scrollbar can vary depending on how you use ScrollableBase. 
592         /// Set the composition that will go into the ScrollableBase according to your ScrollableBase.
593         /// </summary>
594         /// This may be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API
595         [EditorBrowsable(EditorBrowsableState.Never)]
596         protected virtual void SetScrollbar()
597         {
598             if (Scrollbar)
599             {
600                 bool isHorizontal = ScrollingDirection == Direction.Horizontal;
601                 float contentLength = isHorizontal ? ContentContainer.Size.Width : ContentContainer.Size.Height;
602                 float viewportLength = isHorizontal ? Size.Width : Size.Height;
603                 float currentPosition = isHorizontal ? ContentContainer.CurrentPosition.X : ContentContainer.CurrentPosition.Y;
604                 Scrollbar.Initialize(contentLength, viewportLength, currentPosition, isHorizontal);
605             }
606         }
607
608         /// <summary>
609         /// Scrolls to the item at the specified index.
610         /// </summary>
611         /// <param name="index">Index of item.</param>
612         /// This may be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API
613         [EditorBrowsable(EditorBrowsableState.Never)]
614         public void ScrollToIndex(int index)
615         {
616             if (ContentContainer.ChildCount - 1 < index || index < 0)
617             {
618                 return;
619             }
620
621             if (SnapToPage)
622             {
623                 CurrentPage = index;
624             }
625
626             float targetPosition = Math.Min(ScrollingDirection == Direction.Vertical ? Children[index].Position.Y : Children[index].Position.X, maxScrollDistance);
627             AnimateChildTo(ScrollDuration, -targetPosition);
628         }
629
630         private void OnScrollDragStarted()
631         {
632             ScrollEventArgs eventArgs = new ScrollEventArgs(ContentContainer.CurrentPosition);
633             ScrollDragStarted?.Invoke(this, eventArgs);
634         }
635
636         private void OnScrollDragEnded()
637         {
638             ScrollEventArgs eventArgs = new ScrollEventArgs(ContentContainer.CurrentPosition);
639             ScrollDragEnded?.Invoke(this, eventArgs);
640         }
641
642         private void OnScrollAnimationStarted()
643         {
644             ScrollEventArgs eventArgs = new ScrollEventArgs(ContentContainer.CurrentPosition);
645             ScrollAnimationStarted?.Invoke(this, eventArgs);
646         }
647
648         private void OnScrollAnimationEnded()
649         {
650             scrolling = false;
651             base.Remove(mInterruptTouchingChild);
652
653             ScrollEventArgs eventArgs = new ScrollEventArgs(ContentContainer.CurrentPosition);
654             ScrollAnimationEnded?.Invoke(this, eventArgs);
655         }
656
657         private void OnScroll()
658         {
659             ScrollEventArgs eventArgs = new ScrollEventArgs(ContentContainer.CurrentPosition);
660             Scrolling?.Invoke(this, eventArgs);
661
662             bool isHorizontal = ScrollingDirection == Direction.Horizontal;
663             float contentLength = isHorizontal ? ContentContainer.Size.Width : ContentContainer.Size.Height;
664             float currentPosition = isHorizontal ? ContentContainer.CurrentPosition.X : ContentContainer.CurrentPosition.Y;
665
666             scrollBar.Update(contentLength, Math.Abs(currentPosition));
667             CheckPreReachedTargetPosition();
668         }
669
670         private void CheckPreReachedTargetPosition()
671         {
672             // Check whether we reached pre-reached target position
673             if (readyToNotice &&
674                 ContentContainer.CurrentPosition.Y <= finalTargetPosition + NoticeAnimationEndBeforePosition &&
675                 ContentContainer.CurrentPosition.Y >= finalTargetPosition - NoticeAnimationEndBeforePosition)
676             {
677                 //Notice first
678                 readyToNotice = false;
679                 OnPreReachedTargetPosition(finalTargetPosition);
680             }
681         }
682
683         /// <summary>
684         /// This helps developer who wants to know before scroll is reaching target position.
685         /// </summary>
686         /// <param name="targetPosition">Index of item.</param>
687         /// This may be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API
688         [EditorBrowsable(EditorBrowsableState.Never)]
689         protected virtual void OnPreReachedTargetPosition(float targetPosition)
690         {
691
692         }
693
694         private void StopScroll()
695         {
696             if (scrollAnimation != null)
697             {
698                 if (scrollAnimation.State == Animation.States.Playing)
699                 {
700                     Debug.WriteLineIf(LayoutDebugScrollableBase, "StopScroll Animation Playing");
701                     scrollAnimation.Stop(Animation.EndActions.Cancel);
702                     OnScrollAnimationEnded();
703                 }
704                 scrollAnimation.Clear();
705             }
706         }
707
708         private void AnimateChildTo(int duration, float axisPosition)
709         {
710             Debug.WriteLineIf(LayoutDebugScrollableBase, "AnimationTo Animation Duration:" + duration + " Destination:" + axisPosition);
711             finalTargetPosition = axisPosition;
712
713             StopScroll(); // Will replace previous animation so will stop existing one.
714
715             if (scrollAnimation == null)
716             {
717                 scrollAnimation = new Animation();
718                 scrollAnimation.Finished += ScrollAnimationFinished;
719             }
720
721             scrollAnimation.Duration = duration;
722             scrollAnimation.DefaultAlphaFunction = new AlphaFunction(AlphaFunction.BuiltinFunctions.EaseOutSquare);
723             scrollAnimation.AnimateTo(ContentContainer, (ScrollingDirection == Direction.Horizontal) ? "PositionX" : "PositionY", axisPosition, ScrollAlphaFunction);
724             scrolling = true;
725             OnScrollAnimationStarted();
726             scrollAnimation.Play();
727         }
728
729         /// <summary>
730         /// Scroll to specific position with or without animation.
731         /// </summary>
732         /// <param name="position">Destination.</param>
733         /// <param name="animate">Scroll with or without animation</param>
734         [EditorBrowsable(EditorBrowsableState.Never)]
735         public void ScrollTo(float position, bool animate)
736         {
737             float currentPositionX = ContentContainer.CurrentPosition.X != 0 ? ContentContainer.CurrentPosition.X : ContentContainer.Position.X;
738             float currentPositionY = ContentContainer.CurrentPosition.Y != 0 ? ContentContainer.CurrentPosition.Y : ContentContainer.Position.Y;
739             float delta = ScrollingDirection == Direction.Horizontal ? currentPositionX : currentPositionY;
740             // The argument position is the new pan position. So the new position of ScrollableBase becomes (-position).
741             // To move ScrollableBase's position to (-position), it moves by (-position - currentPosition).
742             delta = -position - delta;
743
744             ScrollBy(delta, animate);
745         }
746
747         private float BoundScrollPosition(float targetPosition)
748         {
749             if (ScrollAvailableArea != null)
750             {
751                 float minScrollPosition = ScrollAvailableArea.X;
752                 float maxScrollPosition = ScrollAvailableArea.Y;
753
754                 targetPosition = Math.Min(-minScrollPosition, targetPosition);
755                 targetPosition = Math.Max(-maxScrollPosition, targetPosition);
756             }
757             else
758             {
759                 targetPosition = Math.Min(0, targetPosition);
760                 targetPosition = Math.Max(-maxScrollDistance, targetPosition);
761             }
762
763             return targetPosition;
764         }
765
766         private void ScrollBy(float displacement, bool animate)
767         {
768             if (GetChildCount() == 0 || maxScrollDistance < 0)
769             {
770                 return;
771             }
772
773             float childCurrentPosition = (ScrollingDirection == Direction.Horizontal) ? ContentContainer.PositionX : ContentContainer.PositionY;
774
775             Debug.WriteLineIf(LayoutDebugScrollableBase, "ScrollBy childCurrentPosition:" + childCurrentPosition +
776                                                    " displacement:" + displacement,
777                                                    " maxScrollDistance:" + maxScrollDistance);
778
779             childTargetPosition = childCurrentPosition + displacement; // child current position + gesture displacement
780
781
782             Debug.WriteLineIf(LayoutDebugScrollableBase, "ScrollBy currentAxisPosition:" + childCurrentPosition + "childTargetPosition:" + childTargetPosition);
783
784             if (animate)
785             {
786                 // Calculate scroll animaton duration
787                 float scrollDistance = Math.Abs(displacement);
788                 readyToNotice = true;
789
790                 AnimateChildTo(ScrollDuration, BoundScrollPosition(AdjustTargetPositionOfScrollAnimation(BoundScrollPosition(childTargetPosition))));
791             }
792             else
793             {
794                 finalTargetPosition = BoundScrollPosition(childTargetPosition);
795
796                 // Set position of scrolling child without an animation
797                 if (ScrollingDirection == Direction.Horizontal)
798                 {
799                     ContentContainer.PositionX = finalTargetPosition;
800                 }
801                 else
802                 {
803                     ContentContainer.PositionY = finalTargetPosition;
804                 }
805
806             }
807         }
808
809         /// <summary>
810         /// you can override it to clean-up your own resources.
811         /// </summary>
812         /// <param name="type">DisposeTypes</param>
813         /// This will be public opened in tizen_5.5 after ACR done. Before ACR, need to be hidden as inhouse API.
814         [EditorBrowsable(EditorBrowsableState.Never)]
815         protected override void Dispose(DisposeTypes type)
816         {
817             if (disposed)
818             {
819                 return;
820             }
821
822             if (type == DisposeTypes.Explicit)
823             {
824                 StopScroll();
825
826                 if (mPanGestureDetector != null)
827                 {
828                     mPanGestureDetector.Detected -= OnPanGestureDetected;
829                     mPanGestureDetector.Dispose();
830                     mPanGestureDetector = null;
831                 }
832             }
833             base.Dispose(type);
834         }
835
836         private float CalculateMaximumScrollDistance()
837         {
838             float scrollingChildLength = 0;
839             float scrollerLength = 0;
840             if (ScrollingDirection == Direction.Horizontal)
841             {
842                 Debug.WriteLineIf(LayoutDebugScrollableBase, "Horizontal");
843
844                 scrollingChildLength = ContentContainer.Size.Width;
845                 scrollerLength = Size.Width;
846             }
847             else
848             {
849                 Debug.WriteLineIf(LayoutDebugScrollableBase, "Vertical");
850                 scrollingChildLength = ContentContainer.Size.Height;
851                 scrollerLength = Size.Height;
852             }
853
854             Debug.WriteLineIf(LayoutDebugScrollableBase, "ScrollBy maxScrollDistance:" + (scrollingChildLength - scrollerLength) +
855                                                    " parent length:" + scrollerLength +
856                                                    " scrolling child length:" + scrollingChildLength);
857
858             return Math.Max(scrollingChildLength - scrollerLength, 0);
859         }
860
861         private void PageSnap(float velocity)
862         {
863             Debug.WriteLineIf(LayoutDebugScrollableBase, "PageSnap with pan candidate totalDisplacement:" + totalDisplacementForPan +
864                                                                 " currentPage[" + CurrentPage + "]");
865
866             //Increment current page if total displacement enough to warrant a page change.
867             if (Math.Abs(totalDisplacementForPan) > (mPageWidth * ratioOfScreenWidthToCompleteScroll))
868             {
869                 if (totalDisplacementForPan < 0)
870                 {
871                     CurrentPage = Math.Min(Math.Max(Children.Count - 1, 0), ++CurrentPage);
872                 }
873                 else
874                 {
875                     CurrentPage = Math.Max(0, --CurrentPage);
876                 }
877             }
878             else if (Math.Abs(velocity) > PageFlickThreshold)
879             {
880                 if (velocity < 0)
881                 {
882                     CurrentPage = Math.Min(Math.Max(Children.Count - 1, 0), ++CurrentPage);
883                 }
884                 else
885                 {
886                     CurrentPage = Math.Max(0, --CurrentPage);
887                 }
888             }
889
890             // Animate to new page or reposition to current page
891             float destinationX = -(Children[CurrentPage].Position.X + Children[CurrentPage].CurrentSize.Width / 2 - CurrentSize.Width / 2); // set to middle of current page
892             Debug.WriteLineIf(LayoutDebugScrollableBase, "Snapping to page[" + CurrentPage + "] to:" + destinationX + " from:" + ContentContainer.PositionX);
893             AnimateChildTo(ScrollDuration, destinationX);
894         }
895
896         private void OnPanGestureDetected(object source, PanGestureDetector.DetectedEventArgs e)
897         {
898             if (SnapToPage && scrollAnimation != null && scrollAnimation.State == Animation.States.Playing)
899             {
900                 return;
901             }
902
903             if (e.PanGesture.State == Gesture.StateType.Started)
904             {
905                 readyToNotice = false;
906                 base.Add(mInterruptTouchingChild);
907                 Debug.WriteLineIf(LayoutDebugScrollableBase, "Gesture Start");
908                 if (scrolling && !SnapToPage)
909                 {
910                     StopScroll();
911                 }
912                 totalDisplacementForPan = 0.0f;
913                 OnScrollDragStarted();
914             }
915             else if (e.PanGesture.State == Gesture.StateType.Continuing)
916             {
917                 if (ScrollingDirection == Direction.Horizontal)
918                 {
919                     ScrollBy(e.PanGesture.Displacement.X, false);
920                     totalDisplacementForPan += e.PanGesture.Displacement.X;
921                 }
922                 else
923                 {
924                     ScrollBy(e.PanGesture.Displacement.Y, false);
925                     totalDisplacementForPan += e.PanGesture.Displacement.Y;
926                 }
927                 Debug.WriteLineIf(LayoutDebugScrollableBase, "OnPanGestureDetected Continue totalDisplacementForPan:" + totalDisplacementForPan);
928             }
929             else if (e.PanGesture.State == Gesture.StateType.Finished)
930             {
931                 OnScrollDragEnded();
932                 StopScroll(); // Will replace previous animation so will stop existing one.
933
934                 if (scrollAnimation == null)
935                 {
936                     scrollAnimation = new Animation();
937                     scrollAnimation.Finished += ScrollAnimationFinished;
938                 }
939
940                 if (SnapToPage)
941                 {
942                     PageSnap((ScrollingDirection == Direction.Horizontal) ? e.PanGesture.Velocity.X : e.PanGesture.Velocity.Y);
943                 }
944                 else
945                 {
946                     Decelerating((ScrollingDirection == Direction.Horizontal) ? e.PanGesture.Velocity.X : e.PanGesture.Velocity.Y);
947                 }
948
949                 totalDisplacementForPan = 0;
950                 scrolling = true;
951                 readyToNotice = true;
952                 OnScrollAnimationStarted();
953             }
954         }
955
956         private float CustomScrollAlphaFunction(float progress)
957         {
958             if (panAnimationDelta == 0)
959             {
960                 return 1.0f;
961             }
962             else
963             {
964                 // Parameter "progress" is normalized value. We need to multiply target duration to calculate distance.
965                 // Can get real distance using equation of deceleration (check Decelerating function)
966                 // After get real distance, normalize it
967                 float realDuration = progress * panAnimationDuration;
968                 float realDistance = velocityOfLastPan * ((float)Math.Pow(decelerationRate, realDuration) - 1) / logValueOfDeceleration;
969                 float result = Math.Min(realDistance / Math.Abs(panAnimationDelta), 1.0f);
970                 return result;
971             }
972         }
973
974         private void Decelerating(float velocity)
975         {
976             // Decelerating using deceleration equation ===========
977             //
978             // V   : velocity (pixel per milisecond)
979             // V0  : initial velocity
980             // d   : deceleration rate,
981             // t   : time
982             // X   : final position after decelerating
983             // log : natural logarithm
984             //
985             // V(t) = V0 * d pow t;
986             // X(t) = V0 * (d pow t - 1) / log d;  <-- Integrate the velocity function
987             // X(∞) = V0 * d / (1 - d); <-- Result using inifit T can be final position because T is tending to infinity.
988             //
989             // Because of final T is tending to inifity, we should use threshold value to finish.
990             // Final T = log(-threshold * log d / |V0| ) / log d; 
991
992             velocityOfLastPan = Math.Abs(velocity);
993
994             float currentScrollPosition = -(ScrollingDirection == Direction.Horizontal ? ContentContainer.CurrentPosition.X : ContentContainer.CurrentPosition.Y);
995             panAnimationDelta = (velocityOfLastPan * decelerationRate) / (1 - decelerationRate);
996             panAnimationDelta = velocity > 0 ? -panAnimationDelta : panAnimationDelta;
997
998             float destination = -(panAnimationDelta + currentScrollPosition);
999             float adjustDestination = AdjustTargetPositionOfScrollAnimation(destination);
1000             float maxPosition = ScrollAvailableArea != null ? ScrollAvailableArea.Y : maxScrollDistance;
1001             float minPosition = ScrollAvailableArea != null ? ScrollAvailableArea.X : 0;
1002
1003             if (destination < -maxPosition || destination > minPosition)
1004             {
1005                 panAnimationDelta = velocity > 0 ? (currentScrollPosition - minPosition) : (maxPosition - currentScrollPosition);
1006                 destination = velocity > 0 ? minPosition : -maxPosition;
1007
1008                 if (panAnimationDelta == 0)
1009                 {
1010                     panAnimationDuration = 0.0f;
1011                 }
1012                 else
1013                 {
1014                     panAnimationDuration = (float)Math.Log((panAnimationDelta * logValueOfDeceleration / velocityOfLastPan + 1), decelerationRate);
1015                 }
1016
1017                 Debug.WriteLineIf(LayoutDebugScrollableBase, "\n" +
1018                     "OverRange======================= \n" +
1019                     "[decelerationRate] " + decelerationRate + "\n" +
1020                     "[logValueOfDeceleration] " + logValueOfDeceleration + "\n" +
1021                     "[Velocity] " + velocityOfLastPan + "\n" +
1022                     "[CurrentPosition] " + currentScrollPosition + "\n" +
1023                     "[CandidateDelta] " + panAnimationDelta + "\n" +
1024                     "[Destination] " + destination + "\n" +
1025                     "[Duration] " + panAnimationDuration + "\n" +
1026                     "================================ \n"
1027                 );
1028             }
1029             else
1030             {
1031                 panAnimationDuration = (float)Math.Log(-DecelerationThreshold * logValueOfDeceleration / velocityOfLastPan) / logValueOfDeceleration;
1032
1033                 if (adjustDestination != destination)
1034                 {
1035                     destination = adjustDestination;
1036                     panAnimationDelta = destination + currentScrollPosition;
1037                     velocityOfLastPan = Math.Abs(panAnimationDelta * logValueOfDeceleration / ((float)Math.Pow(decelerationRate, panAnimationDuration) - 1));
1038                     panAnimationDuration = (float)Math.Log(-DecelerationThreshold * logValueOfDeceleration / velocityOfLastPan) / logValueOfDeceleration;
1039                 }
1040
1041                 Debug.WriteLineIf(LayoutDebugScrollableBase, "\n" +
1042                     "================================ \n" +
1043                     "[decelerationRate] " + decelerationRate + "\n" +
1044                     "[logValueOfDeceleration] " + logValueOfDeceleration + "\n" +
1045                     "[Velocity] " + velocityOfLastPan + "\n" +
1046                     "[CurrentPosition] " + currentScrollPosition + "\n" +
1047                     "[CandidateDelta] " + panAnimationDelta + "\n" +
1048                     "[Destination] " + destination + "\n" +
1049                     "[Duration] " + panAnimationDuration + "\n" +
1050                     "================================ \n"
1051                 );
1052             }
1053
1054             finalTargetPosition = destination;
1055
1056             customScrollAlphaFunction = new UserAlphaFunctionDelegate(CustomScrollAlphaFunction);
1057             scrollAnimation.DefaultAlphaFunction = new AlphaFunction(customScrollAlphaFunction);
1058             GC.KeepAlive(customScrollAlphaFunction);
1059             scrollAnimation.Duration = (int)panAnimationDuration;
1060             scrollAnimation.AnimateTo(ContentContainer, (ScrollingDirection == Direction.Horizontal) ? "PositionX" : "PositionY", destination);
1061             scrollAnimation.Play();
1062         }
1063
1064         protected void OnTapGestureDetected(object source, TapGestureDetector.DetectedEventArgs e)
1065         {
1066
1067         }
1068
1069         private void ScrollAnimationFinished(object sender, EventArgs e)
1070         {
1071             OnScrollAnimationEnded();
1072         }
1073
1074         /// <summary>
1075         /// Adjust scrolling position by own scrolling rules.
1076         /// Override this function when developer wants to change destination of flicking.(e.g. always snap to center of item)
1077         /// </summary>
1078         /// This may be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API
1079         [EditorBrowsable(EditorBrowsableState.Never)]
1080         protected virtual float AdjustTargetPositionOfScrollAnimation(float position)
1081         {
1082             return position;
1083         }
1084
1085     }
1086
1087 } // namespace