UTKProperyWindow 개발 중

This commit is contained in:
logonkhi
2026-02-03 20:43:36 +09:00
parent 297ca29082
commit 8181eae4c6
74 changed files with 1268 additions and 385 deletions

179
CLAUDE.md
View File

@@ -11,6 +11,31 @@
- **UI Toolkit(UIElements) 필수 사용**. uGUI(Canvas 기반)는 레거시로 취급합니다.
- UXML(구조)과 USS(스타일)를 분리하고, C# 코드에서 인라인 스타일 지정을 지양합니다.
### 이벤트 콜백 등록 규칙
**⚠️ 중요: `RegisterValueChangedCallback` 대신 `RegisterCallback<ChangeEvent<T>>`를 사용합니다.**
`RegisterValueChangedCallback`은 확장 메서드로 `UnregisterCallback`과 대칭이 맞지 않아 이벤트 해제가 어렵습니다.
```csharp
// ❌ 잘못된 예: 해제가 어려운 방식
field.RegisterValueChangedCallback(OnValueChanged);
// field.UnregisterValueChangedCallback(OnValueChanged); // 이런 메서드 없음!
// ✅ 올바른 예: 대칭적인 등록/해제
field.RegisterCallback<ChangeEvent<float>>(OnValueChanged);
field.UnregisterCallback<ChangeEvent<float>>(OnValueChanged);
private void OnValueChanged(ChangeEvent<float> evt)
{
// evt.newValue, evt.previousValue 사용
}
```
| 메서드 | 권장 | 이유 |
|--------|------|------|
| `RegisterValueChangedCallback` | ❌ | 해제용 메서드 없음 |
| `RegisterCallback<ChangeEvent<T>>` | ✅ | `UnregisterCallback` 대칭 |
### 커스텀 VisualElement (Unity 6)
Unity 6에서는 **레거시 `UxmlFactory`/`UxmlTraits` 방식을 사용하지 않고**, 소스 생성기 기반의 `[UxmlElement]``[UxmlAttribute]`를 사용합니다.
@@ -369,45 +394,149 @@ public async UniTask<UserData?> LoadUserAsync(string userId, CancellationToken c
---
## 11) View 기본 패턴
## 11) UTK 컴포넌트 기본 패턴
UTK 컴포넌트는 다음 패턴을 따릅니다. `UTKHelpBox`를 예시로 설명합니다.
```csharp
#nullable enable
using System;
using System.Threading;
using Cysharp.Threading.Tasks;
using UnityEngine;
using UnityEngine.UIElements;
/// <summary>
/// UI Toolkit View 기본 구조.
/// </summary>
public abstract class UIViewBase : IDisposable
namespace UVC.UIToolkit
{
protected VisualElement Root { get; private set; } = null!;
protected CancellationTokenSource? Cts { get; private set; }
public virtual void Initialize(VisualElement root)
/// <summary>
/// 컴포넌트 설명.
/// </summary>
[UxmlElement]
public partial class UTKExample : VisualElement, IDisposable
{
Root = root ?? throw new ArgumentNullException(nameof(root));
Cts = new CancellationTokenSource();
QueryElements();
RegisterEvents();
}
#region Constants
private const string UXML_PATH = "UIToolkit/Common/UTKExample";
private const string USS_PATH = "UIToolkit/Common/UTKExampleUss";
#endregion
protected abstract void QueryElements();
protected abstract void RegisterEvents();
protected abstract void UnregisterEvents();
#region Fields
private bool _disposed;
private Label? _label;
private string _text = "";
#endregion
public virtual void Dispose()
{
UnregisterEvents();
Cts?.Cancel();
Cts?.Dispose();
Cts = null;
#region Properties
/// <summary>텍스트</summary>
[UxmlAttribute("text")]
public string Text
{
get => _text;
set
{
_text = value;
if (_label != null)
_label.text = value;
}
}
#endregion
#region Constructor
public UTKExample() : base()
{
// 1. 테마 적용
UTKThemeManager.Instance.ApplyThemeToElement(this);
// 2. USS 로드
var uss = Resources.Load<StyleSheet>(USS_PATH);
if (uss != null)
{
styleSheets.Add(uss);
}
// 3. UI 생성 (UXML 또는 Fallback)
CreateUI();
// 4. 테마 변경 구독
SubscribeToThemeChanges();
}
public UTKExample(string text) : this()
{
Text = text;
}
#endregion
#region Setup
private void CreateUI()
{
AddToClassList("utk-example");
var asset = Resources.Load<VisualTreeAsset>(UXML_PATH);
if (asset != null)
{
CreateUIFromUxml(asset);
}
else
{
CreateUIFallback();
}
}
private void CreateUIFromUxml(VisualTreeAsset asset)
{
var root = asset.Instantiate();
// UXML 요소 참조 가져오기
_label = root.Q<Label>("label");
Add(root);
}
private void CreateUIFallback()
{
_label = new Label(_text);
_label.AddToClassList("utk-example__label");
Add(_label);
}
private void SubscribeToThemeChanges()
{
UTKThemeManager.Instance.OnThemeChanged += OnThemeChanged;
RegisterCallback<DetachFromPanelEvent>(_ =>
{
UTKThemeManager.Instance.OnThemeChanged -= OnThemeChanged;
});
}
private void OnThemeChanged(UTKTheme theme)
{
UTKThemeManager.Instance.ApplyThemeToElement(this);
}
#endregion
#region IDisposable
public void Dispose()
{
if (_disposed) return;
_disposed = true;
UTKThemeManager.Instance.OnThemeChanged -= OnThemeChanged;
}
#endregion
}
}
```
### 주요 패턴 요약
| 항목 | 설명 |
|------|------|
| **`[UxmlElement]`** | UXML에서 사용 가능하도록 등록 |
| **`partial class`** | 소스 생성기 요구사항 |
| **`[UxmlAttribute]`** | UXML 속성 매핑 (케밥 케이스) |
| **`IDisposable`** | 리소스 정리 인터페이스 |
| **`UTKThemeManager`** | 테마 스타일시트 적용 |
| **`DetachFromPanelEvent`** | 패널에서 분리 시 이벤트 해제 |
| **UXML 경로** | `Resources.Load<VisualTreeAsset>()` 사용 |
| **USS 경로** | `Resources.Load<StyleSheet>()` 사용, 파일명에 `Uss` 접미사 |
| **Fallback 패턴** | UXML 로드 실패 시 코드로 UI 생성 |
---
## 12) Unity Nullable 주의