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