Files
XRLib/Assets/Scripts/UVC/UIToolkit/Docs/UTKPropertyWindow_WorkOrder.md
2026-01-08 20:15:57 +09:00

37 KiB

UTKPropertyWindow 작업지시서

1. 개요

1.1 목적

기존 IMGUI 기반 PropertyWindow를 UIToolkit으로 전환하여 UTKPropertyWindow를 구현합니다.

1.2 기존 PropertyWindow 구조 분석

PropertyWindow (Controller/MonoBehaviour)
├── PropertyView (View/MonoBehaviour)
│   ├── PropertyGroupView (그룹 UI)
│   └── PropertyUI들 (각 타입별 UI)
├── PropertyItem (Model/Data)
│   └── 16가지 PropertyType 지원
├── PropertyGroup (그룹 관리)
└── IPropertyUI (UI 인터페이스)

지원 PropertyType (16가지)

Type 설명 기존 UI 컴포넌트
String 문자열 TMP_InputField
Int 정수 (슬라이더 옵션) TMP_InputField / Slider
Float 실수 (슬라이더 옵션) TMP_InputField / Slider
Bool 불리언 Toggle
Vector2 2D 벡터 2x InputField
Vector3 3D 벡터 3x InputField
Color 색상 ColorPicker
Date 날짜 DatePicker
DateTime 날짜+시간 DateTimePicker
Enum 열거형 Dropdown
DropdownList 문자열 목록 Dropdown
RadioGroup 라디오 그룹 RadioButton Group
IntRange 정수 범위 2x InputField
FloatRange 실수 범위 2x InputField
DateRange 날짜 범위 2x DatePicker
DateTimeRange 날짜시간 범위 2x DateTimePicker
ColorState 상태+색상 Label + ColorPicker

2. 선행 의존 컴포넌트 (Prerequisites)

UTKPropertyWindow를 완전히 구현하려면 다음 컴포넌트들을 먼저 UIToolkit으로 전환해야 합니다.

2.1 의존 컴포넌트 목록

컴포넌트 기존 위치 용도 우선순위
UTKTooltip UVC/UI/Tooltip/ 마우스 오버 시 툴팁 표시 🔴 높음
UTKColorPicker UVC/UI/Modal/ColorPicker/ Color, ColorState 속성 편집 🔴 높음
UTKDatePicker UVC/UI/Modal/DatePicker/ Date, DateTime, Range 속성 편집 🔴 높음
UTKModalBlocker Prefabs/UI/Modal/ModalBlocker 모달 배경 차단 🟡 중간

2.2 UTKTooltip 설계

기존 구조 분석 (TooltipManager)

// 싱글톤 패턴
public class TooltipManager {
    public void Initialize(Transform? defaultParent, string? tooltipPrefabPath);
    public void HandlePointerEnter(string tooltipKeyOrText, Vector3 mousePosition);
    public void HandlePointerExit();
    public void HideTooltip();
    public void Dispose();
}

UIToolkit 설계

/// <summary>
/// UIToolkit 기반 툴팁 매니저
/// VisualElement에 마우스 오버 시 툴팁 표시
/// </summary>
public class UTKTooltipManager : IDisposable
{
    private static UTKTooltipManager? _instance;
    public static UTKTooltipManager Instance => _instance ??= new UTKTooltipManager();

    private VisualElement? _tooltipElement;
    private Label? _tooltipLabel;
    private bool _isVisible;

    // 초기화 - VisualElement 트리의 루트에 툴팁 요소 추가
    public void Initialize(VisualElement root);

    // 표시/숨김
    public void Show(string text, Vector2 position);
    public void Hide();

    // VisualElement에 툴팁 연결 (확장 메서드로도 제공)
    public void AttachTooltip(VisualElement element, string tooltip);
    public void DetachTooltip(VisualElement element);

    public void Dispose();
}

// 확장 메서드
public static class UTKTooltipExtensions
{
    public static void SetTooltip(this VisualElement element, string tooltip);
    public static void ClearTooltip(this VisualElement element);
}

디렉토리 구조

Assets/Scripts/UVC/UIToolkit/Common/
├── UTKTooltipManager.cs
└── UTKTooltipExtensions.cs

Assets/Resources/UIToolkit/Common/
├── UTKTooltip.uxml
└── UTKTooltip.uss

구현 포인트

  • 마우스 PointerEnterEvent/PointerLeaveEvent 활용
  • 지연 표시 (500ms 후 표시)
  • 화면 경계 체크로 위치 자동 조정
  • LocalizationManager 연동 (다국어 지원)

2.3 UTKColorPicker 설계

기존 구조 분석 (ColorPicker)

