클래스 생성
This commit is contained in:
8
Assets/Scripts/UVC/UI/List/Accordion.meta
Normal file
8
Assets/Scripts/UVC/UI/List/Accordion.meta
Normal file
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 2b427b9e85de96047aa7cfd700a09a2e
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
145
Assets/Scripts/UVC/UI/List/Accordion/AccordionData.cs
Normal file
145
Assets/Scripts/UVC/UI/List/Accordion/AccordionData.cs
Normal file
@@ -0,0 +1,145 @@
|
||||
#nullable enable
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace UVC.UI.List.Accordion
|
||||
{
|
||||
/// <summary>
|
||||
/// 섹션 내 아이템 배치 타입.
|
||||
/// Horizontal: head-content-tail3영역 수평 배치
|
||||
/// Grid: 이미지+캡션2열 그리드
|
||||
/// </summary>
|
||||
public enum AccordionItemLayoutType
|
||||
{
|
||||
Horizontal,
|
||||
Grid
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 수평 아이템 영역에 들어갈 콘텐츠 종류.
|
||||
/// </summary>
|
||||
public enum AccordionContentKind
|
||||
{
|
||||
None,
|
||||
Image,
|
||||
Text,
|
||||
IconButton
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 수평 아이템의 단일 영역(head/content/tail)을 기술하는 데이터.
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
public class AccordionContentSpec
|
||||
{
|
||||
public AccordionContentKind Kind = AccordionContentKind.None;
|
||||
|
||||
// Text
|
||||
public string? Text;
|
||||
public UnityEngine.Object? Font; // TMP_FontAsset 또는 Font
|
||||
public Color TextColor = Color.white;
|
||||
public int FontSize = 22;
|
||||
|
||||
// Image
|
||||
public Sprite? Sprite;
|
||||
public Color ImageColor = Color.white;
|
||||
|
||||
// IconButton
|
||||
public Sprite? Icon;
|
||||
public Action? OnClick;
|
||||
public string? Tooltip;
|
||||
|
||||
public static AccordionContentSpec FromText(string text, Color? color = null, int fontSize = 22, UnityEngine.Object? font = null)
|
||||
=> new AccordionContentSpec
|
||||
{
|
||||
Kind = AccordionContentKind.Text,
|
||||
Text = text,
|
||||
TextColor = color ?? Color.white,
|
||||
FontSize = fontSize,
|
||||
Font = font
|
||||
};
|
||||
|
||||
public static AccordionContentSpec FromImage(Sprite? sprite, Color? color = null)
|
||||
=> new AccordionContentSpec
|
||||
{
|
||||
Kind = AccordionContentKind.Image,
|
||||
Sprite = sprite,
|
||||
ImageColor = color ?? Color.white
|
||||
};
|
||||
|
||||
public static AccordionContentSpec FromIconButton(Sprite? icon, Action onClick, string? tooltip = null)
|
||||
=> new AccordionContentSpec
|
||||
{
|
||||
Kind = AccordionContentKind.IconButton,
|
||||
Icon = icon,
|
||||
OnClick = onClick,
|
||||
Tooltip = tooltip
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 아코디언 항목 데이터 인터페이스.
|
||||
/// </summary>
|
||||
public interface IAccordionItemData
|
||||
{
|
||||
AccordionItemLayoutType LayoutType { get; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// head-content-tail 수평 아이템 데이터.
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
public class AccordionHorizontalItemData : IAccordionItemData
|
||||
{
|
||||
public AccordionItemLayoutType LayoutType => AccordionItemLayoutType.Horizontal;
|
||||
|
||||
public AccordionContentSpec Head = new AccordionContentSpec();
|
||||
public AccordionContentSpec Content = new AccordionContentSpec();
|
||||
public AccordionContentSpec Tail = new AccordionContentSpec();
|
||||
|
||||
/// <summary>
|
||||
/// 임의의 태그/키 값.
|
||||
/// </summary>
|
||||
public string? Tag;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 그리드 셀(이미지+캡션) 데이터.
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
public class AccordionGridItemData : IAccordionItemData
|
||||
{
|
||||
public AccordionItemLayoutType LayoutType => AccordionItemLayoutType.Grid;
|
||||
|
||||
public Sprite? Image;
|
||||
public string Caption = string.Empty;
|
||||
public Color CaptionColor = Color.white;
|
||||
public int CaptionSize = 20;
|
||||
public Action? OnClick;
|
||||
public string? Tag;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 단일 섹션 데이터(제목/펼침/아이템 컬렉션).
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
public class AccordionSectionData
|
||||
{
|
||||
public string Title = string.Empty;
|
||||
public bool IsExpanded = false;
|
||||
public AccordionItemLayoutType LayoutType = AccordionItemLayoutType.Horizontal;
|
||||
|
||||
// 아이템 리스트. LayoutType에 따라 타입을 일치시켜 사용합니다.
|
||||
public List<IAccordionItemData> Items = new List<IAccordionItemData>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 아코디언 전체 데이터 루트.
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
public class AccordionData
|
||||
{
|
||||
public List<AccordionSectionData> Sections = new List<AccordionSectionData>();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 29fe5d846fc771e418d6d0090157dbfd
|
||||
@@ -0,0 +1,65 @@
|
||||
#nullable enable
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
|
||||
namespace UVC.UI.List.Accordion
|
||||
{
|
||||
/// <summary>
|
||||
/// 그리드 셀 항목(이미지+캡션). 셀 크기는 상위 GridLayoutGroup에서 결정.
|
||||
/// </summary>
|
||||
public class AccordionGridItemView : MonoBehaviour
|
||||
{
|
||||
[SerializeField] private Image image = default!;
|
||||
[SerializeField] private Component caption = default!; // TMP or Text
|
||||
[SerializeField] private Button button = default!;
|
||||
|
||||
private void Reset()
|
||||
{
|
||||
EnsureLayout();
|
||||
}
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
EnsureLayout();
|
||||
}
|
||||
|
||||
private void EnsureLayout()
|
||||
{
|
||||
if (image == null)
|
||||
{
|
||||
var imgGO = new GameObject("Image", typeof(RectTransform), typeof(Image));
|
||||
imgGO.transform.SetParent(transform, false);
|
||||
image = imgGO.GetComponent<Image>();
|
||||
image.preserveAspect = true;
|
||||
}
|
||||
if (caption == null)
|
||||
{
|
||||
caption = TMPProxy.CreateTextGO(transform, "Caption");
|
||||
TMPProxy.SetAlignment(caption, "TopLeft");
|
||||
}
|
||||
if (button == null)
|
||||
{
|
||||
var btn = gameObject.GetComponent<Button>();
|
||||
if (btn == null) btn = gameObject.AddComponent<Button>();
|
||||
button = btn;
|
||||
}
|
||||
var v = GetComponent<VerticalLayoutGroup>();
|
||||
if (v == null) v = gameObject.AddComponent<VerticalLayoutGroup>();
|
||||
v.childControlHeight = true;
|
||||
v.childForceExpandHeight = true;
|
||||
v.spacing = 2f;
|
||||
var pad = 4;
|
||||
v.padding = new RectOffset(pad, pad, pad, pad);
|
||||
}
|
||||
|
||||
public void Initialize(AccordionGridItemData data)
|
||||
{
|
||||
image.sprite = data.Image;
|
||||
TMPProxy.SetText(caption, data.Caption);
|
||||
TMPProxy.SetColor(caption, data.CaptionColor);
|
||||
TMPProxy.SetFontSize(caption, data.CaptionSize);
|
||||
button.onClick.RemoveAllListeners();
|
||||
if (data.OnClick != null) button.onClick.AddListener(() => data.OnClick());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: bbb82949b2c1f134dbb493c71bbec615
|
||||
132
Assets/Scripts/UVC/UI/List/Accordion/AccordionItemView.cs
Normal file
132
Assets/Scripts/UVC/UI/List/Accordion/AccordionItemView.cs
Normal file
@@ -0,0 +1,132 @@
|
||||
#nullable enable
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
|
||||
namespace UVC.UI.List.Accordion
|
||||
{
|
||||
/// <summary>
|
||||
/// 수평 아이템(Head-Content-Tail) 뷰. 고정 높이35.
|
||||
/// 각 영역은 Text, Image, IconButton를 지원합니다.
|
||||
/// </summary>
|
||||
public class AccordionItemView : MonoBehaviour
|
||||
{
|
||||
[SerializeField] private RectTransform headRoot = default!;
|
||||
[SerializeField] private RectTransform contentRoot = default!;
|
||||
[SerializeField] private RectTransform tailRoot = default!;
|
||||
|
||||
private void Reset()
|
||||
{
|
||||
EnsureLayout();
|
||||
}
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
EnsureLayout();
|
||||
}
|
||||
|
||||
private void EnsureLayout()
|
||||
{
|
||||
var rt = (RectTransform)transform;
|
||||
rt.sizeDelta = new Vector2(AccordionList.Width, AccordionList.ItemHeight);
|
||||
var h = GetComponent<HorizontalLayoutGroup>();
|
||||
if (h == null) h = gameObject.AddComponent<HorizontalLayoutGroup>();
|
||||
h.childControlHeight = true;
|
||||
h.childForceExpandHeight = true;
|
||||
h.childControlWidth = true;
|
||||
h.childForceExpandWidth = true;
|
||||
h.spacing = 4f;
|
||||
h.padding = new RectOffset(6, 6, 0, 0);
|
||||
|
||||
if (headRoot == null)
|
||||
{
|
||||
headRoot = CreateSlot("Head", 0.25f);
|
||||
}
|
||||
if (contentRoot == null)
|
||||
{
|
||||
contentRoot = CreateSlot("Content", 0.5f);
|
||||
}
|
||||
if (tailRoot == null)
|
||||
{
|
||||
tailRoot = CreateSlot("Tail", 0.25f);
|
||||
}
|
||||
}
|
||||
|
||||
private RectTransform CreateSlot(string name, float flexible)
|
||||
{
|
||||
var go = new GameObject(name, typeof(RectTransform));
|
||||
go.transform.SetParent(transform, false);
|
||||
var layout = go.AddComponent<LayoutElement>();
|
||||
layout.flexibleWidth = flexible;
|
||||
layout.minHeight = AccordionList.ItemHeight;
|
||||
var rt = (RectTransform)go.transform;
|
||||
return rt;
|
||||
}
|
||||
|
||||
public void Initialize(AccordionHorizontalItemData data)
|
||||
{
|
||||
BuildArea(headRoot, data.Head);
|
||||
BuildArea(contentRoot, data.Content);
|
||||
BuildArea(tailRoot, data.Tail);
|
||||
}
|
||||
|
||||
private void BuildArea(RectTransform parent, AccordionContentSpec spec)
|
||||
{
|
||||
// clear
|
||||
for (int i = parent.childCount - 1; i >= 0; i--)
|
||||
{
|
||||
Destroy(parent.GetChild(i).gameObject);
|
||||
}
|
||||
switch (spec.Kind)
|
||||
{
|
||||
case AccordionContentKind.Text:
|
||||
BuildText(parent, spec.Text ?? string.Empty, spec.Font, spec.TextColor, spec.FontSize);
|
||||
break;
|
||||
case AccordionContentKind.Image:
|
||||
BuildImage(parent, spec.Sprite, spec.ImageColor);
|
||||
break;
|
||||
case AccordionContentKind.IconButton:
|
||||
BuildIconButton(parent, spec.Icon, spec.OnClick);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void BuildText(RectTransform parent, string text, UnityEngine.Object? fontAsset, Color color, int size)
|
||||
{
|
||||
var comp = TMPProxy.CreateTextGO(parent, "Text");
|
||||
TMPProxy.SetText(comp, text);
|
||||
TMPProxy.SetColor(comp, color);
|
||||
TMPProxy.SetFontSize(comp, size);
|
||||
TMPProxy.SetFont(comp, fontAsset);
|
||||
TMPProxy.SetAlignment(comp, "MidlineLeft");
|
||||
}
|
||||
|
||||
private void BuildImage(RectTransform parent, Sprite? sprite, Color color)
|
||||
{
|
||||
var go = new GameObject("Image", typeof(RectTransform), typeof(Image));
|
||||
go.transform.SetParent(parent, false);
|
||||
var img = go.GetComponent<Image>();
|
||||
img.sprite = sprite;
|
||||
img.color = color;
|
||||
img.preserveAspect = true;
|
||||
var le = go.AddComponent<LayoutElement>();
|
||||
le.minHeight = AccordionList.ItemHeight;
|
||||
}
|
||||
|
||||
private void BuildIconButton(RectTransform parent, Sprite? icon, System.Action? onClick)
|
||||
{
|
||||
var go = new GameObject("IconButton", typeof(RectTransform), typeof(Image), typeof(Button));
|
||||
go.transform.SetParent(parent, false);
|
||||
var img = go.GetComponent<Image>();
|
||||
img.sprite = icon;
|
||||
img.preserveAspect = true;
|
||||
var btn = go.GetComponent<Button>();
|
||||
btn.onClick.RemoveAllListeners();
|
||||
if (onClick != null) btn.onClick.AddListener(() => onClick());
|
||||
var le = go.AddComponent<LayoutElement>();
|
||||
le.minHeight = AccordionList.ItemHeight;
|
||||
le.minWidth = AccordionList.ItemHeight;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 3c7296b1c3a691547ac7714fde688237
|
||||
124
Assets/Scripts/UVC/UI/List/Accordion/AccordionList.cs
Normal file
124
Assets/Scripts/UVC/UI/List/Accordion/AccordionList.cs
Normal file
@@ -0,0 +1,124 @@
|
||||
#nullable enable
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
|
||||
namespace UVC.UI.List.Accordion
|
||||
{
|
||||
/// <summary>
|
||||
/// 아코디언 전체 리스트 뷰. 섹션 헤더(48)와 항목(35)을 생성/관리합니다.
|
||||
/// 가로 너비는250으로 고정합니다.
|
||||
/// </summary>
|
||||
[DisallowMultipleComponent]
|
||||
public class AccordionList : MonoBehaviour
|
||||
{
|
||||
// Layout constants
|
||||
public const float Width = 250f;
|
||||
public const float HeaderHeight = 48f;
|
||||
public const float ItemHeight = 35f;
|
||||
public const int GridColumns = 2;
|
||||
|
||||
[Header("Prefabs")]
|
||||
[SerializeField] private AccordionSection sectionPrefab = default!;
|
||||
[SerializeField] private AccordionItemView horizontalItemPrefab = default!;
|
||||
[SerializeField] private AccordionGridItemView gridItemPrefab = default!;
|
||||
|
||||
[Header("Root")]
|
||||
[SerializeField] private RectTransform contentRoot = default!; // VerticalLayoutGroup 권장
|
||||
|
||||
[Header("Style")]
|
||||
[SerializeField] private Color sectionHeaderColor = new Color(0.16f, 0.16f, 0.16f);
|
||||
[SerializeField] private Color itemBgColor = new Color(0.10f, 0.10f, 0.10f);
|
||||
|
||||
public AccordionData? Data { get; private set; }
|
||||
private readonly List<AccordionSection> _sections = new();
|
||||
|
||||
private void Reset()
|
||||
{
|
||||
EnsureRootLayout();
|
||||
EnsurePrefabs();
|
||||
}
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
EnsureRootLayout();
|
||||
EnsurePrefabs();
|
||||
}
|
||||
|
||||
private void EnsureRootLayout()
|
||||
{
|
||||
if (contentRoot == null)
|
||||
{
|
||||
var go = new GameObject("ContentRoot", typeof(RectTransform));
|
||||
go.transform.SetParent(transform, false);
|
||||
contentRoot = (RectTransform)go.transform;
|
||||
}
|
||||
|
||||
var rt = (RectTransform)transform;
|
||||
rt.sizeDelta = new Vector2(Width, rt.sizeDelta.y);
|
||||
|
||||
var v = contentRoot.GetComponent<VerticalLayoutGroup>();
|
||||
if (v == null) v = contentRoot.gameObject.AddComponent<VerticalLayoutGroup>();
|
||||
v.childForceExpandHeight = false;
|
||||
v.childControlHeight = true;
|
||||
v.childForceExpandWidth = true;
|
||||
v.childControlWidth = true;
|
||||
v.spacing = 0f;
|
||||
|
||||
var fitter = contentRoot.GetComponent<ContentSizeFitter>();
|
||||
if (fitter == null) fitter = contentRoot.gameObject.AddComponent<ContentSizeFitter>();
|
||||
fitter.verticalFit = ContentSizeFitter.FitMode.PreferredSize;
|
||||
fitter.horizontalFit = ContentSizeFitter.FitMode.Unconstrained;
|
||||
}
|
||||
|
||||
private void EnsurePrefabs()
|
||||
{
|
||||
if (sectionPrefab == null)
|
||||
{
|
||||
var proto = new GameObject("SectionPrototype", typeof(RectTransform), typeof(AccordionSection));
|
||||
proto.transform.SetParent(transform, false);
|
||||
proto.SetActive(false);
|
||||
sectionPrefab = proto.GetComponent<AccordionSection>();
|
||||
}
|
||||
if (horizontalItemPrefab == null)
|
||||
{
|
||||
var proto = new GameObject("HorizontalItemPrototype", typeof(RectTransform), typeof(AccordionItemView));
|
||||
proto.transform.SetParent(transform, false);
|
||||
proto.SetActive(false);
|
||||
horizontalItemPrefab = proto.GetComponent<AccordionItemView>();
|
||||
}
|
||||
if (gridItemPrefab == null)
|
||||
{
|
||||
var proto = new GameObject("GridItemPrototype", typeof(RectTransform), typeof(AccordionGridItemView));
|
||||
proto.transform.SetParent(transform, false);
|
||||
proto.SetActive(false);
|
||||
gridItemPrefab = proto.GetComponent<AccordionGridItemView>();
|
||||
}
|
||||
}
|
||||
|
||||
public void SetData(AccordionData data)
|
||||
{
|
||||
EnsureRootLayout();
|
||||
EnsurePrefabs();
|
||||
Clear();
|
||||
Data = data;
|
||||
foreach (var sectionData in data.Sections)
|
||||
{
|
||||
var section = Instantiate(sectionPrefab, contentRoot);
|
||||
section.gameObject.SetActive(true);
|
||||
section.Initialize(sectionData, this, horizontalItemPrefab, gridItemPrefab);
|
||||
_sections.Add(section);
|
||||
}
|
||||
}
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
foreach (var s in _sections)
|
||||
{
|
||||
if (s != null) Destroy(s.gameObject);
|
||||
}
|
||||
_sections.Clear();
|
||||
Data = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 3da2a3205a856104cbeff1d2a234d19f
|
||||
158
Assets/Scripts/UVC/UI/List/Accordion/AccordionSection.cs
Normal file
158
Assets/Scripts/UVC/UI/List/Accordion/AccordionSection.cs
Normal file
@@ -0,0 +1,158 @@
|
||||
#nullable enable
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
|
||||
namespace UVC.UI.List.Accordion
|
||||
{
|
||||
/// <summary>
|
||||
/// 단일 섹션 헤더(48높이)와 본문을 관리합니다.
|
||||
/// </summary>
|
||||
public class AccordionSection : MonoBehaviour
|
||||
{
|
||||
[SerializeField] private Button headerButton = default!;
|
||||
[SerializeField] private Image headerBg = default!;
|
||||
[SerializeField] private Component titleText = default!; // TMP or Text
|
||||
[SerializeField] private RectTransform bodyRoot = default!; // items parent
|
||||
[SerializeField] private GameObject bodyContainer = default!; // active toggle
|
||||
|
||||
private AccordionSectionData? _data;
|
||||
private AccordionList? _list;
|
||||
private AccordionItemView? _horizontalItemPrefab;
|
||||
private AccordionGridItemView? _gridItemPrefab;
|
||||
|
||||
private void Reset()
|
||||
{
|
||||
EnsureRuntimeComponents();
|
||||
}
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
EnsureRuntimeComponents();
|
||||
}
|
||||
|
||||
private void EnsureRuntimeComponents()
|
||||
{
|
||||
var rt = (RectTransform)transform;
|
||||
if (headerButton == null)
|
||||
{
|
||||
var headerGO = new GameObject("Header", typeof(RectTransform), typeof(Image), typeof(Button));
|
||||
headerGO.transform.SetParent(transform, false);
|
||||
var headerRT = (RectTransform)headerGO.transform;
|
||||
headerRT.anchorMin = new Vector2(0, 1);
|
||||
headerRT.anchorMax = new Vector2(1, 1);
|
||||
headerRT.pivot = new Vector2(0.5f, 1);
|
||||
headerRT.sizeDelta = new Vector2(0, AccordionList.HeaderHeight);
|
||||
headerBg = headerGO.GetComponent<Image>();
|
||||
headerButton = headerGO.GetComponent<Button>();
|
||||
var textComp = TMPProxy.CreateTextGO(headerGO.transform, "Title");
|
||||
var textRT = (RectTransform)textComp.transform;
|
||||
textRT.anchorMin = new Vector2(0, 0);
|
||||
textRT.anchorMax = new Vector2(1, 1);
|
||||
textRT.offsetMin = new Vector2(12, 0);
|
||||
textRT.offsetMax = new Vector2(-12, 0);
|
||||
titleText = textComp;
|
||||
TMPProxy.SetAlignment(titleText, "MidlineLeft");
|
||||
}
|
||||
if (bodyContainer == null)
|
||||
{
|
||||
var bodyGO = new GameObject("BodyContainer", typeof(RectTransform));
|
||||
bodyGO.transform.SetParent(transform, false);
|
||||
bodyContainer = bodyGO;
|
||||
bodyRoot = (RectTransform)bodyGO.transform;
|
||||
var v = bodyGO.AddComponent<VerticalLayoutGroup>();
|
||||
v.childControlHeight = true;
|
||||
v.childForceExpandHeight = false;
|
||||
v.spacing = 0f;
|
||||
var fitter = bodyGO.AddComponent<ContentSizeFitter>();
|
||||
fitter.verticalFit = ContentSizeFitter.FitMode.PreferredSize;
|
||||
}
|
||||
var layout = GetComponent<VerticalLayoutGroup>();
|
||||
if (layout == null) layout = gameObject.AddComponent<VerticalLayoutGroup>();
|
||||
layout.childControlHeight = true;
|
||||
layout.childForceExpandHeight = false;
|
||||
layout.spacing = 0f;
|
||||
}
|
||||
|
||||
public void Initialize(AccordionSectionData data, AccordionList list, AccordionItemView horizontalItemPrefab, AccordionGridItemView gridItemPrefab)
|
||||
{
|
||||
_data = data;
|
||||
_list = list;
|
||||
_horizontalItemPrefab = horizontalItemPrefab;
|
||||
_gridItemPrefab = gridItemPrefab;
|
||||
|
||||
// Appearance
|
||||
var rt = (RectTransform)transform;
|
||||
rt.sizeDelta = new Vector2(AccordionList.Width, rt.sizeDelta.y);
|
||||
|
||||
headerBg.color = new Color(0.18f, 0.18f, 0.18f);
|
||||
TMPProxy.SetText(titleText, data.Title);
|
||||
|
||||
headerButton.onClick.RemoveAllListeners();
|
||||
headerButton.onClick.AddListener(() => Toggle());
|
||||
|
||||
bodyContainer.SetActive(_data.IsExpanded);
|
||||
|
||||
BuildItems();
|
||||
}
|
||||
|
||||
private void Toggle()
|
||||
{
|
||||
if (_data == null) return;
|
||||
_data.IsExpanded = !_data.IsExpanded;
|
||||
bodyContainer.SetActive(_data.IsExpanded);
|
||||
}
|
||||
|
||||
private void ClearItems()
|
||||
{
|
||||
for (int i = bodyRoot.childCount - 1; i >= 0; i--)
|
||||
{
|
||||
var ch = bodyRoot.GetChild(i);
|
||||
Destroy(ch.gameObject);
|
||||
}
|
||||
}
|
||||
|
||||
private void BuildItems()
|
||||
{
|
||||
if (_data == null || _list == null) return;
|
||||
ClearItems();
|
||||
|
||||
if (_data.LayoutType == AccordionItemLayoutType.Horizontal)
|
||||
{
|
||||
foreach (var item in _data.Items)
|
||||
{
|
||||
if (item is AccordionHorizontalItemData h)
|
||||
{
|
||||
var view = Object.Instantiate(_horizontalItemPrefab!, bodyRoot);
|
||||
view.Initialize(h);
|
||||
}
|
||||
}
|
||||
}
|
||||
else // Grid
|
||||
{
|
||||
// Wrap to2 columns using GridLayoutGroup
|
||||
var containerGO = new GameObject("Grid", typeof(RectTransform), typeof(GridLayoutGroup), typeof(ContentSizeFitter));
|
||||
containerGO.transform.SetParent(bodyRoot, false);
|
||||
var gridRT = (RectTransform)containerGO.transform;
|
||||
gridRT.sizeDelta = new Vector2(AccordionList.Width, gridRT.sizeDelta.y);
|
||||
var grid = containerGO.GetComponent<GridLayoutGroup>();
|
||||
float padding = 6f;
|
||||
grid.cellSize = new Vector2((AccordionList.Width - padding * 3) / 2f, AccordionList.ItemHeight * 2f);
|
||||
grid.spacing = new Vector2(padding, padding);
|
||||
grid.constraint = GridLayoutGroup.Constraint.FixedColumnCount;
|
||||
grid.constraintCount = AccordionList.GridColumns;
|
||||
var fitter = containerGO.GetComponent<ContentSizeFitter>();
|
||||
fitter.verticalFit = ContentSizeFitter.FitMode.PreferredSize;
|
||||
fitter.horizontalFit = ContentSizeFitter.FitMode.PreferredSize;
|
||||
|
||||
foreach (var item in _data.Items)
|
||||
{
|
||||
if (item is AccordionGridItemData g)
|
||||
{
|
||||
var view = Object.Instantiate(_gridItemPrefab!, gridRT);
|
||||
view.Initialize(g);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 17ca068f38574f84a969ad833b66e699
|
||||
105
Assets/Scripts/UVC/UI/List/Accordion/TMPProxy.cs
Normal file
105
Assets/Scripts/UVC/UI/List/Accordion/TMPProxy.cs
Normal file
@@ -0,0 +1,105 @@
|
||||
#nullable enable
|
||||
using System;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
|
||||
namespace UVC.UI.List.Accordion
|
||||
{
|
||||
internal static class TMPProxy
|
||||
{
|
||||
private static Type? _tmpTextType;
|
||||
private static Type? _tmpFontType;
|
||||
private static Type? _tmpAlignType;
|
||||
|
||||
private static Type? TMPTextType => _tmpTextType ??= Type.GetType("TMPro.TextMeshProUGUI, Unity.TextMeshPro");
|
||||
private static Type? TMPFontType => _tmpFontType ??= Type.GetType("TMPro.TMP_FontAsset, Unity.TextMeshPro");
|
||||
private static Type? TMPAlignType => _tmpAlignType ??= Type.GetType("TMPro.TextAlignmentOptions, Unity.TextMeshPro");
|
||||
|
||||
public static Component CreateTextGO(Transform parent, string name)
|
||||
{
|
||||
var go = new GameObject(name, typeof(RectTransform));
|
||||
go.transform.SetParent(parent, false);
|
||||
if (TMPTextType != null)
|
||||
{
|
||||
go.AddComponent(TMPTextType);
|
||||
return (Component)go.GetComponent(TMPTextType);
|
||||
}
|
||||
else
|
||||
{
|
||||
var text = go.AddComponent<Text>();
|
||||
text.alignment = TextAnchor.MiddleLeft;
|
||||
text.text = string.Empty;
|
||||
return text;
|
||||
}
|
||||
}
|
||||
|
||||
public static void SetText(Component comp, string text)
|
||||
{
|
||||
if (TMPTextType != null && TMPTextType.IsInstanceOfType(comp))
|
||||
{
|
||||
TMPTextType.GetProperty("text")?.SetValue(comp, text);
|
||||
}
|
||||
else if (comp is Text uText)
|
||||
{
|
||||
uText.text = text;
|
||||
}
|
||||
}
|
||||
|
||||
public static void SetColor(Component comp, Color color)
|
||||
{
|
||||
if (TMPTextType != null && TMPTextType.IsInstanceOfType(comp))
|
||||
{
|
||||
TMPTextType.GetProperty("color")?.SetValue(comp, color);
|
||||
}
|
||||
else if (comp is Text uText)
|
||||
{
|
||||
uText.color = color;
|
||||
}
|
||||
}
|
||||
|
||||
public static void SetFontSize(Component comp, int size)
|
||||
{
|
||||
if (TMPTextType != null && TMPTextType.IsInstanceOfType(comp))
|
||||
{
|
||||
TMPTextType.GetProperty("fontSize")?.SetValue(comp, (float)size);
|
||||
}
|
||||
else if (comp is Text uText)
|
||||
{
|
||||
uText.fontSize = size;
|
||||
}
|
||||
}
|
||||
|
||||
public static void SetFont(Component comp, UnityEngine.Object? fontAsset)
|
||||
{
|
||||
if (fontAsset == null) return;
|
||||
if (TMPTextType != null && TMPTextType.IsInstanceOfType(comp) && TMPFontType != null && TMPFontType.IsInstanceOfType(fontAsset))
|
||||
{
|
||||
TMPTextType.GetProperty("font")?.SetValue(comp, fontAsset);
|
||||
}
|
||||
else if (comp is Text uText && fontAsset is Font uFont)
|
||||
{
|
||||
uText.font = uFont;
|
||||
}
|
||||
}
|
||||
|
||||
public static void SetAlignment(Component comp, string alignment)
|
||||
{
|
||||
if (TMPTextType != null && TMPTextType.IsInstanceOfType(comp) && TMPAlignType != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
var enumVal = Enum.Parse(TMPAlignType, alignment);
|
||||
TMPTextType.GetProperty("alignment")?.SetValue(comp, enumVal);
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
else if (comp is Text uText)
|
||||
{
|
||||
if (string.Equals(alignment, "TopLeft", StringComparison.OrdinalIgnoreCase))
|
||||
uText.alignment = TextAnchor.UpperLeft;
|
||||
else
|
||||
uText.alignment = TextAnchor.MiddleLeft;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
2
Assets/Scripts/UVC/UI/List/Accordion/TMPProxy.cs.meta
Normal file
2
Assets/Scripts/UVC/UI/List/Accordion/TMPProxy.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d2968af399993f94f8aa265253691829
|
||||
68
Assets/Scripts/UVC/UI/Window/AccordionWindow.cs
Normal file
68
Assets/Scripts/UVC/UI/Window/AccordionWindow.cs
Normal file
@@ -0,0 +1,68 @@
|
||||
#nullable enable
|
||||
using System;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
using UVC.UI.List.Accordion;
|
||||
|
||||
namespace UVC.UI.Window
|
||||
{
|
||||
public class AccordionWindow : MonoBehaviour
|
||||
{
|
||||
[SerializeField] private RectTransform root = default!;
|
||||
[SerializeField] private AccordionList? accordionListPrefab = null;
|
||||
private AccordionList? _instance;
|
||||
|
||||
private void Reset()
|
||||
{
|
||||
EnsureRoot();
|
||||
EnsureInstance();
|
||||
}
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
EnsureRoot();
|
||||
EnsureInstance();
|
||||
}
|
||||
|
||||
private void EnsureRoot()
|
||||
{
|
||||
if (root == null)
|
||||
{
|
||||
var go = new GameObject("AccordionRoot", typeof(RectTransform));
|
||||
go.transform.SetParent(transform, false);
|
||||
root = (RectTransform)go.transform;
|
||||
var v = go.AddComponent<VerticalLayoutGroup>();
|
||||
v.childControlHeight = true;
|
||||
v.childForceExpandHeight = false;
|
||||
v.spacing = 0f;
|
||||
var fitter = go.AddComponent<ContentSizeFitter>();
|
||||
fitter.verticalFit = ContentSizeFitter.FitMode.PreferredSize;
|
||||
}
|
||||
}
|
||||
|
||||
private void EnsureInstance()
|
||||
{
|
||||
if (_instance == null)
|
||||
{
|
||||
if (accordionListPrefab != null)
|
||||
{
|
||||
_instance = Instantiate(accordionListPrefab, root);
|
||||
_instance.gameObject.SetActive(true);
|
||||
}
|
||||
else
|
||||
{
|
||||
var go = new GameObject("AccordionList", typeof(RectTransform), typeof(AccordionList));
|
||||
go.transform.SetParent(root, false);
|
||||
_instance = go.GetComponent<AccordionList>();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void SetData(AccordionData data)
|
||||
{
|
||||
EnsureRoot();
|
||||
EnsureInstance();
|
||||
_instance!.SetData(data);
|
||||
}
|
||||
}
|
||||
}
|
||||
2
Assets/Scripts/UVC/UI/Window/AccordionWindow.cs.meta
Normal file
2
Assets/Scripts/UVC/UI/Window/AccordionWindow.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 6ad50e65d5080f04c99dd7606a8a4d70
|
||||
@@ -24,9 +24,9 @@ namespace UVC.UI.Window
|
||||
/// <example>
|
||||
/// <![CDATA[
|
||||
/// // 외부에서 창을 참조했다고 가정
|
||||
/// hierarchyWindow.OnItemSelected += item => Debug.Log($"Selected: {item.Name}");
|
||||
/// hierarchyWindow.AddItem(new TreeListItemData("Root A"));
|
||||
/// hierarchyWindow.AddItemAt(new TreeListItemData("Root B"),0);
|
||||
/// accordionWindow.OnItemSelected += item => Debug.Log($"Selected: {item.Name}");
|
||||
/// accordionWindow.AddItem(new TreeListItemData("Root A"));
|
||||
/// accordionWindow.AddItemAt(new TreeListItemData("Root B"),0);
|
||||
/// ]]>
|
||||
/// </example>
|
||||
/// </summary>
|
||||
|
||||
Reference in New Issue
Block a user