03b15c97c507b54a60ff5aec9ad096ab829c5a43
[platform/core/csapi/tizenfx.git] / src / Tizen.NUI.Components / Controls / LayoutScroller.cs
1 /* Copyright (c) 2019 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.ComponentModel;
19 using System.Diagnostics;
20 namespace Tizen.NUI.Components
21 {
22     /// <summary>
23     /// [Draft] This class provides a View that can scroll a single View with a layout.
24     /// </summary>
25     internal class LayoutScroller : CustomView
26     {
27         static bool LayoutDebugScroller = true; // Debug flag
28
29         private class ScrollerCustomLayout : LayoutGroup
30         {
31             protected override void OnMeasure(MeasureSpecification widthMeasureSpec, MeasureSpecification heightMeasureSpec)
32             {
33                 float totalHeight = 0.0f;
34                 float totalWidth = 0.0f;
35
36                 MeasuredSize.StateType childWidthState = MeasuredSize.StateType.MeasuredSizeOK;
37                 MeasuredSize.StateType childHeightState = MeasuredSize.StateType.MeasuredSizeOK;
38
39                 // measure children
40                 foreach( LayoutItem childLayout in LayoutChildren )
41                 {
42                     if (childLayout != null)
43                     {
44                         // Get size of child
45                         // Use an Unspecified MeasureSpecification mode so scrolling child is not restricted to it's parents size in Height (for vertical scrolling)
46                         MeasureSpecification heightMeasureSpecUnrestricted = new MeasureSpecification( heightMeasureSpec.Size, MeasureSpecification.ModeType.Unspecified);
47                         MeasureChild( childLayout, widthMeasureSpec, heightMeasureSpecUnrestricted );
48                         float childWidth = childLayout.MeasuredWidth.Size.AsDecimal();
49                         float childHeight = childLayout.MeasuredHeight.Size.AsDecimal();
50
51                         // Determine the width and height needed by the children using their given position and size.
52                         // Children could overlap so find the left most and right most child.
53                         Position2D childPosition = childLayout.Owner.Position2D;
54                         float childLeft = childPosition.X;
55                         float childTop = childPosition.Y;
56
57                         // Store current width and height needed to contain all children.
58                         Extents padding = Padding;
59                         Extents childMargin = childLayout.Margin;
60                         totalWidth = childWidth + padding.Start + padding.End + childMargin.Start + childMargin.End;
61                         totalHeight = childHeight + padding.Top + padding.Bottom + childMargin.Top + childMargin.Bottom;
62
63                         if (childLayout.MeasuredWidth.State == MeasuredSize.StateType.MeasuredSizeTooSmall)
64                         {
65                             childWidthState = MeasuredSize.StateType.MeasuredSizeTooSmall;
66                         }
67                         if (childLayout.MeasuredWidth.State == MeasuredSize.StateType.MeasuredSizeTooSmall)
68                         {
69                             childHeightState = MeasuredSize.StateType.MeasuredSizeTooSmall;
70                         }
71                     }
72                 }
73
74
75                 MeasuredSize widthSizeAndState = ResolveSizeAndState(new LayoutLength(totalWidth), widthMeasureSpec, MeasuredSize.StateType.MeasuredSizeOK);
76                 MeasuredSize heightSizeAndState = ResolveSizeAndState(new LayoutLength(totalHeight), heightMeasureSpec, MeasuredSize.StateType.MeasuredSizeOK);
77                 totalWidth = widthSizeAndState.Size.AsDecimal();
78                 totalHeight = heightSizeAndState.Size.AsDecimal();
79
80                 // Ensure layout respects it's given minimum size
81                 totalWidth = Math.Max( totalWidth, SuggestedMinimumWidth.AsDecimal() );
82                 totalHeight = Math.Max( totalHeight, SuggestedMinimumHeight.AsDecimal() );
83
84                 widthSizeAndState.State = childWidthState;
85                 heightSizeAndState.State = childHeightState;
86
87                 SetMeasuredDimensions( ResolveSizeAndState( new LayoutLength(totalWidth), widthMeasureSpec, childWidthState ),
88                                        ResolveSizeAndState( new LayoutLength(totalHeight), heightMeasureSpec, childHeightState ) );
89             }
90
91             protected override void OnLayout(bool changed, LayoutLength left, LayoutLength top, LayoutLength right, LayoutLength bottom)
92             {
93                 foreach( LayoutItem childLayout in LayoutChildren )
94                 {
95                     if( childLayout != null )
96                     {
97                         LayoutLength childWidth = childLayout.MeasuredWidth.Size;
98                         LayoutLength childHeight = childLayout.MeasuredHeight.Size;
99
100                         Position2D childPosition = childLayout.Owner.Position2D;
101                         Extents padding = Padding;
102                         Extents childMargin = childLayout.Margin;
103
104                         LayoutLength childLeft = new LayoutLength(childPosition.X + childMargin.Start + padding.Start);
105                         LayoutLength childTop = new LayoutLength(childPosition.Y + childMargin.Top + padding.Top);
106
107                         childLayout.Layout( childLeft, childTop, childLeft + childWidth, childTop + childHeight );
108                     }
109                 }
110             }
111         }
112
113         private Animation scrollAnimation;
114         private float maxScrollDistance;
115         private float childTargetPosition = 0.0f;
116         private PanGestureDetector mPanGestureDetector;
117         private TapGestureDetector mTapGestureDetector;
118         private View mScrollingChild;
119
120         private bool Scrolling = false;
121
122         /// <summary>
123         /// [Draft] Constructor
124         /// </summary>
125         /// <since_tizen> 6 </since_tizen>
126         public LayoutScroller() : base(typeof(VisualView).FullName, CustomViewBehaviour.ViewBehaviourDefault | CustomViewBehaviour.RequiresTouchEventsSupport)
127         {
128             mPanGestureDetector = new PanGestureDetector();
129             mPanGestureDetector.Attach(this);
130             mPanGestureDetector.Detected += OnPanGestureDetected;
131
132             mTapGestureDetector = new TapGestureDetector();
133             mTapGestureDetector.Attach(this);
134             mTapGestureDetector.Detected += OnTapGestureDetected;
135
136             ClippingMode = ClippingModeType.ClipToBoundingBox;
137
138             mScrollingChild = new View();
139
140             Layout = new ScrollerCustomLayout();
141         }
142
143         public void AddLayoutToScroll(View child)
144         {
145             mScrollingChild = child;
146             Add(mScrollingChild);
147         }
148
149
150         /// <summary>
151         /// Scroll vertically by displacement pixels in screen coordinates.
152         /// </summary>
153         /// <param name="displacement">distance to scroll in pixels. Y increases as scroll position approaches the top.</param>
154         /// <since_tizen> 6 </since_tizen>
155         /// This will be public opened in tizen_5.5 after ACR done. Before ACR, need to be hidden as inhouse API.
156         public float ScrollVerticallyBy(float displacement)
157         {
158             Debug.WriteLineIf( LayoutDebugScroller, "ScrollVerticallyBy displacement:" + displacement);
159             return ScrollBy(displacement, false);
160         }
161
162         internal void StopScroll()
163         {
164             if (scrollAnimation != null && scrollAnimation.State == Animation.States.Playing)
165             {
166                 scrollAnimation.Stop(Animation.EndActions.Cancel);
167                 scrollAnimation.Clear();
168             }
169         }
170
171         // static constructor registers the control type (for user can add kinds of visuals to it)
172         static LayoutScroller()
173         {
174             // ViewRegistry registers control type with DALi type registry
175             // also uses introspection to find any properties that need to be registered with type registry
176             CustomViewRegistry.Instance.Register(CreateInstance, typeof(LayoutScroller));
177         }
178
179         internal static CustomView CreateInstance()
180         {
181             return new LayoutScroller();
182         }
183
184         public void OffsetChildVertically(float displacement, bool animate)
185         {
186             float previousTargetPosition = childTargetPosition;
187
188             childTargetPosition = childTargetPosition + displacement;
189             childTargetPosition = Math.Min(0,childTargetPosition);
190             childTargetPosition = Math.Max(-maxScrollDistance,childTargetPosition);
191
192             Debug.WriteLineIf( LayoutDebugScroller, "OffsetChildVertically currentYPosition:" + mScrollingChild.PositionY + "childTargetPosition:" + childTargetPosition);
193
194             if (animate)
195             {
196                 if (scrollAnimation == null)
197                 {
198                     scrollAnimation = new Animation();
199                     scrollAnimation.Finished += ScrollAnimationFinished;
200
201                 }
202                 else if (scrollAnimation.State == Animation.States.Playing)
203                 {
204                     scrollAnimation.Stop(Animation.EndActions.StopFinal);
205                     scrollAnimation.Clear();
206                 }
207                 scrollAnimation.Duration = 1000;
208                 scrollAnimation.DefaultAlphaFunction = new AlphaFunction(AlphaFunction.BuiltinFunctions.EaseOutSine);
209                 scrollAnimation.AnimateTo(mScrollingChild, "PositionY", childTargetPosition);
210                 scrollAnimation.Play();
211             }
212             else
213             {
214                 // Set position of scrolling child without an animation
215                 mScrollingChild.PositionY = childTargetPosition;
216             }
217         }
218
219         /// <summary>
220         /// you can override it to clean-up your own resources.
221         /// </summary>
222         /// <param name="type">DisposeTypes</param>
223         /// <since_tizen> 6 </since_tizen>
224         /// This will be public opened in tizen_5.5 after ACR done. Before ACR, need to be hidden as inhouse API.
225         [EditorBrowsable(EditorBrowsableState.Never)]
226         protected override void Dispose(DisposeTypes type)
227         {
228             if (disposed)
229             {
230                 return;
231             }
232
233             if (type == DisposeTypes.Explicit)
234             {
235                 StopScroll();
236
237                 if (mPanGestureDetector != null)
238                 {
239                     mPanGestureDetector.Detected -= OnPanGestureDetected;
240                     mPanGestureDetector.Dispose();
241                     mPanGestureDetector = null;
242                 }
243
244                 if (mTapGestureDetector != null)
245                 {
246                     mTapGestureDetector.Detected -= OnTapGestureDetected;
247                     mTapGestureDetector.Dispose();
248                     mTapGestureDetector = null;
249                 }
250             }
251             base.Dispose(type);
252         }
253
254         private float ScrollBy(float displacement, bool animate)
255         {
256             if (GetChildCount() == 0 || displacement == 0)
257             {
258                 return 0;
259             }
260
261             int scrollingChildHeight = (int)mScrollingChild.Layout.MeasuredHeight.Size.AsRoundedValue();
262             maxScrollDistance = scrollingChildHeight - CurrentSize.Height;
263
264             Debug.WriteLineIf( LayoutDebugScroller, "ScrollBy maxScrollDistance:" + maxScrollDistance +
265                                                     " parent length:" + CurrentSize.Height +
266                                                     " scrolling child length:" + mScrollingChild.CurrentSize.Height);
267
268             float absDisplacement = Math.Abs(displacement);
269
270             OffsetChildVertically(displacement, animate);
271
272             return absDisplacement;
273         }
274
275         private void OnPanGestureDetected(object source, PanGestureDetector.DetectedEventArgs e)
276         {
277             if (e.PanGesture.State == Gesture.StateType.Started)
278             {
279                 if(Scrolling)
280                 {
281                     StopScroll();
282                 }
283             }
284             else if (e.PanGesture.State == Gesture.StateType.Continuing)
285             {
286                 ScrollVerticallyBy(e.PanGesture.Displacement.Y);
287             }
288             else if (e.PanGesture.State == Gesture.StateType.Finished)
289             {
290                 ScrollVerticallyBy(e.PanGesture.Velocity.Y * 600);
291             }
292         }
293
294         private void OnTapGestureDetected(object source, TapGestureDetector.DetectedEventArgs e)
295         {
296             if (e.TapGesture.Type == Gesture.GestureType.Tap)
297             {
298                 // Stop scrolling if touch detected
299                 if(Scrolling)
300                 {
301                     StopScroll();
302                 }
303             }
304         }
305
306         private void ScrollAnimationFinished(object sender, EventArgs e)
307         {
308             Scrolling = false;
309         }
310
311
312     }
313
314 } // namespace