From 917a131a10e4fac1cd88c6804224e321f622fd30 Mon Sep 17 00:00:00 2001 From: agnelovaz Date: Thu, 19 Dec 2019 12:40:33 +0000 Subject: [PATCH] [NUI] Layout scroller pages api6 (#1185) * [NUI.Components] LayoutScroller flick added Flick support Configurable Flick animation Ensuring uniform duration of flick animation by removing flick multipler Change-Id: Ib654a38cb2070c29520069a75b5782d458ab4da6 * [NUI.Components] Horizontal Scroll supported * [NUI.Components] LayoutScroller with horizontal Pages support Other features added: FlickDurationModifier renamed to FlickAnimationSpeed Defaults changed so faster. Uses View API for Add/Remove child instead of the new dedicated API. ScrollStart and ScrollEnd signals added Vertical and Horizontal axis enums to set Horizontal or Vertical scrolling Fixes: Tap gesture doesn't stop pages scrolling mid way. Flick in Pages should not stack pages changes. Must wait until scroll ended to flick again. Change-Id: I8dcabff1e956b747987865ceb4272ccb2ce55061 * [NUI.Components] LayoutScroller renamed to Scrollable Change-Id: I42805c3b240997d1921db4b3c1008b120d264cbb * [NUI.Components] Scrollable derives from NUI.Components.Control Change-Id: Iec6f083fe4601ceff40f74bbe1ae8de4d8da6917 * [NUI.Components] Scrollable Pages MatchParent fix Fixed bug in which the Scrollable would be the size of it's content even when set to MatchParent Change-Id: I2be91b1a6f975b4e8aa2a132dc63009a21ef4b62 * [NUI] Rename component Rename component Scrollable -> Scroll * [NUI.Components] rename Scroll to ScrollableBase * [NUI.Components] change PanGesture direction when scrolling direction is changed When scrolling direction is changed, pan gesture direction should be changed. This change makes ScrollableBase support nested list cases. --- src/Tizen.NUI.Components/Controls/DropDown.cs | 25 +- .../Controls/LayoutScroller.cs | 314 ---------- .../Controls/ScrollableBase.cs | 650 +++++++++++++++++++++ src/Tizen.NUI/src/public/Layouting/LayoutGroup.cs | 2 +- 4 files changed, 664 insertions(+), 327 deletions(-) delete mode 100755 src/Tizen.NUI.Components/Controls/LayoutScroller.cs create mode 100755 src/Tizen.NUI.Components/Controls/ScrollableBase.cs diff --git a/src/Tizen.NUI.Components/Controls/DropDown.cs b/src/Tizen.NUI.Components/Controls/DropDown.cs index a34c93c..d4e0b50 100755 --- a/src/Tizen.NUI.Components/Controls/DropDown.cs +++ b/src/Tizen.NUI.Components/Controls/DropDown.cs @@ -120,7 +120,8 @@ namespace Tizen.NUI.Components private TextLabel buttonText = null; private ImageView listBackgroundImage = null; // Component that scrolls the child added to it. - private LayoutScroller layoutScroller = null; + private Scrollable scrollable = null; + // The LinearLayout container to house the items in the drop down list. private View dropDownMenuFullList = null; private DropDownListBridge adapter = new DropDownListBridge(); @@ -350,7 +351,7 @@ namespace Tizen.NUI.Components [EditorBrowsable(EditorBrowsableState.Never)] public void AttachScrollBar(ScrollBar scrollBar) { - if (layoutScroller == null) + if (scrollable == null) { return; } @@ -365,7 +366,7 @@ namespace Tizen.NUI.Components [EditorBrowsable(EditorBrowsableState.Never)] public void DetachScrollBar() { - if (layoutScroller == null) + if (scrollable == null) { return; } @@ -386,7 +387,7 @@ namespace Tizen.NUI.Components CreateButton(); CreateListBackgroundImage(); - if (null == layoutScroller) // layoutScroller used to test of ListContainer Setup invoked already + if (null == scrollable) // scrollable used to test of ListContainer Setup invoked already { SetUpListContainer(); } @@ -405,11 +406,11 @@ namespace Tizen.NUI.Components [EditorBrowsable(EditorBrowsableState.Never)] protected void UpdateDropDown() { - if (null == layoutScroller || null == listBackgroundImage || null == dropDownMenuFullList) return; + if (null == scrollable || null == listBackgroundImage || null == dropDownMenuFullList) return; if (null == Style.ListBackgroundImage.Size) return; // Resize and position scrolling list within the drop down list container. Can be used to position list in relation to the background image. - layoutScroller.Size = Style.ListBackgroundImage.Size - new Size((listPadding.Start + listPadding.End), (listPadding.Top + listPadding.Bottom), 0); - layoutScroller.Position2D = new Position2D(listPadding.Start, listPadding.Top); + scrollable.Size = Style.ListBackgroundImage.Size - new Size((listPadding.Start + listPadding.End), (listPadding.Top + listPadding.Bottom), 0); + scrollable.Position2D = new Position2D(listPadding.Start, listPadding.Top); int listBackgroundImageX = 0; int listBackgroundImageY = 0; @@ -473,7 +474,7 @@ namespace Tizen.NUI.Components Utility.Dispose(headerText); Utility.Dispose(buttonText); Utility.Dispose(button); - Utility.Dispose(layoutScroller); + Utility.Dispose(scrollable); Utility.Dispose(dropDownMenuFullList); Utility.Dispose(listBackgroundImage); } @@ -593,13 +594,13 @@ namespace Tizen.NUI.Components Focusable = true, }; - layoutScroller = new LayoutScroller() + scrollable = new Scrollable() { - Name = "LayoutScroller", + Name = "Scrollable", }; - layoutScroller.AddLayoutToScroll(dropDownMenuFullList); + scrollable.Add(dropDownMenuFullList); - listBackgroundImage.Add(layoutScroller); + listBackgroundImage.Add(scrollable); listBackgroundImage.Hide(); } diff --git a/src/Tizen.NUI.Components/Controls/LayoutScroller.cs b/src/Tizen.NUI.Components/Controls/LayoutScroller.cs deleted file mode 100755 index 03b15c9..0000000 --- a/src/Tizen.NUI.Components/Controls/LayoutScroller.cs +++ /dev/null @@ -1,314 +0,0 @@ -/* Copyright (c) 2019 Samsung Electronics Co., Ltd. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - */ -using System; -using Tizen.NUI.BaseComponents; -using System.ComponentModel; -using System.Diagnostics; -namespace Tizen.NUI.Components -{ - /// - /// [Draft] This class provides a View that can scroll a single View with a layout. - /// - internal class LayoutScroller : CustomView - { - static bool LayoutDebugScroller = true; // Debug flag - - private class ScrollerCustomLayout : LayoutGroup - { - protected override void OnMeasure(MeasureSpecification widthMeasureSpec, MeasureSpecification heightMeasureSpec) - { - float totalHeight = 0.0f; - float totalWidth = 0.0f; - - MeasuredSize.StateType childWidthState = MeasuredSize.StateType.MeasuredSizeOK; - MeasuredSize.StateType childHeightState = MeasuredSize.StateType.MeasuredSizeOK; - - // measure children - foreach( LayoutItem childLayout in LayoutChildren ) - { - if (childLayout != null) - { - // Get size of child - // Use an Unspecified MeasureSpecification mode so scrolling child is not restricted to it's parents size in Height (for vertical scrolling) - MeasureSpecification heightMeasureSpecUnrestricted = new MeasureSpecification( heightMeasureSpec.Size, MeasureSpecification.ModeType.Unspecified); - MeasureChild( childLayout, widthMeasureSpec, heightMeasureSpecUnrestricted ); - float childWidth = childLayout.MeasuredWidth.Size.AsDecimal(); - float childHeight = childLayout.MeasuredHeight.Size.AsDecimal(); - - // Determine the width and height needed by the children using their given position and size. - // Children could overlap so find the left most and right most child. - Position2D childPosition = childLayout.Owner.Position2D; - float childLeft = childPosition.X; - float childTop = childPosition.Y; - - // Store current width and height needed to contain all children. - Extents padding = Padding; - Extents childMargin = childLayout.Margin; - totalWidth = childWidth + padding.Start + padding.End + childMargin.Start + childMargin.End; - totalHeight = childHeight + padding.Top + padding.Bottom + childMargin.Top + childMargin.Bottom; - - if (childLayout.MeasuredWidth.State == MeasuredSize.StateType.MeasuredSizeTooSmall) - { - childWidthState = MeasuredSize.StateType.MeasuredSizeTooSmall; - } - if (childLayout.MeasuredWidth.State == MeasuredSize.StateType.MeasuredSizeTooSmall) - { - childHeightState = MeasuredSize.StateType.MeasuredSizeTooSmall; - } - } - } - - - MeasuredSize widthSizeAndState = ResolveSizeAndState(new LayoutLength(totalWidth), widthMeasureSpec, MeasuredSize.StateType.MeasuredSizeOK); - MeasuredSize heightSizeAndState = ResolveSizeAndState(new LayoutLength(totalHeight), heightMeasureSpec, MeasuredSize.StateType.MeasuredSizeOK); - totalWidth = widthSizeAndState.Size.AsDecimal(); - totalHeight = heightSizeAndState.Size.AsDecimal(); - - // Ensure layout respects it's given minimum size - totalWidth = Math.Max( totalWidth, SuggestedMinimumWidth.AsDecimal() ); - totalHeight = Math.Max( totalHeight, SuggestedMinimumHeight.AsDecimal() ); - - widthSizeAndState.State = childWidthState; - heightSizeAndState.State = childHeightState; - - SetMeasuredDimensions( ResolveSizeAndState( new LayoutLength(totalWidth), widthMeasureSpec, childWidthState ), - ResolveSizeAndState( new LayoutLength(totalHeight), heightMeasureSpec, childHeightState ) ); - } - - protected override void OnLayout(bool changed, LayoutLength left, LayoutLength top, LayoutLength right, LayoutLength bottom) - { - foreach( LayoutItem childLayout in LayoutChildren ) - { - if( childLayout != null ) - { - LayoutLength childWidth = childLayout.MeasuredWidth.Size; - LayoutLength childHeight = childLayout.MeasuredHeight.Size; - - Position2D childPosition = childLayout.Owner.Position2D; - Extents padding = Padding; - Extents childMargin = childLayout.Margin; - - LayoutLength childLeft = new LayoutLength(childPosition.X + childMargin.Start + padding.Start); - LayoutLength childTop = new LayoutLength(childPosition.Y + childMargin.Top + padding.Top); - - childLayout.Layout( childLeft, childTop, childLeft + childWidth, childTop + childHeight ); - } - } - } - } - - private Animation scrollAnimation; - private float maxScrollDistance; - private float childTargetPosition = 0.0f; - private PanGestureDetector mPanGestureDetector; - private TapGestureDetector mTapGestureDetector; - private View mScrollingChild; - - private bool Scrolling = false; - - /// - /// [Draft] Constructor - /// - /// 6 - public LayoutScroller() : base(typeof(VisualView).FullName, CustomViewBehaviour.ViewBehaviourDefault | CustomViewBehaviour.RequiresTouchEventsSupport) - { - mPanGestureDetector = new PanGestureDetector(); - mPanGestureDetector.Attach(this); - mPanGestureDetector.Detected += OnPanGestureDetected; - - mTapGestureDetector = new TapGestureDetector(); - mTapGestureDetector.Attach(this); - mTapGestureDetector.Detected += OnTapGestureDetected; - - ClippingMode = ClippingModeType.ClipToBoundingBox; - - mScrollingChild = new View(); - - Layout = new ScrollerCustomLayout(); - } - - public void AddLayoutToScroll(View child) - { - mScrollingChild = child; - Add(mScrollingChild); - } - - - /// - /// Scroll vertically by displacement pixels in screen coordinates. - /// - /// distance to scroll in pixels. Y increases as scroll position approaches the top. - /// 6 - /// This will be public opened in tizen_5.5 after ACR done. Before ACR, need to be hidden as inhouse API. - public float ScrollVerticallyBy(float displacement) - { - Debug.WriteLineIf( LayoutDebugScroller, "ScrollVerticallyBy displacement:" + displacement); - return ScrollBy(displacement, false); - } - - internal void StopScroll() - { - if (scrollAnimation != null && scrollAnimation.State == Animation.States.Playing) - { - scrollAnimation.Stop(Animation.EndActions.Cancel); - scrollAnimation.Clear(); - } - } - - // static constructor registers the control type (for user can add kinds of visuals to it) - static LayoutScroller() - { - // ViewRegistry registers control type with DALi type registry - // also uses introspection to find any properties that need to be registered with type registry - CustomViewRegistry.Instance.Register(CreateInstance, typeof(LayoutScroller)); - } - - internal static CustomView CreateInstance() - { - return new LayoutScroller(); - } - - public void OffsetChildVertically(float displacement, bool animate) - { - float previousTargetPosition = childTargetPosition; - - childTargetPosition = childTargetPosition + displacement; - childTargetPosition = Math.Min(0,childTargetPosition); - childTargetPosition = Math.Max(-maxScrollDistance,childTargetPosition); - - Debug.WriteLineIf( LayoutDebugScroller, "OffsetChildVertically currentYPosition:" + mScrollingChild.PositionY + "childTargetPosition:" + childTargetPosition); - - if (animate) - { - if (scrollAnimation == null) - { - scrollAnimation = new Animation(); - scrollAnimation.Finished += ScrollAnimationFinished; - - } - else if (scrollAnimation.State == Animation.States.Playing) - { - scrollAnimation.Stop(Animation.EndActions.StopFinal); - scrollAnimation.Clear(); - } - scrollAnimation.Duration = 1000; - scrollAnimation.DefaultAlphaFunction = new AlphaFunction(AlphaFunction.BuiltinFunctions.EaseOutSine); - scrollAnimation.AnimateTo(mScrollingChild, "PositionY", childTargetPosition); - scrollAnimation.Play(); - } - else - { - // Set position of scrolling child without an animation - mScrollingChild.PositionY = childTargetPosition; - } - } - - /// - /// you can override it to clean-up your own resources. - /// - /// DisposeTypes - /// 6 - /// This will be public opened in tizen_5.5 after ACR done. Before ACR, need to be hidden as inhouse API. - [EditorBrowsable(EditorBrowsableState.Never)] - protected override void Dispose(DisposeTypes type) - { - if (disposed) - { - return; - } - - if (type == DisposeTypes.Explicit) - { - StopScroll(); - - if (mPanGestureDetector != null) - { - mPanGestureDetector.Detected -= OnPanGestureDetected; - mPanGestureDetector.Dispose(); - mPanGestureDetector = null; - } - - if (mTapGestureDetector != null) - { - mTapGestureDetector.Detected -= OnTapGestureDetected; - mTapGestureDetector.Dispose(); - mTapGestureDetector = null; - } - } - base.Dispose(type); - } - - private float ScrollBy(float displacement, bool animate) - { - if (GetChildCount() == 0 || displacement == 0) - { - return 0; - } - - int scrollingChildHeight = (int)mScrollingChild.Layout.MeasuredHeight.Size.AsRoundedValue(); - maxScrollDistance = scrollingChildHeight - CurrentSize.Height; - - Debug.WriteLineIf( LayoutDebugScroller, "ScrollBy maxScrollDistance:" + maxScrollDistance + - " parent length:" + CurrentSize.Height + - " scrolling child length:" + mScrollingChild.CurrentSize.Height); - - float absDisplacement = Math.Abs(displacement); - - OffsetChildVertically(displacement, animate); - - return absDisplacement; - } - - private void OnPanGestureDetected(object source, PanGestureDetector.DetectedEventArgs e) - { - if (e.PanGesture.State == Gesture.StateType.Started) - { - if(Scrolling) - { - StopScroll(); - } - } - else if (e.PanGesture.State == Gesture.StateType.Continuing) - { - ScrollVerticallyBy(e.PanGesture.Displacement.Y); - } - else if (e.PanGesture.State == Gesture.StateType.Finished) - { - ScrollVerticallyBy(e.PanGesture.Velocity.Y * 600); - } - } - - private void OnTapGestureDetected(object source, TapGestureDetector.DetectedEventArgs e) - { - if (e.TapGesture.Type == Gesture.GestureType.Tap) - { - // Stop scrolling if touch detected - if(Scrolling) - { - StopScroll(); - } - } - } - - private void ScrollAnimationFinished(object sender, EventArgs e) - { - Scrolling = false; - } - - - } - -} // namespace diff --git a/src/Tizen.NUI.Components/Controls/ScrollableBase.cs b/src/Tizen.NUI.Components/Controls/ScrollableBase.cs new file mode 100755 index 0000000..358a6fa --- /dev/null +++ b/src/Tizen.NUI.Components/Controls/ScrollableBase.cs @@ -0,0 +1,650 @@ +/* Copyright (c) 2019 Samsung Electronics Co., Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +using System; +using Tizen.NUI.BaseComponents; +using System.ComponentModel; +using System.Diagnostics; +namespace Tizen.NUI.Components +{ + /// + /// [Draft] This class provides a View that can scroll a single View with a layout. This View can be a nest of Views. + /// + /// This may be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API. + [EditorBrowsable(EditorBrowsableState.Never)] + public class ScrollableBase : Control + { + static bool LayoutDebugScrollableBase = false; // Debug flag + private Direction mScrollingDirection = Direction.Vertical; + + private class ScrollableBaseCustomLayout : LayoutGroup + { + protected override void OnMeasure(MeasureSpecification widthMeasureSpec, MeasureSpecification heightMeasureSpec) + { + Extents padding = Padding; + float totalHeight = padding.Top + padding.Bottom; + float totalWidth = padding.Start + padding.End; + + MeasuredSize.StateType childWidthState = MeasuredSize.StateType.MeasuredSizeOK; + MeasuredSize.StateType childHeightState = MeasuredSize.StateType.MeasuredSizeOK; + + Direction scrollingDirection = Direction.Vertical; + ScrollableBase scrollableBase = this.Owner as ScrollableBase; + if (scrollableBase) + { + scrollingDirection = scrollableBase.ScrollingDirection; + } + + // measure child, should be a single scrolling child + foreach( LayoutItem childLayout in LayoutChildren ) + { + if (childLayout != null) + { + // Get size of child + // Use an Unspecified MeasureSpecification mode so scrolling child is not restricted to it's parents size in Height (for vertical scrolling) + // or Width for horizontal scrolling + MeasureSpecification unrestrictedMeasureSpec = new MeasureSpecification( heightMeasureSpec.Size, MeasureSpecification.ModeType.Unspecified); + + if (scrollingDirection == Direction.Vertical) + { + MeasureChild( childLayout, widthMeasureSpec, unrestrictedMeasureSpec ); // Height unrestricted by parent + } + else + { + MeasureChild( childLayout, unrestrictedMeasureSpec, heightMeasureSpec ); // Width unrestricted by parent + } + + float childWidth = childLayout.MeasuredWidth.Size.AsDecimal(); + float childHeight = childLayout.MeasuredHeight.Size.AsDecimal(); + + // Determine the width and height needed by the children using their given position and size. + // Children could overlap so find the left most and right most child. + Position2D childPosition = childLayout.Owner.Position2D; + float childLeft = childPosition.X; + float childTop = childPosition.Y; + + // Store current width and height needed to contain all children. + Extents childMargin = childLayout.Margin; + totalWidth = childWidth + childMargin.Start + childMargin.End; + totalHeight = childHeight + childMargin.Top + childMargin.Bottom; + + if (childLayout.MeasuredWidth.State == MeasuredSize.StateType.MeasuredSizeTooSmall) + { + childWidthState = MeasuredSize.StateType.MeasuredSizeTooSmall; + } + if (childLayout.MeasuredWidth.State == MeasuredSize.StateType.MeasuredSizeTooSmall) + { + childHeightState = MeasuredSize.StateType.MeasuredSizeTooSmall; + } + } + } + + + MeasuredSize widthSizeAndState = ResolveSizeAndState(new LayoutLength(totalWidth), widthMeasureSpec, MeasuredSize.StateType.MeasuredSizeOK); + MeasuredSize heightSizeAndState = ResolveSizeAndState(new LayoutLength(totalHeight), heightMeasureSpec, MeasuredSize.StateType.MeasuredSizeOK); + totalWidth = widthSizeAndState.Size.AsDecimal(); + totalHeight = heightSizeAndState.Size.AsDecimal(); + + // Ensure layout respects it's given minimum size + totalWidth = Math.Max( totalWidth, SuggestedMinimumWidth.AsDecimal() ); + totalHeight = Math.Max( totalHeight, SuggestedMinimumHeight.AsDecimal() ); + + widthSizeAndState.State = childWidthState; + heightSizeAndState.State = childHeightState; + + SetMeasuredDimensions( ResolveSizeAndState( new LayoutLength(totalWidth), widthMeasureSpec, childWidthState ), + ResolveSizeAndState( new LayoutLength(totalHeight), heightMeasureSpec, childHeightState ) ); + + } + + protected override void OnLayout(bool changed, LayoutLength left, LayoutLength top, LayoutLength right, LayoutLength bottom) + { + foreach( LayoutItem childLayout in LayoutChildren ) + { + if( childLayout != null ) + { + LayoutLength childWidth = childLayout.MeasuredWidth.Size; + LayoutLength childHeight = childLayout.MeasuredHeight.Size; + + Position2D childPosition = childLayout.Owner.Position2D; + Extents padding = Padding; + Extents childMargin = childLayout.Margin; + + LayoutLength childLeft = new LayoutLength(childPosition.X + childMargin.Start + padding.Start); + LayoutLength childTop = new LayoutLength(childPosition.Y + childMargin.Top + padding.Top); + + childLayout.Layout( childLeft, childTop, childLeft + childWidth, childTop + childHeight ); + } + } + } + } // ScrollableBaseCustomLayout + + /// + /// The direction axis to scroll. + /// + /// 6 + /// This may be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API. + [EditorBrowsable(EditorBrowsableState.Never)] + public enum Direction + { + /// + /// Horizontal axis. + /// + /// 6 + Horizontal, + + /// + /// Vertical axis. + /// + /// 6 + Vertical + } + + /// + /// [Draft] Configurable speed threshold that register the gestures as a flick. + /// If the flick speed less than the threshold then will not be considered a flick. + /// + /// This may be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API. + [EditorBrowsable(EditorBrowsableState.Never)] + public float FlickThreshold { get; set; } = 0.2f; + + /// + /// [Draft] Configurable duration modifer for the flick animation. + /// Determines the speed of the scroll, large value results in a longer flick animation. Range (0.1 - 1.0) + /// + /// This may be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API + [EditorBrowsable(EditorBrowsableState.Never)] + public float FlickAnimationSpeed { get; set; } = 0.4f; + + /// + /// [Draft] Configurable modifer for the distance to be scrolled when flicked detected. + /// It a ratio of the ScrollableBase's length. (not child's length). + /// First value is the ratio of the distance to scroll with the weakest flick. + /// Second value is the ratio of the distance to scroll with the strongest flick. + /// Second > First. + /// + /// This may be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API + [EditorBrowsable(EditorBrowsableState.Never)] + public Vector2 FlickDistanceMultiplierRange { get; set; } = new Vector2(0.6f, 1.8f); + + /// + /// [Draft] Scrolling direction mode. + /// Default is Vertical scrolling. + /// + /// This may be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API + [EditorBrowsable(EditorBrowsableState.Never)] + public Direction ScrollingDirection + { + get + { + return mScrollingDirection; + } + set + { + if(value != mScrollingDirection) + { + mScrollingDirection = value; + mPanGestureDetector.RemoveDirection(value == Direction.Horizontal ? PanGestureDetector.DirectionVertical : PanGestureDetector.DirectionHorizontal); + mPanGestureDetector.AddDirection(value == Direction.Horizontal ? PanGestureDetector.DirectionHorizontal : PanGestureDetector.DirectionVertical); + } + } + } + + /// + /// [Draft] Pages mode, enables moving to the next or return to current page depending on pan displacement. + /// Default is false. + /// + /// This may be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API + [EditorBrowsable(EditorBrowsableState.Never)] + public bool SnapToPage { set; get; } = false; + + /// + /// [Draft] Pages mode, Number of pages. + /// + /// This may be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API + [EditorBrowsable(EditorBrowsableState.Never)] + public int NumberOfPages { set; get; } = 1; + + /// + /// [Draft] Duration of scroll animation. + /// + /// This may be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API + [EditorBrowsable(EditorBrowsableState.Never)] + public int ScrollDuration { set; get; } = 125; + + /// + /// [Draft] Width of the Page. + /// + /// This may be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API + [EditorBrowsable(EditorBrowsableState.Never)] + public int PageWidth { set; get; } = 1080; // Temporary use for prototype, should get ScrollableBase width + + /// + /// ScrollEventArgs is a class to record scroll event arguments which will sent to user. + /// + /// 6 + /// This may be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API + [EditorBrowsable(EditorBrowsableState.Never)] + public class ScrollEventArgs : EventArgs + { + } + + /// + /// An event emitted when the scrolling starts, user can subscribe or unsubscribe to this event handler.
+ ///
+ /// 6 + /// This may be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API + [EditorBrowsable(EditorBrowsableState.Never)] + public event EventHandler ScrollStartedEvent; + + /// + /// An event emitted when the scrolling ends, user can subscribe or unsubscribe to this event handler.
+ ///
+ /// 6 + /// This may be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API + [EditorBrowsable(EditorBrowsableState.Never)] + public event EventHandler ScrollEndedEvent; + + private Animation scrollAnimation; + private float maxScrollDistance; + private float childTargetPosition = 0.0f; + private PanGestureDetector mPanGestureDetector; + private TapGestureDetector mTapGestureDetector; + private View mScrollingChild; + private float multiplier =1.0f; + private bool scrolling = false; + private float ratioOfScreenWidthToCompleteScroll = 0.5f; + private float totalDisplacementForPan = 0.0f; + + // If false then can only flick pages when the current animation/scroll as ended. + private bool flickWhenAnimating = false; + + private int currentPage = 0; + + /// + /// [Draft] Constructor + /// + /// 6 + /// This may be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API + [EditorBrowsable(EditorBrowsableState.Never)] + public ScrollableBase() : base() + { + mPanGestureDetector = new PanGestureDetector(); + mPanGestureDetector.Attach(this); + mPanGestureDetector.Detected += OnPanGestureDetected; + + mTapGestureDetector = new TapGestureDetector(); + mTapGestureDetector.Attach(this); + mTapGestureDetector.Detected += OnTapGestureDetected; + + ClippingMode = ClippingModeType.ClipToBoundingBox; + + mScrollingChild = new View(); + + Layout = new ScrollableBaseCustomLayout(); + } + + /// + /// Called after a child has been added to the owning view. + /// + /// The child which has been added. + /// 6 + /// This may be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API + [EditorBrowsable(EditorBrowsableState.Never)] + public override void OnChildAdd(View view) + { + mScrollingChild = view; + { + if (Children.Count > 1) + Log.Error("ScrollableBase", $"Only 1 child should be added to ScrollableBase."); + } + } + + /// + /// Called after a child has been removed from the owning view. + /// + /// The child which has been removed. + /// 6 + /// This may be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API + [EditorBrowsable(EditorBrowsableState.Never)] + public override void OnChildRemove(View view) + { + mScrollingChild = new View(); + } + + private void OnScrollStart() + { + ScrollEventArgs eventArgs = new ScrollEventArgs(); + ScrollStartedEvent?.Invoke(this, eventArgs); + } + + private void OnScrollEnd() + { + ScrollEventArgs eventArgs = new ScrollEventArgs(); + ScrollEndedEvent?.Invoke(this, eventArgs); + } + + private void StopScroll() + { + if (scrollAnimation != null) + { + if (scrollAnimation.State == Animation.States.Playing) + { + Debug.WriteLineIf(LayoutDebugScrollableBase, "StopScroll Animation Playing"); + scrollAnimation.Stop(Animation.EndActions.Cancel); + OnScrollEnd(); + } + scrollAnimation.Clear(); + } + } + + // static constructor registers the control type + static ScrollableBase() + { + // ViewRegistry registers control type with DALi type registry + // also uses introspection to find any properties that need to be registered with type registry + CustomViewRegistry.Instance.Register(CreateInstance, typeof(ScrollableBase)); + } + + internal static CustomView CreateInstance() + { + return new ScrollableBase(); + } + + private void AnimateChildTo(int duration, float axisPosition) + { + Debug.WriteLineIf(LayoutDebugScrollableBase, "AnimationTo Animation Duration:" + duration + " Destination:" + axisPosition); + + StopScroll(); // Will replace previous animation so will stop existing one. + + if (scrollAnimation == null) + { + scrollAnimation = new Animation(); + scrollAnimation.Finished += ScrollAnimationFinished; + } + + scrollAnimation.Duration = duration; + scrollAnimation.DefaultAlphaFunction = new AlphaFunction(AlphaFunction.BuiltinFunctions.EaseOutSine); + scrollAnimation.AnimateTo(mScrollingChild, (ScrollingDirection == Direction.Horizontal) ? "PositionX" : "PositionY", axisPosition); + scrolling = true; + OnScrollStart(); + scrollAnimation.Play(); + } + + private void ScrollBy(float displacement, bool animate) + { + if (GetChildCount() == 0 || displacement == 0) + { + return; + } + + float childCurrentPosition = (ScrollingDirection == Direction.Horizontal) ? mScrollingChild.PositionX: mScrollingChild.PositionY; + + Debug.WriteLineIf(LayoutDebugScrollableBase, "ScrollBy childCurrentPosition:" + childCurrentPosition + + " displacement:" + displacement, + " maxScrollDistance:" + maxScrollDistance ); + + childTargetPosition = childCurrentPosition + displacement; // child current position + gesture displacement + childTargetPosition = Math.Min(0,childTargetPosition); + childTargetPosition = Math.Max(-maxScrollDistance,childTargetPosition); + + Debug.WriteLineIf( LayoutDebugScrollableBase, "ScrollBy currentAxisPosition:" + childCurrentPosition + "childTargetPosition:" + childTargetPosition); + + if (animate) + { + // Calculate scroll animaton duration + float scrollDistance = 0.0f; + if (childCurrentPosition < childTargetPosition) + { + scrollDistance = Math.Abs(childCurrentPosition + childTargetPosition); + } + else + { + scrollDistance = Math.Abs(childCurrentPosition - childTargetPosition); + } + + int duration = (int)((320*FlickAnimationSpeed) + (scrollDistance * FlickAnimationSpeed)); + Debug.WriteLineIf(LayoutDebugScrollableBase, "Scroll Animation Duration:" + duration + " Distance:" + scrollDistance); + + AnimateChildTo(duration, childTargetPosition); + } + else + { + // Set position of scrolling child without an animation + if (ScrollingDirection == Direction.Horizontal) + { + mScrollingChild.PositionX = childTargetPosition; + } + else + { + mScrollingChild.PositionY = childTargetPosition; + } + } + } + + /// + /// you can override it to clean-up your own resources. + /// + /// DisposeTypes + /// 6 + /// This will be public opened in tizen_5.5 after ACR done. Before ACR, need to be hidden as inhouse API. + [EditorBrowsable(EditorBrowsableState.Never)] + protected override void Dispose(DisposeTypes type) + { + if (disposed) + { + return; + } + + if (type == DisposeTypes.Explicit) + { + StopScroll(); + + if (mPanGestureDetector != null) + { + mPanGestureDetector.Detected -= OnPanGestureDetected; + mPanGestureDetector.Dispose(); + mPanGestureDetector = null; + } + + if (mTapGestureDetector != null) + { + mTapGestureDetector.Detected -= OnTapGestureDetected; + mTapGestureDetector.Dispose(); + mTapGestureDetector = null; + } + } + base.Dispose(type); + } + + private float CalculateDisplacementFromVelocity(float axisVelocity) + { + // Map: flick speed of range (2.0 - 6.0) to flick multiplier of range (0.7 - 1.6) + float speedMinimum = FlickThreshold; + float speedMaximum = FlickThreshold + 6.0f; + float multiplierMinimum = FlickDistanceMultiplierRange.X; + float multiplierMaximum = FlickDistanceMultiplierRange.Y; + + float flickDisplacement = 0.0f; + + float speed = Math.Min(4.0f,Math.Abs(axisVelocity)); + + Debug.WriteLineIf(LayoutDebugScrollableBase, "ScrollableBase Candidate Flick speed:" + speed); + + if (speed > FlickThreshold) + { + // Flick length is the length of the ScrollableBase. + float flickLength = (ScrollingDirection == Direction.Horizontal) ?CurrentSize.Width:CurrentSize.Height; + + // Calculate multiplier by mapping speed between the multiplier minimum and maximum. + multiplier =( (speed - speedMinimum) / ( (speedMaximum - speedMinimum) * (multiplierMaximum - multiplierMinimum) ) )+ multiplierMinimum; + + // flick displacement is the product of the flick length and multiplier + flickDisplacement = ((flickLength * multiplier) * speed) / axisVelocity; // *speed and /velocity to perserve sign. + + Debug.WriteLineIf(LayoutDebugScrollableBase, "Calculated FlickDisplacement[" + flickDisplacement +"] from speed[" + speed + "] multiplier:" + + multiplier); + } + return flickDisplacement; + } + + private float CalculateMaximumScrollDistance() + { + int scrollingChildLength = 0; + int scrollerLength = 0; + if (ScrollingDirection == Direction.Horizontal) + { + Debug.WriteLineIf(LayoutDebugScrollableBase, "Horizontal"); + + scrollingChildLength = (int)mScrollingChild.Layout.MeasuredWidth.Size.AsRoundedValue(); + scrollerLength = CurrentSize.Width; + } + else + { + Debug.WriteLineIf(LayoutDebugScrollableBase, "Vertical"); + scrollingChildLength = (int)mScrollingChild.Layout.MeasuredHeight.Size.AsRoundedValue(); + scrollerLength = CurrentSize.Height; + } + + Debug.WriteLineIf(LayoutDebugScrollableBase, "ScrollBy maxScrollDistance:" + (scrollingChildLength - scrollerLength) + + " parent length:" + scrollerLength + + " scrolling child length:" + scrollingChildLength); + + return scrollingChildLength - scrollerLength; + } + + private void PageSnap() + { + Debug.WriteLineIf(LayoutDebugScrollableBase, "PageSnap with pan candidate totalDisplacement:" + totalDisplacementForPan + + " currentPage[" + currentPage + "]" ); + + //Increment current page if total displacement enough to warrant a page change. + if (Math.Abs(totalDisplacementForPan) > (PageWidth * ratioOfScreenWidthToCompleteScroll)) + { + if (totalDisplacementForPan < 0) + { + currentPage = Math.Min(NumberOfPages-1, ++currentPage); + } + else + { + currentPage = Math.Max(0, --currentPage); + } + } + + // Animate to new page or reposition to current page + int destinationX = -(currentPage * PageWidth); + Debug.WriteLineIf(LayoutDebugScrollableBase, "Snapping to page[" + currentPage + "] to:"+ destinationX + " from:" + mScrollingChild.PositionX); + AnimateChildTo(ScrollDuration, destinationX); + } + + private void Flick(float flickDisplacement) + { + if (SnapToPage) + { + if ( ( flickWhenAnimating && scrolling == true) || ( scrolling == false) ) + { + if(flickDisplacement < 0) + { + currentPage = Math.Min(NumberOfPages - 1, currentPage + 1); + Debug.WriteLineIf(LayoutDebugScrollableBase, "Snap - to page:" + currentPage); + } + else + { + currentPage = Math.Max(0, currentPage - 1); + Debug.WriteLineIf(LayoutDebugScrollableBase, "Snap + to page:" + currentPage); + } + float targetPosition = -(currentPage* PageWidth); // page size + Debug.WriteLineIf(LayoutDebugScrollableBase, "Snapping to :" + targetPosition); + AnimateChildTo(ScrollDuration,targetPosition); + } + } + else + { + ScrollBy(flickDisplacement, true); // Animate flickDisplacement. + } + } + + private void OnPanGestureDetected(object source, PanGestureDetector.DetectedEventArgs e) + { + if (e.PanGesture.State == Gesture.StateType.Started) + { + Debug.WriteLineIf(LayoutDebugScrollableBase, "Gesture Start"); + if (scrolling && !SnapToPage) + { + StopScroll(); + } + maxScrollDistance = CalculateMaximumScrollDistance(); + totalDisplacementForPan = 0.0f; + } + else if (e.PanGesture.State == Gesture.StateType.Continuing) + { + if (ScrollingDirection == Direction.Horizontal) + { + ScrollBy(e.PanGesture.Displacement.X, false); + totalDisplacementForPan += e.PanGesture.Displacement.X; + } + else + { + ScrollBy(e.PanGesture.Displacement.Y, false); + totalDisplacementForPan += e.PanGesture.Displacement.Y; + } + Debug.WriteLineIf(LayoutDebugScrollableBase, "OnPanGestureDetected Continue totalDisplacementForPan:" + totalDisplacementForPan); + + } + else if (e.PanGesture.State == Gesture.StateType.Finished) + { + float axisVelocity = (ScrollingDirection == Direction.Horizontal) ? e.PanGesture.Velocity.X : e.PanGesture.Velocity.Y; + float flickDisplacement = CalculateDisplacementFromVelocity(axisVelocity); + + Debug.WriteLineIf(LayoutDebugScrollableBase, "FlickDisplacement:" + flickDisplacement + "TotalDisplacementForPan:" + totalDisplacementForPan); + + if (flickDisplacement > 0 | flickDisplacement < 0)// Flick detected + { + Flick(flickDisplacement); + } + else + { + // End of panning gesture but was not a flick + if (SnapToPage) + { + PageSnap(); + } + } + totalDisplacementForPan = 0; + } + } + + private void OnTapGestureDetected(object source, TapGestureDetector.DetectedEventArgs e) + { + if (e.TapGesture.Type == Gesture.GestureType.Tap) + { + // Stop scrolling if tap detected (press then relase). + // Unless in Pages mode, do not want a page change to stop part way. + if(scrolling && !SnapToPage) + { + StopScroll(); + } + } + } + + private void ScrollAnimationFinished(object sender, EventArgs e) + { + scrolling = false; + OnScrollEnd(); + } + + } + +} // namespace diff --git a/src/Tizen.NUI/src/public/Layouting/LayoutGroup.cs b/src/Tizen.NUI/src/public/Layouting/LayoutGroup.cs index 070231d..1623b54 100755 --- a/src/Tizen.NUI/src/public/Layouting/LayoutGroup.cs +++ b/src/Tizen.NUI/src/public/Layouting/LayoutGroup.cs @@ -461,7 +461,7 @@ namespace Tizen.NUI { View childOwner = child.Owner; - Extents padding = Padding; // Padding of this layout's owner, not of the child being measured. + Extents padding = childOwner.Padding; // Padding of this layout's owner, not of the child being measured. MeasureSpecification childWidthMeasureSpec = GetChildMeasureSpecification( parentWidthMeasureSpec, new LayoutLength(padding.Start + padding.End ), -- 2.7.4