개선 전 저장
This commit is contained in:
@@ -1,18 +1,299 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
#nullable enable
|
||||
using Cysharp.Threading.Tasks;
|
||||
using DG.Tweening;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Threading;
|
||||
using TMPro;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
using UVC.UI.List.Tree;
|
||||
|
||||
namespace UVC.UI.Window
|
||||
{
|
||||
public class HierarchyWindow: MonoBehaviour
|
||||
public class HierarchyWindow : MonoBehaviour
|
||||
{
|
||||
[SerializeField]
|
||||
protected TreeList treeList;
|
||||
|
||||
/// <summary>
|
||||
/// 검색 결과 용 목록
|
||||
/// </summary>
|
||||
[SerializeField]
|
||||
protected TreeList treeListSearch;
|
||||
|
||||
[SerializeField]
|
||||
protected TMP_InputField inputField;
|
||||
|
||||
[SerializeField]
|
||||
protected Button clearTextButton;
|
||||
|
||||
[SerializeField]
|
||||
protected Image loadingImage;
|
||||
|
||||
// 검색 목록에서 선택된 항목(클론된 데이터)
|
||||
private TreeListItemData? selectedSearchItem;
|
||||
|
||||
// 검색 작업 상태
|
||||
private CancellationTokenSource? searchCts;
|
||||
private bool isSearching = false;
|
||||
private float searchProgress = 0f; //unused for visual progress now but kept for future
|
||||
|
||||
[SerializeField]
|
||||
[Tooltip("로딩 아이콘 회전 속도(도/초)")]
|
||||
private float loadingRotateSpeed = 360f;
|
||||
|
||||
[SerializeField]
|
||||
[Tooltip("로딩 이미지의 채우기 애니메이션 속도(사이클/초)")]
|
||||
private float loadingFillCycle = 0.5f; // cycles per second (full0->1->0 cycle per second)
|
||||
//loadingRotateSpeed 360 일때, loadingFillCycle를 0.5 보다 높게 설정하면 이상해 보임
|
||||
|
||||
// DOTween tweens
|
||||
private Tween? loadingRotationTween;
|
||||
private Tween? loadingFillTween;
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
loadingImage.gameObject.SetActive(false);
|
||||
|
||||
treeListSearch.gameObject.SetActive(false);
|
||||
inputField.onSubmit.AddListener(OnInputFieldSubmit);
|
||||
|
||||
// 검색 리스트의 선택 변경을 감지
|
||||
if (treeListSearch != null)
|
||||
{
|
||||
treeListSearch.OnItemSelectionChanged += OnSearchSelectionChanged;
|
||||
}
|
||||
|
||||
clearTextButton.onClick.AddListener(() =>
|
||||
{
|
||||
inputField.text = string.Empty;
|
||||
// 취소
|
||||
CancelSearch();
|
||||
|
||||
treeListSearch.gameObject.SetActive(false);
|
||||
treeList.gameObject.SetActive(true);
|
||||
|
||||
// 검색에서 선택한 항목이 있으면 원본 트리에서 동일 항목을 선택하고 펼침
|
||||
if (selectedSearchItem != null && treeList != null)
|
||||
{
|
||||
// 원본 데이터 찾기 (이 프로젝트의 Equals는 Name 기반이므로 Name으로 검색)
|
||||
var target = treeList.AllItemDataFlattened.FirstOrDefault(i => i.Name == selectedSearchItem.Name);
|
||||
if (target != null)
|
||||
{
|
||||
// TreeList에 새로 추가된 유틸리티를 이용해 부모 체인을 펼치고 선택 처리
|
||||
treeList.RevealAndSelectItem(target, true);
|
||||
}
|
||||
|
||||
selectedSearchItem = null;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void Update()
|
||||
{
|
||||
// DOTween handles the animations; no per-frame logic needed here
|
||||
}
|
||||
|
||||
private void StartLoadingAnimation()
|
||||
{
|
||||
if (loadingImage == null) return;
|
||||
|
||||
// Ensure any previous tweens are killed
|
||||
StopLoadingAnimation();
|
||||
|
||||
loadingImage.fillAmount = 0f;
|
||||
loadingImage.transform.localRotation = Quaternion.identity;
|
||||
loadingImage.gameObject.SetActive(true);
|
||||
|
||||
// Rotation: rotate360 degrees repeatedly. Duration for one360 rotation (seconds)
|
||||
float rotDuration = (loadingRotateSpeed != 0f) ? (360f / Mathf.Abs(loadingRotateSpeed)) : 1f;
|
||||
// Use LocalAxisAdd to rotate continuously
|
||||
loadingRotationTween = loadingImage.transform.DOLocalRotate(new Vector3(0f, 0f, -360f), rotDuration, RotateMode.LocalAxisAdd)
|
||||
.SetEase(Ease.Linear)
|
||||
.SetLoops(-1, LoopType.Restart);
|
||||
|
||||
// Fill animation:0 ->1 ->0 in one cycle. Forward duration = half cycle
|
||||
// For a0->1 then jump-to-0 repeat, use Restart loop and full cycle duration
|
||||
float fullDuration = (loadingFillCycle > 0f) ? (1f / loadingFillCycle) :1f;
|
||||
loadingFillTween = DOTween.To(() => loadingImage.fillAmount, x => loadingImage.fillAmount = x, 1f, fullDuration)
|
||||
.SetEase(Ease.InOutSine)
|
||||
.SetLoops(-1, LoopType.Yoyo);
|
||||
}
|
||||
|
||||
private void StopLoadingAnimation()
|
||||
{
|
||||
if (loadingRotationTween != null)
|
||||
{
|
||||
loadingRotationTween.Kill();
|
||||
loadingRotationTween = null;
|
||||
}
|
||||
|
||||
if (loadingFillTween != null)
|
||||
{
|
||||
loadingFillTween.Kill();
|
||||
loadingFillTween = null;
|
||||
}
|
||||
|
||||
if (loadingImage != null)
|
||||
{
|
||||
loadingImage.gameObject.SetActive(false);
|
||||
// reset transform / fill
|
||||
loadingImage.transform.localRotation = Quaternion.identity;
|
||||
loadingImage.fillAmount = 0f;
|
||||
}
|
||||
}
|
||||
|
||||
private void CancelSearch()
|
||||
{
|
||||
if (searchCts != null)
|
||||
{
|
||||
try { searchCts.Cancel(); } catch { }
|
||||
searchCts.Dispose();
|
||||
searchCts = null;
|
||||
}
|
||||
isSearching = false;
|
||||
searchProgress = 0f;
|
||||
|
||||
// stop DOTween animations
|
||||
StopLoadingAnimation();
|
||||
}
|
||||
|
||||
private async void OnSearchSelectionChanged(TreeListItemData data, bool isSelected)
|
||||
{
|
||||
if (isSelected)
|
||||
{
|
||||
selectedSearchItem = data;
|
||||
}
|
||||
else if (selectedSearchItem == data)
|
||||
{
|
||||
selectedSearchItem = null;
|
||||
}
|
||||
}
|
||||
|
||||
private void OnInputFieldSubmit(string text)
|
||||
{
|
||||
// 기존 검색 취소
|
||||
CancelSearch();
|
||||
|
||||
// 검색어가 있으면 검색 결과 목록 표시
|
||||
if (!string.IsNullOrEmpty(text))
|
||||
{
|
||||
treeListSearch.gameObject.SetActive(true);
|
||||
treeList.gameObject.SetActive(false);
|
||||
|
||||
// 시작 애니메이션
|
||||
StartLoadingAnimation();
|
||||
|
||||
searchCts = new CancellationTokenSource();
|
||||
// 비동기 검색 실행(UITask 스타일: 메인스레드에서 작업을 분할하여 UI가 멈추지 않게 함)
|
||||
_ = PerformSearchAsync(text, searchCts.Token);
|
||||
}
|
||||
else
|
||||
{
|
||||
treeListSearch.gameObject.SetActive(false);
|
||||
treeList.gameObject.SetActive(true);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 검색을 메인 스레드에서 분할 처리하여 UI 업데이트(로딩 애니메이션)가 가능하도록 구현합니다.
|
||||
/// </summary>
|
||||
private async UniTaskVoid PerformSearchAsync(string text, CancellationToken token)
|
||||
{
|
||||
isSearching = true;
|
||||
searchProgress = 0f;
|
||||
|
||||
var results = new System.Collections.Generic.List<TreeListItemData>();
|
||||
|
||||
var sourceList = treeList?.AllItemDataFlattened;
|
||||
if (sourceList == null)
|
||||
{
|
||||
isSearching = false;
|
||||
StopLoadingAnimation();
|
||||
return;
|
||||
}
|
||||
|
||||
int total = sourceList.Count;
|
||||
if (total == 0)
|
||||
{
|
||||
isSearching = false;
|
||||
StopLoadingAnimation();
|
||||
return;
|
||||
}
|
||||
|
||||
// 소문자 비교 준비
|
||||
string lower = text.ToLowerInvariant();
|
||||
|
||||
// 분할 처리: 일정 갯수마다 await으로 제어권을 반환
|
||||
const int chunk = 100; // 한 번에 처리할 항목 수 (조절 가능)
|
||||
for (int i = 0; i < total; i++)
|
||||
{
|
||||
token.ThrowIfCancellationRequested();
|
||||
|
||||
var item = sourceList[i];
|
||||
if (!string.IsNullOrEmpty(item.Name) && item.Name.ToLowerInvariant().Contains(lower))
|
||||
{
|
||||
results.Add(item);
|
||||
}
|
||||
|
||||
// 진행도 업데이트 (내부 사용)
|
||||
if ((i % chunk) == 0)
|
||||
{
|
||||
searchProgress = (float)i / (float)total;
|
||||
await UniTask.Yield(PlayerLoopTiming.Update);
|
||||
}
|
||||
}
|
||||
|
||||
// 최종 진행도
|
||||
searchProgress = 1f;
|
||||
|
||||
// UI 반영은 메인 스레드에서
|
||||
await UniTask.SwitchToMainThread();
|
||||
|
||||
try
|
||||
{
|
||||
if (token.IsCancellationRequested) return;
|
||||
|
||||
treeListSearch.ClearItems();
|
||||
foreach (var r in results)
|
||||
{
|
||||
treeListSearch.AddItem(r.Clone());
|
||||
}
|
||||
|
||||
// 로딩 종료
|
||||
isSearching = false;
|
||||
searchProgress = 0f;
|
||||
StopLoadingAnimation();
|
||||
}
|
||||
catch (System.Exception ex)
|
||||
{
|
||||
Debug.LogError($"PerformSearchAsync error: {ex}");
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (searchCts != null)
|
||||
{
|
||||
searchCts.Dispose();
|
||||
searchCts = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void OnDestroy()
|
||||
{
|
||||
inputField.onSubmit.RemoveListener(OnInputFieldSubmit);
|
||||
clearTextButton.onClick.RemoveAllListeners();
|
||||
|
||||
if (treeListSearch != null)
|
||||
{
|
||||
treeListSearch.OnItemSelectionChanged -= OnSearchSelectionChanged;
|
||||
}
|
||||
|
||||
CancelSearch();
|
||||
}
|
||||
|
||||
public void AddItem(TreeListItemData data)
|
||||
{
|
||||
treeList.AddItem(data);
|
||||
|
||||
Reference in New Issue
Block a user