# 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) ```csharp // 싱글톤 패턴 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 설계 ```csharp /// /// UIToolkit 기반 툴팁 매니저 /// VisualElement에 마우스 오버 시 툴팁 표시 /// 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) ```csharp public class ColorPicker : MonoBehaviour { // 정적 팩토리 메서드 public static async UniTask 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 설계 ```csharp /// /// UIToolkit 기반 컬러 피커 모달 /// HSV 색공간 + RGB 슬라이더 + Hex 입력 지원 /// [UxmlElement] public partial class UTKColorPicker : VisualElement, IDisposable { // 이벤트 public event Action? OnColorChanged; public event Action? 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) ```csharp public class DatePicker : MonoBehaviour { public static async UniTask Show(DateTime initialDate, Action onDateSelected); public static void Hide(); // 캘린더 UI public TextMeshProUGUI _yearNumText; public TextMeshProUGUI _monthNumText; private List _dateItems; // 네비게이션 public void YearPrev(); public void YearNext(); public void MonthPrev(); public void MonthNext(); // 날짜 선택 public void OnDateItemClick(string day); } ``` #### UIToolkit 설계 ```csharp /// /// UIToolkit 기반 날짜/시간 피커 모달 /// 캘린더 그리드 + 시간 선택 지원 /// [UxmlElement] public partial class UTKDatePicker : VisualElement, IDisposable { // 모드 public enum PickerMode { DateOnly, DateAndTime } // 이벤트 public event Action? 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 설계 ```csharp /// /// 모달 뒤의 배경을 차단하고 클릭 시 닫힘 처리 /// [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 공통 아키텍처 패턴 ```csharp // 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(UXML_PATH); visualTree.CloneTree(this); // 3. 이벤트 기반 통신 (Action 델리게이트) public event Action? OnItemClick; public event Action? OnItemVisibilityChanged; // 4. IDisposable 패턴으로 리소스 정리 public void Dispose() { ... } ``` ### 3.2 데이터 바인딩 패턴 ```csharp // 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 ```csharp 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 ```csharp public interface IUTKPropertyGroup : IUTKPropertyEntry { string GroupId { get; } string GroupName { get; set; } bool IsExpanded { get; set; } bool IsVisible { get; set; } IReadOnlyList Items { get; } void AddItem(IUTKPropertyItem item); void RemoveItem(string itemId); void Clear(); event Action? ItemAdded; event Action? ItemRemoved; } ``` ### 5.3 UTKPropertyList 주요 API ```csharp [UxmlElement] public partial class UTKPropertyList : VisualElement, IDisposable { // 이벤트 public event Action? OnPropertyValueChanged; public event Action? OnGroupExpandedChanged; public event Action? OnPropertyClicked; // 데이터 로드 메서드 public void LoadProperties(List items); public void LoadGroupedProperties(List groups); public void LoadMixedProperties(List 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에서 검증된 패턴 - 아이템 수가 많아질 때 성능 이점 ```csharp private TreeView _treeView; private void InitializeTreeView() { _treeView = this.Q("property-tree-view"); _treeView.makeItem = MakePropertyItem; _treeView.bindItem = BindPropertyItem; _treeView.unbindItem = UnbindPropertyItem; _treeView.itemExpandedChanged += OnItemExpandedChanged; _treeView.selectionType = SelectionType.None; // 선택 비활성화 } ``` ### 6.2 아이템 타입별 UI 생성 ```csharp 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(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 값 변경 처리 (디바운싱 적용) ```csharp public class UTKStringPropertyItem : UTKPropertyItemBase { private TextField? _textField; private CancellationTokenSource? _debounceCts; private const int DEBOUNCE_MS = 300; public override void BindUI(VisualElement element) { _textField = element.Q("value-field"); if (_textField != null) { _textField.value = Value; _textField.SetEnabled(!IsReadOnly); _textField.RegisterValueChangedCallback(OnTextChanged); } } private void OnTextChanged(ChangeEvent 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 그룹 펼침/접힘 애니메이션 ```csharp // 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 메모리 관리 ```csharp 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 이벤트 디바운싱 ```csharp // 텍스트 입력 시 300ms 디바운스 // 슬라이더 드래그 시 50ms 디바운스 // 색상 피커 시 100ms 디바운스 ``` ### 9.3 UXML 템플릿 캐싱 ```csharp private static readonly Dictionary _templateCache = new(); private VisualTreeAsset GetTemplate(UTKPropertyType type) { if (!_templateCache.TryGetValue(type, out var template)) { template = Resources.Load(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 기존 코드 호환성 ```csharp // 기존 (IMGUI) propertyWindow.LoadMixedProperties(entries); propertyWindow.PropertyValueChanged += OnPropertyValueChanged; // 신규 (UIToolkit) - 동일한 API 유지 utkPropertyWindow.LoadMixedProperties(entries); utkPropertyWindow.OnPropertyValueChanged += OnPropertyValueChanged; ``` ### 12.2 데이터 클래스 재사용 기존 `PropertyItem`, `PropertyGroup` 클래스는 그대로 사용 가능하도록 어댑터 제공: ```csharp // 기존 데이터를 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