502 lines
16 KiB
C#
502 lines
16 KiB
C#
#nullable enable
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using UnityEngine;
|
|
using UnityEngine.UIElements;
|
|
namespace UVC.UIToolkit
|
|
{
|
|
/// <summary>
|
|
/// UTKImageList를 래핑하여 윈도우 형태로 제공하는 컴포넌트입니다.
|
|
/// LibraryWindow의 UIToolkit 버전입니다.
|
|
///
|
|
/// <para><b>개요:</b></para>
|
|
/// <para>
|
|
/// UTKImageListWindow는 UTKImageList를 내부에 포함하고 헤더(타이틀, 닫기 버튼)를 추가한
|
|
/// 윈도우 형태의 컴포넌트입니다. 모든 리스트 관련 기능은 내부 UTKImageList에 위임됩니다.
|
|
/// </para>
|
|
///
|
|
/// <para><b>주요 기능:</b></para>
|
|
/// <list type="bullet">
|
|
/// <item>윈도우 형태의 UI 제공 (헤더 + 닫기 버튼)</item>
|
|
/// <item>내부 UTKImageList의 모든 기능 위임</item>
|
|
/// <item>드래그 앤 드롭 이벤트 전달</item>
|
|
/// </list>
|
|
///
|
|
/// <para><b>UXML 사용 예시:</b></para>
|
|
/// <code><![CDATA[
|
|
/// <!-- UXML 파일에서 UTKImageListWindow 사용 -->
|
|
/// <ui:UXML xmlns:utk="UVC.UIToolkit">
|
|
/// <utk:UTKImageListWindow name="library-window" />
|
|
/// </ui:UXML>
|
|
/// ]]></code>
|
|
///
|
|
/// <para><b>C# 사용 예시:</b></para>
|
|
/// <code><![CDATA[
|
|
/// // 1. 윈도우 참조 획득
|
|
/// var imageWindow = root.Q<UTKImageListWindow>("library-window");
|
|
///
|
|
/// // 2. 윈도우 제목 및 닫기 버튼 설정
|
|
/// imageWindow.Title = "프리팹 라이브러리";
|
|
/// imageWindow.ShowCloseButton = true;
|
|
///
|
|
/// // 3. 데이터 구성 - 이미지와 이름을 가진 아이템들
|
|
/// var data = new List<UTKImageListItemData>
|
|
/// {
|
|
/// new UTKImageListItemData
|
|
/// {
|
|
/// itemName = "의자",
|
|
/// imagePath = "Prefabs/Thumbnails/chair",
|
|
/// objectPrefabPath = "Prefabs/Furniture/Chair"
|
|
/// },
|
|
/// new UTKImageListItemData
|
|
/// {
|
|
/// itemName = "책상",
|
|
/// imagePath = "Prefabs/Thumbnails/desk",
|
|
/// objectPrefabPath = "Prefabs/Furniture/Desk"
|
|
/// }
|
|
/// };
|
|
/// imageWindow.SetData(data);
|
|
///
|
|
/// // 4. 아이템 클릭 이벤트 구독
|
|
/// imageWindow.OnItemClick = (item) =>
|
|
/// {
|
|
/// Debug.Log($"클릭된 아이템: {item.itemName}");
|
|
/// };
|
|
///
|
|
/// // 5. 드래그 시작 이벤트
|
|
/// imageWindow.OnItemBeginDrag = (item, screenPos) =>
|
|
/// {
|
|
/// Debug.Log($"드래그 시작: {item.itemName}");
|
|
/// };
|
|
///
|
|
/// // 6. 드래그 중 이벤트
|
|
/// imageWindow.OnItemDrag = (item, screenPos) =>
|
|
/// {
|
|
/// // 드래그 중 위치 업데이트
|
|
/// };
|
|
///
|
|
/// // 7. 드래그 종료 이벤트
|
|
/// imageWindow.OnItemEndDrag = (item, screenPos) =>
|
|
/// {
|
|
/// Debug.Log($"드래그 종료: {item.itemName}");
|
|
/// };
|
|
///
|
|
/// // 8. 드롭 이벤트 (프리팹 인스턴스화)
|
|
/// imageWindow.OnItemDrop = (item) =>
|
|
/// {
|
|
/// if (!string.IsNullOrEmpty(item.objectPrefabPath))
|
|
/// {
|
|
/// var prefab = Resources.Load<GameObject>(item.objectPrefabPath);
|
|
/// Instantiate(prefab, dropPosition, Quaternion.identity);
|
|
/// }
|
|
/// };
|
|
///
|
|
/// // 9. 리스트 영역 밖으로 드래그 시 3D 미리보기 표시
|
|
/// imageWindow.OnDragExitList = (item, screenPos) =>
|
|
/// {
|
|
/// // 리스트 밖 = 3D 씬 영역
|
|
/// Show3DPreview(item.objectPrefabPath, screenPos);
|
|
/// };
|
|
///
|
|
/// // 10. 리스트 영역으로 다시 들어오면 미리보기 숨김
|
|
/// imageWindow.OnDragEnterList = (item, screenPos) =>
|
|
/// {
|
|
/// Hide3DPreview();
|
|
/// };
|
|
///
|
|
/// // 11. 윈도우 닫힘 이벤트
|
|
/// imageWindow.OnClosed += () =>
|
|
/// {
|
|
/// Debug.Log("윈도우가 닫혔습니다.");
|
|
/// };
|
|
///
|
|
/// // 12. 드래그 고스트 이미지 설정
|
|
/// imageWindow.DragImageFollowCursor = true;
|
|
///
|
|
/// // 13. 검색 실행
|
|
/// imageWindow.ApplySearch("의자");
|
|
///
|
|
/// // 14. 아이템 추가/제거
|
|
/// var newItem = new UTKImageListItemData { itemName = "새 아이템" };
|
|
/// imageWindow.AddItem(newItem);
|
|
/// imageWindow.RemoveItem(newItem);
|
|
/// imageWindow.Clear();
|
|
///
|
|
/// // 15. 아이템 개수 확인
|
|
/// int count = imageWindow.ItemCount;
|
|
///
|
|
/// // 16. 윈도우 표시/닫기
|
|
/// imageWindow.Show();
|
|
/// imageWindow.Close();
|
|
///
|
|
/// // 17. 리소스 해제 (OnDestroy에서 호출)
|
|
/// imageWindow.Dispose();
|
|
/// ]]></code>
|
|
///
|
|
/// <para><b>관련 리소스:</b></para>
|
|
/// <list type="bullet">
|
|
/// <item>Resources/UIToolkit/Window/UTKImageListWindow.uxml - 윈도우 레이아웃</item>
|
|
/// <item>Resources/UIToolkit/Window/UTKImageListWindow.uss - 윈도우 스타일</item>
|
|
/// </list>
|
|
/// </summary>
|
|
[UxmlElement]
|
|
public partial class UTKImageListWindow : VisualElement, IDisposable
|
|
{
|
|
#region 상수 (Constants)
|
|
|
|
/// <summary>메인 UXML 파일 경로 (Resources 폴더 기준)</summary>
|
|
private const string UXML_PATH = "UIToolkit/Window/UTKImageListWindow";
|
|
|
|
/// <summary>USS 파일 경로 (Resources 폴더 기준)</summary>
|
|
private const string USS_PATH = "UIToolkit/Window/UTKImageListWindowUss";
|
|
|
|
#endregion
|
|
|
|
#region UI 컴포넌트 참조 (UI Component References)
|
|
|
|
/// <summary>내부 UTKImageList 컴포넌트</summary>
|
|
private UTKImageList? _imageList;
|
|
|
|
/// <summary>윈도우 닫기 버튼 (UTKButton)</summary>
|
|
private UTKButton? _closeButton;
|
|
|
|
/// <summary>윈도우 제목 라벨</summary>
|
|
private Label? _titleLabel;
|
|
|
|
#endregion
|
|
|
|
#region IDisposable
|
|
|
|
private bool _disposed;
|
|
|
|
#endregion
|
|
|
|
#region 공개 속성 (Public Properties)
|
|
|
|
/// <summary>
|
|
/// 드래그 시 이미지가 커서를 따라다니도록 설정합니다.
|
|
/// 내부 UTKImageList에 위임됩니다.
|
|
/// </summary>
|
|
public bool DragImageFollowCursor
|
|
{
|
|
get => _imageList?.DragImageFollowCursor ?? true;
|
|
set { if (_imageList != null) _imageList.DragImageFollowCursor = value; }
|
|
}
|
|
|
|
/// <summary>
|
|
/// 드래그 영역 체크에 사용할 요소를 설정합니다.
|
|
/// 기본값은 윈도우 자체입니다.
|
|
/// </summary>
|
|
public VisualElement? DragBoundsElement
|
|
{
|
|
get => _imageList?.DragBoundsElement;
|
|
set { if (_imageList != null) _imageList.DragBoundsElement = value; }
|
|
}
|
|
|
|
/// <summary>
|
|
/// 윈도우 제목을 가져오거나 설정합니다.
|
|
/// </summary>
|
|
public string Title
|
|
{
|
|
get => _titleLabel?.text ?? string.Empty;
|
|
set { if (_titleLabel != null) _titleLabel.text = value; }
|
|
}
|
|
|
|
/// <summary>
|
|
/// 닫기 버튼 표시 여부를 설정합니다.
|
|
/// </summary>
|
|
public bool ShowCloseButton
|
|
{
|
|
get => _closeButton?.style.display == DisplayStyle.Flex;
|
|
set { if (_closeButton != null) _closeButton.style.display = value ? DisplayStyle.Flex : DisplayStyle.None; }
|
|
}
|
|
|
|
/// <summary>
|
|
/// 현재 표시 중인 아이템 수를 반환합니다.
|
|
/// </summary>
|
|
public int ItemCount => _imageList?.ItemCount ?? 0;
|
|
|
|
#endregion
|
|
|
|
#region 이벤트 위임 (Event Delegation)
|
|
|
|
/// <summary>
|
|
/// 아이템 클릭 이벤트입니다.
|
|
/// 내부 UTKImageList에서 발생한 이벤트를 전달합니다.
|
|
/// </summary>
|
|
public Action<UTKImageListItemData>? OnItemClick
|
|
{
|
|
get => _imageList?.OnItemClick;
|
|
set { if (_imageList != null) _imageList.OnItemClick = value; }
|
|
}
|
|
|
|
/// <summary>
|
|
/// 아이템 드롭 이벤트입니다.
|
|
/// </summary>
|
|
public Action<UTKImageListItemData>? OnItemDrop
|
|
{
|
|
get => _imageList?.OnItemDrop;
|
|
set { if (_imageList != null) _imageList.OnItemDrop = value; }
|
|
}
|
|
|
|
/// <summary>
|
|
/// 드래그 시작 이벤트입니다.
|
|
/// (아이템 데이터, 화면 좌표)
|
|
/// </summary>
|
|
public Action<UTKImageListItemData, Vector2>? OnItemBeginDrag
|
|
{
|
|
get => _imageList?.OnItemBeginDrag;
|
|
set { if (_imageList != null) _imageList.OnItemBeginDrag = value; }
|
|
}
|
|
|
|
/// <summary>
|
|
/// 드래그 중 이벤트입니다.
|
|
/// (아이템 데이터, 화면 좌표)
|
|
/// </summary>
|
|
public Action<UTKImageListItemData, Vector2>? OnItemDrag
|
|
{
|
|
get => _imageList?.OnItemDrag;
|
|
set { if (_imageList != null) _imageList.OnItemDrag = value; }
|
|
}
|
|
|
|
/// <summary>
|
|
/// 드래그 종료 이벤트입니다.
|
|
/// (아이템 데이터, 화면 좌표)
|
|
/// </summary>
|
|
public Action<UTKImageListItemData, Vector2>? OnItemEndDrag
|
|
{
|
|
get => _imageList?.OnItemEndDrag;
|
|
set { if (_imageList != null) _imageList.OnItemEndDrag = value; }
|
|
}
|
|
|
|
/// <summary>
|
|
/// 드래그 중 리스트 영역을 벗어났을 때 발생하는 이벤트입니다.
|
|
/// (아이템 데이터, 화면 좌표)
|
|
/// 3D 프리팹 미리보기를 표시하는데 사용합니다.
|
|
/// </summary>
|
|
public Action<UTKImageListItemData, Vector2>? OnDragExitList
|
|
{
|
|
get => _imageList?.OnDragExitList;
|
|
set { if (_imageList != null) _imageList.OnDragExitList = value; }
|
|
}
|
|
|
|
/// <summary>
|
|
/// 드래그 중 리스트 영역에 다시 진입했을 때 발생하는 이벤트입니다.
|
|
/// (아이템 데이터, 화면 좌표)
|
|
/// 3D 프리팹 미리보기를 숨기는데 사용합니다.
|
|
/// </summary>
|
|
public Action<UTKImageListItemData, Vector2>? OnDragEnterList
|
|
{
|
|
get => _imageList?.OnDragEnterList;
|
|
set { if (_imageList != null) _imageList.OnDragEnterList = value; }
|
|
}
|
|
|
|
/// <summary>
|
|
/// 윈도우가 닫힐 때(숨겨질 때) 발생합니다.
|
|
/// 닫기 버튼 클릭 또는 Close() 메서드 호출 시 트리거됩니다.
|
|
/// </summary>
|
|
public event Action? OnClosed;
|
|
|
|
#endregion
|
|
|
|
#region 생성자 (Constructor)
|
|
|
|
/// <summary>
|
|
/// UTKImageListWindow 컴포넌트를 초기화합니다.
|
|
/// UXML 템플릿을 로드하고 내부 UTKImageList를 설정합니다.
|
|
/// </summary>
|
|
public UTKImageListWindow()
|
|
{
|
|
// 1. 메인 UXML 로드 및 복제
|
|
var visualTree = Resources.Load<VisualTreeAsset>(UXML_PATH);
|
|
if (visualTree == null)
|
|
{
|
|
Debug.LogError($"[UTKImageListWindow] UXML not found at: {UXML_PATH}");
|
|
return;
|
|
}
|
|
visualTree.CloneTree(this);
|
|
|
|
// 2. 테마 적용 및 변경 구독
|
|
UTKThemeManager.Instance.ApplyThemeToElement(this);
|
|
SubscribeToThemeChanges();
|
|
|
|
// USS 로드 (테마 변수 스타일시트 이후에 로드되어야 변수가 해석됨)
|
|
var uss = Resources.Load<StyleSheet>(USS_PATH);
|
|
if (uss != null)
|
|
{
|
|
styleSheets.Add(uss);
|
|
}
|
|
|
|
// 3. 내부 UTKImageList 찾기 (UXML에서 생성된 컴포넌트)
|
|
_imageList = this.Q<UTKImageList>();
|
|
if (_imageList == null)
|
|
{
|
|
Debug.LogError("[UTKImageListWindow] UTKImageList not found in UXML");
|
|
}
|
|
else
|
|
{
|
|
// 드래그 영역 체크에 윈도우 전체 영역을 사용하도록 설정
|
|
_imageList.DragBoundsElement = this;
|
|
}
|
|
|
|
// 4. 헤더 요소 참조
|
|
_titleLabel = this.Q<Label>("title");
|
|
_closeButton = this.Q<UTKButton>("close-btn");
|
|
|
|
// 5. 닫기 버튼 설정 및 이벤트 연결
|
|
if (_closeButton != null)
|
|
{
|
|
_closeButton.SetMaterialIcon(UTKMaterialIcons.Close, 16);
|
|
_closeButton.OnClicked += OnCloseButtonClicked;
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region 공개 메서드 (Public Methods)
|
|
|
|
/// <summary>
|
|
/// 윈도우를 화면에 표시합니다.
|
|
/// </summary>
|
|
public void Show()
|
|
{
|
|
this.style.display = DisplayStyle.Flex;
|
|
_imageList?.Show();
|
|
}
|
|
|
|
/// <summary>
|
|
/// 윈도우를 화면에서 숨깁니다.
|
|
/// OnClosed 이벤트가 발생합니다.
|
|
/// </summary>
|
|
public void Close()
|
|
{
|
|
this.style.display = DisplayStyle.None;
|
|
OnClosed?.Invoke();
|
|
}
|
|
|
|
/// <summary>
|
|
/// 리스트 데이터를 설정합니다.
|
|
/// 내부 UTKImageList에 위임됩니다.
|
|
/// </summary>
|
|
/// <param name="data">표시할 아이템 데이터 리스트</param>
|
|
public void SetData(List<UTKImageListItemData> data)
|
|
{
|
|
_imageList?.SetData(data);
|
|
}
|
|
|
|
/// <summary>
|
|
/// 리스트에 단일 아이템을 추가합니다.
|
|
/// </summary>
|
|
/// <param name="item">추가할 아이템 데이터</param>
|
|
public void AddItem(UTKImageListItemData item)
|
|
{
|
|
_imageList?.AddItem(item);
|
|
}
|
|
|
|
/// <summary>
|
|
/// 리스트에서 아이템을 제거합니다.
|
|
/// </summary>
|
|
/// <param name="item">제거할 아이템 데이터</param>
|
|
/// <returns>제거 성공 여부</returns>
|
|
public bool RemoveItem(UTKImageListItemData item)
|
|
{
|
|
return _imageList?.RemoveItem(item) ?? false;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 모든 데이터를 제거합니다.
|
|
/// </summary>
|
|
public void Clear()
|
|
{
|
|
_imageList?.Clear();
|
|
}
|
|
|
|
/// <summary>
|
|
/// 지정된 검색어로 검색을 실행합니다.
|
|
/// </summary>
|
|
/// <param name="query">검색어</param>
|
|
public void ApplySearch(string query)
|
|
{
|
|
_imageList?.ApplySearch(query);
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region 이벤트 핸들러 (Event Handlers)
|
|
|
|
/// <summary>
|
|
/// 닫기 버튼 클릭 이벤트를 처리합니다.
|
|
/// </summary>
|
|
private void OnCloseButtonClicked()
|
|
{
|
|
Close();
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region 테마 (Theme)
|
|
|
|
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 IDisposable
|
|
|
|
/// <summary>
|
|
/// 리소스를 해제하고 이벤트 핸들러를 정리합니다.
|
|
/// </summary>
|
|
public void Dispose()
|
|
{
|
|
if (_disposed) return;
|
|
_disposed = true;
|
|
|
|
// 테마 변경 이벤트 해제
|
|
UTKThemeManager.Instance.OnThemeChanged -= OnThemeChanged;
|
|
UnregisterCallback<AttachToPanelEvent>(OnAttachToPanelForTheme);
|
|
UnregisterCallback<DetachFromPanelEvent>(OnDetachFromPanelForTheme);
|
|
|
|
// 내부 UTKImageList 정리
|
|
_imageList?.Dispose();
|
|
_imageList = null;
|
|
|
|
// 닫기 버튼 이벤트 해제 및 정리
|
|
if (_closeButton != null)
|
|
{
|
|
_closeButton.OnClicked -= OnCloseButtonClicked;
|
|
_closeButton.Dispose();
|
|
}
|
|
|
|
// 외부 이벤트 구독자 정리
|
|
OnClosed = null;
|
|
|
|
// UI 참조 정리
|
|
_closeButton = null;
|
|
_titleLabel = null;
|
|
}
|
|
|
|
#endregion
|
|
}
|
|
}
|