diff --git a/Editor/UIExtensionsMenuOptions.cs b/Editor/UIExtensionsMenuOptions.cs index 57af6ec..6bba15a 100644 --- a/Editor/UIExtensionsMenuOptions.cs +++ b/Editor/UIExtensionsMenuOptions.cs @@ -466,12 +466,119 @@ namespace UnityEditor.UI { FixedScrollSnapBase(menuCommand, "Scroll Snap Vertical Multiple", ScrollSnap.ScrollDirection.Vertical, 3, 15, new Vector2(100, 100)); } - #endregion + #endregion - #endregion + #region ContentScrollSnapHorizontal + [MenuItem("GameObject/UI/Extensions/Content Scroll Snap Horizontal", false)] + static public void AddContentScrollSnapHorizontal(MenuCommand menuCommand) + { + GameObject contentScrollSnapRoot = CreateUIElementRoot("Content Scroll Snap Horizontal", menuCommand, s_ThickGUIElementSize); - #region UIVertical Scroller - [MenuItem("GameObject/UI/Extensions/UI Vertical Scroller", false)] + GameObject childContent = CreateUIObject("Content", contentScrollSnapRoot); + + GameObject childPage01 = CreateUIObject("Position 1", childContent); + + GameObject childPage02 = CreateUIObject("Position 2", childContent); + + GameObject childPage03 = CreateUIObject("Position 3", childContent); + + GameObject childPage04 = CreateUIObject("Position 4", childContent); + + GameObject childPage05 = CreateUIObject("Position 5", childContent); + + //setup root + RectTransform contentScrollSnapRectTransform = ((RectTransform)contentScrollSnapRoot.transform); + contentScrollSnapRectTransform.anchorMin = new Vector2(0.5f, 0.5f); + contentScrollSnapRectTransform.anchorMax = new Vector2(0.5f, 0.5f); + contentScrollSnapRectTransform.anchoredPosition = Vector2.zero; + contentScrollSnapRectTransform.sizeDelta = new Vector2(100, 200); + + Image image = contentScrollSnapRoot.AddComponent(); + image.sprite = null; + image.color = new Color(1, 0, 0, .5f); + + ScrollRect sr = contentScrollSnapRoot.AddComponent(); + sr.vertical = false; + sr.horizontal = true; + + //setup content container + RectTransform contentTransform = ((RectTransform)childContent.transform); + contentTransform.anchorMin = new Vector2(.5f, .5f); + contentTransform.anchorMax = new Vector2(.5f, .5f); + contentTransform.pivot = new Vector2(.5f, .5f); + contentTransform.sizeDelta = new Vector2(200, 300); + + Image contentImage = childContent.AddComponent(); + contentImage.sprite = null; + contentImage.color = new Color(0, 0, 1, .5f); + + sr.content = contentTransform; + + //setup child 1 + RectTransform childPage01Transform = (RectTransform)childPage01.transform; + childPage01Transform.anchorMin = new Vector2(0, 1); + childPage01Transform.anchorMax = new Vector2(0, 1); + childPage01Transform.pivot = new Vector2(0, 1); + childPage01Transform.anchoredPosition = new Vector2(0, -125); + + Image childPage01Image = childPage01.AddComponent(); + childPage01Image.sprite = null; + childPage01Image.color = Color.white; + + //setup child 2 + RectTransform childPage02Transform = (RectTransform)childPage02.transform; + childPage02Transform.anchorMin = new Vector2(0, 1); + childPage02Transform.anchorMax = new Vector2(0, 1); + childPage02Transform.pivot = new Vector2(0, 1); + childPage02Transform.anchoredPosition = new Vector2(175, -150); + + Image childPage02Image = childPage02.AddComponent(); + childPage02Image.sprite = null; + childPage02Image.color = Color.white; + + //setup child 3 + RectTransform childPage03Transform = (RectTransform)childPage03.transform; + childPage03Transform.anchorMin = new Vector2(0, 1); + childPage03Transform.anchorMax = new Vector2(0, 1); + childPage03Transform.pivot = new Vector2(0, 1); + childPage03Transform.anchoredPosition = new Vector2(315, -125); + childPage03Transform.sizeDelta = new Vector2(50, 100); + + Image childPage03Image = childPage03.AddComponent(); + childPage03Image.sprite = null; + childPage03Image.color = Color.white; + + //setup child 4 + RectTransform childPage04Transform = (RectTransform)childPage04.transform; + childPage04Transform.anchorMin = new Vector2(0, 1); + childPage04Transform.anchorMax = new Vector2(0, 1); + childPage04Transform.pivot = new Vector2(0, 1); + childPage04Transform.anchoredPosition = new Vector2(490, -110); + + Image childPage04Image = childPage04.AddComponent(); + childPage04Image.sprite = null; + childPage04Image.color = Color.white; + + //setup child 5 + RectTransform childPage05Transform = (RectTransform)childPage05.transform; + childPage05Transform.anchorMin = new Vector2(0, 1); + childPage05Transform.anchorMax = new Vector2(0, 1); + childPage05Transform.pivot = new Vector2(0, 1); + childPage05Transform.anchoredPosition = new Vector2(630, -180); + + Image childPage05Image = childPage05.AddComponent(); + childPage05Image.sprite = null; + childPage05Image.color = Color.white; + + //add scroll snap after we've added the content & items + contentScrollSnapRoot.AddComponent(); + } + #endregion + + #endregion + + #region UIVertical Scroller + [MenuItem("GameObject/UI/Extensions/UI Vertical Scroller", false)] static public void AddUIVerticallScroller(MenuCommand menuCommand) { GameObject uiVerticalScrollerRoot = CreateUIElementRoot("UI Vertical Scroller", menuCommand, s_ThickGUIElementSize); diff --git a/Examples/HSS-VSS-ScrollSnap/ContentSnapScrollExample.unity b/Examples/HSS-VSS-ScrollSnap/ContentSnapScrollExample.unity new file mode 100644 index 0000000..3500e65 Binary files /dev/null and b/Examples/HSS-VSS-ScrollSnap/ContentSnapScrollExample.unity differ diff --git a/Examples/HSS-VSS-ScrollSnap/ContentSnapScrollExample.unity.meta b/Examples/HSS-VSS-ScrollSnap/ContentSnapScrollExample.unity.meta new file mode 100644 index 0000000..b377fbc --- /dev/null +++ b/Examples/HSS-VSS-ScrollSnap/ContentSnapScrollExample.unity.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 0749bfbb194c58948abd4f5d4c46b6f6 +timeCreated: 1511210771 +licenseType: Free +DefaultImporter: + userData: + assetBundleName: + assetBundleVariant: diff --git a/Scripts/Layout/ContentScrollSnapHorizontal.cs b/Scripts/Layout/ContentScrollSnapHorizontal.cs new file mode 100644 index 0000000..c02f57d --- /dev/null +++ b/Scripts/Layout/ContentScrollSnapHorizontal.cs @@ -0,0 +1,612 @@ +using System.Collections; +using System.Collections.Generic; +using UnityEngine.EventSystems; +using UnityEngine.Events; +using System; + +namespace UnityEngine.UI.Extensions +{ + [ExecuteInEditMode] + [RequireComponent(typeof(ScrollRect))] + [AddComponentMenu("UI/Extensions/ContentSnapScrollHorizontal")] + public class ContentScrollSnapHorizontal : MonoBehaviour, IBeginDragHandler, IEndDragHandler + { + + [Serializable] + public class StartMovementEvent : UnityEvent { } + [Serializable] + public class CurrentItemChangeEvent : UnityEvent { } + [Serializable] + public class FoundItemToSnapToEvent : UnityEvent { } + [Serializable] + public class SnappedToItemEvent : UnityEvent { } + + public bool ignoreInactiveItems = true; + public MoveInfo startInfo = new MoveInfo(MoveInfo.IndexType.positionIndex, 0); + public GameObject prevButton; + public GameObject nextButton; + public GameObject pagination; + [Tooltip("The velocity below which the scroll rect will start to snap")] + public int snappingVelocityThreshold = 50; + + [Header("Paging Info")] + [Tooltip("Should the pagination & buttons jump or lerp to the items")] + public bool jumpToItem = false; + [Tooltip("The time it will take for the pagination or buttons to move between items")] + public float lerpTime = .3f; + + [Header("Events")] + [SerializeField] + [Tooltip("Event is triggered whenever the scroll rect starts to move, even when triggered programatically")] + private StartMovementEvent m_StartMovementEvent = new StartMovementEvent(); + public StartMovementEvent startMovementEvent + { + get + { + return m_StartMovementEvent; + } + set + { + m_StartMovementEvent = value; + } + } + + [SerializeField] + [Tooltip("Event is triggered whenever the closest item to the center of the scrollrect changes")] + private CurrentItemChangeEvent m_CurrentItemChangeEvent = new CurrentItemChangeEvent(); + public CurrentItemChangeEvent currentItemChangeEvent + { + get + { + return m_CurrentItemChangeEvent; + } + set + { + m_CurrentItemChangeEvent = value; + } + } + + [SerializeField] + [Tooltip("Event is triggered when the ContentSnapScroll decides which item it is going to snap to. Returns the index of the closest position.")] + private FoundItemToSnapToEvent m_FoundItemToSnapToEvent = new FoundItemToSnapToEvent(); + public FoundItemToSnapToEvent foundItemToSnapToEvent + { + get + { + return m_FoundItemToSnapToEvent; + } + set + { + m_FoundItemToSnapToEvent = value; + } + } + + [SerializeField] + [Tooltip("Event is triggered when we finally settle on an element. Returns the index of the item's position.")] + private SnappedToItemEvent m_SnappedToItemEvent = new SnappedToItemEvent(); + public SnappedToItemEvent snappedToItemEvent + { + get + { + return m_SnappedToItemEvent; + } + set + { + m_SnappedToItemEvent = value; + } + } + + private ScrollRect scrollRect; + private RectTransform scrollRectTransform; + private RectTransform contentTransform; + private List contentPositions; + private Vector3 lerpTarget; + private float totalScrollableWidth; + private DrivenRectTransformTracker tracker; + private bool lerp; + private bool pointerDown; + private float mLerpTime; + private int _closestItem; + private bool lerpToContentRunning; + private bool mSliding; + private bool mLerping; + private bool contentIsHorizonalLayoutGroup + { + get + { + return contentTransform.GetComponent() != null; + } + } + + #region Public Info + /// + /// Returns if the SnapScroll is moving + /// + public bool moving + { + get + { + return sliding || lerping; + } + } + + /// + /// Returns if the SnapScroll is moving because of a touch + /// + public bool sliding + { + get + { + return mSliding; + } + } + /// + /// Returns if the SnapScroll is moving programmatically + /// + public bool lerping + { + get + { + return mLerping; + } + } + + /// + /// Returns the closest item's index + /// *Note this is zero based, and based on position not child order + /// + public int closestItemIndex + { + get + { + return contentPositions.IndexOf(FindClosestFrom(contentTransform.localPosition)); + } + } + /// + /// Returns the lerpTarget's index + /// *Note this is zero-based, and based on position not child order + /// + public int lerpTargetIndex + { + get + { + return contentPositions.IndexOf(lerpTarget); + } + } + #endregion + + #region Setup + private void Awake() + { + scrollRect = GetComponent(); + scrollRectTransform = (RectTransform) scrollRect.transform; + contentTransform = scrollRect.content; + + if (nextButton) + nextButton.GetComponent