1630 lines
46 KiB
C#
1630 lines
46 KiB
C#
// ***********************************************************************
|
|
// Assembly : Unity
|
|
// Author : Kimch
|
|
// Created :
|
|
//
|
|
// Last Modified By : Kimch
|
|
// Last Modified On :
|
|
// ***********************************************************************
|
|
// <copyright file= "KUIGrid" company=""></copyright>
|
|
// <summary></summary>
|
|
// ***********************************************************************
|
|
using System;
|
|
using UnityEngine;
|
|
using UnityEngine.Events;
|
|
using UnityEngine.EventSystems;
|
|
using UnityEngine.UI;
|
|
|
|
[AddComponentMenu("UI/UIGrid", 37)]
|
|
[SelectionBase]
|
|
[ExecuteInEditMode]
|
|
[DisallowMultipleComponent]
|
|
[RequireComponent(typeof(RectTransform))]
|
|
public class KUIGrid : UIBehaviour, IInitializePotentialDragHandler, IBeginDragHandler, IEndDragHandler, IDragHandler, IScrollHandler, ICanvasElement, ILayoutElement, ILayoutGroup
|
|
{
|
|
private const float THRESHOLDS_D = 18F;
|
|
|
|
#region Model
|
|
|
|
public KUIPool uiPool;
|
|
|
|
public int itemCount
|
|
{
|
|
get { return uiPool ? uiPool.itemCount : 0; }
|
|
}
|
|
|
|
private RectTransform CreateItem(int index)
|
|
{
|
|
if (uiPool)
|
|
{
|
|
return uiPool.CreateItem(index);
|
|
}
|
|
return null;
|
|
}
|
|
|
|
private void ReturnItem(Transform item)
|
|
{
|
|
if (uiPool)
|
|
{
|
|
uiPool.ReturnItem(item);
|
|
}
|
|
}
|
|
|
|
protected override void Awake()
|
|
{
|
|
base.Awake();
|
|
if (!uiPool)
|
|
{
|
|
uiPool = content.GetComponent<KUIPool>();
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Controller
|
|
|
|
private float _thresholds = 0f;
|
|
private float _thresholdsMask = THRESHOLDS_D;
|
|
|
|
protected int _beginItemIndex = 0;
|
|
protected int _endItemIndex = 0;
|
|
|
|
protected float GetDimension(Vector2 vector)
|
|
{
|
|
return vertical ? vector.y : vector.x;
|
|
}
|
|
|
|
protected Vector2 GetVector(float value)
|
|
{
|
|
return vertical ? new Vector2(0f, value) : new Vector2(-value, 0f);
|
|
}
|
|
|
|
protected float m_ContentCellSize = float.MinValue;
|
|
protected float contentCellSize
|
|
{
|
|
get
|
|
{
|
|
if (m_ContentCellSize != float.MinValue)
|
|
{
|
|
return m_ContentCellSize;
|
|
}
|
|
m_ContentCellSize = 0f;
|
|
m_ContentSpacing = 0f;
|
|
if (content != null)
|
|
{
|
|
var layoutGroup = content.GetComponent<LayoutGroup>();
|
|
if (layoutGroup)
|
|
{
|
|
if (layoutGroup is GridLayoutGroup gLG)
|
|
{
|
|
m_ContentSpacing = GetDimension(gLG.spacing);
|
|
m_ContentCellSize = GetDimension(gLG.cellSize);
|
|
m_ContentPadding = gLG.padding;
|
|
}
|
|
else if (layoutGroup is HorizontalOrVerticalLayoutGroup hvLG)
|
|
{
|
|
m_ContentSpacing = hvLG.spacing;
|
|
m_ContentPadding = hvLG.padding;
|
|
}
|
|
}
|
|
}
|
|
_thresholdsMask = THRESHOLDS_D - m_ContentSpacing;
|
|
|
|
return m_ContentCellSize;
|
|
}
|
|
}
|
|
|
|
private float m_ContentSpacing = float.MinValue;
|
|
protected float contentSpacing
|
|
{
|
|
get
|
|
{
|
|
if (m_ContentSpacing != float.MinValue)
|
|
{
|
|
return m_ContentSpacing;
|
|
}
|
|
m_ContentCellSize = 0f;
|
|
m_ContentSpacing = 0f;
|
|
if (content != null)
|
|
{
|
|
var layoutGroup = content.GetComponent<LayoutGroup>();
|
|
if (layoutGroup)
|
|
{
|
|
if (layoutGroup is GridLayoutGroup gLG)
|
|
{
|
|
m_ContentSpacing = GetDimension(gLG.spacing);
|
|
m_ContentCellSize = GetDimension(gLG.cellSize);
|
|
m_ContentPadding = gLG.padding;
|
|
}
|
|
else if (layoutGroup is HorizontalOrVerticalLayoutGroup hvLG)
|
|
{
|
|
m_ContentSpacing = hvLG.spacing;
|
|
m_ContentPadding = hvLG.padding;
|
|
}
|
|
}
|
|
}
|
|
_thresholdsMask = THRESHOLDS_D - m_ContentSpacing;
|
|
|
|
return m_ContentSpacing;
|
|
}
|
|
}
|
|
|
|
private RectOffset m_ContentPadding;
|
|
|
|
protected float GetItemSize(RectTransform item)
|
|
{
|
|
float retSize = contentCellSize > 0f ? contentCellSize :
|
|
LayoutUtility.GetPreferredSize(item, vertical ? 1 : 0);
|
|
|
|
retSize += contentSpacing;
|
|
return retSize;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 刷新物品
|
|
/// </summary>
|
|
public void RefreshItems()
|
|
{
|
|
if (this.isActiveAndEnabled)
|
|
{
|
|
_endItemIndex = _beginItemIndex;
|
|
//
|
|
for (int i = 0; i < content.childCount; i++)
|
|
{
|
|
if (_endItemIndex < itemCount)
|
|
{
|
|
_endItemIndex++;
|
|
}
|
|
else
|
|
{
|
|
ReturnItem(content.GetChild(i));
|
|
i--;
|
|
}
|
|
}
|
|
|
|
uiPool.RefreshItems();
|
|
}
|
|
}
|
|
|
|
public void RefillItemsFromEnd(int offset = 0)
|
|
{
|
|
StopMovement();
|
|
ClearItems();//清除
|
|
|
|
if (itemCount <= 0)
|
|
{
|
|
return;
|
|
}
|
|
|
|
_endItemIndex = itemCount - offset;
|
|
_beginItemIndex = _endItemIndex;
|
|
|
|
float curFillSize = 0f;
|
|
float maxFillSize = 0f;
|
|
// m_ViewBounds may be not ready when RefillCells on Start
|
|
if (this.vertical)
|
|
{
|
|
maxFillSize = viewRect.rect.size.y;
|
|
}
|
|
else
|
|
{
|
|
maxFillSize = viewRect.rect.size.x;
|
|
}
|
|
|
|
while (curFillSize < maxFillSize)
|
|
{
|
|
float size = CreateItemAtBegin();
|
|
if (size < float.Epsilon)
|
|
{
|
|
break;
|
|
}
|
|
curFillSize += size;
|
|
}
|
|
|
|
float distance = Mathf.Max(0, curFillSize - maxFillSize);
|
|
|
|
var anchoredPosition = m_Content.anchoredPosition;
|
|
if (this.vertical)
|
|
{
|
|
anchoredPosition.y = distance;
|
|
}
|
|
else
|
|
{
|
|
anchoredPosition.x = -distance;
|
|
}
|
|
m_Content.anchoredPosition = anchoredPosition;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 清除所有项
|
|
/// </summary>
|
|
public void ClearItems()
|
|
{
|
|
_beginItemIndex = 0;
|
|
_endItemIndex = 0;
|
|
for (int i = content.childCount - 1; i >= 0; i--)
|
|
{
|
|
ReturnItem(content.GetChild(i));
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// 重新生成项(当数据池变动时调用)
|
|
/// </summary>
|
|
/// <param name="offset"></param>
|
|
public void RefillItems(int offset = 0)
|
|
{
|
|
StopMovement();//停止
|
|
ClearItems();//清除
|
|
|
|
if (itemCount <= 0)
|
|
{
|
|
return;
|
|
}
|
|
|
|
_beginItemIndex = offset;
|
|
_endItemIndex = _beginItemIndex;
|
|
|
|
float curFillSize = 0f;
|
|
float maxFillSize = 0f;
|
|
// m_ViewBounds may be not ready when RefillCells on Start
|
|
if (this.vertical)
|
|
{
|
|
maxFillSize = viewRect.rect.size.y;
|
|
}
|
|
else
|
|
{
|
|
maxFillSize = viewRect.rect.size.x;
|
|
}
|
|
|
|
while (curFillSize < maxFillSize)
|
|
{
|
|
float size = CreateItemAtEnd();
|
|
if (size < float.Epsilon)
|
|
{
|
|
break;
|
|
}
|
|
curFillSize += size;
|
|
}
|
|
|
|
var anchoredPosition = m_Content.anchoredPosition;
|
|
if (this.vertical)
|
|
{
|
|
anchoredPosition.y = 0f;
|
|
}
|
|
else
|
|
{
|
|
anchoredPosition.x = 0f;
|
|
}
|
|
m_Content.anchoredPosition = anchoredPosition;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 创建起始项
|
|
/// </summary>
|
|
/// <returns></returns>
|
|
protected float CreateItemAtBegin()
|
|
{
|
|
if (itemCount >= 0 && _beginItemIndex < contentConstraintCount)
|
|
{
|
|
return 0f;
|
|
}
|
|
|
|
float size = 0f;
|
|
for (int i = 0; i < contentConstraintCount; i++)
|
|
{
|
|
var newItem = CreateItem(--_beginItemIndex);
|
|
newItem.SetAsFirstSibling();
|
|
size = Mathf.Max(GetItemSize(newItem), size);
|
|
}
|
|
|
|
{
|
|
Vector2 offset = GetVector(size);
|
|
content.anchoredPosition += offset;
|
|
m_PrevPosition += offset;
|
|
m_ContentStartPosition += offset;
|
|
}
|
|
|
|
// protection
|
|
if (size > 0f && _thresholds < size)
|
|
{
|
|
_thresholds = size + _thresholdsMask;
|
|
}
|
|
|
|
return size;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 删除起始项(反向)
|
|
/// </summary>
|
|
/// <returns></returns>
|
|
protected float DeleteItemAtBegin()
|
|
{
|
|
if (((m_Dragging || m_Velocity.sqrMagnitude > float.Epsilon) && _endItemIndex >= itemCount - 1) || content.childCount == 0)
|
|
{
|
|
return 0f;
|
|
}
|
|
|
|
float size = 0f;
|
|
for (int i = 0; i < contentConstraintCount; i++)
|
|
{
|
|
var retItem = content.GetChild(0);
|
|
size = Mathf.Max(GetItemSize((RectTransform)retItem), size);
|
|
ReturnItem(retItem);
|
|
|
|
_beginItemIndex++;
|
|
|
|
if (content.childCount == 0)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
{
|
|
Vector2 offset = GetVector(size);
|
|
content.anchoredPosition -= offset;
|
|
m_PrevPosition -= offset;
|
|
m_ContentStartPosition -= offset;
|
|
}
|
|
|
|
return size;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 创建末端项
|
|
/// </summary>
|
|
/// <returns></returns>
|
|
protected float CreateItemAtEnd()
|
|
{
|
|
if (itemCount >= 0 && _endItemIndex >= itemCount)
|
|
{
|
|
return 0f;
|
|
}
|
|
|
|
float size = 0f;
|
|
//fill lines to end first
|
|
int count = contentConstraintCount - (content.childCount % contentConstraintCount);
|
|
for (int i = 0; i < count; i++)
|
|
{
|
|
size = Mathf.Max(this.GetItemSize(this.CreateItem(_endItemIndex++)), size);
|
|
|
|
if (_endItemIndex >= itemCount)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
// protection
|
|
if (size > 0f && _thresholds < size)
|
|
{
|
|
_thresholds = size + _thresholdsMask;
|
|
}
|
|
return size;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 删除末端项
|
|
/// </summary>
|
|
/// <returns>size of item</returns>
|
|
protected float DeleteItemAtEnd()
|
|
{
|
|
if (((m_Dragging || m_Velocity.sqrMagnitude > float.Epsilon) && (_beginItemIndex < contentConstraintCount)) || (content.childCount == 0))
|
|
{
|
|
return 0f;
|
|
}
|
|
|
|
float size = 0f;
|
|
|
|
for (int i = 0; i < contentConstraintCount; i++)
|
|
{
|
|
var retItem = content.GetChild(content.childCount - 1);
|
|
size = Mathf.Max(GetItemSize((RectTransform)retItem), size);
|
|
ReturnItem(retItem);
|
|
|
|
_endItemIndex--;
|
|
if (_endItemIndex % contentConstraintCount == 0 || content.childCount == 0)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
return size;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 更新
|
|
/// </summary>
|
|
/// <param name="viewBounds"></param>
|
|
/// <param name="contentBounds"></param>
|
|
/// <returns></returns>
|
|
protected virtual bool UpdateItems(Bounds viewBounds, Bounds contentBounds)
|
|
{
|
|
Vector2 vMin = viewBounds.min;
|
|
Vector2 vMax = viewBounds.max;
|
|
Vector2 cMin = contentBounds.min;
|
|
Vector2 cMax = contentBounds.max;
|
|
|
|
float sizeTotal = 0f;
|
|
//竖的
|
|
if (this.vertical)
|
|
{
|
|
//底部更新
|
|
if (vMin.y - cMin.y > _thresholds)
|
|
{
|
|
var size = DeleteItemAtEnd();
|
|
sizeTotal = size;
|
|
while (size > 0f && (vMin.y - cMin.y > _thresholds + sizeTotal))
|
|
{
|
|
size = DeleteItemAtEnd();
|
|
sizeTotal += size;
|
|
}
|
|
}
|
|
else if (vMin.y - cMin.y < _thresholdsMask)
|
|
{
|
|
var size = CreateItemAtEnd();
|
|
sizeTotal = size;
|
|
while (size > 0f && (vMin.y - cMin.y < _thresholdsMask - sizeTotal))
|
|
{
|
|
size = CreateItemAtEnd();
|
|
sizeTotal += size;
|
|
}
|
|
}
|
|
//顶部更新
|
|
if (cMax.y - vMax.y > _thresholds)
|
|
{
|
|
var size = DeleteItemAtBegin();
|
|
sizeTotal = size;
|
|
while (size > 0f && (cMax.y - vMax.y > _thresholds + sizeTotal))
|
|
{
|
|
size = DeleteItemAtBegin();
|
|
sizeTotal += size;
|
|
}
|
|
}
|
|
else if (cMax.y - vMax.y < _thresholdsMask)
|
|
{
|
|
var size = CreateItemAtBegin();
|
|
sizeTotal = size;
|
|
while (size > 0f && (cMax.y - vMax.y < _thresholdsMask - sizeTotal))
|
|
{
|
|
size = CreateItemAtBegin();
|
|
sizeTotal += size;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
//右边更新
|
|
if (cMax.x - vMax.x > _thresholds)
|
|
{
|
|
var size = DeleteItemAtEnd();
|
|
sizeTotal = size;
|
|
while (size > 0f && (cMax.x - vMax.x > _thresholds + sizeTotal))
|
|
{
|
|
size = DeleteItemAtEnd();
|
|
sizeTotal += size;
|
|
}
|
|
}
|
|
else if (cMax.x - vMax.x < _thresholdsMask)
|
|
{
|
|
var size = CreateItemAtEnd();
|
|
sizeTotal = size;
|
|
while (size > 0f && (cMax.x - vMax.x < _thresholdsMask - sizeTotal))
|
|
{
|
|
size = CreateItemAtEnd();
|
|
sizeTotal += size;
|
|
}
|
|
}
|
|
//左边更新
|
|
if (vMin.x - cMin.x > _thresholds)
|
|
{
|
|
var size = DeleteItemAtBegin();
|
|
sizeTotal = size;
|
|
while (size > 0f && (vMin.x - cMin.x > _thresholds + sizeTotal))
|
|
{
|
|
size = DeleteItemAtBegin();
|
|
sizeTotal += size;
|
|
}
|
|
}
|
|
else if (vMin.x - cMin.x < _thresholdsMask)
|
|
{
|
|
var size = CreateItemAtBegin();
|
|
sizeTotal = size;
|
|
while (size > 0f && (vMin.x - cMin.x < _thresholdsMask - sizeTotal))
|
|
{
|
|
size = CreateItemAtBegin();
|
|
sizeTotal += size;
|
|
}
|
|
}
|
|
}
|
|
return sizeTotal > 0;
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region ScrollRect Modify
|
|
|
|
private int m_ContentConstraintCount = 0;
|
|
protected int contentConstraintCount
|
|
{
|
|
get
|
|
{
|
|
if (m_ContentConstraintCount > 0)
|
|
{
|
|
return m_ContentConstraintCount;
|
|
}
|
|
m_ContentConstraintCount = 1;
|
|
if (content != null)
|
|
{
|
|
var layoutGroup = content.GetComponent<GridLayoutGroup>();
|
|
if (layoutGroup)
|
|
{
|
|
m_ContentConstraintCount = layoutGroup.constraintCount;
|
|
}
|
|
}
|
|
return m_ContentConstraintCount;
|
|
}
|
|
}
|
|
|
|
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);
|
|
|
|
if (itemCount > 0 && _endItemIndex > _beginItemIndex)
|
|
{
|
|
//TODO: 如果有空格 有一定的误差
|
|
float elementSize = m_ContentBounds.size.x / (_endItemIndex - _beginItemIndex);
|
|
float totalSize = elementSize * itemCount;
|
|
float offset = m_ContentBounds.min.x - elementSize * _beginItemIndex;
|
|
|
|
if (totalSize <= m_ViewBounds.size.x)
|
|
return (m_ViewBounds.min.x > offset) ? 1f : 0f;
|
|
return (m_ViewBounds.min.x - offset) / (totalSize - m_ViewBounds.size.x);
|
|
}
|
|
return 0f;
|
|
}
|
|
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);
|
|
|
|
if (itemCount > 0 && _endItemIndex > _beginItemIndex)
|
|
{
|
|
//TODO: 如果有空格 有一定的误差
|
|
float elementSize = m_ContentBounds.size.y / (_endItemIndex - _beginItemIndex);
|
|
float totalSize = elementSize * itemCount;
|
|
float offset = m_ContentBounds.max.y + elementSize * _beginItemIndex;
|
|
|
|
if (totalSize <= m_ViewBounds.size.y)
|
|
return (m_ViewBounds.min.y > offset) ? 1f : 0f;
|
|
return 1f - (offset - m_ViewBounds.max.y) / (totalSize - m_ViewBounds.size.y);
|
|
}
|
|
return 1f;
|
|
}
|
|
set
|
|
{
|
|
SetNormalizedPosition(value, 1);
|
|
}
|
|
}
|
|
|
|
private void SetNormalizedPosition(float value, int axis)
|
|
{
|
|
//==========ScrollRect Modify==========
|
|
if (itemCount <= 0 || _endItemIndex <= _beginItemIndex)
|
|
return;
|
|
//==========ScrollRect Modify==========
|
|
|
|
EnsureLayoutHasRebuilt();
|
|
UpdateBounds();
|
|
|
|
//==========ScrollRect Modify==========
|
|
Vector3 localPosition = m_Content.localPosition;
|
|
float newLocalPosition = localPosition[axis];
|
|
if (axis == 0)
|
|
{
|
|
float elementSize = m_ContentBounds.size.x / (_endItemIndex - _beginItemIndex);
|
|
float totalSize = elementSize * itemCount;
|
|
float offset = m_ContentBounds.min.x - elementSize * _beginItemIndex;
|
|
|
|
newLocalPosition += m_ViewBounds.min.x - value * (totalSize - m_ViewBounds.size[axis]) - offset;
|
|
}
|
|
else if (axis == 1)
|
|
{
|
|
float elementSize = m_ContentBounds.size.y / (_endItemIndex - _beginItemIndex);
|
|
float totalSize = elementSize * itemCount;
|
|
float offset = m_ContentBounds.max.y + elementSize * _beginItemIndex;
|
|
|
|
newLocalPosition -= offset - (1f - value) * (totalSize - m_ViewBounds.size.y) - m_ViewBounds.max.y;
|
|
}
|
|
|
|
//==========ScrollRect Modify==========
|
|
|
|
// 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();
|
|
}
|
|
}
|
|
|
|
protected void UpdateBounds(bool late = false)
|
|
{
|
|
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;
|
|
|
|
// ============ScrollRect Modify============
|
|
if (Application.isPlaying && late && UpdateItems(m_ViewBounds, m_ContentBounds))
|
|
{
|
|
LayoutRebuilder.ForceRebuildLayoutImmediate(m_Content);
|
|
}
|
|
// ============ScrollRect Modify============
|
|
|
|
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).
|
|
Vector3 delta = Vector3.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 != Vector3.zero)
|
|
{
|
|
m_Content.Translate(delta);
|
|
|
|
// Content position changed; recompute content bounds.
|
|
m_ContentBounds = GetBounds();
|
|
contentSize = m_ContentBounds.size;
|
|
contentPos = m_ContentBounds.center;
|
|
contentPivot = m_Content.pivot;
|
|
AdjustBounds(ref m_ViewBounds, ref contentPivot, ref contentSize, ref contentPos);
|
|
m_ContentBounds.size = contentSize;
|
|
m_ContentBounds.center = contentPos;
|
|
}
|
|
}
|
|
}
|
|
|
|
private void UpdateScrollbars(Vector2 offset)
|
|
{
|
|
if (m_HorizontalScrollbar)
|
|
{
|
|
//==========ScrollRect Modify==========
|
|
if (m_ContentBounds.size.x > 0 && itemCount > 0)
|
|
{
|
|
m_HorizontalScrollbar.size = Mathf.Clamp01((m_ViewBounds.size.x - Mathf.Abs(offset.x)) / m_ContentBounds.size.x * (_endItemIndex - _beginItemIndex) / itemCount);
|
|
}
|
|
else
|
|
{
|
|
m_HorizontalScrollbar.size = 1;
|
|
}
|
|
//==========ScrollRect Modify==========
|
|
|
|
m_HorizontalScrollbar.value = horizontalNormalizedPosition;
|
|
}
|
|
|
|
if (m_VerticalScrollbar)
|
|
{
|
|
//==========ScrollRect Modify==========
|
|
if (m_ContentBounds.size.y > 0 && itemCount > 0)
|
|
{
|
|
m_VerticalScrollbar.size = Mathf.Clamp01((m_ViewBounds.size.y - Mathf.Abs(offset.y)) / m_ContentBounds.size.y * (_endItemIndex - _beginItemIndex) / itemCount);
|
|
}
|
|
else
|
|
{
|
|
m_VerticalScrollbar.size = 1;
|
|
}
|
|
//==========ScrollRect Modify==========
|
|
|
|
m_VerticalScrollbar.value = verticalNormalizedPosition;
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region ScrollRect
|
|
|
|
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 KUIGrid()
|
|
{ }
|
|
|
|
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(true);
|
|
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);
|
|
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).
|
|
// Vector3 delta = Vector3.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 != Vector3.zero)
|
|
// {
|
|
// m_Content.Translate(delta);
|
|
|
|
// // Content position changed; recompute content bounds.
|
|
// m_ContentBounds = GetBounds();
|
|
// contentSize = m_ContentBounds.size;
|
|
// contentPos = m_ContentBounds.center;
|
|
// contentPivot = m_Content.pivot;
|
|
// AdjustBounds(ref m_ViewBounds, ref contentPivot, ref contentSize, ref contentPos);
|
|
// m_ContentBounds.size = contentSize;
|
|
// m_ContentBounds.center = 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
|
|
|
|
#endregion
|
|
}
|
|
|