671f8e3b8d2443f7b299d8c2790a6ea4a8441226
[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;
18 using Tizen.NUI.BaseComponents;
19 using System.Collections.Generic;
20 using System.ComponentModel;
21 using System.Diagnostics;
22 using System.Runtime.InteropServices;
23 using Tizen.NUI.Accessibility;
24
25 namespace Tizen.NUI.Components
26 {
27     /// <summary>
28     /// ScrollEventArgs is a class to record scroll event arguments which will sent to user.
29     /// </summary>
30     /// <since_tizen> 8 </since_tizen>
31     public class ScrollEventArgs : EventArgs
32     {
33         private Position position;
34         private Position scrollPosition;
35
36         /// <summary>
37         /// Default constructor.
38         /// </summary>
39         /// <param name="position">Current contianer position</param>
40         /// <since_tizen> 8 </since_tizen>
41         public ScrollEventArgs(Position position)
42         {
43             this.position = position;
44             this.scrollPosition = new Position(-position);
45         }
46
47         /// <summary>
48         /// Current position of ContentContainer.
49         /// </summary>
50         /// <since_tizen> 8 </since_tizen>
51         public Position Position
52         {
53             get
54             {
55                 return position;
56             }
57         }
58         /// <summary>
59         /// Current scroll position of scrollableBase pan.
60         /// This is the position in the opposite direction to the current position of ContentContainer.
61         /// </summary>
62         [EditorBrowsable(EditorBrowsableState.Never)]
63         public Position ScrollPosition
64         {
65             get
66             {
67                 return scrollPosition;
68             }
69         }
70     }
71
72     /// <summary>
73     /// ScrollOutofBoundEventArgs is to record scroll out-of-bound event arguments which will be sent to user.
74     /// </summary>
75     [EditorBrowsable(EditorBrowsableState.Never)]
76     public class ScrollOutOfBoundEventArgs : EventArgs
77     {
78         /// <summary>
79         /// The direction to be touched.
80         /// </summary>
81         [EditorBrowsable(EditorBrowsableState.Never)]
82         public enum Direction
83         {
84             /// <summary>
85             /// Upwards.
86             /// </summary>
87             [EditorBrowsable(EditorBrowsableState.Never)]
88             Up,
89
90             /// <summary>
91             /// Downwards.
92             /// </summary>
93             [EditorBrowsable(EditorBrowsableState.Never)]
94             Down,
95
96             /// <summary>
97             /// Left bound.
98             /// </summary>
99             [EditorBrowsable(EditorBrowsableState.Never)]
100             Left,
101
102             /// <summary>
103             /// Right bound.
104             /// </summary>
105             [EditorBrowsable(EditorBrowsableState.Never)]
106             Right,
107         }
108
109         /// <summary>
110         /// Constructor.
111         /// </summary>
112         /// <param name="direction">Current pan direction</param>
113         /// <param name="displacement">Current total displacement</param>
114         [EditorBrowsable(EditorBrowsableState.Never)]
115         public ScrollOutOfBoundEventArgs(Direction direction, float displacement)
116         {
117             PanDirection = direction;
118             Displacement = displacement;
119         }
120
121         /// <summary>
122         /// Current pan direction of ContentContainer.
123         /// </summary>
124         [EditorBrowsable(EditorBrowsableState.Never)]
125         public Direction PanDirection
126         {
127             get;
128         }
129
130         /// <summary>
131         /// Current total displacement of ContentContainer.
132         /// if its value is greater than 0, it is at the top/left;
133         /// if less than 0, it is at the bottom/right.
134         /// </summary>
135         [EditorBrowsable(EditorBrowsableState.Never)]
136         public float Displacement
137         {
138             get;
139         }
140     }
141
142     /// <summary>
143     /// This class provides a View that can scroll a single View with a layout. This View can be a nest of Views.
144     /// </summary>
145     /// <since_tizen> 8 </since_tizen>
146     public class ScrollableBase : Control
147     {
148         static bool LayoutDebugScrollableBase = false; // Debug flag
149         private Direction mScrollingDirection = Direction.Vertical;
150         private bool mScrollEnabled = true;
151         private int mScrollDuration = 125;
152         private int mPageWidth = 0;
153         private float mPageFlickThreshold = 0.4f;
154         private float mScrollingEventThreshold = 0.001f;
155
156         private class ScrollableBaseCustomLayout : AbsoluteLayout
157         {
158             protected override void OnMeasure(MeasureSpecification widthMeasureSpec, MeasureSpecification heightMeasureSpec)
159             {
160                 MeasuredSize.StateType childWidthState = MeasuredSize.StateType.MeasuredSizeOK;
161                 MeasuredSize.StateType childHeightState = MeasuredSize.StateType.MeasuredSizeOK;
162
163                 Direction scrollingDirection = Direction.Vertical;
164                 ScrollableBase scrollableBase = this.Owner as ScrollableBase;
165                 if (scrollableBase != null)
166                 {
167                     scrollingDirection = scrollableBase.ScrollingDirection;
168                 }
169
170                 float totalWidth = 0.0f;
171                 float totalHeight = 0.0f;
172
173                 // measure child, should be a single scrolling child
174                 foreach (LayoutItem childLayout in LayoutChildren)
175                 {
176                     if (childLayout != null && childLayout.Owner.Name == "ContentContainer")
177                     {
178                         // Get size of child
179                         // Use an Unspecified MeasureSpecification mode so scrolling child is not restricted to it's parents size in Height (for vertical scrolling)
180                         // or Width for horizontal scrolling
181                         if (scrollingDirection == Direction.Vertical)
182                         {
183                             MeasureSpecification unrestrictedMeasureSpec = new MeasureSpecification(heightMeasureSpec.Size, MeasureSpecification.ModeType.Unspecified);
184                             MeasureChildWithMargins(childLayout, widthMeasureSpec, new LayoutLength(0), unrestrictedMeasureSpec, new LayoutLength(0)); // Height unrestricted by parent
185                         }
186                         else
187                         {
188                             MeasureSpecification unrestrictedMeasureSpec = new MeasureSpecification(widthMeasureSpec.Size, MeasureSpecification.ModeType.Unspecified);
189                             MeasureChildWithMargins(childLayout, unrestrictedMeasureSpec, new LayoutLength(0), heightMeasureSpec, new LayoutLength(0)); // Width unrestricted by parent
190                         }
191
192                         totalWidth = (childLayout.MeasuredWidth.Size + (childLayout.Padding.Start + childLayout.Padding.End)).AsDecimal();
193                         totalHeight = (childLayout.MeasuredHeight.Size + (childLayout.Padding.Top + childLayout.Padding.Bottom)).AsDecimal();
194
195                         if (childLayout.MeasuredWidth.State == MeasuredSize.StateType.MeasuredSizeTooSmall)
196                         {
197                             childWidthState = MeasuredSize.StateType.MeasuredSizeTooSmall;
198                         }
199                         if (childLayout.MeasuredHeight.State == MeasuredSize.StateType.MeasuredSizeTooSmall)
200                         {
201                             childHeightState = MeasuredSize.StateType.MeasuredSizeTooSmall;
202                         }
203                     }
204                 }
205
206                 MeasuredSize widthSizeAndState = ResolveSizeAndState(new LayoutLength(totalWidth), widthMeasureSpec, MeasuredSize.StateType.MeasuredSizeOK);
207                 MeasuredSize heightSizeAndState = ResolveSizeAndState(new LayoutLength(totalHeight), heightMeasureSpec, MeasuredSize.StateType.MeasuredSizeOK);
208                 totalWidth = widthSizeAndState.Size.AsDecimal();
209                 totalHeight = heightSizeAndState.Size.AsDecimal();
210
211                 // Ensure layout respects it's given minimum size
212                 totalWidth = Math.Max(totalWidth, SuggestedMinimumWidth.AsDecimal());
213                 totalHeight = Math.Max(totalHeight, SuggestedMinimumHeight.AsDecimal());
214
215                 widthSizeAndState.State = childWidthState;
216                 heightSizeAndState.State = childHeightState;
217
218                 SetMeasuredDimensions(ResolveSizeAndState(new LayoutLength(totalWidth), widthMeasureSpec, childWidthState),
219                     ResolveSizeAndState(new LayoutLength(totalHeight), heightMeasureSpec, childHeightState));
220
221                 // Size of ScrollableBase is changed. Change Page width too.
222                 if (scrollableBase != null)
223                 {
224                     scrollableBase.mPageWidth = (int)MeasuredWidth.Size.AsRoundedValue();
225                     scrollableBase.OnScrollingChildRelayout(null, null);
226                 }
227             }
228         } //  ScrollableBaseCustomLayout
229
230         /// <summary>
231         /// The direction axis to scroll.
232         /// </summary>
233         /// <since_tizen> 8 </since_tizen>
234         public enum Direction
235         {
236             /// <summary>
237             /// Horizontal axis.
238             /// </summary>
239             /// <since_tizen> 8 </since_tizen>
240             Horizontal,
241
242             /// <summary>
243             /// Vertical axis.
244             /// </summary>
245             /// <since_tizen> 8 </since_tizen>
246             Vertical
247         }
248
249         /// <summary>
250         /// Scrolling direction mode.
251         /// Default is Vertical scrolling.
252         /// </summary>
253         /// <since_tizen> 8 </since_tizen>
254         public Direction ScrollingDirection
255         {
256             get
257             {
258                 return mScrollingDirection;
259             }
260             set
261             {
262                 if (value != mScrollingDirection)
263                 {
264                     mScrollingDirection = value;
265                     mPanGestureDetector.ClearAngles();
266                     mPanGestureDetector.AddDirection(value == Direction.Horizontal ?
267                         PanGestureDetector.DirectionHorizontal : PanGestureDetector.DirectionVertical);
268
269                     ContentContainer.WidthSpecification = ScrollingDirection == Direction.Vertical ? LayoutParamPolicies.MatchParent : LayoutParamPolicies.WrapContent;
270                     ContentContainer.HeightSpecification = ScrollingDirection == Direction.Vertical ? LayoutParamPolicies.WrapContent : LayoutParamPolicies.MatchParent;
271                 }
272             }
273         }
274
275         /// <summary>
276         /// Enable or disable scrolling.
277         /// </summary>
278         /// <since_tizen> 8 </since_tizen>
279         public bool ScrollEnabled
280         {
281             get
282             {
283                 return mScrollEnabled;
284             }
285             set
286             {
287                 if (value != mScrollEnabled)
288                 {
289                     mScrollEnabled = value;
290                     if (mScrollEnabled)
291                     {
292                         mPanGestureDetector.Detected += OnPanGestureDetected;
293                     }
294                     else
295                     {
296                         mPanGestureDetector.Detected -= OnPanGestureDetected;
297                     }
298                 }
299             }
300         }
301
302         /// <summary>
303         /// Gets scrollable status.
304         /// </summary>
305         [EditorBrowsable(EditorBrowsableState.Never)]
306         protected override bool AccessibilityIsScrollable()
307         {
308             return true;
309         }
310
311         /// <summary>
312         /// Pages mode, enables moving to the next or return to current page depending on pan displacement.
313         /// Default is false.
314         /// </summary>
315         /// <since_tizen> 8 </since_tizen>
316         public bool SnapToPage { set; get; } = false;
317
318         /// <summary>
319         /// Get current page.
320         /// Working property with SnapToPage property.
321         /// </summary>
322         /// <since_tizen> 8 </since_tizen>
323         public int CurrentPage { get; private set; } = 0;
324
325         /// <summary>
326         /// Duration of scroll animation.
327         /// Default value is 125ms.
328         /// </summary>
329         /// <since_tizen> 8 </since_tizen>
330         public int ScrollDuration
331         {
332             set
333             {
334                 mScrollDuration = value >= 0 ? value : mScrollDuration;
335             }
336             get
337             {
338                 return mScrollDuration;
339             }
340         }
341
342         /// <summary>
343         /// Scroll Available area.
344         /// </summary>
345         /// <since_tizen> 8 </since_tizen>
346         public Vector2 ScrollAvailableArea { set; get; }
347
348         /// <summary>
349         /// An event emitted when user starts dragging ScrollableBase, user can subscribe or unsubscribe to this event handler.<br />
350         /// </summary>
351         /// <since_tizen> 8 </since_tizen>
352         public event EventHandler<ScrollEventArgs> ScrollDragStarted;
353
354         /// <summary>
355         /// An event emitted when user stops dragging ScrollableBase, user can subscribe or unsubscribe to this event handler.<br />
356         /// </summary>
357         /// <since_tizen> 8 </since_tizen>
358         public event EventHandler<ScrollEventArgs> ScrollDragEnded;
359
360         /// <summary>
361         /// An event emitted when the scrolling slide animation starts, user can subscribe or unsubscribe to this event handler.<br />
362         /// </summary>
363         /// <since_tizen> 8 </since_tizen>
364         public event EventHandler<ScrollEventArgs> ScrollAnimationStarted;
365
366         /// <summary>
367         /// An event emitted when the scrolling slide animation ends, user can subscribe or unsubscribe to this event handler.<br />
368         /// </summary>
369         /// <since_tizen> 8 </since_tizen>
370         public event EventHandler<ScrollEventArgs> ScrollAnimationEnded;
371
372         /// <summary>
373         /// An event emitted when scrolling, user can subscribe or unsubscribe to this event handler.<br />
374         /// </summary>
375         /// <since_tizen> 8 </since_tizen>
376         public event EventHandler<ScrollEventArgs> Scrolling;
377
378         /// <summary>
379         /// An event emitted when scrolling out of bound, user can subscribe or unsubscribe to this event handler.<br />
380         /// </summary>
381         [EditorBrowsable(EditorBrowsableState.Never)]
382         public event EventHandler<ScrollOutOfBoundEventArgs> ScrollOutOfBound;
383
384         /// <summary>
385         /// Scrollbar for ScrollableBase.
386         /// </summary>
387         /// <since_tizen> 8 </since_tizen>
388         public ScrollbarBase Scrollbar
389         {
390             get
391             {
392                 return scrollBar;
393             }
394             set
395             {
396                 if (scrollBar)
397                 {
398                     base.Remove(scrollBar);
399                 }
400                 scrollBar = value;
401
402                 if (scrollBar != null)
403                 {
404                     scrollBar.Name = "ScrollBar";
405                     base.Add(scrollBar);
406
407                     if (hideScrollbar)
408                     {
409                         scrollBar.Hide();
410                     }
411                     else
412                     {
413                         scrollBar.Show();
414                     }
415
416                     SetScrollbar();
417                 }
418             }
419         }
420
421         /// <summary>
422         /// Always hide Scrollbar.
423         /// </summary>
424         /// <since_tizen> 8 </since_tizen>
425         public bool HideScrollbar
426         {
427             get
428             {
429                 return hideScrollbar;
430             }
431             set
432             {
433                 hideScrollbar = value;
434
435                 if (scrollBar)
436                 {
437                     if (value)
438                     {
439                         scrollBar.Hide();
440                     }
441                     else
442                     {
443                         scrollBar.Show();
444                     }
445                 }
446             }
447         }
448
449         /// <summary>
450         /// Container which has content of ScrollableBase.
451         /// </summary>
452         /// <since_tizen> 8 </since_tizen>
453         public View ContentContainer { get; private set; }
454
455         /// <summary>
456         /// Set the layout on this View. Replaces any existing Layout.
457         /// </summary>
458         /// <since_tizen> 8 </since_tizen>
459         public new LayoutItem Layout
460         {
461             get
462             {
463                 return ContentContainer.Layout;
464             }
465             set
466             {
467                 ContentContainer.Layout = value;
468             }
469         }
470
471         /// <summary>
472         /// List of children of Container.
473         /// </summary>
474         /// <since_tizen> 8 </since_tizen>
475         public new List<View> Children
476         {
477             get
478             {
479                 return ContentContainer.Children;
480             }
481         }
482
483         /// <summary>
484         /// Deceleration rate of scrolling by finger.
485         /// Rate should be bigger than 0 and smaller than 1.
486         /// Default value is 0.998f;
487         /// </summary>
488         /// <since_tizen> 8 </since_tizen>
489         public float DecelerationRate
490         {
491             get
492             {
493                 return decelerationRate;
494             }
495             set
496             {
497                 decelerationRate = (value < 1 && value > 0) ? value : decelerationRate;
498                 logValueOfDeceleration = (float)Math.Log(value);
499             }
500         }
501
502         /// <summary>
503         /// Threashold not to go infinit at the end of scrolling animation.
504         /// </summary>
505         [EditorBrowsable(EditorBrowsableState.Never)]
506         public float DecelerationThreshold { get; set; } = 0.1f;
507
508         /// <summary>
509         /// Scrolling event will be thrown when this amount of scroll position is changed.
510         /// If this threshold becomes smaller, the tracking detail increases but the scrolling range that can be tracked becomes smaller.
511         /// If large sized ContentContainer is required, please use larger threshold value.
512         /// Default ScrollingEventThreshold value is 0.001f.
513         /// </summary>
514         [EditorBrowsable(EditorBrowsableState.Never)]
515         public float ScrollingEventThreshold
516         {
517             get
518             {
519                 return mScrollingEventThreshold;
520             }
521             set
522             {
523                 if (mScrollingEventThreshold != value && value > 0)
524                 {
525                     ContentContainer.RemovePropertyNotification(propertyNotification);
526                     propertyNotification = ContentContainer.AddPropertyNotification("position", PropertyCondition.Step(value));
527                     propertyNotification.Notified += OnPropertyChanged;
528                     mScrollingEventThreshold = value;
529                 }
530             }
531         }
532
533         /// <summary>
534         /// Page will be changed when velocity of panning is over threshold.
535         /// The unit of threshold is pixel per milisec.
536         /// </summary>
537         /// <since_tizen> 8 </since_tizen>
538         public float PageFlickThreshold
539         {
540             get
541             {
542                 return mPageFlickThreshold;
543             }
544             set
545             {
546                 mPageFlickThreshold = value >= 0f ? value : mPageFlickThreshold;
547             }
548         }
549
550         /// <summary>
551         /// Padding for the ScrollableBase
552         /// </summary>
553         [EditorBrowsable(EditorBrowsableState.Never)]
554         public new Extents Padding
555         {
556             get
557             {
558                 return ContentContainer.Padding;
559             }
560             set
561             {
562                 ContentContainer.Padding = value;
563             }
564         }
565
566         /// <summary>
567         /// Alphafunction for scroll animation.
568         /// </summary>
569         [EditorBrowsable(EditorBrowsableState.Never)]
570         public AlphaFunction ScrollAlphaFunction { get; set; } = new AlphaFunction(AlphaFunction.BuiltinFunctions.Linear);
571
572         private bool hideScrollbar = true;
573         private float maxScrollDistance;
574         private float childTargetPosition = 0.0f;
575         private PanGestureDetector mPanGestureDetector;
576         private View mInterruptTouchingChild;
577         private ScrollbarBase scrollBar;
578         private bool scrolling = false;
579         private float ratioOfScreenWidthToCompleteScroll = 0.5f;
580         private float totalDisplacementForPan = 0.0f;
581         private Size previousContainerSize = new Size();
582         private Size previousSize = new Size();
583         private PropertyNotification propertyNotification;
584         private float noticeAnimationEndBeforePosition = 0.0f;
585         private bool readyToNotice = false;
586
587         /// <summary>
588         /// Notice before animation is finished.
589         /// </summary>
590         [EditorBrowsable(EditorBrowsableState.Never)]
591         // Let's consider more whether this needs to be set as protected.
592         public float NoticeAnimationEndBeforePosition
593         {
594             get => noticeAnimationEndBeforePosition;
595             set => noticeAnimationEndBeforePosition = value;
596         }
597
598         // Let's consider more whether this needs to be set as protected.
599         private float finalTargetPosition;
600
601         private Animation scrollAnimation;
602         // Declare user alpha function delegate
603         [UnmanagedFunctionPointer(CallingConvention.StdCall)]
604         private delegate float UserAlphaFunctionDelegate(float progress);
605         private UserAlphaFunctionDelegate customScrollAlphaFunction;
606         private float velocityOfLastPan = 0.0f;
607         private float panAnimationDuration = 0.0f;
608         private float panAnimationDelta = 0.0f;
609         private float logValueOfDeceleration = 0.0f;
610         private float decelerationRate = 0.0f;
611
612         private View topOverShootingShadowView;
613         private View bottomOverShootingShadowView;
614         private View leftOverShootingShadowView;
615         private View rightOverShootingShadowView;
616         private const int overShootingShadowScaleHeightLimit = 64 * 3;
617         private const int overShootingShadowAnimationDuration = 300;
618         private Animation overShootingShadowAnimation;
619         private bool isOverShootingShadowShown = false;
620         private float startShowShadowDisplacement;
621
622         /// <summary>
623         /// Default Constructor
624         /// </summary>
625         /// <since_tizen> 8 </since_tizen>
626         public ScrollableBase() : base()
627         {
628             DecelerationRate = 0.998f;
629
630             base.Layout = new ScrollableBaseCustomLayout();
631             mPanGestureDetector = new PanGestureDetector();
632             mPanGestureDetector.Attach(this);
633             mPanGestureDetector.AddDirection(PanGestureDetector.DirectionVertical);
634             if (mPanGestureDetector.GetMaximumTouchesRequired() < 2) mPanGestureDetector.SetMaximumTouchesRequired(2);
635             mPanGestureDetector.Detected += OnPanGestureDetected;
636
637             ClippingMode = ClippingModeType.ClipToBoundingBox;
638
639             //Default Scrolling child
640             ContentContainer = new View()
641             {
642                 Name = "ContentContainer",
643                 WidthSpecification = ScrollingDirection == Direction.Vertical ? LayoutParamPolicies.MatchParent : LayoutParamPolicies.WrapContent,
644                 HeightSpecification = ScrollingDirection == Direction.Vertical ? LayoutParamPolicies.WrapContent : LayoutParamPolicies.MatchParent,
645             };
646             ContentContainer.Relayout += OnScrollingChildRelayout;
647             propertyNotification = ContentContainer.AddPropertyNotification("position", PropertyCondition.Step(mScrollingEventThreshold));
648             propertyNotification.Notified += OnPropertyChanged;
649             base.Add(ContentContainer);
650
651             //Interrupt touching when panning is started
652             mInterruptTouchingChild = new View()
653             {
654                 Size = new Size(Window.Instance.WindowSize),
655                 BackgroundColor = Color.Transparent,
656             };
657             mInterruptTouchingChild.TouchEvent += OnIterruptTouchingChildTouched;
658             Scrollbar = new Scrollbar();
659
660             //Show vertical shadow on the top (or bottom) of the scrollable when panning down (or up).
661             topOverShootingShadowView = new View
662             {
663                 BackgroundImage = FrameworkInformation.ResourcePath + "nui_component_default_scroll_over_shooting_top.png",
664                 Opacity = 1.0f,
665                 SizeHeight = 0.0f,
666                 PositionUsesPivotPoint = true,
667                 ParentOrigin = NUI.ParentOrigin.TopCenter,
668                 PivotPoint = NUI.PivotPoint.TopCenter,
669             };
670             bottomOverShootingShadowView = new View
671             {
672                 BackgroundImage = FrameworkInformation.ResourcePath + "nui_component_default_scroll_over_shooting_bottom.png",
673                 Opacity = 1.0f,
674                 SizeHeight = 0.0f,
675                 PositionUsesPivotPoint = true,
676                 ParentOrigin = NUI.ParentOrigin.BottomCenter,
677                 PivotPoint = NUI.PivotPoint.BottomCenter,
678             };
679             //Show horizontal shadow on the left (or right) of the scrollable when panning down (or up).
680             leftOverShootingShadowView = new View
681             {
682                 BackgroundImage = FrameworkInformation.ResourcePath + "nui_component_default_scroll_over_shooting_left.png",
683                 Opacity = 1.0f,
684                 SizeWidth = 0.0f,
685                 PositionUsesPivotPoint = true,
686                 ParentOrigin = NUI.ParentOrigin.CenterLeft,
687                 PivotPoint = NUI.PivotPoint.CenterLeft,
688             };
689             rightOverShootingShadowView = new View
690             {
691                 BackgroundImage = FrameworkInformation.ResourcePath + "nui_component_default_scroll_over_shooting_right.png",
692                 Opacity = 1.0f,
693                 SizeWidth = 0.0f,
694                 PositionUsesPivotPoint = true,
695                 ParentOrigin = NUI.ParentOrigin.CenterRight,
696                 PivotPoint = NUI.PivotPoint.CenterRight,
697             };
698
699             AccessibilityManager.Instance.SetAccessibilityAttribute(this, AccessibilityManager.AccessibilityAttribute.Trait, "ScrollableBase");
700         }
701
702         private bool OnIterruptTouchingChildTouched(object source, View.TouchEventArgs args)
703         {
704             if (args.Touch.GetState(0) == PointStateType.Down)
705             {
706                 if (scrolling && !SnapToPage)
707                 {
708                     StopScroll();
709                 }
710             }
711             return true;
712         }
713
714         private void OnPropertyChanged(object source, PropertyNotification.NotifyEventArgs args)
715         {
716             OnScroll();
717         }
718
719         /// <summary>
720         /// Called after a child has been added to the owning view.
721         /// </summary>
722         /// <param name="view">The child which has been added.</param>
723         /// <since_tizen> 8 </since_tizen>
724         public override void Add(View view)
725         {
726             ContentContainer.Add(view);
727         }
728
729         /// <summary>
730         /// Called after a child has been removed from the owning view.
731         /// </summary>
732         /// <param name="view">The child which has been removed.</param>
733         /// <since_tizen> 8 </since_tizen>
734         public override void Remove(View view)
735         {
736             if (SnapToPage && CurrentPage == Children.IndexOf(view) && CurrentPage == Children.Count - 1)
737             {
738                 // Target View is current page and also last child.
739                 // CurrentPage should be changed to previous page.
740                 CurrentPage = Math.Max(0, CurrentPage - 1);
741                 ScrollToIndex(CurrentPage);
742             }
743
744             ContentContainer.Remove(view);
745         }
746
747         private void OnScrollingChildRelayout(object source, EventArgs args)
748         {
749             // Size is changed. Calculate maxScrollDistance.
750             bool isSizeChanged = previousContainerSize.Width != ContentContainer.Size.Width || previousContainerSize.Height != ContentContainer.Size.Height ||
751                 previousSize.Width != Size.Width || previousSize.Height != Size.Height;
752
753             if (isSizeChanged)
754             {
755                 maxScrollDistance = CalculateMaximumScrollDistance();
756                 SetScrollbar();
757             }
758
759             previousContainerSize = ContentContainer.Size;
760             previousSize = Size;
761         }
762
763         /// <summary>
764         /// The composition of a Scrollbar can vary depending on how you use ScrollableBase.
765         /// Set the composition that will go into the ScrollableBase according to your ScrollableBase.
766         /// </summary>
767         /// <since_tizen> 8 </since_tizen>
768         [EditorBrowsable(EditorBrowsableState.Never)]
769         protected virtual void SetScrollbar()
770         {
771             if (Scrollbar)
772             {
773                 bool isHorizontal = ScrollingDirection == Direction.Horizontal;
774                 float contentLength = isHorizontal ? ContentContainer.Size.Width : ContentContainer.Size.Height;
775                 float viewportLength = isHorizontal ? Size.Width : Size.Height;
776                 float currentPosition = isHorizontal ? ContentContainer.CurrentPosition.X : ContentContainer.CurrentPosition.Y;
777                 Scrollbar.Initialize(contentLength, viewportLength, currentPosition, isHorizontal);
778             }
779         }
780
781         /// <summary>
782         /// Scrolls to the item at the specified index.
783         /// </summary>
784         /// <param name="index">Index of item.</param>
785         /// <since_tizen> 8 </since_tizen>
786         public void ScrollToIndex(int index)
787         {
788             if (ContentContainer.ChildCount - 1 < index || index < 0)
789             {
790                 return;
791             }
792
793             if (SnapToPage)
794             {
795                 CurrentPage = index;
796             }
797
798             float targetPosition = Math.Min(ScrollingDirection == Direction.Vertical ? Children[index].Position.Y : Children[index].Position.X, maxScrollDistance);
799             AnimateChildTo(ScrollDuration, -targetPosition);
800         }
801
802         private void OnScrollDragStarted()
803         {
804             ScrollEventArgs eventArgs = new ScrollEventArgs(ContentContainer.CurrentPosition);
805             ScrollDragStarted?.Invoke(this, eventArgs);
806         }
807
808         private void OnScrollDragEnded()
809         {
810             ScrollEventArgs eventArgs = new ScrollEventArgs(ContentContainer.CurrentPosition);
811             ScrollDragEnded?.Invoke(this, eventArgs);
812         }
813
814         private void OnScrollAnimationStarted()
815         {
816             ScrollEventArgs eventArgs = new ScrollEventArgs(ContentContainer.CurrentPosition);
817             ScrollAnimationStarted?.Invoke(this, eventArgs);
818         }
819
820         private void OnScrollAnimationEnded()
821         {
822             scrolling = false;
823             base.Remove(mInterruptTouchingChild);
824
825             ScrollEventArgs eventArgs = new ScrollEventArgs(ContentContainer.CurrentPosition);
826             ScrollAnimationEnded?.Invoke(this, eventArgs);
827         }
828
829         private void OnScroll()
830         {
831             ScrollEventArgs eventArgs = new ScrollEventArgs(ContentContainer.CurrentPosition);
832             Scrolling?.Invoke(this, eventArgs);
833
834             bool isHorizontal = ScrollingDirection == Direction.Horizontal;
835             float contentLength = isHorizontal ? ContentContainer.Size.Width : ContentContainer.Size.Height;
836             float currentPosition = isHorizontal ? ContentContainer.CurrentPosition.X : ContentContainer.CurrentPosition.Y;
837
838             scrollBar?.Update(contentLength, Math.Abs(currentPosition));
839             CheckPreReachedTargetPosition();
840         }
841
842         private void CheckPreReachedTargetPosition()
843         {
844             // Check whether we reached pre-reached target position
845             if (readyToNotice &&
846                 ContentContainer.CurrentPosition.Y <= finalTargetPosition + NoticeAnimationEndBeforePosition &&
847                 ContentContainer.CurrentPosition.Y >= finalTargetPosition - NoticeAnimationEndBeforePosition)
848             {
849                 //Notice first
850                 readyToNotice = false;
851                 OnPreReachedTargetPosition(finalTargetPosition);
852             }
853         }
854
855         /// <summary>
856         /// This helps developer who wants to know before scroll is reaching target position.
857         /// </summary>
858         /// <param name="targetPosition">Index of item.</param>
859         /// This may be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API
860         [EditorBrowsable(EditorBrowsableState.Never)]
861         protected virtual void OnPreReachedTargetPosition(float targetPosition)
862         {
863
864         }
865
866         private void StopScroll()
867         {
868             if (scrollAnimation != null)
869             {
870                 if (scrollAnimation.State == Animation.States.Playing)
871                 {
872                     Debug.WriteLineIf(LayoutDebugScrollableBase, "StopScroll Animation Playing");
873                     scrollAnimation.Stop(Animation.EndActions.Cancel);
874                     OnScrollAnimationEnded();
875                 }
876                 scrollAnimation.Clear();
877             }
878         }
879
880         private void AnimateChildTo(int duration, float axisPosition)
881         {
882             Debug.WriteLineIf(LayoutDebugScrollableBase, "AnimationTo Animation Duration:" + duration + " Destination:" + axisPosition);
883             finalTargetPosition = axisPosition;
884
885             StopScroll(); // Will replace previous animation so will stop existing one.
886
887             if (scrollAnimation == null)
888             {
889                 scrollAnimation = new Animation();
890                 scrollAnimation.Finished += ScrollAnimationFinished;
891             }
892
893             scrollAnimation.Duration = duration;
894             scrollAnimation.DefaultAlphaFunction = new AlphaFunction(AlphaFunction.BuiltinFunctions.EaseOutSquare);
895             scrollAnimation.AnimateTo(ContentContainer, (ScrollingDirection == Direction.Horizontal) ? "PositionX" : "PositionY", axisPosition, ScrollAlphaFunction);
896             scrolling = true;
897             OnScrollAnimationStarted();
898             scrollAnimation.Play();
899         }
900
901         /// <summary>
902         /// Scroll to specific position with or without animation.
903         /// </summary>
904         /// <param name="position">Destination.</param>
905         /// <param name="animate">Scroll with or without animation</param>
906         /// <since_tizen> 8 </since_tizen>
907         public void ScrollTo(float position, bool animate)
908         {
909             float currentPositionX = ContentContainer.CurrentPosition.X != 0 ? ContentContainer.CurrentPosition.X : ContentContainer.Position.X;
910             float currentPositionY = ContentContainer.CurrentPosition.Y != 0 ? ContentContainer.CurrentPosition.Y : ContentContainer.Position.Y;
911             float delta = ScrollingDirection == Direction.Horizontal ? currentPositionX : currentPositionY;
912             // The argument position is the new pan position. So the new position of ScrollableBase becomes (-position).
913             // To move ScrollableBase's position to (-position), it moves by (-position - currentPosition).
914             delta = -position - delta;
915
916             ScrollBy(delta, animate);
917         }
918
919         private float BoundScrollPosition(float targetPosition)
920         {
921             if (ScrollAvailableArea != null)
922             {
923                 float minScrollPosition = ScrollAvailableArea.X;
924                 float maxScrollPosition = ScrollAvailableArea.Y;
925
926                 targetPosition = Math.Min(-minScrollPosition, targetPosition);
927                 targetPosition = Math.Max(-maxScrollPosition, targetPosition);
928             }
929             else
930             {
931                 targetPosition = Math.Min(0, targetPosition);
932                 targetPosition = Math.Max(-maxScrollDistance, targetPosition);
933             }
934
935             return targetPosition;
936         }
937
938         private void ScrollBy(float displacement, bool animate)
939         {
940             if (GetChildCount() == 0 || maxScrollDistance < 0)
941             {
942                 return;
943             }
944
945             float childCurrentPosition = (ScrollingDirection == Direction.Horizontal) ? ContentContainer.PositionX : ContentContainer.PositionY;
946
947             Debug.WriteLineIf(LayoutDebugScrollableBase, "ScrollBy childCurrentPosition:" + childCurrentPosition +
948                 " displacement:" + displacement,
949                 " maxScrollDistance:" + maxScrollDistance);
950
951             childTargetPosition = childCurrentPosition + displacement; // child current position + gesture displacement
952
953             Debug.WriteLineIf(LayoutDebugScrollableBase, "ScrollBy currentAxisPosition:" + childCurrentPosition + "childTargetPosition:" + childTargetPosition);
954
955             if (animate)
956             {
957                 // Calculate scroll animaton duration
958                 float scrollDistance = Math.Abs(displacement);
959                 readyToNotice = true;
960
961                 AnimateChildTo(ScrollDuration, BoundScrollPosition(AdjustTargetPositionOfScrollAnimation(BoundScrollPosition(childTargetPosition))));
962             }
963             else
964             {
965                 finalTargetPosition = BoundScrollPosition(childTargetPosition);
966
967                 // Set position of scrolling child without an animation
968                 if (ScrollingDirection == Direction.Horizontal)
969                 {
970                     ContentContainer.PositionX = finalTargetPosition;
971                 }
972                 else
973                 {
974                     ContentContainer.PositionY = finalTargetPosition;
975                 }
976             }
977         }
978
979         /// <summary>
980         /// you can override it to clean-up your own resources.
981         /// </summary>
982         /// <param name="type">DisposeTypes</param>
983         /// This will be public opened in tizen_5.5 after ACR done. Before ACR, need to be hidden as inhouse API.
984         [EditorBrowsable(EditorBrowsableState.Never)]
985         protected override void Dispose(DisposeTypes type)
986         {
987             if (disposed)
988             {
989                 return;
990             }
991
992             if (type == DisposeTypes.Explicit)
993             {
994                 StopOverShootingShadowAnimation();
995                 StopScroll();
996
997                 if (mPanGestureDetector != null)
998                 {
999                     mPanGestureDetector.Detected -= OnPanGestureDetected;
1000                     mPanGestureDetector.Dispose();
1001                     mPanGestureDetector = null;
1002                 }
1003
1004                 propertyNotification.Dispose();
1005             }
1006             base.Dispose(type);
1007         }
1008
1009         private float CalculateMaximumScrollDistance()
1010         {
1011             float scrollingChildLength = 0;
1012             float scrollerLength = 0;
1013             if (ScrollingDirection == Direction.Horizontal)
1014             {
1015                 Debug.WriteLineIf(LayoutDebugScrollableBase, "Horizontal");
1016
1017                 scrollingChildLength = ContentContainer.Size.Width;
1018                 scrollerLength = Size.Width;
1019             }
1020             else
1021             {
1022                 Debug.WriteLineIf(LayoutDebugScrollableBase, "Vertical");
1023                 scrollingChildLength = ContentContainer.Size.Height;
1024                 scrollerLength = Size.Height;
1025             }
1026
1027             Debug.WriteLineIf(LayoutDebugScrollableBase, "ScrollBy maxScrollDistance:" + (scrollingChildLength - scrollerLength) +
1028                 " parent length:" + scrollerLength +
1029                 " scrolling child length:" + scrollingChildLength);
1030
1031             return Math.Max(scrollingChildLength - scrollerLength, 0);
1032         }
1033
1034         private void PageSnap(float velocity)
1035         {
1036             float destination;
1037
1038             Debug.WriteLineIf(LayoutDebugScrollableBase, "PageSnap with pan candidate totalDisplacement:" + totalDisplacementForPan +
1039                 " currentPage[" + CurrentPage + "]");
1040
1041             //Increment current page if total displacement enough to warrant a page change.
1042             if (Math.Abs(totalDisplacementForPan) > (mPageWidth * ratioOfScreenWidthToCompleteScroll))
1043             {
1044                 if (totalDisplacementForPan < 0)
1045                 {
1046                     CurrentPage = Math.Min(Math.Max(Children.Count - 1, 0), ++CurrentPage);
1047                 }
1048                 else
1049                 {
1050                     CurrentPage = Math.Max(0, --CurrentPage);
1051                 }
1052             }
1053             else if (Math.Abs(velocity) > PageFlickThreshold)
1054             {
1055                 if (velocity < 0)
1056                 {
1057                     CurrentPage = Math.Min(Math.Max(Children.Count - 1, 0), ++CurrentPage);
1058                 }
1059                 else
1060                 {
1061                     CurrentPage = Math.Max(0, --CurrentPage);
1062                 }
1063             }
1064
1065             // Animate to new page or reposition to current page
1066             if (ScrollingDirection == Direction.Horizontal)
1067                 destination = -(Children[CurrentPage].Position.X + Children[CurrentPage].CurrentSize.Width / 2 - CurrentSize.Width / 2); // set to middle of current page
1068             else
1069                 destination = -(Children[CurrentPage].Position.Y + Children[CurrentPage].CurrentSize.Height / 2 - CurrentSize.Height / 2);
1070
1071             AnimateChildTo(ScrollDuration, destination);
1072         }
1073
1074         /// <summary>
1075         /// Enable/Disable overshooting effect. default is disabled.
1076         /// </summary>
1077         [EditorBrowsable(EditorBrowsableState.Never)]
1078         public bool EnableOverShootingEffect { get; set; } = false;
1079
1080         private void AttachOverShootingShadowView()
1081         {
1082             if (!EnableOverShootingEffect)
1083                 return;
1084
1085             // stop animation if necessary.
1086             StopOverShootingShadowAnimation();
1087
1088             if (ScrollingDirection == Direction.Horizontal)
1089             {
1090                 base.Add(leftOverShootingShadowView);
1091                 base.Add(rightOverShootingShadowView);
1092
1093                 leftOverShootingShadowView.Size = new Size(0.0f, SizeHeight);
1094                 leftOverShootingShadowView.Opacity = 1.0f;
1095                 leftOverShootingShadowView.RaiseToTop();
1096
1097                 rightOverShootingShadowView.Size = new Size(0.0f, SizeHeight);
1098                 rightOverShootingShadowView.Opacity = 1.0f;
1099                 rightOverShootingShadowView.RaiseToTop();
1100             }
1101             else
1102             {
1103                 base.Add(topOverShootingShadowView);
1104                 base.Add(bottomOverShootingShadowView);
1105
1106                 topOverShootingShadowView.Size = new Size(SizeWidth, 0.0f);
1107                 topOverShootingShadowView.Opacity = 1.0f;
1108                 topOverShootingShadowView.RaiseToTop();
1109
1110                 bottomOverShootingShadowView.Size = new Size(SizeWidth, 0.0f);
1111                 bottomOverShootingShadowView.Opacity = 1.0f;
1112                 bottomOverShootingShadowView.RaiseToTop();
1113             }
1114
1115             // at the beginning, height or width of overshooting shadow is 0, so it is invisible.
1116             isOverShootingShadowShown = false;
1117         }
1118
1119         private void DragOverShootingShadow(float totalPanDisplacement, float panDisplacement)
1120         {
1121             if (!EnableOverShootingEffect)
1122                 return;
1123
1124             if (totalPanDisplacement > 0) // downwards
1125             {
1126                 // check if reaching at the top / left.
1127                 if ((int)finalTargetPosition != 0)
1128                 {
1129                     isOverShootingShadowShown = false;
1130                     return;
1131                 }
1132
1133                 // save start displacement, and re-calculate displacement.
1134                 if (!isOverShootingShadowShown)
1135                 {
1136                     startShowShadowDisplacement = totalPanDisplacement;
1137                 }
1138                 isOverShootingShadowShown = true;
1139
1140                 float newDisplacement = (int)totalPanDisplacement < (int)startShowShadowDisplacement ? 0 : totalPanDisplacement - startShowShadowDisplacement;
1141
1142                 if (ScrollingDirection == Direction.Horizontal)
1143                 {
1144                     // scale limit of height is 60%.
1145                     float heightScale = newDisplacement / overShootingShadowScaleHeightLimit;
1146                     leftOverShootingShadowView.SizeHeight = heightScale > 0.6f ? SizeHeight * 0.4f : SizeHeight * (1.0f - heightScale);
1147
1148                     // scale limit of width is 300%.
1149                     leftOverShootingShadowView.SizeWidth = newDisplacement > overShootingShadowScaleHeightLimit ? overShootingShadowScaleHeightLimit : newDisplacement;
1150
1151                     // trigger event
1152                     ScrollOutOfBoundEventArgs.Direction scrollDirection = panDisplacement > 0 ?
1153                        ScrollOutOfBoundEventArgs.Direction.Right : ScrollOutOfBoundEventArgs.Direction.Left;
1154                     OnScrollOutOfBound(scrollDirection, totalPanDisplacement);
1155                 }
1156                 else
1157                 {
1158                     // scale limit of width is 60%.
1159                     float widthScale = newDisplacement / overShootingShadowScaleHeightLimit;
1160                     topOverShootingShadowView.SizeWidth = widthScale > 0.6f ? SizeWidth * 0.4f : SizeWidth * (1.0f - widthScale);
1161
1162                     // scale limit of height is 300%.
1163                     topOverShootingShadowView.SizeHeight = newDisplacement > overShootingShadowScaleHeightLimit ? overShootingShadowScaleHeightLimit : newDisplacement;
1164
1165                     // trigger event
1166                     ScrollOutOfBoundEventArgs.Direction scrollDirection = panDisplacement > 0 ?
1167                        ScrollOutOfBoundEventArgs.Direction.Down : ScrollOutOfBoundEventArgs.Direction.Up;
1168                     OnScrollOutOfBound(scrollDirection, totalPanDisplacement);
1169                 }
1170             }
1171             else if (totalPanDisplacement < 0) // upwards
1172             {
1173                 // check if reaching at the bottom.
1174                 if (-(int)finalTargetPosition != (int)maxScrollDistance)
1175                 {
1176                     isOverShootingShadowShown = false;
1177                     return;
1178                 }
1179
1180                 // save start displacement, and re-calculate displacement.
1181                 if (!isOverShootingShadowShown)
1182                 {
1183                     startShowShadowDisplacement = totalPanDisplacement;
1184                 }
1185                 isOverShootingShadowShown = true;
1186
1187                 float newDisplacement = (int)startShowShadowDisplacement < (int)totalPanDisplacement ? 0 : startShowShadowDisplacement - totalPanDisplacement;
1188
1189                 if (ScrollingDirection == Direction.Horizontal)
1190                 {
1191                     // scale limit of height is 60%.
1192                     float heightScale = newDisplacement / overShootingShadowScaleHeightLimit;
1193                     rightOverShootingShadowView.SizeHeight = heightScale > 0.6f ? SizeHeight * 0.4f : SizeHeight * (1.0f - heightScale);
1194
1195                     // scale limit of width is 300%.
1196                     rightOverShootingShadowView.SizeWidth = newDisplacement > overShootingShadowScaleHeightLimit ? overShootingShadowScaleHeightLimit : newDisplacement;
1197
1198                     // trigger event
1199                     ScrollOutOfBoundEventArgs.Direction scrollDirection = panDisplacement > 0 ?
1200                        ScrollOutOfBoundEventArgs.Direction.Right : ScrollOutOfBoundEventArgs.Direction.Left;
1201                     OnScrollOutOfBound(scrollDirection, totalPanDisplacement);
1202                 }
1203                 else
1204                 {
1205                     // scale limit of width is 60%.
1206                     float widthScale = newDisplacement / overShootingShadowScaleHeightLimit;
1207                     bottomOverShootingShadowView.SizeWidth = widthScale > 0.6f ? SizeWidth * 0.4f : SizeWidth * (1.0f - widthScale);
1208
1209                     // scale limit of height is 300%.
1210                     bottomOverShootingShadowView.SizeHeight = newDisplacement > overShootingShadowScaleHeightLimit ? overShootingShadowScaleHeightLimit : newDisplacement;
1211
1212                     // trigger event
1213                     ScrollOutOfBoundEventArgs.Direction scrollDirection = panDisplacement > 0 ?
1214                        ScrollOutOfBoundEventArgs.Direction.Down : ScrollOutOfBoundEventArgs.Direction.Up;
1215                     OnScrollOutOfBound(scrollDirection, totalPanDisplacement);
1216                 }
1217             }
1218             else
1219             {
1220                 // if total displacement is 0, shadow would become invisible.
1221                 isOverShootingShadowShown = false;
1222             }
1223         }
1224
1225         private void PlayOverShootingShadowAnimation()
1226         {
1227             if (!EnableOverShootingEffect)
1228                 return;
1229
1230             // stop animation if necessary.
1231             StopOverShootingShadowAnimation();
1232
1233             if (overShootingShadowAnimation == null)
1234             {
1235                 overShootingShadowAnimation = new Animation(overShootingShadowAnimationDuration);
1236                 overShootingShadowAnimation.Finished += OnOverShootingShadowAnimationFinished;
1237             }
1238
1239             if (ScrollingDirection == Direction.Horizontal)
1240             {
1241                 View targetView = totalDisplacementForPan < 0 ? rightOverShootingShadowView : leftOverShootingShadowView;
1242                 overShootingShadowAnimation.AnimateTo(targetView, "SizeHeight", SizeHeight);
1243                 overShootingShadowAnimation.AnimateTo(targetView, "SizeWidth", 0.0f);
1244                 overShootingShadowAnimation.AnimateTo(targetView, "Opacity", 0.0f);
1245             }
1246             else
1247             {
1248                 View targetView = totalDisplacementForPan < 0 ? bottomOverShootingShadowView : topOverShootingShadowView;
1249                 overShootingShadowAnimation.AnimateTo(targetView, "SizeWidth", SizeWidth);
1250                 overShootingShadowAnimation.AnimateTo(targetView, "SizeHeight", 0.0f);
1251                 overShootingShadowAnimation.AnimateTo(targetView, "Opacity", 0.0f);
1252             }
1253             overShootingShadowAnimation.Play();
1254         }
1255
1256         private void StopOverShootingShadowAnimation()
1257         {
1258             if (overShootingShadowAnimation == null || overShootingShadowAnimation.State != Animation.States.Playing)
1259                 return;
1260
1261             overShootingShadowAnimation.Stop(Animation.EndActions.Cancel);
1262             OnOverShootingShadowAnimationFinished(null, null);
1263             overShootingShadowAnimation.Clear();
1264         }
1265
1266         private void OnOverShootingShadowAnimationFinished(object sender, EventArgs e)
1267         {
1268             if (ScrollingDirection == Direction.Horizontal)
1269             {
1270                 base.Remove(leftOverShootingShadowView);
1271                 base.Remove(rightOverShootingShadowView);
1272
1273                 leftOverShootingShadowView.Size = new Size(0.0f, SizeHeight);
1274                 rightOverShootingShadowView.Size = new Size(0.0f, SizeHeight);
1275             }
1276             else
1277             {
1278                 base.Remove(topOverShootingShadowView);
1279                 base.Remove(bottomOverShootingShadowView);
1280
1281                 topOverShootingShadowView.Size = new Size(SizeWidth, 0.0f);
1282                 bottomOverShootingShadowView.Size = new Size(SizeWidth, 0.0f);
1283             }
1284
1285             // after animation finished, height/width & opacity of vertical shadow both are 0, so it is invisible.
1286             isOverShootingShadowShown = false;
1287         }
1288
1289         private void OnScrollOutOfBound(ScrollOutOfBoundEventArgs.Direction direction, float displacement)
1290         {
1291             ScrollOutOfBoundEventArgs args = new ScrollOutOfBoundEventArgs(direction, displacement);
1292             ScrollOutOfBound?.Invoke(this, args);
1293         }
1294
1295         private void OnPanGestureDetected(object source, PanGestureDetector.DetectedEventArgs e)
1296         {
1297             OnPanGesture(e.PanGesture);
1298         }
1299
1300         private void OnPanGesture(PanGesture panGesture)
1301         {
1302             if (SnapToPage && scrollAnimation != null && scrollAnimation.State == Animation.States.Playing)
1303             {
1304                 return;
1305             }
1306
1307             if (panGesture.State == Gesture.StateType.Started)
1308             {
1309                 readyToNotice = false;
1310                 base.Add(mInterruptTouchingChild);
1311                 AttachOverShootingShadowView();
1312                 Debug.WriteLineIf(LayoutDebugScrollableBase, "Gesture Start");
1313                 if (scrolling && !SnapToPage)
1314                 {
1315                     StopScroll();
1316                 }
1317                 totalDisplacementForPan = 0.0f;
1318                 OnScrollDragStarted();
1319             }
1320             else if (panGesture.State == Gesture.StateType.Continuing)
1321             {
1322                 if (ScrollingDirection == Direction.Horizontal)
1323                 {
1324                     // if vertical shadow is shown, does not scroll.
1325                     if (!isOverShootingShadowShown)
1326                     {
1327                         ScrollBy(panGesture.Displacement.X, false);
1328                     }
1329                     totalDisplacementForPan += panGesture.Displacement.X;
1330                     DragOverShootingShadow(totalDisplacementForPan, panGesture.Displacement.X);
1331                 }
1332                 else
1333                 {
1334                     // if vertical shadow is shown, does not scroll.
1335                     if (!isOverShootingShadowShown)
1336                     {
1337                         ScrollBy(panGesture.Displacement.Y, false);
1338                     }
1339                     totalDisplacementForPan += panGesture.Displacement.Y;
1340                     DragOverShootingShadow(totalDisplacementForPan, panGesture.Displacement.Y);
1341                 }
1342                 Debug.WriteLineIf(LayoutDebugScrollableBase, "OnPanGestureDetected Continue totalDisplacementForPan:" + totalDisplacementForPan);
1343             }
1344             else if (panGesture.State == Gesture.StateType.Finished || panGesture.State == Gesture.StateType.Cancelled)
1345             {
1346                 PlayOverShootingShadowAnimation();
1347                 OnScrollDragEnded();
1348                 StopScroll(); // Will replace previous animation so will stop existing one.
1349
1350                 if (scrollAnimation == null)
1351                 {
1352                     scrollAnimation = new Animation();
1353                     scrollAnimation.Finished += ScrollAnimationFinished;
1354                 }
1355
1356                 float panVelocity = (ScrollingDirection == Direction.Horizontal) ? panGesture.Velocity.X : panGesture.Velocity.Y;
1357
1358                 if (SnapToPage)
1359                 {
1360                     PageSnap(panVelocity);
1361                 }
1362                 else
1363                 {
1364                     if (panVelocity == 0)
1365                     {
1366                         float currentScrollPosition = (ScrollingDirection == Direction.Horizontal ? ContentContainer.CurrentPosition.X : ContentContainer.CurrentPosition.Y);
1367                         scrollAnimation.DefaultAlphaFunction = new AlphaFunction(AlphaFunction.BuiltinFunctions.Linear);
1368                         scrollAnimation.Duration = 0;
1369                         scrollAnimation.AnimateTo(ContentContainer, (ScrollingDirection == Direction.Horizontal) ? "PositionX" : "PositionY", currentScrollPosition);
1370                         scrollAnimation.Play();
1371                     }
1372                     else
1373                     {
1374                         Decelerating(panVelocity, scrollAnimation);
1375                     }
1376                 }
1377
1378                 totalDisplacementForPan = 0;
1379                 scrolling = true;
1380                 readyToNotice = true;
1381                 OnScrollAnimationStarted();
1382             }
1383         }
1384
1385         internal void BaseRemove(View view)
1386         {
1387             base.Remove(view);
1388         }
1389
1390         internal override bool OnAccessibilityPan(PanGesture gestures)
1391         {
1392             if (SnapToPage && scrollAnimation != null && scrollAnimation.State == Animation.States.Playing)
1393             {
1394                 return false;
1395             }
1396
1397             OnPanGesture(gestures);
1398             return true;
1399         }
1400
1401         private float CustomScrollAlphaFunction(float progress)
1402         {
1403             if (panAnimationDelta == 0)
1404             {
1405                 return 1.0f;
1406             }
1407             else
1408             {
1409                 // Parameter "progress" is normalized value. We need to multiply target duration to calculate distance.
1410                 // Can get real distance using equation of deceleration (check Decelerating function)
1411                 // After get real distance, normalize it
1412                 float realDuration = progress * panAnimationDuration;
1413                 float realDistance = velocityOfLastPan * ((float)Math.Pow(decelerationRate, realDuration) - 1) / logValueOfDeceleration;
1414                 float result = Math.Min(realDistance / Math.Abs(panAnimationDelta), 1.0f);
1415                 return result;
1416             }
1417         }
1418
1419         /// <summary>
1420         /// you can override it to custom your decelerating
1421         /// </summary>
1422         /// <param name="velocity">Velocity of current pan.</param>
1423         /// <param name="animation">Scroll animation.</param>
1424         [EditorBrowsable(EditorBrowsableState.Never)]
1425         protected virtual void Decelerating(float velocity, Animation animation)
1426         {
1427             // Decelerating using deceleration equation ===========
1428             //
1429             // V   : velocity (pixel per milisecond)
1430             // V0  : initial velocity
1431             // d   : deceleration rate,
1432             // t   : time
1433             // X   : final position after decelerating
1434             // log : natural logarithm
1435             //
1436             // V(t) = V0 * d pow t;
1437             // X(t) = V0 * (d pow t - 1) / log d;  <-- Integrate the velocity function
1438             // X(∞) = V0 * d / (1 - d); <-- Result using inifit T can be final position because T is tending to infinity.
1439             //
1440             // Because of final T is tending to inifity, we should use threshold value to finish.
1441             // Final T = log(-threshold * log d / |V0| ) / log d;
1442
1443             velocityOfLastPan = Math.Abs(velocity);
1444
1445             float currentScrollPosition = -(ScrollingDirection == Direction.Horizontal ? ContentContainer.CurrentPosition.X : ContentContainer.CurrentPosition.Y);
1446             panAnimationDelta = (velocityOfLastPan * decelerationRate) / (1 - decelerationRate);
1447             panAnimationDelta = velocity > 0 ? -panAnimationDelta : panAnimationDelta;
1448
1449             float destination = -(panAnimationDelta + currentScrollPosition);
1450             float adjustDestination = AdjustTargetPositionOfScrollAnimation(destination);
1451             float maxPosition = ScrollAvailableArea != null ? ScrollAvailableArea.Y : maxScrollDistance;
1452             float minPosition = ScrollAvailableArea != null ? ScrollAvailableArea.X : 0;
1453
1454             if (destination < -maxPosition || destination > minPosition)
1455             {
1456                 panAnimationDelta = velocity > 0 ? (currentScrollPosition - minPosition) : (maxPosition - currentScrollPosition);
1457                 destination = velocity > 0 ? minPosition : -maxPosition;
1458
1459                 if (panAnimationDelta == 0)
1460                 {
1461                     panAnimationDuration = 0.0f;
1462                 }
1463                 else
1464                 {
1465                     panAnimationDuration = (float)Math.Log((panAnimationDelta * logValueOfDeceleration / velocityOfLastPan + 1), decelerationRate);
1466                 }
1467
1468                 Debug.WriteLineIf(LayoutDebugScrollableBase, "\n" +
1469                     "OverRange======================= \n" +
1470                     "[decelerationRate] " + decelerationRate + "\n" +
1471                     "[logValueOfDeceleration] " + logValueOfDeceleration + "\n" +
1472                     "[Velocity] " + velocityOfLastPan + "\n" +
1473                     "[CurrentPosition] " + currentScrollPosition + "\n" +
1474                     "[CandidateDelta] " + panAnimationDelta + "\n" +
1475                     "[Destination] " + destination + "\n" +
1476                     "[Duration] " + panAnimationDuration + "\n" +
1477                     "================================ \n"
1478                 );
1479             }
1480             else
1481             {
1482                 panAnimationDuration = (float)Math.Log(-DecelerationThreshold * logValueOfDeceleration / velocityOfLastPan) / logValueOfDeceleration;
1483
1484                 if (adjustDestination != destination)
1485                 {
1486                     destination = adjustDestination;
1487                     panAnimationDelta = destination + currentScrollPosition;
1488                     velocityOfLastPan = Math.Abs(panAnimationDelta * logValueOfDeceleration / ((float)Math.Pow(decelerationRate, panAnimationDuration) - 1));
1489                     panAnimationDuration = (float)Math.Log(-DecelerationThreshold * logValueOfDeceleration / velocityOfLastPan) / logValueOfDeceleration;
1490                 }
1491
1492                 Debug.WriteLineIf(LayoutDebugScrollableBase, "\n" +
1493                     "================================ \n" +
1494                     "[decelerationRate] " + decelerationRate + "\n" +
1495                     "[logValueOfDeceleration] " + logValueOfDeceleration + "\n" +
1496                     "[Velocity] " + velocityOfLastPan + "\n" +
1497                     "[CurrentPosition] " + currentScrollPosition + "\n" +
1498                     "[CandidateDelta] " + panAnimationDelta + "\n" +
1499                     "[Destination] " + destination + "\n" +
1500                     "[Duration] " + panAnimationDuration + "\n" +
1501                     "================================ \n"
1502                 );
1503             }
1504
1505             finalTargetPosition = destination;
1506
1507             customScrollAlphaFunction = new UserAlphaFunctionDelegate(CustomScrollAlphaFunction);
1508             animation.DefaultAlphaFunction = new AlphaFunction(customScrollAlphaFunction);
1509             GC.KeepAlive(customScrollAlphaFunction);
1510             animation.Duration = (int)panAnimationDuration;
1511             animation.AnimateTo(ContentContainer, (ScrollingDirection == Direction.Horizontal) ? "PositionX" : "PositionY", destination);
1512             animation.Play();
1513         }
1514
1515         private void ScrollAnimationFinished(object sender, EventArgs e)
1516         {
1517             OnScrollAnimationEnded();
1518         }
1519
1520         /// <summary>
1521         /// Adjust scrolling position by own scrolling rules.
1522         /// Override this function when developer wants to change destination of flicking.(e.g. always snap to center of item)
1523         /// </summary>
1524         /// This may be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API
1525         [EditorBrowsable(EditorBrowsableState.Never)]
1526         protected virtual float AdjustTargetPositionOfScrollAnimation(float position)
1527         {
1528             return position;
1529         }
1530
1531         /// <summary>
1532         /// Scroll position given to ScrollTo.
1533         /// This is the position in the opposite direction to the position of ContentContainer.
1534         /// </summary>
1535         /// <since_tizen> 8 </since_tizen>
1536         public Position ScrollPosition
1537         {
1538             get
1539             {
1540                 return new Position(-ContentContainer.Position);
1541             }
1542         }
1543
1544         /// <summary>
1545         /// Current scroll position in the middle of ScrollTo animation.
1546         /// This is the position in the opposite direction to the current position of ContentContainer.
1547         /// </summary>
1548         /// <since_tizen> 8 </since_tizen>
1549         public Position ScrollCurrentPosition
1550         {
1551             get
1552             {
1553                 return new Position(-ContentContainer.CurrentPosition);
1554             }
1555         }
1556
1557         /// <summary>
1558         /// Remove all children in ContentContainer.
1559         /// </summary>
1560         /// <param name="dispose">If true, removed child is disposed.</param>
1561         [EditorBrowsable(EditorBrowsableState.Never)]
1562         public void RemoveAllChildren(bool dispose = false)
1563         {
1564             RecursiveRemoveChildren(ContentContainer, dispose);
1565         }
1566
1567         private void RecursiveRemoveChildren(View parent, bool dispose)
1568         {
1569             if (parent == null)
1570             {
1571                 return;
1572             }
1573             int maxChild = (int)parent.GetChildCount();
1574             for (int i = maxChild - 1; i >= 0; --i)
1575             {
1576                 View child = parent.GetChildAt((uint)i);
1577                 if (child == null)
1578                 {
1579                     continue;
1580                 }
1581                 RecursiveRemoveChildren(child, dispose);
1582                 parent.Remove(child);
1583                 if (dispose)
1584                 {
1585                     child.Dispose();
1586                 }
1587             }
1588         }
1589
1590     }
1591
1592 } // namespace