diff options
Diffstat (limited to 'Assets/uGUI-2017.1/UnityEngine.UI/UI/Core/UIControls/ScrollRect.cs')
-rw-r--r-- | Assets/uGUI-2017.1/UnityEngine.UI/UI/Core/UIControls/ScrollRect.cs | 873 |
1 files changed, 873 insertions, 0 deletions
diff --git a/Assets/uGUI-2017.1/UnityEngine.UI/UI/Core/UIControls/ScrollRect.cs b/Assets/uGUI-2017.1/UnityEngine.UI/UI/Core/UIControls/ScrollRect.cs new file mode 100644 index 0000000..106f15c --- /dev/null +++ b/Assets/uGUI-2017.1/UnityEngine.UI/UI/Core/UIControls/ScrollRect.cs @@ -0,0 +1,873 @@ +using System; +using UnityEngine.Events; +using UnityEngine.EventSystems; + +namespace UnityEngine.UI +{ + [AddComponentMenu("UI/Scroll Rect", 37)] + [SelectionBase] + [ExecuteInEditMode] + [DisallowMultipleComponent] + [RequireComponent(typeof(RectTransform))] + public class ScrollRect + : UIBehaviour + , IInitializePotentialDragHandler + , IBeginDragHandler + , IEndDragHandler + , IDragHandler + , IScrollHandler + , ICanvasElement + , ILayoutElement + , ILayoutGroup + { + public enum MovementType + { + Unrestricted, // Unrestricted movement -- can scroll forever + Elastic, // Restricted but flexible -- can go past the edges, but springs back in place + Clamped, // Restricted movement where it's not possible to go past the edges + } + + public enum ScrollbarVisibility + { + Permanent, + AutoHide, + AutoHideAndExpandViewport, + } + + [Serializable] + public class ScrollRectEvent : UnityEvent<Vector2> {} + + [SerializeField] + private RectTransform m_Content; + public RectTransform content { get { return m_Content; } set { m_Content = value; } } + + [SerializeField] + private bool m_Horizontal = true; + public bool horizontal { get { return m_Horizontal; } set { m_Horizontal = value; } } + + [SerializeField] + private bool m_Vertical = true; + public bool vertical { get { return m_Vertical; } set { m_Vertical = value; } } + + [SerializeField] + private MovementType m_MovementType = MovementType.Elastic; + public MovementType movementType { get { return m_MovementType; } set { m_MovementType = value; } } + + [SerializeField] + private float m_Elasticity = 0.1f; // Only used for MovementType.Elastic + public float elasticity { get { return m_Elasticity; } set { m_Elasticity = value; } } + + [SerializeField] + private bool m_Inertia = true; + public bool inertia { get { return m_Inertia; } set { m_Inertia = value; } } + + [SerializeField] + private float m_DecelerationRate = 0.135f; // Only used when inertia is enabled + public float decelerationRate { get { return m_DecelerationRate; } set { m_DecelerationRate = value; } } + + [SerializeField] + private float m_ScrollSensitivity = 1.0f; + public float scrollSensitivity { get { return m_ScrollSensitivity; } set { m_ScrollSensitivity = value; } } + + [SerializeField] + private RectTransform m_Viewport; + public RectTransform viewport { get { return m_Viewport; } set { m_Viewport = value; SetDirtyCaching(); } } + + [SerializeField] + private Scrollbar m_HorizontalScrollbar; + public Scrollbar horizontalScrollbar + { + get + { + return m_HorizontalScrollbar; + } + set + { + if (m_HorizontalScrollbar) + m_HorizontalScrollbar.onValueChanged.RemoveListener(SetHorizontalNormalizedPosition); + m_HorizontalScrollbar = value; + if (m_HorizontalScrollbar) + m_HorizontalScrollbar.onValueChanged.AddListener(SetHorizontalNormalizedPosition); + SetDirtyCaching(); + } + } + + [SerializeField] + private Scrollbar m_VerticalScrollbar; + public Scrollbar verticalScrollbar + { + get + { + return m_VerticalScrollbar; + } + set + { + if (m_VerticalScrollbar) + m_VerticalScrollbar.onValueChanged.RemoveListener(SetVerticalNormalizedPosition); + m_VerticalScrollbar = value; + if (m_VerticalScrollbar) + m_VerticalScrollbar.onValueChanged.AddListener(SetVerticalNormalizedPosition); + SetDirtyCaching(); + } + } + + [SerializeField] + private ScrollbarVisibility m_HorizontalScrollbarVisibility; + public ScrollbarVisibility horizontalScrollbarVisibility { get { return m_HorizontalScrollbarVisibility; } set { m_HorizontalScrollbarVisibility = value; SetDirtyCaching(); } } + + [SerializeField] + private ScrollbarVisibility m_VerticalScrollbarVisibility; + public ScrollbarVisibility verticalScrollbarVisibility { get { return m_VerticalScrollbarVisibility; } set { m_VerticalScrollbarVisibility = value; SetDirtyCaching(); } } + + [SerializeField] + private float m_HorizontalScrollbarSpacing; + public float horizontalScrollbarSpacing { get { return m_HorizontalScrollbarSpacing; } set { m_HorizontalScrollbarSpacing = value; SetDirty(); } } + + [SerializeField] + private float m_VerticalScrollbarSpacing; + public float verticalScrollbarSpacing { get { return m_VerticalScrollbarSpacing; } set { m_VerticalScrollbarSpacing = value; SetDirty(); } } + + [SerializeField] + private ScrollRectEvent m_OnValueChanged = new ScrollRectEvent(); + public ScrollRectEvent onValueChanged { get { return m_OnValueChanged; } set { m_OnValueChanged = value; } } + + // The offset from handle position to mouse down position + private Vector2 m_PointerStartLocalCursor = Vector2.zero; + protected Vector2 m_ContentStartPosition = Vector2.zero; + + private RectTransform m_ViewRect; + + protected RectTransform viewRect + { + get + { + if (m_ViewRect == null) + m_ViewRect = m_Viewport; + if (m_ViewRect == null) + m_ViewRect = (RectTransform)transform; + return m_ViewRect; + } + } + + protected Bounds m_ContentBounds; + private Bounds m_ViewBounds; + + private Vector2 m_Velocity; + public Vector2 velocity { get { return m_Velocity; } set { m_Velocity = value; } } + + private bool m_Dragging; + + private Vector2 m_PrevPosition = Vector2.zero; + private Bounds m_PrevContentBounds; + private Bounds m_PrevViewBounds; + [NonSerialized] + private bool m_HasRebuiltLayout = false; + + private bool m_HSliderExpand; + private bool m_VSliderExpand; + private float m_HSliderHeight; + private float m_VSliderWidth; + + [System.NonSerialized] private RectTransform m_Rect; + private RectTransform rectTransform + { + get + { + if (m_Rect == null) + m_Rect = GetComponent<RectTransform>(); + return m_Rect; + } + } + + private RectTransform m_HorizontalScrollbarRect; + private RectTransform m_VerticalScrollbarRect; + + private DrivenRectTransformTracker m_Tracker; + + protected ScrollRect() + {} + + public virtual void Rebuild(CanvasUpdate executing) + { + if (executing == CanvasUpdate.Prelayout) + { + UpdateCachedData(); + } + + if (executing == CanvasUpdate.PostLayout) + { + UpdateBounds(); + UpdateScrollbars(Vector2.zero); + UpdatePrevData(); + + m_HasRebuiltLayout = true; + } + } + + public virtual void LayoutComplete() + {} + + public virtual void GraphicUpdateComplete() + {} + + void UpdateCachedData() + { + Transform transform = this.transform; + m_HorizontalScrollbarRect = m_HorizontalScrollbar == null ? null : m_HorizontalScrollbar.transform as RectTransform; + m_VerticalScrollbarRect = m_VerticalScrollbar == null ? null : m_VerticalScrollbar.transform as RectTransform; + + // These are true if either the elements are children, or they don't exist at all. + bool viewIsChild = (viewRect.parent == transform); + bool hScrollbarIsChild = (!m_HorizontalScrollbarRect || m_HorizontalScrollbarRect.parent == transform); + bool vScrollbarIsChild = (!m_VerticalScrollbarRect || m_VerticalScrollbarRect.parent == transform); + bool allAreChildren = (viewIsChild && hScrollbarIsChild && vScrollbarIsChild); + + m_HSliderExpand = allAreChildren && m_HorizontalScrollbarRect && horizontalScrollbarVisibility == ScrollbarVisibility.AutoHideAndExpandViewport; + m_VSliderExpand = allAreChildren && m_VerticalScrollbarRect && verticalScrollbarVisibility == ScrollbarVisibility.AutoHideAndExpandViewport; + m_HSliderHeight = (m_HorizontalScrollbarRect == null ? 0 : m_HorizontalScrollbarRect.rect.height); + m_VSliderWidth = (m_VerticalScrollbarRect == null ? 0 : m_VerticalScrollbarRect.rect.width); + } + + protected override void OnEnable() + { + base.OnEnable(); + + if (m_HorizontalScrollbar) + m_HorizontalScrollbar.onValueChanged.AddListener(SetHorizontalNormalizedPosition); + if (m_VerticalScrollbar) + m_VerticalScrollbar.onValueChanged.AddListener(SetVerticalNormalizedPosition); + + CanvasUpdateRegistry.RegisterCanvasElementForLayoutRebuild(this); + } + + protected override void OnDisable() + { + CanvasUpdateRegistry.UnRegisterCanvasElementForRebuild(this); + + if (m_HorizontalScrollbar) + m_HorizontalScrollbar.onValueChanged.RemoveListener(SetHorizontalNormalizedPosition); + if (m_VerticalScrollbar) + m_VerticalScrollbar.onValueChanged.RemoveListener(SetVerticalNormalizedPosition); + + m_HasRebuiltLayout = false; + m_Tracker.Clear(); + m_Velocity = Vector2.zero; + LayoutRebuilder.MarkLayoutForRebuild(rectTransform); + base.OnDisable(); + } + + public override bool IsActive() + { + return base.IsActive() && m_Content != null; + } + + private void EnsureLayoutHasRebuilt() + { + if (!m_HasRebuiltLayout && !CanvasUpdateRegistry.IsRebuildingLayout()) + Canvas.ForceUpdateCanvases(); + } + + public virtual void StopMovement() + { + m_Velocity = Vector2.zero; + } + + public virtual void OnScroll(PointerEventData data) + { + if (!IsActive()) + return; + + EnsureLayoutHasRebuilt(); + UpdateBounds(); + + Vector2 delta = data.scrollDelta; + // Down is positive for scroll events, while in UI system up is positive. + delta.y *= -1; + if (vertical && !horizontal) + { + if (Mathf.Abs(delta.x) > Mathf.Abs(delta.y)) + delta.y = delta.x; + delta.x = 0; + } + if (horizontal && !vertical) + { + if (Mathf.Abs(delta.y) > Mathf.Abs(delta.x)) + delta.x = delta.y; + delta.y = 0; + } + + Vector2 position = m_Content.anchoredPosition; + position += delta * m_ScrollSensitivity; + if (m_MovementType == MovementType.Clamped) + position += CalculateOffset(position - m_Content.anchoredPosition); + + SetContentAnchoredPosition(position); + UpdateBounds(); + } + + public virtual void OnInitializePotentialDrag(PointerEventData eventData) + { + if (eventData.button != PointerEventData.InputButton.Left) + return; + + m_Velocity = Vector2.zero; + } + + public virtual void OnBeginDrag(PointerEventData eventData) + { + if (eventData.button != PointerEventData.InputButton.Left) + return; + + if (!IsActive()) + return; + + UpdateBounds(); + + m_PointerStartLocalCursor = Vector2.zero; + RectTransformUtility.ScreenPointToLocalPointInRectangle(viewRect, eventData.position, eventData.pressEventCamera, out m_PointerStartLocalCursor); + m_ContentStartPosition = m_Content.anchoredPosition; + m_Dragging = true; + } + + public virtual void OnEndDrag(PointerEventData eventData) + { + if (eventData.button != PointerEventData.InputButton.Left) + return; + + m_Dragging = false; + } + + public virtual void OnDrag(PointerEventData eventData) + { + if (eventData.button != PointerEventData.InputButton.Left) + return; + + if (!IsActive()) + return; + + Vector2 localCursor; + if (!RectTransformUtility.ScreenPointToLocalPointInRectangle(viewRect, eventData.position, eventData.pressEventCamera, out localCursor)) + return; + + UpdateBounds(); + + var pointerDelta = localCursor - m_PointerStartLocalCursor; + Vector2 position = m_ContentStartPosition + pointerDelta; + + // Offset to get content into place in the view. + Vector2 offset = CalculateOffset(position - m_Content.anchoredPosition); + position += offset; + if (m_MovementType == MovementType.Elastic) + { + if (offset.x != 0) + position.x = position.x - RubberDelta(offset.x, m_ViewBounds.size.x); + if (offset.y != 0) + position.y = position.y - RubberDelta(offset.y, m_ViewBounds.size.y); + } + + SetContentAnchoredPosition(position); + } + + protected virtual void SetContentAnchoredPosition(Vector2 position) + { + if (!m_Horizontal) + position.x = m_Content.anchoredPosition.x; + if (!m_Vertical) + position.y = m_Content.anchoredPosition.y; + + if (position != m_Content.anchoredPosition) + { + m_Content.anchoredPosition = position; + UpdateBounds(); + } + } + + protected virtual void LateUpdate() + { + if (!m_Content) + return; + + EnsureLayoutHasRebuilt(); + UpdateScrollbarVisibility(); + UpdateBounds(); + float deltaTime = Time.unscaledDeltaTime; + Vector2 offset = CalculateOffset(Vector2.zero); + if (!m_Dragging && (offset != Vector2.zero || m_Velocity != Vector2.zero)) + { + Vector2 position = m_Content.anchoredPosition; + for (int axis = 0; axis < 2; axis++) + { + // Apply spring physics if movement is elastic and content has an offset from the view. + if (m_MovementType == MovementType.Elastic && offset[axis] != 0) + { + float speed = m_Velocity[axis]; + position[axis] = Mathf.SmoothDamp(m_Content.anchoredPosition[axis], m_Content.anchoredPosition[axis] + offset[axis], ref speed, m_Elasticity, Mathf.Infinity, deltaTime); + if (Mathf.Abs(speed) < 1) + speed = 0; + m_Velocity[axis] = speed; + } + // Else move content according to velocity with deceleration applied. + else if (m_Inertia) + { + m_Velocity[axis] *= Mathf.Pow(m_DecelerationRate, deltaTime); + if (Mathf.Abs(m_Velocity[axis]) < 1) + m_Velocity[axis] = 0; + position[axis] += m_Velocity[axis] * deltaTime; + } + // If we have neither elaticity or friction, there shouldn't be any velocity. + else + { + m_Velocity[axis] = 0; + } + } + + if (m_Velocity != Vector2.zero) + { + if (m_MovementType == MovementType.Clamped) + { + offset = CalculateOffset(position - m_Content.anchoredPosition); + position += offset; + } + + SetContentAnchoredPosition(position); + } + } + + if (m_Dragging && m_Inertia) + { + Vector3 newVelocity = (m_Content.anchoredPosition - m_PrevPosition) / deltaTime; + m_Velocity = Vector3.Lerp(m_Velocity, newVelocity, deltaTime * 10); + } + + if (m_ViewBounds != m_PrevViewBounds || m_ContentBounds != m_PrevContentBounds || m_Content.anchoredPosition != m_PrevPosition) + { + UpdateScrollbars(offset); + UISystemProfilerApi.AddMarker("ScrollRect.value", this); + m_OnValueChanged.Invoke(normalizedPosition); + UpdatePrevData(); + } + } + + protected void UpdatePrevData() + { + if (m_Content == null) + m_PrevPosition = Vector2.zero; + else + m_PrevPosition = m_Content.anchoredPosition; + m_PrevViewBounds = m_ViewBounds; + m_PrevContentBounds = m_ContentBounds; + } + + private void UpdateScrollbars(Vector2 offset) + { + if (m_HorizontalScrollbar) + { + if (m_ContentBounds.size.x > 0) + m_HorizontalScrollbar.size = Mathf.Clamp01((m_ViewBounds.size.x - Mathf.Abs(offset.x)) / m_ContentBounds.size.x); + else + m_HorizontalScrollbar.size = 1; + + m_HorizontalScrollbar.value = horizontalNormalizedPosition; + } + + if (m_VerticalScrollbar) + { + if (m_ContentBounds.size.y > 0) + m_VerticalScrollbar.size = Mathf.Clamp01((m_ViewBounds.size.y - Mathf.Abs(offset.y)) / m_ContentBounds.size.y); + else + m_VerticalScrollbar.size = 1; + + m_VerticalScrollbar.value = verticalNormalizedPosition; + } + } + + public Vector2 normalizedPosition + { + get + { + return new Vector2(horizontalNormalizedPosition, verticalNormalizedPosition); + } + set + { + SetNormalizedPosition(value.x, 0); + SetNormalizedPosition(value.y, 1); + } + } + + public float horizontalNormalizedPosition + { + get + { + UpdateBounds(); + if (m_ContentBounds.size.x <= m_ViewBounds.size.x) + return (m_ViewBounds.min.x > m_ContentBounds.min.x) ? 1 : 0; + return (m_ViewBounds.min.x - m_ContentBounds.min.x) / (m_ContentBounds.size.x - m_ViewBounds.size.x); + } + set + { + SetNormalizedPosition(value, 0); + } + } + + public float verticalNormalizedPosition + { + get + { + UpdateBounds(); + if (m_ContentBounds.size.y <= m_ViewBounds.size.y) + return (m_ViewBounds.min.y > m_ContentBounds.min.y) ? 1 : 0; + ; + return (m_ViewBounds.min.y - m_ContentBounds.min.y) / (m_ContentBounds.size.y - m_ViewBounds.size.y); + } + set + { + SetNormalizedPosition(value, 1); + } + } + + private void SetHorizontalNormalizedPosition(float value) { SetNormalizedPosition(value, 0); } + private void SetVerticalNormalizedPosition(float value) { SetNormalizedPosition(value, 1); } + + protected virtual void SetNormalizedPosition(float value, int axis) + { + EnsureLayoutHasRebuilt(); + UpdateBounds(); + // How much the content is larger than the view. + float hiddenLength = m_ContentBounds.size[axis] - m_ViewBounds.size[axis]; + // Where the position of the lower left corner of the content bounds should be, in the space of the view. + float contentBoundsMinPosition = m_ViewBounds.min[axis] - value * hiddenLength; + // The new content localPosition, in the space of the view. + float newLocalPosition = m_Content.localPosition[axis] + contentBoundsMinPosition - m_ContentBounds.min[axis]; + + Vector3 localPosition = m_Content.localPosition; + if (Mathf.Abs(localPosition[axis] - newLocalPosition) > 0.01f) + { + localPosition[axis] = newLocalPosition; + m_Content.localPosition = localPosition; + m_Velocity[axis] = 0; + UpdateBounds(); + } + } + + private static float RubberDelta(float overStretching, float viewSize) + { + return (1 - (1 / ((Mathf.Abs(overStretching) * 0.55f / viewSize) + 1))) * viewSize * Mathf.Sign(overStretching); + } + + protected override void OnRectTransformDimensionsChange() + { + SetDirty(); + } + + private bool hScrollingNeeded + { + get + { + if (Application.isPlaying) + return m_ContentBounds.size.x > m_ViewBounds.size.x + 0.01f; + return true; + } + } + private bool vScrollingNeeded + { + get + { + if (Application.isPlaying) + return m_ContentBounds.size.y > m_ViewBounds.size.y + 0.01f; + return true; + } + } + + public virtual void CalculateLayoutInputHorizontal() {} + public virtual void CalculateLayoutInputVertical() {} + + public virtual float minWidth { get { return -1; } } + public virtual float preferredWidth { get { return -1; } } + public virtual float flexibleWidth { get { return -1; } } + + public virtual float minHeight { get { return -1; } } + public virtual float preferredHeight { get { return -1; } } + public virtual float flexibleHeight { get { return -1; } } + + public virtual int layoutPriority { get { return -1; } } + + public virtual void SetLayoutHorizontal() + { + m_Tracker.Clear(); + + if (m_HSliderExpand || m_VSliderExpand) + { + m_Tracker.Add(this, viewRect, + DrivenTransformProperties.Anchors | + DrivenTransformProperties.SizeDelta | + DrivenTransformProperties.AnchoredPosition); + + // Make view full size to see if content fits. + viewRect.anchorMin = Vector2.zero; + viewRect.anchorMax = Vector2.one; + viewRect.sizeDelta = Vector2.zero; + viewRect.anchoredPosition = Vector2.zero; + + // Recalculate content layout with this size to see if it fits when there are no scrollbars. + LayoutRebuilder.ForceRebuildLayoutImmediate(content); + m_ViewBounds = new Bounds(viewRect.rect.center, viewRect.rect.size); + m_ContentBounds = GetBounds(); + } + + // If it doesn't fit vertically, enable vertical scrollbar and shrink view horizontally to make room for it. + if (m_VSliderExpand && vScrollingNeeded) + { + viewRect.sizeDelta = new Vector2(-(m_VSliderWidth + m_VerticalScrollbarSpacing), viewRect.sizeDelta.y); + + // Recalculate content layout with this size to see if it fits vertically + // when there is a vertical scrollbar (which may reflowed the content to make it taller). + LayoutRebuilder.ForceRebuildLayoutImmediate(content); + m_ViewBounds = new Bounds(viewRect.rect.center, viewRect.rect.size); + m_ContentBounds = GetBounds(); + } + + // If it doesn't fit horizontally, enable horizontal scrollbar and shrink view vertically to make room for it. + if (m_HSliderExpand && hScrollingNeeded) + { + viewRect.sizeDelta = new Vector2(viewRect.sizeDelta.x, -(m_HSliderHeight + m_HorizontalScrollbarSpacing)); + m_ViewBounds = new Bounds(viewRect.rect.center, viewRect.rect.size); + m_ContentBounds = GetBounds(); + } + + // If the vertical slider didn't kick in the first time, and the horizontal one did, + // we need to check again if the vertical slider now needs to kick in. + // If it doesn't fit vertically, enable vertical scrollbar and shrink view horizontally to make room for it. + if (m_VSliderExpand && vScrollingNeeded && viewRect.sizeDelta.x == 0 && viewRect.sizeDelta.y < 0) + { + viewRect.sizeDelta = new Vector2(-(m_VSliderWidth + m_VerticalScrollbarSpacing), viewRect.sizeDelta.y); + } + } + + public virtual void SetLayoutVertical() + { + UpdateScrollbarLayout(); + m_ViewBounds = new Bounds(viewRect.rect.center, viewRect.rect.size); + m_ContentBounds = GetBounds(); + } + + void UpdateScrollbarVisibility() + { + UpdateOneScrollbarVisibility(vScrollingNeeded, m_Vertical, m_VerticalScrollbarVisibility, m_VerticalScrollbar); + UpdateOneScrollbarVisibility(hScrollingNeeded, m_Horizontal, m_HorizontalScrollbarVisibility, m_HorizontalScrollbar); + } + + private static void UpdateOneScrollbarVisibility(bool xScrollingNeeded, bool xAxisEnabled, ScrollbarVisibility scrollbarVisibility, Scrollbar scrollbar) + { + if (scrollbar) + { + if (scrollbarVisibility == ScrollbarVisibility.Permanent) + { + if (scrollbar.gameObject.activeSelf != xAxisEnabled) + scrollbar.gameObject.SetActive(xAxisEnabled); + } + else + { + if (scrollbar.gameObject.activeSelf != xScrollingNeeded) + scrollbar.gameObject.SetActive(xScrollingNeeded); + } + } + } + + void UpdateScrollbarLayout() + { + if (m_VSliderExpand && m_HorizontalScrollbar) + { + m_Tracker.Add(this, m_HorizontalScrollbarRect, + DrivenTransformProperties.AnchorMinX | + DrivenTransformProperties.AnchorMaxX | + DrivenTransformProperties.SizeDeltaX | + DrivenTransformProperties.AnchoredPositionX); + m_HorizontalScrollbarRect.anchorMin = new Vector2(0, m_HorizontalScrollbarRect.anchorMin.y); + m_HorizontalScrollbarRect.anchorMax = new Vector2(1, m_HorizontalScrollbarRect.anchorMax.y); + m_HorizontalScrollbarRect.anchoredPosition = new Vector2(0, m_HorizontalScrollbarRect.anchoredPosition.y); + if (vScrollingNeeded) + m_HorizontalScrollbarRect.sizeDelta = new Vector2(-(m_VSliderWidth + m_VerticalScrollbarSpacing), m_HorizontalScrollbarRect.sizeDelta.y); + else + m_HorizontalScrollbarRect.sizeDelta = new Vector2(0, m_HorizontalScrollbarRect.sizeDelta.y); + } + + if (m_HSliderExpand && m_VerticalScrollbar) + { + m_Tracker.Add(this, m_VerticalScrollbarRect, + DrivenTransformProperties.AnchorMinY | + DrivenTransformProperties.AnchorMaxY | + DrivenTransformProperties.SizeDeltaY | + DrivenTransformProperties.AnchoredPositionY); + m_VerticalScrollbarRect.anchorMin = new Vector2(m_VerticalScrollbarRect.anchorMin.x, 0); + m_VerticalScrollbarRect.anchorMax = new Vector2(m_VerticalScrollbarRect.anchorMax.x, 1); + m_VerticalScrollbarRect.anchoredPosition = new Vector2(m_VerticalScrollbarRect.anchoredPosition.x, 0); + if (hScrollingNeeded) + m_VerticalScrollbarRect.sizeDelta = new Vector2(m_VerticalScrollbarRect.sizeDelta.x, -(m_HSliderHeight + m_HorizontalScrollbarSpacing)); + else + m_VerticalScrollbarRect.sizeDelta = new Vector2(m_VerticalScrollbarRect.sizeDelta.x, 0); + } + } + + protected void UpdateBounds() + { + m_ViewBounds = new Bounds(viewRect.rect.center, viewRect.rect.size); + m_ContentBounds = GetBounds(); + + if (m_Content == null) + return; + + Vector3 contentSize = m_ContentBounds.size; + Vector3 contentPos = m_ContentBounds.center; + var contentPivot = m_Content.pivot; + AdjustBounds(ref m_ViewBounds, ref contentPivot, ref contentSize, ref contentPos); + m_ContentBounds.size = contentSize; + m_ContentBounds.center = contentPos; + + if (movementType == MovementType.Clamped) + { + // Adjust content so that content bounds bottom (right side) is never higher (to the left) than the view bounds bottom (right side). + // top (left side) is never lower (to the right) than the view bounds top (left side). + // All this can happen if content has shrunk. + // This works because content size is at least as big as view size (because of the call to InternalUpdateBounds above). + Vector2 delta = Vector2.zero; + if (m_ViewBounds.max.x > m_ContentBounds.max.x) + { + delta.x = Math.Min(m_ViewBounds.min.x - m_ContentBounds.min.x, m_ViewBounds.max.x - m_ContentBounds.max.x); + } + else if (m_ViewBounds.min.x < m_ContentBounds.min.x) + { + delta.x = Math.Max(m_ViewBounds.min.x - m_ContentBounds.min.x, m_ViewBounds.max.x - m_ContentBounds.max.x); + } + + if (m_ViewBounds.min.y < m_ContentBounds.min.y) + { + delta.y = Math.Max(m_ViewBounds.min.y - m_ContentBounds.min.y, m_ViewBounds.max.y - m_ContentBounds.max.y); + } + else if (m_ViewBounds.max.y > m_ContentBounds.max.y) + { + delta.y = Math.Min(m_ViewBounds.min.y - m_ContentBounds.min.y, m_ViewBounds.max.y - m_ContentBounds.max.y); + } + if (delta.sqrMagnitude > float.Epsilon) + { + contentPos = m_Content.anchoredPosition + delta; + if (!m_Horizontal) + contentPos.x = m_Content.anchoredPosition.x; + if (!m_Vertical) + contentPos.y = m_Content.anchoredPosition.y; + AdjustBounds(ref m_ViewBounds, ref contentPivot, ref contentSize, ref contentPos); + } + } + } + + internal static void AdjustBounds(ref Bounds viewBounds, ref Vector2 contentPivot, ref Vector3 contentSize, ref Vector3 contentPos) + { + // Make sure content bounds are at least as large as view by adding padding if not. + // One might think at first that if the content is smaller than the view, scrolling should be allowed. + // However, that's not how scroll views normally work. + // Scrolling is *only* possible when content is *larger* than view. + // We use the pivot of the content rect to decide in which directions the content bounds should be expanded. + // E.g. if pivot is at top, bounds are expanded downwards. + // This also works nicely when ContentSizeFitter is used on the content. + Vector3 excess = viewBounds.size - contentSize; + if (excess.x > 0) + { + contentPos.x -= excess.x * (contentPivot.x - 0.5f); + contentSize.x = viewBounds.size.x; + } + if (excess.y > 0) + { + contentPos.y -= excess.y * (contentPivot.y - 0.5f); + contentSize.y = viewBounds.size.y; + } + } + + private readonly Vector3[] m_Corners = new Vector3[4]; + private Bounds GetBounds() + { + if (m_Content == null) + return new Bounds(); + m_Content.GetWorldCorners(m_Corners); + var viewWorldToLocalMatrix = viewRect.worldToLocalMatrix; + return InternalGetBounds(m_Corners, ref viewWorldToLocalMatrix); + } + + internal static Bounds InternalGetBounds(Vector3[] corners, ref Matrix4x4 viewWorldToLocalMatrix) + { + var vMin = new Vector3(float.MaxValue, float.MaxValue, float.MaxValue); + var vMax = new Vector3(float.MinValue, float.MinValue, float.MinValue); + + for (int j = 0; j < 4; j++) + { + Vector3 v = viewWorldToLocalMatrix.MultiplyPoint3x4(corners[j]); + vMin = Vector3.Min(v, vMin); + vMax = Vector3.Max(v, vMax); + } + + var bounds = new Bounds(vMin, Vector3.zero); + bounds.Encapsulate(vMax); + return bounds; + } + + private Vector2 CalculateOffset(Vector2 delta) + { + return InternalCalculateOffset(ref m_ViewBounds, ref m_ContentBounds, m_Horizontal, m_Vertical, m_MovementType, ref delta); + } + + internal static Vector2 InternalCalculateOffset(ref Bounds viewBounds, ref Bounds contentBounds, bool horizontal, bool vertical, MovementType movementType, ref Vector2 delta) + { + Vector2 offset = Vector2.zero; + if (movementType == MovementType.Unrestricted) + return offset; + + Vector2 min = contentBounds.min; + Vector2 max = contentBounds.max; + + if (horizontal) + { + min.x += delta.x; + max.x += delta.x; + if (min.x > viewBounds.min.x) + offset.x = viewBounds.min.x - min.x; + else if (max.x < viewBounds.max.x) + offset.x = viewBounds.max.x - max.x; + } + + if (vertical) + { + min.y += delta.y; + max.y += delta.y; + if (max.y < viewBounds.max.y) + offset.y = viewBounds.max.y - max.y; + else if (min.y > viewBounds.min.y) + offset.y = viewBounds.min.y - min.y; + } + + return offset; + } + + protected void SetDirty() + { + if (!IsActive()) + return; + + LayoutRebuilder.MarkLayoutForRebuild(rectTransform); + } + + protected void SetDirtyCaching() + { + if (!IsActive()) + return; + + CanvasUpdateRegistry.RegisterCanvasElementForLayoutRebuild(this); + LayoutRebuilder.MarkLayoutForRebuild(rectTransform); + } + + #if UNITY_EDITOR + protected override void OnValidate() + { + SetDirtyCaching(); + } + + #endif + } +} |