503 lines
16 KiB
C#
503 lines
16 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using UnityEngine;
|
|
using UnityEngine.UI;
|
|
|
|
namespace Gpm.Ui
|
|
{
|
|
/// <summary>
|
|
/// 스크롤 뷰의 스크롤 방향과 콘텐츠 정렬 방식을 정의합니다.
|
|
/// </summary>
|
|
public enum ScrollAxis
|
|
{
|
|
DEFAULT = 0, // ScrollRect의 설정을 따름
|
|
VERTICAL_TOP, // 수직, 위쪽 정렬
|
|
VERTICAL_CENTER, // 수직, 중앙 정렬
|
|
VERTICAL_BOTTOM, // 수직, 아래쪽 정렬
|
|
HORIZONTAL_LEFT, // 수평, 왼쪽 정렬
|
|
HORIZONTAL_CENTER, // 수평, 중앙 정렬
|
|
HORIZONTAL_RIGHT, // 수평, 오른쪽 정렬
|
|
}
|
|
|
|
/// <summary>
|
|
/// 이 클래스는 InfiniteScroll과 같은 스크롤 컴포넌트의 레이아웃 규칙을 정의합니다.
|
|
/// 스크롤 방향, 정렬, 간격, 그리드 설정 등 UI 항목을 어떻게 배치할지에 대한 모든 정보를 담고 있습니다.
|
|
/// MonoBehaviour가 아니며, 다른 컴포넌트의 필드로 선언되어 인스펙터에서 값을 설정하는 방식으로 사용됩니다.
|
|
/// </summary>
|
|
/// <example>
|
|
/// <b>사용 예시 (InfiniteScroll.cs 내에서):</b>
|
|
/// <code>
|
|
/// public class InfiniteScroll : MonoBehaviour
|
|
/// {
|
|
/// // ScrollLayout을 필드로 선언하여 인스펙터에서 레이아웃을 상세하게 설정할 수 있습니다.
|
|
/// public ScrollLayout layout;
|
|
///
|
|
/// // ... 스크롤 로직 ...
|
|
/// }
|
|
/// </code>
|
|
/// 위와 같이 `InfiniteScroll` 컴포넌트가 `ScrollLayout`을 멤버 변수로 가지고,
|
|
/// 사용자는 Unity 인스펙터에서 `layout` 변수의 세부 항목(축, 간격, 그리드 등)을 설정하여 스크롤 뷰의 동작을 커스터마이징할 수 있습니다.
|
|
/// </example>
|
|
[Serializable]
|
|
public class ScrollLayout
|
|
{
|
|
/// <summary>
|
|
/// 그리드 레이아웃에서 각 열 또는 행의 크기 정보를 담는 클래스입니다.
|
|
/// </summary>
|
|
[Serializable]
|
|
public class LayoutValue
|
|
{
|
|
public enum ValueType
|
|
{
|
|
DEFAULT, // 기본값
|
|
RATE, // 비율
|
|
}
|
|
|
|
[HideInInspector]
|
|
public ValueType valueType;
|
|
public float value; // 크기 값 (비율)
|
|
}
|
|
|
|
/// <summary>
|
|
/// 스크롤의 주 축(수직 또는 수평)과 정렬 기준을 설정합니다.
|
|
/// </summary>
|
|
public ScrollAxis axis;
|
|
|
|
/// <summary>
|
|
/// 콘텐츠 전체의 상하좌우 여백입니다.
|
|
/// Vector2(가로 여백, 세로 여백)
|
|
/// </summary>
|
|
public Vector2 padding;
|
|
|
|
/// <summary>
|
|
/// UI 항목들 사이의 간격입니다.
|
|
/// Vector2(가로 간격, 세로 간격)
|
|
/// </summary>
|
|
public Vector2 space;
|
|
|
|
/// <summary>
|
|
/// true일 경우, 수직 스크롤에서 위에서 아래로 항목을 채웁니다.
|
|
/// </summary>
|
|
public bool topToBotton = true;
|
|
|
|
/// <summary>
|
|
/// true일 경우, 수평 스크롤에서 왼쪽에서 오른쪽으로 항목을 채웁니다.
|
|
/// </summary>
|
|
public bool leftToRight = true;
|
|
|
|
/// <summary>
|
|
/// 그리드 레이아웃을 설정하는 데 사용됩니다.
|
|
/// 리스트에 항목이 2개 이상이면 그리드로 동작하며, 각 항목의 `value`는 그리드 행/열의 상대적 크기 비율을 나타냅니다.
|
|
/// 예: 수직 스크롤에서 values 리스트에 3개의 항목을 넣으면 3열 그리드가 됩니다.
|
|
/// </summary>
|
|
public List<LayoutValue> values = new List<LayoutValue>();
|
|
|
|
/// <summary>
|
|
/// 주어진 축이 수직 스크롤인지 확인합니다.
|
|
/// </summary>
|
|
static public bool IsVertical(ScrollAxis axis)
|
|
{
|
|
return axis == ScrollAxis.VERTICAL_TOP ||
|
|
axis == ScrollAxis.VERTICAL_CENTER ||
|
|
axis == ScrollAxis.VERTICAL_BOTTOM;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 설정된 `axis`에 맞게 `ScrollRect`의 스크롤 방향(vertical/horizontal)을 강제로 설정합니다.
|
|
/// `axis`가 `DEFAULT`이면 `ScrollRect`의 기존 설정을 따릅니다.
|
|
/// </summary>
|
|
public void CheckAxis(ScrollRect scrollRect)
|
|
{
|
|
if (axis == ScrollAxis.DEFAULT)
|
|
{
|
|
if (scrollRect.vertical == true)
|
|
{
|
|
axis = ScrollAxis.VERTICAL_TOP;
|
|
}
|
|
else
|
|
{
|
|
axis = ScrollAxis.HORIZONTAL_LEFT;
|
|
}
|
|
}
|
|
|
|
if (IsVertical() == true)
|
|
{
|
|
scrollRect.vertical = true;
|
|
}
|
|
else
|
|
{
|
|
scrollRect.horizontal = true;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// 현재 레이아웃이 수직 스크롤인지 확인합니다.
|
|
/// </summary>
|
|
public bool IsVertical()
|
|
{
|
|
return IsVertical(axis);
|
|
}
|
|
|
|
/// <summary>
|
|
/// 설정된 `axis`에 따라 콘텐츠 `RectTransform`의 피벗(pivot)을 계산하여 반환합니다.
|
|
/// 이는 콘텐츠 정렬의 기준점이 됩니다.
|
|
/// </summary>
|
|
public Vector2 GetAxisPivot()
|
|
{
|
|
Vector2 pivot = Vector2.zero;
|
|
if (axis == ScrollAxis.VERTICAL_TOP)
|
|
{
|
|
pivot = new Vector2(0.5f, 1);
|
|
}
|
|
else if (axis == ScrollAxis.VERTICAL_CENTER)
|
|
{
|
|
pivot = new Vector2(0.5f, 0.5f);
|
|
}
|
|
else if (axis == ScrollAxis.VERTICAL_BOTTOM)
|
|
{
|
|
pivot = new Vector2(0.5f, 0);
|
|
}
|
|
else if (axis == ScrollAxis.DEFAULT ||
|
|
axis == ScrollAxis.HORIZONTAL_LEFT)
|
|
{
|
|
pivot = new Vector2(0, 0.5f);
|
|
}
|
|
else if (axis == ScrollAxis.HORIZONTAL_CENTER)
|
|
{
|
|
pivot = new Vector2(0.5f, 0.5f);
|
|
}
|
|
else if (axis == ScrollAxis.HORIZONTAL_RIGHT)
|
|
{
|
|
pivot = new Vector2(1.0f, 0.5f);
|
|
}
|
|
return pivot;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 그리드 레이아웃 값(`values`)을 기본 상태(비율 1)로 초기화합니다.
|
|
/// </summary>
|
|
|
|
public void SetDefaults()
|
|
{
|
|
foreach (LayoutValue layoutValue in values)
|
|
{
|
|
if (layoutValue.valueType == LayoutValue.ValueType.DEFAULT)
|
|
{
|
|
layoutValue.valueType = LayoutValue.ValueType.RATE;
|
|
layoutValue.value = 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// 콘텐츠 RectTransform의 현재 스크롤 위치를 주 축 기준으로 가져옵니다.
|
|
/// </summary>
|
|
public float GetAxisPosition(RectTransform content)
|
|
{
|
|
if (IsVertical() == true)
|
|
{
|
|
return topToBotton ? content.offsetMax.y : -content.offsetMin.y;
|
|
}
|
|
else
|
|
{
|
|
return leftToRight ? -content.offsetMin.x : content.offsetMax.x;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// 주어진 오프셋 값을 스크롤 방향에 맞는 위치 값으로 변환합니다.
|
|
/// </summary>
|
|
public float GetAxisPostionFromOffset(float offset)
|
|
{
|
|
if (IsVertical() == true)
|
|
{
|
|
return topToBotton ? offset : -offset;
|
|
}
|
|
else
|
|
{
|
|
return leftToRight ? -offset : offset;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// 스크롤 아이템의 주 축 방향 크기를 설정합니다.
|
|
/// </summary>
|
|
/// <param name="rectTransform">크기를 설정할 아이템의 RectTransform</param>
|
|
/// <param name="itemIndex">아이템의 인덱스 (현재 사용되지 않음)</param>
|
|
/// <param name="size">설정할 크기 값</param>
|
|
public void FitItemSize(RectTransform rectTransform, int itemIndex, float size)
|
|
{
|
|
rectTransform.sizeDelta = GetAxisVector(size);
|
|
}
|
|
|
|
/// <summary>
|
|
/// 그리드 레이아웃에서 각 항목이 교차 축(cross axis) 방향으로 올바른 위치에 배치되도록 `RectTransform`의 앵커(anchor)를 조절합니다.
|
|
/// 예를 들어, 수직 스크롤의 3열 그리드라면 각 항목이 1, 2, 3번째 열 중 어디에 위치할지를 계산합니다.
|
|
/// </summary>
|
|
public void FitItemInlinePosition(RectTransform rectTransform, int itemIndex, float crossSize)
|
|
{
|
|
float min = 0;
|
|
float max = 1;
|
|
if (IsGrid() == true)
|
|
{
|
|
float inlineMaxSize = 0;
|
|
foreach (LayoutValue layoutValue in values)
|
|
{
|
|
inlineMaxSize += layoutValue.value;
|
|
}
|
|
|
|
float crossSapce = space[IsVertical() ? 0 : 1];
|
|
if(crossSapce != 0)
|
|
{
|
|
crossSapce = crossSapce/ crossSize;
|
|
inlineMaxSize += (crossSapce * (values.Count - 1));
|
|
}
|
|
|
|
int inlineIndex = itemIndex % values.Count;
|
|
|
|
float inlinePos = 0;
|
|
float inlineSize = values[inlineIndex].value;
|
|
|
|
if (inlineMaxSize > 0 &&
|
|
inlineSize > 0)
|
|
{
|
|
for (int index = 0; index < inlineIndex; index++)
|
|
{
|
|
inlinePos += values[index].value + crossSapce;
|
|
}
|
|
inlinePos = inlinePos / inlineMaxSize;
|
|
inlineSize = inlineSize / inlineMaxSize;
|
|
|
|
if ((IsVertical() == true && leftToRight == false) ||
|
|
(IsVertical() == false && topToBotton == true))
|
|
{
|
|
inlinePos = 1 - inlinePos - inlineSize;
|
|
}
|
|
|
|
min = inlinePos;
|
|
max = inlinePos + inlineSize;
|
|
}
|
|
else
|
|
{
|
|
min = 0;
|
|
max = 0;
|
|
}
|
|
}
|
|
|
|
Vector2 anchorMin = rectTransform.anchorMin;
|
|
Vector2 anchorMax = rectTransform.anchorMax;
|
|
if (IsVertical() == true)
|
|
{
|
|
anchorMin.x = min;
|
|
anchorMax.x = max;
|
|
}
|
|
else
|
|
{
|
|
anchorMin.y = min;
|
|
anchorMax.y = max;
|
|
}
|
|
rectTransform.anchorMin = anchorMin;
|
|
rectTransform.anchorMax = anchorMax;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 그리드 레이아웃의 행 또는 열의 개수를 반환합니다.
|
|
/// </summary>
|
|
public int GridCount()
|
|
{
|
|
return values.Count;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 현재 레이아웃이 그리드인지 확인합니다. (행/열 개수가 2개 이상)
|
|
/// </summary>
|
|
public bool IsGrid()
|
|
{
|
|
if (GridCount() > 1)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 스크롤 아이템의 피벗(pivot)을 계산하여 반환합니다.
|
|
/// 아이템 정렬 방향(topToBottom, leftToRight)에 따라 결정됩니다.
|
|
/// </summary>
|
|
public Vector2 GetItemPivot()
|
|
{
|
|
return new Vector2(leftToRight ? 0.0f : 1.0f, topToBotton ? 1.0f : 0.0f);
|
|
}
|
|
|
|
/// <summary>
|
|
/// 스크롤 아이템의 앵커(anchor)를 계산하여 반환합니다.
|
|
/// 주 축 방향으로 앵커를 피벗 위치에 고정시켜 아이템이 해당 라인을 따라 배치되도록 합니다.
|
|
/// </summary>
|
|
public Rect GetItemAnchor()
|
|
{
|
|
Vector2 pivot = GetItemPivot();
|
|
|
|
Rect anchor = Rect.MinMaxRect(0, 0, 1, 1);
|
|
if (IsVertical() == true)
|
|
{
|
|
anchor.yMin = pivot[1];
|
|
anchor.yMax = pivot[1];
|
|
}
|
|
else
|
|
{
|
|
anchor.xMin = pivot[0];
|
|
anchor.xMax = pivot[0];
|
|
}
|
|
|
|
return anchor;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 주어진 값을 주 축 방향의 값으로 하는 Vector2를 생성합니다. 교차 축의 값은 0이 됩니다.
|
|
/// </summary>
|
|
public Vector2 GetAxisVector(float value)
|
|
{
|
|
return GetAxisVector(Vector2.zero, value);
|
|
}
|
|
|
|
/// <summary>
|
|
/// 주어진 Vector2에서 주 축에 해당하는 값을 새로운 값으로 설정하여 반환합니다.
|
|
/// </summary>
|
|
public Vector2 GetAxisVector(Vector2 vector, float value)
|
|
{
|
|
if (IsVertical() == true)
|
|
{
|
|
vector.y = value;
|
|
}
|
|
else
|
|
{
|
|
vector.x = value;
|
|
}
|
|
|
|
return vector;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 주어진 RectTransform에서 스크롤의 주 축(Main Axis)에 해당하는 크기를 반환합니다.
|
|
/// (예: 수직 스크롤이면 높이, 수평 스크롤이면 너비)
|
|
/// </summary>
|
|
public float GetMainSize(RectTransform transform)
|
|
{
|
|
return GetMainSize(transform.rect);
|
|
}
|
|
|
|
/// <summary>
|
|
/// 주어진 Rect에서 스크롤의 주 축에 해당하는 크기를 반환합니다.
|
|
/// </summary>
|
|
public float GetMainSize(Rect rect)
|
|
{
|
|
return IsVertical() == true ? rect.height : rect.width;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 주어진 Rect에서 스크롤의 교차 축(Cross Axis)에 해당하는 크기를 반환합니다.
|
|
/// (예: 수직 스크롤이면 너비, 수평 스크롤이면 높이)
|
|
/// </summary>
|
|
public float GetCrossSize(Rect rect)
|
|
{
|
|
return IsVertical() == true ? rect.width : rect.height;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 주어진 Vector2에서 스크롤의 주 축에 해당하는 크기를 반환합니다.
|
|
/// </summary>
|
|
public float GetMainSize(Vector2 delta)
|
|
{
|
|
return IsVertical() == true ? delta.y : delta.x;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 주어진 Vector2에서 스크롤의 교차 축에 해당하는 크기를 반환합니다.
|
|
/// </summary>
|
|
public float GetCrossSize(Vector2 delta)
|
|
{
|
|
return IsVertical() == true ? delta.x : delta.y;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 주 축에 해당하는 Vector2의 인덱스를 반환합니다. (x=0, y=1)
|
|
/// </summary>
|
|
public int GetMainIndex()
|
|
{
|
|
return IsVertical() ? 1 : 0;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 교차 축에 해당하는 Vector2의 인덱스를 반환합니다. (x=0, y=1)
|
|
/// </summary>
|
|
public int GetCrossIndex()
|
|
{
|
|
return IsVertical() ? 0 : 1;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 주 축 방향의 여백(Padding)입니다.
|
|
/// </summary>
|
|
public float MainPadding
|
|
{
|
|
get
|
|
{
|
|
return padding[GetMainIndex()];
|
|
}
|
|
|
|
set
|
|
{
|
|
padding[GetMainIndex()] = value;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// 교차 축 방향의 여백(Padding)입니다.
|
|
/// </summary>
|
|
public float CrossPadding
|
|
{
|
|
get
|
|
{
|
|
return padding[GetCrossIndex()];
|
|
}
|
|
|
|
set
|
|
{
|
|
padding[GetCrossIndex()] = value;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// 주 축 방향의 항목 간 간격(Space)입니다.
|
|
/// </summary>
|
|
public float MainSpace
|
|
{
|
|
get
|
|
{
|
|
return space[GetMainIndex()];
|
|
}
|
|
|
|
set
|
|
{
|
|
space[GetMainIndex()] = value;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// 교차 축 방향의 항목 간 간격(Space)입니다.
|
|
/// </summary>
|
|
public float CrossSpace
|
|
{
|
|
get
|
|
{
|
|
return space[GetCrossIndex()];
|
|
}
|
|
|
|
set
|
|
{
|
|
space[GetCrossIndex()] = value;
|
|
}
|
|
}
|
|
}
|
|
} |