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