public class ColorPicker : MonoBehaviour {
    // 정적 팩토리 메서드
    public static async UniTask<bool> Create(
        Color original,
        string message,
        ColorEvent onColorChanged,
        ColorEvent onColorSelected,
        Action onCloseAction = null,
        bool useAlpha = false
    );

    public static void Cancel();
    public static void Done();

    // UI 컴포넌트
    public Slider mainComponent;      // Hue 슬라이더
    public Slider rComponent;         // R 슬라이더
    public Slider gComponent;         // G 슬라이더
    public Slider bComponent;         // B 슬라이더
    public Slider aComponent;         // Alpha 슬라이더
    public TMP_InputField hexaComponent; // Hex 입력
    public RawImage colorComponent;   // 미리보기

    // HSV 색공간 지원
    private sealed class HSV { ... }
}

UIToolkit 설계

/// <summary>
/// UIToolkit 기반 컬러 피커 모달
/// HSV 색공간 + RGB 슬라이더 + Hex 입력 지원
/// </summary>
[UxmlElement]
public partial class UTKColorPicker : VisualElement, IDisposable
{
    // 이벤트
    public event Action<Color>? OnColorChanged;
    public event Action<Color>? OnColorSelected;
    public event Action? OnClosed;

    // 정적 팩토리 메서드
    public static UTKColorPicker Show(
        VisualElement parent,
        Color initialColor,
        string title = "Color Picker",
        bool useAlpha = false
    );

    // 공개 메서드
    public void SetColor(Color color);
    public Color GetColor();
    public void Close();
    public void Cancel();
    public void Confirm();

    public void Dispose();
}

디렉토리 구조

Assets/Scripts/UVC/UIToolkit/Modal/
├── UTKColorPicker.cs
├── UTKColorPickerHSV.cs      # HSV 변환 유틸리티
└── UTKModalBlocker.cs        # 모달 배경

Assets/Resources/UIToolkit/Modal/
├── UTKColorPicker.uxml
├── UTKColorPicker.uss
├── UTKModalBlocker.uxml
└── UTKModalBlocker.uss

UI 레이아웃

