1698 lines
63 KiB
C#
1698 lines
63 KiB
C#
using System.Net;
|
|
using UnityEngine;
|
|
using UnityEngine.Events;
|
|
using UnityEngine.EventSystems;
|
|
using System;
|
|
using System.Collections;
|
|
using System.Collections.Generic;
|
|
using UnityEngine.UI;
|
|
|
|
namespace BF
|
|
{
|
|
[DisallowMultipleComponent]
|
|
[RequireComponent(typeof(RectTransform))]
|
|
public abstract class ScrollRectBaseOld : 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,
|
|
}
|
|
|
|
#region ----------------------- Expansion Field -------------------------
|
|
|
|
public BFCell bfCell;
|
|
public bool useBar = false;
|
|
private int totalCount; //Total cell count, negative means infinite loop mode
|
|
protected float startThreshold = -1;
|
|
protected float endThreshold = -1;
|
|
// [Tooltip("Reverse direction for dragging")]
|
|
// public bool reverseDirection = false;
|
|
[Tooltip("Rubber scale for outside")]
|
|
public float rubberScale = 1;
|
|
protected abstract float GetSize(RectTransform item);
|
|
protected abstract float GetDimension(Vector2 vector);
|
|
protected abstract Vector2 GetVector(float value);
|
|
protected int directionSign = 0; // horizontal will be 1 || vertical will be -1
|
|
protected GridLayoutGroup gridLayout = null;
|
|
private float contentSpacing = -1;
|
|
protected float ContentSpacing
|
|
{
|
|
get
|
|
{
|
|
if (contentSpacing >= 0)
|
|
return contentSpacing;
|
|
contentSpacing = 0;
|
|
if (content != null)
|
|
{
|
|
HorizontalOrVerticalLayoutGroup layout1 = content.GetComponent<HorizontalOrVerticalLayoutGroup>();
|
|
if (layout1 != null)
|
|
contentSpacing = layout1.spacing;
|
|
gridLayout = content.GetComponent<GridLayoutGroup>();
|
|
if (gridLayout != null)
|
|
contentSpacing = Mathf.Abs(GetDimension(gridLayout.spacing));
|
|
}
|
|
return contentSpacing;
|
|
}
|
|
}
|
|
|
|
private int contentConstraintCount = 0;
|
|
protected int ContentConstraintCount
|
|
{
|
|
get
|
|
{
|
|
if (contentConstraintCount > 0)
|
|
return contentConstraintCount;
|
|
contentConstraintCount = 1;
|
|
if (content != null)
|
|
{
|
|
GridLayoutGroup layout2 = content.GetComponent<GridLayoutGroup>();
|
|
if (layout2 != null)
|
|
{
|
|
if (layout2.constraint == GridLayoutGroup.Constraint.Flexible)
|
|
Debug.LogWarning("[LoopScrollRect] Flexible not supported yet");
|
|
contentConstraintCount = layout2.constraintCount;
|
|
}
|
|
}
|
|
return contentConstraintCount;
|
|
}
|
|
}
|
|
|
|
[SerializeField]
|
|
public Action<Vector2> onValueChanged;
|
|
RectTransform cacheRoot;//缓存root
|
|
bool initedCacheRoot;
|
|
List<CellObj> cachedCellList = new List<CellObj>();//缓存cellObj
|
|
private List<CellObj> allCells = new List<CellObj>();
|
|
private int objectIndex;
|
|
protected int curTopIndex = 0;
|
|
protected int curBottomIndex = 0;
|
|
protected int cellStartIndex = 0;
|
|
protected int cellEndIndex = 0;
|
|
protected int selectedIndex = 0;
|
|
public Action<GameObject> luaInstantiateCellAction;
|
|
public Action<int, int> luaRefreshAction;
|
|
public Action<bool, int> luaSetSelectedAction;
|
|
#endregion
|
|
|
|
#region ----------------------- Original Field -------------------------
|
|
|
|
public RectTransform content;
|
|
public bool horizontal = true;
|
|
public bool vertical = true;
|
|
public MovementType movementType = MovementType.Elastic;
|
|
public float elasticity = 0.1f; // Only used for MovementType.Elastic
|
|
public bool inertia = true;
|
|
public float decelerationRate = 0.135f; // Only used when inertia is enabled
|
|
public float scrollSensitivity = 1.0f;
|
|
public RectTransform viewport;
|
|
private bool scrolling = false;
|
|
|
|
// The offset from handle position to mouse down position
|
|
private Vector2 pointerStartLocalCursor = Vector2.zero;
|
|
private Vector2 contentStartPosition = Vector2.zero;
|
|
|
|
private Bounds contentBounds;
|
|
private Bounds viewBounds;
|
|
|
|
public Vector2 velocity;
|
|
private bool dragging;
|
|
|
|
private Vector2 prevPosition = Vector2.zero;
|
|
private Bounds prevContentBounds;
|
|
private Bounds prevViewBounds;
|
|
[NonSerialized]
|
|
private bool hasRebuiltLayout = false;
|
|
|
|
[NonSerialized]
|
|
private RectTransform rectTrans;
|
|
private RectTransform RectTrans
|
|
{
|
|
get
|
|
{
|
|
if (rectTrans == null)
|
|
rectTrans = GetComponent<RectTransform>();
|
|
return rectTrans;
|
|
}
|
|
}
|
|
|
|
private DrivenRectTransformTracker tracker;
|
|
|
|
#endregion
|
|
|
|
|
|
#region ----------------------- ScrollBar -------------------------
|
|
[SerializeField]
|
|
Scrollbar horizontalScrollbar;
|
|
public Scrollbar HorizontalScrollbar
|
|
{
|
|
get
|
|
{
|
|
return horizontalScrollbar;
|
|
}
|
|
set
|
|
{
|
|
if (horizontalScrollbar)
|
|
horizontalScrollbar.onValueChanged.RemoveListener(SetHorizontalNormalizedPosition);
|
|
horizontalScrollbar = value;
|
|
if (horizontalScrollbar)
|
|
horizontalScrollbar.onValueChanged.AddListener(SetHorizontalNormalizedPosition);
|
|
SetDirtyCaching();
|
|
}
|
|
}
|
|
|
|
[SerializeField]
|
|
Scrollbar verticalScrollbar;
|
|
public Scrollbar VerticalScrollbar
|
|
{
|
|
get
|
|
{
|
|
return verticalScrollbar;
|
|
}
|
|
set
|
|
{
|
|
if (verticalScrollbar)
|
|
verticalScrollbar.onValueChanged.RemoveListener(SetVerticalNormalizedPosition);
|
|
verticalScrollbar = value;
|
|
if (verticalScrollbar)
|
|
verticalScrollbar.onValueChanged.AddListener(SetVerticalNormalizedPosition);
|
|
SetDirtyCaching();
|
|
}
|
|
}
|
|
|
|
[SerializeField]
|
|
ScrollbarVisibility horizontalScrollbarVisibility;
|
|
public ScrollbarVisibility HorizontalScrollbarVisibility
|
|
{
|
|
get { return horizontalScrollbarVisibility; }
|
|
set { horizontalScrollbarVisibility = value; SetDirtyCaching(); }
|
|
}
|
|
|
|
[SerializeField]
|
|
ScrollbarVisibility verticalScrollbarVisibility;
|
|
public ScrollbarVisibility VerticalScrollbarVisibility
|
|
{
|
|
get { return verticalScrollbarVisibility; }
|
|
set { verticalScrollbarVisibility = value; SetDirtyCaching(); }
|
|
}
|
|
|
|
[SerializeField]
|
|
float horizontalScrollbarSpacing;
|
|
public float HorizontalScrollbarSpacing
|
|
{
|
|
get { return horizontalScrollbarSpacing; }
|
|
set { horizontalScrollbarSpacing = value; SetDirty(); }
|
|
}
|
|
|
|
[SerializeField]
|
|
float verticalScrollbarSpacing;
|
|
public float VerticalScrollbarSpacing
|
|
{
|
|
get { return verticalScrollbarSpacing; }
|
|
set { verticalScrollbarSpacing = value; SetDirty(); }
|
|
}
|
|
|
|
bool hSliderExpand;
|
|
bool vSliderExpand;
|
|
float hSliderHeight;
|
|
float vSliderWidth;
|
|
|
|
RectTransform horizontalScrollbarRect;
|
|
RectTransform verticalScrollbarRect;
|
|
|
|
#endregion
|
|
|
|
protected ScrollRectBaseOld()
|
|
{
|
|
flexibleWidth = -1;
|
|
}
|
|
|
|
protected override void OnEnable()
|
|
{
|
|
base.OnEnable();
|
|
if (useBar)
|
|
{
|
|
if (horizontalScrollbar)
|
|
horizontalScrollbar.onValueChanged.AddListener(SetHorizontalNormalizedPosition);
|
|
if (verticalScrollbar)
|
|
verticalScrollbar.onValueChanged.AddListener(SetVerticalNormalizedPosition);
|
|
}
|
|
CanvasUpdateRegistry.RegisterCanvasElementForLayoutRebuild(this);
|
|
}
|
|
|
|
protected override void OnDisable()
|
|
{
|
|
CanvasUpdateRegistry.UnRegisterCanvasElementForRebuild(this);
|
|
if (useBar)
|
|
{
|
|
if (horizontalScrollbar)
|
|
horizontalScrollbar.onValueChanged.RemoveListener(SetHorizontalNormalizedPosition);
|
|
if (verticalScrollbar)
|
|
verticalScrollbar.onValueChanged.RemoveListener(SetVerticalNormalizedPosition);
|
|
}
|
|
hasRebuiltLayout = false;
|
|
tracker.Clear();
|
|
velocity = Vector2.zero;
|
|
LayoutRebuilder.MarkLayoutForRebuild(RectTrans);
|
|
base.OnDisable();
|
|
}
|
|
|
|
protected override void Awake()
|
|
{
|
|
if (!initedCacheRoot)
|
|
InitCacheRoot();
|
|
}
|
|
|
|
void InitCacheRoot()
|
|
{
|
|
var cacheTransform = transform.Find(StringConst.CACHE_ROOT_NAME);
|
|
if (cacheTransform == null)
|
|
{
|
|
GameObject go = new GameObject(StringConst.CACHE_ROOT_NAME);
|
|
cacheRoot = go.AddComponent<RectTransform>();
|
|
cacheRoot.SetParent(transform);
|
|
cacheRoot.localPosition = Vector3.zero;
|
|
go.SetActive(false);
|
|
}
|
|
if (cacheRoot == null)
|
|
cacheRoot = cacheTransform.transform as RectTransform;
|
|
initedCacheRoot = true;
|
|
}
|
|
|
|
protected override void OnDestroy()
|
|
{
|
|
luaInstantiateCellAction = null;
|
|
}
|
|
|
|
void CacheCell(CellObj cell)
|
|
{
|
|
if (cell != null)
|
|
{
|
|
cell.gameObject.transform.SetParent(cacheRoot);
|
|
cachedCellList.Add(cell);
|
|
}
|
|
}
|
|
|
|
public void AddOnvalueChangedFunc(Action<Vector2> func)
|
|
{
|
|
onValueChanged = func;
|
|
}
|
|
|
|
public void AddOnInstantiateCellAction(Action<GameObject> action)
|
|
{
|
|
luaInstantiateCellAction = action;
|
|
}
|
|
|
|
public void AddRefreshAction(Action<int, int> action)
|
|
{
|
|
luaRefreshAction = action;
|
|
}
|
|
|
|
public void AddSetSelectedAction(Action<bool, int> action)
|
|
{
|
|
luaSetSelectedAction = action;
|
|
}
|
|
|
|
public void SetTotalCount(int count)
|
|
{
|
|
totalCount = count;
|
|
}
|
|
|
|
public int GetTotalCount()
|
|
{
|
|
return totalCount;
|
|
}
|
|
|
|
public void SetSelected(int index)
|
|
{
|
|
RefreshRealShowIndex();
|
|
selectedIndex = index;
|
|
|
|
int count = allCells.Count;
|
|
for (int i = 0; i < count; i++)
|
|
{
|
|
var cell = allCells[i];
|
|
luaSetSelectedAction?.Invoke(cell.index == index, cell.objectIndex);
|
|
}
|
|
}
|
|
|
|
public int GetSelectedIndex()
|
|
{
|
|
return selectedIndex;
|
|
}
|
|
|
|
public void AddCell()
|
|
{
|
|
RefreshRealShowIndex();
|
|
totalCount++;
|
|
if (IsNotFull())
|
|
{
|
|
float size = NewItemAtEnd();
|
|
while (size > 0 && IsNotFull())
|
|
{
|
|
size = NewItemAtEnd();
|
|
}
|
|
}
|
|
}
|
|
|
|
public virtual bool IsNotFull() { return false; }
|
|
|
|
public void RemoveCell(int index, bool alignFlag = true)
|
|
{
|
|
if (totalCount >= index + 1)
|
|
{
|
|
RefreshRealShowIndex();
|
|
|
|
if (index == selectedIndex)
|
|
{
|
|
selectedIndex = 0;
|
|
}
|
|
else
|
|
{
|
|
selectedIndex = Math.Max(0, index > selectedIndex ? selectedIndex : selectedIndex - 1);
|
|
}
|
|
|
|
cellStartIndex = index + 1 < cellStartIndex ? cellStartIndex - 1 : cellStartIndex;
|
|
|
|
if (cellEndIndex == totalCount)
|
|
{
|
|
totalCount--;
|
|
//已经滑到底端
|
|
DeleteItemAtEnd();
|
|
RefreshAll();
|
|
}
|
|
else
|
|
{
|
|
totalCount--;
|
|
RefreshAll();
|
|
}
|
|
}
|
|
}
|
|
|
|
public void ClearCells()
|
|
{
|
|
if (Application.isPlaying)
|
|
{
|
|
int count = cellEndIndex - cellStartIndex;
|
|
cellStartIndex = 0;
|
|
cellEndIndex = 0;
|
|
curTopIndex = 0;
|
|
curBottomIndex = 0;
|
|
selectedIndex = 0;
|
|
for (int i = count - 1; i >= 0; i--)
|
|
{
|
|
var gameObject = content.GetChild(i).gameObject;
|
|
CellObj cell = GetCell(gameObject);
|
|
if (cell != null)
|
|
{
|
|
CacheCell(cell);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
protected CellObj GetCell(GameObject gameObject)
|
|
{
|
|
int count = allCells.Count;
|
|
for (int i = count - 1; i >= 0; i--)
|
|
{
|
|
if (allCells[i] != null && allCells[i].gameObject == gameObject)
|
|
{
|
|
return allCells[i];
|
|
}
|
|
}
|
|
return default;
|
|
}
|
|
|
|
public virtual void RefreshRealShowIndex()
|
|
{
|
|
curTopIndex = 0;
|
|
curBottomIndex = 0;
|
|
}
|
|
|
|
public void ScrollToCellImmediately(int index, bool alignFlag = true)
|
|
{
|
|
RefreshRealShowIndex();
|
|
|
|
if (alignFlag)
|
|
{
|
|
RefillCells(index);
|
|
}
|
|
else
|
|
{
|
|
RefillCellsFromEnd(index);
|
|
}
|
|
}
|
|
|
|
public void ScrollToCell(int index, float speed, bool alignFlag = true)
|
|
{
|
|
if (totalCount >= 0 && (index < 0 || index >= totalCount))
|
|
return;
|
|
if (speed <= 0)
|
|
return;
|
|
StopAllCoroutines();
|
|
StartCoroutine(ScrollToCellCoroutine(index, speed, alignFlag));
|
|
}
|
|
|
|
IEnumerator ScrollToCellCoroutine(int index, float speed, bool alignFlag = true)
|
|
{
|
|
bool needMoving = true;
|
|
while (needMoving)
|
|
{
|
|
yield return null;
|
|
if (!dragging)
|
|
{
|
|
float move = 0;
|
|
if (index < cellStartIndex)
|
|
move = -Time.deltaTime * speed;
|
|
else if (index >= cellEndIndex)
|
|
move = Time.deltaTime * speed;
|
|
else
|
|
{
|
|
viewBounds = new Bounds(viewport.rect.center, viewport.rect.size);
|
|
var m_ItemBounds = GetBounds4Item(index);
|
|
var offset = 0.0f;
|
|
if (directionSign == -1)
|
|
if (alignFlag)
|
|
{
|
|
offset = viewBounds.max.y - m_ItemBounds.max.y;
|
|
}
|
|
else
|
|
{
|
|
offset = viewBounds.max.y - m_ItemBounds.min.y;
|
|
}
|
|
else if (directionSign == 1)
|
|
if (alignFlag)
|
|
{
|
|
offset = m_ItemBounds.min.x - viewBounds.min.x;
|
|
}
|
|
else
|
|
{
|
|
offset = m_ItemBounds.min.x - viewBounds.max.x;
|
|
}
|
|
// check if we cannot move on
|
|
if (totalCount >= 0)
|
|
{
|
|
if (offset > 0 && cellEndIndex == totalCount && !alignFlag)
|
|
{
|
|
m_ItemBounds = GetBounds4Item(totalCount - 1);
|
|
// reach bottom
|
|
if ((directionSign == -1 && m_ItemBounds.min.y > viewBounds.min.y) ||
|
|
(directionSign == 1 && m_ItemBounds.max.x < viewBounds.max.x))
|
|
{
|
|
needMoving = false;
|
|
break;
|
|
}
|
|
}
|
|
else if (offset < 0 && cellStartIndex == 0 && alignFlag)
|
|
{
|
|
m_ItemBounds = GetBounds4Item(0);
|
|
if ((directionSign == -1 && m_ItemBounds.max.y < viewBounds.max.y) ||
|
|
(directionSign == 1 && m_ItemBounds.min.x > viewBounds.min.x))
|
|
{
|
|
needMoving = false;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
float maxMove = Time.deltaTime * speed;
|
|
if (Mathf.Abs(offset) < maxMove)
|
|
{
|
|
needMoving = false;
|
|
move = offset;
|
|
}
|
|
else
|
|
move = Mathf.Sign(offset) * maxMove;
|
|
}
|
|
if (Math.Abs(move) > 0)
|
|
{
|
|
Vector2 offset = GetVector(move);
|
|
content.anchoredPosition += offset;
|
|
prevPosition += offset;
|
|
contentStartPosition += offset;
|
|
}
|
|
}
|
|
}
|
|
StopMovement();
|
|
UpdatePrevData();
|
|
}
|
|
|
|
public void RefreshAll()
|
|
{
|
|
if (Application.isPlaying && this.isActiveAndEnabled)
|
|
{
|
|
for (var i = cellEndIndex - cellStartIndex - 1; i >= 0 ; i-- )
|
|
{
|
|
var gameObject = content.GetChild(i).gameObject;
|
|
CellObj cell = GetCell(gameObject);
|
|
|
|
cell.index = cellStartIndex + i;
|
|
luaRefreshAction?.Invoke(cell.index, cell.objectIndex);
|
|
}
|
|
}
|
|
}
|
|
|
|
public void RefillCellsFromEnd(int offset = 0)
|
|
{
|
|
StopMovement();
|
|
|
|
for (var i = cellEndIndex - cellStartIndex - 1; i >= 0 ; i-- )
|
|
{
|
|
var gameObject = content.GetChild(i).gameObject;
|
|
CacheCell(GetCell(gameObject));
|
|
}
|
|
|
|
|
|
cellEndIndex = totalCount - offset;
|
|
cellStartIndex = cellEndIndex;
|
|
curTopIndex = 0;
|
|
curBottomIndex = 0;
|
|
if (totalCount >= 0 && ContentConstraintCount != 1)
|
|
Debug.LogWarning("FromEnd ContentConstraintCount should be 1");
|
|
float sizeToFill = 0, sizeFilled = 0;
|
|
if (directionSign == -1)
|
|
sizeToFill = viewport.rect.size.y;
|
|
else
|
|
sizeToFill = viewport.rect.size.x;
|
|
bool sizeFlag = true;
|
|
while (sizeToFill > sizeFilled)
|
|
{
|
|
float size;
|
|
if (sizeFlag)
|
|
{
|
|
size = NewItemAtStart();
|
|
if (size <= 0) sizeFlag = false;
|
|
}
|
|
else
|
|
{
|
|
size = NewItemAtEnd();
|
|
if (size <= 0) break;
|
|
}
|
|
sizeFilled += size;
|
|
}
|
|
|
|
//溢出
|
|
Vector2 pos = content.anchoredPosition;
|
|
float dist = - Mathf.Max(0, sizeFilled - sizeToFill);
|
|
if (directionSign == -1)
|
|
pos.y = -dist;
|
|
else if (directionSign == 1)
|
|
pos.x = -dist;
|
|
content.anchoredPosition = pos;
|
|
}
|
|
|
|
public void RefiilCellsFromEndForFrame(int intervalFrame, int offset = 0)
|
|
{
|
|
if (gameObject.active)
|
|
{
|
|
StopMovement();
|
|
|
|
for (var i = cellEndIndex - cellStartIndex - 1; i >= 0 ; i-- )
|
|
{
|
|
var gameObject = content.GetChild(i).gameObject;
|
|
CacheCell(GetCell(gameObject));
|
|
}
|
|
|
|
|
|
cellEndIndex = totalCount - offset;
|
|
cellStartIndex = cellEndIndex;
|
|
curTopIndex = 0;
|
|
curBottomIndex = 0;
|
|
if (totalCount >= 0 && ContentConstraintCount != 1)
|
|
Debug.LogWarning("RefillFromEnd ContentConstraintCount should be 1");
|
|
|
|
StopCoroutine("RefillCellsFromEndForFrameCoroutine");
|
|
StartCoroutine("RefillCellsFromEndForFrameCoroutine", intervalFrame);
|
|
}
|
|
}
|
|
|
|
IEnumerator RefillCellsFromEndForFrameCoroutine(int intervaleFrame)
|
|
{
|
|
var waitForEndOfFatme = new WaitForEndOfFrame();
|
|
float sizeToFill = 0, sizeFilled = 0;
|
|
if (directionSign == -1)
|
|
sizeToFill = viewport.rect.size.y;
|
|
else
|
|
sizeToFill = viewport.rect.size.x;
|
|
bool sizeFlag = true;
|
|
while (sizeToFill > sizeFilled)
|
|
{
|
|
float size;
|
|
if (sizeFlag)
|
|
{
|
|
size = NewItemAtStart();
|
|
if (size <= 0) sizeFlag = false;
|
|
for (var i = 0; i < intervaleFrame; ++i)
|
|
{
|
|
yield return waitForEndOfFatme;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
size = NewItemAtEnd();
|
|
if (size <= 0) break;
|
|
for (var i = 0; i < intervaleFrame; ++i)
|
|
{
|
|
yield return waitForEndOfFatme;
|
|
}
|
|
}
|
|
sizeFilled += size;
|
|
}
|
|
|
|
//溢出
|
|
Vector2 pos = content.anchoredPosition;
|
|
float dist = - Mathf.Max(0, sizeFilled - sizeToFill);
|
|
if (directionSign == -1)
|
|
pos.y = -dist;
|
|
else if (directionSign == 1)
|
|
pos.x = -dist;
|
|
content.anchoredPosition = pos;
|
|
}
|
|
|
|
public void RefillCells(int index = 0)
|
|
{
|
|
StopMovement();
|
|
|
|
for (var i = cellEndIndex - cellStartIndex - 1; i >= 0 ; i-- )
|
|
{
|
|
var gameObject = content.GetChild(i).gameObject;
|
|
CacheCell(GetCell(gameObject));
|
|
}
|
|
|
|
if (index > 0 && index > ContentConstraintCount)
|
|
{
|
|
var offset = index % ContentConstraintCount;
|
|
if (offset == 0)
|
|
{
|
|
cellStartIndex = index - ContentConstraintCount;
|
|
}
|
|
else
|
|
{
|
|
cellStartIndex = index - offset;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
cellStartIndex = 0;
|
|
}
|
|
|
|
cellEndIndex = cellStartIndex;
|
|
curTopIndex = 0;
|
|
curBottomIndex = 0;
|
|
// Don't `Canvas.ForceUpdateCanvases();` here, or it will new/delete cells to change itemTypeStart/End
|
|
|
|
float sizeToFill = 0, sizeFilled = 0;
|
|
// m_ViewBounds may be not ready when RefillCells on Start
|
|
if (directionSign == -1)
|
|
sizeToFill = viewport.rect.size.y;
|
|
else
|
|
sizeToFill = viewport.rect.size.x;
|
|
bool sizeFlag = true;
|
|
while (sizeToFill > sizeFilled)
|
|
{
|
|
float size;
|
|
if (sizeFlag)
|
|
{
|
|
size = NewItemAtEnd();
|
|
if (size <= 0) sizeFlag = false;
|
|
}
|
|
else
|
|
{
|
|
size = NewItemAtStart();
|
|
if (size <= 0) break;
|
|
}
|
|
sizeFilled += size;
|
|
}
|
|
Vector2 pos = content.anchoredPosition;
|
|
if (directionSign == -1)
|
|
pos.y = 0;
|
|
else if (directionSign == 1)
|
|
pos.x = 0;
|
|
content.anchoredPosition = pos;
|
|
}
|
|
|
|
public void RefillCellsForFrame(int intervalFrame, int index = 0)
|
|
{
|
|
if (gameObject.active)
|
|
{
|
|
StopMovement();
|
|
|
|
for (var i = cellEndIndex - cellStartIndex - 1; i >= 0 ; i-- )
|
|
{
|
|
var gameObject = content.GetChild(i).gameObject;
|
|
CacheCell(GetCell(gameObject));
|
|
}
|
|
|
|
if (index > 0 && index > ContentConstraintCount)
|
|
{
|
|
var offset = index % ContentConstraintCount;
|
|
if (offset == 0)
|
|
{
|
|
cellStartIndex = index - ContentConstraintCount;
|
|
}
|
|
else
|
|
{
|
|
cellStartIndex = index - offset;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
cellStartIndex = 0;
|
|
}
|
|
|
|
cellEndIndex = cellStartIndex;
|
|
curTopIndex = 0;
|
|
curBottomIndex = 0;
|
|
// Don't `Canvas.ForceUpdateCanvases();` here, or it will new/delete cells to change itemTypeStart/End
|
|
|
|
Vector2 pos = content.anchoredPosition;
|
|
if (directionSign == -1)
|
|
pos.y = 0;
|
|
else if (directionSign == 1)
|
|
pos.x = 0;
|
|
content.anchoredPosition = pos;
|
|
|
|
StopCoroutine("RefillCellsForFrameCoroutine");
|
|
StartCoroutine("RefillCellsForFrameCoroutine", intervalFrame);
|
|
}
|
|
}
|
|
|
|
IEnumerator RefillCellsForFrameCoroutine(int intervaleFrame)
|
|
{
|
|
var waitForEndOfFatme = new WaitForEndOfFrame();
|
|
float sizeToFill = 0, sizeFilled = 0;
|
|
// m_ViewBounds may be not ready when RefillCells on Start
|
|
if (directionSign == -1)
|
|
sizeToFill = viewport.rect.size.y;
|
|
else
|
|
sizeToFill = viewport.rect.size.x;
|
|
var sizeFlag = true;
|
|
while (sizeToFill > sizeFilled)
|
|
{
|
|
float size = 0;
|
|
if (sizeFlag)
|
|
{
|
|
|
|
if (totalCount >= 0 && cellEndIndex >= totalCount)
|
|
{
|
|
size = 0;
|
|
}
|
|
else
|
|
{
|
|
int count = ContentConstraintCount - (cellEndIndex - cellStartIndex) % ContentConstraintCount;
|
|
for (int i = 0; i < count; i++)
|
|
{
|
|
RectTransform newItem = InstantiateNextItem(cellEndIndex);
|
|
if (newItem == null)
|
|
{
|
|
break;
|
|
}
|
|
size = GetSize(newItem);
|
|
//size = Mathf.Max(GetSize(newItem), size);
|
|
|
|
cellEndIndex++;
|
|
if (totalCount >= 0 && cellEndIndex >= totalCount)
|
|
{
|
|
break;
|
|
}
|
|
|
|
for (var w = 0; w < intervaleFrame; ++w)
|
|
{
|
|
yield return waitForEndOfFatme;
|
|
}
|
|
}
|
|
|
|
endThreshold = size * 1.5f;
|
|
if (startThreshold < 0)
|
|
{
|
|
startThreshold = size * 1.5f;
|
|
}
|
|
}
|
|
|
|
if (size <= 0)
|
|
{
|
|
sizeFlag = false;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (totalCount >= 0 && cellStartIndex - ContentConstraintCount < 0)
|
|
{
|
|
size = 0;
|
|
}
|
|
else
|
|
{
|
|
for (int i = 0; i < ContentConstraintCount; i++)
|
|
{
|
|
cellStartIndex--;
|
|
RectTransform newItem = InstantiateNextItem(cellStartIndex);
|
|
if (newItem == null)
|
|
{
|
|
size = 0;
|
|
}
|
|
newItem.SetAsFirstSibling();
|
|
size = GetSize(newItem);
|
|
//size = Mathf.Max(GetSize(newItem), size);
|
|
|
|
for (var w = 0; w < intervaleFrame; ++w)
|
|
{
|
|
yield return waitForEndOfFatme;
|
|
}
|
|
}
|
|
|
|
startThreshold = size * 1.5f;
|
|
if (endThreshold < 0)
|
|
{
|
|
endThreshold =size * 1.5f;
|
|
}
|
|
|
|
Vector2 offset = GetVector(size);
|
|
content.anchoredPosition += offset;
|
|
prevPosition += offset;
|
|
contentStartPosition += offset;
|
|
}
|
|
|
|
|
|
if (size <= 0)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
sizeFilled += size;
|
|
}
|
|
}
|
|
|
|
protected float NewItemAtStart()
|
|
{
|
|
if (totalCount >= 0 && cellStartIndex - ContentConstraintCount < 0)
|
|
return 0;
|
|
float size = 0;
|
|
for (int i = 0; i < ContentConstraintCount; i++)
|
|
{
|
|
cellStartIndex--;
|
|
RectTransform newItem = InstantiateNextItem(cellStartIndex);
|
|
if (newItem == null)
|
|
return 0;
|
|
newItem.SetAsFirstSibling();
|
|
size = GetSize(newItem);
|
|
//size = Mathf.Max(GetSize(newItem), size);
|
|
}
|
|
|
|
startThreshold = size * 1.5f;
|
|
if (endThreshold < 0)
|
|
{
|
|
endThreshold = size * 1.5f;
|
|
}
|
|
|
|
Vector2 offset = GetVector(size);
|
|
content.anchoredPosition += offset;
|
|
prevPosition += offset;
|
|
contentStartPosition += offset;
|
|
return size;
|
|
}
|
|
|
|
protected float DeleteItemAtStart()
|
|
{
|
|
// special case: when moving or dragging, we cannot simply delete start when we've reached the end
|
|
if (((dragging || velocity != Vector2.zero) && totalCount >= 0 && cellEndIndex >= totalCount - 1)
|
|
|| (cellEndIndex == 0 && cellStartIndex == 0))//content.childCount == 0)
|
|
return 0;
|
|
float size = 0;
|
|
for (int i = 0; i < ContentConstraintCount; i++)
|
|
{
|
|
RectTransform oldItem = content.GetChild(0) as RectTransform;
|
|
size = Mathf.Max(GetSize(oldItem), size);
|
|
CacheCell(GetCell(oldItem.gameObject));
|
|
cellStartIndex++;
|
|
if ((cellEndIndex == 0 && cellStartIndex == 0))//content.childCount == 0)
|
|
break;
|
|
}
|
|
|
|
Vector2 offset = GetVector(size);
|
|
content.anchoredPosition -= offset;
|
|
prevPosition -= offset;
|
|
contentStartPosition -= offset;
|
|
return size;
|
|
}
|
|
|
|
protected float NewItemAtEnd()
|
|
{
|
|
if (totalCount >= 0 && cellEndIndex >= totalCount)
|
|
return 0;
|
|
float size = 0;
|
|
// issue 4: fill lines to end first
|
|
//int count = ContentConstraintCount - (content.childCount % ContentConstraintCount);
|
|
int count = ContentConstraintCount - (cellEndIndex - cellStartIndex) % ContentConstraintCount;
|
|
for (int i = 0; i < count; i++)
|
|
{
|
|
RectTransform newItem = InstantiateNextItem(cellEndIndex);
|
|
if (newItem == null)
|
|
break;
|
|
size = GetSize(newItem);
|
|
//size = Mathf.Max(GetSize(newItem), size);
|
|
cellEndIndex++;
|
|
if (totalCount >= 0 && cellEndIndex >= totalCount)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
endThreshold = size * 1.5f;
|
|
if (startThreshold < 0)
|
|
{
|
|
startThreshold = size * 1.5f;
|
|
}
|
|
return size;
|
|
}
|
|
|
|
protected float DeleteItemAtEnd()
|
|
{
|
|
if (((dragging || velocity != Vector2.zero) && totalCount >= 0 && cellStartIndex < ContentConstraintCount)
|
|
|| (cellEndIndex == 0 && cellStartIndex == 0))//|| content.childCount == 0)
|
|
return 0;
|
|
float size = 0;
|
|
for (int i = 0; i < ContentConstraintCount; i++)
|
|
{
|
|
var index = cellEndIndex - cellStartIndex - 1;
|
|
if (index < 0 || index > content.childCount - 1)
|
|
{
|
|
Debug.LogWarning(string.Format("caijieLog try get child out of bounds index : {0}, childCount : {1}, totalCount : {2} ", index, content.childCount, totalCount));
|
|
break;
|
|
}
|
|
RectTransform oldItem = content.GetChild(index) as RectTransform;
|
|
|
|
size = Mathf.Max(GetSize(oldItem), size);
|
|
CacheCell(GetCell(oldItem.gameObject));
|
|
cellEndIndex--;
|
|
if (cellEndIndex % ContentConstraintCount == 0 || (cellEndIndex == 0 && cellStartIndex == 0) || totalCount >= 0 && cellEndIndex >= totalCount) //content.childCount == 0)
|
|
break; //just delete the whole row
|
|
}
|
|
return size;
|
|
}
|
|
|
|
RectTransform InstantiateNextItem(int index)
|
|
{
|
|
if (totalCount < index + 1)
|
|
{
|
|
return null;
|
|
}
|
|
RectTransform nextItemTrans = null;
|
|
CellObj nextCell = null;
|
|
if (cachedCellList.Count > 0)
|
|
{
|
|
int cachedCellIndex = cachedCellList.Count - 1;
|
|
nextItemTrans = cachedCellList[cachedCellIndex].gameObject.transform as RectTransform;
|
|
nextCell = cachedCellList[cachedCellIndex];
|
|
cachedCellList.RemoveAt(cachedCellIndex);
|
|
nextItemTrans.transform.SetParent(content, false);
|
|
nextItemTrans.gameObject.SetActive(true);
|
|
}else{
|
|
nextItemTrans = Instantiate(bfCell.CachedRectTransform);
|
|
nextItemTrans.SetParent(content, false);
|
|
nextItemTrans.gameObject.SetActive(true);
|
|
nextCell = new CellObj();
|
|
objectIndex++;
|
|
nextCell.objectIndex = objectIndex;
|
|
nextCell.gameObject = nextItemTrans.gameObject;
|
|
luaInstantiateCellAction?.Invoke(nextCell.gameObject);
|
|
}
|
|
nextCell.index = index;
|
|
allCells.Add(nextCell);
|
|
luaRefreshAction?.Invoke(index, nextCell.objectIndex);
|
|
return nextItemTrans;
|
|
}
|
|
|
|
public virtual void Rebuild(CanvasUpdate executing)
|
|
{
|
|
if (executing == CanvasUpdate.Prelayout)
|
|
if (useBar)
|
|
{
|
|
UpdateCachedData();
|
|
}
|
|
if (executing == CanvasUpdate.PostLayout)
|
|
{
|
|
UpdateBounds();
|
|
if (useBar)
|
|
{
|
|
UpdateScrollbars(Vector2.zero);
|
|
}
|
|
UpdatePrevData();
|
|
hasRebuiltLayout = true;
|
|
}
|
|
}
|
|
|
|
public virtual void LayoutComplete() { }
|
|
|
|
public virtual void GraphicUpdateComplete() { }
|
|
|
|
void UpdateCachedData()
|
|
{
|
|
if (!useBar)
|
|
{
|
|
return;
|
|
}
|
|
|
|
horizontalScrollbarRect = horizontalScrollbar == null ? null : horizontalScrollbar.transform as RectTransform;
|
|
verticalScrollbarRect = verticalScrollbar == null ? null : verticalScrollbar.transform as RectTransform;
|
|
// These are true if either the elements are children, or they don't exist at all.
|
|
bool viewIsChild = (viewport.parent == transform);
|
|
bool hScrollbarIsChild = (!horizontalScrollbarRect || horizontalScrollbarRect.parent == transform);
|
|
bool vScrollbarIsChild = (!verticalScrollbarRect || verticalScrollbarRect.parent == transform);
|
|
bool allAreChildren = (viewIsChild && hScrollbarIsChild && vScrollbarIsChild);
|
|
hSliderExpand = allAreChildren && horizontalScrollbarRect && HorizontalScrollbarVisibility == ScrollbarVisibility.AutoHideAndExpandViewport;
|
|
vSliderExpand = allAreChildren && verticalScrollbarRect && VerticalScrollbarVisibility == ScrollbarVisibility.AutoHideAndExpandViewport;
|
|
hSliderHeight = (horizontalScrollbarRect == null ? 0 : horizontalScrollbarRect.rect.height);
|
|
vSliderWidth = (verticalScrollbarRect == null ? 0 : verticalScrollbarRect.rect.width);
|
|
}
|
|
|
|
public override bool IsActive()
|
|
{
|
|
return base.IsActive() && content != null;
|
|
}
|
|
|
|
void EnsureLayoutHasRebuilt()
|
|
{
|
|
if (!hasRebuiltLayout && !CanvasUpdateRegistry.IsRebuildingLayout())
|
|
Canvas.ForceUpdateCanvases();
|
|
}
|
|
|
|
public virtual void StopMovement()
|
|
{
|
|
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;
|
|
}
|
|
//2018new
|
|
if (data.IsScrolling())
|
|
scrolling = true;
|
|
|
|
Vector2 position = content.anchoredPosition;
|
|
position += delta * scrollSensitivity;
|
|
if (movementType == MovementType.Clamped)
|
|
position += CalculateOffset(position - content.anchoredPosition);
|
|
SetContentAnchoredPosition(position);
|
|
UpdateBounds();
|
|
}
|
|
|
|
public virtual void OnInitializePotentialDrag(PointerEventData eventData)
|
|
{
|
|
if (eventData.button != PointerEventData.InputButton.Left)
|
|
return;
|
|
velocity = Vector2.zero;
|
|
}
|
|
|
|
public virtual void OnBeginDrag(PointerEventData eventData)
|
|
{
|
|
if (eventData.button != PointerEventData.InputButton.Left)
|
|
return;
|
|
if (!IsActive())
|
|
return;
|
|
UpdateBounds();
|
|
pointerStartLocalCursor = Vector2.zero;
|
|
RectTransformUtility.ScreenPointToLocalPointInRectangle(viewport, eventData.position, eventData.pressEventCamera, out pointerStartLocalCursor);
|
|
contentStartPosition = content.anchoredPosition;
|
|
dragging = true;
|
|
}
|
|
|
|
public virtual void OnEndDrag(PointerEventData eventData)
|
|
{
|
|
if (eventData.button != PointerEventData.InputButton.Left)
|
|
return;
|
|
dragging = false;
|
|
}
|
|
|
|
public virtual void OnDrag(PointerEventData eventData)
|
|
{
|
|
if (eventData.button != PointerEventData.InputButton.Left)
|
|
return;
|
|
if (!IsActive())
|
|
return;
|
|
Vector2 localCursor;
|
|
if (!RectTransformUtility.ScreenPointToLocalPointInRectangle(viewport, eventData.position, eventData.pressEventCamera, out localCursor))
|
|
return;
|
|
UpdateBounds();
|
|
var pointerDelta = localCursor - pointerStartLocalCursor;
|
|
Vector2 position = contentStartPosition + pointerDelta;
|
|
|
|
// Offset to get content into place in the view.
|
|
Vector2 offset = CalculateOffset(position - content.anchoredPosition);
|
|
position += offset;
|
|
if (movementType == MovementType.Elastic)
|
|
{
|
|
if (offset.x != 0)
|
|
position.x = position.x - RubberDelta(offset.x, viewBounds.size.x) * rubberScale;
|
|
if (offset.y != 0)
|
|
position.y = position.y - RubberDelta(offset.y, viewBounds.size.y) * rubberScale;
|
|
}
|
|
SetContentAnchoredPosition(position);
|
|
}
|
|
|
|
protected virtual void SetContentAnchoredPosition(Vector2 position)
|
|
{
|
|
if (!horizontal)
|
|
position.x = content.anchoredPosition.x;
|
|
if (!vertical)
|
|
position.y = content.anchoredPosition.y;
|
|
if (position != content.anchoredPosition)
|
|
{
|
|
content.anchoredPosition = position;
|
|
UpdateBounds(true);
|
|
}
|
|
}
|
|
|
|
protected virtual void LateUpdate()
|
|
{
|
|
if (!content)
|
|
return;
|
|
EnsureLayoutHasRebuilt();
|
|
UpdateBounds();
|
|
float deltaTime = Time.unscaledDeltaTime;
|
|
Vector2 offset = CalculateOffset(Vector2.zero);
|
|
if (!dragging && (offset != Vector2.zero || velocity != Vector2.zero))
|
|
{
|
|
Vector2 position = 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 (movementType == MovementType.Elastic && offset[axis] != 0)
|
|
{
|
|
float speed = velocity[axis];
|
|
//2018new
|
|
var smoothTime = elasticity;
|
|
// if (scrolling)
|
|
// {
|
|
// smoothTime *= 3.0f;
|
|
// }
|
|
position[axis] = Mathf.SmoothDamp(content.anchoredPosition[axis], content.anchoredPosition[axis] + offset[axis], ref speed, smoothTime, Mathf.Infinity, deltaTime);
|
|
if (Mathf.Abs(speed) < 1)
|
|
{
|
|
speed = 0;
|
|
}
|
|
velocity[axis] = speed;
|
|
}
|
|
// Else move content according to velocity with deceleration applied.
|
|
else if (inertia)
|
|
{
|
|
velocity[axis] *= Mathf.Pow(decelerationRate, deltaTime);
|
|
if (Mathf.Abs(velocity[axis]) < 1)
|
|
velocity[axis] = 0;
|
|
position[axis] += velocity[axis] * deltaTime;
|
|
}
|
|
// If we have neither elaticity or friction, there shouldn't be any velocity.
|
|
else
|
|
velocity[axis] = 0;
|
|
}
|
|
if (velocity != Vector2.zero)
|
|
{
|
|
if (movementType == MovementType.Clamped)
|
|
{
|
|
offset = CalculateOffset(position - content.anchoredPosition);
|
|
position += offset;
|
|
}
|
|
SetContentAnchoredPosition(position);
|
|
}
|
|
}
|
|
if (dragging && inertia)
|
|
{
|
|
Vector3 newVelocity = (content.anchoredPosition - prevPosition) / deltaTime;
|
|
velocity = Vector3.Lerp(velocity, newVelocity, deltaTime * 10);
|
|
}
|
|
if (viewBounds != prevViewBounds || contentBounds != prevContentBounds || content.anchoredPosition != prevPosition)
|
|
{
|
|
if (useBar)
|
|
{
|
|
UpdateScrollbars(offset);
|
|
}
|
|
onValueChanged?.Invoke(NormalizedPosition);
|
|
UpdatePrevData();
|
|
}
|
|
//2018new
|
|
if (useBar)
|
|
{
|
|
UpdateScrollbarVisibility();
|
|
}
|
|
scrolling = false;
|
|
hasRebuiltLayout = false;
|
|
}
|
|
|
|
void UpdatePrevData()
|
|
{
|
|
if (content == null)
|
|
prevPosition = Vector2.zero;
|
|
else
|
|
prevPosition = content.anchoredPosition;
|
|
prevViewBounds = viewBounds;
|
|
prevContentBounds = contentBounds;
|
|
}
|
|
|
|
void UpdateScrollbars(Vector2 offset)
|
|
{
|
|
if (horizontalScrollbar)
|
|
{
|
|
if (contentBounds.size.x > 0 && totalCount > 0)
|
|
horizontalScrollbar.size = Mathf.Clamp01((viewBounds.size.x - Mathf.Abs(offset.x)) / contentBounds.size.x * (cellEndIndex - cellStartIndex) / totalCount);
|
|
else
|
|
horizontalScrollbar.size = 1;
|
|
horizontalScrollbar.value = HorizontalNormalizedPosition;
|
|
}
|
|
if (verticalScrollbar)
|
|
{
|
|
if (contentBounds.size.y > 0 && totalCount > 0)
|
|
verticalScrollbar.size = Mathf.Clamp01((viewBounds.size.y - Mathf.Abs(offset.y)) / contentBounds.size.y * (cellEndIndex - cellStartIndex) / totalCount);
|
|
else
|
|
verticalScrollbar.size = 1;
|
|
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 (totalCount > 0 && cellEndIndex > cellStartIndex)
|
|
{
|
|
//TODO: consider contentSpacing
|
|
float elementSize = contentBounds.size.x / (cellEndIndex - cellStartIndex);
|
|
float totalSize = elementSize * totalCount;
|
|
float offset = contentBounds.min.x - elementSize * cellStartIndex;
|
|
if (totalSize <= viewBounds.size.x)
|
|
return (viewBounds.min.x > offset) ? 1 : 0;
|
|
return (viewBounds.min.x - offset) / (totalSize - viewBounds.size.x);
|
|
}
|
|
else
|
|
return 0.5f;
|
|
}
|
|
set
|
|
{
|
|
SetNormalizedPosition(value, 0);
|
|
}
|
|
}
|
|
|
|
public float VerticalNormalizedPosition
|
|
{
|
|
get
|
|
{
|
|
UpdateBounds();
|
|
if (totalCount > 0 && cellEndIndex > cellStartIndex)
|
|
{
|
|
//TODO: consider contentSpacinge
|
|
float elementSize = contentBounds.size.y / (cellEndIndex - cellStartIndex);
|
|
float totalSize = elementSize * totalCount;
|
|
float offset = contentBounds.max.y + elementSize * cellStartIndex;
|
|
if (totalSize <= viewBounds.size.y)
|
|
return (offset > viewBounds.max.y) ? 1 : 0;
|
|
return (offset - viewBounds.max.y) / (totalSize - viewBounds.size.y);
|
|
}
|
|
else
|
|
return 0.5f;
|
|
}
|
|
set
|
|
{
|
|
SetNormalizedPosition(value, 1);
|
|
}
|
|
}
|
|
|
|
void SetHorizontalNormalizedPosition(float value)
|
|
{
|
|
SetNormalizedPosition(value, 0);
|
|
}
|
|
|
|
void SetVerticalNormalizedPosition(float value)
|
|
{
|
|
SetNormalizedPosition(value, 1);
|
|
}
|
|
|
|
void SetNormalizedPosition(float value, int axis)
|
|
{
|
|
if (totalCount <= 0 || cellEndIndex <= cellStartIndex)
|
|
return;
|
|
EnsureLayoutHasRebuilt();
|
|
UpdateBounds();
|
|
Vector3 localPosition = content.localPosition;
|
|
float newLocalPosition = localPosition[axis];
|
|
if (axis == 0)
|
|
{
|
|
float elementSize = contentBounds.size.x / (cellEndIndex - cellStartIndex);
|
|
float totalSize = elementSize * totalCount;
|
|
float offset = contentBounds.min.x - elementSize * cellStartIndex;
|
|
newLocalPosition += viewBounds.min.x - value * (totalSize - viewBounds.size[axis]) - offset;
|
|
}
|
|
else if (axis == 1)
|
|
{
|
|
float elementSize = contentBounds.size.y / (cellEndIndex - cellStartIndex);
|
|
float totalSize = elementSize * totalCount;
|
|
float offset = contentBounds.max.y + elementSize * cellStartIndex;
|
|
newLocalPosition -= offset - value * (totalSize - viewBounds.size.y) - viewBounds.max.y;
|
|
}
|
|
if (Mathf.Abs(localPosition[axis] - newLocalPosition) > 0.01f)
|
|
{
|
|
localPosition[axis] = newLocalPosition;
|
|
content.localPosition = localPosition;
|
|
velocity[axis] = 0;
|
|
UpdateBounds(true);
|
|
}
|
|
}
|
|
|
|
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();
|
|
}
|
|
|
|
bool hScrollingNeeded
|
|
{
|
|
get
|
|
{
|
|
if (Application.isPlaying)
|
|
return contentBounds.size.x > viewBounds.size.x + 0.01f;
|
|
return true;
|
|
}
|
|
}
|
|
bool vScrollingNeeded
|
|
{
|
|
get
|
|
{
|
|
if (Application.isPlaying)
|
|
return contentBounds.size.y > 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; private set; }
|
|
|
|
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()
|
|
{
|
|
tracker.Clear();
|
|
if (hSliderExpand || vSliderExpand)
|
|
{
|
|
tracker.Add(this, viewport,
|
|
DrivenTransformProperties.Anchors |
|
|
DrivenTransformProperties.SizeDelta |
|
|
DrivenTransformProperties.AnchoredPosition);
|
|
// Make view full size to see if content fits.
|
|
viewport.anchorMin = Vector2.zero;
|
|
viewport.anchorMax = Vector2.one;
|
|
viewport.sizeDelta = Vector2.zero;
|
|
viewport.anchoredPosition = Vector2.zero;
|
|
// Recalculate content layout with this size to see if it fits when there are no scrollbars.
|
|
LayoutRebuilder.ForceRebuildLayoutImmediate(content);
|
|
viewBounds = new Bounds(viewport.rect.center, viewport.rect.size);
|
|
contentBounds = GetBounds();
|
|
}
|
|
// If it doesn't fit vertically, enable vertical scrollbar and shrink view horizontally to make room for it.
|
|
if (vSliderExpand && vScrollingNeeded)
|
|
{
|
|
viewport.sizeDelta = new Vector2(-(vSliderWidth + verticalScrollbarSpacing), viewport.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);
|
|
viewBounds = new Bounds(viewport.rect.center, viewport.rect.size);
|
|
contentBounds = GetBounds();
|
|
}
|
|
// If it doesn't fit horizontally, enable horizontal scrollbar and shrink view vertically to make room for it.
|
|
if (hSliderExpand && hScrollingNeeded)
|
|
{
|
|
viewport.sizeDelta = new Vector2(viewport.sizeDelta.x, -(hSliderHeight + horizontalScrollbarSpacing));
|
|
viewBounds = new Bounds(viewport.rect.center, viewport.rect.size);
|
|
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 (vSliderExpand && vScrollingNeeded && viewport.sizeDelta.x == 0 && viewport.sizeDelta.y < 0)
|
|
viewport.sizeDelta = new Vector2(-(vSliderWidth + verticalScrollbarSpacing), viewport.sizeDelta.y);
|
|
}
|
|
|
|
public virtual void SetLayoutVertical()
|
|
{
|
|
if (useBar)
|
|
{
|
|
UpdateScrollbarLayout();
|
|
}
|
|
viewBounds = new Bounds(viewport.rect.center, viewport.rect.size);
|
|
contentBounds = GetBounds();
|
|
}
|
|
|
|
void UpdateScrollbarVisibility()
|
|
{
|
|
if (verticalScrollbar && verticalScrollbarVisibility != ScrollbarVisibility.Permanent && verticalScrollbar.gameObject.activeSelf != vScrollingNeeded)
|
|
verticalScrollbar.gameObject.SetActive(vScrollingNeeded);
|
|
if (horizontalScrollbar && horizontalScrollbarVisibility != ScrollbarVisibility.Permanent && horizontalScrollbar.gameObject.activeSelf != hScrollingNeeded)
|
|
horizontalScrollbar.gameObject.SetActive(hScrollingNeeded);
|
|
}
|
|
|
|
void UpdateScrollbarLayout()
|
|
{
|
|
if (!useBar)
|
|
{
|
|
return;
|
|
}
|
|
if (vSliderExpand && horizontalScrollbar)
|
|
{
|
|
tracker.Add(this, horizontalScrollbarRect,
|
|
DrivenTransformProperties.AnchorMinX |
|
|
DrivenTransformProperties.AnchorMaxX |
|
|
DrivenTransformProperties.SizeDeltaX |
|
|
DrivenTransformProperties.AnchoredPositionX);
|
|
horizontalScrollbarRect.anchorMin = new Vector2(0, horizontalScrollbarRect.anchorMin.y);
|
|
horizontalScrollbarRect.anchorMax = new Vector2(1, horizontalScrollbarRect.anchorMax.y);
|
|
horizontalScrollbarRect.anchoredPosition = new Vector2(0, horizontalScrollbarRect.anchoredPosition.y);
|
|
if (vScrollingNeeded)
|
|
horizontalScrollbarRect.sizeDelta = new Vector2(-(vSliderWidth + verticalScrollbarSpacing), horizontalScrollbarRect.sizeDelta.y);
|
|
else
|
|
horizontalScrollbarRect.sizeDelta = new Vector2(0, horizontalScrollbarRect.sizeDelta.y);
|
|
}
|
|
|
|
if (hSliderExpand && verticalScrollbar)
|
|
{
|
|
tracker.Add(this, verticalScrollbarRect,
|
|
DrivenTransformProperties.AnchorMinY |
|
|
DrivenTransformProperties.AnchorMaxY |
|
|
DrivenTransformProperties.SizeDeltaY |
|
|
DrivenTransformProperties.AnchoredPositionY);
|
|
verticalScrollbarRect.anchorMin = new Vector2(verticalScrollbarRect.anchorMin.x, 0);
|
|
verticalScrollbarRect.anchorMax = new Vector2(verticalScrollbarRect.anchorMax.x, 1);
|
|
verticalScrollbarRect.anchoredPosition = new Vector2(verticalScrollbarRect.anchoredPosition.x, 0);
|
|
if (hScrollingNeeded)
|
|
verticalScrollbarRect.sizeDelta = new Vector2(verticalScrollbarRect.sizeDelta.x, -(hSliderHeight + horizontalScrollbarSpacing));
|
|
else
|
|
verticalScrollbarRect.sizeDelta = new Vector2(verticalScrollbarRect.sizeDelta.x, 0);
|
|
}
|
|
}
|
|
|
|
void UpdateBounds(bool updateItems = false)
|
|
{
|
|
viewBounds = new Bounds(viewport.rect.center, viewport.rect.size);
|
|
contentBounds = GetBounds();
|
|
if (content == null)
|
|
return;
|
|
// Don't do this in Rebuild
|
|
if (Application.isPlaying && updateItems && UpdateItems(viewBounds, contentBounds))
|
|
{
|
|
Canvas.ForceUpdateCanvases();
|
|
contentBounds = GetBounds();
|
|
}
|
|
// Make sure content bounds arAdjustBoundse 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 contentSize = contentBounds.size;
|
|
Vector3 contentPos = contentBounds.center;
|
|
Vector3 excess = viewBounds.size - contentSize;
|
|
if (excess.x > 0)
|
|
{
|
|
contentPos.x -= excess.x * (content.pivot.x - 0.5f);
|
|
contentSize.x = viewBounds.size.x;
|
|
}
|
|
if (excess.y > 0)
|
|
{
|
|
contentPos.y -= excess.y * (content.pivot.y - 0.5f);
|
|
contentSize.y = viewBounds.size.y;
|
|
}
|
|
contentBounds.size = contentSize;
|
|
contentBounds.center = contentPos;
|
|
}
|
|
|
|
protected virtual bool UpdateItems(Bounds viewBounds, Bounds contentBounds)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
readonly Vector3[] m_Corners = new Vector3[4];
|
|
Bounds GetBounds()
|
|
{
|
|
if (content == null)
|
|
return new Bounds();
|
|
var vMin = new Vector3(float.MaxValue, float.MaxValue, float.MaxValue);
|
|
var vMax = new Vector3(float.MinValue, float.MinValue, float.MinValue);
|
|
var toLocal = viewport.worldToLocalMatrix;
|
|
content.GetWorldCorners(m_Corners);
|
|
for (int j = 0; j < 4; j++)
|
|
{
|
|
Vector3 v = toLocal.MultiplyPoint3x4(m_Corners[j]);
|
|
vMin = Vector3.Min(v, vMin);
|
|
vMax = Vector3.Max(v, vMax);
|
|
}
|
|
var bounds = new Bounds(vMin, Vector3.zero);
|
|
bounds.Encapsulate(vMax);
|
|
return bounds;
|
|
}
|
|
|
|
Bounds GetBounds4Item(int index)
|
|
{
|
|
if (content == null)
|
|
return new Bounds();
|
|
var vMin = new Vector3(float.MaxValue, float.MaxValue, float.MaxValue);
|
|
var vMax = new Vector3(float.MinValue, float.MinValue, float.MinValue);
|
|
var toLocal = viewport.worldToLocalMatrix;
|
|
int offset = index - cellStartIndex;
|
|
if (offset < 0 || offset >= cellEndIndex - cellStartIndex)//content.childCount)
|
|
return new Bounds();
|
|
RectTransform rectTransfrom = content.GetChild(offset) as RectTransform;
|
|
if (rectTransfrom == null)
|
|
return new Bounds();
|
|
rectTransfrom.GetWorldCorners(m_Corners);
|
|
for (int j = 0; j < 4; j++)
|
|
{
|
|
Vector3 v = toLocal.MultiplyPoint3x4(m_Corners[j]);
|
|
vMin = Vector3.Min(v, vMin);
|
|
vMax = Vector3.Max(v, vMax);
|
|
}
|
|
var bounds = new Bounds(vMin, Vector3.zero);
|
|
bounds.Encapsulate(vMax);
|
|
return bounds;
|
|
}
|
|
|
|
protected Bounds GetCellBounds(int index)
|
|
{
|
|
if (content == null)
|
|
return new Bounds();
|
|
var vMin = new Vector3(float.MaxValue, float.MaxValue, float.MaxValue);
|
|
var vMax = new Vector3(float.MinValue, float.MinValue, float.MinValue);
|
|
var toLocal = viewport.worldToLocalMatrix;
|
|
var rt = content.GetChild(index) as RectTransform;
|
|
if (rt == null)
|
|
return new Bounds();
|
|
rt.GetWorldCorners(m_Corners);
|
|
for (int j = 0; j < 4; j++)
|
|
{
|
|
Vector3 v = toLocal.MultiplyPoint3x4(m_Corners[j]);
|
|
vMin = Vector3.Min(v, vMin);
|
|
vMax = Vector3.Max(v, vMax);
|
|
}
|
|
var bounds = new Bounds(vMin, Vector3.zero);
|
|
bounds.Encapsulate(vMax);
|
|
return bounds;
|
|
}
|
|
|
|
Vector2 CalculateOffset(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(RectTrans);
|
|
}
|
|
|
|
protected void SetDirtyCaching()
|
|
{
|
|
if (!IsActive())
|
|
return;
|
|
CanvasUpdateRegistry.RegisterCanvasElementForLayoutRebuild(this);
|
|
LayoutRebuilder.MarkLayoutForRebuild(RectTrans);
|
|
}
|
|
|
|
#if UNITY_EDITOR
|
|
protected override void OnValidate()
|
|
{
|
|
SetDirtyCaching();
|
|
}
|
|
#endif
|
|
}
|
|
} |