#nullable enable using System; using System.Collections.Generic; using UnityEngine; using UnityEngine.UIElements; namespace UVC.UIToolkit { /// /// 리스트 뷰 컴포넌트. /// Unity ListView를 래핑하여 커스텀 스타일을 적용합니다. /// 가상화(Virtualization)를 지원하여 대량의 데이터도 효율적으로 표시합니다. /// /// /// ListView(리스트 뷰)란? /// /// ListView는 여러 항목을 세로로 나열하여 표시하는 UI 컴포넌트입니다. /// 파일 목록, 연락처, 설정 항목 등 반복되는 데이터를 표시할 때 사용합니다. /// /// /// 가상화(Virtualization)란? /// /// 수천 개의 항목이 있어도 화면에 보이는 항목만 실제로 렌더링하는 기술입니다. /// 스크롤 시 보이지 않는 항목은 메모리에서 재활용되어 성능이 유지됩니다. /// 이를 위해 makeItem(요소 생성)과 bindItem(데이터 바인딩)을 분리합니다. /// /// /// 필수 설정: /// /// itemsSource - 표시할 데이터 컬렉션 (IList) /// makeItem - 항목 VisualElement 생성 함수 /// bindItem - 데이터를 요소에 바인딩하는 함수 /// /// /// 주요 속성: /// /// fixedItemHeight - 항목 높이 (고정 시 성능 향상) /// selectionType - 선택 모드 (None, Single, Multiple) /// selectedIndex - 현재 선택된 인덱스 /// showBorder - 테두리 표시 여부 /// /// /// 주요 이벤트: /// /// OnItemSelected - 항목 선택 시 (인덱스 전달) /// OnItemDoubleClicked - 항목 더블클릭 시 (인덱스 전달) /// /// /// 실제 활용 예시: /// /// 파일 탐색기 - 파일/폴더 목록 /// 설정 화면 - 설정 항목 목록 /// 채팅 앱 - 대화 목록 /// 게임 인벤토리 - 아이템 목록 /// 데이터 테이블 - 행 단위 데이터 /// /// /// /// C# 코드에서 사용: /// /// // 리스트 뷰 생성 /// var listView = new UTKListView(); /// listView.fixedItemHeight = 30; // 항목 높이 고정 (성능 향상) /// /// // 데이터 소스 설정 /// var items = new List { "항목 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 /// UXML에서 사용: /// /// /// /// /// /// /// /// /// /// /// [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 /// 아이템 선택 이벤트 public event Action? OnItemSelected; /// 아이템 더블클릭 이벤트 public event Action? OnItemDoubleClicked; #endregion #region Constructor public UTKListView() : base() { UTKThemeManager.Instance.ApplyThemeToElement(this); var uss = Resources.Load(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(OnAttachToPanelForTheme); RegisterCallback(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 items) { OnItemSelected?.Invoke(selectedIndex); } private void OnItemsChosen(IEnumerable items) { OnItemDoubleClicked?.Invoke(selectedIndex); } #endregion #region IDisposable public void Dispose() { if (_disposed) return; _disposed = true; selectionChanged -= OnSelectionChanged; itemsChosen -= OnItemsChosen; UTKThemeManager.Instance.OnThemeChanged -= OnThemeChanged; UnregisterCallback(OnAttachToPanelForTheme); UnregisterCallback(OnDetachFromPanelForTheme); OnItemSelected = null; OnItemDoubleClicked = null; } #endregion } }