┌─────────────────────────────────────────┐
│ [Title]                          [X]    │
├─────────────────────────────────────────┤
│ ┌───────────────────┐  ┌──────────────┐ │
│ │                   │  │ Hue Slider   │ │
│ │  SV Color Box     │  │ ████████████ │ │
│ │  (Saturation/     │  └──────────────┘ │
│ │   Value)          │                   │
│ │                   │  ┌──────────────┐ │
│ └───────────────────┘  │ Preview      │ │
│                        └──────────────┘ │
├─────────────────────────────────────────┤
│ R: [████████████] [255]                 │
│ G: [████████████] [128]                 │
│ B: [████████████] [64]                  │
│ A: [████████████] [255]  (optional)     │
├─────────────────────────────────────────┤
│ Hex: [#FF8040FF]                        │
├─────────────────────────────────────────┤
│              [Cancel]  [OK]             │
└─────────────────────────────────────────┘

구현 포인트

  • HSV 색공간 박스: VisualElement + PointerEvent로 구현
  • Hue 슬라이더: Slider 컨트롤 + 그라데이션 배경
  • RGB 슬라이더: Slider + IntegerField 연동
  • Hex 입력: TextField + 유효성 검사
  • 실시간 미리보기
  • 디바운싱으로 과도한 이벤트 방지 (100ms)

2.4 UTKDatePicker 설계

기존 구조 분석 (DatePicker)

public class DatePicker : MonoBehaviour {
    public static async UniTask Show(DateTime initialDate, Action<DateTime> onDateSelected);
    public static void Hide();

    // 캘린더 UI
    public TextMeshProUGUI _yearNumText;
    public TextMeshProUGUI _monthNumText;
    private List<GameObject> _dateItems;

    // 네비게이션
    public void YearPrev();
    public void YearNext();
    public void MonthPrev();
    public void MonthNext();

    // 날짜 선택
    public void OnDateItemClick(string day);
}

UIToolkit 설계

/// <summary>
/// UIToolkit 기반 날짜/시간 피커 모달
/// 캘린더 그리드 + 시간 선택 지원
/// </summary>
[UxmlElement]
public partial class UTKDatePicker : VisualElement, IDisposable
{
    // 모드
    public enum PickerMode { DateOnly, DateAndTime }

    // 이벤트
    public event Action<DateTime>? OnDateSelected;
    public event Action? OnClosed;

    // 정적 팩토리 메서드
    public static UTKDatePicker Show(
        VisualElement parent,
        DateTime initialDate,
        PickerMode mode = PickerMode.DateOnly,
        string title = "Select Date"
    );

    // 공개 메서드
    public void SetDate(DateTime date);
    public DateTime GetDate();
    public void Close();

    // 네비게이션
    public void PreviousMonth();
    public void NextMonth();
    public void PreviousYear();
    public void NextYear();

    public void Dispose();
}

디렉토리 구조

Assets/Scripts/UVC/UIToolkit/Modal/
├── UTKDatePicker.cs
└── UTKTimePicker.cs          # 시간 선택 (옵션)

Assets/Resources/UIToolkit/Modal/
├── UTKDatePicker.uxml
├── UTKDatePicker.uss
├── UTKTimePicker.uxml        # DateAndTime 모드용
└── UTKTimePicker.uss

UI 레이아웃

┌─────────────────────────────────────────┐
│ [Title]                          [X]    │
├─────────────────────────────────────────┤
│   [<] [<<]   2026년 01월   [>>] [>]     │
├─────────────────────────────────────────┤
│  일   월   화   수   목   금   토       │
├─────────────────────────────────────────┤
│       1    2    3    4    5    6        │
│  7    8    9   10   11   12   13        │
│ 14   15   16   17   18   19   20        │
│ 21   22   23   24   25  [26]  27        │  ← 선택된 날짜
│ 28   29   30   31                       │
├─────────────────────────────────────────┤
│ (DateAndTime 모드일 때)                 │
│ 시간: [14] : [30]    [AM/PM]            │
├─────────────────────────────────────────┤
│              [Cancel]  [OK]             │
└─────────────────────────────────────────┘

구현 포인트

  • 6x7 그리드 레이아웃 (42개 날짜 셀)
  • 요일별 색상 구분 (토:파랑, 일:빨강)
  • 오늘 날짜 하이라이트
  • 선택 날짜 강조
  • 월 경계 비활성 날짜 처리
  • 시간 선택: IntegerField (시/분) 또는 DropdownField

2.5 UTKModalBlocker 설계

/// <summary>
/// 모달 뒤의 배경을 차단하고 클릭 시 닫힘 처리
/// </summary>
[UxmlElement]
public partial class UTKModalBlocker : VisualElement, IDisposable
{
    public event Action? OnBlockerClicked;

    // 팩토리
    public static UTKModalBlocker Show(
        VisualElement root,
        float opacity = 0.7f,
        bool closeOnClick = false
    );

    public void Hide();
    public void Dispose();
}

2.6 선행 작업 구현 단계

Phase 컴포넌트 예상 작업량 우선순위
Phase 0-1 UTKTooltip 0.5일 🔴
Phase 0-2 UTKModalBlocker 0.5일 🔴
Phase 0-3 UTKColorPicker 1.5일 🔴
Phase 0-4 UTKDatePicker 1.5일 🔴

총 예상 작업량: 4일

⚠️ 중요: Phase 1~9의 UTKPropertyWindow 구현을 시작하기 전에 Phase 0의 선행 컴포넌트 구현이 완료되어야 합니다.


3. 기존 UIToolkit 패턴 분석 (UVC.UIToolkit)

3.1 공통 아키텍처 패턴

// 1. UxmlElement 속성으로 UXML에서 사용 가능
[UxmlElement]
public partial class UTKXxx : VisualElement, IDisposable

// 2. Resources에서 UXML/USS 로드
private const string UXML_PATH = "UIToolkit/xxx/UTKXxx";
var visualTree = Resources.Load<VisualTreeAsset>(UXML_PATH);
visualTree.CloneTree(this);

// 3. 이벤트 기반 통신 (Action 델리게이트)
public event Action<ItemData>? OnItemClick;
public event Action<ItemData, bool>? OnItemVisibilityChanged;

// 4. IDisposable 패턴으로 리소스 정리
public void Dispose() { ... }

3.2 데이터 바인딩 패턴

// UTKAccordionList 스타일: TreeView 기반 가상화
_treeView.makeItem = MakeTreeItem;
_treeView.bindItem = BindTreeItem;
_treeView.unbindItem = UnbindTreeItem;

// UTKComponentList 스타일: ListView 기반
_listView.makeItem = () => new UTKComponentListItem();
_listView.bindItem = (element, index) => { ... };

3.3 성능 최적화 기법

  1. 가상화 (Virtualization): TreeView/ListView의 가상화 활용
  2. 비동기 이미지 로딩: UniTask + CancellationToken
  3. 스프라이트 캐싱: Dictionary 기반 캐시
  4. 디바운싱: 빠른 연속 입력 처리
  5. 이벤트 구독 관리: Dispose 시 명시적 해제

4. UTKPropertyWindow 설계

4.1 디렉토리 구조

Assets/Scripts/UVC/UIToolkit/Property/
├── Core/
│   ├── IUTKPropertyEntry.cs       # 엔트리 인터페이스
│   ├── IUTKPropertyGroup.cs       # 그룹 인터페이스
│   ├── IUTKPropertyItem.cs        # 아이템 인터페이스
│   ├── UTKPropertyGroup.cs        # 그룹 구현
│   └── UTKPropertyType.cs         # 타입 열거형
├── Data/
│   ├── UTKPropertyItemData.cs     # 통합 데이터 클래스
│   └── UTKPropertyGroupData.cs    # 그룹 데이터
├── Items/
│   ├── Base/
│   │   └── UTKPropertyItemBase.cs # 아이템 기본 클래스
│   ├── UTKStringPropertyItem.cs
│   ├── UTKIntPropertyItem.cs
│   ├── UTKFloatPropertyItem.cs
│   ├── UTKBoolPropertyItem.cs
│   ├── UTKVector2PropertyItem.cs
│   ├── UTKVector3PropertyItem.cs
│   ├── UTKColorPropertyItem.cs
│   ├── UTKDatePropertyItem.cs
│   ├── UTKDateTimePropertyItem.cs
│   ├── UTKEnumPropertyItem.cs
│   ├── UTKDropdownPropertyItem.cs
│   ├── UTKRadioPropertyItem.cs
│   ├── UTKIntRangePropertyItem.cs
│   ├── UTKFloatRangePropertyItem.cs
│   ├── UTKDateRangePropertyItem.cs
│   ├── UTKDateTimeRangePropertyItem.cs
│   └── UTKColorStatePropertyItem.cs
├── UTKPropertyList.cs             # 핵심 리스트 컴포넌트
└── UTKPropertyWindow.cs           # 윈도우 래퍼

Assets/Resources/UIToolkit/Property/
├── UTKPropertyList.uxml
├── UTKPropertyList.uss
├── UTKPropertyWindow.uxml
├── UTKPropertyWindow.uss
├── Items/
│   ├── UTKPropertyGroup.uxml
│   ├── UTKPropertyGroup.uss
│   ├── UTKStringPropertyItem.uxml
│   ├── UTKIntPropertyItem.uxml
│   ├── UTKFloatPropertyItem.uxml
│   ├── UTKBoolPropertyItem.uxml
│   ├── UTKVector2PropertyItem.uxml
│   ├── UTKVector3PropertyItem.uxml
│   ├── UTKColorPropertyItem.uxml
│   ├── UTKDatePropertyItem.uxml
│   ├── UTKDateTimePropertyItem.uxml
│   ├── UTKEnumPropertyItem.uxml
│   ├── UTKDropdownPropertyItem.uxml
│   ├── UTKRadioPropertyItem.uxml
│   ├── UTKRangePropertyItem.uxml  # Int/Float/Date/DateTime 공용
│   └── UTKColorStatePropertyItem.uxml
└── Common/
    └── UTKPropertyCommon.uss      # 공통 스타일

Assets/Sample/UIToolkit/
└── UTKPropertyWindowSample.cs     # 샘플 코드

4.2 클래스 다이어그램

                      ┌─────────────────────┐
                      │  UTKPropertyWindow  │
                      │  (VisualElement)    │
                      │  - Window 래퍼      │
                      └──────────┬──────────┘
                                 │ contains
                      ┌──────────▼──────────┐
                      │   UTKPropertyList   │
                      │   (VisualElement)   │
                      │   - 핵심 리스트     │
                      └──────────┬──────────┘
                                 │ uses
              ┌──────────────────┼──────────────────┐
              │                  │                  │
    ┌─────────▼─────────┐ ┌─────▼──────┐ ┌─────────▼─────────┐
    │ UTKPropertyGroup  │ │  TreeView  │ │ UTKPropertyItem   │
    │ (VisualElement)   │ │ (가상화)   │ │ (VisualElement)   │
    │ - 그룹 헤더       │ └────────────┘ │ - 개별 속성 UI    │
    └───────────────────┘                └─────────┬─────────┘
                                                   │ extends
                         ┌─────────────────────────┼─────────────────────────┐
                         │                         │                         │
              ┌──────────▼──────────┐   ┌─────────▼─────────┐    ┌─────────▼─────────┐
              │ UTKStringProperty   │   │ UTKBoolProperty   │    │ UTKColorProperty  │
              │ UTKIntProperty      │   │ UTKEnumProperty   │    │ UTKVectorProperty │
              │ UTKFloatProperty    │   │ UTKDropdownProp   │    │ UTKDateProperty   │
              └─────────────────────┘   └───────────────────┘    └───────────────────┘

5. 핵심 인터페이스 설계

5.1 IUTKPropertyItem

public interface IUTKPropertyItem : IUTKPropertyEntry
{
    string Id { get; }
    string Name { get; }
    string? Description { get; set; }
    string? Tooltip { get; set; }
    bool IsReadOnly { get; set; }
    bool IsVisible { get; set; }
    UTKPropertyType PropertyType { get; }
    string? GroupId { get; set; }

    object GetValue();
    void SetValue(object value);

    // UIToolkit 전용
    VisualElement CreateUI();
    void BindUI(VisualElement element);
    void UnbindUI(VisualElement element);
}

5.2 IUTKPropertyGroup

public interface IUTKPropertyGroup : IUTKPropertyEntry
{
    string GroupId { get; }
    string GroupName { get; set; }
    bool IsExpanded { get; set; }
    bool IsVisible { get; set; }
    IReadOnlyList<IUTKPropertyItem> Items { get; }

    void AddItem(IUTKPropertyItem item);
    void RemoveItem(string itemId);
    void Clear();

    event Action<IUTKPropertyItem>? ItemAdded;
    event Action<IUTKPropertyItem>? ItemRemoved;
}

5.3 UTKPropertyList 주요 API

[UxmlElement]
public partial class UTKPropertyList : VisualElement, IDisposable
{
    // 이벤트
    public event Action<UTKPropertyValueChangedEventArgs>? OnPropertyValueChanged;
    public event Action<IUTKPropertyGroup, bool>? OnGroupExpandedChanged;
    public event Action<IUTKPropertyItem>? OnPropertyClicked;

    // 데이터 로드 메서드
    public void LoadProperties(List<IUTKPropertyItem> items);
    public void LoadGroupedProperties(List<IUTKPropertyGroup> groups);
    public void LoadMixedProperties(List<IUTKPropertyEntry> entries);

    // 그룹 관리
    public void AddGroup(IUTKPropertyGroup group);
    public void RemoveGroup(string groupId);
    public IUTKPropertyGroup? GetGroup(string groupId);
    public void SetGroupExpanded(string groupId, bool expanded);
    public void ToggleGroupExpanded(string groupId);

    // 속성 관리
    public void AddProperty(IUTKPropertyItem item);
    public void AddPropertyToGroup(string groupId, IUTKPropertyItem item);
    public void RemoveProperty(string itemId);
    public IUTKPropertyItem? GetProperty(string itemId);

    // 값 관리
    public void UpdatePropertyValue(string propertyId, object newValue);
    public void SetPropertyValue(string propertyId, object value);

    // 가시성/읽기전용
    public void SetPropertyVisibility(string propertyId, bool visible);
    public void SetGroupVisibility(string groupId, bool visible);
    public void SetPropertyReadOnly(string propertyId, bool isReadOnly);
    public void SetGroupReadOnly(string groupId, bool isReadOnly);

    // 유틸리티
    public void Clear();
    public void Refresh();
    public void Show();
    public void Hide();
}

6. 구현 세부사항

6.1 가상화 전략 (ListView vs TreeView)

권장: TreeView 사용

이유:

  • 그룹의 펼침/접힘이 트리 구조와 자연스럽게 매핑
  • UTKAccordionList에서 검증된 패턴
  • 아이템 수가 많아질 때 성능 이점
private TreeView _treeView;

private void InitializeTreeView()
{
    _treeView = this.Q<TreeView>("property-tree-view");
    _treeView.makeItem = MakePropertyItem;
    _treeView.bindItem = BindPropertyItem;
    _treeView.unbindItem = UnbindPropertyItem;
    _treeView.itemExpandedChanged += OnItemExpandedChanged;
    _treeView.selectionType = SelectionType.None; // 선택 비활성화
}

6.2 아이템 타입별 UI 생성

private VisualElement MakePropertyItem()
{
    var container = new VisualElement();
    container.AddToClassList("utk-property-item-container");
    return container;
}

private void BindPropertyItem(VisualElement element, int index)
{
    var itemData = _treeView.GetItemDataForIndex<IUTKPropertyEntry>(index);
    if (itemData == null) return;

    element.Clear();

    switch (itemData)
    {
        case IUTKPropertyGroup group:
            BindGroupItem(element, group);
            break;
        case IUTKPropertyItem item:
            BindPropertyItemByType(element, item);
            break;
    }
}

private void BindPropertyItemByType(VisualElement container, IUTKPropertyItem item)
{
    VisualElement itemUI = item.PropertyType switch
    {
        UTKPropertyType.String => CreateStringPropertyUI(item),
        UTKPropertyType.Int => CreateIntPropertyUI(item),
        UTKPropertyType.Float => CreateFloatPropertyUI(item),
        UTKPropertyType.Bool => CreateBoolPropertyUI(item),
        UTKPropertyType.Vector2 => CreateVector2PropertyUI(item),
        UTKPropertyType.Vector3 => CreateVector3PropertyUI(item),
        UTKPropertyType.Color => CreateColorPropertyUI(item),
        // ... 나머지 타입들
        _ => CreateFallbackPropertyUI(item)
    };

    container.Add(itemUI);
    item.BindUI(itemUI);
}

6.3 값 변경 처리 (디바운싱 적용)

public class UTKStringPropertyItem : UTKPropertyItemBase<string>
{
    private TextField? _textField;
    private CancellationTokenSource? _debounceCts;
    private const int DEBOUNCE_MS = 300;

    public override void BindUI(VisualElement element)
    {
        _textField = element.Q<TextField>("value-field");
        if (_textField != null)
        {
            _textField.value = Value;
            _textField.SetEnabled(!IsReadOnly);
            _textField.RegisterValueChangedCallback(OnTextChanged);
        }
    }

    private void OnTextChanged(ChangeEvent<string> evt)
    {
        _debounceCts?.Cancel();
        _debounceCts = new CancellationTokenSource();

        DebounceValueChange(evt.newValue, _debounceCts.Token).Forget();
    }

    private async UniTaskVoid DebounceValueChange(string newValue, CancellationToken ct)
    {
        try
        {
            await UniTask.Delay(DEBOUNCE_MS, cancellationToken: ct);

            var oldValue = Value;
            Value = newValue;
            NotifyValueChanged(oldValue, newValue);
        }
        catch (OperationCanceledException) { }
    }

    public override void UnbindUI(VisualElement element)
    {
        _debounceCts?.Cancel();
        _debounceCts?.Dispose();
        _debounceCts = null;

        if (_textField != null)
        {
            _textField.UnregisterValueChangedCallback(OnTextChanged);
            _textField = null;
        }
    }
}

6.4 그룹 펼침/접힘 애니메이션

// USS
.utk-property-group-content {
    overflow: hidden;
    transition: height 0.2s ease-out;
}

.utk-property-group-content.collapsed {
    height: 0;
}

// C#
private void OnGroupHeaderClicked(IUTKPropertyGroup group)
{
    group.IsExpanded = !group.IsExpanded;

    var contentElement = _groupElements[group.GroupId].Q("group-content");
    if (group.IsExpanded)
    {
        contentElement.RemoveFromClassList("collapsed");
        _treeView.ExpandItem(group.TreeViewId);
    }
    else
    {
        contentElement.AddToClassList("collapsed");
        _treeView.CollapseItem(group.TreeViewId);
    }

    OnGroupExpandedChanged?.Invoke(group, group.IsExpanded);
}

6.5 메모리 관리

public void Dispose()
{
    if (_disposed) return;
    _disposed = true;

    // 1. 모든 이벤트 구독 해제
    if (_treeView != null)
    {
        _treeView.makeItem = null;
        _treeView.bindItem = null;
        _treeView.unbindItem = null;
        _treeView.itemExpandedChanged -= OnItemExpandedChanged;
    }

    // 2. 캔슬레이션 토큰 정리
    _debounceCts?.Cancel();
    _debounceCts?.Dispose();
    _debounceCts = null;

    // 3. 데이터 정리
    foreach (var entry in _entries)
    {
        if (entry is IDisposable disposable)
            disposable.Dispose();
    }
    _entries.Clear();
    _groupIndex.Clear();
    _itemIndex.Clear();

    // 4. 이벤트 핸들러 정리
    OnPropertyValueChanged = null;
    OnGroupExpandedChanged = null;
    OnPropertyClicked = null;

    // 5. UI 참조 정리
    _treeView = null;
    _searchField = null;
}

7. 구현 단계별 작업

Phase 0: 선행 의존 컴포넌트 (0단계) ⚠️ 필수

순서 작업 상세
0-1 UTKTooltip VisualElement 기반 툴팁 시스템
0-2 UTKModalBlocker 모달 배경 차단 컴포넌트
0-3 UTKColorPicker HSV + RGB 컬러 피커 모달
0-4 UTKDatePicker 캘린더 기반 날짜/시간 피커 모달

⚠️ Phase 0이 완료되어야 Phase 1~9 진행 가능

Phase 1: 기반 구조 (1단계)

순서 작업 상세
1-1 디렉토리 생성 Scripts/UVC/UIToolkit/Property/ 구조 생성
1-2 Core 인터페이스 IUTKPropertyEntry, IUTKPropertyItem, IUTKPropertyGroup
1-3 타입 열거형 UTKPropertyType (기존 PropertyType과 동일)
1-4 기본 데이터 클래스 UTKPropertyItemBase 추상 클래스
1-5 이벤트 클래스 UTKPropertyValueChangedEventArgs

Phase 2: 기본 속성 타입 (2단계)

순서 작업 UXML/USS
2-1 UTKStringPropertyItem UTKStringPropertyItem.uxml/uss
2-2 UTKIntPropertyItem UTKIntPropertyItem.uxml/uss
2-3 UTKFloatPropertyItem UTKFloatPropertyItem.uxml/uss
2-4 UTKBoolPropertyItem UTKBoolPropertyItem.uxml/uss

Phase 3: 복합 속성 타입 (3단계)

순서 작업 UXML/USS
3-1 UTKVector2PropertyItem UTKVector2PropertyItem.uxml/uss
3-2 UTKVector3PropertyItem UTKVector3PropertyItem.uxml/uss
3-3 UTKColorPropertyItem UTKColorPropertyItem.uxml/uss

Phase 4: 날짜/시간 속성 타입 (4단계)

순서 작업 UXML/USS
4-1 UTKDatePropertyItem UTKDatePropertyItem.uxml/uss
4-2 UTKDateTimePropertyItem UTKDateTimePropertyItem.uxml/uss
4-3 UTKDateRangePropertyItem 공용 UXML 사용
4-4 UTKDateTimeRangePropertyItem 공용 UXML 사용

Phase 5: 선택 속성 타입 (5단계)

순서 작업 UXML/USS
5-1 UTKEnumPropertyItem UTKEnumPropertyItem.uxml/uss
5-2 UTKDropdownPropertyItem UTKDropdownPropertyItem.uxml/uss
5-3 UTKRadioPropertyItem UTKRadioPropertyItem.uxml/uss

Phase 6: 범위/기타 속성 타입 (6단계)

순서 작업 UXML/USS
6-1 UTKIntRangePropertyItem UTKRangePropertyItem.uxml (공용)
6-2 UTKFloatRangePropertyItem 공용 UXML 사용
6-3 UTKColorStatePropertyItem UTKColorStatePropertyItem.uxml/uss

Phase 7: 그룹 및 리스트 (7단계)

순서 작업 UXML/USS
7-1 UTKPropertyGroup UTKPropertyGroup.uxml/uss
7-2 UTKPropertyList UTKPropertyList.uxml/uss
7-3 TreeView 바인딩 makeItem/bindItem/unbindItem
7-4 그룹 펼침/접힘 itemExpandedChanged 처리

Phase 8: 윈도우 및 최적화 (8단계)

순서 작업 상세
8-1 UTKPropertyWindow 헤더, 닫기 버튼 포함 래퍼
8-2 검색 기능 속성명/값 필터링 (선택적)
8-3 성능 최적화 디바운싱, 캐싱 검증
8-4 메모리 누수 테스트 Dispose 패턴 검증

Phase 9: 테스트 및 샘플 (9단계)

순서 작업 상세
9-1 UTKPropertyWindowSample 기존 샘플과 동일한 데이터
9-2 성능 비교 IMGUI vs UIToolkit
9-3 문서화 XML 주석 완성

8. 관심사 분리 원칙

8.1 데이터/로직 분리

┌─────────────────────────────────────────────────────────┐
│                    Data Layer                           │
│  - UTKPropertyItemData (순수 데이터)                    │
│  - UTKPropertyGroupData (순수 데이터)                   │
│  - 비즈니스 로직 없음, 직렬화 가능                      │
└─────────────────────────────────────────────────────────┘
                           ▼
┌─────────────────────────────────────────────────────────┐
│                   Logic Layer                           │
│  - UTKPropertyList (Controller)                         │
│  - 데이터 관리, 이벤트 발생                             │
│  - UI 생성 위임                                         │
└─────────────────────────────────────────────────────────┘
                           ▼
┌─────────────────────────────────────────────────────────┐
│                    View Layer                           │
│  - UTKPropertyItemBase (추상 UI)                        │
│  - 각 타입별 PropertyItem (구체적 UI)                   │
│  - UXML/USS (선언적 스타일)                             │
└─────────────────────────────────────────────────────────┘

8.2 단일 책임 원칙 적용

클래스 책임
UTKPropertyItemData 데이터 저장 및 직렬화
UTKPropertyList 데이터 관리 및 TreeView 조율
UTKPropertyItemBase UI 생성/바인딩 공통 로직
UTKXxxPropertyItem 특정 타입 UI 렌더링
UTKPropertyGroup 그룹 헤더 렌더링
UTKPropertyWindow 윈도우 프레임 제공

9. 성능 고려사항

9.1 가상화 필수

  • TreeView의 가상화로 수백 개 속성도 부드럽게 처리
  • makeItem/bindItem/unbindItem 패턴 준수
  • 불필요한 요소 생성 방지

9.2 이벤트 디바운싱

// 텍스트 입력 시 300ms 디바운스
// 슬라이더 드래그 시 50ms 디바운스
// 색상 피커 시 100ms 디바운스

9.3 UXML 템플릿 캐싱

private static readonly Dictionary<UTKPropertyType, VisualTreeAsset> _templateCache = new();

private VisualTreeAsset GetTemplate(UTKPropertyType type)
{
    if (!_templateCache.TryGetValue(type, out var template))
    {
        template = Resources.Load<VisualTreeAsset>(GetTemplatePath(type));
        _templateCache[type] = template;
    }
    return template;
}

9.4 USS 통합

  • 공통 스타일은 UTKPropertyCommon.uss에 집중
  • 타입별 스타일은 개별 USS에 분리
  • CSS 변수 활용으로 테마 지원

10. 메모리 관리 체크리스트

  • 모든 이벤트 구독은 Dispose에서 해제
  • CancellationTokenSource는 Cancel() 후 Dispose()
  • Dictionary/List는 Clear() 호출
  • VisualElement 참조는 null 할당
  • 순환 참조 방지 (WeakReference 검토)
  • Unity 리소스(Sprite 등) 명시적 언로드

11. 테스트 시나리오

11.1 기능 테스트

시나리오 검증 항목
속성 로드 16가지 타입 모두 정상 렌더링
값 변경 이벤트 발생 및 데이터 동기화
그룹 토글 펼침/접힘 상태 유지
읽기전용 입력 비활성화 및 시각적 표시
가시성 개별/그룹 표시/숨김
Undo/Redo SetPropertyValue로 값 복원

11.2 성능 테스트

시나리오 목표
100개 속성 로드 < 100ms
500개 속성 스크롤 60 FPS 유지
빠른 값 변경 메모리 누수 없음
반복 Dispose GC 부담 최소화

12. 마이그레이션 가이드

12.1 기존 코드 호환성

// 기존 (IMGUI)
propertyWindow.LoadMixedProperties(entries);
propertyWindow.PropertyValueChanged += OnPropertyValueChanged;

// 신규 (UIToolkit) - 동일한 API 유지
utkPropertyWindow.LoadMixedProperties(entries);
utkPropertyWindow.OnPropertyValueChanged += OnPropertyValueChanged;

12.2 데이터 클래스 재사용

기존 PropertyItem, PropertyGroup 클래스는 그대로 사용 가능하도록 어댑터 제공:

// 기존 데이터를 UTK 형식으로 변환
var utkEntries = LegacyPropertyConverter.Convert(existingEntries);
utkPropertyList.LoadMixedProperties(utkEntries);

13. 참고 자료

13.1 기존 코드 위치

파일 경로
PropertyWindow Assets/Scripts/UVC/UI/Window/PropertyWindow/PropertyWindow.cs
PropertyView Assets/Scripts/UVC/UI/Window/PropertyWindow/PropertyView.cs
PropertyItem Assets/Scripts/UVC/UI/Window/PropertyWindow/PropertyItem.cs
PropertyGroup Assets/Scripts/UVC/UI/Window/PropertyWindow/Core/PropertyGroup.cs
PropertyUIBase Assets/Scripts/UVC/UI/Window/PropertyWindow/View/PropertyUIBase.cs
샘플 Assets/Sample/PropertyWindowSample.cs

13.2 UIToolkit 참조 코드

파일 용도
UTKAccordionList TreeView 가상화, 드래그, 검색 패턴
UTKComponentList ListView 바인딩, 이벤트 패턴
UTKComponentListWindow Window 래퍼 패턴

13.3 의존 컴포넌트 기존 코드

파일 경로
ColorPicker Assets/Scripts/UVC/UI/Modal/ColorPicker/ColorPicker.cs
DatePicker Assets/Scripts/UVC/UI/Modal/DatePicker/DatePicker.cs
TooltipManager Assets/Scripts/UVC/UI/Tooltip/TooltipManager.cs
TooltipHandler Assets/Scripts/UVC/UI/Tooltip/TooltipHandler.cs

14. 승인 및 리뷰

  • 설계 리뷰 완료
  • 기능 명세 확정
  • 성능 요구사항 합의
  • 마이그레이션 계획 승인

작성일: 2026-01-06 작성자: Claude Code 버전: 1.0