mirror of
https://github.com/RHeavenStudio/HeavenStudio.git
synced 2025-06-12 15:07:39 +02:00
Editor stuff
This commit is contained in:
@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 1f5595dc255ff30488626bfc60c045a6
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ff6158f1872894fd18d8b73aa8b3727e
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -0,0 +1,125 @@
|
||||
/// <summary>
|
||||
/// Credit - ryanslikesocool
|
||||
/// Sourced from - https://github.com/ryanslikesocool/Unity-Card-UI
|
||||
/// </summary>
|
||||
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
|
||||
namespace UnityEngine.UI.Extensions
|
||||
{
|
||||
public class CardExpanding2D : MonoBehaviour
|
||||
{
|
||||
|
||||
[SerializeField]
|
||||
private float lerpSpeed = 8f;
|
||||
|
||||
[SerializeField]
|
||||
private RectTransform buttonRect = null;
|
||||
private Vector2 closeButtonMin = Vector2.zero;
|
||||
private Vector2 closeButtonMax = Vector2.zero;
|
||||
|
||||
[SerializeField]
|
||||
private Vector2 cardSize = Vector2.zero;
|
||||
[SerializeField]
|
||||
private Vector2 pageSize = Vector2.zero;
|
||||
|
||||
private Vector2 cardCenter = Vector2.zero;
|
||||
private Vector2 pageCenter = Vector2.zero;
|
||||
|
||||
private Vector2 cardMin = Vector2.zero;
|
||||
private Vector2 cardMax = Vector2.zero;
|
||||
private Vector2 pageMin = Vector2.zero;
|
||||
private Vector2 pageMax = Vector2.zero;
|
||||
|
||||
private RectTransform rectTrans;
|
||||
///I wouldn't recommend changing animationActive's value here unless you want the card to start as a page.
|
||||
private int animationActive = -1;
|
||||
|
||||
void Start()
|
||||
{
|
||||
rectTrans = GetComponent<RectTransform>();
|
||||
|
||||
///Setting up the button's starting color and page position.
|
||||
buttonRect.GetComponent<Image>().color = new Color32(228, 0, 0, 0);
|
||||
|
||||
closeButtonMin = new Vector2(pageMin.x + pageSize.x - 64, pageMin.y + pageSize.y - 64);
|
||||
closeButtonMax = new Vector2(pageMax.x - 16, pageMax.y - 16);
|
||||
|
||||
///Setting up the card and page offsets.
|
||||
cardMin = new Vector2(cardCenter.x - cardSize.x * 0.5f, cardCenter.y - cardSize.y * 0.5f);
|
||||
cardMax = new Vector2(cardCenter.x + cardSize.x * 0.5f, cardCenter.y + cardSize.y * 0.5f);
|
||||
|
||||
pageMin = new Vector2(pageCenter.x - pageSize.x * 0.5f, pageCenter.y - pageSize.y * 0.5f);
|
||||
pageMax = new Vector2(pageCenter.x + pageSize.x * 0.5f, pageCenter.y + pageSize.y * 0.5f);
|
||||
}
|
||||
|
||||
void Update()
|
||||
{
|
||||
///When animationActive == 1, the card is expanding into a page.
|
||||
if (animationActive == 1)
|
||||
{
|
||||
rectTrans.offsetMin = Vector2.Lerp(rectTrans.offsetMin, pageMin, Time.deltaTime * lerpSpeed);
|
||||
rectTrans.offsetMax = Vector2.Lerp(rectTrans.offsetMax, pageMax, Time.deltaTime * lerpSpeed);
|
||||
|
||||
if (rectTrans.offsetMin.x < pageMin.x * 0.995f && rectTrans.offsetMin.y < pageMin.y * 0.995f && rectTrans.offsetMax.x > pageMax.x * 0.995f && rectTrans.offsetMax.y > pageMax.y * 0.995f)
|
||||
{
|
||||
rectTrans.offsetMin = pageMin;
|
||||
rectTrans.offsetMax = pageMax;
|
||||
|
||||
///Changes the button color so it's visible in the page view.
|
||||
buttonRect.GetComponent<Image>().color = Color32.Lerp(buttonRect.GetComponent<Image>().color, new Color32(228, 0, 0, 191), Time.deltaTime * lerpSpeed);
|
||||
|
||||
if (Mathf.Abs(buttonRect.GetComponent<Image>().color.a - 191) < 2)
|
||||
{
|
||||
buttonRect.GetComponent<Image>().color = new Color32(228, 0, 0, 191);
|
||||
|
||||
animationActive = 0;
|
||||
CardStack2D.canUseHorizontalAxis = true;
|
||||
}
|
||||
}
|
||||
///When animationActive == -1, the page is shrinking into a card.
|
||||
}
|
||||
else if (animationActive == -1)
|
||||
{
|
||||
buttonRect.GetComponent<Image>().color = Color32.Lerp(buttonRect.GetComponent<Image>().color, new Color32(228, 0, 0, 0), Time.deltaTime * lerpSpeed * 1.25f);
|
||||
|
||||
rectTrans.offsetMin = Vector2.Lerp(rectTrans.offsetMin, cardMin, Time.deltaTime * lerpSpeed);
|
||||
rectTrans.offsetMax = Vector2.Lerp(rectTrans.offsetMax, cardMax, Time.deltaTime * lerpSpeed);
|
||||
|
||||
if (rectTrans.offsetMin.x > cardMin.x * 1.005f && rectTrans.offsetMin.y > cardMin.y * 1.005f && rectTrans.offsetMax.x < cardMax.x * 1.005f && rectTrans.offsetMax.y < cardMax.y * 1.005f)
|
||||
{
|
||||
rectTrans.offsetMin = cardMin;
|
||||
rectTrans.offsetMax = cardMax;
|
||||
|
||||
///Makes the button take up the whole card.
|
||||
buttonRect.offsetMin = Vector2.zero;
|
||||
buttonRect.offsetMax = Vector2.zero;
|
||||
|
||||
animationActive = 0;
|
||||
CardStack2D.canUseHorizontalAxis = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void ToggleCard()
|
||||
{
|
||||
CardStack2D.canUseHorizontalAxis = false;
|
||||
if (animationActive != 1)
|
||||
{
|
||||
animationActive = 1;
|
||||
cardCenter = transform.localPosition;
|
||||
|
||||
///Makes the button the right size in page view.
|
||||
buttonRect.offsetMin = closeButtonMin;
|
||||
buttonRect.offsetMax = closeButtonMax;
|
||||
}
|
||||
else if (animationActive != -1)
|
||||
{
|
||||
animationActive = -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 9ab9da748840643a3bd4794f6f138979
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -0,0 +1,82 @@
|
||||
/// <summary>
|
||||
/// Credit - ryanslikesocool
|
||||
/// Sourced from - https://github.com/ryanslikesocool/Unity-Card-UI
|
||||
/// </summary>
|
||||
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace UnityEngine.UI.Extensions
|
||||
{
|
||||
|
||||
[RequireComponent(typeof(Rigidbody))]
|
||||
public class CardPopup2D : MonoBehaviour
|
||||
{
|
||||
[SerializeField]
|
||||
private float rotationSpeed = 1f;
|
||||
[SerializeField]
|
||||
private float centeringSpeed = 4f;
|
||||
[SerializeField]
|
||||
private bool singleScene = false;
|
||||
|
||||
private Rigidbody rbody;
|
||||
private bool isFalling;
|
||||
private Vector3 cardFallRotation;
|
||||
private bool fallToZero;
|
||||
private float startZPos;
|
||||
|
||||
void Start()
|
||||
{
|
||||
rbody = GetComponent<Rigidbody>();
|
||||
rbody.useGravity = false;
|
||||
startZPos = transform.position.z;
|
||||
}
|
||||
|
||||
void Update()
|
||||
{
|
||||
if (isFalling)
|
||||
{
|
||||
transform.rotation = Quaternion.Lerp(transform.rotation, Quaternion.Euler(cardFallRotation), Time.deltaTime * rotationSpeed);
|
||||
}
|
||||
|
||||
///This conditional makes the popup fall nicely into place.
|
||||
if (fallToZero)
|
||||
{
|
||||
transform.position = Vector3.Lerp(transform.position, new Vector3(0, 0, startZPos), Time.deltaTime * centeringSpeed);
|
||||
transform.rotation = Quaternion.Lerp(transform.rotation, Quaternion.Euler(Vector3.zero), Time.deltaTime * centeringSpeed);
|
||||
if (Vector3.Distance(transform.position, new Vector3(0, 0, startZPos)) < 0.0025f)
|
||||
{
|
||||
transform.position = new Vector3(0, 0, startZPos);
|
||||
fallToZero = false;
|
||||
}
|
||||
}
|
||||
|
||||
///This is totally unnecessary.
|
||||
if (transform.position.y < -4)
|
||||
{
|
||||
isFalling = false;
|
||||
rbody.useGravity = false;
|
||||
rbody.velocity = Vector3.zero;
|
||||
transform.position = new Vector3(0, 8, startZPos);
|
||||
if (singleScene)
|
||||
{
|
||||
CardEnter();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void CardEnter()
|
||||
{
|
||||
fallToZero = true;
|
||||
}
|
||||
|
||||
///A negative fallRotation will result in the card turning clockwise, while a positive fallRotation makes the card turn counterclockwise.
|
||||
public void CardFallAway(float fallRotation)
|
||||
{
|
||||
rbody.useGravity = true;
|
||||
isFalling = true;
|
||||
cardFallRotation = new Vector3(0, 0, fallRotation);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a41308ab5f4aa489c9cf797b9152351a
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -0,0 +1,123 @@
|
||||
/// <summary>
|
||||
/// Credit - ryanslikesocool
|
||||
/// Sourced from - https://github.com/ryanslikesocool/Unity-Card-UI
|
||||
/// </summary>
|
||||
|
||||
using System.Collections;
|
||||
|
||||
namespace UnityEngine.UI.Extensions
|
||||
{
|
||||
|
||||
public class CardStack2D : MonoBehaviour
|
||||
{
|
||||
|
||||
[SerializeField]
|
||||
private float cardMoveSpeed = 8f;
|
||||
[SerializeField]
|
||||
private float buttonCooldownTime = 0.125f;
|
||||
[SerializeField]
|
||||
private int cardZMultiplier = 32;
|
||||
[SerializeField]
|
||||
private bool useDefaultUsedXPos = true;
|
||||
[SerializeField]
|
||||
private int usedCardXPos = 1280;
|
||||
[SerializeField]
|
||||
private KeyCode leftButton = KeyCode.LeftArrow;
|
||||
[SerializeField]
|
||||
private KeyCode rightButton = KeyCode.RightArrow;
|
||||
[SerializeField]
|
||||
private Transform[] cards = null;
|
||||
|
||||
|
||||
|
||||
private int cardArrayOffset;
|
||||
private Vector3[] cardPositions;
|
||||
private int xPowerDifference;
|
||||
|
||||
///Static variables can be used across the scene if this script is in it.
|
||||
///Thankfully it doesn't matter if another script attempts to use the variable and this script isn't in the scene.
|
||||
public static bool canUseHorizontalAxis = true;
|
||||
|
||||
void Start()
|
||||
{
|
||||
///I've found that 9 is a good number for this.
|
||||
///I wouldn't really recommend changing it, but go ahead if you want to.
|
||||
xPowerDifference = 9 - cards.Length;
|
||||
|
||||
///This is optional, but makes it super easy to figure out the off screen position for cards.
|
||||
///Unfortunately, it's only really useful if the cards are the same width.
|
||||
if (useDefaultUsedXPos)
|
||||
{
|
||||
int cardWidth = (int)(cards[0].GetComponent<RectTransform>().rect.width);
|
||||
usedCardXPos = (int)(Screen.width * 0.5f + cardWidth);
|
||||
}
|
||||
|
||||
cardPositions = new Vector3[cards.Length * 2 - 1];
|
||||
|
||||
///This loop is for cards still in the stack.
|
||||
for (int i = cards.Length; i > -1; i--)
|
||||
{
|
||||
if (i < cards.Length - 1)
|
||||
{
|
||||
cardPositions[i] = new Vector3(-Mathf.Pow(2, i + xPowerDifference) + cardPositions[i + 1].x, 0, cardZMultiplier * Mathf.Abs(i + 1 - cards.Length));
|
||||
}
|
||||
else
|
||||
{
|
||||
cardPositions[i] = Vector3.zero;
|
||||
}
|
||||
}
|
||||
|
||||
///This loop is for cards outside of the stack.
|
||||
for (int i = cards.Length; i < cardPositions.Length; i++)
|
||||
{
|
||||
cardPositions[i] = new Vector3(usedCardXPos + 4 * (i - cards.Length), 0, -2 + -2 * (i - cards.Length));
|
||||
}
|
||||
}
|
||||
|
||||
void Update()
|
||||
{
|
||||
if (canUseHorizontalAxis)
|
||||
{
|
||||
///Controls for the cards.
|
||||
if ((UIExtensionsInputManager.GetAxisRaw("Horizontal") < 0 || UIExtensionsInputManager.GetKey(leftButton)) && cardArrayOffset > 0)
|
||||
{
|
||||
cardArrayOffset--;
|
||||
StartCoroutine(ButtonCooldown());
|
||||
}
|
||||
else if ((UIExtensionsInputManager.GetAxisRaw("Horizontal") > 0 || UIExtensionsInputManager.GetKey(rightButton)) && cardArrayOffset < cards.Length - 1)
|
||||
{
|
||||
cardArrayOffset++;
|
||||
StartCoroutine(ButtonCooldown());
|
||||
}
|
||||
}
|
||||
|
||||
///This loop moves the cards. I know that none of my lerps are the "right way," but it looks much nicer.
|
||||
for (int i = 0; i < cards.Length; i++)
|
||||
{
|
||||
cards[i].localPosition = Vector3.Lerp(cards[i].localPosition, cardPositions[i + cardArrayOffset], Time.deltaTime * cardMoveSpeed);
|
||||
if (Mathf.Abs(cards[i].localPosition.x - cardPositions[i + cardArrayOffset].x) < 0.01f)
|
||||
{
|
||||
cards[i].localPosition = cardPositions[i + cardArrayOffset];
|
||||
|
||||
///This disables interaction with cards that are not on top of the stack.
|
||||
if (cards[i].localPosition.x == 0)
|
||||
{
|
||||
cards[i].gameObject.GetComponent<CanvasGroup>().interactable = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
cards[i].gameObject.GetComponent<CanvasGroup>().interactable = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
///Stops the cards from scrolling super quickly if a button on the horizontal axis is held down.
|
||||
IEnumerator ButtonCooldown()
|
||||
{
|
||||
canUseHorizontalAxis = false;
|
||||
yield return new WaitForSeconds(buttonCooldownTime);
|
||||
canUseHorizontalAxis = true;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 3c0edb9a3f5da4e129739a8f92a115af
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 2e00d759ad79242bc80e41df440cc3df
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -0,0 +1,222 @@
|
||||
/// <summary>
|
||||
/// Credit - ryanslikesocool
|
||||
/// Sourced from - https://github.com/ryanslikesocool/Unity-Card-UI
|
||||
/// </summary>
|
||||
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
|
||||
namespace UnityEngine.UI.Extensions
|
||||
{
|
||||
|
||||
[ExecuteInEditMode]
|
||||
public class CardExpanding3D : MonoBehaviour
|
||||
{
|
||||
[SerializeField]
|
||||
private float lerpSpeed = 12;
|
||||
[SerializeField]
|
||||
private float cornerSize = 64;
|
||||
|
||||
[Header("Parts")]
|
||||
public RectTransform[] cardCorners;
|
||||
public RectTransform[] cardEdges;
|
||||
public RectTransform cardCenter;
|
||||
|
||||
[Header("Card Info")]
|
||||
[Tooltip("Positions and sizes card to its current transform.")]
|
||||
public bool cardAutoSize = true;
|
||||
public Vector2 cardSize;
|
||||
public Vector2 cardPosition;
|
||||
[Range(1, 96)]
|
||||
public int cardSuperness = 4;
|
||||
|
||||
[Header("Page Info")]
|
||||
[Tooltip("Positions and sizes the page to the top third of the screen.")]
|
||||
public bool pageAutoSize = true;
|
||||
public Vector2 pageSize;
|
||||
public Vector2 pagePosition;
|
||||
[Range(1, 96)]
|
||||
public int pageSuperness = 96;
|
||||
|
||||
///Just like with the 2D version of this script, I don't recommend touching this.
|
||||
private int animationActive = 0;
|
||||
|
||||
private Vector2[] nextCornerPos = new Vector2[4];
|
||||
private Vector2[] nextEdgePos = new Vector2[4];
|
||||
private Vector2[] nextEdgeScale = new Vector2[4];
|
||||
private Vector2 nextCenterScale;
|
||||
private Vector2 nextPos;
|
||||
private int nextSuperness;
|
||||
|
||||
private RectTransform rect;
|
||||
private Vector2 nextMin;
|
||||
private Vector2 nextMax;
|
||||
|
||||
void Start()
|
||||
{
|
||||
if (cardAutoSize)
|
||||
{
|
||||
cardSize = new Vector2(cardCorners[0].localScale.x * 2 + cardEdges[0].localScale.x, cardCorners[0].localScale.y * 2 + cardEdges[0].localScale.y);
|
||||
cardPosition = cardCenter.localPosition;
|
||||
}
|
||||
|
||||
if (pageAutoSize)
|
||||
{
|
||||
pageSize = new Vector2(Screen.width, Screen.height / 3);
|
||||
pagePosition = new Vector2(0, Screen.height / 2 - pageSize.y / 2);
|
||||
}
|
||||
|
||||
rect = GetComponent<RectTransform>();
|
||||
}
|
||||
|
||||
void Update()
|
||||
{
|
||||
if (animationActive == 1 || animationActive == -1)
|
||||
{
|
||||
///Lerps the corners to new positions and supernesses.
|
||||
for (int i = 0; i < cardCorners.Length; i++)
|
||||
{
|
||||
cardCorners[i].localPosition = Vector3.Lerp(cardCorners[i].localPosition, nextCornerPos[i], Time.deltaTime * lerpSpeed);
|
||||
|
||||
cardCorners[i].GetComponent<SuperellipsePoints>().superness = Mathf.Lerp(cardCorners[i].GetComponent<SuperellipsePoints>().superness, nextSuperness, Time.deltaTime * lerpSpeed);
|
||||
|
||||
///Forces everything to either the card layout or the page layout once the superness is similar enough.
|
||||
if (Mathf.Abs(cardCorners[i].GetComponent<SuperellipsePoints>().superness - nextSuperness) <= 1)
|
||||
{
|
||||
cardCorners[i].localPosition = nextCornerPos[i];
|
||||
cardEdges[i].localPosition = nextEdgePos[i];
|
||||
cardEdges[i].localScale = new Vector3(nextEdgeScale[i].x, nextEdgeScale[i].y, 1);
|
||||
transform.localPosition = nextPos;
|
||||
cardCenter.localScale = new Vector3(nextCenterScale.x, nextCenterScale.y, 1);
|
||||
cardCorners[i].GetComponent<SuperellipsePoints>().superness = nextSuperness;
|
||||
rect.offsetMin = nextMin;
|
||||
rect.offsetMax = nextMax;
|
||||
}
|
||||
}
|
||||
|
||||
///Lerps the edges to new positions and sizes.
|
||||
for (int i = 0; i < cardEdges.Length; i++)
|
||||
{
|
||||
cardEdges[i].localPosition = Vector3.Lerp(cardEdges[i].localPosition, nextEdgePos[i], Time.deltaTime * lerpSpeed);
|
||||
cardEdges[i].localScale = Vector3.Lerp(cardEdges[i].localScale, new Vector3(nextEdgeScale[i].x, nextEdgeScale[i].y, 1), Time.deltaTime * lerpSpeed);
|
||||
}
|
||||
|
||||
///Lerps the center to new position and size.
|
||||
transform.localPosition = Vector3.Lerp(transform.localPosition, nextPos, Time.deltaTime * lerpSpeed);
|
||||
cardCenter.localScale = Vector3.Lerp(cardCenter.localScale, new Vector3(nextCenterScale.x, nextCenterScale.y, 1), Time.deltaTime * lerpSpeed);
|
||||
|
||||
///Lerps the RectTransform.
|
||||
rect.offsetMin = Vector3.Lerp(rect.offsetMin, nextMin, Time.deltaTime * lerpSpeed);
|
||||
rect.offsetMax = Vector3.Lerp(rect.offsetMax, nextMax, Time.deltaTime * lerpSpeed);
|
||||
}
|
||||
}
|
||||
|
||||
public void ToggleCard()
|
||||
{
|
||||
if (animationActive != 1 || animationActive == 0)
|
||||
{
|
||||
animationActive = 1;
|
||||
|
||||
///Gets new corner positions.
|
||||
for (int i = 0; i < cardCorners.Length; i++)
|
||||
{
|
||||
float posX = pageSize.x / 2 * Mathf.Sign(cardCorners[i].localScale.x) - cardCorners[i].localScale.x;
|
||||
float posY = pageSize.y / 2 * Mathf.Sign(cardCorners[i].localScale.y) - cardCorners[i].localScale.y;
|
||||
|
||||
nextCornerPos[i] = new Vector2(posX, posY);
|
||||
}
|
||||
|
||||
///Same concept as the last loop.
|
||||
for (int i = 0; i < cardEdges.Length; i++)
|
||||
{
|
||||
float posX = 0;
|
||||
float posY = 0;
|
||||
|
||||
float scaleX = 0;
|
||||
float scaleY = 0;
|
||||
|
||||
if (cardEdges[i].localPosition.x != 0)
|
||||
{
|
||||
posX = Mathf.Sign(cardEdges[i].localPosition.x) * ((pageSize.x / 2) - (cardEdges[i].localScale.x / 2));
|
||||
posY = 0;
|
||||
|
||||
scaleX = cornerSize;
|
||||
scaleY = pageSize.y - cornerSize * 2;
|
||||
}
|
||||
else if (cardEdges[i].localPosition.y != 0)
|
||||
{
|
||||
posX = 0;
|
||||
posY = Mathf.Sign(cardEdges[i].localPosition.y) * ((pageSize.y / 2) - (cardEdges[i].localScale.y / 2));
|
||||
|
||||
scaleX = pageSize.x - cornerSize * 2;
|
||||
scaleY = cornerSize;
|
||||
}
|
||||
|
||||
nextEdgePos[i] = new Vector2(posX, posY);
|
||||
nextEdgeScale[i] = new Vector2(scaleX, scaleY);
|
||||
}
|
||||
|
||||
nextCenterScale = pageSize - new Vector2(cornerSize * 2, cornerSize * 2);
|
||||
nextPos = pagePosition;
|
||||
|
||||
nextSuperness = pageSuperness;
|
||||
|
||||
nextMin = new Vector2(-pageSize.x / 2, -pageSize.y / 2) + nextPos;
|
||||
nextMax = new Vector2(pageSize.x / 2, pageSize.y / 2) + nextPos;
|
||||
}
|
||||
else if (animationActive != -1)
|
||||
{
|
||||
animationActive = -1;
|
||||
|
||||
///Gets new corner positions.
|
||||
for (int i = 0; i < cardCorners.Length; i++)
|
||||
{
|
||||
float posX = Mathf.Sign(cardCorners[i].localScale.x) * (cardSize.x / 2) - cardCorners[i].localScale.x;
|
||||
float posY = Mathf.Sign(cardCorners[i].localScale.y) * (cardSize.y / 2) - cardCorners[i].localScale.y;
|
||||
|
||||
nextCornerPos[i] = new Vector2(posX, posY);
|
||||
}
|
||||
|
||||
///Same concept as the last loop.
|
||||
for (int i = 0; i < cardEdges.Length; i++)
|
||||
{
|
||||
float posX = 0;
|
||||
float posY = 0;
|
||||
|
||||
float scaleX = 0;
|
||||
float scaleY = 0;
|
||||
|
||||
if (cardEdges[i].localPosition.x != 0)
|
||||
{
|
||||
posX = Mathf.Sign(cardEdges[i].localPosition.x) * (cardSize.x / 2) - Mathf.Sign(cardEdges[i].localPosition.x) * (cardEdges[i].localScale.x / 2);
|
||||
posY = 0;
|
||||
|
||||
scaleX = cornerSize;
|
||||
scaleY = cardSize.y - cornerSize * 2;
|
||||
}
|
||||
else if (cardEdges[i].localPosition.y != 0)
|
||||
{
|
||||
posX = 0;
|
||||
posY = Mathf.Sign(cardEdges[i].localPosition.y) * (cardSize.y / 2) - Mathf.Sign(cardEdges[i].localPosition.y) * (cardEdges[i].localScale.y / 2);
|
||||
|
||||
scaleX = cardSize.x - cornerSize * 2;
|
||||
scaleY = cornerSize;
|
||||
}
|
||||
|
||||
nextEdgePos[i] = new Vector2(posX, posY);
|
||||
nextEdgeScale[i] = new Vector2(scaleX, scaleY);
|
||||
}
|
||||
|
||||
nextCenterScale = cardSize - new Vector2(cornerSize * 2, cornerSize * 2);
|
||||
nextPos = cardPosition;
|
||||
|
||||
nextSuperness = cardSuperness;
|
||||
|
||||
nextMin = new Vector2(-cardSize.x / 2, -cardSize.y / 2) + nextPos;
|
||||
nextMax = new Vector2(cardSize.x / 2, cardSize.y / 2) + nextPos;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 113e4a3911e6c4f3e8427bd79605739c
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 85c7285af214d46818136659e00053bd
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -0,0 +1,44 @@
|
||||
/// <summary>
|
||||
/// Credit - ryanslikesocool
|
||||
/// Sourced from - https://github.com/ryanslikesocool/Unity-Card-UI
|
||||
/// </summary>
|
||||
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
namespace UnityEngine.UI.Extensions
|
||||
{
|
||||
|
||||
///Credit where credit is due
|
||||
///https://wiki.unity3d.com/index.php?title=Triangulator
|
||||
[ExecuteInEditMode]
|
||||
public class MeshCreator : MonoBehaviour
|
||||
{
|
||||
public void CreateMesh(List<Vector2> points)
|
||||
{
|
||||
// Create Vector2 vertices
|
||||
Vector2[] vertices2D = points.ToArray();
|
||||
|
||||
// Use the triangulator to get indices for creating triangles
|
||||
Triangulator tr = new Triangulator(vertices2D);
|
||||
int[] indices = tr.Triangulate();
|
||||
|
||||
// Create the Vector3 vertices
|
||||
Vector3[] vertices = new Vector3[vertices2D.Length];
|
||||
for (int i = 0; i < vertices.Length; i++)
|
||||
{
|
||||
vertices[i] = new Vector3(vertices2D[i].x, vertices2D[i].y, 0);
|
||||
}
|
||||
|
||||
// Create the mesh
|
||||
Mesh msh = new Mesh();
|
||||
msh.vertices = vertices;
|
||||
msh.triangles = indices;
|
||||
msh.RecalculateNormals();
|
||||
msh.RecalculateBounds();
|
||||
|
||||
// Set up game object with mesh;
|
||||
GetComponent<MeshFilter>().mesh = msh;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c87d7e3a53e4c4bf9ae3657f0dff98b6
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -0,0 +1,91 @@
|
||||
/// <summary>
|
||||
/// Credit - ryanslikesocool
|
||||
/// Sourced from - https://github.com/ryanslikesocool/Unity-Card-UI
|
||||
/// </summary>
|
||||
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace UnityEngine.UI.Extensions
|
||||
{
|
||||
///The formula for a basic superellipse is
|
||||
///Mathf.Pow(Mathf.Abs(x / a), n) + Mathf.Pow(Mathf.Abs(y / b), n) = 1
|
||||
[ExecuteInEditMode]
|
||||
public class SuperellipsePoints : MonoBehaviour
|
||||
{
|
||||
public float xLimits = 1f;
|
||||
public float yLimits = 1f;
|
||||
[Range(1f, 96f)]
|
||||
public float superness = 4f;
|
||||
|
||||
private float lastXLim;
|
||||
private float lastYLim;
|
||||
private float lastSuper;
|
||||
|
||||
[Space]
|
||||
[Range(1, 32)]
|
||||
public int levelOfDetail = 4;
|
||||
|
||||
private int lastLoD;
|
||||
|
||||
[Space]
|
||||
public Material material;
|
||||
|
||||
private List<Vector2> pointList = new List<Vector2>();
|
||||
|
||||
void Start()
|
||||
{
|
||||
RecalculateSuperellipse();
|
||||
|
||||
GetComponent<MeshRenderer>().material = material;
|
||||
|
||||
lastXLim = xLimits;
|
||||
lastYLim = yLimits;
|
||||
lastSuper = superness;
|
||||
|
||||
lastLoD = levelOfDetail;
|
||||
}
|
||||
|
||||
void Update()
|
||||
{
|
||||
if (lastXLim != xLimits || lastYLim != yLimits || lastSuper != superness || lastLoD != levelOfDetail)
|
||||
{
|
||||
RecalculateSuperellipse();
|
||||
}
|
||||
|
||||
lastXLim = xLimits;
|
||||
lastYLim = yLimits;
|
||||
lastSuper = superness;
|
||||
|
||||
lastLoD = levelOfDetail;
|
||||
}
|
||||
|
||||
void RecalculateSuperellipse()
|
||||
{
|
||||
pointList.Clear();
|
||||
|
||||
float realLoD = levelOfDetail * 4;
|
||||
|
||||
for (float i = 0; i < xLimits; i += 1 / realLoD)
|
||||
{
|
||||
float y = Superellipse(xLimits, yLimits, i, superness);
|
||||
Vector2 tempVecTwo = new Vector2(i, y);
|
||||
pointList.Add(tempVecTwo);
|
||||
}
|
||||
pointList.Add(new Vector2(xLimits, 0));
|
||||
pointList.Add(Vector2.zero);
|
||||
|
||||
GetComponent<MeshCreator>().CreateMesh(pointList);
|
||||
}
|
||||
|
||||
float Superellipse(float a, float b, float x, float n)
|
||||
{
|
||||
float alpha = Mathf.Pow((x / a), n);
|
||||
float beta = 1 - alpha;
|
||||
float y = Mathf.Pow(beta, 1 / n) * b;
|
||||
|
||||
return y;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: df6beaa1c87204b919c209b770d44bc9
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -0,0 +1,132 @@
|
||||
/// <summary>
|
||||
/// Credit - ryanslikesocool
|
||||
/// Sourced from - https://github.com/ryanslikesocool/Unity-Card-UI
|
||||
/// </summary>
|
||||
|
||||
using UnityEngine;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace UnityEngine.UI.Extensions
|
||||
{
|
||||
///Credit where credit is due
|
||||
///https://wiki.unity3d.com/index.php?title=Triangulator
|
||||
[ExecuteInEditMode]
|
||||
public class Triangulator
|
||||
{
|
||||
private List<Vector2> m_points = new List<Vector2>();
|
||||
|
||||
public Triangulator(Vector2[] points)
|
||||
{
|
||||
m_points = new List<Vector2>(points);
|
||||
}
|
||||
|
||||
public int[] Triangulate()
|
||||
{
|
||||
List<int> indices = new List<int>();
|
||||
|
||||
int n = m_points.Count;
|
||||
if (n < 3)
|
||||
return indices.ToArray();
|
||||
|
||||
int[] V = new int[n];
|
||||
if (Area() > 0)
|
||||
{
|
||||
for (int v = 0; v < n; v++)
|
||||
V[v] = v;
|
||||
}
|
||||
else
|
||||
{
|
||||
for (int v = 0; v < n; v++)
|
||||
V[v] = (n - 1) - v;
|
||||
}
|
||||
|
||||
int nv = n;
|
||||
int count = 2 * nv;
|
||||
for (int m = 0, v = nv - 1; nv > 2;)
|
||||
{
|
||||
if ((count--) <= 0)
|
||||
return indices.ToArray();
|
||||
|
||||
int u = v;
|
||||
if (nv <= u)
|
||||
u = 0;
|
||||
v = u + 1;
|
||||
if (nv <= v)
|
||||
v = 0;
|
||||
int w = v + 1;
|
||||
if (nv <= w)
|
||||
w = 0;
|
||||
|
||||
if (Snip(u, v, w, nv, V))
|
||||
{
|
||||
int a, b, c, s, t;
|
||||
a = V[u];
|
||||
b = V[v];
|
||||
c = V[w];
|
||||
indices.Add(a);
|
||||
indices.Add(b);
|
||||
indices.Add(c);
|
||||
m++;
|
||||
for (s = v, t = v + 1; t < nv; s++, t++)
|
||||
V[s] = V[t];
|
||||
nv--;
|
||||
count = 2 * nv;
|
||||
}
|
||||
}
|
||||
|
||||
indices.Reverse();
|
||||
return indices.ToArray();
|
||||
}
|
||||
|
||||
private float Area()
|
||||
{
|
||||
int n = m_points.Count;
|
||||
float A = 0.0f;
|
||||
for (int p = n - 1, q = 0; q < n; p = q++)
|
||||
{
|
||||
Vector2 pval = m_points[p];
|
||||
Vector2 qval = m_points[q];
|
||||
A += pval.x * qval.y - qval.x * pval.y;
|
||||
}
|
||||
return (A * 0.5f);
|
||||
}
|
||||
|
||||
private bool Snip(int u, int v, int w, int n, int[] V)
|
||||
{
|
||||
int p;
|
||||
Vector2 A = m_points[V[u]];
|
||||
Vector2 B = m_points[V[v]];
|
||||
Vector2 C = m_points[V[w]];
|
||||
if (Mathf.Epsilon > (((B.x - A.x) * (C.y - A.y)) - ((B.y - A.y) * (C.x - A.x))))
|
||||
return false;
|
||||
for (p = 0; p < n; p++)
|
||||
{
|
||||
if ((p == u) || (p == v) || (p == w))
|
||||
continue;
|
||||
Vector2 P = m_points[V[p]];
|
||||
if (InsideTriangle(A, B, C, P))
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private bool InsideTriangle(Vector2 A, Vector2 B, Vector2 C, Vector2 P)
|
||||
{
|
||||
float ax, ay, bx, by, cx, cy, apx, apy, bpx, bpy, cpx, cpy;
|
||||
float cCROSSap, bCROSScp, aCROSSbp;
|
||||
|
||||
ax = C.x - B.x; ay = C.y - B.y;
|
||||
bx = A.x - C.x; by = A.y - C.y;
|
||||
cx = B.x - A.x; cy = B.y - A.y;
|
||||
apx = P.x - A.x; apy = P.y - A.y;
|
||||
bpx = P.x - B.x; bpy = P.y - B.y;
|
||||
cpx = P.x - C.x; cpy = P.y - C.y;
|
||||
|
||||
aCROSSbp = ax * bpy - ay * bpx;
|
||||
cCROSSap = cx * apy - cy * apx;
|
||||
bCROSScp = bx * cpy - by * cpx;
|
||||
|
||||
return ((aCROSSbp >= 0.0f) && (bCROSScp >= 0.0f) && (cCROSSap >= 0.0f));
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ac33bc8ebf2744ad6ae91f80f8542546
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -0,0 +1,676 @@
|
||||
/// Credit Beka Westberg
|
||||
/// Sourced from - https://bitbucket.org/UnityUIExtensions/unity-ui-extensions/pull-requests/28
|
||||
/// Updated by SimonDarksideJ - Added some exception management and a SetNewItems to replace the content programmatically
|
||||
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine.EventSystems;
|
||||
using UnityEngine.Events;
|
||||
using System;
|
||||
|
||||
namespace UnityEngine.UI.Extensions
|
||||
{
|
||||
[ExecuteInEditMode]
|
||||
[RequireComponent(typeof(ScrollRect))]
|
||||
[AddComponentMenu("UI/Extensions/ContentSnapScrollHorizontal")]
|
||||
public class ContentScrollSnapHorizontal : MonoBehaviour, IBeginDragHandler, IEndDragHandler
|
||||
{
|
||||
|
||||
[Serializable]
|
||||
public class StartMovementEvent : UnityEvent { }
|
||||
[Serializable]
|
||||
public class CurrentItemChangeEvent : UnityEvent<int> { }
|
||||
[Serializable]
|
||||
public class FoundItemToSnapToEvent : UnityEvent<int> { }
|
||||
[Serializable]
|
||||
public class SnappedToItemEvent : UnityEvent<int> { }
|
||||
|
||||
public bool ignoreInactiveItems = true;
|
||||
public MoveInfo startInfo = new MoveInfo(MoveInfo.IndexType.positionIndex, 0);
|
||||
public GameObject prevButton;
|
||||
public GameObject nextButton;
|
||||
public GameObject pagination;
|
||||
[Tooltip("The velocity below which the scroll rect will start to snap")]
|
||||
public int snappingVelocityThreshold = 50;
|
||||
|
||||
[Header("Paging Info")]
|
||||
[Tooltip("Should the pagination & buttons jump or lerp to the items")]
|
||||
public bool jumpToItem = false;
|
||||
[Tooltip("The time it will take for the pagination or buttons to move between items")]
|
||||
public float lerpTime = .3f;
|
||||
|
||||
[Header("Events")]
|
||||
[SerializeField]
|
||||
[Tooltip("Event is triggered whenever the scroll rect starts to move, even when triggered programmatically")]
|
||||
private StartMovementEvent m_StartMovementEvent = new StartMovementEvent();
|
||||
public StartMovementEvent MovementStarted
|
||||
{
|
||||
get
|
||||
{
|
||||
return m_StartMovementEvent;
|
||||
}
|
||||
set
|
||||
{
|
||||
m_StartMovementEvent = value;
|
||||
}
|
||||
}
|
||||
|
||||
[SerializeField]
|
||||
[Tooltip("Event is triggered whenever the closest item to the center of the scrollrect changes")]
|
||||
private CurrentItemChangeEvent m_CurrentItemChangeEvent = new CurrentItemChangeEvent();
|
||||
public CurrentItemChangeEvent CurrentItemChanged
|
||||
{
|
||||
get
|
||||
{
|
||||
return m_CurrentItemChangeEvent;
|
||||
}
|
||||
set
|
||||
{
|
||||
m_CurrentItemChangeEvent = value;
|
||||
}
|
||||
}
|
||||
|
||||
[SerializeField]
|
||||
[Tooltip("Event is triggered when the ContentSnapScroll decides which item it is going to snap to. Returns the index of the closest position.")]
|
||||
private FoundItemToSnapToEvent m_FoundItemToSnapToEvent = new FoundItemToSnapToEvent();
|
||||
public FoundItemToSnapToEvent ItemFoundToSnap
|
||||
{
|
||||
get
|
||||
{
|
||||
return m_FoundItemToSnapToEvent;
|
||||
}
|
||||
set
|
||||
{
|
||||
m_FoundItemToSnapToEvent = value;
|
||||
}
|
||||
}
|
||||
|
||||
[SerializeField]
|
||||
[Tooltip("Event is triggered when we finally settle on an element. Returns the index of the item's position.")]
|
||||
private SnappedToItemEvent m_SnappedToItemEvent = new SnappedToItemEvent();
|
||||
public SnappedToItemEvent ItemSnappedTo
|
||||
{
|
||||
get
|
||||
{
|
||||
return m_SnappedToItemEvent;
|
||||
}
|
||||
set
|
||||
{
|
||||
m_SnappedToItemEvent = value;
|
||||
}
|
||||
}
|
||||
|
||||
private ScrollRect scrollRect = null;
|
||||
private RectTransform scrollRectTransform = null;
|
||||
private RectTransform contentTransform = null;
|
||||
private List<Vector3> contentPositions = new List<Vector3>();
|
||||
private Vector3 lerpTarget = Vector3.zero;
|
||||
private float totalScrollableWidth = 0;
|
||||
private DrivenRectTransformTracker tracker ;
|
||||
private float mLerpTime = 0;
|
||||
private int _closestItem = 0;
|
||||
private bool mSliding = false;
|
||||
private bool mLerping = false;
|
||||
private bool ContentIsHorizonalLayoutGroup
|
||||
{
|
||||
get
|
||||
{
|
||||
return contentTransform.GetComponent<HorizontalLayoutGroup>() != null;
|
||||
}
|
||||
}
|
||||
|
||||
#region Public Info
|
||||
/// <summary>
|
||||
/// Returns if the SnapScroll is moving
|
||||
/// </summary>
|
||||
public bool Moving
|
||||
{
|
||||
get
|
||||
{
|
||||
return Sliding || Lerping;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns if the SnapScroll is moving because of a touch
|
||||
/// </summary>
|
||||
public bool Sliding
|
||||
{
|
||||
get
|
||||
{
|
||||
return mSliding;
|
||||
}
|
||||
}
|
||||
/// <summary>
|
||||
/// Returns if the SnapScroll is moving programmatically
|
||||
/// </summary>
|
||||
public bool Lerping
|
||||
{
|
||||
get
|
||||
{
|
||||
return mLerping;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the closest item's index
|
||||
/// *Note this is zero based, and based on position not child order
|
||||
/// </summary>
|
||||
public int ClosestItemIndex
|
||||
{
|
||||
get
|
||||
{
|
||||
return contentPositions.IndexOf(FindClosestFrom(contentTransform.localPosition));
|
||||
}
|
||||
}
|
||||
/// <summary>
|
||||
/// Returns the lerpTarget's index
|
||||
/// *Note this is zero-based, and based on position not child order
|
||||
/// </summary>
|
||||
public int LerpTargetIndex
|
||||
{
|
||||
get
|
||||
{
|
||||
return contentPositions.IndexOf(lerpTarget);
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Setup
|
||||
private void Awake()
|
||||
{
|
||||
scrollRect = GetComponent<ScrollRect>();
|
||||
scrollRectTransform = (RectTransform) scrollRect.transform;
|
||||
contentTransform = scrollRect.content;
|
||||
|
||||
if (nextButton)
|
||||
nextButton.GetComponent<Button>().onClick.AddListener(() => { NextItem(); });
|
||||
|
||||
if (prevButton)
|
||||
prevButton.GetComponent<Button>().onClick.AddListener(() => { PreviousItem(); });
|
||||
|
||||
if (IsScrollRectAvailable)
|
||||
{
|
||||
SetupDrivenTransforms();
|
||||
SetupSnapScroll();
|
||||
scrollRect.horizontalNormalizedPosition = 0;
|
||||
_closestItem = 0;
|
||||
GoTo(startInfo);
|
||||
}
|
||||
}
|
||||
|
||||
public void SetNewItems(ref List<Transform> newItems)
|
||||
{
|
||||
if (scrollRect && contentTransform)
|
||||
{
|
||||
for (int i = scrollRect.content.childCount - 1; i >= 0; i--)
|
||||
{
|
||||
Transform child = contentTransform.GetChild(i);
|
||||
child.SetParent(null);
|
||||
GameObject.DestroyImmediate(child.gameObject);
|
||||
}
|
||||
|
||||
foreach (Transform item in newItems)
|
||||
{
|
||||
GameObject newItem = item.gameObject;
|
||||
if (newItem.IsPrefab())
|
||||
{
|
||||
newItem = Instantiate(item.gameObject, contentTransform);
|
||||
}
|
||||
else
|
||||
{
|
||||
newItem.transform.SetParent(contentTransform);
|
||||
}
|
||||
}
|
||||
|
||||
SetupDrivenTransforms();
|
||||
SetupSnapScroll();
|
||||
scrollRect.horizontalNormalizedPosition = 0;
|
||||
_closestItem = 0;
|
||||
GoTo(startInfo);
|
||||
}
|
||||
}
|
||||
|
||||
private bool IsScrollRectAvailable
|
||||
{
|
||||
get
|
||||
{
|
||||
if (scrollRect &&
|
||||
contentTransform &&
|
||||
contentTransform.childCount > 0)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private void OnDisable()
|
||||
{
|
||||
tracker.Clear();
|
||||
}
|
||||
|
||||
private void SetupDrivenTransforms()
|
||||
{
|
||||
tracker = new DrivenRectTransformTracker();
|
||||
tracker.Clear();
|
||||
|
||||
//So that we can calculate everything correctly
|
||||
foreach (RectTransform child in contentTransform)
|
||||
{
|
||||
tracker.Add(this, child, DrivenTransformProperties.Anchors);
|
||||
|
||||
child.anchorMax = new Vector2(0, 1);
|
||||
child.anchorMin = new Vector2(0, 1);
|
||||
}
|
||||
}
|
||||
|
||||
private void SetupSnapScroll()
|
||||
{
|
||||
if (ContentIsHorizonalLayoutGroup)
|
||||
{
|
||||
//because you can't get the anchored positions of UI elements
|
||||
//when they are in a layout group (as far as I could tell)
|
||||
SetupWithHorizontalLayoutGroup();
|
||||
}
|
||||
else
|
||||
{
|
||||
SetupWithCalculatedSpacing();
|
||||
}
|
||||
}
|
||||
|
||||
private void SetupWithHorizontalLayoutGroup()
|
||||
{
|
||||
HorizontalLayoutGroup horizLayoutGroup = contentTransform.GetComponent<HorizontalLayoutGroup>();
|
||||
float childTotalWidths = 0;
|
||||
int activeChildren = 0;
|
||||
for (int i = 0; i < contentTransform.childCount; i++)
|
||||
{
|
||||
if (!ignoreInactiveItems || contentTransform.GetChild(i).gameObject.activeInHierarchy)
|
||||
{
|
||||
childTotalWidths += ((RectTransform)contentTransform.GetChild(i)).sizeDelta.x;
|
||||
activeChildren++;
|
||||
}
|
||||
}
|
||||
float spacingTotal = (activeChildren - 1) * horizLayoutGroup.spacing;
|
||||
float totalWidth = childTotalWidths + spacingTotal + horizLayoutGroup.padding.left + horizLayoutGroup.padding.right;
|
||||
|
||||
contentTransform.sizeDelta = new Vector2(totalWidth, contentTransform.sizeDelta.y);
|
||||
float scrollRectWidth = Mathf.Min(((RectTransform)contentTransform.GetChild(0)).sizeDelta.x, ((RectTransform)contentTransform.GetChild(contentTransform.childCount - 1)).sizeDelta.x);
|
||||
|
||||
/*---If the scroll view is set to stretch width this breaks stuff---*/
|
||||
scrollRectTransform.sizeDelta = new Vector2(scrollRectWidth, scrollRectTransform.sizeDelta.y);
|
||||
|
||||
contentPositions = new List<Vector3>();
|
||||
float widthOfScrollRect = scrollRectTransform.sizeDelta.x;
|
||||
totalScrollableWidth = totalWidth - widthOfScrollRect;
|
||||
float checkedChildrenTotalWidths = horizLayoutGroup.padding.left;
|
||||
int activeChildrenBeforeSelf = 0;
|
||||
for (int i = 0; i < contentTransform.childCount; i++)
|
||||
{
|
||||
if (!ignoreInactiveItems || contentTransform.GetChild(i).gameObject.activeInHierarchy)
|
||||
{
|
||||
float widthOfSelf = ((RectTransform)contentTransform.GetChild(i)).sizeDelta.x;
|
||||
float offset = checkedChildrenTotalWidths + (horizLayoutGroup.spacing * activeChildrenBeforeSelf) + ((widthOfSelf - widthOfScrollRect) / 2);
|
||||
scrollRect.horizontalNormalizedPosition = offset / totalScrollableWidth;
|
||||
contentPositions.Add(contentTransform.localPosition);
|
||||
|
||||
checkedChildrenTotalWidths += widthOfSelf;
|
||||
activeChildrenBeforeSelf++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void SetupWithCalculatedSpacing()
|
||||
{
|
||||
//we need them in order from left to right for pagination & buttons & our scrollRectWidth
|
||||
List<RectTransform> childrenFromLeftToRight = new List<RectTransform>();
|
||||
for (int i = 0; i < contentTransform.childCount; i++)
|
||||
{
|
||||
if (!ignoreInactiveItems || contentTransform.GetChild(i).gameObject.activeInHierarchy)
|
||||
{
|
||||
RectTransform childBeingSorted = ((RectTransform)contentTransform.GetChild(i));
|
||||
int insertIndex = childrenFromLeftToRight.Count;
|
||||
for (int j = 0; j < childrenFromLeftToRight.Count; j++)
|
||||
{
|
||||
if (DstFromTopLeftOfTransformToTopLeftOfParent(childBeingSorted).x < DstFromTopLeftOfTransformToTopLeftOfParent(childrenFromLeftToRight[j]).x)
|
||||
{
|
||||
insertIndex = j;
|
||||
break;
|
||||
}
|
||||
}
|
||||
childrenFromLeftToRight.Insert(insertIndex, childBeingSorted);
|
||||
}
|
||||
}
|
||||
RectTransform childFurthestToTheRight = childrenFromLeftToRight[childrenFromLeftToRight.Count - 1];
|
||||
float totalWidth = DstFromTopLeftOfTransformToTopLeftOfParent(childFurthestToTheRight).x + childFurthestToTheRight.sizeDelta.x;
|
||||
|
||||
contentTransform.sizeDelta = new Vector2(totalWidth, contentTransform.sizeDelta.y);
|
||||
float scrollRectWidth = Mathf.Min(childrenFromLeftToRight[0].sizeDelta.x, childrenFromLeftToRight[childrenFromLeftToRight.Count - 1].sizeDelta.x);
|
||||
|
||||
// Note: sizeDelta will not be calculated properly if the scroll view is set to stretch width.
|
||||
scrollRectTransform.sizeDelta = new Vector2(scrollRectWidth, scrollRectTransform.sizeDelta.y);
|
||||
|
||||
contentPositions = new List<Vector3>();
|
||||
float widthOfScrollRect = scrollRectTransform.sizeDelta.x;
|
||||
totalScrollableWidth = totalWidth - widthOfScrollRect;
|
||||
for (int i = 0; i < childrenFromLeftToRight.Count; i++)
|
||||
{
|
||||
float offset = DstFromTopLeftOfTransformToTopLeftOfParent(childrenFromLeftToRight[i]).x + ((childrenFromLeftToRight[i].sizeDelta.x - widthOfScrollRect) / 2);
|
||||
scrollRect.horizontalNormalizedPosition = offset / totalScrollableWidth;
|
||||
contentPositions.Add(contentTransform.localPosition);
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Public Movement Functions
|
||||
/// <summary>
|
||||
/// Function for going to a specific screen.
|
||||
/// *Note the index is based on a zero-starting index.
|
||||
/// </summary>
|
||||
/// <param name="info">All of the info about how you want it to move</param>
|
||||
public void GoTo(MoveInfo info)
|
||||
{
|
||||
if (!Moving && info.index != ClosestItemIndex)
|
||||
{
|
||||
MovementStarted.Invoke();
|
||||
}
|
||||
|
||||
if (info.indexType == MoveInfo.IndexType.childIndex)
|
||||
{
|
||||
mLerpTime = info.duration;
|
||||
GoToChild(info.index, info.jump);
|
||||
}
|
||||
else if (info.indexType == MoveInfo.IndexType.positionIndex)
|
||||
{
|
||||
mLerpTime = info.duration;
|
||||
GoToContentPos(info.index, info.jump);
|
||||
}
|
||||
}
|
||||
|
||||
private void GoToChild(int index, bool jump)
|
||||
{
|
||||
int clampedIndex = Mathf.Clamp(index, 0, contentPositions.Count - 1); //contentPositions amount == the amount of available children
|
||||
|
||||
if (ContentIsHorizonalLayoutGroup) //the contentPositions are in child order
|
||||
{
|
||||
lerpTarget = contentPositions[clampedIndex];
|
||||
if (jump)
|
||||
{
|
||||
contentTransform.localPosition = lerpTarget;
|
||||
}
|
||||
else
|
||||
{
|
||||
StopMovement();
|
||||
StartCoroutine("LerpToContent");
|
||||
}
|
||||
}
|
||||
else //the contentPositions are in order from left -> right;
|
||||
{
|
||||
int availableChildIndex = 0; //an available child is one we can snap to
|
||||
Vector3 previousContentTransformPos = contentTransform.localPosition;
|
||||
for (int i = 0; i < contentTransform.childCount; i++)
|
||||
{
|
||||
if (!ignoreInactiveItems || contentTransform.GetChild(i).gameObject.activeInHierarchy)
|
||||
{
|
||||
if (availableChildIndex == clampedIndex)
|
||||
{
|
||||
RectTransform startChild = (RectTransform) contentTransform.GetChild(i);
|
||||
float offset = DstFromTopLeftOfTransformToTopLeftOfParent(startChild).x + ((startChild.sizeDelta.x - scrollRectTransform.sizeDelta.x) / 2);
|
||||
scrollRect.horizontalNormalizedPosition = offset / totalScrollableWidth;
|
||||
lerpTarget = contentTransform.localPosition;
|
||||
if (!jump)
|
||||
{
|
||||
contentTransform.localPosition = previousContentTransformPos;
|
||||
StopMovement();
|
||||
StartCoroutine("LerpToContent");
|
||||
}
|
||||
return;
|
||||
}
|
||||
availableChildIndex++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void GoToContentPos(int index, bool jump)
|
||||
{
|
||||
int clampedIndex = Mathf.Clamp(index, 0, contentPositions.Count - 1); //contentPositions amount == the amount of available children
|
||||
|
||||
//the content positions are all in order from left -> right
|
||||
//which is what we want so there's no need to check
|
||||
|
||||
lerpTarget = contentPositions[clampedIndex];
|
||||
if (jump)
|
||||
{
|
||||
contentTransform.localPosition = lerpTarget;
|
||||
}
|
||||
else
|
||||
{
|
||||
StopMovement();
|
||||
StartCoroutine("LerpToContent");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Function for going to the next item
|
||||
/// *Note the next item is the item to the right of the current item, this is not based on child order
|
||||
/// </summary>
|
||||
public void NextItem()
|
||||
{
|
||||
int index;
|
||||
if (Sliding)
|
||||
{
|
||||
index = ClosestItemIndex + 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
index = LerpTargetIndex + 1;
|
||||
}
|
||||
MoveInfo info = new MoveInfo(MoveInfo.IndexType.positionIndex, index, jumpToItem, lerpTime);
|
||||
GoTo(info);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Function for going to the previous item
|
||||
/// *Note the next item is the item to the left of the current item, this is not based on child order
|
||||
/// </summary>
|
||||
public void PreviousItem()
|
||||
{
|
||||
int index;
|
||||
if (Sliding)
|
||||
{
|
||||
index = ClosestItemIndex - 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
index = LerpTargetIndex - 1;
|
||||
}
|
||||
MoveInfo info = new MoveInfo(MoveInfo.IndexType.positionIndex, index, jumpToItem, lerpTime);
|
||||
GoTo(info);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Function for recalculating the size of the content & the snap positions, such as when you remove or add a child
|
||||
/// </summary>
|
||||
public void UpdateLayout()
|
||||
{
|
||||
SetupDrivenTransforms();
|
||||
SetupSnapScroll();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Recalculates the size of the content & snap positions, and moves to a new item afterwards.
|
||||
/// </summary>
|
||||
/// <param name="info">All of the info about how you want it to move</param>
|
||||
public void UpdateLayoutAndMoveTo(MoveInfo info)
|
||||
{
|
||||
SetupDrivenTransforms();
|
||||
SetupSnapScroll();
|
||||
GoTo(info);
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Behind the Scenes Movement stuff
|
||||
public void OnBeginDrag(PointerEventData ped)
|
||||
{
|
||||
if (contentPositions.Count < 2)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
StopMovement();
|
||||
if (!Moving)
|
||||
{
|
||||
MovementStarted.Invoke();
|
||||
}
|
||||
}
|
||||
|
||||
public void OnEndDrag(PointerEventData ped)
|
||||
{
|
||||
if (contentPositions.Count <= 1)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (IsScrollRectAvailable)
|
||||
{
|
||||
StartCoroutine("SlideAndLerp");
|
||||
}
|
||||
}
|
||||
|
||||
private void Update()
|
||||
{
|
||||
if (IsScrollRectAvailable)
|
||||
{
|
||||
if (_closestItem != ClosestItemIndex)
|
||||
{
|
||||
CurrentItemChanged.Invoke(ClosestItemIndex);
|
||||
ChangePaginationInfo(ClosestItemIndex);
|
||||
_closestItem = ClosestItemIndex;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private IEnumerator SlideAndLerp()
|
||||
{
|
||||
mSliding = true;
|
||||
while (Mathf.Abs(scrollRect.velocity.x) > snappingVelocityThreshold)
|
||||
{
|
||||
yield return null;
|
||||
}
|
||||
|
||||
lerpTarget = FindClosestFrom(contentTransform.localPosition);
|
||||
ItemFoundToSnap.Invoke(LerpTargetIndex);
|
||||
|
||||
while (Vector3.Distance(contentTransform.localPosition, lerpTarget) > 1)
|
||||
{
|
||||
contentTransform.localPosition = Vector3.Lerp(scrollRect.content.localPosition, lerpTarget, 7.5f * Time.deltaTime);
|
||||
yield return null;
|
||||
}
|
||||
mSliding = false;
|
||||
scrollRect.velocity = Vector2.zero;
|
||||
contentTransform.localPosition = lerpTarget;
|
||||
ItemSnappedTo.Invoke(LerpTargetIndex);
|
||||
}
|
||||
|
||||
private IEnumerator LerpToContent()
|
||||
{
|
||||
ItemFoundToSnap.Invoke(LerpTargetIndex);
|
||||
mLerping = true;
|
||||
Vector3 originalContentPos = contentTransform.localPosition;
|
||||
float elapsedTime = 0;
|
||||
while (elapsedTime < mLerpTime)
|
||||
{
|
||||
elapsedTime += Time.deltaTime;
|
||||
contentTransform.localPosition = Vector3.Lerp(originalContentPos, lerpTarget, (elapsedTime / mLerpTime));
|
||||
yield return null;
|
||||
}
|
||||
ItemSnappedTo.Invoke(LerpTargetIndex);
|
||||
mLerping = false;
|
||||
}
|
||||
#endregion
|
||||
|
||||
private void StopMovement()
|
||||
{
|
||||
scrollRect.velocity = Vector2.zero;
|
||||
StopCoroutine("SlideAndLerp");
|
||||
StopCoroutine("LerpToContent");
|
||||
}
|
||||
|
||||
private void ChangePaginationInfo(int targetScreen)
|
||||
{
|
||||
if (pagination)
|
||||
for (int i = 0; i < pagination.transform.childCount; i++)
|
||||
{
|
||||
pagination.transform.GetChild(i).GetComponent<Toggle>().isOn = (targetScreen == i);
|
||||
}
|
||||
}
|
||||
|
||||
private Vector2 DstFromTopLeftOfTransformToTopLeftOfParent(RectTransform rt)
|
||||
{
|
||||
//gets rid of any pivot weirdness
|
||||
return new Vector2(rt.anchoredPosition.x - (rt.sizeDelta.x * rt.pivot.x), rt.anchoredPosition.y + (rt.sizeDelta.y * (1 - rt.pivot.y)));
|
||||
}
|
||||
|
||||
private Vector3 FindClosestFrom(Vector3 start)
|
||||
{
|
||||
Vector3 closest = Vector3.zero;
|
||||
float distance = Mathf.Infinity;
|
||||
|
||||
foreach (Vector3 position in contentPositions)
|
||||
{
|
||||
if (Vector3.Distance(start, position) < distance)
|
||||
{
|
||||
distance = Vector3.Distance(start, position);
|
||||
closest = position;
|
||||
}
|
||||
}
|
||||
return closest;
|
||||
}
|
||||
|
||||
[System.Serializable]
|
||||
public struct MoveInfo
|
||||
{
|
||||
public enum IndexType { childIndex, positionIndex }
|
||||
[Tooltip("Child Index means the Index corresponds to the content item at that index in the hierarchy.\n" +
|
||||
"Position Index means the Index corresponds to the content item in that snap position.\n" +
|
||||
"A higher Position Index in a Horizontal Scroll Snap means it would be further to the right.")]
|
||||
public IndexType indexType;
|
||||
[Tooltip("Zero based")]
|
||||
public int index;
|
||||
[Tooltip("If this is true the snap scroll will jump to the index, otherwise it will lerp there.")]
|
||||
public bool jump;
|
||||
[Tooltip("If jump is false this is the time it will take to lerp to the index")]
|
||||
public float duration;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a MoveInfo that jumps to the index
|
||||
/// </summary>
|
||||
/// <param name="_indexType">Whether you want to get the child at the index or the snap position at the index</param>
|
||||
/// <param name="_index">Where you want it to jump</param>
|
||||
public MoveInfo(IndexType _indexType, int _index)
|
||||
{
|
||||
indexType = _indexType;
|
||||
index = _index;
|
||||
jump = true;
|
||||
duration = 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a MoveInfo
|
||||
/// </summary>
|
||||
/// <param name="_indexType">Whether you want to get the child at the index or the snap position at the index</param>
|
||||
/// <param name="_index">Where you want it to jump</param>
|
||||
/// <param name="_jump">Whether you want it to jump or lerp to the index</param>
|
||||
/// <param name="_duration">How long it takes to lerp to the index</param>
|
||||
public MoveInfo(IndexType _indexType, int _index, bool _jump, float _duration)
|
||||
{
|
||||
indexType = _indexType;
|
||||
index = _index;
|
||||
jump = _jump;
|
||||
duration = _duration;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 6b5e39a5eb1058947a287d21183b661c
|
||||
timeCreated: 1511208372
|
||||
licenseType: Free
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -0,0 +1,93 @@
|
||||
/// <summary>
|
||||
/// Curved Layout Group Created by Freezy - http://www.ElicitIce.com
|
||||
/// Posted on Unity Forums http://forum.unity3d.com/threads/curved-layout.403985/
|
||||
///
|
||||
/// Free for any use and alteration, source code may not be sold without my permission.
|
||||
/// If you make improvements on this script please share them with the community.
|
||||
///
|
||||
/// </summary>
|
||||
|
||||
namespace UnityEngine.UI.Extensions
|
||||
{
|
||||
/// <summary>
|
||||
/// TODO:
|
||||
/// - add automatic child sizing, like in the HorizontalOrVerticalLayoutGroup.cs
|
||||
/// - nicer anchor handling for initial child positions
|
||||
/// </summary>
|
||||
[AddComponentMenu("Layout/Extensions/Curved Layout")]
|
||||
public class CurvedLayout : LayoutGroup {
|
||||
public Vector3 CurveOffset;
|
||||
|
||||
// Yes these two could be combined into a single vector
|
||||
// but this makes it easier to use?
|
||||
[Tooltip("axis along which to place the items, Normalized before use")]
|
||||
public Vector3 itemAxis;
|
||||
[Tooltip("size of each item along the Normalized axis")]
|
||||
public float itemSize;
|
||||
|
||||
// the slope can be moved by altering this setting, it could be constrained to the 0-1 range, but other values are useful for animations
|
||||
public float centerpoint = 0.5f;
|
||||
|
||||
protected override void OnEnable() { base.OnEnable(); CalculateRadial(); }
|
||||
public override void SetLayoutHorizontal() {
|
||||
}
|
||||
public override void SetLayoutVertical() {
|
||||
}
|
||||
public override void CalculateLayoutInputVertical() {
|
||||
CalculateRadial();
|
||||
}
|
||||
public override void CalculateLayoutInputHorizontal() {
|
||||
CalculateRadial();
|
||||
}
|
||||
#if UNITY_EDITOR
|
||||
protected override void OnValidate() {
|
||||
base.OnValidate();
|
||||
CalculateRadial();
|
||||
}
|
||||
#endif
|
||||
|
||||
void CalculateRadial() {
|
||||
m_Tracker.Clear();
|
||||
if (transform.childCount == 0)
|
||||
return;
|
||||
|
||||
//one liner for figuring out the desired pivot (should be moved into a utility function)
|
||||
Vector2 pivot = new Vector2(((int)childAlignment % 3) * 0.5f, ((int)childAlignment / 3) * 0.5f);
|
||||
|
||||
//this seems to work ok-ish
|
||||
Vector3 lastPos = new Vector3(
|
||||
GetStartOffset(0, GetTotalPreferredSize(0)),
|
||||
GetStartOffset(1, GetTotalPreferredSize(1)),
|
||||
0f
|
||||
);
|
||||
|
||||
// 0 = first, 1 = last child
|
||||
float lerp = 0;
|
||||
//no need to catch divide by 0 as childCount > 0
|
||||
float step = 1f / transform.childCount;
|
||||
|
||||
//normalize and create a distance between items
|
||||
var dist = itemAxis.normalized * itemSize;
|
||||
|
||||
for (int i = 0; i < transform.childCount; i++) {
|
||||
RectTransform child = (RectTransform)transform.GetChild(i);
|
||||
if (child != null) {
|
||||
//stop the user from altering certain values in the editor
|
||||
m_Tracker.Add(this, child,
|
||||
DrivenTransformProperties.Anchors |
|
||||
DrivenTransformProperties.AnchoredPosition |
|
||||
DrivenTransformProperties.Pivot);
|
||||
Vector3 vPos = lastPos + dist;
|
||||
|
||||
child.localPosition = lastPos = vPos + (lerp - centerpoint) * CurveOffset;
|
||||
|
||||
child.pivot = pivot;
|
||||
//child anchors are not yet calculated, each child should set it's own size for now
|
||||
child.anchorMin = child.anchorMax = new Vector2(0.5f, 0.5f);
|
||||
lerp += step;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 8de815c5a2c592d4da033da3146168c5
|
||||
timeCreated: 1463330156
|
||||
licenseType: Pro
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d1ddf7c4a2094c8429c834b8c71bb812
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 6096cd9225d267f4da3b54947c75809c
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -0,0 +1,72 @@
|
||||
/// Credit setchi (https://github.com/setchi)
|
||||
/// Sourced from - https://github.com/setchi/FancyScrollView
|
||||
|
||||
|
||||
namespace UnityEngine.UI.Extensions
|
||||
{
|
||||
/// <summary>
|
||||
/// <see cref="FancyScrollView{TItemData, TContext}"/> のセルを実装するための抽象基底クラス.
|
||||
/// <see cref="FancyCell{TItemData, TContext}.Context"/> が不要な場合は
|
||||
/// 代わりに <see cref="FancyCell{TItemData}"/> を使用します.
|
||||
/// </summary>
|
||||
/// <typeparam name="TItemData">アイテムのデータ型.</typeparam>
|
||||
/// <typeparam name="TContext"><see cref="Context"/> の型.</typeparam>
|
||||
public abstract class FancyCell<TItemData, TContext> : MonoBehaviour where TContext : class, new()
|
||||
{
|
||||
/// <summary>
|
||||
/// このセルで表示しているデータのインデックス.
|
||||
/// </summary>
|
||||
public int Index { get; set; } = -1;
|
||||
|
||||
/// <summary>
|
||||
/// このセルの可視状態.
|
||||
/// </summary>
|
||||
public virtual bool IsVisible => gameObject.activeSelf;
|
||||
|
||||
/// <summary>
|
||||
/// <see cref="FancyScrollView{TItemData, TContext}.Context"/> の参照.
|
||||
/// セルとスクロールビュー間で同じインスタンスが共有されます. 情報の受け渡しや状態の保持に使用します.
|
||||
/// </summary>
|
||||
protected TContext Context { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// <see cref="Context"/> をセットします.
|
||||
/// </summary>
|
||||
/// <param name="context">コンテキスト.</param>
|
||||
public virtual void SetContext(TContext context) => Context = context;
|
||||
|
||||
/// <summary>
|
||||
/// 初期化を行います.
|
||||
/// </summary>
|
||||
public virtual void Initialize() { }
|
||||
|
||||
/// <summary>
|
||||
/// このセルの可視状態を設定します.
|
||||
/// </summary>
|
||||
/// <param name="visible">可視状態なら <c>true</c>, 非可視状態なら <c>false</c>.</param>
|
||||
public virtual void SetVisible(bool visible) => gameObject.SetActive(visible);
|
||||
|
||||
/// <summary>
|
||||
/// アイテムデータに基づいてこのセルの表示内容を更新します.
|
||||
/// </summary>
|
||||
/// <param name="itemData">アイテムデータ.</param>
|
||||
public abstract void UpdateContent(TItemData itemData);
|
||||
|
||||
/// <summary>
|
||||
/// <c>0.0f</c> ~ <c>1.0f</c> の値に基づいてこのセルのスクロール位置を更新します.
|
||||
/// </summary>
|
||||
/// <param name="position">ビューポート範囲の正規化されたスクロール位置.</param>
|
||||
public abstract void UpdatePosition(float position);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <see cref="FancyScrollView{TItemData}"/> のセルを実装するための抽象基底クラス.
|
||||
/// </summary>
|
||||
/// <typeparam name="TItemData">アイテムのデータ型.</typeparam>
|
||||
/// <seealso cref="FancyCell{TItemData, TContext}"/>
|
||||
public abstract class FancyCell<TItemData> : FancyCell<TItemData, NullContext>
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public sealed override void SetContext(NullContext context) => base.SetContext(context);
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 6610dee308450ec40899aeedfd85e972
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -0,0 +1,216 @@
|
||||
/// Credit setchi (https://github.com/setchi)
|
||||
/// Sourced from - https://github.com/setchi/FancyScrollView
|
||||
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace UnityEngine.UI.Extensions
|
||||
{
|
||||
/// <summary>
|
||||
/// スクロールビューを実装するための抽象基底クラス.
|
||||
/// 無限スクロールおよびスナップに対応しています.
|
||||
/// <see cref="FancyScrollView{TItemData, TContext}.Context"/> が不要な場合は
|
||||
/// 代わりに <see cref="FancyScrollView{TItemData}"/> を使用します.
|
||||
/// </summary>
|
||||
/// <typeparam name="TItemData">アイテムのデータ型.</typeparam>
|
||||
/// <typeparam name="TContext"><see cref="Context"/> の型.</typeparam>
|
||||
public abstract class FancyScrollView<TItemData, TContext> : MonoBehaviour where TContext : class, new()
|
||||
{
|
||||
/// <summary>
|
||||
/// セル同士の間隔.
|
||||
/// </summary>
|
||||
[SerializeField, Range(1e-2f, 1f)] protected float cellInterval = 0.2f;
|
||||
|
||||
/// <summary>
|
||||
/// スクロール位置の基準.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// たとえば、 <c>0.5</c> を指定してスクロール位置が <c>0</c> の場合, 中央に最初のセルが配置されます.
|
||||
/// </remarks>
|
||||
[SerializeField, Range(0f, 1f)] protected float scrollOffset = 0.5f;
|
||||
|
||||
/// <summary>
|
||||
/// セルを循環して配置させるどうか.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <c>true</c> にすると最後のセルの後に最初のセル, 最初のセルの前に最後のセルが並ぶようになります.
|
||||
/// 無限スクロールを実装する場合は <c>true</c> を指定します.
|
||||
/// </remarks>
|
||||
[SerializeField] protected bool loop = false;
|
||||
|
||||
/// <summary>
|
||||
/// セルの親要素となる <c>Transform</c>.
|
||||
/// </summary>
|
||||
[SerializeField] protected Transform cellContainer = default;
|
||||
|
||||
readonly IList<FancyCell<TItemData, TContext>> pool = new List<FancyCell<TItemData, TContext>>();
|
||||
|
||||
/// <summary>
|
||||
/// 初期化済みかどうか.
|
||||
/// </summary>
|
||||
protected bool initialized;
|
||||
|
||||
/// <summary>
|
||||
/// 現在のスクロール位置.
|
||||
/// </summary>
|
||||
protected float currentPosition;
|
||||
|
||||
/// <summary>
|
||||
/// セルの Prefab.
|
||||
/// </summary>
|
||||
protected abstract GameObject CellPrefab { get; }
|
||||
|
||||
/// <summary>
|
||||
/// アイテム一覧のデータ.
|
||||
/// </summary>
|
||||
protected IList<TItemData> ItemsSource { get; set; } = new List<TItemData>();
|
||||
|
||||
/// <summary>
|
||||
/// <typeparamref name="TContext"/> のインスタンス.
|
||||
/// セルとスクロールビュー間で同じインスタンスが共有されます. 情報の受け渡しや状態の保持に使用します.
|
||||
/// </summary>
|
||||
protected TContext Context { get; } = new TContext();
|
||||
|
||||
/// <summary>
|
||||
/// 初期化を行います.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// 最初にセルが生成される直前に呼び出されます.
|
||||
/// </remarks>
|
||||
protected virtual void Initialize() { }
|
||||
|
||||
/// <summary>
|
||||
/// 渡されたアイテム一覧に基づいて表示内容を更新します.
|
||||
/// </summary>
|
||||
/// <param name="itemsSource">アイテム一覧.</param>
|
||||
protected virtual void UpdateContents(IList<TItemData> itemsSource)
|
||||
{
|
||||
ItemsSource = itemsSource;
|
||||
Refresh();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// セルのレイアウトを強制的に更新します.
|
||||
/// </summary>
|
||||
protected virtual void Relayout() => UpdatePosition(currentPosition, false);
|
||||
|
||||
/// <summary>
|
||||
/// セルのレイアウトと表示内容を強制的に更新します.
|
||||
/// </summary>
|
||||
protected virtual void Refresh() => UpdatePosition(currentPosition, true);
|
||||
|
||||
/// <summary>
|
||||
/// スクロール位置を更新します.
|
||||
/// </summary>
|
||||
/// <param name="position">スクロール位置.</param>
|
||||
protected virtual void UpdatePosition(float position) => UpdatePosition(position, false);
|
||||
|
||||
void UpdatePosition(float position, bool forceRefresh)
|
||||
{
|
||||
if (!initialized)
|
||||
{
|
||||
Initialize();
|
||||
initialized = true;
|
||||
}
|
||||
|
||||
currentPosition = position;
|
||||
|
||||
var p = position - scrollOffset / cellInterval;
|
||||
var firstIndex = Mathf.CeilToInt(p);
|
||||
var firstPosition = (Mathf.Ceil(p) - p) * cellInterval;
|
||||
|
||||
if (firstPosition + pool.Count * cellInterval < 1f)
|
||||
{
|
||||
ResizePool(firstPosition);
|
||||
}
|
||||
|
||||
UpdateCells(firstPosition, firstIndex, forceRefresh);
|
||||
}
|
||||
|
||||
void ResizePool(float firstPosition)
|
||||
{
|
||||
Debug.Assert(CellPrefab != null);
|
||||
Debug.Assert(cellContainer != null);
|
||||
|
||||
var addCount = Mathf.CeilToInt((1f - firstPosition) / cellInterval) - pool.Count;
|
||||
for (var i = 0; i < addCount; i++)
|
||||
{
|
||||
var cell = Instantiate(CellPrefab, cellContainer).GetComponent<FancyCell<TItemData, TContext>>();
|
||||
if (cell == null)
|
||||
{
|
||||
throw new MissingComponentException(string.Format(
|
||||
"FancyCell<{0}, {1}> component not found in {2}.",
|
||||
typeof(TItemData).FullName, typeof(TContext).FullName, CellPrefab.name));
|
||||
}
|
||||
|
||||
cell.SetContext(Context);
|
||||
cell.Initialize();
|
||||
cell.SetVisible(false);
|
||||
pool.Add(cell);
|
||||
}
|
||||
}
|
||||
|
||||
void UpdateCells(float firstPosition, int firstIndex, bool forceRefresh)
|
||||
{
|
||||
for (var i = 0; i < pool.Count; i++)
|
||||
{
|
||||
var index = firstIndex + i;
|
||||
var position = firstPosition + i * cellInterval;
|
||||
var cell = pool[CircularIndex(index, pool.Count)];
|
||||
|
||||
if (loop)
|
||||
{
|
||||
index = CircularIndex(index, ItemsSource.Count);
|
||||
}
|
||||
|
||||
if (index < 0 || index >= ItemsSource.Count || position > 1f)
|
||||
{
|
||||
cell.SetVisible(false);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (forceRefresh || cell.Index != index || !cell.IsVisible)
|
||||
{
|
||||
cell.Index = index;
|
||||
cell.SetVisible(true);
|
||||
cell.UpdateContent(ItemsSource[index]);
|
||||
}
|
||||
|
||||
cell.UpdatePosition(position);
|
||||
}
|
||||
}
|
||||
|
||||
int CircularIndex(int i, int size) => size < 1 ? 0 : i < 0 ? size - 1 + (i + 1) % size : i % size;
|
||||
|
||||
#if UNITY_EDITOR
|
||||
bool cachedLoop;
|
||||
float cachedCellInterval, cachedScrollOffset;
|
||||
|
||||
void LateUpdate()
|
||||
{
|
||||
if (cachedLoop != loop ||
|
||||
cachedCellInterval != cellInterval ||
|
||||
cachedScrollOffset != scrollOffset)
|
||||
{
|
||||
cachedLoop = loop;
|
||||
cachedCellInterval = cellInterval;
|
||||
cachedScrollOffset = scrollOffset;
|
||||
|
||||
UpdatePosition(currentPosition);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <see cref="FancyScrollView{TItemData}"/> のコンテキストクラス.
|
||||
/// </summary>
|
||||
public sealed class NullContext { }
|
||||
|
||||
/// <summary>
|
||||
/// スクロールビューを実装するための抽象基底クラス.
|
||||
/// 無限スクロールおよびスナップに対応しています.
|
||||
/// </summary>
|
||||
/// <typeparam name="TItemData"></typeparam>
|
||||
/// <seealso cref="FancyScrollView{TItemData, TContext}"/>
|
||||
public abstract class FancyScrollView<TItemData> : FancyScrollView<TItemData, NullContext> { }
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 0e38f4de7b1b5a5429228884014d12ec
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c7768f2982b0142ab876d2bb4b597646
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -0,0 +1,72 @@
|
||||
/// Credit setchi (https://github.com/setchi)
|
||||
/// Sourced from - https://github.com/setchi/FancyScrollView
|
||||
|
||||
using System.Linq;
|
||||
|
||||
namespace UnityEngine.UI.Extensions
|
||||
{
|
||||
/// <summary>
|
||||
/// 複数の <see cref="FancyCell{TItemData, TContext}"/> を持つセルグループ実装するための抽象基底クラス.
|
||||
/// </summary>
|
||||
/// <typeparam name="TItemData">アイテムのデータ型.</typeparam>
|
||||
/// <typeparam name="TContext"><see cref="FancyCell{TItemData, TContext}.Context"/> の型.</typeparam>
|
||||
public abstract class FancyCellGroup<TItemData, TContext> : FancyCell<TItemData[], TContext>
|
||||
where TContext : class, IFancyCellGroupContext, new()
|
||||
{
|
||||
/// <summary>
|
||||
/// このグループで表示するセルの配列.
|
||||
/// </summary>
|
||||
protected virtual FancyCell<TItemData, TContext>[] Cells { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// このグループで表示するセルの配列をインスタンス化します.
|
||||
/// </summary>
|
||||
/// <returns>このグループで表示するセルの配列.</returns>
|
||||
protected virtual FancyCell<TItemData, TContext>[] InstantiateCells()
|
||||
{
|
||||
return Enumerable.Range(0, Context.GetGroupCount())
|
||||
.Select(_ => Instantiate(Context.CellTemplate, transform))
|
||||
.Select(x => x.GetComponent<FancyCell<TItemData, TContext>>())
|
||||
.ToArray();
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void Initialize()
|
||||
{
|
||||
Cells = InstantiateCells();
|
||||
Debug.Assert(Cells.Length == Context.GetGroupCount());
|
||||
|
||||
for (var i = 0; i < Cells.Length; i++)
|
||||
{
|
||||
Cells[i].SetContext(Context);
|
||||
Cells[i].Initialize();
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void UpdateContent(TItemData[] contents)
|
||||
{
|
||||
var firstCellIndex = Index * Context.GetGroupCount();
|
||||
|
||||
for (var i = 0; i < Cells.Length; i++)
|
||||
{
|
||||
Cells[i].Index = i + firstCellIndex;
|
||||
Cells[i].SetVisible(i < contents.Length);
|
||||
|
||||
if (Cells[i].IsVisible)
|
||||
{
|
||||
Cells[i].UpdateContent(contents[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void UpdatePosition(float position)
|
||||
{
|
||||
for (var i = 0; i < Cells.Length; i++)
|
||||
{
|
||||
Cells[i].UpdatePosition(position);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 5d97e25c7748b8d44acd2298e509c8f1
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -0,0 +1,181 @@
|
||||
/// Credit setchi (https://github.com/setchi)
|
||||
/// Sourced from - https://github.com/setchi/FancyScrollView
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using UnityEngine.UI.Extensions.EasingCore;
|
||||
|
||||
namespace UnityEngine.UI.Extensions
|
||||
{
|
||||
/// <summary>
|
||||
/// グリッドレイアウトのスクロールビューを実装するための抽象基底クラス.
|
||||
/// 無限スクロールおよびスナップには対応していません.
|
||||
/// <see cref="FancyScrollView{TItemData, TContext}.Context"/> が不要な場合は
|
||||
/// 代わりに <see cref="FancyGridView{TItemData}"/> を使用します.
|
||||
/// </summary>
|
||||
/// <typeparam name="TItemData">アイテムのデータ型.</typeparam>
|
||||
/// <typeparam name="TContext"><see cref="FancyScrollView{TItemData, TContext}.Context"/> の型.</typeparam>
|
||||
public abstract class FancyGridView<TItemData, TContext> : FancyScrollRect<TItemData[], TContext>
|
||||
where TContext : class, IFancyGridViewContext, new()
|
||||
{
|
||||
/// <summary>
|
||||
/// デフォルトのセルグループクラス.
|
||||
/// </summary>
|
||||
protected abstract class DefaultCellGroup : FancyCellGroup<TItemData, TContext> { }
|
||||
|
||||
/// <summary>
|
||||
/// 最初にセルを配置する軸方向のセル同士の余白.
|
||||
/// </summary>
|
||||
[SerializeField] protected float startAxisSpacing = 0f;
|
||||
|
||||
/// <summary>
|
||||
/// 最初にセルを配置する軸方向のセル数.
|
||||
/// </summary>
|
||||
[SerializeField] protected int startAxisCellCount = 4;
|
||||
|
||||
/// <summary>
|
||||
/// セルのサイズ.
|
||||
/// </summary>
|
||||
[SerializeField] protected Vector2 cellSize = new Vector2(100f, 100f);
|
||||
|
||||
/// <summary>
|
||||
/// セルのグループ Prefab.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <see cref="FancyGridView{TItemData, TContext}"/> では,
|
||||
/// <see cref="FancyScrollView{TItemData, TContext}.CellPrefab"/> を最初にセルを配置する軸方向のセルコンテナとして使用します.
|
||||
/// </remarks>
|
||||
protected sealed override GameObject CellPrefab => cellGroupTemplate;
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override float CellSize => Scroller.ScrollDirection == ScrollDirection.Horizontal
|
||||
? cellSize.x
|
||||
: cellSize.y;
|
||||
|
||||
/// <summary>
|
||||
/// アイテムの総数.
|
||||
/// </summary>
|
||||
public int DataCount { get; private set; }
|
||||
|
||||
GameObject cellGroupTemplate;
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
Debug.Assert(startAxisCellCount > 0);
|
||||
|
||||
Context.ScrollDirection = Scroller.ScrollDirection;
|
||||
Context.GetGroupCount = () => startAxisCellCount;
|
||||
Context.GetStartAxisSpacing = () => startAxisSpacing;
|
||||
Context.GetCellSize = () => Scroller.ScrollDirection == ScrollDirection.Horizontal
|
||||
? cellSize.y
|
||||
: cellSize.x;
|
||||
|
||||
SetupCellTemplate();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 最初にセルが生成される直前に呼び出されます.
|
||||
/// <see cref="Setup{TGroup}(FancyCell{TItemData, TContext})"/> メソッドを使用してセルテンプレートのセットアップを行ってください.
|
||||
/// </summary>
|
||||
/// <example>
|
||||
/// <code><![CDATA[
|
||||
/// using UnityEngine;
|
||||
/// using FancyScrollView;
|
||||
///
|
||||
/// public class MyGridView : FancyGridView<ItemData, Context>
|
||||
/// {
|
||||
/// class CellGroup : DefaultCellGroup { }
|
||||
///
|
||||
/// [SerializeField] Cell cellPrefab = default;
|
||||
///
|
||||
/// protected override void SetupCellTemplate() => Setup<CellGroup>(cellPrefab);
|
||||
/// }
|
||||
/// ]]></code>
|
||||
/// </example>
|
||||
protected abstract void SetupCellTemplate();
|
||||
|
||||
/// <summary>
|
||||
/// セルテンプレートのセットアップを行います.
|
||||
/// </summary>
|
||||
/// <param name="cellTemplate">セルのテンプレート.</param>
|
||||
/// <typeparam name="TGroup">セルグループの型.</typeparam>
|
||||
protected virtual void Setup<TGroup>(FancyCell<TItemData, TContext> cellTemplate)
|
||||
where TGroup : FancyCell<TItemData[], TContext>
|
||||
{
|
||||
Context.CellTemplate = cellTemplate.gameObject;
|
||||
|
||||
cellGroupTemplate = new GameObject("Group").AddComponent<TGroup>().gameObject;
|
||||
cellGroupTemplate.transform.SetParent(cellContainer, false);
|
||||
cellGroupTemplate.SetActive(false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 渡されたアイテム一覧に基づいて表示内容を更新します.
|
||||
/// </summary>
|
||||
/// <param name="items">アイテム一覧.</param>
|
||||
public virtual void UpdateContents(IList<TItemData> items)
|
||||
{
|
||||
DataCount = items.Count;
|
||||
|
||||
var itemGroups = items
|
||||
.Select((item, index) => (item, index))
|
||||
.GroupBy(
|
||||
x => x.index / startAxisCellCount,
|
||||
x => x.item)
|
||||
.Select(group => group.ToArray())
|
||||
.ToArray();
|
||||
|
||||
UpdateContents(itemGroups);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 指定したアイテムの位置までジャンプします.
|
||||
/// </summary>
|
||||
/// <param name="itemIndex">アイテムのインデックス.</param>
|
||||
/// <param name="alignment">ビューポート内におけるセル位置の基準. 0f(先頭) ~ 1f(末尾).</param>
|
||||
protected override void JumpTo(int itemIndex, float alignment = 0.5f)
|
||||
{
|
||||
var groupIndex = itemIndex / startAxisCellCount;
|
||||
base.JumpTo(groupIndex, alignment);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 指定したアイテムの位置まで移動します.
|
||||
/// </summary>
|
||||
/// <param name="itemIndex">アイテムのインデックス.</param>
|
||||
/// <param name="duration">移動にかける秒数.</param>
|
||||
/// <param name="alignment">ビューポート内におけるセル位置の基準. 0f(先頭) ~ 1f(末尾).</param>
|
||||
/// <param name="onComplete">移動が完了した際に呼び出されるコールバック.</param>
|
||||
protected override void ScrollTo(int itemIndex, float duration, float alignment = 0.5f, Action onComplete = null)
|
||||
{
|
||||
var groupIndex = itemIndex / startAxisCellCount;
|
||||
base.ScrollTo(groupIndex, duration, alignment, onComplete);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 指定したアイテムの位置まで移動します.
|
||||
/// </summary>
|
||||
/// <param name="itemIndex">アイテムのインデックス.</param>
|
||||
/// <param name="duration">移動にかける秒数.</param>
|
||||
/// <param name="easing">移動に使用するイージング.</param>
|
||||
/// <param name="alignment">ビューポート内におけるセル位置の基準. 0f(先頭) ~ 1f(末尾).</param>
|
||||
/// <param name="onComplete">移動が完了した際に呼び出されるコールバック.</param>
|
||||
protected override void ScrollTo(int itemIndex, float duration, Ease easing, float alignment = 0.5f, Action onComplete = null)
|
||||
{
|
||||
var groupIndex = itemIndex / startAxisCellCount;
|
||||
base.ScrollTo(groupIndex, duration, easing, alignment, onComplete);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// グリッドレイアウトのスクロールビューを実装するための抽象基底クラス.
|
||||
/// 無限スクロールおよびスナップには対応していません.
|
||||
/// </summary>
|
||||
/// <typeparam name="TItemData">アイテムのデータ型.</typeparam>
|
||||
/// <seealso cref="FancyGridView{TItemData, TContext}"/>
|
||||
public abstract class FancyGridView<TItemData> : FancyGridView<TItemData, FancyGridViewContext> { }
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e806c8d18b1ff45bb87e9a5b87ec85e3
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -0,0 +1,42 @@
|
||||
/// Credit setchi (https://github.com/setchi)
|
||||
/// Sourced from - https://github.com/setchi/FancyScrollView
|
||||
|
||||
namespace UnityEngine.UI.Extensions
|
||||
{
|
||||
/// <summary>
|
||||
/// <see cref="FancyGridView{TItemData, TContext}"/> のセルを実装するための抽象基底クラス.
|
||||
/// <see cref="FancyCell{TItemData, TContext}.Context"/> が不要な場合は
|
||||
/// 代わりに <see cref="FancyGridViewCell{TItemData}"/> を使用します.
|
||||
/// </summary>
|
||||
/// <typeparam name="TItemData">アイテムのデータ型.</typeparam>
|
||||
/// <typeparam name="TContext"><see cref="FancyCell{TItemData, TContext}.Context"/> の型.</typeparam>
|
||||
public abstract class FancyGridViewCell<TItemData, TContext> : FancyScrollRectCell<TItemData, TContext>
|
||||
where TContext : class, IFancyGridViewContext, new()
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
protected override void UpdatePosition(float normalizedPosition, float localPosition)
|
||||
{
|
||||
var cellSize = Context.GetCellSize();
|
||||
var spacing = Context.GetStartAxisSpacing();
|
||||
var groupCount = Context.GetGroupCount();
|
||||
|
||||
var indexInGroup = Index % groupCount;
|
||||
var positionInGroup = (cellSize + spacing) * (indexInGroup - (groupCount - 1) * 0.5f);
|
||||
|
||||
transform.localPosition = Context.ScrollDirection == ScrollDirection.Horizontal
|
||||
? new Vector2(-localPosition, -positionInGroup)
|
||||
: new Vector2(positionInGroup, localPosition);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <see cref="FancyGridView{TItemData}"/> のセルを実装するための抽象基底クラス.
|
||||
/// </summary>
|
||||
/// <typeparam name="TItemData">アイテムのデータ型.</typeparam>
|
||||
/// <seealso cref="FancyGridViewCell{TItemData, TContext}"/>
|
||||
public abstract class FancyGridViewCell<TItemData> : FancyGridViewCell<TItemData, FancyGridViewContext>
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public sealed override void SetContext(FancyGridViewContext context) => base.SetContext(context);
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ab8a59bbf5118824ab084e32342ad86b
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -0,0 +1,20 @@
|
||||
/// Credit setchi (https://github.com/setchi)
|
||||
/// Sourced from - https://github.com/setchi/FancyScrollView
|
||||
|
||||
using System;
|
||||
|
||||
namespace UnityEngine.UI.Extensions
|
||||
{
|
||||
/// <summary>
|
||||
/// <see cref="FancyGridView{TItemData, TContext}"/> のコンテキスト基底クラス.
|
||||
/// </summary>
|
||||
public class FancyGridViewContext : IFancyGridViewContext
|
||||
{
|
||||
ScrollDirection IFancyScrollRectContext.ScrollDirection { get; set; }
|
||||
Func<(float ScrollSize, float ReuseMargin)> IFancyScrollRectContext.CalculateScrollSize { get; set; }
|
||||
GameObject IFancyCellGroupContext.CellTemplate { get; set; }
|
||||
Func<int> IFancyCellGroupContext.GetGroupCount { get; set; }
|
||||
Func<float> IFancyGridViewContext.GetStartAxisSpacing { get; set; }
|
||||
Func<float> IFancyGridViewContext.GetCellSize { get; set; }
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 954a17398bfb54ee7baac3d7ab7e822c
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -0,0 +1,16 @@
|
||||
/// Credit setchi (https://github.com/setchi)
|
||||
/// Sourced from - https://github.com/setchi/FancyScrollView
|
||||
|
||||
using System;
|
||||
|
||||
namespace UnityEngine.UI.Extensions
|
||||
{
|
||||
/// <summary>
|
||||
/// <see cref="FancyCellGroup{TItemData, TContext}"/> のコンテキストインターフェース.
|
||||
/// </summary>
|
||||
public interface IFancyCellGroupContext
|
||||
{
|
||||
GameObject CellTemplate { get; set; }
|
||||
Func<int> GetGroupCount { get; set; }
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 1dc086f250206754aa8449e252d50388
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -0,0 +1,16 @@
|
||||
/// Credit setchi (https://github.com/setchi)
|
||||
/// Sourced from - https://github.com/setchi/FancyScrollView
|
||||
|
||||
using System;
|
||||
|
||||
namespace UnityEngine.UI.Extensions
|
||||
{
|
||||
/// <summary>
|
||||
/// <see cref="FancyGridView{TItemData, TContext}"/> のコンテキストインターフェース.
|
||||
/// </summary>
|
||||
public interface IFancyGridViewContext : IFancyScrollRectContext, IFancyCellGroupContext
|
||||
{
|
||||
Func<float> GetStartAxisSpacing { get; set; }
|
||||
Func<float> GetCellSize { get; set ; }
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 3cf2d53d9c81945c28f7c558a7c40ba3
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e2c5beedd885c4490a86bb4973f965bf
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -0,0 +1,303 @@
|
||||
/// Credit setchi (https://github.com/setchi)
|
||||
/// Sourced from - https://github.com/setchi/FancyScrollView
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine.UI.Extensions.EasingCore;
|
||||
|
||||
namespace UnityEngine.UI.Extensions
|
||||
{
|
||||
/// <summary>
|
||||
/// ScrollRect スタイルのスクロールビューを実装するための抽象基底クラス.
|
||||
/// 無限スクロールおよびスナップには対応していません.
|
||||
/// <see cref="FancyScrollView{TItemData, TContext}.Context"/> が不要な場合は
|
||||
/// 代わりに <see cref="FancyScrollRect{TItemData}"/> を使用します.
|
||||
/// </summary>
|
||||
/// <typeparam name="TItemData">アイテムのデータ型.</typeparam>
|
||||
/// <typeparam name="TContext"><see cref="FancyScrollView{TItemData, TContext}.Context"/> の型.</typeparam>
|
||||
[RequireComponent(typeof(Scroller))]
|
||||
public abstract class FancyScrollRect<TItemData, TContext> : FancyScrollView<TItemData, TContext>
|
||||
where TContext : class, IFancyScrollRectContext, new()
|
||||
{
|
||||
/// <summary>
|
||||
/// スクロール中にセルが再利用されるまでの余白のセル数.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <c>0</c> を指定するとセルが完全に隠れた直後に再利用されます.
|
||||
/// <c>1</c> 以上を指定すると, そのセル数だけ余分にスクロールしてから再利用されます.
|
||||
/// </remarks>
|
||||
[SerializeField] protected float reuseCellMarginCount = 0f;
|
||||
|
||||
/// <summary>
|
||||
/// コンテンツ先頭の余白.
|
||||
/// </summary>
|
||||
[SerializeField] protected float paddingHead = 0f;
|
||||
|
||||
/// <summary>
|
||||
/// コンテンツ末尾の余白.
|
||||
/// </summary>
|
||||
[SerializeField] protected float paddingTail = 0f;
|
||||
|
||||
/// <summary>
|
||||
/// スクロール軸方向のセル同士の余白.
|
||||
/// </summary>
|
||||
[SerializeField] protected float spacing = 0f;
|
||||
|
||||
/// <summary>
|
||||
/// セルのサイズ.
|
||||
/// </summary>
|
||||
protected abstract float CellSize { get; }
|
||||
|
||||
/// <summary>
|
||||
/// スクロール可能かどうか.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// アイテム数が十分少なくビューポート内に全てのセルが収まっている場合は <c>false</c>, それ以外は <c>true</c> になります.
|
||||
/// </remarks>
|
||||
protected virtual bool Scrollable => MaxScrollPosition > 0f;
|
||||
|
||||
Scroller cachedScroller;
|
||||
|
||||
/// <summary>
|
||||
/// スクロール位置を制御する <see cref="FancyScrollView.Scroller"/> のインスタンス.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <see cref="Scroller"/> のスクロール位置を変更する際は必ず <see cref="ToScrollerPosition(float)"/> を使用して変換した位置を使用してください.
|
||||
/// </remarks>
|
||||
protected Scroller Scroller => cachedScroller ?? (cachedScroller = GetComponent<Scroller>());
|
||||
|
||||
float ScrollLength => 1f / Mathf.Max(cellInterval, 1e-2f) - 1f;
|
||||
|
||||
float ViewportLength => ScrollLength - reuseCellMarginCount * 2f;
|
||||
|
||||
float PaddingHeadLength => (paddingHead - spacing * 0.5f) / (CellSize + spacing);
|
||||
|
||||
float MaxScrollPosition => ItemsSource.Count
|
||||
- ScrollLength
|
||||
+ reuseCellMarginCount * 2f
|
||||
+ (paddingHead + paddingTail - spacing) / (CellSize + spacing);
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
Context.ScrollDirection = Scroller.ScrollDirection;
|
||||
Context.CalculateScrollSize = () =>
|
||||
{
|
||||
var interval = CellSize + spacing;
|
||||
var reuseMargin = interval * reuseCellMarginCount;
|
||||
var scrollSize = Scroller.ViewportSize + interval + reuseMargin * 2f;
|
||||
return (scrollSize, reuseMargin);
|
||||
};
|
||||
|
||||
AdjustCellIntervalAndScrollOffset();
|
||||
Scroller.OnValueChanged(OnScrollerValueChanged);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <see cref="Scroller"/> のスクロール位置が変更された際の処理.
|
||||
/// </summary>
|
||||
/// <param name="p"><see cref="Scroller"/> のスクロール位置.</param>
|
||||
void OnScrollerValueChanged(float p)
|
||||
{
|
||||
base.UpdatePosition(Scrollable ? ToFancyScrollViewPosition(p) : 0f);
|
||||
|
||||
if (Scroller.Scrollbar)
|
||||
{
|
||||
if (p > ItemsSource.Count - 1)
|
||||
{
|
||||
ShrinkScrollbar(p - (ItemsSource.Count - 1));
|
||||
}
|
||||
else if (p < 0f)
|
||||
{
|
||||
ShrinkScrollbar(-p);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// スクロール範囲を超えてスクロールされた量に基づいて, スクロールバーのサイズを縮小します.
|
||||
/// </summary>
|
||||
/// <param name="offset">スクロール範囲を超えてスクロールされた量.</param>
|
||||
void ShrinkScrollbar(float offset)
|
||||
{
|
||||
var scale = 1f - ToFancyScrollViewPosition(offset) / (ViewportLength - PaddingHeadLength);
|
||||
UpdateScrollbarSize((ViewportLength - PaddingHeadLength) * scale);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override void Refresh()
|
||||
{
|
||||
AdjustCellIntervalAndScrollOffset();
|
||||
RefreshScroller();
|
||||
base.Refresh();
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override void Relayout()
|
||||
{
|
||||
AdjustCellIntervalAndScrollOffset();
|
||||
RefreshScroller();
|
||||
base.Relayout();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <see cref="Scroller"/> の各種状態を更新します.
|
||||
/// </summary>
|
||||
protected void RefreshScroller()
|
||||
{
|
||||
Scroller.Draggable = Scrollable;
|
||||
Scroller.ScrollSensitivity = ToScrollerPosition(ViewportLength - PaddingHeadLength);
|
||||
Scroller.Position = ToScrollerPosition(currentPosition);
|
||||
|
||||
if (Scroller.Scrollbar)
|
||||
{
|
||||
Scroller.Scrollbar.gameObject.SetActive(Scrollable);
|
||||
UpdateScrollbarSize(ViewportLength);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override void UpdateContents(IList<TItemData> items)
|
||||
{
|
||||
Debug.Assert(Context.CalculateScrollSize != null);
|
||||
|
||||
AdjustCellIntervalAndScrollOffset();
|
||||
base.UpdateContents(items);
|
||||
|
||||
Scroller.SetTotalCount(items.Count);
|
||||
RefreshScroller();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// スクロール位置を更新します.
|
||||
/// </summary>
|
||||
/// <param name="position">スクロール位置.</param>
|
||||
protected new void UpdatePosition(float position)
|
||||
{
|
||||
Scroller.Position = ToScrollerPosition(position, 0.5f);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 指定したアイテムの位置までジャンプします.
|
||||
/// </summary>
|
||||
/// <param name="itemIndex">アイテムのインデックス.</param>
|
||||
/// <param name="alignment">ビューポート内におけるセル位置の基準. 0f(先頭) ~ 1f(末尾).</param>
|
||||
protected virtual void JumpTo(int itemIndex, float alignment = 0.5f)
|
||||
{
|
||||
Scroller.Position = ToScrollerPosition(itemIndex, alignment);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 指定したアイテムの位置まで移動します.
|
||||
/// </summary>
|
||||
/// <param name="index">アイテムのインデックス.</param>
|
||||
/// <param name="duration">移動にかける秒数.</param>
|
||||
/// <param name="alignment">ビューポート内におけるセル位置の基準. 0f(先頭) ~ 1f(末尾).</param>
|
||||
/// <param name="onComplete">移動が完了した際に呼び出されるコールバック.</param>
|
||||
protected virtual void ScrollTo(int index, float duration, float alignment = 0.5f, Action onComplete = null)
|
||||
{
|
||||
Scroller.ScrollTo(ToScrollerPosition(index, alignment), duration, onComplete);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 指定したアイテムの位置まで移動します.
|
||||
/// </summary>
|
||||
/// <param name="index">アイテムのインデックス.</param>
|
||||
/// <param name="duration">移動にかける秒数.</param>
|
||||
/// <param name="easing">移動に使用するイージング.</param>
|
||||
/// <param name="alignment">ビューポート内におけるセル位置の基準. 0f(先頭) ~ 1f(末尾).</param>
|
||||
/// <param name="onComplete">移動が完了した際に呼び出されるコールバック.</param>
|
||||
protected virtual void ScrollTo(int index, float duration, Ease easing, float alignment = 0.5f, Action onComplete = null)
|
||||
{
|
||||
Scroller.ScrollTo(ToScrollerPosition(index, alignment), duration, easing, onComplete);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// ビューポートとコンテンツの長さに基づいてスクロールバーのサイズを更新します.
|
||||
/// </summary>
|
||||
/// <param name="viewportLength">ビューポートのサイズ.</param>
|
||||
protected void UpdateScrollbarSize(float viewportLength)
|
||||
{
|
||||
var contentLength = Mathf.Max(ItemsSource.Count + (paddingHead + paddingTail - spacing) / (CellSize + spacing), 1);
|
||||
Scroller.Scrollbar.size = Scrollable ? Mathf.Clamp01(viewportLength / contentLength) : 1f;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <see cref="Scroller"/> が扱うスクロール位置を <see cref="FancyScrollRect{TItemData, TContext}"/> が扱うスクロール位置に変換します.
|
||||
/// </summary>
|
||||
/// <param name="position"><see cref="Scroller"/> が扱うスクロール位置.</param>
|
||||
/// <returns><see cref="FancyScrollRect{TItemData, TContext}"/> が扱うスクロール位置.</returns>
|
||||
protected float ToFancyScrollViewPosition(float position)
|
||||
{
|
||||
return position / Mathf.Max(ItemsSource.Count - 1, 1) * MaxScrollPosition - PaddingHeadLength;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <see cref="FancyScrollRect{TItemData, TContext}"/> が扱うスクロール位置を <see cref="Scroller"/> が扱うスクロール位置に変換します.
|
||||
/// </summary>
|
||||
/// <param name="position"><see cref="FancyScrollRect{TItemData, TContext}"/> が扱うスクロール位置.</param>
|
||||
/// <returns><see cref="Scroller"/> が扱うスクロール位置.</returns>
|
||||
protected float ToScrollerPosition(float position)
|
||||
{
|
||||
return (position + PaddingHeadLength) / MaxScrollPosition * Mathf.Max(ItemsSource.Count - 1, 1);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <see cref="FancyScrollRect{TItemData, TContext}"/> が扱うスクロール位置を <see cref="Scroller"/> が扱うスクロール位置に変換します.
|
||||
/// </summary>
|
||||
/// <param name="position"><see cref="FancyScrollRect{TItemData, TContext}"/> が扱うスクロール位置.</param>
|
||||
/// <param name="alignment">ビューポート内におけるセル位置の基準. 0f(先頭) ~ 1f(末尾).</param>
|
||||
/// <returns><see cref="Scroller"/> が扱うスクロール位置.</returns>
|
||||
protected float ToScrollerPosition(float position, float alignment = 0.5f)
|
||||
{
|
||||
var offset = alignment * (ScrollLength - (1f + reuseCellMarginCount * 2f))
|
||||
+ (1f - alignment - 0.5f) * spacing / (CellSize + spacing);
|
||||
return ToScrollerPosition(Mathf.Clamp(position - offset, 0f, MaxScrollPosition));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 指定された設定を実現するための
|
||||
/// <see cref="FancyScrollView{TItemData,TContext}.cellInterval"/> と
|
||||
/// <see cref="FancyScrollView{TItemData,TContext}.scrollOffset"/> を計算して適用します.
|
||||
/// </summary>
|
||||
protected void AdjustCellIntervalAndScrollOffset()
|
||||
{
|
||||
var totalSize = Scroller.ViewportSize + (CellSize + spacing) * (1f + reuseCellMarginCount * 2f);
|
||||
cellInterval = (CellSize + spacing) / totalSize;
|
||||
scrollOffset = cellInterval * (1f + reuseCellMarginCount);
|
||||
}
|
||||
|
||||
protected virtual void OnValidate()
|
||||
{
|
||||
AdjustCellIntervalAndScrollOffset();
|
||||
|
||||
if (loop)
|
||||
{
|
||||
loop = false;
|
||||
Debug.LogError("Loop is currently not supported in FancyScrollRect.");
|
||||
}
|
||||
|
||||
if (Scroller.SnapEnabled)
|
||||
{
|
||||
Scroller.SnapEnabled = false;
|
||||
Debug.LogError("Snap is currently not supported in FancyScrollRect.");
|
||||
}
|
||||
|
||||
if (Scroller.MovementType == MovementType.Unrestricted)
|
||||
{
|
||||
Scroller.MovementType = MovementType.Elastic;
|
||||
Debug.LogError("MovementType.Unrestricted is currently not supported in FancyScrollRect.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// ScrollRect スタイルのスクロールビューを実装するための抽象基底クラス.
|
||||
/// 無限スクロールおよびスナップには対応していません.
|
||||
/// </summary>
|
||||
/// <typeparam name="TItemData">アイテムのデータ型.</typeparam>
|
||||
/// <seealso cref="FancyScrollRect{TItemData, TContext}"/>
|
||||
public abstract class FancyScrollRect<TItemData> : FancyScrollRect<TItemData, FancyScrollRectContext> { }
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 66c8eb84fdbde4a4a8273b98227a282d
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -0,0 +1,56 @@
|
||||
/// Credit setchi (https://github.com/setchi)
|
||||
/// Sourced from - https://github.com/setchi/FancyScrollView
|
||||
|
||||
namespace UnityEngine.UI.Extensions
|
||||
{
|
||||
/// <summary>
|
||||
/// <see cref="FancyScrollRect{TItemData, TContext}"/> のセルを実装するための抽象基底クラス.
|
||||
/// <see cref="FancyCell{TItemData, TContext}.Context"/> が不要な場合は
|
||||
/// 代わりに <see cref="FancyScrollRectCell{TItemData}"/> を使用します.
|
||||
/// </summary>
|
||||
/// <typeparam name="TItemData">アイテムのデータ型.</typeparam>
|
||||
/// <typeparam name="TContext"><see cref="FancyCell{TItemData, TContext}.Context"/> の型.</typeparam>
|
||||
public abstract class FancyScrollRectCell<TItemData, TContext> : FancyCell<TItemData, TContext>
|
||||
where TContext : class, IFancyScrollRectContext, new()
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public override void UpdatePosition(float position)
|
||||
{
|
||||
var (scrollSize, reuseMargin) = Context.CalculateScrollSize();
|
||||
|
||||
var normalizedPosition = (Mathf.Lerp(0f, scrollSize, position) - reuseMargin) / (scrollSize - reuseMargin * 2f);
|
||||
|
||||
var start = 0.5f * scrollSize;
|
||||
var end = -start;
|
||||
|
||||
UpdatePosition(normalizedPosition, Mathf.Lerp(start, end, position));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// このセルの位置を更新します.
|
||||
/// </summary>
|
||||
/// <param name="normalizedPosition">
|
||||
/// ビューポートの範囲で正規化されたスクロール位置.
|
||||
/// <see cref="FancyScrollRect{TItemData, TContext}.reuseCellMarginCount"/> の値に基づいて
|
||||
/// <c>0.0</c> ~ <c>1.0</c> の範囲を超えた値が渡されることがあります.
|
||||
/// </param>
|
||||
/// <param name="localPosition">ローカル位置.</param>
|
||||
protected virtual void UpdatePosition(float normalizedPosition, float localPosition)
|
||||
{
|
||||
transform.localPosition = Context.ScrollDirection == ScrollDirection.Horizontal
|
||||
? new Vector2(-localPosition, 0)
|
||||
: new Vector2(0, localPosition);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <see cref="FancyScrollRect{TItemData}"/> のセルを実装するための抽象基底クラス.
|
||||
/// </summary>
|
||||
/// <typeparam name="TItemData">アイテムのデータ型.</typeparam>
|
||||
/// <seealso cref="FancyScrollRectCell{TItemData, TContext}"/>
|
||||
public abstract class FancyScrollRectCell<TItemData> : FancyScrollRectCell<TItemData, FancyScrollRectContext>
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public sealed override void SetContext(FancyScrollRectContext context) => base.SetContext(context);
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 09f137a55810740eab42e24ef242dcfa
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -0,0 +1,16 @@
|
||||
/// Credit setchi (https://github.com/setchi)
|
||||
/// Sourced from - https://github.com/setchi/FancyScrollView
|
||||
|
||||
using System;
|
||||
|
||||
namespace UnityEngine.UI.Extensions
|
||||
{
|
||||
/// <summary>
|
||||
/// <see cref="FancyScrollRect{TItemData, TContext}"/> のコンテキスト基底クラス.
|
||||
/// </summary>
|
||||
public class FancyScrollRectContext : IFancyScrollRectContext
|
||||
{
|
||||
ScrollDirection IFancyScrollRectContext.ScrollDirection { get; set; }
|
||||
Func<(float ScrollSize, float ReuseMargin)> IFancyScrollRectContext.CalculateScrollSize { get; set; }
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 91383bd46cee541a7a03e08cfaa47c16
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -0,0 +1,16 @@
|
||||
/// Credit setchi (https://github.com/setchi)
|
||||
/// Sourced from - https://github.com/setchi/FancyScrollView
|
||||
|
||||
using System;
|
||||
|
||||
namespace UnityEngine.UI.Extensions
|
||||
{
|
||||
/// <summary>
|
||||
/// <see cref="FancyScrollRect{TItemData, TContext}"/> のコンテキストインターフェース.
|
||||
/// </summary>
|
||||
public interface IFancyScrollRectContext
|
||||
{
|
||||
ScrollDirection ScrollDirection { get; set; }
|
||||
Func<(float ScrollSize, float ReuseMargin)> CalculateScrollSize { get; set; }
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e425de6354b6946c7a3b9f2c807b60fe
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 691a9f5ee4aec4112a01b9a4332cac51
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -0,0 +1,194 @@
|
||||
/*
|
||||
* EasingCore (https://github.com/setchi/EasingCore)
|
||||
* Copyright (c) 2020 setchi
|
||||
* Licensed under MIT (https://github.com/setchi/EasingCore/blob/master/LICENSE)
|
||||
*/
|
||||
|
||||
namespace UnityEngine.UI.Extensions.EasingCore
|
||||
{
|
||||
public enum Ease
|
||||
{
|
||||
Linear,
|
||||
InBack,
|
||||
InBounce,
|
||||
InCirc,
|
||||
InCubic,
|
||||
InElastic,
|
||||
InExpo,
|
||||
InQuad,
|
||||
InQuart,
|
||||
InQuint,
|
||||
InSine,
|
||||
OutBack,
|
||||
OutBounce,
|
||||
OutCirc,
|
||||
OutCubic,
|
||||
OutElastic,
|
||||
OutExpo,
|
||||
OutQuad,
|
||||
OutQuart,
|
||||
OutQuint,
|
||||
OutSine,
|
||||
InOutBack,
|
||||
InOutBounce,
|
||||
InOutCirc,
|
||||
InOutCubic,
|
||||
InOutElastic,
|
||||
InOutExpo,
|
||||
InOutQuad,
|
||||
InOutQuart,
|
||||
InOutQuint,
|
||||
InOutSine,
|
||||
}
|
||||
|
||||
public delegate float EasingFunction(float t);
|
||||
|
||||
public static class Easing
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the easing function
|
||||
/// </summary>
|
||||
/// <param name="type">Ease type</param>
|
||||
/// <returns>Easing function</returns>
|
||||
public static EasingFunction Get(Ease type)
|
||||
{
|
||||
switch (type)
|
||||
{
|
||||
case Ease.Linear: return linear;
|
||||
case Ease.InBack: return inBack;
|
||||
case Ease.InBounce: return inBounce;
|
||||
case Ease.InCirc: return inCirc;
|
||||
case Ease.InCubic: return inCubic;
|
||||
case Ease.InElastic: return inElastic;
|
||||
case Ease.InExpo: return inExpo;
|
||||
case Ease.InQuad: return inQuad;
|
||||
case Ease.InQuart: return inQuart;
|
||||
case Ease.InQuint: return inQuint;
|
||||
case Ease.InSine: return inSine;
|
||||
case Ease.OutBack: return outBack;
|
||||
case Ease.OutBounce: return outBounce;
|
||||
case Ease.OutCirc: return outCirc;
|
||||
case Ease.OutCubic: return outCubic;
|
||||
case Ease.OutElastic: return outElastic;
|
||||
case Ease.OutExpo: return outExpo;
|
||||
case Ease.OutQuad: return outQuad;
|
||||
case Ease.OutQuart: return outQuart;
|
||||
case Ease.OutQuint: return outQuint;
|
||||
case Ease.OutSine: return outSine;
|
||||
case Ease.InOutBack: return inOutBack;
|
||||
case Ease.InOutBounce: return inOutBounce;
|
||||
case Ease.InOutCirc: return inOutCirc;
|
||||
case Ease.InOutCubic: return inOutCubic;
|
||||
case Ease.InOutElastic: return inOutElastic;
|
||||
case Ease.InOutExpo: return inOutExpo;
|
||||
case Ease.InOutQuad: return inOutQuad;
|
||||
case Ease.InOutQuart: return inOutQuart;
|
||||
case Ease.InOutQuint: return inOutQuint;
|
||||
case Ease.InOutSine: return inOutSine;
|
||||
default: return linear;
|
||||
}
|
||||
|
||||
float linear(float t) => t;
|
||||
|
||||
float inBack(float t) => t * t * t - t * Mathf.Sin(t * Mathf.PI);
|
||||
|
||||
float outBack(float t) => 1f - inBack(1f - t);
|
||||
|
||||
float inOutBack(float t) =>
|
||||
t < 0.5f
|
||||
? 0.5f * inBack(2f * t)
|
||||
: 0.5f * outBack(2f * t - 1f) + 0.5f;
|
||||
|
||||
float inBounce(float t) => 1f - outBounce(1f - t);
|
||||
|
||||
float outBounce(float t) =>
|
||||
t < 4f / 11.0f ?
|
||||
(121f * t * t) / 16.0f :
|
||||
t < 8f / 11.0f ?
|
||||
(363f / 40.0f * t * t) - (99f / 10.0f * t) + 17f / 5.0f :
|
||||
t < 9f / 10.0f ?
|
||||
(4356f / 361.0f * t * t) - (35442f / 1805.0f * t) + 16061f / 1805.0f :
|
||||
(54f / 5.0f * t * t) - (513f / 25.0f * t) + 268f / 25.0f;
|
||||
|
||||
float inOutBounce(float t) =>
|
||||
t < 0.5f
|
||||
? 0.5f * inBounce(2f * t)
|
||||
: 0.5f * outBounce(2f * t - 1f) + 0.5f;
|
||||
|
||||
float inCirc(float t) => 1f - Mathf.Sqrt(1f - (t * t));
|
||||
|
||||
float outCirc(float t) => Mathf.Sqrt((2f - t) * t);
|
||||
|
||||
float inOutCirc(float t) =>
|
||||
t < 0.5f
|
||||
? 0.5f * (1 - Mathf.Sqrt(1f - 4f * (t * t)))
|
||||
: 0.5f * (Mathf.Sqrt(-((2f * t) - 3f) * ((2f * t) - 1f)) + 1f);
|
||||
|
||||
float inCubic(float t) => t * t * t;
|
||||
|
||||
float outCubic(float t) => inCubic(t - 1f) + 1f;
|
||||
|
||||
float inOutCubic(float t) =>
|
||||
t < 0.5f
|
||||
? 4f * t * t * t
|
||||
: 0.5f * inCubic(2f * t - 2f) + 1f;
|
||||
|
||||
float inElastic(float t) => Mathf.Sin(13f * (Mathf.PI * 0.5f) * t) * Mathf.Pow(2f, 10f * (t - 1f));
|
||||
|
||||
float outElastic(float t) => Mathf.Sin(-13f * (Mathf.PI * 0.5f) * (t + 1)) * Mathf.Pow(2f, -10f * t) + 1f;
|
||||
|
||||
float inOutElastic(float t) =>
|
||||
t < 0.5f
|
||||
? 0.5f * Mathf.Sin(13f * (Mathf.PI * 0.5f) * (2f * t)) * Mathf.Pow(2f, 10f * ((2f * t) - 1f))
|
||||
: 0.5f * (Mathf.Sin(-13f * (Mathf.PI * 0.5f) * ((2f * t - 1f) + 1f)) * Mathf.Pow(2f, -10f * (2f * t - 1f)) + 2f);
|
||||
|
||||
float inExpo(float t) => Mathf.Approximately(0.0f, t) ? t : Mathf.Pow(2f, 10f * (t - 1f));
|
||||
|
||||
float outExpo(float t) => Mathf.Approximately(1.0f, t) ? t : 1f - Mathf.Pow(2f, -10f * t);
|
||||
|
||||
float inOutExpo(float v) =>
|
||||
Mathf.Approximately(0.0f, v) || Mathf.Approximately(1.0f, v)
|
||||
? v
|
||||
: v < 0.5f
|
||||
? 0.5f * Mathf.Pow(2f, (20f * v) - 10f)
|
||||
: -0.5f * Mathf.Pow(2f, (-20f * v) + 10f) + 1f;
|
||||
|
||||
float inQuad(float t) => t * t;
|
||||
|
||||
float outQuad(float t) => -t * (t - 2f);
|
||||
|
||||
float inOutQuad(float t) =>
|
||||
t < 0.5f
|
||||
? 2f * t * t
|
||||
: -2f * t * t + 4f * t - 1f;
|
||||
|
||||
float inQuart(float t) => t * t * t * t;
|
||||
|
||||
float outQuart(float t)
|
||||
{
|
||||
var u = t - 1f;
|
||||
return u * u * u * (1f - t) + 1f;
|
||||
}
|
||||
|
||||
float inOutQuart(float t) =>
|
||||
t < 0.5f
|
||||
? 8f * inQuart(t)
|
||||
: -8f * inQuart(t - 1f) + 1f;
|
||||
|
||||
float inQuint(float t) => t * t * t * t * t;
|
||||
|
||||
float outQuint(float t) => inQuint(t - 1f) + 1f;
|
||||
|
||||
float inOutQuint(float t) =>
|
||||
t < 0.5f
|
||||
? 16f * inQuint(t)
|
||||
: 0.5f * inQuint(2f * t - 2f) + 1f;
|
||||
|
||||
float inSine(float t) => Mathf.Sin((t - 1f) * (Mathf.PI * 0.5f)) + 1f;
|
||||
|
||||
float outSine(float t) => Mathf.Sin(t * (Mathf.PI * 0.5f));
|
||||
|
||||
float inOutSine(float t) => 0.5f * (1f - Mathf.Cos(t * Mathf.PI));
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 3b2a35f9ff1c8487582b74620e380486
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -0,0 +1,13 @@
|
||||
/// Credit setchi (https://github.com/setchi)
|
||||
/// Sourced from - https://github.com/setchi/FancyScrollView
|
||||
|
||||
namespace UnityEngine.UI.Extensions
|
||||
{
|
||||
public enum MovementDirection
|
||||
{
|
||||
Left,
|
||||
Right,
|
||||
Up,
|
||||
Down,
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 11769de5e972543c7a4132ceff94a5c4
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -0,0 +1,12 @@
|
||||
/// Credit setchi (https://github.com/setchi)
|
||||
/// Sourced from - https://github.com/setchi/FancyScrollView
|
||||
|
||||
namespace UnityEngine.UI.Extensions
|
||||
{
|
||||
public enum MovementType
|
||||
{
|
||||
Unrestricted = ScrollRect.MovementType.Unrestricted,
|
||||
Elastic = ScrollRect.MovementType.Elastic,
|
||||
Clamped = ScrollRect.MovementType.Clamped
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 585de8f4f272547c89041f94f2ad92b8
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -0,0 +1,11 @@
|
||||
/// Credit setchi (https://github.com/setchi)
|
||||
/// Sourced from - https://github.com/setchi/FancyScrollView
|
||||
|
||||
namespace UnityEngine.UI.Extensions
|
||||
{
|
||||
public enum ScrollDirection
|
||||
{
|
||||
Vertical,
|
||||
Horizontal,
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 3392a30c085c642139c92e94ec0d6309
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -0,0 +1,597 @@
|
||||
/// Credit setchi (https://github.com/setchi)
|
||||
/// Sourced from - https://github.com/setchi/FancyScrollView
|
||||
|
||||
using System;
|
||||
using UnityEngine.EventSystems;
|
||||
using UnityEngine.UI.Extensions.EasingCore;
|
||||
|
||||
namespace UnityEngine.UI.Extensions
|
||||
{
|
||||
/// <summary>
|
||||
/// スクロール位置の制御を行うコンポーネント.
|
||||
/// </summary>
|
||||
public class Scroller : UIBehaviour, IPointerUpHandler, IPointerDownHandler, IBeginDragHandler, IEndDragHandler, IDragHandler, IScrollHandler
|
||||
{
|
||||
[SerializeField] RectTransform viewport = default;
|
||||
|
||||
/// <summary>
|
||||
/// ビューポートのサイズ.
|
||||
/// </summary>
|
||||
public float ViewportSize => scrollDirection == ScrollDirection.Horizontal
|
||||
? viewport.rect.size.x
|
||||
: viewport.rect.size.y;
|
||||
|
||||
[SerializeField] ScrollDirection scrollDirection = ScrollDirection.Vertical;
|
||||
|
||||
/// <summary>
|
||||
/// スクロール方向.
|
||||
/// </summary>
|
||||
public ScrollDirection ScrollDirection => scrollDirection;
|
||||
|
||||
[SerializeField] MovementType movementType = MovementType.Elastic;
|
||||
|
||||
/// <summary>
|
||||
/// コンテンツがスクロール範囲を越えて移動するときに使用する挙動.
|
||||
/// </summary>
|
||||
public MovementType MovementType
|
||||
{
|
||||
get => movementType;
|
||||
set => movementType = value;
|
||||
}
|
||||
|
||||
[SerializeField] float elasticity = 0.1f;
|
||||
|
||||
/// <summary>
|
||||
/// コンテンツがスクロール範囲を越えて移動するときに使用する弾力性の量.
|
||||
/// </summary>
|
||||
public float Elasticity
|
||||
{
|
||||
get => elasticity;
|
||||
set => elasticity = value;
|
||||
}
|
||||
|
||||
[SerializeField] float scrollSensitivity = 1f;
|
||||
|
||||
/// <summary>
|
||||
/// <see cref="ViewportSize"/> の端から端まで Drag したときのスクロール位置の変化量.
|
||||
/// </summary>
|
||||
public float ScrollSensitivity
|
||||
{
|
||||
get => scrollSensitivity;
|
||||
set => scrollSensitivity = value;
|
||||
}
|
||||
|
||||
[SerializeField] bool inertia = true;
|
||||
|
||||
/// <summary>
|
||||
/// 慣性を使用するかどうか. <c>true</c> を指定すると慣性が有効に, <c>false</c> を指定すると慣性が無効になります.
|
||||
/// </summary>
|
||||
public bool Inertia
|
||||
{
|
||||
get => inertia;
|
||||
set => inertia = value;
|
||||
}
|
||||
|
||||
[SerializeField] float decelerationRate = 0.03f;
|
||||
|
||||
/// <summary>
|
||||
/// スクロールの減速率. <see cref="Inertia"/> が <c>true</c> の場合のみ有効です.
|
||||
/// </summary>
|
||||
public float DecelerationRate
|
||||
{
|
||||
get => decelerationRate;
|
||||
set => decelerationRate = value;
|
||||
}
|
||||
|
||||
[SerializeField] Snap snap = new Snap {
|
||||
Enable = true,
|
||||
VelocityThreshold = 0.5f,
|
||||
Duration = 0.3f,
|
||||
Easing = Ease.InOutCubic
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// <c>true</c> ならスナップし, <c>false</c>ならスナップしません.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// スナップを有効にすると, 慣性でスクロールが止まる直前に最寄りのセルへ移動します.
|
||||
/// </remarks>
|
||||
public bool SnapEnabled
|
||||
{
|
||||
get => snap.Enable;
|
||||
set => snap.Enable = value;
|
||||
}
|
||||
|
||||
[SerializeField] bool draggable = true;
|
||||
|
||||
/// <summary>
|
||||
/// Drag 入力を受付けるかどうか.
|
||||
/// </summary>
|
||||
public bool Draggable
|
||||
{
|
||||
get => draggable;
|
||||
set => draggable = value;
|
||||
}
|
||||
|
||||
[SerializeField] Scrollbar scrollbar = default;
|
||||
|
||||
/// <summary>
|
||||
/// スクロールバーのオブジェクト.
|
||||
/// </summary>
|
||||
public Scrollbar Scrollbar => scrollbar;
|
||||
|
||||
/// <summary>
|
||||
/// 現在のスクロール位置.
|
||||
/// </summary>
|
||||
/// <value></value>
|
||||
public float Position
|
||||
{
|
||||
get => currentPosition;
|
||||
set
|
||||
{
|
||||
autoScrollState.Reset();
|
||||
velocity = 0f;
|
||||
dragging = false;
|
||||
|
||||
UpdatePosition(value);
|
||||
}
|
||||
}
|
||||
|
||||
readonly AutoScrollState autoScrollState = new AutoScrollState();
|
||||
|
||||
Action<float> onValueChanged;
|
||||
Action<int> onSelectionChanged;
|
||||
|
||||
Vector2 beginDragPointerPosition;
|
||||
float scrollStartPosition;
|
||||
float prevPosition;
|
||||
float currentPosition;
|
||||
|
||||
int totalCount;
|
||||
|
||||
bool hold;
|
||||
bool scrolling;
|
||||
bool dragging;
|
||||
float velocity;
|
||||
|
||||
[Serializable]
|
||||
class Snap
|
||||
{
|
||||
public bool Enable;
|
||||
public float VelocityThreshold;
|
||||
public float Duration;
|
||||
public Ease Easing;
|
||||
}
|
||||
|
||||
static readonly EasingFunction DefaultEasingFunction = Easing.Get(Ease.OutCubic);
|
||||
|
||||
class AutoScrollState
|
||||
{
|
||||
public bool Enable;
|
||||
public bool Elastic;
|
||||
public float Duration;
|
||||
public EasingFunction EasingFunction;
|
||||
public float StartTime;
|
||||
public float EndPosition;
|
||||
|
||||
public Action OnComplete;
|
||||
|
||||
public void Reset()
|
||||
{
|
||||
Enable = false;
|
||||
Elastic = false;
|
||||
Duration = 0f;
|
||||
StartTime = 0f;
|
||||
EasingFunction = DefaultEasingFunction;
|
||||
EndPosition = 0f;
|
||||
OnComplete = null;
|
||||
}
|
||||
|
||||
public void Complete()
|
||||
{
|
||||
OnComplete?.Invoke();
|
||||
Reset();
|
||||
}
|
||||
}
|
||||
|
||||
protected override void Start()
|
||||
{
|
||||
base.Start();
|
||||
|
||||
if (scrollbar)
|
||||
{
|
||||
scrollbar.onValueChanged.AddListener(x => UpdatePosition(x * (totalCount - 1f), false));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// スクロール位置が変化したときのコールバックを設定します.
|
||||
/// </summary>
|
||||
/// <param name="callback">スクロール位置が変化したときのコールバック.</param>
|
||||
public void OnValueChanged(Action<float> callback) => onValueChanged = callback;
|
||||
|
||||
/// <summary>
|
||||
/// 選択位置が変化したときのコールバックを設定します.
|
||||
/// </summary>
|
||||
/// <param name="callback">選択位置が変化したときのコールバック.</param>
|
||||
public void OnSelectionChanged(Action<int> callback) => onSelectionChanged = callback;
|
||||
|
||||
/// <summary>
|
||||
/// アイテムの総数を設定します.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <paramref name="totalCount"/> を元に最大スクロール位置を計算します.
|
||||
/// </remarks>
|
||||
/// <param name="totalCount">アイテムの総数.</param>
|
||||
public void SetTotalCount(int totalCount) => this.totalCount = totalCount;
|
||||
|
||||
/// <summary>
|
||||
/// 指定した位置まで移動します.
|
||||
/// </summary>
|
||||
/// <param name="position">スクロール位置. <c>0f</c> ~ <c>totalCount - 1f</c> の範囲.</param>
|
||||
/// <param name="duration">移動にかける秒数.</param>
|
||||
/// <param name="onComplete">移動が完了した際に呼び出されるコールバック.</param>
|
||||
public void ScrollTo(float position, float duration, Action onComplete = null) => ScrollTo(position, duration, Ease.OutCubic, onComplete);
|
||||
|
||||
/// <summary>
|
||||
/// 指定した位置まで移動します.
|
||||
/// </summary>
|
||||
/// <param name="position">スクロール位置. <c>0f</c> ~ <c>totalCount - 1f</c> の範囲.</param>
|
||||
/// <param name="duration">移動にかける秒数.</param>
|
||||
/// <param name="easing">移動に使用するイージング.</param>
|
||||
/// <param name="onComplete">移動が完了した際に呼び出されるコールバック.</param>
|
||||
public void ScrollTo(float position, float duration, Ease easing, Action onComplete = null) => ScrollTo(position, duration, Easing.Get(easing), onComplete);
|
||||
|
||||
/// <summary>
|
||||
/// 指定した位置まで移動します.
|
||||
/// </summary>
|
||||
/// <param name="position">スクロール位置. <c>0f</c> ~ <c>totalCount - 1f</c> の範囲.</param>
|
||||
/// <param name="duration">移動にかける秒数.</param>
|
||||
/// <param name="easingFunction">移動に使用するイージング関数.</param>
|
||||
/// <param name="onComplete">移動が完了した際に呼び出されるコールバック.</param>
|
||||
public void ScrollTo(float position, float duration, EasingFunction easingFunction, Action onComplete = null)
|
||||
{
|
||||
if (duration <= 0f)
|
||||
{
|
||||
Position = CircularPosition(position, totalCount);
|
||||
onComplete?.Invoke();
|
||||
return;
|
||||
}
|
||||
|
||||
autoScrollState.Reset();
|
||||
autoScrollState.Enable = true;
|
||||
autoScrollState.Duration = duration;
|
||||
autoScrollState.EasingFunction = easingFunction ?? DefaultEasingFunction;
|
||||
autoScrollState.StartTime = Time.unscaledTime;
|
||||
autoScrollState.EndPosition = currentPosition + CalculateMovementAmount(currentPosition, position);
|
||||
autoScrollState.OnComplete = onComplete;
|
||||
|
||||
velocity = 0f;
|
||||
scrollStartPosition = currentPosition;
|
||||
|
||||
UpdateSelection(Mathf.RoundToInt(CircularPosition(autoScrollState.EndPosition, totalCount)));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 指定したインデックスの位置までジャンプします.
|
||||
/// </summary>
|
||||
/// <param name="index">アイテムのインデックス.</param>
|
||||
public void JumpTo(int index)
|
||||
{
|
||||
if (index < 0 || index > totalCount - 1)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(index));
|
||||
}
|
||||
|
||||
UpdateSelection(index);
|
||||
Position = index;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <paramref name="sourceIndex"/> から <paramref name="destIndex"/> に移動する際の移動方向を返します.
|
||||
/// スクロール範囲が無制限に設定されている場合は, 最短距離の移動方向を返します.
|
||||
/// </summary>
|
||||
/// <param name="sourceIndex">移動元のインデックス.</param>
|
||||
/// <param name="destIndex">移動先のインデックス.</param>
|
||||
/// <returns></returns>
|
||||
public MovementDirection GetMovementDirection(int sourceIndex, int destIndex)
|
||||
{
|
||||
var movementAmount = CalculateMovementAmount(sourceIndex, destIndex);
|
||||
return scrollDirection == ScrollDirection.Horizontal
|
||||
? movementAmount > 0
|
||||
? MovementDirection.Left
|
||||
: MovementDirection.Right
|
||||
: movementAmount > 0
|
||||
? MovementDirection.Up
|
||||
: MovementDirection.Down;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
void IPointerDownHandler.OnPointerDown(PointerEventData eventData)
|
||||
{
|
||||
if (!draggable || eventData.button != PointerEventData.InputButton.Left)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
hold = true;
|
||||
velocity = 0f;
|
||||
autoScrollState.Reset();
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
void IPointerUpHandler.OnPointerUp(PointerEventData eventData)
|
||||
{
|
||||
if (!draggable || eventData.button != PointerEventData.InputButton.Left)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (hold && snap.Enable)
|
||||
{
|
||||
UpdateSelection(Mathf.Clamp(Mathf.RoundToInt(currentPosition), 0, totalCount - 1));
|
||||
ScrollTo(Mathf.RoundToInt(currentPosition), snap.Duration, snap.Easing);
|
||||
}
|
||||
|
||||
hold = false;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
void IScrollHandler.OnScroll(PointerEventData eventData)
|
||||
{
|
||||
if (!draggable)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var delta = eventData.scrollDelta;
|
||||
|
||||
// Down is positive for scroll events, while in UI system up is positive.
|
||||
delta.y *= -1;
|
||||
var scrollDelta = scrollDirection == ScrollDirection.Horizontal
|
||||
? Mathf.Abs(delta.y) > Mathf.Abs(delta.x)
|
||||
? delta.y
|
||||
: delta.x
|
||||
: Mathf.Abs(delta.x) > Mathf.Abs(delta.y)
|
||||
? delta.x
|
||||
: delta.y;
|
||||
|
||||
if (eventData.IsScrolling())
|
||||
{
|
||||
scrolling = true;
|
||||
}
|
||||
|
||||
var position = currentPosition + scrollDelta / ViewportSize * scrollSensitivity;
|
||||
if (movementType == MovementType.Clamped)
|
||||
{
|
||||
position += CalculateOffset(position);
|
||||
}
|
||||
|
||||
if (autoScrollState.Enable)
|
||||
{
|
||||
autoScrollState.Reset();
|
||||
}
|
||||
|
||||
UpdatePosition(position);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
void IBeginDragHandler.OnBeginDrag(PointerEventData eventData)
|
||||
{
|
||||
if (!draggable || eventData.button != PointerEventData.InputButton.Left)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
hold = false;
|
||||
RectTransformUtility.ScreenPointToLocalPointInRectangle(
|
||||
viewport,
|
||||
eventData.position,
|
||||
eventData.pressEventCamera,
|
||||
out beginDragPointerPosition);
|
||||
|
||||
scrollStartPosition = currentPosition;
|
||||
dragging = true;
|
||||
autoScrollState.Reset();
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
void IDragHandler.OnDrag(PointerEventData eventData)
|
||||
{
|
||||
if (!draggable || eventData.button != PointerEventData.InputButton.Left || !dragging)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!RectTransformUtility.ScreenPointToLocalPointInRectangle(
|
||||
viewport,
|
||||
eventData.position,
|
||||
eventData.pressEventCamera,
|
||||
out var dragPointerPosition))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var pointerDelta = dragPointerPosition - beginDragPointerPosition;
|
||||
var position = (scrollDirection == ScrollDirection.Horizontal ? -pointerDelta.x : pointerDelta.y)
|
||||
/ ViewportSize
|
||||
* scrollSensitivity
|
||||
+ scrollStartPosition;
|
||||
|
||||
var offset = CalculateOffset(position);
|
||||
position += offset;
|
||||
|
||||
if (movementType == MovementType.Elastic)
|
||||
{
|
||||
if (offset != 0f)
|
||||
{
|
||||
position -= RubberDelta(offset, scrollSensitivity);
|
||||
}
|
||||
}
|
||||
|
||||
UpdatePosition(position);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
void IEndDragHandler.OnEndDrag(PointerEventData eventData)
|
||||
{
|
||||
if (!draggable || eventData.button != PointerEventData.InputButton.Left)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
dragging = false;
|
||||
}
|
||||
|
||||
float CalculateOffset(float position)
|
||||
{
|
||||
if (movementType == MovementType.Unrestricted)
|
||||
{
|
||||
return 0f;
|
||||
}
|
||||
|
||||
if (position < 0f)
|
||||
{
|
||||
return -position;
|
||||
}
|
||||
|
||||
if (position > totalCount - 1)
|
||||
{
|
||||
return totalCount - 1 - position;
|
||||
}
|
||||
|
||||
return 0f;
|
||||
}
|
||||
|
||||
void UpdatePosition(float position, bool updateScrollbar = true)
|
||||
{
|
||||
onValueChanged?.Invoke(currentPosition = position);
|
||||
|
||||
if (scrollbar && updateScrollbar)
|
||||
{
|
||||
scrollbar.value = Mathf.Clamp01(position / Mathf.Max(totalCount - 1f, 1e-4f));
|
||||
}
|
||||
}
|
||||
|
||||
void UpdateSelection(int index) => onSelectionChanged?.Invoke(index);
|
||||
|
||||
float RubberDelta(float overStretching, float viewSize) =>
|
||||
(1 - 1 / (Mathf.Abs(overStretching) * 0.55f / viewSize + 1)) * viewSize * Mathf.Sign(overStretching);
|
||||
|
||||
void Update()
|
||||
{
|
||||
var deltaTime = Time.unscaledDeltaTime;
|
||||
var offset = CalculateOffset(currentPosition);
|
||||
|
||||
if (autoScrollState.Enable)
|
||||
{
|
||||
var position = 0f;
|
||||
|
||||
if (autoScrollState.Elastic)
|
||||
{
|
||||
position = Mathf.SmoothDamp(currentPosition, currentPosition + offset, ref velocity,
|
||||
elasticity, Mathf.Infinity, deltaTime);
|
||||
|
||||
if (Mathf.Abs(velocity) < 0.01f)
|
||||
{
|
||||
position = Mathf.Clamp(Mathf.RoundToInt(position), 0, totalCount - 1);
|
||||
velocity = 0f;
|
||||
autoScrollState.Complete();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var alpha = Mathf.Clamp01((Time.unscaledTime - autoScrollState.StartTime) /
|
||||
Mathf.Max(autoScrollState.Duration, float.Epsilon));
|
||||
position = Mathf.LerpUnclamped(scrollStartPosition, autoScrollState.EndPosition,
|
||||
autoScrollState.EasingFunction(alpha));
|
||||
|
||||
if (Mathf.Approximately(alpha, 1f))
|
||||
{
|
||||
autoScrollState.Complete();
|
||||
}
|
||||
}
|
||||
|
||||
UpdatePosition(position);
|
||||
}
|
||||
else if (!(dragging || scrolling) && (!Mathf.Approximately(offset, 0f) || !Mathf.Approximately(velocity, 0f)))
|
||||
{
|
||||
var position = currentPosition;
|
||||
|
||||
if (movementType == MovementType.Elastic && !Mathf.Approximately(offset, 0f))
|
||||
{
|
||||
autoScrollState.Reset();
|
||||
autoScrollState.Enable = true;
|
||||
autoScrollState.Elastic = true;
|
||||
|
||||
UpdateSelection(Mathf.Clamp(Mathf.RoundToInt(position), 0, totalCount - 1));
|
||||
}
|
||||
else if (inertia)
|
||||
{
|
||||
velocity *= Mathf.Pow(decelerationRate, deltaTime);
|
||||
|
||||
if (Mathf.Abs(velocity) < 0.001f)
|
||||
{
|
||||
velocity = 0f;
|
||||
}
|
||||
|
||||
position += velocity * deltaTime;
|
||||
|
||||
if (snap.Enable && Mathf.Abs(velocity) < snap.VelocityThreshold)
|
||||
{
|
||||
ScrollTo(Mathf.RoundToInt(currentPosition), snap.Duration, snap.Easing);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
velocity = 0f;
|
||||
}
|
||||
|
||||
if (!Mathf.Approximately(velocity, 0f))
|
||||
{
|
||||
if (movementType == MovementType.Clamped)
|
||||
{
|
||||
offset = CalculateOffset(position);
|
||||
position += offset;
|
||||
|
||||
if (Mathf.Approximately(position, 0f) || Mathf.Approximately(position, totalCount - 1f))
|
||||
{
|
||||
velocity = 0f;
|
||||
UpdateSelection(Mathf.RoundToInt(position));
|
||||
}
|
||||
}
|
||||
|
||||
UpdatePosition(position);
|
||||
}
|
||||
}
|
||||
|
||||
if (!autoScrollState.Enable && (dragging || scrolling) && inertia)
|
||||
{
|
||||
var newVelocity = (currentPosition - prevPosition) / deltaTime;
|
||||
velocity = Mathf.Lerp(velocity, newVelocity, deltaTime * 10f);
|
||||
}
|
||||
|
||||
prevPosition = currentPosition;
|
||||
scrolling = false;
|
||||
}
|
||||
|
||||
float CalculateMovementAmount(float sourcePosition, float destPosition)
|
||||
{
|
||||
if (movementType != MovementType.Unrestricted)
|
||||
{
|
||||
return Mathf.Clamp(destPosition, 0, totalCount - 1) - sourcePosition;
|
||||
}
|
||||
|
||||
var amount = CircularPosition(destPosition, totalCount) - CircularPosition(sourcePosition, totalCount);
|
||||
|
||||
if (Mathf.Abs(amount) > totalCount * 0.5f)
|
||||
{
|
||||
amount = Mathf.Sign(-amount) * (totalCount - Mathf.Abs(amount));
|
||||
}
|
||||
|
||||
return amount;
|
||||
}
|
||||
|
||||
float CircularPosition(float p, int size) => size < 1 ? 0 : p < 0 ? size - 1 + (p + 1) % size : p % size;
|
||||
}
|
||||
}
|
@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 9f0e995f494626a4f878eedbded37c8d
|
||||
timeCreated: 1487408581
|
||||
licenseType: Free
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -0,0 +1,428 @@
|
||||
/// Credit Simie
|
||||
/// Sourced from - http://forum.unity3d.com/threads/flowlayoutgroup.296709/
|
||||
/// Example http://forum.unity3d.com/threads/flowlayoutgroup.296709/
|
||||
/// Update by Martin Sharkbomb - http://forum.unity3d.com/threads/flowlayoutgroup.296709/#post-1977028
|
||||
/// Last item alignment fix by Vicente Russo - https://bitbucket.org/SimonDarksideJ/unity-ui-extensions/issues/22/flow-layout-group-align
|
||||
/// Vertical Flow by Ramon Molossi
|
||||
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace UnityEngine.UI.Extensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Layout Group controller that arranges children in bars, fitting as many on a line until total size exceeds parent bounds
|
||||
/// </summary>
|
||||
[AddComponentMenu("Layout/Extensions/Flow Layout Group")]
|
||||
public class FlowLayoutGroup : LayoutGroup
|
||||
{
|
||||
public enum Axis { Horizontal = 0, Vertical = 1 }
|
||||
|
||||
public float SpacingX = 0f;
|
||||
public float SpacingY = 0f;
|
||||
public bool ExpandHorizontalSpacing = false;
|
||||
|
||||
public bool ChildForceExpandWidth = false;
|
||||
public bool ChildForceExpandHeight = false;
|
||||
public bool invertOrder = false;
|
||||
private float _layoutHeight;
|
||||
private float _layoutWidth;
|
||||
|
||||
[SerializeField] protected Axis m_StartAxis = Axis.Horizontal;
|
||||
public Axis startAxis { get { return m_StartAxis; } set { SetProperty(ref m_StartAxis, value); } }
|
||||
|
||||
public override void CalculateLayoutInputHorizontal()
|
||||
{
|
||||
if (startAxis == Axis.Horizontal) {
|
||||
base.CalculateLayoutInputHorizontal ();
|
||||
var minWidth = GetGreatestMinimumChildWidth () + padding.left + padding.right;
|
||||
SetLayoutInputForAxis (minWidth, -1, -1, 0);
|
||||
} else {
|
||||
_layoutWidth = SetLayout (0, true);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public override void SetLayoutHorizontal()
|
||||
{
|
||||
SetLayout(0, false);
|
||||
}
|
||||
|
||||
public override void SetLayoutVertical()
|
||||
{
|
||||
SetLayout(1, false);
|
||||
}
|
||||
|
||||
public override void CalculateLayoutInputVertical()
|
||||
{
|
||||
if (startAxis == Axis.Horizontal) {
|
||||
_layoutHeight = SetLayout (1, true);
|
||||
} else {
|
||||
base.CalculateLayoutInputHorizontal ();
|
||||
var minHeight = GetGreatestMinimumChildHeigth () + padding.bottom + padding.top;
|
||||
SetLayoutInputForAxis (minHeight, -1, -1, 1);
|
||||
}
|
||||
}
|
||||
|
||||
protected bool IsCenterAlign
|
||||
{
|
||||
get
|
||||
{
|
||||
return childAlignment == TextAnchor.LowerCenter || childAlignment == TextAnchor.MiddleCenter ||
|
||||
childAlignment == TextAnchor.UpperCenter;
|
||||
}
|
||||
}
|
||||
|
||||
protected bool IsRightAlign
|
||||
{
|
||||
get
|
||||
{
|
||||
return childAlignment == TextAnchor.LowerRight || childAlignment == TextAnchor.MiddleRight ||
|
||||
childAlignment == TextAnchor.UpperRight;
|
||||
}
|
||||
}
|
||||
|
||||
protected bool IsMiddleAlign
|
||||
{
|
||||
get
|
||||
{
|
||||
return childAlignment == TextAnchor.MiddleLeft || childAlignment == TextAnchor.MiddleRight ||
|
||||
childAlignment == TextAnchor.MiddleCenter;
|
||||
}
|
||||
}
|
||||
|
||||
protected bool IsLowerAlign
|
||||
{
|
||||
get
|
||||
{
|
||||
return childAlignment == TextAnchor.LowerLeft || childAlignment == TextAnchor.LowerRight ||
|
||||
childAlignment == TextAnchor.LowerCenter;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Holds the rects that will make up the current bar being processed
|
||||
/// </summary>
|
||||
private readonly IList<RectTransform> _itemList = new List<RectTransform>();
|
||||
|
||||
/// <summary>
|
||||
/// Main layout method
|
||||
/// </summary>
|
||||
/// <param name="width">Width to calculate the layout with</param>
|
||||
/// <param name="axis">0 for horizontal axis, 1 for vertical</param>
|
||||
/// <param name="layoutInput">If true, sets the layout input for the axis. If false, sets child position for axis</param>
|
||||
public float SetLayout(int axis, bool layoutInput)
|
||||
{
|
||||
//container height and width
|
||||
var groupHeight = rectTransform.rect.height;
|
||||
var groupWidth = rectTransform.rect.width;
|
||||
|
||||
float spacingBetweenBars = 0;
|
||||
float spacingBetweenElements = 0;
|
||||
float offset = 0;
|
||||
float counterOffset = 0;
|
||||
float groupSize = 0;
|
||||
float workingSize = 0;
|
||||
if (startAxis == Axis.Horizontal) {
|
||||
groupSize = groupHeight;
|
||||
workingSize = groupWidth - padding.left - padding.right;
|
||||
if (IsLowerAlign) {
|
||||
offset = (float)padding.bottom;
|
||||
counterOffset = (float)padding.top;
|
||||
} else {
|
||||
offset = (float)padding.top;
|
||||
counterOffset = (float)padding.bottom;
|
||||
}
|
||||
spacingBetweenBars = SpacingY;
|
||||
spacingBetweenElements = SpacingX;
|
||||
} else if (startAxis == Axis.Vertical) {
|
||||
groupSize = groupWidth;
|
||||
workingSize = groupHeight - padding.top - padding.bottom;
|
||||
if (IsRightAlign) {
|
||||
offset = (float)padding.right;
|
||||
counterOffset = (float)padding.left;
|
||||
} else {
|
||||
offset = (float)padding.left;
|
||||
counterOffset = (float)padding.right;
|
||||
}
|
||||
spacingBetweenBars = SpacingX;
|
||||
spacingBetweenElements = SpacingY;
|
||||
}
|
||||
|
||||
var currentBarSize = 0f;
|
||||
var currentBarSpace = 0f;
|
||||
|
||||
for (var i = 0; i < rectChildren.Count; i++) {
|
||||
|
||||
int index = i;
|
||||
var child = rectChildren [index];
|
||||
float childSize = 0;
|
||||
float childOtherSize = 0;
|
||||
//get height and width of elements.
|
||||
if (startAxis == Axis.Horizontal) {
|
||||
if (invertOrder) {
|
||||
index = IsLowerAlign ? rectChildren.Count - 1 - i : i;
|
||||
}
|
||||
child = rectChildren [index];
|
||||
childSize = LayoutUtility.GetPreferredSize (child, 0);
|
||||
childSize = Mathf.Min (childSize, workingSize);
|
||||
childOtherSize = LayoutUtility.GetPreferredSize (child, 1);
|
||||
childOtherSize = Mathf.Min (childOtherSize, workingSize);
|
||||
} else if (startAxis == Axis.Vertical) {
|
||||
if (invertOrder) {
|
||||
index = IsRightAlign ? rectChildren.Count - 1 - i : i;
|
||||
}
|
||||
child = rectChildren [index];
|
||||
childSize = LayoutUtility.GetPreferredSize (child, 1);
|
||||
childSize = Mathf.Min (childSize, workingSize);
|
||||
childOtherSize = LayoutUtility.GetPreferredSize (child, 0);
|
||||
childOtherSize = Mathf.Min (childOtherSize, workingSize);
|
||||
}
|
||||
|
||||
// If adding this element would exceed the bounds of the container,
|
||||
// go to a new bar after processing the current bar
|
||||
if (currentBarSize + childSize > workingSize) {
|
||||
|
||||
currentBarSize -= spacingBetweenElements;
|
||||
|
||||
// Process current bar elements positioning
|
||||
if (!layoutInput) {
|
||||
if (startAxis == Axis.Horizontal) {
|
||||
float newOffset = CalculateRowVerticalOffset (groupSize, offset, currentBarSpace);
|
||||
LayoutRow (_itemList, currentBarSize, currentBarSpace, workingSize, padding.left, newOffset, axis);
|
||||
} else if (startAxis == Axis.Vertical) {
|
||||
float newOffset = CalculateColHorizontalOffset (groupSize, offset, currentBarSpace);
|
||||
LayoutCol (_itemList, currentBarSpace, currentBarSize, workingSize, newOffset, padding.top, axis);
|
||||
}
|
||||
}
|
||||
|
||||
// Clear existing bar
|
||||
_itemList.Clear ();
|
||||
|
||||
// Add the current bar space to total barSpace accumulator, and reset to 0 for the next row
|
||||
offset += currentBarSpace;
|
||||
offset += spacingBetweenBars;
|
||||
|
||||
currentBarSpace = 0;
|
||||
currentBarSize = 0;
|
||||
|
||||
}
|
||||
|
||||
currentBarSize += childSize;
|
||||
_itemList.Add (child);
|
||||
|
||||
// We need the largest element height to determine the starting position of the next line
|
||||
if (childOtherSize > currentBarSpace) {
|
||||
currentBarSpace = childOtherSize;
|
||||
}
|
||||
|
||||
// Don't do this for the last one
|
||||
if (i < rectChildren.Count - 1){
|
||||
currentBarSize += spacingBetweenElements;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Layout the final bar
|
||||
if (!layoutInput) {
|
||||
if (startAxis == Axis.Horizontal) {
|
||||
float newOffset = CalculateRowVerticalOffset (groupHeight, offset, currentBarSpace);
|
||||
currentBarSize -= spacingBetweenElements;
|
||||
LayoutRow (_itemList, currentBarSize, currentBarSpace, workingSize - (ChildForceExpandWidth ? 0 : spacingBetweenElements), padding.left, newOffset, axis);
|
||||
}else if (startAxis == Axis.Vertical) {
|
||||
float newOffset = CalculateColHorizontalOffset(groupWidth, offset, currentBarSpace);
|
||||
currentBarSize -= spacingBetweenElements;
|
||||
LayoutCol(_itemList, currentBarSpace, currentBarSize, workingSize - (ChildForceExpandHeight ? 0 : spacingBetweenElements), newOffset, padding.top, axis);
|
||||
}
|
||||
}
|
||||
|
||||
_itemList.Clear();
|
||||
|
||||
// Add the last bar space to the barSpace accumulator
|
||||
offset += currentBarSpace;
|
||||
offset += counterOffset;
|
||||
|
||||
if (layoutInput) {
|
||||
SetLayoutInputForAxis(offset, offset, -1, axis);
|
||||
}
|
||||
return offset;
|
||||
}
|
||||
|
||||
private float CalculateRowVerticalOffset(float groupHeight, float yOffset, float currentRowHeight)
|
||||
{
|
||||
if (IsLowerAlign) {
|
||||
return groupHeight - yOffset - currentRowHeight;
|
||||
} else if (IsMiddleAlign) {
|
||||
return groupHeight * 0.5f - _layoutHeight * 0.5f + yOffset;
|
||||
} else {
|
||||
return yOffset;
|
||||
}
|
||||
}
|
||||
|
||||
private float CalculateColHorizontalOffset(float groupWidth, float xOffset, float currentColWidth)
|
||||
{
|
||||
if (IsRightAlign) {
|
||||
return groupWidth - xOffset - currentColWidth;
|
||||
} else if (IsCenterAlign) {
|
||||
return groupWidth * 0.5f - _layoutWidth * 0.5f + xOffset;
|
||||
} else {
|
||||
return xOffset;
|
||||
}
|
||||
}
|
||||
|
||||
protected void LayoutRow(IList<RectTransform> contents, float rowWidth, float rowHeight, float maxWidth, float xOffset, float yOffset, int axis)
|
||||
{
|
||||
var xPos = xOffset;
|
||||
|
||||
if (!ChildForceExpandWidth && IsCenterAlign) {
|
||||
xPos += (maxWidth - rowWidth) * 0.5f;
|
||||
} else if (!ChildForceExpandWidth && IsRightAlign) {
|
||||
xPos += (maxWidth - rowWidth);
|
||||
}
|
||||
|
||||
var extraWidth = 0f;
|
||||
var extraSpacing = 0f;
|
||||
|
||||
if (ChildForceExpandWidth) {
|
||||
extraWidth = (maxWidth - rowWidth)/_itemList.Count;
|
||||
}
|
||||
else if (ExpandHorizontalSpacing) {
|
||||
extraSpacing = (maxWidth - rowWidth)/(_itemList.Count - 1);
|
||||
if (_itemList.Count > 1) {
|
||||
if (IsCenterAlign) {
|
||||
xPos -= extraSpacing * 0.5f * (_itemList.Count - 1);
|
||||
} else if (IsRightAlign) {
|
||||
xPos -= extraSpacing * (_itemList.Count - 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (var j = 0; j < _itemList.Count; j++) {
|
||||
|
||||
var index = IsLowerAlign ? _itemList.Count - 1 - j : j;
|
||||
|
||||
var rowChild = _itemList[index];
|
||||
|
||||
var rowChildWidth = LayoutUtility.GetPreferredSize(rowChild, 0) + extraWidth;
|
||||
var rowChildHeight = LayoutUtility.GetPreferredSize(rowChild, 1);
|
||||
|
||||
if (ChildForceExpandHeight)
|
||||
rowChildHeight = rowHeight;
|
||||
|
||||
rowChildWidth = Mathf.Min(rowChildWidth, maxWidth);
|
||||
|
||||
var yPos = yOffset;
|
||||
|
||||
if (IsMiddleAlign) {
|
||||
yPos += (rowHeight - rowChildHeight) * 0.5f;
|
||||
} else if (IsLowerAlign) {
|
||||
yPos += (rowHeight - rowChildHeight);
|
||||
}
|
||||
|
||||
if (ExpandHorizontalSpacing && j > 0) {
|
||||
xPos += extraSpacing;
|
||||
}
|
||||
|
||||
if (axis == 0) {
|
||||
SetChildAlongAxis (rowChild, 0, xPos, rowChildWidth);
|
||||
} else {
|
||||
SetChildAlongAxis (rowChild, 1, yPos, rowChildHeight);
|
||||
}
|
||||
|
||||
// Don't do horizontal spacing for the last one
|
||||
if (j < _itemList.Count - 1) {
|
||||
xPos += rowChildWidth + SpacingX;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected void LayoutCol(IList<RectTransform> contents, float colWidth, float colHeight, float maxHeight, float xOffset, float yOffset, int axis)
|
||||
{
|
||||
var yPos = yOffset;
|
||||
|
||||
if (!ChildForceExpandHeight && IsMiddleAlign) {
|
||||
yPos += (maxHeight - colHeight) * 0.5f;
|
||||
} else if (!ChildForceExpandHeight && IsLowerAlign) {
|
||||
yPos += (maxHeight - colHeight);
|
||||
}
|
||||
|
||||
var extraHeight = 0f;
|
||||
var extraSpacing = 0f;
|
||||
|
||||
if (ChildForceExpandHeight) {
|
||||
extraHeight = (maxHeight - colHeight)/_itemList.Count;
|
||||
}
|
||||
else if (ExpandHorizontalSpacing) {
|
||||
extraSpacing = (maxHeight - colHeight)/(_itemList.Count - 1);
|
||||
if (_itemList.Count > 1) {
|
||||
if (IsMiddleAlign) {
|
||||
yPos -= extraSpacing * 0.5f * (_itemList.Count - 1);
|
||||
} else if (IsLowerAlign) {
|
||||
yPos -= extraSpacing * (_itemList.Count - 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (var j = 0; j < _itemList.Count; j++) {
|
||||
|
||||
var index = IsRightAlign ? _itemList.Count - 1 - j : j;
|
||||
|
||||
var rowChild = _itemList[index];
|
||||
|
||||
var rowChildWidth = LayoutUtility.GetPreferredSize(rowChild, 0) ;
|
||||
var rowChildHeight = LayoutUtility.GetPreferredSize(rowChild, 1) + extraHeight;
|
||||
|
||||
if (ChildForceExpandWidth) {
|
||||
rowChildWidth = colWidth;
|
||||
}
|
||||
|
||||
rowChildHeight = Mathf.Min(rowChildHeight, maxHeight);
|
||||
|
||||
var xPos = xOffset;
|
||||
|
||||
if (IsCenterAlign) {
|
||||
xPos += (colWidth - rowChildWidth) * 0.5f;
|
||||
} else if (IsRightAlign) {
|
||||
xPos += (colWidth - rowChildWidth);
|
||||
}
|
||||
|
||||
//
|
||||
if (ExpandHorizontalSpacing && j > 0) {
|
||||
yPos += extraSpacing;
|
||||
}
|
||||
|
||||
if (axis == 0) {
|
||||
SetChildAlongAxis (rowChild, 0, xPos, rowChildWidth);
|
||||
} else {
|
||||
SetChildAlongAxis (rowChild, 1, yPos, rowChildHeight);
|
||||
}
|
||||
|
||||
// Don't do vertical spacing for the last one
|
||||
if (j < _itemList.Count - 1) {
|
||||
yPos += rowChildHeight + SpacingY;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public float GetGreatestMinimumChildWidth()
|
||||
{
|
||||
var max = 0f;
|
||||
for (var i = 0; i < rectChildren.Count; i++) {
|
||||
var w = LayoutUtility.GetMinWidth(rectChildren[i]);
|
||||
|
||||
max = Mathf.Max(w, max);
|
||||
}
|
||||
return max;
|
||||
}
|
||||
|
||||
public float GetGreatestMinimumChildHeigth()
|
||||
{
|
||||
var max = 0f;
|
||||
for (var i = 0; i < rectChildren.Count; i++) {
|
||||
var w = LayoutUtility.GetMinHeight(rectChildren[i]);
|
||||
|
||||
max = Mathf.Max(w, max);
|
||||
}
|
||||
return max;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 558b109da67a27b4686138b955f3a7e8
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
@ -0,0 +1,323 @@
|
||||
/// Credit BinaryX
|
||||
/// Sourced from - http://forum.unity3d.com/threads/scripts-useful-4-6-scripts-collection.264161/page-2#post-1945602
|
||||
/// Updated by SimonDarksideJ - removed dependency on a custom ScrollRect script. Now implements drag interfaces and standard Scroll Rect.
|
||||
|
||||
using System;
|
||||
using UnityEngine.EventSystems;
|
||||
|
||||
namespace UnityEngine.UI.Extensions
|
||||
{
|
||||
|
||||
[RequireComponent(typeof(ScrollRect))]
|
||||
[AddComponentMenu("Layout/Extensions/Horizontal Scroll Snap")]
|
||||
public class HorizontalScrollSnap : ScrollSnapBase
|
||||
{
|
||||
private bool updated = true;
|
||||
|
||||
void Start()
|
||||
{
|
||||
_isVertical = false;
|
||||
_childAnchorPoint = new Vector2(0, 0.5f);
|
||||
_currentPage = StartingScreen;
|
||||
panelDimensions = gameObject.GetComponent<RectTransform>().rect;
|
||||
UpdateLayout();
|
||||
}
|
||||
|
||||
void Update()
|
||||
{
|
||||
updated = false;
|
||||
|
||||
if (!_lerp && (_scroll_rect.velocity == Vector2.zero && _scroll_rect.inertia))
|
||||
{
|
||||
if (!_settled && !_pointerDown)
|
||||
{
|
||||
if (!IsRectSettledOnaPage(_screensContainer.anchoredPosition))
|
||||
{
|
||||
ScrollToClosestElement();
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
else if (_lerp)
|
||||
{
|
||||
_screensContainer.anchoredPosition = Vector3.Lerp(_screensContainer.anchoredPosition, _lerp_target, transitionSpeed * (UseTimeScale ? Time.deltaTime : Time.unscaledDeltaTime));
|
||||
if (Vector3.Distance(_screensContainer.anchoredPosition, _lerp_target) < 0.2f)
|
||||
{
|
||||
_screensContainer.anchoredPosition = _lerp_target;
|
||||
_lerp = false;
|
||||
EndScreenChange();
|
||||
}
|
||||
}
|
||||
|
||||
if (UseHardSwipe) return;
|
||||
|
||||
CurrentPage = GetPageforPosition(_screensContainer.anchoredPosition);
|
||||
|
||||
//If the container is moving check if it needs to settle on a page
|
||||
if (!_pointerDown)
|
||||
{
|
||||
if (_scroll_rect.velocity.x > 0.01 || _scroll_rect.velocity.x < -0.01)
|
||||
{
|
||||
//if the pointer is released and is moving slower than the threshold, then just land on a page
|
||||
if (IsRectMovingSlowerThanThreshold(0))
|
||||
{
|
||||
ScrollToClosestElement();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private bool IsRectMovingSlowerThanThreshold(float startingSpeed)
|
||||
{
|
||||
return (_scroll_rect.velocity.x > startingSpeed && _scroll_rect.velocity.x < SwipeVelocityThreshold) ||
|
||||
(_scroll_rect.velocity.x < startingSpeed && _scroll_rect.velocity.x > -SwipeVelocityThreshold);
|
||||
}
|
||||
|
||||
public void DistributePages()
|
||||
{
|
||||
_screens = _screensContainer.childCount;
|
||||
_scroll_rect.horizontalNormalizedPosition = 0;
|
||||
|
||||
float _offset = 0;
|
||||
float _dimension = 0;
|
||||
Rect panelDimensions = gameObject.GetComponent<RectTransform>().rect;
|
||||
float currentXPosition = 0;
|
||||
var pageStepValue = _childSize = (int)panelDimensions.width * ((PageStep == 0) ? 3 : PageStep);
|
||||
|
||||
for (int i = 0; i < _screensContainer.transform.childCount; i++)
|
||||
{
|
||||
RectTransform child = _screensContainer.transform.GetChild(i).gameObject.GetComponent<RectTransform>();
|
||||
currentXPosition = _offset + i * pageStepValue;
|
||||
child.sizeDelta = new Vector2(panelDimensions.width, panelDimensions.height);
|
||||
child.anchoredPosition = new Vector2(currentXPosition, 0f);
|
||||
child.anchorMin = child.anchorMax = child.pivot = _childAnchorPoint;
|
||||
}
|
||||
|
||||
_dimension = currentXPosition + _offset * -1;
|
||||
|
||||
_screensContainer.GetComponent<RectTransform>().offsetMax = new Vector2(_dimension, 0f);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Add a new child to this Scroll Snap and recalculate it's children
|
||||
/// </summary>
|
||||
/// <param name="GO">GameObject to add to the ScrollSnap</param>
|
||||
public void AddChild(GameObject GO)
|
||||
{
|
||||
AddChild(GO, false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Add a new child to this Scroll Snap and recalculate it's children
|
||||
/// </summary>
|
||||
/// <param name="GO">GameObject to add to the ScrollSnap</param>
|
||||
/// <param name="WorldPositionStays">Should the world position be updated to it's parent transform?</param>
|
||||
public void AddChild(GameObject GO, bool WorldPositionStays)
|
||||
{
|
||||
_scroll_rect.horizontalNormalizedPosition = 0;
|
||||
GO.transform.SetParent(_screensContainer, WorldPositionStays);
|
||||
InitialiseChildObjectsFromScene();
|
||||
DistributePages();
|
||||
if (MaskArea)
|
||||
UpdateVisible();
|
||||
|
||||
SetScrollContainerPosition();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Remove a new child to this Scroll Snap and recalculate it's children
|
||||
/// *Note, this is an index address (0-x)
|
||||
/// </summary>
|
||||
/// <param name="index">Index element of child to remove</param>
|
||||
/// <param name="ChildRemoved">Resulting removed GO</param>
|
||||
public void RemoveChild(int index, out GameObject ChildRemoved)
|
||||
{
|
||||
RemoveChild(index, false, out ChildRemoved);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Remove a new child to this Scroll Snap and recalculate it's children
|
||||
/// *Note, this is an index address (0-x)
|
||||
/// </summary>
|
||||
/// <param name="index">Index element of child to remove</param>
|
||||
/// <param name="WorldPositionStays">If true, the parent-relative position, scale and rotation are modified such that the object keeps the same world space position, rotation and scale as before</param>
|
||||
/// <param name="ChildRemoved">Resulting removed GO</param>
|
||||
public void RemoveChild(int index, bool WorldPositionStays, out GameObject ChildRemoved)
|
||||
{
|
||||
ChildRemoved = null;
|
||||
if (index < 0 || index > _screensContainer.childCount)
|
||||
{
|
||||
return;
|
||||
}
|
||||
_scroll_rect.horizontalNormalizedPosition = 0;
|
||||
|
||||
Transform child = _screensContainer.transform.GetChild(index);
|
||||
child.SetParent(null, WorldPositionStays);
|
||||
ChildRemoved = child.gameObject;
|
||||
InitialiseChildObjectsFromScene();
|
||||
DistributePages();
|
||||
if (MaskArea)
|
||||
UpdateVisible();
|
||||
|
||||
if (_currentPage > _screens - 1)
|
||||
{
|
||||
CurrentPage = _screens - 1;
|
||||
}
|
||||
|
||||
SetScrollContainerPosition();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Remove all children from this ScrollSnap
|
||||
/// </summary>
|
||||
/// <param name="ChildrenRemoved">Array of child GO's removed</param>
|
||||
public void RemoveAllChildren(out GameObject[] ChildrenRemoved)
|
||||
{
|
||||
RemoveAllChildren(false, out ChildrenRemoved);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Remove all children from this ScrollSnap
|
||||
/// </summary>
|
||||
/// <param name="WorldPositionStays">If true, the parent-relative position, scale and rotation are modified such that the object keeps the same world space position, rotation and scale as before</param>
|
||||
/// <param name="ChildrenRemoved">Array of child GO's removed</param>
|
||||
public void RemoveAllChildren(bool WorldPositionStays, out GameObject[] ChildrenRemoved)
|
||||
{
|
||||
var _screenCount = _screensContainer.childCount;
|
||||
ChildrenRemoved = new GameObject[_screenCount];
|
||||
|
||||
for (int i = _screenCount - 1; i >= 0; i--)
|
||||
{
|
||||
ChildrenRemoved[i] = _screensContainer.GetChild(i).gameObject;
|
||||
ChildrenRemoved[i].transform.SetParent(null, WorldPositionStays);
|
||||
}
|
||||
|
||||
_scroll_rect.horizontalNormalizedPosition = 0;
|
||||
CurrentPage = 0;
|
||||
InitialiseChildObjectsFromScene();
|
||||
DistributePages();
|
||||
if (MaskArea)
|
||||
UpdateVisible();
|
||||
}
|
||||
|
||||
private void SetScrollContainerPosition()
|
||||
{
|
||||
_scrollStartPosition = _screensContainer.anchoredPosition.x;
|
||||
_scroll_rect.horizontalNormalizedPosition = (float)(_currentPage) / (_screens - 1);
|
||||
OnCurrentScreenChange(_currentPage);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// used for changing / updating between screen resolutions
|
||||
/// </summary>
|
||||
public void UpdateLayout()
|
||||
{
|
||||
_lerp = false;
|
||||
DistributePages();
|
||||
if (MaskArea)
|
||||
UpdateVisible();
|
||||
SetScrollContainerPosition();
|
||||
OnCurrentScreenChange(_currentPage);
|
||||
}
|
||||
|
||||
private void OnRectTransformDimensionsChange()
|
||||
{
|
||||
if (_childAnchorPoint != Vector2.zero)
|
||||
{
|
||||
UpdateLayout();
|
||||
}
|
||||
}
|
||||
|
||||
private void OnEnable()
|
||||
{
|
||||
InitialiseChildObjectsFromScene();
|
||||
DistributePages();
|
||||
if (MaskArea)
|
||||
UpdateVisible();
|
||||
|
||||
if (JumpOnEnable || !RestartOnEnable)
|
||||
SetScrollContainerPosition();
|
||||
if (RestartOnEnable)
|
||||
GoToScreen(StartingScreen);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Release screen to swipe
|
||||
/// </summary>
|
||||
/// <param name="eventData"></param>
|
||||
public override void OnEndDrag(PointerEventData eventData)
|
||||
{
|
||||
if (updated)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// to prevent double dragging, only act on EndDrag once per frame
|
||||
updated = true;
|
||||
|
||||
_pointerDown = false;
|
||||
|
||||
if (_scroll_rect.horizontal)
|
||||
{
|
||||
if (UseSwipeDeltaThreshold && Math.Abs(eventData.delta.x) < SwipeDeltaThreshold)
|
||||
{
|
||||
ScrollToClosestElement();
|
||||
}
|
||||
else
|
||||
{
|
||||
var distance = Vector3.Distance(_startPosition, _screensContainer.anchoredPosition);
|
||||
|
||||
if (UseHardSwipe)
|
||||
{
|
||||
_scroll_rect.velocity = Vector3.zero;
|
||||
|
||||
if (distance > FastSwipeThreshold)
|
||||
{
|
||||
if (_startPosition.x - _screensContainer.anchoredPosition.x > 0)
|
||||
{
|
||||
NextScreen();
|
||||
}
|
||||
else
|
||||
{
|
||||
PreviousScreen();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
ScrollToClosestElement();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (UseFastSwipe && distance < panelDimensions.width && distance >= FastSwipeThreshold)
|
||||
{
|
||||
_scroll_rect.velocity = Vector3.zero;
|
||||
if (_startPosition.x - _screensContainer.anchoredPosition.x > 0)
|
||||
{
|
||||
if (_startPosition.x - _screensContainer.anchoredPosition.x > _childSize / 3)
|
||||
{
|
||||
ScrollToClosestElement();
|
||||
}
|
||||
else
|
||||
{
|
||||
NextScreen();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (_startPosition.x - _screensContainer.anchoredPosition.x < -_childSize / 3)
|
||||
{
|
||||
ScrollToClosestElement();
|
||||
}
|
||||
else
|
||||
{
|
||||
PreviousScreen();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 609dcc22aadcc16418bfac22716ee9a6
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
@ -0,0 +1,14 @@
|
||||
/// Credit SimonDarksideJ
|
||||
/// Required for scrollbar support to work across ALL scroll snaps
|
||||
|
||||
|
||||
namespace UnityEngine.UI.Extensions
|
||||
{
|
||||
internal interface IScrollSnap
|
||||
{
|
||||
void ChangePage(int page);
|
||||
void SetLerp(bool value);
|
||||
int CurrentPage();
|
||||
void StartScreenChange();
|
||||
}
|
||||
}
|
@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 08ba470d8c8dc384e9d5828136742452
|
||||
timeCreated: 1498921617
|
||||
licenseType: Free
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -0,0 +1,101 @@
|
||||
|
||||
/// Credit Danny Goodayle
|
||||
/// Sourced from - http://www.justapixel.co.uk/radial-layouts-nice-and-simple-in-unity3ds-ui-system/
|
||||
/// Updated by SimonDarksideJ - removed dependency on a custom ScrollRect script. Now implements drag interfaces and standard Scroll Rect.
|
||||
/// Child Layout fix by John Hattan - enables an options
|
||||
|
||||
/*
|
||||
Radial Layout Group by Just a Pixel (Danny Goodayle) - http://www.justapixel.co.uk
|
||||
Copyright (c) 2015
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
*/
|
||||
|
||||
namespace UnityEngine.UI.Extensions
|
||||
{
|
||||
[AddComponentMenu("Layout/Extensions/Radial Layout")]
|
||||
public class RadialLayout : LayoutGroup
|
||||
{
|
||||
public float fDistance;
|
||||
[Range(0f, 360f)]
|
||||
public float MinAngle, MaxAngle, StartAngle;
|
||||
public bool OnlyLayoutVisible = false;
|
||||
protected override void OnEnable() { base.OnEnable(); CalculateRadial(); }
|
||||
public override void SetLayoutHorizontal()
|
||||
{
|
||||
}
|
||||
public override void SetLayoutVertical()
|
||||
{
|
||||
}
|
||||
public override void CalculateLayoutInputVertical()
|
||||
{
|
||||
CalculateRadial();
|
||||
}
|
||||
public override void CalculateLayoutInputHorizontal()
|
||||
{
|
||||
CalculateRadial();
|
||||
}
|
||||
#if UNITY_EDITOR
|
||||
protected override void OnValidate()
|
||||
{
|
||||
base.OnValidate();
|
||||
CalculateRadial();
|
||||
}
|
||||
#endif
|
||||
void CalculateRadial()
|
||||
{
|
||||
m_Tracker.Clear();
|
||||
if (transform.childCount == 0)
|
||||
return;
|
||||
|
||||
int ChildrenToFormat = 0;
|
||||
if (OnlyLayoutVisible)
|
||||
{
|
||||
for (int i = 0; i < transform.childCount; i++)
|
||||
{
|
||||
RectTransform child = (RectTransform)transform.GetChild(i);
|
||||
if ((child != null) && child.gameObject.activeSelf)
|
||||
++ChildrenToFormat;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
ChildrenToFormat = transform.childCount;
|
||||
}
|
||||
|
||||
float fOffsetAngle = (MaxAngle - MinAngle) / ChildrenToFormat;
|
||||
|
||||
float fAngle = StartAngle;
|
||||
for (int i = 0; i < transform.childCount; i++)
|
||||
{
|
||||
RectTransform child = (RectTransform)transform.GetChild(i);
|
||||
if ((child != null) && (!OnlyLayoutVisible || child.gameObject.activeSelf))
|
||||
{
|
||||
//Adding the elements to the tracker stops the user from modifying their positions via the editor.
|
||||
m_Tracker.Add(this, child,
|
||||
DrivenTransformProperties.Anchors |
|
||||
DrivenTransformProperties.AnchoredPosition |
|
||||
DrivenTransformProperties.Pivot);
|
||||
Vector3 vPos = new Vector3(Mathf.Cos(fAngle * Mathf.Deg2Rad), Mathf.Sin(fAngle * Mathf.Deg2Rad), 0);
|
||||
child.localPosition = vPos * fDistance;
|
||||
//Force objects to be center aligned, this can be changed however I'd suggest you keep all of the objects with the same anchor points.
|
||||
child.anchorMin = child.anchorMax = child.pivot = new Vector2(0.5f, 0.5f);
|
||||
fAngle += fOffsetAngle;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 1ebce7906e5d20a4fb26d8b510b81926
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
@ -0,0 +1,383 @@
|
||||
/// Credit setchi (https://github.com/setchi)
|
||||
/// Sourced from - https://github.com/setchi/FancyScrollView
|
||||
|
||||
using System;
|
||||
using UnityEngine.Events;
|
||||
using UnityEngine.EventSystems;
|
||||
|
||||
namespace UnityEngine.UI.Extensions
|
||||
{
|
||||
public class ScrollPositionController : UIBehaviour, IBeginDragHandler, IEndDragHandler, IDragHandler
|
||||
{
|
||||
[SerializeField]
|
||||
RectTransform viewport = null;
|
||||
[SerializeField]
|
||||
ScrollDirection directionOfRecognize = ScrollDirection.Vertical;
|
||||
[SerializeField]
|
||||
MovementType movementType = MovementType.Elastic;
|
||||
[SerializeField]
|
||||
float elasticity = 0.1f;
|
||||
[SerializeField]
|
||||
float scrollSensitivity = 1f;
|
||||
[SerializeField]
|
||||
bool inertia = true;
|
||||
[SerializeField, Tooltip("Only used when inertia is enabled")]
|
||||
float decelerationRate = 0.03f;
|
||||
[SerializeField, Tooltip("Only used when inertia is enabled")]
|
||||
Snap snap = new Snap { Enable = true, VelocityThreshold = 0.5f, Duration = 0.3f };
|
||||
[SerializeField]
|
||||
int dataCount;
|
||||
|
||||
readonly AutoScrollState autoScrollState = new AutoScrollState();
|
||||
|
||||
Action<float> onUpdatePosition;
|
||||
Action<int> onItemSelected;
|
||||
|
||||
Vector2 pointerStartLocalPosition;
|
||||
float dragStartScrollPosition;
|
||||
float prevScrollPosition;
|
||||
float currentScrollPosition;
|
||||
|
||||
bool dragging;
|
||||
float velocity;
|
||||
|
||||
enum ScrollDirection
|
||||
{
|
||||
Vertical,
|
||||
Horizontal,
|
||||
}
|
||||
|
||||
enum MovementType
|
||||
{
|
||||
Unrestricted = ScrollRect.MovementType.Unrestricted,
|
||||
Elastic = ScrollRect.MovementType.Elastic,
|
||||
Clamped = ScrollRect.MovementType.Clamped
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
struct Snap
|
||||
{
|
||||
public bool Enable;
|
||||
public float VelocityThreshold;
|
||||
public float Duration;
|
||||
}
|
||||
|
||||
class AutoScrollState
|
||||
{
|
||||
public bool Enable;
|
||||
public bool Elastic;
|
||||
public float Duration;
|
||||
public float StartTime;
|
||||
public float EndScrollPosition;
|
||||
|
||||
public void Reset()
|
||||
{
|
||||
Enable = false;
|
||||
Elastic = false;
|
||||
Duration = 0f;
|
||||
StartTime = 0f;
|
||||
EndScrollPosition = 0f;
|
||||
}
|
||||
}
|
||||
|
||||
public void OnUpdatePosition(Action<float> onUpdatePosition)
|
||||
{
|
||||
this.onUpdatePosition = onUpdatePosition;
|
||||
}
|
||||
|
||||
public void OnItemSelected(Action<int> onItemSelected)
|
||||
{
|
||||
this.onItemSelected = onItemSelected;
|
||||
}
|
||||
|
||||
public void SetDataCount(int dataCount)
|
||||
{
|
||||
this.dataCount = dataCount;
|
||||
}
|
||||
|
||||
public void ScrollTo(int index, float duration)
|
||||
{
|
||||
autoScrollState.Reset();
|
||||
autoScrollState.Enable = true;
|
||||
autoScrollState.Duration = duration;
|
||||
autoScrollState.StartTime = Time.unscaledTime;
|
||||
autoScrollState.EndScrollPosition = CalculateDestinationIndex(index);
|
||||
|
||||
velocity = 0f;
|
||||
dragStartScrollPosition = currentScrollPosition;
|
||||
|
||||
ItemSelected(Mathf.RoundToInt(GetCircularPosition(autoScrollState.EndScrollPosition, dataCount)));
|
||||
}
|
||||
|
||||
public void JumpTo(int index)
|
||||
{
|
||||
autoScrollState.Reset();
|
||||
|
||||
velocity = 0f;
|
||||
dragging = false;
|
||||
|
||||
index = CalculateDestinationIndex(index);
|
||||
|
||||
ItemSelected(index);
|
||||
UpdatePosition(index);
|
||||
}
|
||||
|
||||
void IBeginDragHandler.OnBeginDrag(PointerEventData eventData)
|
||||
{
|
||||
if (eventData.button != PointerEventData.InputButton.Left)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
pointerStartLocalPosition = Vector2.zero;
|
||||
RectTransformUtility.ScreenPointToLocalPointInRectangle(
|
||||
viewport,
|
||||
eventData.position,
|
||||
eventData.pressEventCamera,
|
||||
out pointerStartLocalPosition);
|
||||
|
||||
dragStartScrollPosition = currentScrollPosition;
|
||||
dragging = true;
|
||||
autoScrollState.Reset();
|
||||
}
|
||||
|
||||
void IDragHandler.OnDrag(PointerEventData eventData)
|
||||
{
|
||||
if (eventData.button != PointerEventData.InputButton.Left)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!dragging)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Vector2 localCursor;
|
||||
if (!RectTransformUtility.ScreenPointToLocalPointInRectangle(
|
||||
viewport,
|
||||
eventData.position,
|
||||
eventData.pressEventCamera,
|
||||
out localCursor))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var pointerDelta = localCursor - pointerStartLocalPosition;
|
||||
var position = (directionOfRecognize == ScrollDirection.Horizontal ? -pointerDelta.x : pointerDelta.y)
|
||||
/ GetViewportSize()
|
||||
* scrollSensitivity
|
||||
+ dragStartScrollPosition;
|
||||
|
||||
var offset = CalculateOffset(position);
|
||||
position += offset;
|
||||
|
||||
if (movementType == MovementType.Elastic)
|
||||
{
|
||||
if (offset != 0f)
|
||||
{
|
||||
position -= RubberDelta(offset, scrollSensitivity);
|
||||
}
|
||||
}
|
||||
|
||||
UpdatePosition(position);
|
||||
}
|
||||
|
||||
void IEndDragHandler.OnEndDrag(PointerEventData eventData)
|
||||
{
|
||||
if (eventData.button != PointerEventData.InputButton.Left)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
dragging = false;
|
||||
}
|
||||
|
||||
float GetViewportSize()
|
||||
{
|
||||
return directionOfRecognize == ScrollDirection.Horizontal
|
||||
? viewport.rect.size.x
|
||||
: viewport.rect.size.y;
|
||||
}
|
||||
|
||||
float CalculateOffset(float position)
|
||||
{
|
||||
if (movementType == MovementType.Unrestricted)
|
||||
{
|
||||
return 0f;
|
||||
}
|
||||
|
||||
if (position < 0f)
|
||||
{
|
||||
return -position;
|
||||
}
|
||||
|
||||
if (position > dataCount - 1)
|
||||
{
|
||||
return dataCount - 1 - position;
|
||||
}
|
||||
|
||||
return 0f;
|
||||
}
|
||||
|
||||
void UpdatePosition(float position)
|
||||
{
|
||||
currentScrollPosition = position;
|
||||
|
||||
if (onUpdatePosition != null)
|
||||
{
|
||||
onUpdatePosition(currentScrollPosition);
|
||||
}
|
||||
}
|
||||
|
||||
void ItemSelected(int index)
|
||||
{
|
||||
if (onItemSelected != null)
|
||||
{
|
||||
onItemSelected(index);
|
||||
}
|
||||
}
|
||||
|
||||
float RubberDelta(float overStretching, float viewSize)
|
||||
{
|
||||
return (1 - (1 / ((Mathf.Abs(overStretching) * 0.55f / viewSize) + 1))) * viewSize * Mathf.Sign(overStretching);
|
||||
}
|
||||
|
||||
void Update()
|
||||
{
|
||||
var deltaTime = Time.unscaledDeltaTime;
|
||||
var offset = CalculateOffset(currentScrollPosition);
|
||||
|
||||
if (autoScrollState.Enable)
|
||||
{
|
||||
var position = 0f;
|
||||
|
||||
if (autoScrollState.Elastic)
|
||||
{
|
||||
var speed = velocity;
|
||||
position = Mathf.SmoothDamp(currentScrollPosition, currentScrollPosition + offset, ref speed, elasticity, Mathf.Infinity, deltaTime);
|
||||
velocity = speed;
|
||||
|
||||
if (Mathf.Abs(velocity) < 0.01f)
|
||||
{
|
||||
position = Mathf.Clamp(Mathf.RoundToInt(position), 0, dataCount - 1);
|
||||
velocity = 0f;
|
||||
autoScrollState.Reset();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var alpha = Mathf.Clamp01((Time.unscaledTime - autoScrollState.StartTime) / Mathf.Max(autoScrollState.Duration, float.Epsilon));
|
||||
position = Mathf.Lerp(dragStartScrollPosition, autoScrollState.EndScrollPosition, EaseInOutCubic(0, 1, alpha));
|
||||
|
||||
if (Mathf.Approximately(alpha, 1f))
|
||||
{
|
||||
autoScrollState.Reset();
|
||||
}
|
||||
}
|
||||
|
||||
UpdatePosition(position);
|
||||
}
|
||||
else if (!dragging && (!Mathf.Approximately(offset, 0f) || !Mathf.Approximately(velocity, 0f)))
|
||||
{
|
||||
var position = currentScrollPosition;
|
||||
|
||||
if (movementType == MovementType.Elastic && !Mathf.Approximately(offset, 0f))
|
||||
{
|
||||
autoScrollState.Reset();
|
||||
autoScrollState.Enable = true;
|
||||
autoScrollState.Elastic = true;
|
||||
|
||||
ItemSelected(Mathf.Clamp(Mathf.RoundToInt(position), 0, dataCount - 1));
|
||||
}
|
||||
else if (inertia)
|
||||
{
|
||||
velocity *= Mathf.Pow(decelerationRate, deltaTime);
|
||||
|
||||
if (Mathf.Abs(velocity) < 0.001f)
|
||||
{
|
||||
velocity = 0f;
|
||||
}
|
||||
|
||||
position += velocity * deltaTime;
|
||||
|
||||
if (snap.Enable && Mathf.Abs(velocity) < snap.VelocityThreshold)
|
||||
{
|
||||
ScrollTo(Mathf.RoundToInt(currentScrollPosition), snap.Duration);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
velocity = 0f;
|
||||
}
|
||||
|
||||
if (!Mathf.Approximately(velocity, 0f))
|
||||
{
|
||||
if (movementType == MovementType.Clamped)
|
||||
{
|
||||
offset = CalculateOffset(position);
|
||||
position += offset;
|
||||
|
||||
if (Mathf.Approximately(position, 0f) || Mathf.Approximately(position, dataCount - 1f))
|
||||
{
|
||||
velocity = 0f;
|
||||
ItemSelected(Mathf.RoundToInt(position));
|
||||
}
|
||||
}
|
||||
|
||||
UpdatePosition(position);
|
||||
}
|
||||
}
|
||||
|
||||
if (!autoScrollState.Enable && dragging && inertia)
|
||||
{
|
||||
var newVelocity = (currentScrollPosition - prevScrollPosition) / deltaTime;
|
||||
velocity = Mathf.Lerp(velocity, newVelocity, deltaTime * 10f);
|
||||
}
|
||||
|
||||
if (currentScrollPosition != prevScrollPosition)
|
||||
{
|
||||
prevScrollPosition = currentScrollPosition;
|
||||
}
|
||||
}
|
||||
|
||||
int CalculateDestinationIndex(int index)
|
||||
{
|
||||
return movementType == MovementType.Unrestricted
|
||||
? CalculateClosestIndex(index)
|
||||
: Mathf.Clamp(index, 0, dataCount - 1);
|
||||
}
|
||||
|
||||
int CalculateClosestIndex(int index)
|
||||
{
|
||||
var diff = GetCircularPosition(index, dataCount)
|
||||
- GetCircularPosition(currentScrollPosition, dataCount);
|
||||
|
||||
if (Mathf.Abs(diff) > dataCount * 0.5f)
|
||||
{
|
||||
diff = Mathf.Sign(-diff) * (dataCount - Mathf.Abs(diff));
|
||||
}
|
||||
|
||||
return Mathf.RoundToInt(diff + currentScrollPosition);
|
||||
}
|
||||
|
||||
float GetCircularPosition(float position, int length)
|
||||
{
|
||||
return position < 0 ? length - 1 + (position + 1) % length : position % length;
|
||||
}
|
||||
|
||||
float EaseInOutCubic(float start, float end, float value)
|
||||
{
|
||||
value /= 0.5f;
|
||||
end -= start;
|
||||
|
||||
if (value < 1f)
|
||||
{
|
||||
return end * 0.5f * value * value * value + start;
|
||||
}
|
||||
|
||||
value -= 2f;
|
||||
return end * 0.5f * (value * value * value + 2f) + start;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: cc9ad31350b1b6348b57c626195a562d
|
||||
timeCreated: 1501610618
|
||||
licenseType: Free
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -0,0 +1,572 @@
|
||||
/// Credit BinaryX
|
||||
/// Sourced from - http://forum.unity3d.com/threads/scripts-useful-4-6-scripts-collection.264161/page-2#post-1945602
|
||||
/// Updated by simonDarksideJ - removed dependency on a custom ScrollRect script. Now implements drag interfaces and standard Scroll Rect.
|
||||
/// Update by xesenix - rewrote almost the entire code
|
||||
/// - configuration for direction move instead of 2 concurrent class (easier to change direction in editor)
|
||||
/// - supports list layout with horizontal or vertical layout need to match direction with type of layout used
|
||||
/// - dynamic checks if scrolled list size changes and recalculates anchor positions
|
||||
/// and item size based on itemsVisibleAtOnce and size of root container
|
||||
/// if you don't wish to use this auto resize turn of autoLayoutItems
|
||||
/// - fixed current page made it independent from pivot
|
||||
/// - replaced pagination with delegate function
|
||||
using System;
|
||||
using UnityEngine.EventSystems;
|
||||
|
||||
namespace UnityEngine.UI.Extensions
|
||||
{
|
||||
[ExecuteInEditMode]
|
||||
[RequireComponent(typeof(ScrollRect))]
|
||||
[AddComponentMenu("UI/Extensions/Scroll Snap")]
|
||||
public class ScrollSnap : MonoBehaviour, IBeginDragHandler, IEndDragHandler, IDragHandler, IScrollSnap
|
||||
{
|
||||
// needed because of reversed behaviour of axis Y compared to X
|
||||
// (positions of children lower in children list in horizontal directions grows when in vertical it gets smaller)
|
||||
public enum ScrollDirection
|
||||
{
|
||||
Horizontal,
|
||||
Vertical
|
||||
}
|
||||
|
||||
private ScrollRect _scroll_rect;
|
||||
|
||||
private RectTransform _scrollRectTransform;
|
||||
|
||||
private Transform _listContainerTransform;
|
||||
|
||||
//private RectTransform _rectTransform;
|
||||
|
||||
private int _pages;
|
||||
|
||||
private int _startingPage = 0;
|
||||
|
||||
// anchor points to lerp to see child on certain indexes
|
||||
private Vector3[] _pageAnchorPositions;
|
||||
|
||||
private Vector3 _lerpTarget;
|
||||
|
||||
private bool _lerp;
|
||||
|
||||
// item list related
|
||||
private float _listContainerMinPosition;
|
||||
|
||||
private float _listContainerMaxPosition;
|
||||
|
||||
private float _listContainerSize;
|
||||
|
||||
private RectTransform _listContainerRectTransform;
|
||||
|
||||
private Vector2 _listContainerCachedSize;
|
||||
|
||||
private float _itemSize;
|
||||
|
||||
private int _itemsCount = 0;
|
||||
|
||||
// drag related
|
||||
private bool _startDrag = true;
|
||||
|
||||
private Vector3 _positionOnDragStart = new Vector3();
|
||||
|
||||
private int _pageOnDragStart;
|
||||
|
||||
private bool _fastSwipeTimer = false;
|
||||
|
||||
private int _fastSwipeCounter = 0;
|
||||
|
||||
private int _fastSwipeTarget = 10;
|
||||
|
||||
[Tooltip("Button to go to the next page. (optional)")]
|
||||
public Button NextButton;
|
||||
|
||||
[Tooltip("Button to go to the previous page. (optional)")]
|
||||
public Button PrevButton;
|
||||
|
||||
[Tooltip("Number of items visible in one page of scroll frame.")]
|
||||
[RangeAttribute(1, 100)]
|
||||
public int ItemsVisibleAtOnce = 1;
|
||||
|
||||
[Tooltip("Sets minimum width of list items to 1/itemsVisibleAtOnce.")]
|
||||
public bool AutoLayoutItems = true;
|
||||
|
||||
[Tooltip("If you wish to update scrollbar numberOfSteps to number of active children on list.")]
|
||||
public bool LinkScrolbarSteps = false;
|
||||
|
||||
[Tooltip("If you wish to update scrollrect sensitivity to size of list element.")]
|
||||
public bool LinkScrolrectScrollSensitivity = false;
|
||||
|
||||
public Boolean UseFastSwipe = true;
|
||||
|
||||
public int FastSwipeThreshold = 100;
|
||||
|
||||
public delegate void PageSnapChange(int page);
|
||||
|
||||
public event PageSnapChange onPageChange;
|
||||
|
||||
public ScrollDirection direction = ScrollDirection.Horizontal;
|
||||
|
||||
// Use this for initialization
|
||||
void Start()
|
||||
{
|
||||
_lerp = false;
|
||||
|
||||
_scroll_rect = gameObject.GetComponent<ScrollRect>();
|
||||
_scrollRectTransform = gameObject.GetComponent<RectTransform>();
|
||||
_listContainerTransform = _scroll_rect.content;
|
||||
_listContainerRectTransform = _listContainerTransform.GetComponent<RectTransform>();
|
||||
|
||||
//_rectTransform = _listContainerTransform.gameObject.GetComponent<RectTransform>();
|
||||
UpdateListItemsSize();
|
||||
UpdateListItemPositions();
|
||||
|
||||
PageChanged(CurrentPage());
|
||||
|
||||
if (NextButton)
|
||||
{
|
||||
NextButton.GetComponent<Button>().onClick.AddListener(() =>
|
||||
{
|
||||
NextScreen();
|
||||
});
|
||||
}
|
||||
|
||||
if (PrevButton)
|
||||
{
|
||||
PrevButton.GetComponent<Button>().onClick.AddListener(() =>
|
||||
{
|
||||
PreviousScreen();
|
||||
});
|
||||
}
|
||||
if (_scroll_rect.horizontalScrollbar != null && _scroll_rect.horizontal)
|
||||
{
|
||||
|
||||
var hscroll = _scroll_rect.horizontalScrollbar.gameObject.GetOrAddComponent<ScrollSnapScrollbarHelper>();
|
||||
hscroll.ss = this;
|
||||
}
|
||||
if (_scroll_rect.verticalScrollbar != null && _scroll_rect.vertical)
|
||||
{
|
||||
var vscroll = _scroll_rect.verticalScrollbar.gameObject.GetOrAddComponent<ScrollSnapScrollbarHelper>();
|
||||
vscroll.ss = this;
|
||||
}
|
||||
}
|
||||
|
||||
public void UpdateListItemsSize()
|
||||
{
|
||||
float size = 0;
|
||||
float currentSize = 0;
|
||||
if (direction == ScrollSnap.ScrollDirection.Horizontal)
|
||||
{
|
||||
size = _scrollRectTransform.rect.width / ItemsVisibleAtOnce;
|
||||
currentSize = _listContainerRectTransform.rect.width / _itemsCount;
|
||||
}
|
||||
else
|
||||
{
|
||||
size = _scrollRectTransform.rect.height / ItemsVisibleAtOnce;
|
||||
currentSize = _listContainerRectTransform.rect.height / _itemsCount;
|
||||
}
|
||||
|
||||
_itemSize = size;
|
||||
|
||||
if (LinkScrolrectScrollSensitivity)
|
||||
{
|
||||
_scroll_rect.scrollSensitivity = _itemSize;
|
||||
}
|
||||
|
||||
if (AutoLayoutItems && currentSize != size && _itemsCount > 0)
|
||||
{
|
||||
if (direction == ScrollSnap.ScrollDirection.Horizontal)
|
||||
{
|
||||
foreach (var tr in _listContainerTransform)
|
||||
{
|
||||
GameObject child = ((Transform)tr).gameObject;
|
||||
if (child.activeInHierarchy)
|
||||
{
|
||||
var childLayout = child.GetComponent<LayoutElement>();
|
||||
|
||||
if (childLayout == null)
|
||||
{
|
||||
childLayout = child.AddComponent<LayoutElement>();
|
||||
}
|
||||
|
||||
childLayout.minWidth = _itemSize;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (var tr in _listContainerTransform)
|
||||
{
|
||||
GameObject child = ((Transform)tr).gameObject;
|
||||
if (child.activeInHierarchy)
|
||||
{
|
||||
var childLayout = child.GetComponent<LayoutElement>();
|
||||
|
||||
if (childLayout == null)
|
||||
{
|
||||
childLayout = child.AddComponent<LayoutElement>();
|
||||
}
|
||||
|
||||
childLayout.minHeight = _itemSize;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void UpdateListItemPositions()
|
||||
{
|
||||
if (!_listContainerRectTransform.rect.size.Equals(_listContainerCachedSize))
|
||||
{
|
||||
// checking how many children of list are active
|
||||
int activeCount = 0;
|
||||
|
||||
foreach (var tr in _listContainerTransform)
|
||||
{
|
||||
if (((Transform)tr).gameObject.activeInHierarchy)
|
||||
{
|
||||
activeCount++;
|
||||
}
|
||||
}
|
||||
|
||||
// if anything changed since last check reinitialize anchors list
|
||||
_itemsCount = 0;
|
||||
Array.Resize(ref _pageAnchorPositions, activeCount);
|
||||
|
||||
if (activeCount > 0)
|
||||
{
|
||||
_pages = Mathf.Max(activeCount - ItemsVisibleAtOnce + 1, 1);
|
||||
|
||||
if (direction == ScrollDirection.Horizontal)
|
||||
{
|
||||
// looking for list spanning range min/max
|
||||
_scroll_rect.horizontalNormalizedPosition = 0;
|
||||
_listContainerMaxPosition = _listContainerTransform.localPosition.x;
|
||||
_scroll_rect.horizontalNormalizedPosition = 1;
|
||||
_listContainerMinPosition = _listContainerTransform.localPosition.x;
|
||||
|
||||
_listContainerSize = _listContainerMaxPosition - _listContainerMinPosition;
|
||||
|
||||
for (var i = 0; i < _pages; i++)
|
||||
{
|
||||
_pageAnchorPositions[i] = new Vector3(
|
||||
_listContainerMaxPosition - _itemSize * i,
|
||||
_listContainerTransform.localPosition.y,
|
||||
_listContainerTransform.localPosition.z
|
||||
);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
//Debug.Log ("-------------looking for list spanning range----------------");
|
||||
// looking for list spanning range
|
||||
_scroll_rect.verticalNormalizedPosition = 1;
|
||||
_listContainerMinPosition = _listContainerTransform.localPosition.y;
|
||||
_scroll_rect.verticalNormalizedPosition = 0;
|
||||
_listContainerMaxPosition = _listContainerTransform.localPosition.y;
|
||||
|
||||
_listContainerSize = _listContainerMaxPosition - _listContainerMinPosition;
|
||||
|
||||
for (var i = 0; i < _pages; i++)
|
||||
{
|
||||
_pageAnchorPositions[i] = new Vector3(
|
||||
_listContainerTransform.localPosition.x,
|
||||
_listContainerMinPosition + _itemSize * i,
|
||||
_listContainerTransform.localPosition.z
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
UpdateScrollbar(LinkScrolbarSteps);
|
||||
_startingPage = Mathf.Min(_startingPage, _pages);
|
||||
ResetPage();
|
||||
}
|
||||
|
||||
if (_itemsCount != activeCount)
|
||||
{
|
||||
PageChanged(CurrentPage());
|
||||
}
|
||||
|
||||
_itemsCount = activeCount;
|
||||
_listContainerCachedSize.Set(_listContainerRectTransform.rect.size.x, _listContainerRectTransform.rect.size.y);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public void ResetPage()
|
||||
{
|
||||
if (direction == ScrollDirection.Horizontal)
|
||||
{
|
||||
_scroll_rect.horizontalNormalizedPosition = _pages > 1 ? (float)_startingPage / (float)(_pages - 1) : 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
_scroll_rect.verticalNormalizedPosition = _pages > 1 ? (float)(_pages - _startingPage - 1) / (float)(_pages - 1) : 0;
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateScrollbar(bool linkSteps)
|
||||
{
|
||||
if (linkSteps)
|
||||
{
|
||||
if (direction == ScrollDirection.Horizontal)
|
||||
{
|
||||
if (_scroll_rect.horizontalScrollbar != null)
|
||||
{
|
||||
_scroll_rect.horizontalScrollbar.numberOfSteps = _pages;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (_scroll_rect.verticalScrollbar != null)
|
||||
{
|
||||
_scroll_rect.verticalScrollbar.numberOfSteps = _pages;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (direction == ScrollDirection.Horizontal)
|
||||
{
|
||||
if (_scroll_rect.horizontalScrollbar != null)
|
||||
{
|
||||
_scroll_rect.horizontalScrollbar.numberOfSteps = 0;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (_scroll_rect.verticalScrollbar != null)
|
||||
{
|
||||
_scroll_rect.verticalScrollbar.numberOfSteps = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void LateUpdate()
|
||||
{
|
||||
UpdateListItemsSize();
|
||||
UpdateListItemPositions();
|
||||
|
||||
if (_lerp)
|
||||
{
|
||||
UpdateScrollbar(false);
|
||||
|
||||
_listContainerTransform.localPosition = Vector3.Lerp(_listContainerTransform.localPosition, _lerpTarget, 7.5f * Time.deltaTime);
|
||||
|
||||
if (Vector3.Distance(_listContainerTransform.localPosition, _lerpTarget) < 0.001f)
|
||||
{
|
||||
_listContainerTransform.localPosition = _lerpTarget;
|
||||
_lerp = false;
|
||||
|
||||
UpdateScrollbar(LinkScrolbarSteps);
|
||||
}
|
||||
|
||||
//change the info bullets at the bottom of the screen. Just for visual effect
|
||||
if (Vector3.Distance(_listContainerTransform.localPosition, _lerpTarget) < 10f)
|
||||
{
|
||||
PageChanged(CurrentPage());
|
||||
}
|
||||
}
|
||||
|
||||
if (_fastSwipeTimer)
|
||||
{
|
||||
_fastSwipeCounter++;
|
||||
}
|
||||
}
|
||||
|
||||
private bool fastSwipe = false; //to determine if a fast swipe was performed
|
||||
|
||||
|
||||
//Function for switching screens with buttons
|
||||
public void NextScreen()
|
||||
{
|
||||
UpdateListItemPositions();
|
||||
|
||||
if (CurrentPage() < _pages - 1)
|
||||
{
|
||||
_lerp = true;
|
||||
_lerpTarget = _pageAnchorPositions[CurrentPage() + 1];
|
||||
|
||||
PageChanged(CurrentPage() + 1);
|
||||
}
|
||||
}
|
||||
|
||||
//Function for switching screens with buttons
|
||||
public void PreviousScreen()
|
||||
{
|
||||
UpdateListItemPositions();
|
||||
|
||||
if (CurrentPage() > 0)
|
||||
{
|
||||
_lerp = true;
|
||||
_lerpTarget = _pageAnchorPositions[CurrentPage() - 1];
|
||||
|
||||
PageChanged(CurrentPage() - 1);
|
||||
}
|
||||
}
|
||||
|
||||
//Because the CurrentScreen function is not so reliable, these are the functions used for swipes
|
||||
private void NextScreenCommand()
|
||||
{
|
||||
if (_pageOnDragStart < _pages - 1)
|
||||
{
|
||||
int targetPage = Mathf.Min(_pages - 1, _pageOnDragStart + ItemsVisibleAtOnce);
|
||||
_lerp = true;
|
||||
|
||||
_lerpTarget = _pageAnchorPositions[targetPage];
|
||||
|
||||
PageChanged(targetPage);
|
||||
}
|
||||
}
|
||||
|
||||
//Because the CurrentScreen function is not so reliable, these are the functions used for swipes
|
||||
private void PrevScreenCommand()
|
||||
{
|
||||
if (_pageOnDragStart > 0)
|
||||
{
|
||||
int targetPage = Mathf.Max(0, _pageOnDragStart - ItemsVisibleAtOnce);
|
||||
_lerp = true;
|
||||
|
||||
_lerpTarget = _pageAnchorPositions[targetPage];
|
||||
|
||||
PageChanged(targetPage);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//returns the current screen that the is seeing
|
||||
public int CurrentPage()
|
||||
{
|
||||
float pos;
|
||||
|
||||
if (direction == ScrollDirection.Horizontal)
|
||||
{
|
||||
pos = _listContainerMaxPosition - _listContainerTransform.localPosition.x;
|
||||
pos = Mathf.Clamp(pos, 0, _listContainerSize);
|
||||
}
|
||||
else
|
||||
{
|
||||
pos = _listContainerTransform.localPosition.y - _listContainerMinPosition;
|
||||
pos = Mathf.Clamp(pos, 0, _listContainerSize);
|
||||
}
|
||||
|
||||
float page = pos / _itemSize;
|
||||
|
||||
return Mathf.Clamp(Mathf.RoundToInt(page), 0, _pages);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Added to provide a uniform interface for the ScrollBarHelper
|
||||
/// </summary>
|
||||
public void SetLerp(bool value)
|
||||
{
|
||||
_lerp = value;
|
||||
}
|
||||
|
||||
public void ChangePage(int page)
|
||||
{
|
||||
if (0 <= page && page < _pages)
|
||||
{
|
||||
_lerp = true;
|
||||
|
||||
_lerpTarget = _pageAnchorPositions[page];
|
||||
|
||||
PageChanged(page);
|
||||
}
|
||||
}
|
||||
|
||||
//changes the bullets on the bottom of the page - pagination
|
||||
private void PageChanged(int currentPage)
|
||||
{
|
||||
_startingPage = currentPage;
|
||||
|
||||
if (NextButton)
|
||||
{
|
||||
NextButton.interactable = currentPage < _pages - 1;
|
||||
}
|
||||
|
||||
if (PrevButton)
|
||||
{
|
||||
PrevButton.interactable = currentPage > 0;
|
||||
}
|
||||
|
||||
if (onPageChange != null)
|
||||
{
|
||||
onPageChange(currentPage);
|
||||
}
|
||||
}
|
||||
|
||||
#region Interfaces
|
||||
public void OnBeginDrag(PointerEventData eventData)
|
||||
{
|
||||
UpdateScrollbar(false);
|
||||
|
||||
_fastSwipeCounter = 0;
|
||||
_fastSwipeTimer = true;
|
||||
|
||||
_positionOnDragStart = eventData.position;
|
||||
_pageOnDragStart = CurrentPage();
|
||||
}
|
||||
|
||||
public void OnEndDrag(PointerEventData eventData)
|
||||
{
|
||||
_startDrag = true;
|
||||
float change = 0;
|
||||
|
||||
if (direction == ScrollDirection.Horizontal)
|
||||
{
|
||||
change = _positionOnDragStart.x - eventData.position.x;
|
||||
}
|
||||
else
|
||||
{
|
||||
change = -_positionOnDragStart.y + eventData.position.y;
|
||||
}
|
||||
|
||||
if (UseFastSwipe)
|
||||
{
|
||||
fastSwipe = false;
|
||||
_fastSwipeTimer = false;
|
||||
|
||||
if (_fastSwipeCounter <= _fastSwipeTarget)
|
||||
{
|
||||
if (Math.Abs(change) > FastSwipeThreshold)
|
||||
{
|
||||
fastSwipe = true;
|
||||
}
|
||||
}
|
||||
if (fastSwipe)
|
||||
{
|
||||
if (change > 0)
|
||||
{
|
||||
NextScreenCommand();
|
||||
}
|
||||
else
|
||||
{
|
||||
PrevScreenCommand();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_lerp = true;
|
||||
_lerpTarget = _pageAnchorPositions[CurrentPage()];
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_lerp = true;
|
||||
_lerpTarget = _pageAnchorPositions[CurrentPage()];
|
||||
}
|
||||
}
|
||||
|
||||
public void OnDrag(PointerEventData eventData)
|
||||
{
|
||||
_lerp = false;
|
||||
|
||||
if (_startDrag)
|
||||
{
|
||||
OnBeginDrag(eventData);
|
||||
_startDrag = false;
|
||||
}
|
||||
}
|
||||
|
||||
public void StartScreenChange() { }
|
||||
#endregion
|
||||
}
|
||||
}
|
@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c598b387777d96643991be3f0b6c98c2
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
@ -0,0 +1,650 @@
|
||||
/// Credit BinaryX
|
||||
/// Sourced from - http://forum.unity3d.com/threads/scripts-useful-4-6-scripts-collection.264161/page-2#post-1945602
|
||||
/// Updated by simonDarksideJ - removed dependency on a custom ScrollRect script. Now implements drag interfaces and standard Scroll Rect.
|
||||
|
||||
using System;
|
||||
using UnityEngine.Events;
|
||||
using UnityEngine.EventSystems;
|
||||
|
||||
namespace UnityEngine.UI.Extensions
|
||||
{
|
||||
public class ScrollSnapBase : MonoBehaviour, IBeginDragHandler, IDragHandler, IEndDragHandler, IScrollSnap, IPointerClickHandler
|
||||
{
|
||||
internal Rect panelDimensions;
|
||||
internal RectTransform _screensContainer;
|
||||
internal bool _isVertical;
|
||||
|
||||
internal int _screens = 1;
|
||||
|
||||
internal float _scrollStartPosition;
|
||||
internal float _childSize;
|
||||
private float _childPos, _maskSize;
|
||||
internal Vector2 _childAnchorPoint;
|
||||
internal ScrollRect _scroll_rect;
|
||||
internal Vector3 _lerp_target;
|
||||
internal bool _lerp;
|
||||
internal bool _pointerDown = false;
|
||||
internal bool _settled = true;
|
||||
internal Vector3 _startPosition = new Vector3();
|
||||
[Tooltip("The currently active page")]
|
||||
internal int _currentPage;
|
||||
internal int _previousPage;
|
||||
internal int _halfNoVisibleItems;
|
||||
internal bool _isInfinite; // Is a UI Infinite scroller attached to the control
|
||||
internal int _infiniteWindow; // The infinite window the control is in
|
||||
internal float _infiniteOffset; // How much to offset a repositioning
|
||||
private int _bottomItem, _topItem;
|
||||
internal bool _startEventCalled = false;
|
||||
internal bool _endEventCalled = false;
|
||||
internal bool _suspendEvents = false;
|
||||
|
||||
[Serializable]
|
||||
public class SelectionChangeStartEvent : UnityEvent { }
|
||||
[Serializable]
|
||||
public class SelectionPageChangedEvent : UnityEvent<int> { }
|
||||
[Serializable]
|
||||
public class SelectionChangeEndEvent : UnityEvent<int> { }
|
||||
|
||||
[Tooltip("The screen / page to start the control on\n*Note, this is a 0 indexed array")]
|
||||
[SerializeField]
|
||||
public int StartingScreen = 0;
|
||||
|
||||
[Tooltip("The distance between two pages based on page height, by default pages are next to each other")]
|
||||
[SerializeField]
|
||||
[Range(0, 8)]
|
||||
public float PageStep = 1;
|
||||
|
||||
[Tooltip("The gameobject that contains toggles which suggest pagination. (optional)")]
|
||||
public GameObject Pagination;
|
||||
|
||||
[Tooltip("Button to go to the previous page. (optional)")]
|
||||
public GameObject PrevButton;
|
||||
|
||||
[Tooltip("Button to go to the next page. (optional)")]
|
||||
public GameObject NextButton;
|
||||
|
||||
[Tooltip("Transition speed between pages. (optional)")]
|
||||
public float transitionSpeed = 7.5f;
|
||||
|
||||
[Tooltip("Hard Swipe forces to swiping to the next / previous page (optional)")]
|
||||
public Boolean UseHardSwipe = false;
|
||||
|
||||
[Tooltip("Fast Swipe makes swiping page next / previous (optional)")]
|
||||
public Boolean UseFastSwipe = false;
|
||||
|
||||
[Tooltip("Swipe Delta Threshold looks at the speed of input to decide if a swipe will be initiated (optional)")]
|
||||
public Boolean UseSwipeDeltaThreshold = false;
|
||||
|
||||
[Tooltip("Offset for how far a swipe has to travel to initiate a page change (optional)")]
|
||||
public int FastSwipeThreshold = 100;
|
||||
|
||||
[Tooltip("Speed at which the ScrollRect will keep scrolling before slowing down and stopping (optional)")]
|
||||
public int SwipeVelocityThreshold = 100;
|
||||
|
||||
[Tooltip("Threshold for swipe speed to initiate a swipe, below threshold will return to closest page (optional)")]
|
||||
public float SwipeDeltaThreshold = 5.0f;
|
||||
|
||||
[Tooltip("Use time scale instead of unscaled time (optional)")]
|
||||
public Boolean UseTimeScale = true;
|
||||
|
||||
[Tooltip("The visible bounds area, controls which items are visible/enabled. *Note Should use a RectMask. (optional)")]
|
||||
public RectTransform MaskArea;
|
||||
|
||||
[Tooltip("Pixel size to buffer around Mask Area. (optional)")]
|
||||
public float MaskBuffer = 1;
|
||||
|
||||
public int CurrentPage
|
||||
{
|
||||
get
|
||||
{
|
||||
return _currentPage;
|
||||
}
|
||||
|
||||
internal set
|
||||
{
|
||||
if (_isInfinite)
|
||||
{
|
||||
//Work out which infinite window we are in
|
||||
float infWindow = (float)value / (float)_screensContainer.childCount;
|
||||
|
||||
if (infWindow < 0)
|
||||
{
|
||||
_infiniteWindow = (int)(Math.Floor(infWindow));
|
||||
}
|
||||
else
|
||||
{
|
||||
_infiniteWindow = value / _screensContainer.childCount;
|
||||
}
|
||||
//Invert the value if negative and differentiate from Window 0
|
||||
_infiniteWindow = value < 0 ? (-_infiniteWindow) : _infiniteWindow;
|
||||
|
||||
//Calculate the page within the child count range
|
||||
value = value % _screensContainer.childCount;
|
||||
if (value < 0)
|
||||
{
|
||||
value = _screensContainer.childCount + value;
|
||||
}
|
||||
else if (value > _screensContainer.childCount - 1)
|
||||
{
|
||||
value = value - _screensContainer.childCount;
|
||||
}
|
||||
}
|
||||
if ((value != _currentPage && value >= 0 && value < _screensContainer.childCount) || (value == 0 && _screensContainer.childCount == 0))
|
||||
{
|
||||
_previousPage = _currentPage;
|
||||
_currentPage = value;
|
||||
if (MaskArea) UpdateVisible();
|
||||
if (!_lerp) ScreenChange();
|
||||
OnCurrentScreenChange(_currentPage);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[Tooltip("By default the container will lerp to the start when enabled in the scene, this option overrides this and forces it to simply jump without lerping")]
|
||||
public bool JumpOnEnable = false;
|
||||
|
||||
[Tooltip("By default the container will return to the original starting page when enabled, this option overrides this behaviour and stays on the current selection")]
|
||||
public bool RestartOnEnable = false;
|
||||
|
||||
[Tooltip("(Experimental)\nBy default, child array objects will use the parent transform\nHowever you can disable this for some interesting effects")]
|
||||
public bool UseParentTransform = true;
|
||||
|
||||
[Tooltip("Scroll Snap children. (optional)\nEither place objects in the scene as children OR\nPrefabs in this array, NOT BOTH")]
|
||||
public GameObject[] ChildObjects;
|
||||
|
||||
[SerializeField]
|
||||
[Tooltip("Event fires when a user starts to change the selection")]
|
||||
private SelectionChangeStartEvent m_OnSelectionChangeStartEvent = new SelectionChangeStartEvent();
|
||||
public SelectionChangeStartEvent OnSelectionChangeStartEvent { get { return m_OnSelectionChangeStartEvent; } set { m_OnSelectionChangeStartEvent = value; } }
|
||||
|
||||
[SerializeField]
|
||||
[Tooltip("Event fires as the page changes, while dragging or jumping")]
|
||||
private SelectionPageChangedEvent m_OnSelectionPageChangedEvent = new SelectionPageChangedEvent();
|
||||
public SelectionPageChangedEvent OnSelectionPageChangedEvent { get { return m_OnSelectionPageChangedEvent; } set { m_OnSelectionPageChangedEvent = value; } }
|
||||
|
||||
[SerializeField]
|
||||
[Tooltip("Event fires when the page settles after a user has dragged")]
|
||||
private SelectionChangeEndEvent m_OnSelectionChangeEndEvent = new SelectionChangeEndEvent();
|
||||
public SelectionChangeEndEvent OnSelectionChangeEndEvent { get { return m_OnSelectionChangeEndEvent; } set { m_OnSelectionChangeEndEvent = value; } }
|
||||
|
||||
// Use this for initialization
|
||||
void Awake()
|
||||
{
|
||||
if (_scroll_rect == null)
|
||||
{
|
||||
_scroll_rect = gameObject.GetComponent<ScrollRect>();
|
||||
}
|
||||
if (_scroll_rect.horizontalScrollbar && _scroll_rect.horizontal)
|
||||
{
|
||||
var hscroll = _scroll_rect.horizontalScrollbar.gameObject.AddComponent<ScrollSnapScrollbarHelper>();
|
||||
hscroll.ss = this;
|
||||
}
|
||||
if (_scroll_rect.verticalScrollbar && _scroll_rect.vertical)
|
||||
{
|
||||
var vscroll = _scroll_rect.verticalScrollbar.gameObject.AddComponent<ScrollSnapScrollbarHelper>();
|
||||
vscroll.ss = this;
|
||||
}
|
||||
panelDimensions = gameObject.GetComponent<RectTransform>().rect;
|
||||
|
||||
if (StartingScreen < 0)
|
||||
{
|
||||
StartingScreen = 0;
|
||||
}
|
||||
|
||||
_screensContainer = _scroll_rect.content;
|
||||
|
||||
InitialiseChildObjects();
|
||||
|
||||
if (NextButton)
|
||||
NextButton.GetComponent<Button>().onClick.AddListener(() => { NextScreen(); });
|
||||
|
||||
if (PrevButton)
|
||||
PrevButton.GetComponent<Button>().onClick.AddListener(() => { PreviousScreen(); });
|
||||
|
||||
_isInfinite = GetComponent<UI_InfiniteScroll>() != null;
|
||||
}
|
||||
|
||||
internal void InitialiseChildObjects()
|
||||
{
|
||||
if (ChildObjects != null && ChildObjects.Length > 0)
|
||||
{
|
||||
if (_screensContainer.transform.childCount > 0)
|
||||
{
|
||||
Debug.LogError("ScrollRect Content has children, this is not supported when using managed Child Objects\n Either remove the ScrollRect Content children or clear the ChildObjects array");
|
||||
return;
|
||||
}
|
||||
|
||||
InitialiseChildObjectsFromArray();
|
||||
|
||||
if (GetComponent<UI_InfiniteScroll>() != null)
|
||||
{
|
||||
GetComponent<UI_InfiniteScroll>().Init();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
InitialiseChildObjectsFromScene();
|
||||
}
|
||||
}
|
||||
|
||||
internal void InitialiseChildObjectsFromScene()
|
||||
{
|
||||
int childCount = _screensContainer.childCount;
|
||||
ChildObjects = new GameObject[childCount];
|
||||
for (int i = 0; i < childCount; i++)
|
||||
{
|
||||
ChildObjects[i] = _screensContainer.transform.GetChild(i).gameObject;
|
||||
if (MaskArea && ChildObjects[i].activeSelf)
|
||||
{
|
||||
ChildObjects[i].SetActive(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal void InitialiseChildObjectsFromArray()
|
||||
{
|
||||
int childCount = ChildObjects.Length;
|
||||
RectTransform childRect;
|
||||
GameObject child;
|
||||
for (int i = 0; i < childCount; i++)
|
||||
{
|
||||
child = GameObject.Instantiate(ChildObjects[i]);
|
||||
//Optionally, use original GO transform when initialising, by default will use parent RectTransform position/rotation
|
||||
if (UseParentTransform)
|
||||
{
|
||||
childRect = child.GetComponent<RectTransform>();
|
||||
childRect.rotation = _screensContainer.rotation;
|
||||
childRect.localScale = _screensContainer.localScale;
|
||||
childRect.position = _screensContainer.position;
|
||||
}
|
||||
|
||||
child.transform.SetParent(_screensContainer.transform);
|
||||
ChildObjects[i] = child;
|
||||
if (MaskArea && ChildObjects[i].activeSelf)
|
||||
{
|
||||
ChildObjects[i].SetActive(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal void UpdateVisible()
|
||||
{
|
||||
//If there are no objects in the scene or a mask, exit
|
||||
if (!MaskArea || ChildObjects == null || ChildObjects.Length < 1 || _screensContainer.childCount < 1)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_maskSize = _isVertical ? MaskArea.rect.height : MaskArea.rect.width;
|
||||
_halfNoVisibleItems = (int)Math.Round(_maskSize / (_childSize * MaskBuffer), MidpointRounding.AwayFromZero) / 2;
|
||||
_bottomItem = _topItem = 0;
|
||||
//work out how many items below the current page can be visible
|
||||
for (int i = _halfNoVisibleItems + 1; i > 0; i--)
|
||||
{
|
||||
_bottomItem = _currentPage - i < 0 ? 0 : i;
|
||||
if (_bottomItem > 0) break;
|
||||
}
|
||||
|
||||
//work out how many items above the current page can be visible
|
||||
for (int i = _halfNoVisibleItems + 1; i > 0; i--)
|
||||
{
|
||||
_topItem = _screensContainer.childCount - _currentPage - i < 0 ? 0 : i;
|
||||
if (_topItem > 0) break;
|
||||
}
|
||||
|
||||
//Set the active items active
|
||||
for (int i = CurrentPage - _bottomItem; i < CurrentPage + _topItem; i++)
|
||||
{
|
||||
try
|
||||
{
|
||||
ChildObjects[i].SetActive(true);
|
||||
}
|
||||
catch
|
||||
{
|
||||
Debug.Log("Failed to setactive child [" + i + "]");
|
||||
}
|
||||
}
|
||||
|
||||
//Deactivate items out of visibility at the bottom of the ScrollRect Mask (only on scroll)
|
||||
if (_currentPage > _halfNoVisibleItems) ChildObjects[CurrentPage - _bottomItem].SetActive(false);
|
||||
//Deactivate items out of visibility at the top of the ScrollRect Mask (only on scroll)
|
||||
if (_screensContainer.childCount - _currentPage > _topItem) ChildObjects[CurrentPage + _topItem].SetActive(false);
|
||||
}
|
||||
|
||||
//Function for switching screens with buttons
|
||||
public void NextScreen()
|
||||
{
|
||||
if (_currentPage < _screens - 1 || _isInfinite)
|
||||
{
|
||||
if (!_lerp) StartScreenChange();
|
||||
|
||||
_lerp = true;
|
||||
if (_isInfinite)
|
||||
{
|
||||
CurrentPage = GetPageforPosition(_screensContainer.anchoredPosition) + 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
CurrentPage = _currentPage + 1;
|
||||
}
|
||||
GetPositionforPage(_currentPage, ref _lerp_target);
|
||||
ScreenChange();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
//Function for switching screens with buttons
|
||||
public void PreviousScreen()
|
||||
{
|
||||
if (_currentPage > 0 || _isInfinite)
|
||||
{
|
||||
if (!_lerp) StartScreenChange();
|
||||
|
||||
_lerp = true;
|
||||
if (_isInfinite)
|
||||
{
|
||||
CurrentPage = GetPageforPosition(_screensContainer.anchoredPosition) - 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
CurrentPage = _currentPage - 1;
|
||||
}
|
||||
GetPositionforPage(_currentPage, ref _lerp_target);
|
||||
ScreenChange();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Function for switching to a specific screen
|
||||
/// *Note, this is based on a 0 starting index - 0 to x
|
||||
/// </summary>
|
||||
/// <param name="screenIndex">0 starting index of page to jump to</param>
|
||||
/// <param name="pagination">Override the screen movement if driven from a pagination control</param>
|
||||
public void GoToScreen(int screenIndex, bool pagination = false)
|
||||
{
|
||||
if (screenIndex <= _screens - 1 && screenIndex >= 0)
|
||||
{
|
||||
if (!_lerp || pagination) StartScreenChange();
|
||||
|
||||
_lerp = true;
|
||||
CurrentPage = screenIndex;
|
||||
GetPositionforPage(_currentPage, ref _lerp_target);
|
||||
ScreenChange();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the closest page for the current Scroll Rect container position
|
||||
/// </summary>
|
||||
/// <param name="pos">Position to test, normally the Scroll Rect container Local position</param>
|
||||
/// <returns>Closest Page number (zero indexed array value)</returns>
|
||||
internal int GetPageforPosition(Vector3 pos)
|
||||
{
|
||||
return _isVertical ?
|
||||
(int)Math.Round((_scrollStartPosition - pos.y) / _childSize) :
|
||||
(int)Math.Round((_scrollStartPosition - pos.x) / _childSize);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Validates if the current Scroll Rect container position is within the bounds for a page
|
||||
/// </summary>
|
||||
/// <param name="pos">Position to test, normally the Scroll Rect container Local position</param>
|
||||
/// <returns>True / False, is the position in the bounds of a page</returns>
|
||||
internal bool IsRectSettledOnaPage(Vector3 pos)
|
||||
{
|
||||
return _isVertical ?
|
||||
-((pos.y - _scrollStartPosition) / _childSize) == -(int)Math.Round((pos.y - _scrollStartPosition) / _childSize) :
|
||||
-((pos.x - _scrollStartPosition) / _childSize) == -(int)Math.Round((pos.x - _scrollStartPosition) / _childSize);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the local position for a child page based on the required page number
|
||||
/// </summary>
|
||||
/// <param name="page">Page that the position is required for (Zero indexed array value)</param>
|
||||
/// <param name="target">Outputs the local position for the selected page</param>
|
||||
internal void GetPositionforPage(int page, ref Vector3 target)
|
||||
{
|
||||
_childPos = -_childSize * page;
|
||||
if (_isVertical)
|
||||
{
|
||||
_infiniteOffset = _screensContainer.anchoredPosition.y < 0 ? -_screensContainer.sizeDelta.y * _infiniteWindow : _screensContainer.sizeDelta.y * _infiniteWindow;
|
||||
_infiniteOffset = _infiniteOffset == 0 ? 0 : _infiniteOffset < 0 ? _infiniteOffset - _childSize * _infiniteWindow : _infiniteOffset + _childSize * _infiniteWindow;
|
||||
target.y = _childPos + _scrollStartPosition + _infiniteOffset;
|
||||
}
|
||||
else
|
||||
{
|
||||
_infiniteOffset = _screensContainer.anchoredPosition.x < 0 ? -_screensContainer.sizeDelta.x * _infiniteWindow : _screensContainer.sizeDelta.x * _infiniteWindow;
|
||||
_infiniteOffset = _infiniteOffset == 0 ? 0 : _infiniteOffset < 0 ? _infiniteOffset - _childSize * _infiniteWindow : _infiniteOffset + _childSize * _infiniteWindow;
|
||||
target.x = _childPos + _scrollStartPosition + _infiniteOffset;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates the _Lerp target to the closest page and updates the pagination bullets. Each control's update loop will then handle the move.
|
||||
/// </summary>
|
||||
internal void ScrollToClosestElement()
|
||||
{
|
||||
_lerp = true;
|
||||
CurrentPage = GetPageforPosition(_screensContainer.anchoredPosition);
|
||||
GetPositionforPage(_currentPage, ref _lerp_target);
|
||||
OnCurrentScreenChange(_currentPage);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// notifies pagination indicator and navigation buttons of a screen change
|
||||
/// </summary>
|
||||
internal void OnCurrentScreenChange(int currentScreen)
|
||||
{
|
||||
ChangeBulletsInfo(currentScreen);
|
||||
ToggleNavigationButtons(currentScreen);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// changes the bullets on the bottom of the page - pagination
|
||||
/// </summary>
|
||||
/// <param name="targetScreen"></param>
|
||||
private void ChangeBulletsInfo(int targetScreen)
|
||||
{
|
||||
if (Pagination)
|
||||
{
|
||||
for (int i = 0; i < Pagination.transform.childCount; i++)
|
||||
{
|
||||
Pagination.transform.GetChild(i).GetComponent<Toggle>().isOn = (targetScreen == i) ? true : false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Make a lock function for pagination, to prevent event leaking
|
||||
|
||||
/// <summary>
|
||||
/// disables the page navigation buttons when at the first or last screen
|
||||
/// </summary>
|
||||
/// <param name="targetScreen"></param>
|
||||
private void ToggleNavigationButtons(int targetScreen)
|
||||
{
|
||||
//If this is using an Infinite Scroll, then don't disable
|
||||
if (!_isInfinite)
|
||||
{
|
||||
if (PrevButton)
|
||||
{
|
||||
PrevButton.GetComponent<Button>().interactable = targetScreen > 0;
|
||||
}
|
||||
|
||||
if (NextButton)
|
||||
{
|
||||
NextButton.GetComponent<Button>().interactable = targetScreen < _screensContainer.transform.childCount - 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void OnValidate()
|
||||
{
|
||||
if (_scroll_rect == null)
|
||||
{
|
||||
_scroll_rect = GetComponent<ScrollRect>();
|
||||
}
|
||||
if (!_scroll_rect.horizontal && !_scroll_rect.vertical)
|
||||
{
|
||||
Debug.LogError("ScrollRect has to have a direction, please select either Horizontal OR Vertical with the appropriate control.");
|
||||
}
|
||||
if (_scroll_rect.horizontal && _scroll_rect.vertical)
|
||||
{
|
||||
Debug.LogError("ScrollRect has to be unidirectional, only use either Horizontal or Vertical on the ScrollRect, NOT both.");
|
||||
}
|
||||
var ScrollRectContent = gameObject.GetComponent<ScrollRect>().content;
|
||||
if (ScrollRectContent != null)
|
||||
{
|
||||
var children = ScrollRectContent.childCount;
|
||||
if (children != 0 || ChildObjects != null)
|
||||
{
|
||||
var childCount = ChildObjects == null || ChildObjects.Length == 0 ? children : ChildObjects.Length;
|
||||
if (StartingScreen > childCount - 1)
|
||||
{
|
||||
StartingScreen = childCount - 1;
|
||||
}
|
||||
|
||||
if (StartingScreen < 0)
|
||||
{
|
||||
StartingScreen = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (MaskBuffer <= 0)
|
||||
{
|
||||
MaskBuffer = 1;
|
||||
}
|
||||
|
||||
if (PageStep < 0)
|
||||
{
|
||||
PageStep = 0;
|
||||
}
|
||||
|
||||
if (PageStep > 8)
|
||||
{
|
||||
PageStep = 9;
|
||||
}
|
||||
var infiniteScroll = GetComponent<UI_InfiniteScroll>();
|
||||
if (ChildObjects != null && ChildObjects.Length > 0 && infiniteScroll != null && !infiniteScroll.InitByUser)
|
||||
{
|
||||
Debug.LogError($"[{gameObject.name}]When using procedural children with a ScrollSnap (Adding Prefab ChildObjects) and the Infinite Scroll component\nYou must set the 'InitByUser' option to true, to enable late initialising");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Event fires when the user starts to change the page, either via swipe or button.
|
||||
/// </summary>
|
||||
public void StartScreenChange()
|
||||
{
|
||||
if (!_startEventCalled)
|
||||
{
|
||||
_suspendEvents = true;
|
||||
|
||||
_startEventCalled = true;
|
||||
_endEventCalled = false;
|
||||
OnSelectionChangeStartEvent.Invoke();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Event fires when the currently viewed page changes, also updates while the scroll is moving
|
||||
/// </summary>
|
||||
internal void ScreenChange()
|
||||
{
|
||||
OnSelectionPageChangedEvent.Invoke(_currentPage);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Event fires when control settles on a page, outputs the new page number
|
||||
/// </summary>
|
||||
internal void EndScreenChange()
|
||||
{
|
||||
if (!_endEventCalled)
|
||||
{
|
||||
_suspendEvents = false;
|
||||
|
||||
_endEventCalled = true;
|
||||
_startEventCalled = false;
|
||||
_settled = true;
|
||||
OnSelectionChangeEndEvent.Invoke(_currentPage);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the Transform of the Current page
|
||||
/// </summary>
|
||||
/// <returns>Currently selected Page Transform</returns>
|
||||
public Transform CurrentPageObject()
|
||||
{
|
||||
return _screensContainer.GetChild(CurrentPage);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the Transform of the Current page in an out parameter for performance
|
||||
/// </summary>
|
||||
/// <param name="returnObject">Currently selected Page Transform</param>
|
||||
public void CurrentPageObject(out Transform returnObject)
|
||||
{
|
||||
returnObject = _screensContainer.GetChild(CurrentPage);
|
||||
}
|
||||
|
||||
#region Drag Interfaces
|
||||
/// <summary>
|
||||
/// Touch screen to start swiping
|
||||
/// </summary>
|
||||
/// <param name="eventData"></param>
|
||||
public void OnBeginDrag(PointerEventData eventData)
|
||||
{
|
||||
_pointerDown = true;
|
||||
_settled = false;
|
||||
StartScreenChange();
|
||||
_startPosition = _screensContainer.anchoredPosition;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// While dragging do
|
||||
/// </summary>
|
||||
/// <param name="eventData"></param>
|
||||
public void OnDrag(PointerEventData eventData)
|
||||
{
|
||||
_lerp = false;
|
||||
}
|
||||
|
||||
public virtual void OnEndDrag(PointerEventData eventData) { }
|
||||
|
||||
#endregion
|
||||
|
||||
#region IScrollSnap Interface
|
||||
|
||||
/// <summary>
|
||||
/// Added to provide a uniform interface for the ScrollBarHelper
|
||||
/// </summary>
|
||||
int IScrollSnap.CurrentPage()
|
||||
{
|
||||
return CurrentPage = GetPageforPosition(_screensContainer.anchoredPosition);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Added to provide a uniform interface for the ScrollBarHelper
|
||||
/// </summary>
|
||||
public void SetLerp(bool value)
|
||||
{
|
||||
_lerp = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Added to provide a uniform interface for the ScrollBarHelper
|
||||
/// </summary>
|
||||
public void ChangePage(int page)
|
||||
{
|
||||
GoToScreen(page);
|
||||
}
|
||||
|
||||
public void OnPointerClick(PointerEventData eventData)
|
||||
{
|
||||
var position = _screensContainer.anchoredPosition;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c2a77c45e5354bf40bbd63bd817dee47
|
||||
timeCreated: 1480931407
|
||||
licenseType: Pro
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -0,0 +1,54 @@
|
||||
/// Credit Anonymous donation
|
||||
/// Sourced from - https://bitbucket.org/UnityUIExtensions/unity-ui-extensions/issues/120/horizontal-scroll-snap-scroll-bar-fix
|
||||
/// Updated by simonDarksideJ - Made extension support all types of scroll snap
|
||||
|
||||
using UnityEngine.EventSystems;
|
||||
|
||||
namespace UnityEngine.UI.Extensions
|
||||
{
|
||||
[DisallowMultipleComponent]
|
||||
public class ScrollSnapScrollbarHelper : MonoBehaviour, IPointerDownHandler, IPointerUpHandler, IBeginDragHandler, IEndDragHandler, IDragHandler
|
||||
{
|
||||
internal IScrollSnap ss;
|
||||
|
||||
public void OnBeginDrag(PointerEventData eventData)
|
||||
{
|
||||
OnScrollBarDown();
|
||||
}
|
||||
|
||||
public void OnDrag(PointerEventData eventData)
|
||||
{
|
||||
ss.CurrentPage();
|
||||
}
|
||||
|
||||
public void OnEndDrag(PointerEventData eventData)
|
||||
{
|
||||
OnScrollBarUp();
|
||||
}
|
||||
|
||||
public void OnPointerDown(PointerEventData eventData)
|
||||
{
|
||||
OnScrollBarDown();
|
||||
}
|
||||
|
||||
public void OnPointerUp(PointerEventData eventData)
|
||||
{
|
||||
OnScrollBarUp();
|
||||
}
|
||||
|
||||
void OnScrollBarDown()
|
||||
{
|
||||
if (ss != null)
|
||||
{
|
||||
ss.SetLerp(false);
|
||||
ss.StartScreenChange();
|
||||
}
|
||||
}
|
||||
|
||||
void OnScrollBarUp()
|
||||
{
|
||||
ss.SetLerp(true);
|
||||
ss.ChangePage(ss.CurrentPage());
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 2ec759101f5786b4b89aae8a1b47ca4b
|
||||
timeCreated: 1498919960
|
||||
licenseType: Free
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -0,0 +1,288 @@
|
||||
/// Credit RahulOfTheRamanEffect
|
||||
/// Sourced from - https://forum.unity3d.com/members/rahuloftheramaneffect.773241/
|
||||
|
||||
namespace UnityEngine.UI.Extensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Arranges child objects into a non-uniform grid, with fixed column widths and flexible row heights
|
||||
/// </summary>
|
||||
[AddComponentMenu("Layout/Extensions/Table Layout Group")]
|
||||
public class TableLayoutGroup : LayoutGroup
|
||||
{
|
||||
public enum Corner
|
||||
{
|
||||
UpperLeft = 0,
|
||||
UpperRight = 1,
|
||||
LowerLeft = 2,
|
||||
LowerRight = 3
|
||||
}
|
||||
|
||||
[SerializeField]
|
||||
protected Corner startCorner = Corner.UpperLeft;
|
||||
/// <summary>
|
||||
/// The corner starting from which the cells should be arranged
|
||||
/// </summary>
|
||||
public Corner StartCorner
|
||||
{
|
||||
get { return startCorner; }
|
||||
set
|
||||
{
|
||||
SetProperty(ref startCorner, value);
|
||||
}
|
||||
}
|
||||
|
||||
[SerializeField]
|
||||
protected float[] columnWidths = new float[1] { 96f };
|
||||
/// <summary>
|
||||
/// The widths of all the columns in the table
|
||||
/// </summary>
|
||||
public float[] ColumnWidths
|
||||
{
|
||||
get { return columnWidths; }
|
||||
set
|
||||
{
|
||||
SetProperty(ref columnWidths, value);
|
||||
}
|
||||
}
|
||||
|
||||
[SerializeField]
|
||||
protected float minimumRowHeight = 32f;
|
||||
/// <summary>
|
||||
/// The minimum height for any row in the table
|
||||
/// </summary>
|
||||
public float MinimumRowHeight
|
||||
{
|
||||
get { return minimumRowHeight; }
|
||||
set
|
||||
{
|
||||
SetProperty(ref minimumRowHeight, value);
|
||||
}
|
||||
}
|
||||
|
||||
[SerializeField]
|
||||
protected bool flexibleRowHeight = true;
|
||||
/// <summary>
|
||||
/// Expand rows to fit the cell with the highest preferred height?
|
||||
/// </summary>
|
||||
public bool FlexibleRowHeight
|
||||
{
|
||||
get { return flexibleRowHeight; }
|
||||
set
|
||||
{
|
||||
SetProperty(ref flexibleRowHeight, value);
|
||||
}
|
||||
}
|
||||
|
||||
[SerializeField]
|
||||
protected float columnSpacing = 0f;
|
||||
/// <summary>
|
||||
/// The horizontal spacing between each cell in the table
|
||||
/// </summary>
|
||||
public float ColumnSpacing
|
||||
{
|
||||
get { return columnSpacing; }
|
||||
set
|
||||
{
|
||||
SetProperty(ref columnSpacing, value);
|
||||
}
|
||||
}
|
||||
|
||||
[SerializeField]
|
||||
protected float rowSpacing = 0;
|
||||
/// <summary>
|
||||
/// The vertical spacing between each row in the table
|
||||
/// </summary>
|
||||
public float RowSpacing
|
||||
{
|
||||
get { return rowSpacing; }
|
||||
set
|
||||
{
|
||||
SetProperty(ref rowSpacing, value);
|
||||
}
|
||||
}
|
||||
|
||||
// Temporarily stores data generated during the execution CalculateLayoutInputVertical for use in SetLayoutVertical
|
||||
private float[] preferredRowHeights;
|
||||
|
||||
public override void CalculateLayoutInputHorizontal()
|
||||
{
|
||||
base.CalculateLayoutInputHorizontal();
|
||||
|
||||
float horizontalSize = padding.horizontal;
|
||||
|
||||
// We calculate the actual cell count for cases where the number of children is lesser than the number of columns
|
||||
int actualCellCount = Mathf.Min(rectChildren.Count, columnWidths.Length);
|
||||
|
||||
for (int i = 0; i < actualCellCount; i++)
|
||||
{
|
||||
horizontalSize += columnWidths[i];
|
||||
horizontalSize += columnSpacing;
|
||||
}
|
||||
|
||||
horizontalSize -= columnSpacing;
|
||||
|
||||
SetLayoutInputForAxis(horizontalSize, horizontalSize, 0, 0);
|
||||
}
|
||||
|
||||
public override void CalculateLayoutInputVertical()
|
||||
{
|
||||
int columnCount = columnWidths.Length;
|
||||
int rowCount = Mathf.CeilToInt(rectChildren.Count / (float)columnCount);
|
||||
|
||||
preferredRowHeights = new float[rowCount];
|
||||
|
||||
float totalMinHeight = padding.vertical;
|
||||
float totalPreferredHeight = padding.vertical;
|
||||
|
||||
if (rowCount > 1)
|
||||
{
|
||||
float heightFromSpacing = ((rowCount - 1) * rowSpacing);
|
||||
totalMinHeight += heightFromSpacing;
|
||||
totalPreferredHeight += heightFromSpacing;
|
||||
}
|
||||
|
||||
if (flexibleRowHeight)
|
||||
{
|
||||
// If flexibleRowHeight is enabled, find the max value for minimum and preferred heights in each row
|
||||
|
||||
float maxMinimumHeightInRow = 0;
|
||||
float maxPreferredHeightInRow = 0;
|
||||
|
||||
for (int i = 0; i < rowCount; i++)
|
||||
{
|
||||
maxMinimumHeightInRow = minimumRowHeight;
|
||||
maxPreferredHeightInRow = minimumRowHeight;
|
||||
|
||||
for (int j = 0; j < columnCount; j++)
|
||||
{
|
||||
int childIndex = (i * columnCount) + j;
|
||||
|
||||
// Safeguard against tables with incomplete rows
|
||||
if (childIndex == rectChildren.Count)
|
||||
break;
|
||||
|
||||
maxPreferredHeightInRow = Mathf.Max(LayoutUtility.GetPreferredHeight(rectChildren[childIndex]), maxPreferredHeightInRow);
|
||||
maxMinimumHeightInRow = Mathf.Max(LayoutUtility.GetMinHeight(rectChildren[childIndex]), maxMinimumHeightInRow);
|
||||
}
|
||||
|
||||
totalMinHeight += maxMinimumHeightInRow;
|
||||
totalPreferredHeight += maxPreferredHeightInRow;
|
||||
|
||||
// Add calculated row height to a commonly accessible array for reuse in SetLayoutVertical()
|
||||
preferredRowHeights[i] = maxPreferredHeightInRow;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// If flexibleRowHeight is disabled, then use the minimumRowHeight to calculate vertical layout information
|
||||
for (int i = 0; i < rowCount; i++)
|
||||
preferredRowHeights[i] = minimumRowHeight;
|
||||
|
||||
totalMinHeight += rowCount * minimumRowHeight;
|
||||
totalPreferredHeight = totalMinHeight;
|
||||
}
|
||||
|
||||
totalPreferredHeight = Mathf.Max(totalMinHeight, totalPreferredHeight);
|
||||
SetLayoutInputForAxis(totalMinHeight, totalPreferredHeight, 1, 1);
|
||||
}
|
||||
|
||||
public override void SetLayoutHorizontal()
|
||||
{
|
||||
// If no column width is defined, then assign a reasonable default
|
||||
if (columnWidths.Length == 0)
|
||||
columnWidths = new float[1] { 0f };
|
||||
|
||||
int columnCount = columnWidths.Length;
|
||||
int cornerX = (int)startCorner % 2;
|
||||
|
||||
float startOffset = 0;
|
||||
float requiredSizeWithoutPadding = 0;
|
||||
|
||||
// We calculate the actual cell count for cases where the number of children is lesser than the number of columns
|
||||
int actualCellCount = Mathf.Min(rectChildren.Count, columnWidths.Length);
|
||||
|
||||
for (int i = 0; i < actualCellCount; i++)
|
||||
{
|
||||
requiredSizeWithoutPadding += columnWidths[i];
|
||||
requiredSizeWithoutPadding += columnSpacing;
|
||||
}
|
||||
|
||||
requiredSizeWithoutPadding -= columnSpacing;
|
||||
|
||||
startOffset = GetStartOffset(0, requiredSizeWithoutPadding);
|
||||
|
||||
if (cornerX == 1)
|
||||
startOffset += requiredSizeWithoutPadding;
|
||||
|
||||
float positionX = startOffset;
|
||||
|
||||
for (int i = 0; i < rectChildren.Count; i++)
|
||||
{
|
||||
int currentColumnIndex = i % columnCount;
|
||||
|
||||
// If it's the first cell in the row, reset positionX
|
||||
if (currentColumnIndex == 0)
|
||||
positionX = startOffset;
|
||||
|
||||
if (cornerX == 1)
|
||||
positionX -= columnWidths[currentColumnIndex];
|
||||
|
||||
SetChildAlongAxis(rectChildren[i], 0, positionX, columnWidths[currentColumnIndex]);
|
||||
|
||||
if (cornerX == 1)
|
||||
positionX -= columnSpacing;
|
||||
else
|
||||
positionX += columnWidths[currentColumnIndex] + columnSpacing;
|
||||
}
|
||||
}
|
||||
|
||||
public override void SetLayoutVertical()
|
||||
{
|
||||
int columnCount = columnWidths.Length;
|
||||
int rowCount = preferredRowHeights.Length;
|
||||
|
||||
int cornerY = (int)startCorner / 2;
|
||||
|
||||
float startOffset = 0;
|
||||
float requiredSizeWithoutPadding = 0;
|
||||
|
||||
for (int i = 0; i < rowCount; i++)
|
||||
requiredSizeWithoutPadding += preferredRowHeights[i];
|
||||
|
||||
if (rowCount > 1)
|
||||
requiredSizeWithoutPadding += (rowCount - 1) * rowSpacing;
|
||||
|
||||
startOffset = GetStartOffset(1, requiredSizeWithoutPadding);
|
||||
|
||||
if (cornerY == 1)
|
||||
startOffset += requiredSizeWithoutPadding;
|
||||
|
||||
float positionY = startOffset;
|
||||
|
||||
for (int i = 0; i < rowCount; i++)
|
||||
{
|
||||
if (cornerY == 1)
|
||||
positionY -= preferredRowHeights[i];
|
||||
|
||||
for (int j = 0; j < columnCount; j++)
|
||||
{
|
||||
int childIndex = (i * columnCount) + j;
|
||||
|
||||
// Safeguard against tables with incomplete rows
|
||||
if (childIndex == rectChildren.Count)
|
||||
break;
|
||||
|
||||
SetChildAlongAxis(rectChildren[childIndex], 1, positionY, preferredRowHeights[i]);
|
||||
}
|
||||
|
||||
if (cornerY == 1)
|
||||
positionY -= rowSpacing;
|
||||
else
|
||||
positionY += preferredRowHeights[i] + rowSpacing;
|
||||
}
|
||||
|
||||
// Set preferredRowHeights to null to free memory
|
||||
preferredRowHeights = null;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 6cee1f3bb43ca9c4c9e00b54a998d9ce
|
||||
timeCreated: 1483117134
|
||||
licenseType: Pro
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -0,0 +1,106 @@
|
||||
/// Credit Ges
|
||||
/// Sourced from - http://forum.unity3d.com/threads/scripts-useful-4-6-scripts-collection.264161/page-3#post-2280109
|
||||
|
||||
using UnityEngine.EventSystems;
|
||||
|
||||
namespace UnityEngine.UI.Extensions
|
||||
{
|
||||
[ExecuteInEditMode]
|
||||
[RequireComponent(typeof(RectTransform))]
|
||||
[AddComponentMenu("Layout/Extensions/Tile Size Fitter")]
|
||||
public class TileSizeFitter : UIBehaviour, ILayoutSelfController
|
||||
{
|
||||
[SerializeField]
|
||||
private Vector2 m_Border = Vector2.zero;
|
||||
public Vector2 Border { get { return m_Border; } set { if (SetPropertyUtility.SetStruct(ref m_Border, value)) SetDirty(); } }
|
||||
|
||||
[SerializeField]
|
||||
private Vector2 m_TileSize = Vector2.zero;
|
||||
public Vector2 TileSize { get { return m_TileSize; } set { if (SetPropertyUtility.SetStruct(ref m_TileSize, value)) SetDirty(); } }
|
||||
|
||||
[System.NonSerialized]
|
||||
private RectTransform m_Rect;
|
||||
private RectTransform rectTransform { get { if (m_Rect == null) m_Rect = GetComponent<RectTransform>(); return m_Rect; } }
|
||||
|
||||
private DrivenRectTransformTracker m_Tracker;
|
||||
|
||||
#region Unity Lifetime calls
|
||||
|
||||
protected override void OnEnable()
|
||||
{
|
||||
base.OnEnable();
|
||||
SetDirty();
|
||||
}
|
||||
|
||||
protected override void OnDisable()
|
||||
{
|
||||
m_Tracker.Clear();
|
||||
LayoutRebuilder.MarkLayoutForRebuild(rectTransform);
|
||||
base.OnDisable();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
protected override void OnRectTransformDimensionsChange()
|
||||
{
|
||||
UpdateRect();
|
||||
}
|
||||
|
||||
private void UpdateRect()
|
||||
{
|
||||
if (!IsActive())
|
||||
return;
|
||||
|
||||
m_Tracker.Clear();
|
||||
|
||||
m_Tracker.Add(this, rectTransform,
|
||||
DrivenTransformProperties.Anchors |
|
||||
DrivenTransformProperties.AnchoredPosition);
|
||||
rectTransform.anchorMin = Vector2.zero;
|
||||
rectTransform.anchorMax = Vector2.one;
|
||||
rectTransform.anchoredPosition = Vector2.zero;
|
||||
|
||||
m_Tracker.Add(this, rectTransform,
|
||||
DrivenTransformProperties.SizeDeltaX |
|
||||
DrivenTransformProperties.SizeDeltaY);
|
||||
Vector2 sizeDelta = GetParentSize() - Border;
|
||||
if (TileSize.x > 0.001f)
|
||||
sizeDelta.x -= Mathf.Floor(sizeDelta.x / TileSize.x) * TileSize.x;
|
||||
else
|
||||
sizeDelta.x = 0;
|
||||
if (TileSize.y > 0.001f)
|
||||
sizeDelta.y -= Mathf.Floor(sizeDelta.y / TileSize.y) * TileSize.y;
|
||||
else
|
||||
sizeDelta.y = 0;
|
||||
rectTransform.sizeDelta = -sizeDelta;
|
||||
}
|
||||
|
||||
private Vector2 GetParentSize()
|
||||
{
|
||||
RectTransform parent = rectTransform.parent as RectTransform;
|
||||
if (!parent)
|
||||
return Vector2.zero;
|
||||
return parent.rect.size;
|
||||
}
|
||||
|
||||
public virtual void SetLayoutHorizontal() { }
|
||||
public virtual void SetLayoutVertical() { }
|
||||
|
||||
protected void SetDirty()
|
||||
{
|
||||
if (!IsActive())
|
||||
return;
|
||||
|
||||
UpdateRect();
|
||||
}
|
||||
|
||||
#if UNITY_EDITOR
|
||||
protected override void OnValidate()
|
||||
{
|
||||
m_TileSize.x = Mathf.Clamp(m_TileSize.x, 0.001f, 1000f);
|
||||
m_TileSize.y = Mathf.Clamp(m_TileSize.y, 0.001f, 1000f);
|
||||
SetDirty();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 614c629b8dffdb548b9bef9189606bb9
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
@ -0,0 +1,254 @@
|
||||
/// Credit Mrs. YakaYocha
|
||||
/// Sourced from - https://www.youtube.com/channel/UCHp8LZ_0-iCvl-5pjHATsgw
|
||||
/// Please donate: https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=RJ8D9FRFQF9VS
|
||||
|
||||
using UnityEngine.Events;
|
||||
|
||||
namespace UnityEngine.UI.Extensions
|
||||
{
|
||||
[RequireComponent(typeof(ScrollRect))]
|
||||
[AddComponentMenu("Layout/Extensions/Vertical Scroller")]
|
||||
public class UIVerticalScroller : MonoBehaviour
|
||||
{
|
||||
[Tooltip("desired ScrollRect")]
|
||||
public ScrollRect scrollRect;
|
||||
[Tooltip("Center display area (position of zoomed content)")]
|
||||
public RectTransform center;
|
||||
[Tooltip("Size / spacing of elements")]
|
||||
public RectTransform elementSize;
|
||||
[Tooltip("Scale = 1/ (1+distance from center * shrinkage)")]
|
||||
public Vector2 elementShrinkage = new Vector2(1f / 200, 1f / 200);
|
||||
[Tooltip("Minimum element scale (furthest from center)")]
|
||||
public Vector2 minScale = new Vector2(0.7f, 0.7f);
|
||||
[Tooltip("Select the item to be in center on start.")]
|
||||
public int startingIndex = -1;
|
||||
[Tooltip("Stop scrolling past last element from inertia.")]
|
||||
public bool stopMomentumOnEnd = true;
|
||||
[Tooltip("Set Items out of center to not interactible.")]
|
||||
public bool disableUnfocused = true;
|
||||
[Tooltip("Button to go to the next page. (optional)")]
|
||||
public GameObject scrollUpButton;
|
||||
[Tooltip("Button to go to the previous page. (optional)")]
|
||||
public GameObject scrollDownButton;
|
||||
[Tooltip("Event fired when a specific item is clicked, exposes index number of item. (optional)")]
|
||||
public IntEvent OnButtonClicked;
|
||||
[Tooltip("Event fired when the focused item is Changed. (optional)")]
|
||||
public IntEvent OnFocusChanged;
|
||||
[HideInInspector]
|
||||
public GameObject[] _arrayOfElements;
|
||||
|
||||
public int focusedElementIndex { get; private set; }
|
||||
|
||||
public string result { get; private set; }
|
||||
|
||||
private float[] distReposition;
|
||||
private float[] distance;
|
||||
//private int elementsDistance;
|
||||
|
||||
|
||||
//Scrollable area (content of desired ScrollRect)
|
||||
[HideInInspector]
|
||||
public RectTransform scrollingPanel{ get { return scrollRect.content; } }
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Constructor when not used as component but called from other script, don't forget to set the non-optional properties.
|
||||
/// </summary>
|
||||
public UIVerticalScroller()
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Constructor when not used as component but called from other script
|
||||
/// </summary>
|
||||
public UIVerticalScroller(RectTransform center, RectTransform elementSize, ScrollRect scrollRect, GameObject[] arrayOfElements)
|
||||
{
|
||||
this.center = center;
|
||||
this.elementSize = elementSize;
|
||||
this.scrollRect = scrollRect;
|
||||
_arrayOfElements = arrayOfElements;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Awake this instance.
|
||||
/// </summary>
|
||||
public void Awake()
|
||||
{
|
||||
if (!scrollRect)
|
||||
{
|
||||
scrollRect = GetComponent<ScrollRect>();
|
||||
}
|
||||
if (!center)
|
||||
{
|
||||
Debug.LogError("Please define the RectTransform for the Center viewport of the scrollable area");
|
||||
}
|
||||
if (!elementSize)
|
||||
{
|
||||
elementSize = center;
|
||||
}
|
||||
if (_arrayOfElements == null || _arrayOfElements.Length == 0)
|
||||
{
|
||||
_arrayOfElements = new GameObject[scrollingPanel.childCount];
|
||||
for (int i = 0; i < scrollingPanel.childCount; i++)
|
||||
{
|
||||
_arrayOfElements[i] = scrollingPanel.GetChild(i).gameObject;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Recognises and resizes the children.
|
||||
/// </summary>
|
||||
/// <param name="startingIndex">Starting index.</param>
|
||||
/// <param name="arrayOfElements">Array of elements.</param>
|
||||
public void updateChildren(int startingIndex = -1, GameObject[] arrayOfElements = null)
|
||||
{
|
||||
// Set _arrayOfElements to arrayOfElements if given, otherwise to child objects of the scrolling panel.
|
||||
if (arrayOfElements != null)
|
||||
{
|
||||
_arrayOfElements = arrayOfElements;
|
||||
}
|
||||
else
|
||||
{
|
||||
_arrayOfElements = new GameObject[scrollingPanel.childCount];
|
||||
for (int i = 0; i < scrollingPanel.childCount; i++)
|
||||
{
|
||||
_arrayOfElements[i] = scrollingPanel.GetChild(i).gameObject;
|
||||
}
|
||||
}
|
||||
|
||||
// resize the elements to match elementSize rect
|
||||
for (var i = 0; i < _arrayOfElements.Length; i++)
|
||||
{
|
||||
int j = i;
|
||||
_arrayOfElements[i].GetComponent<Button>().onClick.RemoveAllListeners();
|
||||
if (OnButtonClicked != null)
|
||||
{
|
||||
_arrayOfElements[i].GetComponent<Button>().onClick.AddListener(() => OnButtonClicked.Invoke(j));
|
||||
}
|
||||
RectTransform r = _arrayOfElements[i].GetComponent<RectTransform>();
|
||||
r.anchorMax = r.anchorMin = r.pivot = new Vector2(0.5f, 0.5f);
|
||||
r.localPosition = new Vector2(0, i * elementSize.rect.size.y);
|
||||
r.sizeDelta = elementSize.rect.size;
|
||||
}
|
||||
|
||||
// prepare for scrolling
|
||||
distance = new float[_arrayOfElements.Length];
|
||||
distReposition = new float[_arrayOfElements.Length];
|
||||
focusedElementIndex = -1;
|
||||
|
||||
//scrollRect.scrollSensitivity = elementSize.rect.height / 5;
|
||||
|
||||
// if starting index is given, snap to respective element
|
||||
if (startingIndex > -1)
|
||||
{
|
||||
startingIndex = startingIndex > _arrayOfElements.Length ? _arrayOfElements.Length - 1 : startingIndex;
|
||||
SnapToElement(startingIndex);
|
||||
}
|
||||
}
|
||||
|
||||
public void Start()
|
||||
{
|
||||
|
||||
if (scrollUpButton)
|
||||
scrollUpButton.GetComponent<Button>().onClick.AddListener(() =>
|
||||
{
|
||||
ScrollUp();
|
||||
});
|
||||
|
||||
if (scrollDownButton)
|
||||
scrollDownButton.GetComponent<Button>().onClick.AddListener(() =>
|
||||
{
|
||||
ScrollDown();
|
||||
});
|
||||
updateChildren(startingIndex, _arrayOfElements);
|
||||
}
|
||||
|
||||
|
||||
public void Update()
|
||||
{
|
||||
if (_arrayOfElements.Length < 1)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
for (var i = 0; i < _arrayOfElements.Length; i++)
|
||||
{
|
||||
distReposition[i] = center.GetComponent<RectTransform>().position.y - _arrayOfElements[i].GetComponent<RectTransform>().position.y;
|
||||
distance[i] = Mathf.Abs(distReposition[i]);
|
||||
|
||||
//Magnifying effect
|
||||
Vector2 scale = Vector2.Max(minScale, new Vector2(1 / (1 + distance[i] * elementShrinkage.x), (1 / (1 + distance[i] * elementShrinkage.y))));
|
||||
_arrayOfElements[i].GetComponent<RectTransform>().transform.localScale = new Vector3(scale.x, scale.y, 1f);
|
||||
}
|
||||
|
||||
// detect focused element
|
||||
float minDistance = Mathf.Min(distance);
|
||||
int oldFocusedElement = focusedElementIndex;
|
||||
for (var i = 0; i < _arrayOfElements.Length; i++)
|
||||
{
|
||||
_arrayOfElements[i].GetComponent<CanvasGroup>().interactable = !disableUnfocused || minDistance == distance[i];
|
||||
if (minDistance == distance[i])
|
||||
{
|
||||
focusedElementIndex = i;
|
||||
result = _arrayOfElements[i].GetComponentInChildren<Text>().text;
|
||||
}
|
||||
}
|
||||
if (focusedElementIndex != oldFocusedElement && OnFocusChanged != null)
|
||||
{
|
||||
OnFocusChanged.Invoke(focusedElementIndex);
|
||||
}
|
||||
|
||||
|
||||
if (!UIExtensionsInputManager.GetMouseButton(0))
|
||||
{
|
||||
// scroll slowly to nearest element when not dragged
|
||||
ScrollingElements();
|
||||
}
|
||||
|
||||
|
||||
// stop scrolling past last element from inertia
|
||||
if (stopMomentumOnEnd
|
||||
&& (_arrayOfElements[0].GetComponent<RectTransform>().position.y > center.position.y
|
||||
|| _arrayOfElements[_arrayOfElements.Length - 1].GetComponent<RectTransform>().position.y < center.position.y))
|
||||
{
|
||||
scrollRect.velocity = Vector2.zero;
|
||||
}
|
||||
}
|
||||
|
||||
private void ScrollingElements()
|
||||
{
|
||||
float newY = Mathf.Lerp(scrollingPanel.anchoredPosition.y, scrollingPanel.anchoredPosition.y + distReposition[focusedElementIndex], Time.deltaTime * 2f);
|
||||
Vector2 newPosition = new Vector2(scrollingPanel.anchoredPosition.x, newY);
|
||||
scrollingPanel.anchoredPosition = newPosition;
|
||||
}
|
||||
|
||||
public void SnapToElement(int element)
|
||||
{
|
||||
float deltaElementPositionY = elementSize.rect.height * element;
|
||||
Vector2 newPosition = new Vector2(scrollingPanel.anchoredPosition.x, -deltaElementPositionY);
|
||||
scrollingPanel.anchoredPosition = newPosition;
|
||||
|
||||
}
|
||||
|
||||
public void ScrollUp()
|
||||
{
|
||||
float deltaUp = elementSize.rect.height / 1.2f;
|
||||
Vector2 newPositionUp = new Vector2(scrollingPanel.anchoredPosition.x, scrollingPanel.anchoredPosition.y - deltaUp);
|
||||
scrollingPanel.anchoredPosition = Vector2.Lerp(scrollingPanel.anchoredPosition, newPositionUp, 1);
|
||||
}
|
||||
|
||||
public void ScrollDown()
|
||||
{
|
||||
float deltaDown = elementSize.rect.height / 1.2f;
|
||||
Vector2 newPositionDown = new Vector2(scrollingPanel.anchoredPosition.x, scrollingPanel.anchoredPosition.y + deltaDown);
|
||||
scrollingPanel.anchoredPosition = newPositionDown;
|
||||
}
|
||||
|
||||
[System.Serializable]
|
||||
public class IntEvent:UnityEvent<int>
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b9f902652954d44beb3a916831d27435
|
||||
timeCreated: 1449409849
|
||||
licenseType: Free
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -0,0 +1,318 @@
|
||||
/// Credit BinaryX, SimonDarksideJ
|
||||
/// Sourced from - http://forum.unity3d.com/threads/scripts-useful-4-6-scripts-collection.264161/page-2#post-1945602
|
||||
/// Updated by SimonDarksideJ - removed dependency on a custom ScrollRect script. Now implements drag interfaces and standard Scroll Rect.
|
||||
/// Updated by SimonDarksideJ - major refactoring on updating current position and scroll management
|
||||
|
||||
using System;
|
||||
using UnityEngine.EventSystems;
|
||||
|
||||
namespace UnityEngine.UI.Extensions
|
||||
{
|
||||
[RequireComponent(typeof(ScrollRect))]
|
||||
[AddComponentMenu("Layout/Extensions/Vertical Scroll Snap")]
|
||||
public class VerticalScrollSnap : ScrollSnapBase
|
||||
{
|
||||
private bool updated = true;
|
||||
|
||||
void Start()
|
||||
{
|
||||
_isVertical = true;
|
||||
_childAnchorPoint = new Vector2(0.5f,0);
|
||||
_currentPage = StartingScreen;
|
||||
panelDimensions = gameObject.GetComponent<RectTransform>().rect;
|
||||
UpdateLayout();
|
||||
}
|
||||
|
||||
void Update()
|
||||
{
|
||||
updated = false;
|
||||
|
||||
if (!_lerp && _scroll_rect.velocity == Vector2.zero)
|
||||
{
|
||||
if (!_settled && !_pointerDown)
|
||||
{
|
||||
if (!IsRectSettledOnaPage(_screensContainer.anchoredPosition))
|
||||
{
|
||||
ScrollToClosestElement();
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
else if (_lerp)
|
||||
{
|
||||
_screensContainer.anchoredPosition = Vector3.Lerp(_screensContainer.anchoredPosition, _lerp_target, transitionSpeed * (UseTimeScale ? Time.deltaTime : Time.unscaledDeltaTime));
|
||||
if (Vector3.Distance(_screensContainer.anchoredPosition, _lerp_target) < 0.1f)
|
||||
{
|
||||
_screensContainer.anchoredPosition = _lerp_target;
|
||||
_lerp = false;
|
||||
EndScreenChange();
|
||||
}
|
||||
}
|
||||
|
||||
if (UseHardSwipe) return;
|
||||
|
||||
CurrentPage = GetPageforPosition(_screensContainer.anchoredPosition);
|
||||
|
||||
//If the container is moving check if it needs to settle on a page
|
||||
if (!_pointerDown)
|
||||
{
|
||||
if (_scroll_rect.velocity.y > 0.01 || _scroll_rect.velocity.y < -0.01)
|
||||
{
|
||||
// if the pointer is released and is moving slower than the threshold, then just land on a page
|
||||
if (IsRectMovingSlowerThanThreshold(0))
|
||||
{
|
||||
ScrollToClosestElement();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private bool IsRectMovingSlowerThanThreshold(float startingSpeed)
|
||||
{
|
||||
return (_scroll_rect.velocity.y > startingSpeed && _scroll_rect.velocity.y < SwipeVelocityThreshold) ||
|
||||
(_scroll_rect.velocity.y < startingSpeed && _scroll_rect.velocity.y > -SwipeVelocityThreshold);
|
||||
}
|
||||
|
||||
public void DistributePages()
|
||||
{
|
||||
_screens = _screensContainer.childCount;
|
||||
_scroll_rect.verticalNormalizedPosition = 0;
|
||||
|
||||
float _offset = 0;
|
||||
float _dimension = 0;
|
||||
Rect panelDimensions = gameObject.GetComponent<RectTransform>().rect;
|
||||
float currentYPosition = 0;
|
||||
var pageStepValue = _childSize = (int)panelDimensions.height * ((PageStep == 0) ? 3 : PageStep);
|
||||
|
||||
for (int i = 0; i < _screensContainer.transform.childCount; i++)
|
||||
{
|
||||
RectTransform child = _screensContainer.transform.GetChild(i).gameObject.GetComponent<RectTransform>();
|
||||
currentYPosition = _offset + i * pageStepValue;
|
||||
child.sizeDelta = new Vector2(panelDimensions.width, panelDimensions.height);
|
||||
child.anchoredPosition = new Vector2(0f, currentYPosition);
|
||||
child.anchorMin = child.anchorMax = child.pivot = _childAnchorPoint;
|
||||
}
|
||||
|
||||
_dimension = currentYPosition + _offset * -1;
|
||||
|
||||
_screensContainer.GetComponent<RectTransform>().offsetMax = new Vector2(0f, _dimension);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Add a new child to this Scroll Snap and recalculate it's children
|
||||
/// </summary>
|
||||
/// <param name="GO">GameObject to add to the ScrollSnap</param>
|
||||
public void AddChild(GameObject GO)
|
||||
{
|
||||
AddChild(GO, false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Add a new child to this Scroll Snap and recalculate it's children
|
||||
/// </summary>
|
||||
/// <param name="GO">GameObject to add to the ScrollSnap</param>
|
||||
/// <param name="WorldPositionStays">Should the world position be updated to it's parent transform?</param>
|
||||
public void AddChild(GameObject GO, bool WorldPositionStays)
|
||||
{
|
||||
_scroll_rect.verticalNormalizedPosition = 0;
|
||||
GO.transform.SetParent(_screensContainer, WorldPositionStays);
|
||||
InitialiseChildObjectsFromScene();
|
||||
DistributePages();
|
||||
if (MaskArea) UpdateVisible();
|
||||
|
||||
SetScrollContainerPosition();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Remove a new child to this Scroll Snap and recalculate it's children
|
||||
/// *Note, this is an index address (0-x)
|
||||
/// </summary>
|
||||
/// <param name="index">Index element of child to remove</param>
|
||||
/// <param name="ChildRemoved">Resulting removed GO</param>
|
||||
public void RemoveChild(int index, out GameObject ChildRemoved)
|
||||
{
|
||||
RemoveChild(index, false, out ChildRemoved);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Remove a new child to this Scroll Snap and recalculate it's children
|
||||
/// *Note, this is an index address (0-x)
|
||||
/// </summary>
|
||||
/// <param name="index">Index element of child to remove</param>
|
||||
/// <param name="WorldPositionStays">If true, the parent-relative position, scale and rotation are modified such that the object keeps the same world space position, rotation and scale as before</param>
|
||||
/// <param name="ChildRemoved">Resulting removed GO</param>
|
||||
public void RemoveChild(int index, bool WorldPositionStays, out GameObject ChildRemoved)
|
||||
{
|
||||
ChildRemoved = null;
|
||||
if (index < 0 || index > _screensContainer.childCount)
|
||||
{
|
||||
return;
|
||||
}
|
||||
_scroll_rect.verticalNormalizedPosition = 0;
|
||||
|
||||
Transform child = _screensContainer.transform.GetChild(index);
|
||||
child.SetParent(null, WorldPositionStays);
|
||||
ChildRemoved = child.gameObject;
|
||||
InitialiseChildObjectsFromScene();
|
||||
DistributePages();
|
||||
if (MaskArea) UpdateVisible();
|
||||
|
||||
if (_currentPage > _screens - 1)
|
||||
{
|
||||
CurrentPage = _screens - 1;
|
||||
}
|
||||
|
||||
SetScrollContainerPosition();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Remove all children from this ScrollSnap
|
||||
/// </summary>
|
||||
/// <param name="ChildrenRemoved">Array of child GO's removed</param>
|
||||
public void RemoveAllChildren(out GameObject[] ChildrenRemoved)
|
||||
{
|
||||
RemoveAllChildren(false, out ChildrenRemoved);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Remove all children from this ScrollSnap
|
||||
/// </summary>
|
||||
/// <param name="WorldPositionStays">If true, the parent-relative position, scale and rotation are modified such that the object keeps the same world space position, rotation and scale as before</param>
|
||||
/// <param name="ChildrenRemoved">Array of child GO's removed</param>
|
||||
public void RemoveAllChildren(bool WorldPositionStays, out GameObject[] ChildrenRemoved)
|
||||
{
|
||||
var _screenCount = _screensContainer.childCount;
|
||||
ChildrenRemoved = new GameObject[_screenCount];
|
||||
|
||||
for (int i = _screenCount - 1; i >= 0; i--)
|
||||
{
|
||||
ChildrenRemoved[i] = _screensContainer.GetChild(i).gameObject;
|
||||
ChildrenRemoved[i].transform.SetParent(null, WorldPositionStays);
|
||||
}
|
||||
|
||||
_scroll_rect.verticalNormalizedPosition = 0;
|
||||
CurrentPage = 0;
|
||||
InitialiseChildObjectsFromScene();
|
||||
DistributePages();
|
||||
if (MaskArea) UpdateVisible();
|
||||
}
|
||||
|
||||
private void SetScrollContainerPosition()
|
||||
{
|
||||
_scrollStartPosition = _screensContainer.anchoredPosition.y;
|
||||
_scroll_rect.verticalNormalizedPosition = (float)(_currentPage) / (_screens - 1);
|
||||
OnCurrentScreenChange(_currentPage);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// used for changing / updating between screen resolutions
|
||||
/// </summary>
|
||||
public void UpdateLayout()
|
||||
{
|
||||
_lerp = false;
|
||||
DistributePages();
|
||||
if (MaskArea) UpdateVisible();
|
||||
SetScrollContainerPosition();
|
||||
OnCurrentScreenChange(_currentPage);
|
||||
}
|
||||
|
||||
private void OnRectTransformDimensionsChange()
|
||||
{
|
||||
if (_childAnchorPoint != Vector2.zero)
|
||||
{
|
||||
UpdateLayout();
|
||||
}
|
||||
}
|
||||
|
||||
private void OnEnable()
|
||||
{
|
||||
InitialiseChildObjectsFromScene();
|
||||
DistributePages();
|
||||
if (MaskArea)
|
||||
UpdateVisible();
|
||||
|
||||
if (JumpOnEnable || !RestartOnEnable)
|
||||
SetScrollContainerPosition();
|
||||
if (RestartOnEnable)
|
||||
GoToScreen(StartingScreen);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Release screen to swipe
|
||||
/// </summary>
|
||||
/// <param name="eventData"></param>
|
||||
public override void OnEndDrag(PointerEventData eventData)
|
||||
{
|
||||
if (updated)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// to prevent double dragging, only act on EndDrag once per frame
|
||||
updated = true;
|
||||
|
||||
_pointerDown = false;
|
||||
|
||||
if (_scroll_rect.vertical)
|
||||
{
|
||||
if (UseSwipeDeltaThreshold && Math.Abs(eventData.delta.y) < SwipeDeltaThreshold)
|
||||
{
|
||||
ScrollToClosestElement();
|
||||
}
|
||||
else
|
||||
{
|
||||
var distance = Vector3.Distance(_startPosition, _screensContainer.anchoredPosition);
|
||||
if (UseHardSwipe)
|
||||
{
|
||||
_scroll_rect.velocity = Vector3.zero;
|
||||
|
||||
if (distance > FastSwipeThreshold)
|
||||
{
|
||||
if (_startPosition.y - _screensContainer.anchoredPosition.y > 0)
|
||||
{
|
||||
NextScreen();
|
||||
}
|
||||
else
|
||||
{
|
||||
PreviousScreen();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
ScrollToClosestElement();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (UseFastSwipe && distance < panelDimensions.height + FastSwipeThreshold && distance >= 1f)
|
||||
{
|
||||
_scroll_rect.velocity = Vector3.zero;
|
||||
if (_startPosition.y - _screensContainer.anchoredPosition.y > 0)
|
||||
{
|
||||
if (_startPosition.y - _screensContainer.anchoredPosition.y > _childSize / 3)
|
||||
{
|
||||
ScrollToClosestElement();
|
||||
}
|
||||
else
|
||||
{
|
||||
NextScreen();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (_startPosition.y - _screensContainer.anchoredPosition.y > -_childSize / 3)
|
||||
{
|
||||
ScrollToClosestElement();
|
||||
}
|
||||
else
|
||||
{
|
||||
PreviousScreen();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e9dccd2761b104249a8eb0636b550188
|
||||
timeCreated: 1440928417
|
||||
licenseType: Pro
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
Reference in New Issue
Block a user