1 /* Copyright (c) 2019 Samsung Electronics Co., Ltd.
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
7 * http://www.apache.org/licenses/LICENSE-2.0
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.
17 using Tizen.NUI.BaseComponents;
18 using System.ComponentModel;
19 using System.Diagnostics;
20 namespace Tizen.NUI.Components
23 /// [Draft] This class provides a View that can scroll a single View with a layout.
25 internal class LayoutScroller : CustomView
27 static bool LayoutDebugScroller = true; // Debug flag
29 private class ScrollerCustomLayout : LayoutGroup
31 protected override void OnMeasure(MeasureSpecification widthMeasureSpec, MeasureSpecification heightMeasureSpec)
33 float totalHeight = 0.0f;
34 float totalWidth = 0.0f;
36 MeasuredSize.StateType childWidthState = MeasuredSize.StateType.MeasuredSizeOK;
37 MeasuredSize.StateType childHeightState = MeasuredSize.StateType.MeasuredSizeOK;
40 foreach( LayoutItem childLayout in LayoutChildren )
42 if (childLayout != null)
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();
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;
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;
63 if (childLayout.MeasuredWidth.State == MeasuredSize.StateType.MeasuredSizeTooSmall)
65 childWidthState = MeasuredSize.StateType.MeasuredSizeTooSmall;
67 if (childLayout.MeasuredWidth.State == MeasuredSize.StateType.MeasuredSizeTooSmall)
69 childHeightState = MeasuredSize.StateType.MeasuredSizeTooSmall;
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();
80 // Ensure layout respects it's given minimum size
81 totalWidth = Math.Max( totalWidth, SuggestedMinimumWidth.AsDecimal() );
82 totalHeight = Math.Max( totalHeight, SuggestedMinimumHeight.AsDecimal() );
84 widthSizeAndState.State = childWidthState;
85 heightSizeAndState.State = childHeightState;
87 SetMeasuredDimensions( ResolveSizeAndState( new LayoutLength(totalWidth), widthMeasureSpec, childWidthState ),
88 ResolveSizeAndState( new LayoutLength(totalHeight), heightMeasureSpec, childHeightState ) );
91 protected override void OnLayout(bool changed, LayoutLength left, LayoutLength top, LayoutLength right, LayoutLength bottom)
93 foreach( LayoutItem childLayout in LayoutChildren )
95 if( childLayout != null )
97 LayoutLength childWidth = childLayout.MeasuredWidth.Size;
98 LayoutLength childHeight = childLayout.MeasuredHeight.Size;
100 Position2D childPosition = childLayout.Owner.Position2D;
101 Extents padding = Padding;
102 Extents childMargin = childLayout.Margin;
104 LayoutLength childLeft = new LayoutLength(childPosition.X + childMargin.Start + padding.Start);
105 LayoutLength childTop = new LayoutLength(childPosition.Y + childMargin.Top + padding.Top);
107 childLayout.Layout( childLeft, childTop, childLeft + childWidth, childTop + childHeight );
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;
120 private bool Scrolling = false;
123 /// [Draft] Constructor
125 /// <since_tizen> 6 </since_tizen>
126 public LayoutScroller() : base(typeof(VisualView).FullName, CustomViewBehaviour.ViewBehaviourDefault | CustomViewBehaviour.RequiresTouchEventsSupport)
128 mPanGestureDetector = new PanGestureDetector();
129 mPanGestureDetector.Attach(this);
130 mPanGestureDetector.Detected += OnPanGestureDetected;
132 mTapGestureDetector = new TapGestureDetector();
133 mTapGestureDetector.Attach(this);
134 mTapGestureDetector.Detected += OnTapGestureDetected;
136 ClippingMode = ClippingModeType.ClipToBoundingBox;
138 mScrollingChild = new View();
140 Layout = new ScrollerCustomLayout();
143 public void AddLayoutToScroll(View child)
145 mScrollingChild = child;
146 Add(mScrollingChild);
151 /// Scroll vertically by displacement pixels in screen coordinates.
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)
158 Debug.WriteLineIf( LayoutDebugScroller, "ScrollVerticallyBy displacement:" + displacement);
159 return ScrollBy(displacement, false);
162 internal void StopScroll()
164 if (scrollAnimation != null && scrollAnimation.State == Animation.States.Playing)
166 scrollAnimation.Stop(Animation.EndActions.Cancel);
167 scrollAnimation.Clear();
171 // static constructor registers the control type (for user can add kinds of visuals to it)
172 static LayoutScroller()
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));
179 internal static CustomView CreateInstance()
181 return new LayoutScroller();
184 public void OffsetChildVertically(float displacement, bool animate)
186 float previousTargetPosition = childTargetPosition;
188 childTargetPosition = childTargetPosition + displacement;
189 childTargetPosition = Math.Min(0,childTargetPosition);
190 childTargetPosition = Math.Max(-maxScrollDistance,childTargetPosition);
192 Debug.WriteLineIf( LayoutDebugScroller, "OffsetChildVertically currentYPosition:" + mScrollingChild.PositionY + "childTargetPosition:" + childTargetPosition);
196 if (scrollAnimation == null)
198 scrollAnimation = new Animation();
199 scrollAnimation.Finished += ScrollAnimationFinished;
202 else if (scrollAnimation.State == Animation.States.Playing)
204 scrollAnimation.Stop(Animation.EndActions.StopFinal);
205 scrollAnimation.Clear();
207 scrollAnimation.Duration = 1000;
208 scrollAnimation.DefaultAlphaFunction = new AlphaFunction(AlphaFunction.BuiltinFunctions.EaseOutSine);
209 scrollAnimation.AnimateTo(mScrollingChild, "PositionY", childTargetPosition);
210 scrollAnimation.Play();
214 // Set position of scrolling child without an animation
215 mScrollingChild.PositionY = childTargetPosition;
220 /// you can override it to clean-up your own resources.
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)
233 if (type == DisposeTypes.Explicit)
237 if (mPanGestureDetector != null)
239 mPanGestureDetector.Detected -= OnPanGestureDetected;
240 mPanGestureDetector.Dispose();
241 mPanGestureDetector = null;
244 if (mTapGestureDetector != null)
246 mTapGestureDetector.Detected -= OnTapGestureDetected;
247 mTapGestureDetector.Dispose();
248 mTapGestureDetector = null;
254 private float ScrollBy(float displacement, bool animate)
256 if (GetChildCount() == 0 || displacement == 0)
261 int scrollingChildHeight = (int)mScrollingChild.Layout.MeasuredHeight.Size.AsRoundedValue();
262 maxScrollDistance = scrollingChildHeight - CurrentSize.Height;
264 Debug.WriteLineIf( LayoutDebugScroller, "ScrollBy maxScrollDistance:" + maxScrollDistance +
265 " parent length:" + CurrentSize.Height +
266 " scrolling child length:" + mScrollingChild.CurrentSize.Height);
268 float absDisplacement = Math.Abs(displacement);
270 OffsetChildVertically(displacement, animate);
272 return absDisplacement;
275 private void OnPanGestureDetected(object source, PanGestureDetector.DetectedEventArgs e)
277 if (e.PanGesture.State == Gesture.StateType.Started)
284 else if (e.PanGesture.State == Gesture.StateType.Continuing)
286 ScrollVerticallyBy(e.PanGesture.Displacement.Y);
288 else if (e.PanGesture.State == Gesture.StateType.Finished)
290 ScrollVerticallyBy(e.PanGesture.Velocity.Y * 600);
294 private void OnTapGestureDetected(object source, TapGestureDetector.DetectedEventArgs e)
296 if (e.TapGesture.Type == Gesture.GestureType.Tap)
298 // Stop scrolling if touch detected
306 private void ScrollAnimationFinished(object sender, EventArgs e)