[NUI] Clean up unused api (#1785)
[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         private bool hideScrollbar = true;
448         private float maxScrollDistance;
449         private float childTargetPosition = 0.0f;
450         private PanGestureDetector mPanGestureDetector;
451         private View mInterruptTouchingChild;
452         private ScrollbarBase scrollBar;
453         private bool scrolling = false;
454         private float ratioOfScreenWidthToCompleteScroll = 0.5f;
455         private float totalDisplacementForPan = 0.0f;
456         private Size previousContainerSize = new Size();
457
458         // If false then can only flick pages when the current animation/scroll as ended.
459         private bool flickWhenAnimating = false;
460         private PropertyNotification propertyNotification;
461
462         private float noticeAnimationEndBeforePosition = 0.0f;
463         private bool readyToNotice = false;
464         // Let's consider more whether this needs to be set as protected.
465         public float NoticeAnimationEndBeforePosition { get => noticeAnimationEndBeforePosition; set => noticeAnimationEndBeforePosition = value; }
466
467
468         // Let's consider more whether this needs to be set as protected.
469         private float finalTargetPosition;
470
471         private Animation scrollAnimation;
472         // Declare user alpha function delegate
473         [UnmanagedFunctionPointer(CallingConvention.StdCall)]
474         private delegate float UserAlphaFunctionDelegate(float progress);
475         private UserAlphaFunctionDelegate customScrollAlphaFunction;
476         private float velocityOfLastPan = 0.0f;
477         private float panAnimationDuration = 0.0f;
478         private float panAnimationDelta = 0.0f;
479         private float logValueOfDeceleration = 0.0f;
480         private float decelerationRate = 0.0f;
481
482
483         /// <summary>
484         /// [Draft] Constructor
485         /// </summary>
486         /// This may be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API
487         [EditorBrowsable(EditorBrowsableState.Never)]
488         public ScrollableBase() : base()
489         {
490             DecelerationRate = 0.998f;
491
492             base.Layout = new ScrollableBaseCustomLayout();
493             mPanGestureDetector = new PanGestureDetector();
494             mPanGestureDetector.Attach(this);
495             mPanGestureDetector.AddDirection(PanGestureDetector.DirectionVertical);
496             mPanGestureDetector.Detected += OnPanGestureDetected;
497
498             ClippingMode = ClippingModeType.ClipChildren;
499
500             //Default Scrolling child
501             ContentContainer = new View()
502             {
503                 WidthSpecification = ScrollingDirection == Direction.Vertical ? LayoutParamPolicies.MatchParent : LayoutParamPolicies.WrapContent,
504                 HeightSpecification = ScrollingDirection == Direction.Vertical ? LayoutParamPolicies.WrapContent : LayoutParamPolicies.MatchParent,
505                 Layout = new AbsoluteLayout() { SetPositionByLayout = false },
506             };
507             ContentContainer.Relayout += OnScrollingChildRelayout;
508             propertyNotification = ContentContainer.AddPropertyNotification("position", PropertyCondition.Step(1.0f));
509             propertyNotification.Notified += OnPropertyChanged;
510             base.Add(ContentContainer);
511
512             //Interrupt touching when panning is started
513             mInterruptTouchingChild = new View()
514             {
515                 Size = new Size(Window.Instance.WindowSize),
516                 BackgroundColor = Color.Transparent,
517             };
518             mInterruptTouchingChild.TouchEvent += OnIterruptTouchingChildTouched;
519             Scrollbar = new Scrollbar();
520         }
521
522         private bool OnIterruptTouchingChildTouched(object source, View.TouchEventArgs args)
523         {
524             if (args.Touch.GetState(0) == PointStateType.Down)
525             {
526                 if (scrolling && !SnapToPage)
527                 {
528                     StopScroll();
529                 }
530             }
531             return true;
532         }
533
534         private void OnPropertyChanged(object source, PropertyNotification.NotifyEventArgs args)
535         {
536             OnScroll();
537         }
538
539         /// <summary>
540         /// Called after a child has been added to the owning view.
541         /// </summary>
542         /// <param name="view">The child which has been added.</param>
543         /// This may be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API
544         [EditorBrowsable(EditorBrowsableState.Never)]
545         public override void Add(View view)
546         {
547             ContentContainer.Add(view);
548         }
549
550         /// <summary>
551         /// Called after a child has been removed from the owning view.
552         /// </summary>
553         /// <param name="view">The child which has been removed.</param>
554         /// This may be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API
555         [EditorBrowsable(EditorBrowsableState.Never)]
556         public override void Remove(View view)
557         {
558             if (SnapToPage && CurrentPage == Children.IndexOf(view) && CurrentPage == Children.Count - 1)
559             {
560                 // Target View is current page and also last child.
561                 // CurrentPage should be changed to previous page.
562                 CurrentPage = Math.Max(0, CurrentPage - 1);
563                 ScrollToIndex(CurrentPage);
564             }
565
566             ContentContainer.Remove(view);
567         }
568
569         private void OnScrollingChildRelayout(object source, EventArgs args)
570         {
571             // Size is changed. Calculate maxScrollDistance.
572             bool isSizeChanged = previousContainerSize.Width != ContentContainer.Size.Width || previousContainerSize.Height != ContentContainer.Size.Height;
573
574             if (isSizeChanged)
575             {
576                 maxScrollDistance = CalculateMaximumScrollDistance();
577                 SetScrollbar();
578             }
579
580             previousContainerSize = ContentContainer.Size;
581         }
582
583         /// <summary>
584         /// The composition of a Scrollbar can vary depending on how you use ScrollableBase. 
585         /// Set the composition that will go into the ScrollableBase according to your ScrollableBase.
586         /// </summary>
587         /// This may be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API
588         [EditorBrowsable(EditorBrowsableState.Never)]
589         protected virtual void SetScrollbar()
590         {
591             if (Scrollbar)
592             {
593                 bool isHorizontal = ScrollingDirection == Direction.Horizontal;
594                 float contentLength = isHorizontal ? ContentContainer.Size.Width : ContentContainer.Size.Height;
595                 float viewportLength = isHorizontal ? Size.Width : Size.Height;
596                 float currentPosition = isHorizontal ? ContentContainer.CurrentPosition.X : ContentContainer.CurrentPosition.Y;
597                 Scrollbar.Initialize(contentLength, viewportLength, currentPosition, isHorizontal);
598             }
599         }
600
601         /// <summary>
602         /// Scrolls to the item at the specified index.
603         /// </summary>
604         /// <param name="index">Index of item.</param>
605         /// This may be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API
606         [EditorBrowsable(EditorBrowsableState.Never)]
607         public void ScrollToIndex(int index)
608         {
609             if (ContentContainer.ChildCount - 1 < index || index < 0)
610             {
611                 return;
612             }
613
614             if (SnapToPage)
615             {
616                 CurrentPage = index;
617             }
618
619             float targetPosition = Math.Min(ScrollingDirection == Direction.Vertical ? Children[index].Position.Y : Children[index].Position.X, maxScrollDistance);
620             AnimateChildTo(ScrollDuration, -targetPosition);
621         }
622
623         private void OnScrollDragStarted()
624         {
625             ScrollEventArgs eventArgs = new ScrollEventArgs(ContentContainer.CurrentPosition);
626             ScrollDragStarted?.Invoke(this, eventArgs);
627         }
628
629         private void OnScrollDragEnded()
630         {
631             ScrollEventArgs eventArgs = new ScrollEventArgs(ContentContainer.CurrentPosition);
632             ScrollDragEnded?.Invoke(this, eventArgs);
633         }
634
635         private void OnScrollAnimationStarted()
636         {
637             ScrollEventArgs eventArgs = new ScrollEventArgs(ContentContainer.CurrentPosition);
638             ScrollAnimationStarted?.Invoke(this, eventArgs);
639         }
640
641         private void OnScrollAnimationEnded()
642         {
643             scrolling = false;
644             base.Remove(mInterruptTouchingChild);
645
646             ScrollEventArgs eventArgs = new ScrollEventArgs(ContentContainer.CurrentPosition);
647             ScrollAnimationEnded?.Invoke(this, eventArgs);
648         }
649
650         private void OnScroll()
651         {
652             ScrollEventArgs eventArgs = new ScrollEventArgs(ContentContainer.CurrentPosition);
653             Scrolling?.Invoke(this, eventArgs);
654
655             bool isHorizontal = ScrollingDirection == Direction.Horizontal;
656             float contentLength = isHorizontal ? ContentContainer.Size.Width : ContentContainer.Size.Height;
657             float currentPosition = isHorizontal ? ContentContainer.CurrentPosition.X : ContentContainer.CurrentPosition.Y;
658
659             scrollBar.Update(contentLength, Math.Abs(currentPosition));
660             CheckPreReachedTargetPosition();
661         }
662
663         private void CheckPreReachedTargetPosition()
664         {
665             // Check whether we reached pre-reached target position
666             if (readyToNotice &&
667                 ContentContainer.CurrentPosition.Y <= finalTargetPosition + NoticeAnimationEndBeforePosition &&
668                 ContentContainer.CurrentPosition.Y >= finalTargetPosition - NoticeAnimationEndBeforePosition)
669             {
670                 //Notice first
671                 readyToNotice = false;
672                 OnPreReachedTargetPosition(finalTargetPosition);
673             }
674         }
675
676         /// <summary>
677         /// This helps developer who wants to know before scroll is reaching target position.
678         /// </summary>
679         /// <param name="targetPosition">Index of item.</param>
680         /// This may be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API
681         [EditorBrowsable(EditorBrowsableState.Never)]
682         protected virtual void OnPreReachedTargetPosition(float targetPosition)
683         {
684
685         }
686
687         private void StopScroll()
688         {
689             if (scrollAnimation != null)
690             {
691                 if (scrollAnimation.State == Animation.States.Playing)
692                 {
693                     Debug.WriteLineIf(LayoutDebugScrollableBase, "StopScroll Animation Playing");
694                     scrollAnimation.Stop(Animation.EndActions.Cancel);
695                     OnScrollAnimationEnded();
696                 }
697                 scrollAnimation.Clear();
698             }
699         }
700
701         private void AnimateChildTo(int duration, float axisPosition)
702         {
703             Debug.WriteLineIf(LayoutDebugScrollableBase, "AnimationTo Animation Duration:" + duration + " Destination:" + axisPosition);
704             finalTargetPosition = axisPosition;
705
706             StopScroll(); // Will replace previous animation so will stop existing one.
707
708             if (scrollAnimation == null)
709             {
710                 scrollAnimation = new Animation();
711                 scrollAnimation.Finished += ScrollAnimationFinished;
712             }
713
714             scrollAnimation.Duration = duration;
715             scrollAnimation.DefaultAlphaFunction = new AlphaFunction(AlphaFunction.BuiltinFunctions.EaseOutSquare);
716             scrollAnimation.AnimateTo(ContentContainer, (ScrollingDirection == Direction.Horizontal) ? "PositionX" : "PositionY", axisPosition);
717             scrolling = true;
718             OnScrollAnimationStarted();
719             scrollAnimation.Play();
720         }
721
722         /// <summary>
723         /// Scroll to specific position with or without animation.
724         /// </summary>
725         /// <param name="position">Destination.</param>
726         /// <param name="animate">Scroll with or without animation</param>
727         [EditorBrowsable(EditorBrowsableState.Never)]
728         public void ScrollTo(float position, bool animate)
729         {
730             float currentPositionX = ContentContainer.CurrentPosition.X != 0 ? ContentContainer.CurrentPosition.X : ContentContainer.Position.X;
731             float currentPositionY = ContentContainer.CurrentPosition.Y != 0 ? ContentContainer.CurrentPosition.Y : ContentContainer.Position.Y;
732             float delta = ScrollingDirection == Direction.Horizontal ? currentPositionX : currentPositionY;
733             // The argument position is the new pan position. So the new position of ScrollableBase becomes (-position).
734             // To move ScrollableBase's position to (-position), it moves by (-position - currentPosition).
735             delta = -position - delta;
736
737             ScrollBy(delta, animate);
738         }
739
740         private float BoundScrollPosition(float targetPosition)
741         {
742             if (ScrollAvailableArea != null)
743             {
744                 float minScrollPosition = ScrollAvailableArea.X;
745                 float maxScrollPosition = ScrollAvailableArea.Y;
746
747                 targetPosition = Math.Min(-minScrollPosition, targetPosition);
748                 targetPosition = Math.Max(-maxScrollPosition, targetPosition);
749             }
750             else
751             {
752                 targetPosition = Math.Min(0, targetPosition);
753                 targetPosition = Math.Max(-maxScrollDistance, targetPosition);
754             }
755
756             return targetPosition;
757         }
758
759         private void ScrollBy(float displacement, bool animate)
760         {
761             if (GetChildCount() == 0 || maxScrollDistance < 0)
762             {
763                 return;
764             }
765
766             float childCurrentPosition = (ScrollingDirection == Direction.Horizontal) ? ContentContainer.PositionX : ContentContainer.PositionY;
767
768             Debug.WriteLineIf(LayoutDebugScrollableBase, "ScrollBy childCurrentPosition:" + childCurrentPosition +
769                                                    " displacement:" + displacement,
770                                                    " maxScrollDistance:" + maxScrollDistance);
771
772             childTargetPosition = childCurrentPosition + displacement; // child current position + gesture displacement
773
774
775             Debug.WriteLineIf(LayoutDebugScrollableBase, "ScrollBy currentAxisPosition:" + childCurrentPosition + "childTargetPosition:" + childTargetPosition);
776
777             if (animate)
778             {
779                 // Calculate scroll animaton duration
780                 float scrollDistance = Math.Abs(displacement);
781                 readyToNotice = true;
782
783                 AnimateChildTo(ScrollDuration, BoundScrollPosition(AdjustTargetPositionOfScrollAnimation(BoundScrollPosition(childTargetPosition))));
784             }
785             else
786             {
787                 finalTargetPosition = BoundScrollPosition(childTargetPosition);
788
789                 // Set position of scrolling child without an animation
790                 if (ScrollingDirection == Direction.Horizontal)
791                 {
792                     ContentContainer.PositionX = finalTargetPosition;
793                 }
794                 else
795                 {
796                     ContentContainer.PositionY = finalTargetPosition;
797                 }
798
799             }
800         }
801
802         /// <summary>
803         /// you can override it to clean-up your own resources.
804         /// </summary>
805         /// <param name="type">DisposeTypes</param>
806         /// This will be public opened in tizen_5.5 after ACR done. Before ACR, need to be hidden as inhouse API.
807         [EditorBrowsable(EditorBrowsableState.Never)]
808         protected override void Dispose(DisposeTypes type)
809         {
810             if (disposed)
811             {
812                 return;
813             }
814
815             if (type == DisposeTypes.Explicit)
816             {
817                 StopScroll();
818
819                 if (mPanGestureDetector != null)
820                 {
821                     mPanGestureDetector.Detected -= OnPanGestureDetected;
822                     mPanGestureDetector.Dispose();
823                     mPanGestureDetector = null;
824                 }
825             }
826             base.Dispose(type);
827         }
828
829         private float CalculateMaximumScrollDistance()
830         {
831             float scrollingChildLength = 0;
832             float scrollerLength = 0;
833             if (ScrollingDirection == Direction.Horizontal)
834             {
835                 Debug.WriteLineIf(LayoutDebugScrollableBase, "Horizontal");
836
837                 scrollingChildLength = ContentContainer.Size.Width;
838                 scrollerLength = Size.Width;
839             }
840             else
841             {
842                 Debug.WriteLineIf(LayoutDebugScrollableBase, "Vertical");
843                 scrollingChildLength = ContentContainer.Size.Height;
844                 scrollerLength = Size.Height;
845             }
846
847             Debug.WriteLineIf(LayoutDebugScrollableBase, "ScrollBy maxScrollDistance:" + (scrollingChildLength - scrollerLength) +
848                                                    " parent length:" + scrollerLength +
849                                                    " scrolling child length:" + scrollingChildLength);
850
851             return Math.Max(scrollingChildLength - scrollerLength, 0);
852         }
853
854         private void PageSnap()
855         {
856             Debug.WriteLineIf(LayoutDebugScrollableBase, "PageSnap with pan candidate totalDisplacement:" + totalDisplacementForPan +
857                                                                 " currentPage[" + CurrentPage + "]");
858
859             //Increment current page if total displacement enough to warrant a page change.
860             if (Math.Abs(totalDisplacementForPan) > (mPageWidth * ratioOfScreenWidthToCompleteScroll))
861             {
862                 if (totalDisplacementForPan < 0)
863                 {
864                     CurrentPage = Math.Min(Math.Max(Children.Count - 1, 0), ++CurrentPage);
865                 }
866                 else
867                 {
868                     CurrentPage = Math.Max(0, --CurrentPage);
869                 }
870             }
871
872             // Animate to new page or reposition to current page
873             float destinationX = -(Children[CurrentPage].Position.X + Children[CurrentPage].CurrentSize.Width / 2 - CurrentSize.Width / 2); // set to middle of current page
874             Debug.WriteLineIf(LayoutDebugScrollableBase, "Snapping to page[" + CurrentPage + "] to:" + destinationX + " from:" + ContentContainer.PositionX);
875             AnimateChildTo(ScrollDuration, destinationX);
876         }
877
878         private void OnPanGestureDetected(object source, PanGestureDetector.DetectedEventArgs e)
879         {
880             if (e.PanGesture.State == Gesture.StateType.Started)
881             {
882                 readyToNotice = false;
883                 base.Add(mInterruptTouchingChild);
884                 Debug.WriteLineIf(LayoutDebugScrollableBase, "Gesture Start");
885                 if (scrolling && !SnapToPage)
886                 {
887                     StopScroll();
888                 }
889                 totalDisplacementForPan = 0.0f;
890                 OnScrollDragStarted();
891             }
892             else if (e.PanGesture.State == Gesture.StateType.Continuing)
893             {
894                 if (ScrollingDirection == Direction.Horizontal)
895                 {
896                     ScrollBy(e.PanGesture.Displacement.X, false);
897                     totalDisplacementForPan += e.PanGesture.Displacement.X;
898                 }
899                 else
900                 {
901                     ScrollBy(e.PanGesture.Displacement.Y, false);
902                     totalDisplacementForPan += e.PanGesture.Displacement.Y;
903                 }
904                 Debug.WriteLineIf(LayoutDebugScrollableBase, "OnPanGestureDetected Continue totalDisplacementForPan:" + totalDisplacementForPan);
905             }
906             else if (e.PanGesture.State == Gesture.StateType.Finished)
907             {
908                 OnScrollDragEnded();
909                 StopScroll(); // Will replace previous animation so will stop existing one.
910
911                 if (scrollAnimation == null)
912                 {
913                     scrollAnimation = new Animation();
914                     scrollAnimation.Finished += ScrollAnimationFinished;
915                 }
916
917                 if (SnapToPage)
918                 {
919                     PageSnap();
920                 }
921                 else
922                 {
923                     Decelerating((ScrollingDirection == Direction.Horizontal) ? e.PanGesture.Velocity.X : e.PanGesture.Velocity.Y);
924                 }
925
926                 totalDisplacementForPan = 0;
927                 scrolling = true;
928                 readyToNotice = true;
929                 OnScrollAnimationStarted();
930             }
931         }
932
933         private float CustomScrollAlphaFunction(float progress)
934         {
935             if (panAnimationDelta == 0)
936             {
937                 return 1.0f;
938             }
939             else
940             {
941                 // Parameter "progress" is normalized value. We need to multiply target duration to calculate distance.
942                 // Can get real distance using equation of deceleration (check Decelerating function)
943                 // After get real distance, normalize it
944                 float realDuration = progress * panAnimationDuration;
945                 float realDistance = velocityOfLastPan * ((float)Math.Pow(decelerationRate, realDuration) - 1) / logValueOfDeceleration;
946                 float result = Math.Min(realDistance / Math.Abs(panAnimationDelta), 1.0f);
947                 return result;
948             }
949         }
950
951         private void Decelerating(float velocity)
952         {
953             // Decelerating using deceleration equation ===========
954             //
955             // V   : velocity (pixel per milisecond)
956             // V0  : initial velocity
957             // d   : deceleration rate,
958             // t   : time
959             // X   : final position after decelerating
960             // log : natural logarithm
961             //
962             // V(t) = V0 * d pow t;
963             // X(t) = V0 * (d pow t - 1) / log d;  <-- Integrate the velocity function
964             // X(∞) = V0 * d / (1 - d); <-- Result using inifit T can be final position because T is tending to infinity.
965             //
966             // Because of final T is tending to inifity, we should use threshold value to finish.
967             // Final T = log(-threshold * log d / |V0| ) / log d; 
968
969             velocityOfLastPan = Math.Abs(velocity);
970
971             float currentScrollPosition = -(ScrollingDirection == Direction.Horizontal ? ContentContainer.CurrentPosition.X : ContentContainer.CurrentPosition.Y);
972             panAnimationDelta = (velocityOfLastPan * decelerationRate) / (1 - decelerationRate);
973             panAnimationDelta = velocity > 0 ? -panAnimationDelta : panAnimationDelta;
974
975             float destination = -(panAnimationDelta + currentScrollPosition);
976             float adjustDestination = AdjustTargetPositionOfScrollAnimation(destination);
977             float maxPosition = ScrollAvailableArea != null ? ScrollAvailableArea.Y : maxScrollDistance;
978             float minPosition = ScrollAvailableArea != null ? ScrollAvailableArea.X : 0;
979
980             if (destination < -maxPosition || destination > minPosition)
981             {
982                 panAnimationDelta = velocity > 0 ? (currentScrollPosition - minPosition) : (maxPosition - currentScrollPosition);
983                 destination = velocity > 0 ? minPosition : -maxPosition;
984
985                 if (panAnimationDelta == 0)
986                 {
987                     panAnimationDuration = 0.0f;
988                 }
989                 else
990                 {
991                     panAnimationDuration = (float)Math.Log((panAnimationDelta * logValueOfDeceleration / velocityOfLastPan + 1), decelerationRate);
992                 }
993
994                 Debug.WriteLineIf(LayoutDebugScrollableBase, "\n" +
995                     "OverRange======================= \n" +
996                     "[decelerationRate] " + decelerationRate + "\n" +
997                     "[logValueOfDeceleration] " + logValueOfDeceleration + "\n" +
998                     "[Velocity] " + velocityOfLastPan + "\n" +
999                     "[CurrentPosition] " + currentScrollPosition + "\n" +
1000                     "[CandidateDelta] " + panAnimationDelta + "\n" +
1001                     "[Destination] " + destination + "\n" +
1002                     "[Duration] " + panAnimationDuration + "\n" +
1003                     "================================ \n"
1004                 );
1005             }
1006             else
1007             {
1008                 panAnimationDuration = (float)Math.Log(-DecelerationThreshold * logValueOfDeceleration / velocityOfLastPan) / logValueOfDeceleration;
1009
1010                 if (adjustDestination != destination)
1011                 {
1012                     destination = adjustDestination;
1013                     panAnimationDelta = destination + currentScrollPosition;
1014                     velocityOfLastPan = Math.Abs(panAnimationDelta * logValueOfDeceleration / ((float)Math.Pow(decelerationRate, panAnimationDuration) - 1));
1015                     panAnimationDuration = (float)Math.Log(-DecelerationThreshold * logValueOfDeceleration / velocityOfLastPan) / logValueOfDeceleration;
1016                 }
1017
1018                 Debug.WriteLineIf(LayoutDebugScrollableBase, "\n" +
1019                     "================================ \n" +
1020                     "[decelerationRate] " + decelerationRate + "\n" +
1021                     "[logValueOfDeceleration] " + logValueOfDeceleration + "\n" +
1022                     "[Velocity] " + velocityOfLastPan + "\n" +
1023                     "[CurrentPosition] " + currentScrollPosition + "\n" +
1024                     "[CandidateDelta] " + panAnimationDelta + "\n" +
1025                     "[Destination] " + destination + "\n" +
1026                     "[Duration] " + panAnimationDuration + "\n" +
1027                     "================================ \n"
1028                 );
1029             }
1030
1031             finalTargetPosition = destination;
1032
1033             customScrollAlphaFunction = new UserAlphaFunctionDelegate(CustomScrollAlphaFunction);
1034             scrollAnimation.DefaultAlphaFunction = new AlphaFunction(customScrollAlphaFunction);
1035             GC.KeepAlive(customScrollAlphaFunction);
1036             scrollAnimation.Duration = (int)panAnimationDuration;
1037             scrollAnimation.AnimateTo(ContentContainer, (ScrollingDirection == Direction.Horizontal) ? "PositionX" : "PositionY", destination);
1038             scrollAnimation.Play();
1039         }
1040
1041         protected void OnTapGestureDetected(object source, TapGestureDetector.DetectedEventArgs e)
1042         {
1043
1044         }
1045
1046         private void ScrollAnimationFinished(object sender, EventArgs e)
1047         {
1048             OnScrollAnimationEnded();
1049         }
1050
1051         /// <summary>
1052         /// Adjust scrolling position by own scrolling rules.
1053         /// Override this function when developer wants to change destination of flicking.(e.g. always snap to center of item)
1054         /// </summary>
1055         /// This may be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API
1056         [EditorBrowsable(EditorBrowsableState.Never)]
1057         protected virtual float AdjustTargetPositionOfScrollAnimation(float position)
1058         {
1059             return position;
1060         }
1061
1062     }
1063
1064 } // namespace