218 lines
7.9 KiB
C#
218 lines
7.9 KiB
C#
#nullable enable
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using UnityEngine;
|
|
using UnityEngine.UIElements;
|
|
|
|
namespace UVC.UIToolkit
|
|
{
|
|
/// <summary>
|
|
/// 리스트 뷰 컴포넌트.
|
|
/// Unity ListView를 래핑하여 커스텀 스타일을 적용합니다.
|
|
/// 가상화(Virtualization)를 지원하여 대량의 데이터도 효율적으로 표시합니다.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// <para><b>ListView(리스트 뷰)란?</b></para>
|
|
/// <para>
|
|
/// ListView는 여러 항목을 세로로 나열하여 표시하는 UI 컴포넌트입니다.
|
|
/// 파일 목록, 연락처, 설정 항목 등 반복되는 데이터를 표시할 때 사용합니다.
|
|
/// </para>
|
|
///
|
|
/// <para><b>가상화(Virtualization)란?</b></para>
|
|
/// <para>
|
|
/// 수천 개의 항목이 있어도 화면에 보이는 항목만 실제로 렌더링하는 기술입니다.
|
|
/// 스크롤 시 보이지 않는 항목은 메모리에서 재활용되어 성능이 유지됩니다.
|
|
/// 이를 위해 <c>makeItem</c>(요소 생성)과 <c>bindItem</c>(데이터 바인딩)을 분리합니다.
|
|
/// </para>
|
|
///
|
|
/// <para><b>필수 설정:</b></para>
|
|
/// <list type="number">
|
|
/// <item><description><c>itemsSource</c> - 표시할 데이터 컬렉션 (IList)</description></item>
|
|
/// <item><description><c>makeItem</c> - 항목 VisualElement 생성 함수</description></item>
|
|
/// <item><description><c>bindItem</c> - 데이터를 요소에 바인딩하는 함수</description></item>
|
|
/// </list>
|
|
///
|
|
/// <para><b>주요 속성:</b></para>
|
|
/// <list type="bullet">
|
|
/// <item><description><c>fixedItemHeight</c> - 항목 높이 (고정 시 성능 향상)</description></item>
|
|
/// <item><description><c>selectionType</c> - 선택 모드 (None, Single, Multiple)</description></item>
|
|
/// <item><description><c>selectedIndex</c> - 현재 선택된 인덱스</description></item>
|
|
/// <item><description><c>showBorder</c> - 테두리 표시 여부</description></item>
|
|
/// </list>
|
|
///
|
|
/// <para><b>주요 이벤트:</b></para>
|
|
/// <list type="bullet">
|
|
/// <item><description><c>OnItemSelected</c> - 항목 선택 시 (인덱스 전달)</description></item>
|
|
/// <item><description><c>OnItemDoubleClicked</c> - 항목 더블클릭 시 (인덱스 전달)</description></item>
|
|
/// </list>
|
|
///
|
|
/// <para><b>실제 활용 예시:</b></para>
|
|
/// <list type="bullet">
|
|
/// <item><description>파일 탐색기 - 파일/폴더 목록</description></item>
|
|
/// <item><description>설정 화면 - 설정 항목 목록</description></item>
|
|
/// <item><description>채팅 앱 - 대화 목록</description></item>
|
|
/// <item><description>게임 인벤토리 - 아이템 목록</description></item>
|
|
/// <item><description>데이터 테이블 - 행 단위 데이터</description></item>
|
|
/// </list>
|
|
/// </remarks>
|
|
/// <example>
|
|
/// <para><b>C# 코드에서 사용:</b></para>
|
|
/// <code>
|
|
/// // 리스트 뷰 생성
|
|
/// var listView = new UTKListView();
|
|
/// listView.fixedItemHeight = 30; // 항목 높이 고정 (성능 향상)
|
|
///
|
|
/// // 데이터 소스 설정
|
|
/// var items = new List<string> { "항목 1", "항목 2", "항목 3" };
|
|
/// listView.itemsSource = items;
|
|
///
|
|
/// // 항목 생성 함수 (가상화를 위해 재사용됨)
|
|
/// listView.makeItem = () => new Label();
|
|
///
|
|
/// // 데이터 바인딩 함수 (스크롤 시 호출됨)
|
|
/// listView.bindItem = (element, index) => {
|
|
/// (element as Label).text = items[index];
|
|
/// };
|
|
///
|
|
/// // 선택 이벤트
|
|
/// listView.OnItemSelected += (index) => {
|
|
/// Debug.Log($"선택: {items[index]}");
|
|
/// };
|
|
///
|
|
/// // 더블클릭 이벤트 (파일 열기 등)
|
|
/// listView.OnItemDoubleClicked += (index) => {
|
|
/// OpenFile(items[index]);
|
|
/// };
|
|
///
|
|
/// // 복잡한 항목 구조
|
|
/// listView.makeItem = () => {
|
|
/// var container = new VisualElement();
|
|
/// container.Add(new Label { name = "title" });
|
|
/// container.Add(new Label { name = "subtitle" });
|
|
/// return container;
|
|
/// };
|
|
///
|
|
/// listView.bindItem = (element, index) => {
|
|
/// var data = myDataList[index];
|
|
/// element.Q<Label>("title").text = data.Title;
|
|
/// element.Q<Label>("subtitle").text = data.Subtitle;
|
|
/// };
|
|
/// </code>
|
|
/// <para><b>UXML에서 사용:</b></para>
|
|
/// <code>
|
|
/// <!-- 기본 리스트 뷰 -->
|
|
/// <utk:UTKListView fixed-item-height="30" selection-type="Single" />
|
|
///
|
|
/// <!-- 다중 선택 가능 -->
|
|
/// <utk:UTKListView fixed-item-height="40" selection-type="Multiple" />
|
|
///
|
|
/// <!-- 테두리 표시 -->
|
|
/// <utk:UTKListView show-border="true" />
|
|
/// </code>
|
|
/// </example>
|
|
[UxmlElement]
|
|
public partial class UTKListView : ListView, IDisposable
|
|
{
|
|
#region Constants
|
|
private const string USS_PATH = "UIToolkit/List/UTKListView";
|
|
#endregion
|
|
|
|
#region Fields
|
|
private bool _disposed;
|
|
#endregion
|
|
|
|
#region Events
|
|
/// <summary>아이템 선택 이벤트</summary>
|
|
public event Action<int>? OnItemSelected;
|
|
/// <summary>아이템 더블클릭 이벤트</summary>
|
|
public event Action<int>? OnItemDoubleClicked;
|
|
#endregion
|
|
|
|
#region Constructor
|
|
public UTKListView() : base()
|
|
{
|
|
UTKThemeManager.Instance.ApplyThemeToElement(this);
|
|
|
|
var uss = Resources.Load<StyleSheet>(USS_PATH);
|
|
if (uss != null)
|
|
{
|
|
styleSheets.Add(uss);
|
|
}
|
|
else
|
|
{
|
|
Debug.LogWarning($"[UTKListView] Failed to load USS: {USS_PATH}");
|
|
}
|
|
|
|
SetupStyles();
|
|
SetupEvents();
|
|
SubscribeToThemeChanges();
|
|
}
|
|
#endregion
|
|
|
|
#region Setup
|
|
private void SetupStyles()
|
|
{
|
|
AddToClassList("utk-listview");
|
|
}
|
|
|
|
private void SetupEvents()
|
|
{
|
|
selectionChanged += OnSelectionChanged;
|
|
itemsChosen += OnItemsChosen;
|
|
}
|
|
|
|
private void SubscribeToThemeChanges()
|
|
{
|
|
UTKThemeManager.Instance.OnThemeChanged += OnThemeChanged;
|
|
RegisterCallback<AttachToPanelEvent>(OnAttachToPanelForTheme);
|
|
RegisterCallback<DetachFromPanelEvent>(OnDetachFromPanelForTheme);
|
|
}
|
|
|
|
private void OnAttachToPanelForTheme(AttachToPanelEvent evt)
|
|
{
|
|
UTKThemeManager.Instance.OnThemeChanged -= OnThemeChanged;
|
|
UTKThemeManager.Instance.OnThemeChanged += OnThemeChanged;
|
|
UTKThemeManager.Instance.ApplyThemeToElement(this);
|
|
}
|
|
|
|
private void OnDetachFromPanelForTheme(DetachFromPanelEvent evt)
|
|
{
|
|
UTKThemeManager.Instance.OnThemeChanged -= OnThemeChanged;
|
|
}
|
|
|
|
private void OnThemeChanged(UTKTheme theme)
|
|
{
|
|
UTKThemeManager.Instance.ApplyThemeToElement(this);
|
|
}
|
|
#endregion
|
|
|
|
#region Event Handlers
|
|
private void OnSelectionChanged(IEnumerable<object> items)
|
|
{
|
|
OnItemSelected?.Invoke(selectedIndex);
|
|
}
|
|
|
|
private void OnItemsChosen(IEnumerable<object> items)
|
|
{
|
|
OnItemDoubleClicked?.Invoke(selectedIndex);
|
|
}
|
|
#endregion
|
|
|
|
#region IDisposable
|
|
public void Dispose()
|
|
{
|
|
if (_disposed) return;
|
|
_disposed = true;
|
|
|
|
selectionChanged -= OnSelectionChanged;
|
|
itemsChosen -= OnItemsChosen;
|
|
UTKThemeManager.Instance.OnThemeChanged -= OnThemeChanged;
|
|
UnregisterCallback<AttachToPanelEvent>(OnAttachToPanelForTheme);
|
|
UnregisterCallback<DetachFromPanelEvent>(OnDetachFromPanelForTheme);
|
|
OnItemSelected = null;
|
|
OnItemDoubleClicked = null;
|
|
}
|
|
#endregion
|
|
}
|
|
}
|