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; } } } }