UTKProperyWIndow 수정 중

This commit is contained in:
logonkhi
2026-02-02 19:33:27 +09:00
parent f2d0f3d423
commit 297ca29082
118 changed files with 3043 additions and 1924 deletions

View File

@@ -0,0 +1,547 @@
# UVC.UIToolkit 컴포넌트 라이브러리
본 문서는 `Assets/Scripts/UVC/UIToolkit` 폴더의 UI Toolkit 컴포넌트 라이브러리에 대한 참조 가이드입니다.
---
## 1) 폴더 구조
```
Assets/Scripts/UVC/UIToolkit/
├── Button/ # 버튼 컴포넌트
│ ├── UTKButton.cs # 메인 버튼 (9가지 variant)
│ ├── UTKCheckBox.cs # 체크박스
│ ├── UTKToggle.cs # 토글 스위치
│ ├── UTKRadioButton.cs # 라디오 버튼
│ └── UTKToggleButtonGroup.cs # 토글 버튼 그룹
├── Card/
│ └── UTKCard.cs # 카드 컨테이너
├── Common/ # 공통 유틸리티
│ ├── UTKCodeBlock.cs # 코드 블록 표시
│ ├── UTKFoldout.cs # 접이식 컨테이너
│ ├── UTKHelpBox.cs # 도움말/정보 박스
│ ├── UTKImage.cs # 이미지 로더 (내부/URL)
│ ├── UTKImageIcons.cs # 이미지 아이콘 레지스트리
│ ├── UTKMaterialIcons.cs # Material Symbols (4102개)
│ ├── UTKScrollView.cs # 커스텀 스크롤 뷰
│ └── UTKTooltipManager.cs # 툴팁 시스템
├── Dropdown/
│ ├── UTKDropdown.cs # 일반 드롭다운
│ └── UTKEnumDropDown.cs # Enum 전용 드롭다운
├── Input/ # 입력 필드
│ ├── UTKBoundsField.cs # Bounds 입력
│ ├── UTKDoubleField.cs # Double 입력
│ ├── UTKFloatField.cs # Float 입력
│ ├── UTKInputField.cs # 텍스트 입력
│ ├── UTKIntegerField.cs # Integer 입력
│ ├── UTKLongField.cs # Long 입력
│ ├── UTKNumberStepper.cs # 숫자 스테퍼 (+/-)
│ ├── UTKRectField.cs # Rect 입력
│ ├── UTKVector2Field.cs # Vector2 입력
│ ├── UTKVector3Field.cs # Vector3 입력
│ └── UTKVector4Field.cs # Vector4 입력
├── Label/
│ └── UTKLabel.cs # 커스텀 레이블
├── List/ # 리스트/트리 컴포넌트
│ ├── UTKAccordionList.cs # 아코디언 리스트 (섹션+그리드)
│ ├── UTKAccordionListItemData.cs
│ ├── UTKComponentList.cs # 컴포넌트 리스트
│ ├── UTKComponentListItemData.cs
│ ├── UTKImageList.cs # 이미지 그리드 리스트
│ ├── UTKImageListItemData.cs
│ ├── UTKListView.cs # 일반 리스트 뷰
│ ├── UTKMultiColumnListView.cs # 다중 컬럼 리스트
│ ├── UTKMultiColumnTreeView.cs # 다중 컬럼 트리
│ ├── UTKTreeListItemData.cs
│ └── UTKTreeView.cs # 트리 뷰
├── Modal/ # 모달/다이얼로그
│ ├── UTKAlert.cs # 알림 다이얼로그
│ ├── UTKColorPicker.cs # 색상 선택기
│ ├── UTKDatePicker.cs # 날짜 선택기
│ ├── UTKModal.cs # 기본 모달
│ ├── UTKModalBlocker.cs # 모달 배경 블로커
│ ├── UTKNotification.cs # 알림 토스트
│ ├── UTKPanel.cs # 패널 컨테이너
│ └── UTKToast.cs # 토스트 알림
├── Property/ # 프로퍼티 에디터 시스템
│ ├── Core/ # 핵심 인터페이스
│ │ ├── IUTKPropertyEntry.cs
│ │ ├── IUTKPropertyGroup.cs
│ │ ├── IUTKPropertyItem.cs
│ │ ├── UTKPropertyGroup.cs
│ │ ├── UTKPropertyType.cs
│ │ └── UTKPropertyValueChangedEventArgs.cs
│ ├── Data/ # 데이터 타입
│ │ ├── UTKColorState.cs
│ │ └── UTKRangeValue.cs
│ ├── Items/ # 프로퍼티 아이템 (16종)
│ │ ├── Base/UTKPropertyItemBase.cs
│ │ ├── UTKBoolPropertyItem.cs
│ │ ├── UTKColorPropertyItem.cs
│ │ ├── UTKColorStatePropertyItem.cs
│ │ ├── UTKDatePropertyItem.cs
│ │ ├── UTKDateRangePropertyItem.cs
│ │ ├── UTKDateTimePropertyItem.cs
│ │ ├── UTKDateTimeRangePropertyItem.cs
│ │ ├── UTKDropdownPropertyItem.cs
│ │ ├── UTKEnumPropertyItem.cs
│ │ ├── UTKFloatPropertyItem.cs
│ │ ├── UTKFloatRangePropertyItem.cs
│ │ ├── UTKIntPropertyItem.cs
│ │ ├── UTKIntRangePropertyItem.cs
│ │ ├── UTKRadioPropertyItem.cs
│ │ ├── UTKStringPropertyItem.cs
│ │ ├── UTKVector2PropertyItem.cs
│ │ └── UTKVector3PropertyItem.cs
│ ├── UTKPropertyList.cs # 프로퍼티 리스트 컨테이너
│ └── UTKPropertyWindow.cs # 프로퍼티 윈도우
├── Slider/
│ ├── UTKMinMaxSlider.cs # 범위 슬라이더
│ ├── UTKProgressBar.cs # 프로그레스 바
│ └── UTKSlider.cs # 단일 슬라이더
├── Tab/
│ ├── UTKTab.cs # 개별 탭
│ └── UTKTabView.cs # 탭 컨테이너
├── Window/ # 윈도우 컴포넌트
│ ├── UTKAccordionListWindow.cs
│ ├── UTKComponentListWindow.cs
│ ├── UTKComponentTabListWindow.cs
│ ├── UTKImageListWindow.cs
│ └── UTKTreeListWindow.cs
├── UTKStyleGuide.cs # 스타일 상수 및 색상
└── UTKThemeManager.cs # 테마 관리 (Light/Dark)
```
---
## 2) 핵심 시스템
### 테마 관리 (UTKThemeManager)
라이트/다크 테마 전환을 관리하는 싱글톤입니다.
```csharp
// 테마 등록 및 전환
UTKThemeManager.Instance.RegisterRoot(rootVisualElement);
UTKThemeManager.Instance.SetTheme(UTKTheme.Dark);
UTKThemeManager.Instance.ToggleTheme();
// 테마 변경 이벤트 구독
UTKThemeManager.OnThemeChanged += OnThemeChanged;
// 관리 USS 파일
// - UTKVariables.uss (CSS 변수)
// - UTKThemeDark.uss / UTKThemeLight.uss (테마별 스타일)
// - UTKComponents.uss (컴포넌트 공통 스타일)
```
### 스타일 가이드 (UTKStyleGuide)
색상, 간격, 폰트, 컴포넌트 크기 등의 상수를 정의합니다.
| 카테고리 | 설명 |
|----------|------|
| Base Colors | Base01~Base21 기본 색상 팔레트 |
| Accent Colors | Blue, Red, Green, Purple, Yellow, Orange, Gray, Seti |
| Semantic Colors | Text, Background, Border, Button, Calendar, State, Scrollbar |
### 아이콘 시스템
**1순위: Material Icons (UTKMaterialIcons)**
```csharp
// 폰트 로드
await UTKMaterialIcons.LoadFontAsync();
// 아이콘 적용
var icon = UTKMaterialIcons.GetIcon("settings");
UTKMaterialIcons.ApplyIconStyle(label, fontSize: 24);
// 버튼에 아이콘 설정
button.SetMaterialIcon(UTKMaterialIcons.Home);
```
**2순위: Image Icons (UTKImageIcons)**
```csharp
// 필요한 아이콘이 Material에 없는 경우에만 사용
var texture = UTKImageIcons.LoadTexture("path/to/icon");
button.SetImageIcon(UTKImageIcons.CustomIcon);
```
> **참고**: Material Icons는 4,102개의 아이콘을 제공하며, 폰트 기반으로 메모리 효율이 높습니다.
---
## 3) 주요 컴포넌트
### UTKButton
다양한 스타일과 크기를 지원하는 버튼 컴포넌트입니다.
**Variants:**
- Normal, Primary, Secondary, Ghost, Danger
- OutlineNormal, OutlinePrimary, OutlineDanger, Text
**Sizes:** Small, Medium, Large
```csharp
var button = new UTKButton("확인", UTKButton.Variant.Primary, UTKButton.Size.Medium);
button.SetMaterialIcon(UTKMaterialIcons.Check);
button.OnClicked += () => Debug.Log("Clicked!");
// UXML 사용
<utk:UTKButton text="확인"
icon="check"
variant="Primary"
size="Medium"
icon-only="false" />
```
### UTKAccordionList
섹션별 접기/펼치기가 가능한 아코디언 리스트입니다.
```csharp
var accordionList = new UTKAccordionList();
accordionList.SetData(sections); // List<UTKAccordionListItemData>
accordionList.Search("검색어");
accordionList.ExpandAll();
accordionList.CollapseAll();
// 이벤트
accordionList.OnItemClick += (item) => { };
accordionList.OnDragExitList += (item) => { };
accordionList.OnSectionToggled += (section, expanded) => { };
```
**특징:**
- TreeView 기반 가상화
- 검색 필터링
- 드래그 앤 드롭 지원
- 비동기 이미지 로딩 및 캐싱
- 그리드 레이아웃 (2열)
### UTKTreeView / UTKTreeListWindow
계층 구조 데이터를 트리 형태로 표시합니다.
```csharp
var treeView = new UTKTreeView();
treeView.SetData(rootItems); // List<UTKTreeListItemData>
// TreeListWindow 사용
var window = new UTKTreeListWindow();
window.SetData(items);
window.OnItemSelected += (item) => { };
```
### UTKModal / UTKColorPicker / UTKDatePicker
모달 다이얼로그 및 특수 선택기입니다.
```csharp
// 기본 모달
var modal = new UTKModal("제목");
modal.AddContent(contentElement);
modal.AddToFooter(confirmButton);
modal.Show();
modal.OnClosed += () => { };
// 색상 선택기
var colorPicker = new UTKColorPicker();
colorPicker.Color = Color.red;
colorPicker.OnColorChanged += (color) => { };
// 날짜 선택기
var datePicker = new UTKDatePicker();
datePicker.SelectedDate = DateTime.Now;
datePicker.OnDateSelected += (date) => { };
```
### UTKImage
내부 리소스 및 외부 URL에서 이미지를 로드합니다.
```csharp
var image = new UTKImage();
// Resources 로드
await image.LoadAsync("UIToolkit/Icons/sample");
// HTTP/HTTPS 로드
await image.LoadAsync("https://example.com/image.png");
// 이벤트
image.OnImageLoaded += (texture) => { };
image.OnImageFailed += (error) => { };
// 스케일 모드
image.ScaleMode = ScaleMode.ScaleToFit; // ScaleToFit, ScaleAndCrop, StretchToFill
```
---
## 4) 프로퍼티 에디터 시스템
Inspector 스타일의 프로퍼티 편집 시스템입니다.
### 아키텍처
```
IUTKPropertyEntry (기본 인터페이스)
├── IUTKPropertyItem<T> # 개별 프로퍼티
│ ├── OnValueChanged event
│ ├── GetValue() / SetValue()
│ └── CreateUI() / BindUI() / UnbindUI()
└── IUTKPropertyGroup # 프로퍼티 그룹
├── Items 컬렉션
├── AddItem() / RemoveItem()
└── OnExpandedChanged event
```
### 프로퍼티 타입 (16종)
| 타입 | 클래스 | 설명 |
|------|--------|------|
| String | UTKStringPropertyItem | 텍스트 입력 |
| Int | UTKIntPropertyItem | 정수 (슬라이더 옵션) |
| Float | UTKFloatPropertyItem | 실수 (슬라이더 옵션) |
| Bool | UTKBoolPropertyItem | 토글 스위치 |
| Vector2 | UTKVector2PropertyItem | 2D 벡터 |
| Vector3 | UTKVector3PropertyItem | 3D 벡터 |
| Color | UTKColorPropertyItem | 색상 선택기 |
| Date | UTKDatePropertyItem | 날짜 선택 |
| DateTime | UTKDateTimePropertyItem | 날짜+시간 선택 |
| Enum | UTKEnumPropertyItem | Enum 드롭다운 |
| Dropdown | UTKDropdownPropertyItem | 문자열 드롭다운 |
| Radio | UTKRadioPropertyItem | 라디오 버튼 그룹 |
| IntRange | UTKIntRangePropertyItem | 정수 범위 (Min/Max) |
| FloatRange | UTKFloatRangePropertyItem | 실수 범위 |
| DateRange | UTKDateRangePropertyItem | 날짜 범위 |
| DateTimeRange | UTKDateTimeRangePropertyItem | 날짜+시간 범위 |
| ColorState | UTKColorStatePropertyItem | 색상+상태 (커스텀) |
### 사용 예시
```csharp
// 프로퍼티 윈도우 생성
var propertyWindow = new UTKPropertyWindow("설정");
// 그룹 생성
var generalGroup = new UTKPropertyGroup("일반 설정");
// 프로퍼티 아이템 추가
generalGroup.AddItem(new UTKStringPropertyItem("이름", "기본값"));
generalGroup.AddItem(new UTKIntPropertyItem("레벨", 1, minValue: 1, maxValue: 100));
generalGroup.AddItem(new UTKBoolPropertyItem("활성화", true));
generalGroup.AddItem(new UTKColorPropertyItem("색상", Color.white));
// 그룹을 윈도우에 추가
propertyWindow.PropertyList.AddGroup(generalGroup);
// 값 변경 이벤트
generalGroup.OnValueChanged += (sender, args) => {
Debug.Log($"{args.PropertyName}: {args.OldValue} → {args.NewValue}");
};
```
**특징:**
- 디바운스 지원 (기본 300ms)
- 읽기 전용 모드
- 툴팁 지원
- IDisposable 패턴으로 메모리 관리
---
## 5) Window 컴포넌트
### UTKAccordionListWindow
아코디언 리스트를 포함한 윈도우입니다.
```csharp
var window = new UTKAccordionListWindow();
window.SetTitle("아이템 목록");
window.SetData(sections);
window.OnItemSelected += (item) => { };
```
### UTKComponentListWindow
컴포넌트 리스트 윈도우입니다.
```csharp
var window = new UTKComponentListWindow();
window.SetData(components);
window.OnComponentSelected += (component) => { };
```
### UTKTreeListWindow
트리 구조 데이터를 표시하는 윈도우입니다.
```csharp
var window = new UTKTreeListWindow();
window.SetData(treeItems);
window.OnItemSelected += (item) => { };
window.ExpandAll();
```
### UTKImageListWindow
이미지 그리드를 표시하는 윈도우입니다.
```csharp
var window = new UTKImageListWindow();
window.SetData(images);
window.OnImageSelected += (image) => { };
```
---
## 6) 입력 필드
### 기본 입력 필드
```csharp
// 정수 입력
var intField = new UTKIntegerField("수량");
intField.Value = 10;
intField.OnValueChanged += (value) => { };
// 실수 입력
var floatField = new UTKFloatField("크기");
floatField.Value = 1.5f;
// 벡터 입력
var vector3Field = new UTKVector3Field("위치");
vector3Field.Value = Vector3.zero;
```
### UTKNumberStepper
증가/감소 버튼이 있는 숫자 입력 필드입니다.
```csharp
var stepper = new UTKNumberStepper();
stepper.Value = 5;
stepper.Min = 0;
stepper.Max = 100;
stepper.Step = 1;
```
---
## 7) UXML 속성 규칙
Unity 6 소스 생성기 기반의 `[UxmlElement]``[UxmlAttribute]`를 사용합니다.
```csharp
[UxmlElement]
public partial class UTKCustomComponent : VisualElement
{
[UxmlAttribute("title")]
public string Title { get; set; }
[UxmlAttribute("is-enabled")]
public bool IsEnabled { get; set; }
[UxmlAttribute("border-width")]
public int BorderWidth { get; set; }
}
```
**UXML 사용:**
```xml
<utk:UTKCustomComponent title="제목" is-enabled="true" border-width="2" />
```
**주의사항:**
- 클래스는 반드시 `partial`로 선언
- 속성명은 케밥 케이스(kebab-case) 소문자 사용
- 레거시 `UxmlFactory`/`UxmlTraits` 방식 사용 금지
---
## 8) 네이밍 규칙
| 항목 | 규칙 | 예시 |
|------|------|------|
| 클래스 | UTK 접두사 + PascalCase | `UTKButton`, `UTKAccordionList` |
| UXML 파일 | `{ComponentName}.uxml` | `UTKAccordionListWindow.uxml` |
| USS 파일 | `{ComponentName}Uss.uss` | `UTKAccordionListWindowUss.uss` |
| Public 멤버 | PascalCase | `OnClicked`, `Value` |
| Private 필드 | _camelCase | `_button`, `_labelCache` |
| 이벤트 | On 접두사 | `OnValueChanged`, `OnClicked` |
> **중요**: UXML과 USS 파일명을 다르게 지정해야 `Resources.Load<T>()` 충돌을 방지할 수 있습니다.
---
## 9) 메모리 관리 패턴
### 이벤트 구독/해제
```csharp
private EventCallback<ClickEvent>? _onClick;
void OnEnable()
{
_onClick = OnButtonClick;
_button?.RegisterCallback(_onClick);
UTKThemeManager.OnThemeChanged += OnThemeChanged;
}
void OnDisable()
{
_button?.UnregisterCallback(_onClick);
UTKThemeManager.OnThemeChanged -= OnThemeChanged;
}
```
### IDisposable 패턴
```csharp
public void Dispose()
{
UnregisterEvents();
_cts?.Cancel();
_cts?.Dispose();
_cts = null;
}
```
### 체크리스트
- [ ] `RegisterCallback<T>``UnregisterCallback<T>` 대칭 확인
- [ ] `UTKThemeManager.OnThemeChanged` 구독 해제
- [ ] `CancellationTokenSource` 정리 (`Cancel``Dispose`)
- [ ] 람다/클로저 캡처로 인한 누수 점검
- [ ] 이미지 캐시 정리 (해당 시)
---
## 10) 주요 API 요약
| 카테고리 | 클래스 | 주요 메서드 |
|----------|--------|-------------|
| **테마** | UTKThemeManager | `RegisterRoot`, `SetTheme`, `ToggleTheme` |
| **버튼** | UTKButton | `SetMaterialIcon`, `SetImageIcon`, `OnClicked` |
| **리스트** | UTKAccordionList | `SetData`, `Search`, `ExpandAll`, `OnItemClick` |
| **트리** | UTKTreeView | `SetData`, `ExpandAll`, `CollapseAll` |
| **프로퍼티** | UTKPropertyWindow | `LoadProperties`, `AddGroup`, `UpdatePropertyValue` |
| **모달** | UTKModal | `Show`, `Close`, `AddContent`, `AddToFooter` |
| **이미지** | UTKImage | `LoadAsync`, `SetTexture`, `ClearImage` |
| **입력** | UTK*Field | `Value`, `OnValueChanged` |
---
## 11) 참조 리소스
| 경로 | 설명 |
|------|------|
| `Assets/Resources/UIToolkit/Style/` | USS 스타일 파일 |
| `Assets/Resources/UIToolkit/Common/` | 공통 UXML 컴포넌트 |
| `Assets/Resources/UIToolkit/Window/` | 윈도우 UXML 템플릿 |
| `Assets/Resources/UIToolkit/Modal/` | 모달 UXML 템플릿 |
| `Assets/Resources/UIToolkit/List/` | 리스트 UXML 템플릿 |
| `Assets/Resources/UIToolkit/Property/` | 프로퍼티 UXML 템플릿 |

View File

@@ -1,5 +1,5 @@
fileFormatVersion: 2
guid: c578c7c6f7f524444942296a64ad16be
guid: 212bcd23b16ea4443a97af8619e3d333
TextScriptImporter:
externalObjects: {}
userData:

View File

@@ -83,24 +83,24 @@ namespace UVC.UIToolkit
/// </code>
/// <para><b>UXML에서 사용:</b></para>
/// <code>
/// &lt;ui:UXML xmlns:utk="UVC.UIToolkit"&gt;
/// &lt;!-- 내부 리소스 이미지 --&gt;
/// &lt;utk:UTKImage src="UIToolkit/Images/icon_setting_22"
/// style="width: 48px; height: 48px;" /&gt;
/// <ui:UXML xmlns:utk="UVC.UIToolkit">
/// <!-- 내부 리소스 이미지 -->
/// <utk:UTKImage src="UIToolkit/Images/icon_setting_22"
/// style="width: 48px; height: 48px;" />
///
/// &lt;!-- 외부 URL 이미지 --&gt;
/// &lt;utk:UTKImage src="https://example.com/image.png"
/// <!-- 외부 URL 이미지 -->
/// <utk:UTKImage src="https://example.com/image.png"
/// style="width: 200px; height: 150px;"
/// scale-mode="ScaleToFit" /&gt;
/// scale-mode="ScaleToFit" />
///
/// &lt;!-- 둥근 프로필 이미지 --&gt;
/// &lt;utk:UTKImage src="https://api.example.com/avatar"
/// class="profile-avatar" /&gt;
/// <!-- 둥근 프로필 이미지 -->
/// <utk:UTKImage src="https://api.example.com/avatar"
/// class="profile-avatar" />
///
/// &lt;!-- 틴트 색상 적용 --&gt;
/// &lt;utk:UTKImage src="UIToolkit/Images/icon_home"
/// tint-color="#FF5500" /&gt;
/// &lt;/ui:UXML&gt;
/// <!-- 틴트 색상 적용 -->
/// <utk:UTKImage src="UIToolkit/Images/icon_home"
/// tint-color="#FF5500" />
/// </ui:UXML>
/// </code>
/// </example>
[UxmlElement]

View File

@@ -1,8 +0,0 @@
fileFormatVersion: 2
guid: e911fc8aa11c95841af462e5a845974b
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -1,5 +1,6 @@
#nullable enable
using System;
using System.Collections.Generic;
using System.Threading;
using Cysharp.Threading.Tasks;
using UnityEngine;
@@ -18,6 +19,11 @@ namespace UVC.UIToolkit
protected const int DEFAULT_DEBOUNCE_MS = 300;
protected const string USS_CLASS_READONLY = "utk-property-item--readonly";
protected const string USS_CLASS_HIDDEN = "utk-property-item--hidden";
protected const string UXML_BASE_PATH = "UIToolkit/Property/Items/";
#endregion
#region Static Cache
private static readonly Dictionary<string, VisualTreeAsset> _uxmlCache = new();
#endregion
#region Fields
@@ -243,9 +249,9 @@ namespace UVC.UIToolkit
_rootElement.SetTooltip(_tooltip);
}
protected Label CreateNameLabel()
protected UTKLabel CreateNameLabel()
{
var label = new Label(Name);
var label = new UTKLabel(Name, UTKLabel.LabelSize.Body2);
label.AddToClassList("utk-property-item__label");
return label;
}
@@ -263,6 +269,62 @@ namespace UVC.UIToolkit
return container;
}
/// <summary>
/// UXML 템플릿에서 UI 생성
/// </summary>
/// <param name="uxmlName">UXML 파일명 (확장자 제외)</param>
/// <returns>생성된 루트 VisualElement</returns>
protected VisualElement? CreateUIFromUxml(string uxmlName)
{
var asset = LoadUxmlAsset(uxmlName);
if (asset == null)
{
Debug.LogWarning($"[UTKPropertyItem] UXML not found: {uxmlName}");
return null;
}
var root = asset.Instantiate();
var itemRoot = root.Q<VisualElement>("item-root") ?? root;
// name-label에 Name 설정
var nameLabel = itemRoot.Q<UTKLabel>("name-label");
if (nameLabel != null)
{
nameLabel.Text = Name;
}
return itemRoot;
}
/// <summary>
/// UXML 에셋 로드 (캐시 사용)
/// </summary>
protected static VisualTreeAsset? LoadUxmlAsset(string uxmlName)
{
string path = UXML_BASE_PATH + uxmlName;
if (_uxmlCache.TryGetValue(path, out var cached))
{
return cached;
}
var asset = Resources.Load<VisualTreeAsset>(path);
if (asset != null)
{
_uxmlCache[path] = asset;
}
return asset;
}
/// <summary>
/// UXML 캐시 클리어 (에디터 용도)
/// </summary>
public static void ClearUxmlCache()
{
_uxmlCache.Clear();
}
#endregion
#region IDisposable

View File

@@ -10,7 +10,7 @@ namespace UVC.UIToolkit
public class UTKBoolPropertyItem : UTKPropertyItemBase<bool>
{
#region Fields
private Toggle? _toggle;
private UTKToggle? _toggle;
#endregion
#region Properties
@@ -26,6 +26,23 @@ namespace UVC.UIToolkit
#region Override Methods
public override VisualElement CreateUI()
{
var container = CreateUIFromUxml("UTKBoolPropertyItem");
if (container == null)
{
return CreateUIFallback();
}
_toggle = container.Q<UTKToggle>("value-field");
if (_toggle != null)
{
_toggle.IsOn = Value;
}
return container;
}
private VisualElement CreateUIFallback()
{
var container = CreateContainer();
@@ -35,9 +52,9 @@ namespace UVC.UIToolkit
var valueContainer = new VisualElement();
valueContainer.AddToClassList("utk-property-item__value");
_toggle = new Toggle();
_toggle = new UTKToggle();
_toggle.name = "value-field";
_toggle.value = Value;
_toggle.IsOn = Value;
valueContainer.Add(_toggle);
container.Add(valueContainer);
@@ -49,12 +66,12 @@ namespace UVC.UIToolkit
{
base.BindUI(element);
_toggle = element.Q<Toggle>("value-field");
_toggle = element.Q<UTKToggle>("value-field");
if (_toggle != null)
{
_toggle.value = Value;
_toggle.SetEnabled(!IsReadOnly);
_toggle.RegisterValueChangedCallback(OnToggleChanged);
_toggle.IsOn = Value;
_toggle.IsEnabled = !IsReadOnly;
_toggle.OnValueChanged += OnToggleChanged;
}
}
@@ -62,7 +79,7 @@ namespace UVC.UIToolkit
{
if (_toggle != null)
{
_toggle.UnregisterValueChangedCallback(OnToggleChanged);
_toggle.OnValueChanged -= OnToggleChanged;
_toggle = null;
}
@@ -71,9 +88,9 @@ namespace UVC.UIToolkit
public override void RefreshUI()
{
if (_toggle != null && _toggle.value != Value)
if (_toggle != null && _toggle.IsOn != Value)
{
_toggle.SetValueWithoutNotify(Value);
_toggle.SetOn(Value, false);
}
}
@@ -83,16 +100,16 @@ namespace UVC.UIToolkit
if (_toggle != null)
{
_toggle.SetEnabled(!IsReadOnly);
_toggle.IsEnabled = !IsReadOnly;
}
}
#endregion
#region Private Methods
private void OnToggleChanged(ChangeEvent<bool> evt)
private void OnToggleChanged(bool newValue)
{
// Toggle은 디바운스 없이 즉시 적용
Value = evt.newValue;
Value = newValue;
}
#endregion
}

View File

@@ -12,8 +12,8 @@ namespace UVC.UIToolkit
{
#region Fields
private VisualElement? _colorPreview;
private Button? _pickerButton;
private TextField? _hexField;
private UTKButton? _pickerButton;
private UTKInputField? _hexField;
private UTKColorPicker? _currentPicker;
private bool _useAlpha;
#endregion
@@ -39,6 +39,31 @@ namespace UVC.UIToolkit
#region Override Methods
public override VisualElement CreateUI()
{
var container = CreateUIFromUxml("UTKColorPropertyItem");
if (container == null)
{
return CreateUIFallback();
}
_colorPreview = container.Q<VisualElement>("color-preview");
_hexField = container.Q<UTKInputField>("hex-field");
_pickerButton = container.Q<UTKButton>("picker-btn");
if (_colorPreview != null)
{
_colorPreview.style.backgroundColor = Value;
}
if (_hexField != null)
{
_hexField.Value = ColorToHex(Value);
}
return container;
}
private VisualElement CreateUIFallback()
{
var container = CreateContainer();
@@ -56,17 +81,16 @@ namespace UVC.UIToolkit
valueContainer.Add(_colorPreview);
// Hex 입력
_hexField = new TextField();
_hexField = new UTKInputField();
_hexField.name = "hex-field";
_hexField.value = ColorToHex(Value);
_hexField.Value = ColorToHex(Value);
_hexField.style.width = 80;
_hexField.style.marginLeft = 5;
valueContainer.Add(_hexField);
// 피커 버튼
_pickerButton = new Button();
_pickerButton = new UTKButton("...", "", UTKButton.ButtonVariant.Secondary);
_pickerButton.name = "picker-btn";
_pickerButton.text = "...";
_pickerButton.AddToClassList("utk-property-item__picker-btn");
valueContainer.Add(_pickerButton);
@@ -80,8 +104,8 @@ namespace UVC.UIToolkit
base.BindUI(element);
_colorPreview = element.Q<VisualElement>("color-preview");
_hexField = element.Q<TextField>("hex-field");
_pickerButton = element.Q<Button>("picker-btn");
_hexField = element.Q<UTKInputField>("hex-field");
_pickerButton = element.Q<UTKButton>("picker-btn");
if (_colorPreview != null)
{
@@ -91,15 +115,15 @@ namespace UVC.UIToolkit
if (_hexField != null)
{
_hexField.value = ColorToHex(Value);
_hexField.Value = ColorToHex(Value);
_hexField.SetEnabled(!IsReadOnly);
_hexField.RegisterValueChangedCallback(OnHexChanged);
_hexField.OnValueChanged += OnHexChanged;
}
if (_pickerButton != null)
{
_pickerButton.SetEnabled(!IsReadOnly);
_pickerButton.clicked += OnPickerButtonClicked;
_pickerButton.IsEnabled = !IsReadOnly;
_pickerButton.OnClicked += OnPickerButtonClicked;
}
}
@@ -113,13 +137,13 @@ namespace UVC.UIToolkit
if (_hexField != null)
{
_hexField.UnregisterValueChangedCallback(OnHexChanged);
_hexField.OnValueChanged -= OnHexChanged;
_hexField = null;
}
if (_pickerButton != null)
{
_pickerButton.clicked -= OnPickerButtonClicked;
_pickerButton.OnClicked -= OnPickerButtonClicked;
_pickerButton = null;
}
@@ -138,9 +162,9 @@ namespace UVC.UIToolkit
if (_hexField != null)
{
var hex = ColorToHex(Value);
if (_hexField.value != hex)
if (_hexField.Value != hex)
{
_hexField.SetValueWithoutNotify(hex);
_hexField.SetValue(hex, false);
}
}
}
@@ -150,7 +174,10 @@ namespace UVC.UIToolkit
base.UpdateReadOnlyState();
_hexField?.SetEnabled(!IsReadOnly);
_pickerButton?.SetEnabled(!IsReadOnly);
if (_pickerButton != null)
{
_pickerButton.IsEnabled = !IsReadOnly;
}
}
#endregion
@@ -205,7 +232,7 @@ namespace UVC.UIToolkit
if (_hexField != null)
{
_hexField.SetValueWithoutNotify(ColorToHex(color));
_hexField.SetValue(ColorToHex(color), false);
}
}
@@ -215,9 +242,9 @@ namespace UVC.UIToolkit
ClosePicker();
}
private void OnHexChanged(ChangeEvent<string> evt)
private void OnHexChanged(string newValue)
{
if (TryParseHex(evt.newValue, out Color color))
if (TryParseHex(newValue, out Color color))
{
DebounceValueChange(color, 300).Forget();
}

View File

@@ -11,9 +11,9 @@ namespace UVC.UIToolkit
public class UTKColorStatePropertyItem : UTKPropertyItemBase<UTKColorState>
{
#region Fields
private Label? _stateLabel;
private UTKLabel? _stateLabel;
private VisualElement? _colorPreview;
private Button? _pickerButton;
private UTKButton? _pickerButton;
private UTKColorPicker? _currentPicker;
#endregion
@@ -35,6 +35,31 @@ namespace UVC.UIToolkit
#region Override Methods
public override VisualElement CreateUI()
{
var container = CreateUIFromUxml("UTKColorStatePropertyItem");
if (container == null)
{
return CreateUIFallback();
}
_stateLabel = container.Q<UTKLabel>("state-label");
_colorPreview = container.Q<VisualElement>("color-preview");
_pickerButton = container.Q<UTKButton>("picker-btn");
if (_stateLabel != null)
{
_stateLabel.Text = Value.State;
}
if (_colorPreview != null)
{
_colorPreview.style.backgroundColor = Value.Color;
}
return container;
}
private VisualElement CreateUIFallback()
{
var container = CreateContainer();
@@ -46,7 +71,7 @@ namespace UVC.UIToolkit
valueContainer.style.flexDirection = FlexDirection.Row;
// 상태 라벨
_stateLabel = new Label(Value.State);
_stateLabel = new UTKLabel(Value.State, UTKLabel.LabelSize.Body2);
_stateLabel.name = "state-label";
_stateLabel.AddToClassList("utk-property-item__state-label");
valueContainer.Add(_stateLabel);
@@ -59,7 +84,7 @@ namespace UVC.UIToolkit
valueContainer.Add(_colorPreview);
// 피커 버튼
_pickerButton = new Button { text = "..." };
_pickerButton = new UTKButton("...", "", UTKButton.ButtonVariant.Secondary);
_pickerButton.name = "picker-btn";
_pickerButton.AddToClassList("utk-property-item__picker-btn");
valueContainer.Add(_pickerButton);
@@ -73,13 +98,13 @@ namespace UVC.UIToolkit
{
base.BindUI(element);
_stateLabel = element.Q<Label>("state-label");
_stateLabel = element.Q<UTKLabel>("state-label");
_colorPreview = element.Q<VisualElement>("color-preview");
_pickerButton = element.Q<Button>("picker-btn");
_pickerButton = element.Q<UTKButton>("picker-btn");
if (_stateLabel != null)
{
_stateLabel.text = Value.State;
_stateLabel.Text = Value.State;
}
if (_colorPreview != null)
@@ -90,8 +115,8 @@ namespace UVC.UIToolkit
if (_pickerButton != null)
{
_pickerButton.SetEnabled(!IsReadOnly);
_pickerButton.clicked += OnPickerButtonClicked;
_pickerButton.IsEnabled = !IsReadOnly;
_pickerButton.OnClicked += OnPickerButtonClicked;
}
}
@@ -105,7 +130,7 @@ namespace UVC.UIToolkit
if (_pickerButton != null)
{
_pickerButton.clicked -= OnPickerButtonClicked;
_pickerButton.OnClicked -= OnPickerButtonClicked;
_pickerButton = null;
}
@@ -116,9 +141,9 @@ namespace UVC.UIToolkit
public override void RefreshUI()
{
if (_stateLabel != null && _stateLabel.text != Value.State)
if (_stateLabel != null && _stateLabel.Text != Value.State)
{
_stateLabel.text = Value.State;
_stateLabel.Text = Value.State;
}
if (_colorPreview != null)
@@ -130,7 +155,7 @@ namespace UVC.UIToolkit
protected override void UpdateReadOnlyState()
{
base.UpdateReadOnlyState();
_pickerButton?.SetEnabled(!IsReadOnly);
if (_pickerButton != null) _pickerButton.IsEnabled = !IsReadOnly;
}
#endregion

View File

@@ -11,8 +11,8 @@ namespace UVC.UIToolkit
public class UTKDatePropertyItem : UTKPropertyItemBase<DateTime>
{
#region Fields
private TextField? _dateField;
private Button? _pickerButton;
private UTKInputField? _dateField;
private UTKButton? _pickerButton;
private UTKDatePicker? _currentPicker;
private string _dateFormat = "yyyy-MM-dd";
#endregion
@@ -41,6 +41,25 @@ namespace UVC.UIToolkit
#region Override Methods
public override VisualElement CreateUI()
{
var container = CreateUIFromUxml("UTKDatePropertyItem");
if (container == null)
{
return CreateUIFallback();
}
_dateField = container.Q<UTKInputField>("date-field");
_pickerButton = container.Q<UTKButton>("picker-btn");
if (_dateField != null)
{
_dateField.Value = Value.ToString(_dateFormat);
}
return container;
}
private VisualElement CreateUIFallback()
{
var container = CreateContainer();
@@ -50,15 +69,14 @@ namespace UVC.UIToolkit
var valueContainer = new VisualElement();
valueContainer.AddToClassList("utk-property-item__value");
_dateField = new TextField();
_dateField = new UTKInputField();
_dateField.name = "date-field";
_dateField.value = Value.ToString(_dateFormat);
_dateField.Value = Value.ToString(_dateFormat);
_dateField.style.flexGrow = 1;
valueContainer.Add(_dateField);
_pickerButton = new Button();
_pickerButton = new UTKButton("...", "", UTKButton.ButtonVariant.Secondary);
_pickerButton.name = "picker-btn";
_pickerButton.text = "...";
_pickerButton.AddToClassList("utk-property-item__picker-btn");
valueContainer.Add(_pickerButton);
@@ -71,20 +89,20 @@ namespace UVC.UIToolkit
{
base.BindUI(element);
_dateField = element.Q<TextField>("date-field");
_pickerButton = element.Q<Button>("picker-btn");
_dateField = element.Q<UTKInputField>("date-field");
_pickerButton = element.Q<UTKButton>("picker-btn");
if (_dateField != null)
{
_dateField.value = Value.ToString(_dateFormat);
_dateField.Value = Value.ToString(_dateFormat);
_dateField.SetEnabled(!IsReadOnly);
_dateField.RegisterValueChangedCallback(OnDateTextChanged);
_dateField.OnValueChanged += OnDateTextChanged;
}
if (_pickerButton != null)
{
_pickerButton.SetEnabled(!IsReadOnly);
_pickerButton.clicked += OnPickerButtonClicked;
_pickerButton.IsEnabled = !IsReadOnly;
_pickerButton.OnClicked += OnPickerButtonClicked;
}
}
@@ -92,13 +110,13 @@ namespace UVC.UIToolkit
{
if (_dateField != null)
{
_dateField.UnregisterValueChangedCallback(OnDateTextChanged);
_dateField.OnValueChanged -= OnDateTextChanged;
_dateField = null;
}
if (_pickerButton != null)
{
_pickerButton.clicked -= OnPickerButtonClicked;
_pickerButton.OnClicked -= OnPickerButtonClicked;
_pickerButton = null;
}
@@ -112,9 +130,9 @@ namespace UVC.UIToolkit
if (_dateField != null)
{
var formatted = Value.ToString(_dateFormat);
if (_dateField.value != formatted)
if (_dateField.Value != formatted)
{
_dateField.SetValueWithoutNotify(formatted);
_dateField.SetValue(formatted, false);
}
}
}
@@ -124,7 +142,7 @@ namespace UVC.UIToolkit
base.UpdateReadOnlyState();
_dateField?.SetEnabled(!IsReadOnly);
_pickerButton?.SetEnabled(!IsReadOnly);
if (_pickerButton != null) _pickerButton.IsEnabled = !IsReadOnly;
}
#endregion
@@ -171,9 +189,9 @@ namespace UVC.UIToolkit
_currentPicker = null;
}
private void OnDateTextChanged(ChangeEvent<string> evt)
private void OnDateTextChanged(string newValue)
{
if (DateTime.TryParse(evt.newValue, out DateTime date))
if (DateTime.TryParse(newValue, out DateTime date))
{
DebounceValueChange(date.Date, 500).Forget();
}

View File

@@ -11,10 +11,10 @@ namespace UVC.UIToolkit
public class UTKDateRangePropertyItem : UTKPropertyItemBase<UTKDateRange>
{
#region Fields
private TextField? _startField;
private TextField? _endField;
private Button? _startPickerBtn;
private Button? _endPickerBtn;
private UTKInputField? _startField;
private UTKInputField? _endField;
private UTKButton? _startPickerBtn;
private UTKButton? _endPickerBtn;
private UTKDatePicker? _currentPicker;
private bool _isEditingStart;
private string _dateFormat = "yyyy-MM-dd";
@@ -48,6 +48,32 @@ namespace UVC.UIToolkit
#region Override Methods
public override VisualElement CreateUI()
{
var container = CreateUIFromUxml("UTKDateRangePropertyItem");
if (container == null)
{
return CreateUIFallback();
}
_startField = container.Q<UTKInputField>("start-field");
_endField = container.Q<UTKInputField>("end-field");
_startPickerBtn = container.Q<UTKButton>("start-picker-btn");
_endPickerBtn = container.Q<UTKButton>("end-picker-btn");
if (_startField != null)
{
_startField.Value = Value.Start.ToString(_dateFormat);
}
if (_endField != null)
{
_endField.Value = Value.End.ToString(_dateFormat);
}
return container;
}
private VisualElement CreateUIFallback()
{
var container = CreateContainer();
@@ -59,29 +85,29 @@ namespace UVC.UIToolkit
valueContainer.style.flexDirection = FlexDirection.Row;
// Start field
_startField = new TextField();
_startField = new UTKInputField();
_startField.name = "start-field";
_startField.value = Value.Start.ToString(_dateFormat);
_startField.Value = Value.Start.ToString(_dateFormat);
_startField.style.flexGrow = 1;
valueContainer.Add(_startField);
_startPickerBtn = new Button { text = "..." };
_startPickerBtn = new UTKButton("...", "", UTKButton.ButtonVariant.Secondary);
_startPickerBtn.name = "start-picker-btn";
_startPickerBtn.AddToClassList("utk-property-item__picker-btn");
valueContainer.Add(_startPickerBtn);
var separator = new Label("~");
var separator = new UTKLabel("~", UTKLabel.LabelSize.Body2);
separator.AddToClassList("utk-property-item__range-separator");
valueContainer.Add(separator);
// End field
_endField = new TextField();
_endField = new UTKInputField();
_endField.name = "end-field";
_endField.value = Value.End.ToString(_dateFormat);
_endField.Value = Value.End.ToString(_dateFormat);
_endField.style.flexGrow = 1;
valueContainer.Add(_endField);
_endPickerBtn = new Button { text = "..." };
_endPickerBtn = new UTKButton("...", "", UTKButton.ButtonVariant.Secondary);
_endPickerBtn.name = "end-picker-btn";
_endPickerBtn.AddToClassList("utk-property-item__picker-btn");
valueContainer.Add(_endPickerBtn);
@@ -95,35 +121,35 @@ namespace UVC.UIToolkit
{
base.BindUI(element);
_startField = element.Q<TextField>("start-field");
_endField = element.Q<TextField>("end-field");
_startPickerBtn = element.Q<Button>("start-picker-btn");
_endPickerBtn = element.Q<Button>("end-picker-btn");
_startField = element.Q<UTKInputField>("start-field");
_endField = element.Q<UTKInputField>("end-field");
_startPickerBtn = element.Q<UTKButton>("start-picker-btn");
_endPickerBtn = element.Q<UTKButton>("end-picker-btn");
if (_startField != null)
{
_startField.value = Value.Start.ToString(_dateFormat);
_startField.Value = Value.Start.ToString(_dateFormat);
_startField.SetEnabled(!IsReadOnly);
_startField.RegisterValueChangedCallback(OnStartTextChanged);
_startField.OnValueChanged += OnStartTextChanged;
}
if (_endField != null)
{
_endField.value = Value.End.ToString(_dateFormat);
_endField.Value = Value.End.ToString(_dateFormat);
_endField.SetEnabled(!IsReadOnly);
_endField.RegisterValueChangedCallback(OnEndTextChanged);
_endField.OnValueChanged += OnEndTextChanged;
}
if (_startPickerBtn != null)
{
_startPickerBtn.SetEnabled(!IsReadOnly);
_startPickerBtn.clicked += OnStartPickerClicked;
_startPickerBtn.IsEnabled = !IsReadOnly;
_startPickerBtn.OnClicked += OnStartPickerClicked;
}
if (_endPickerBtn != null)
{
_endPickerBtn.SetEnabled(!IsReadOnly);
_endPickerBtn.clicked += OnEndPickerClicked;
_endPickerBtn.IsEnabled = !IsReadOnly;
_endPickerBtn.OnClicked += OnEndPickerClicked;
}
}
@@ -131,25 +157,25 @@ namespace UVC.UIToolkit
{
if (_startField != null)
{
_startField.UnregisterValueChangedCallback(OnStartTextChanged);
_startField.OnValueChanged -= OnStartTextChanged;
_startField = null;
}
if (_endField != null)
{
_endField.UnregisterValueChangedCallback(OnEndTextChanged);
_endField.OnValueChanged -= OnEndTextChanged;
_endField = null;
}
if (_startPickerBtn != null)
{
_startPickerBtn.clicked -= OnStartPickerClicked;
_startPickerBtn.OnClicked -= OnStartPickerClicked;
_startPickerBtn = null;
}
if (_endPickerBtn != null)
{
_endPickerBtn.clicked -= OnEndPickerClicked;
_endPickerBtn.OnClicked -= OnEndPickerClicked;
_endPickerBtn = null;
}
@@ -162,18 +188,18 @@ namespace UVC.UIToolkit
if (_startField != null)
{
var formatted = Value.Start.ToString(_dateFormat);
if (_startField.value != formatted)
if (_startField.Value != formatted)
{
_startField.SetValueWithoutNotify(formatted);
_startField.SetValue(formatted, false);
}
}
if (_endField != null)
{
var formatted = Value.End.ToString(_dateFormat);
if (_endField.value != formatted)
if (_endField.Value != formatted)
{
_endField.SetValueWithoutNotify(formatted);
_endField.SetValue(formatted, false);
}
}
}
@@ -184,8 +210,8 @@ namespace UVC.UIToolkit
_startField?.SetEnabled(!IsReadOnly);
_endField?.SetEnabled(!IsReadOnly);
_startPickerBtn?.SetEnabled(!IsReadOnly);
_endPickerBtn?.SetEnabled(!IsReadOnly);
if (_startPickerBtn != null) _startPickerBtn.IsEnabled = !IsReadOnly;
if (_endPickerBtn != null) _endPickerBtn.IsEnabled = !IsReadOnly;
}
#endregion
@@ -241,21 +267,21 @@ namespace UVC.UIToolkit
private void OnPickerClosed() => _currentPicker = null;
private void OnStartTextChanged(ChangeEvent<string> evt)
private void OnStartTextChanged(string newValue)
{
if (DateTime.TryParse(evt.newValue, out DateTime date))
if (DateTime.TryParse(newValue, out DateTime date))
{
var newValue = new UTKDateRange(date.Date, Value.End);
DebounceValueChange(newValue, 500).Forget();
var range = new UTKDateRange(date.Date, Value.End);
DebounceValueChange(range, 500).Forget();
}
}
private void OnEndTextChanged(ChangeEvent<string> evt)
private void OnEndTextChanged(string newValue)
{
if (DateTime.TryParse(evt.newValue, out DateTime date))
if (DateTime.TryParse(newValue, out DateTime date))
{
var newValue = new UTKDateRange(Value.Start, date.Date);
DebounceValueChange(newValue, 500).Forget();
var range = new UTKDateRange(Value.Start, date.Date);
DebounceValueChange(range, 500).Forget();
}
}
#endregion

View File

@@ -11,8 +11,8 @@ namespace UVC.UIToolkit
public class UTKDateTimePropertyItem : UTKPropertyItemBase<DateTime>
{
#region Fields
private TextField? _dateTimeField;
private Button? _pickerButton;
private UTKInputField? _dateTimeField;
private UTKButton? _pickerButton;
private UTKDatePicker? _currentPicker;
private string _dateTimeFormat = "yyyy-MM-dd HH:mm";
#endregion
@@ -41,6 +41,25 @@ namespace UVC.UIToolkit
#region Override Methods
public override VisualElement CreateUI()
{
var container = CreateUIFromUxml("UTKDateTimePropertyItem");
if (container == null)
{
return CreateUIFallback();
}
_dateTimeField = container.Q<UTKInputField>("datetime-field");
_pickerButton = container.Q<UTKButton>("picker-btn");
if (_dateTimeField != null)
{
_dateTimeField.Value = Value.ToString(_dateTimeFormat);
}
return container;
}
private VisualElement CreateUIFallback()
{
var container = CreateContainer();
@@ -50,15 +69,14 @@ namespace UVC.UIToolkit
var valueContainer = new VisualElement();
valueContainer.AddToClassList("utk-property-item__value");
_dateTimeField = new TextField();
_dateTimeField = new UTKInputField();
_dateTimeField.name = "datetime-field";
_dateTimeField.value = Value.ToString(_dateTimeFormat);
_dateTimeField.Value = Value.ToString(_dateTimeFormat);
_dateTimeField.style.flexGrow = 1;
valueContainer.Add(_dateTimeField);
_pickerButton = new Button();
_pickerButton = new UTKButton("...", "", UTKButton.ButtonVariant.Secondary);
_pickerButton.name = "picker-btn";
_pickerButton.text = "...";
_pickerButton.AddToClassList("utk-property-item__picker-btn");
valueContainer.Add(_pickerButton);
@@ -71,20 +89,20 @@ namespace UVC.UIToolkit
{
base.BindUI(element);
_dateTimeField = element.Q<TextField>("datetime-field");
_pickerButton = element.Q<Button>("picker-btn");
_dateTimeField = element.Q<UTKInputField>("datetime-field");
_pickerButton = element.Q<UTKButton>("picker-btn");
if (_dateTimeField != null)
{
_dateTimeField.value = Value.ToString(_dateTimeFormat);
_dateTimeField.Value = Value.ToString(_dateTimeFormat);
_dateTimeField.SetEnabled(!IsReadOnly);
_dateTimeField.RegisterValueChangedCallback(OnDateTimeTextChanged);
_dateTimeField.OnValueChanged += OnDateTimeTextChanged;
}
if (_pickerButton != null)
{
_pickerButton.SetEnabled(!IsReadOnly);
_pickerButton.clicked += OnPickerButtonClicked;
_pickerButton.IsEnabled = !IsReadOnly;
_pickerButton.OnClicked += OnPickerButtonClicked;
}
}
@@ -92,13 +110,13 @@ namespace UVC.UIToolkit
{
if (_dateTimeField != null)
{
_dateTimeField.UnregisterValueChangedCallback(OnDateTimeTextChanged);
_dateTimeField.OnValueChanged -= OnDateTimeTextChanged;
_dateTimeField = null;
}
if (_pickerButton != null)
{
_pickerButton.clicked -= OnPickerButtonClicked;
_pickerButton.OnClicked -= OnPickerButtonClicked;
_pickerButton = null;
}
@@ -112,9 +130,9 @@ namespace UVC.UIToolkit
if (_dateTimeField != null)
{
var formatted = Value.ToString(_dateTimeFormat);
if (_dateTimeField.value != formatted)
if (_dateTimeField.Value != formatted)
{
_dateTimeField.SetValueWithoutNotify(formatted);
_dateTimeField.SetValue(formatted, false);
}
}
}
@@ -124,7 +142,7 @@ namespace UVC.UIToolkit
base.UpdateReadOnlyState();
_dateTimeField?.SetEnabled(!IsReadOnly);
_pickerButton?.SetEnabled(!IsReadOnly);
if (_pickerButton != null) _pickerButton.IsEnabled = !IsReadOnly;
}
#endregion
@@ -171,9 +189,9 @@ namespace UVC.UIToolkit
_currentPicker = null;
}
private void OnDateTimeTextChanged(ChangeEvent<string> evt)
private void OnDateTimeTextChanged(string newValue)
{
if (DateTime.TryParse(evt.newValue, out DateTime dateTime))
if (DateTime.TryParse(newValue, out DateTime dateTime))
{
DebounceValueChange(dateTime, 500).Forget();
}

View File

@@ -11,10 +11,10 @@ namespace UVC.UIToolkit
public class UTKDateTimeRangePropertyItem : UTKPropertyItemBase<UTKDateTimeRange>
{
#region Fields
private TextField? _startField;
private TextField? _endField;
private Button? _startPickerBtn;
private Button? _endPickerBtn;
private UTKInputField? _startField;
private UTKInputField? _endField;
private UTKButton? _startPickerBtn;
private UTKButton? _endPickerBtn;
private UTKDatePicker? _currentPicker;
private bool _isEditingStart;
private string _dateTimeFormat = "yyyy-MM-dd HH:mm";
@@ -48,6 +48,32 @@ namespace UVC.UIToolkit
#region Override Methods
public override VisualElement CreateUI()
{
var container = CreateUIFromUxml("UTKDateTimeRangePropertyItem");
if (container == null)
{
return CreateUIFallback();
}
_startField = container.Q<UTKInputField>("start-field");
_endField = container.Q<UTKInputField>("end-field");
_startPickerBtn = container.Q<UTKButton>("start-picker-btn");
_endPickerBtn = container.Q<UTKButton>("end-picker-btn");
if (_startField != null)
{
_startField.Value = Value.Start.ToString(_dateTimeFormat);
}
if (_endField != null)
{
_endField.Value = Value.End.ToString(_dateTimeFormat);
}
return container;
}
private VisualElement CreateUIFallback()
{
var container = CreateContainer();
@@ -59,29 +85,29 @@ namespace UVC.UIToolkit
valueContainer.style.flexDirection = FlexDirection.Row;
// Start field
_startField = new TextField();
_startField = new UTKInputField();
_startField.name = "start-field";
_startField.value = Value.Start.ToString(_dateTimeFormat);
_startField.Value = Value.Start.ToString(_dateTimeFormat);
_startField.style.flexGrow = 1;
valueContainer.Add(_startField);
_startPickerBtn = new Button { text = "..." };
_startPickerBtn = new UTKButton("...", "", UTKButton.ButtonVariant.Secondary);
_startPickerBtn.name = "start-picker-btn";
_startPickerBtn.AddToClassList("utk-property-item__picker-btn");
valueContainer.Add(_startPickerBtn);
var separator = new Label("~");
var separator = new UTKLabel("~", UTKLabel.LabelSize.Body2);
separator.AddToClassList("utk-property-item__range-separator");
valueContainer.Add(separator);
// End field
_endField = new TextField();
_endField = new UTKInputField();
_endField.name = "end-field";
_endField.value = Value.End.ToString(_dateTimeFormat);
_endField.Value = Value.End.ToString(_dateTimeFormat);
_endField.style.flexGrow = 1;
valueContainer.Add(_endField);
_endPickerBtn = new Button { text = "..." };
_endPickerBtn = new UTKButton("...", "", UTKButton.ButtonVariant.Secondary);
_endPickerBtn.name = "end-picker-btn";
_endPickerBtn.AddToClassList("utk-property-item__picker-btn");
valueContainer.Add(_endPickerBtn);
@@ -95,35 +121,35 @@ namespace UVC.UIToolkit
{
base.BindUI(element);
_startField = element.Q<TextField>("start-field");
_endField = element.Q<TextField>("end-field");
_startPickerBtn = element.Q<Button>("start-picker-btn");
_endPickerBtn = element.Q<Button>("end-picker-btn");
_startField = element.Q<UTKInputField>("start-field");
_endField = element.Q<UTKInputField>("end-field");
_startPickerBtn = element.Q<UTKButton>("start-picker-btn");
_endPickerBtn = element.Q<UTKButton>("end-picker-btn");
if (_startField != null)
{
_startField.value = Value.Start.ToString(_dateTimeFormat);
_startField.Value = Value.Start.ToString(_dateTimeFormat);
_startField.SetEnabled(!IsReadOnly);
_startField.RegisterValueChangedCallback(OnStartTextChanged);
_startField.OnValueChanged += OnStartTextChanged;
}
if (_endField != null)
{
_endField.value = Value.End.ToString(_dateTimeFormat);
_endField.Value = Value.End.ToString(_dateTimeFormat);
_endField.SetEnabled(!IsReadOnly);
_endField.RegisterValueChangedCallback(OnEndTextChanged);
_endField.OnValueChanged += OnEndTextChanged;
}
if (_startPickerBtn != null)
{
_startPickerBtn.SetEnabled(!IsReadOnly);
_startPickerBtn.clicked += OnStartPickerClicked;
_startPickerBtn.IsEnabled = !IsReadOnly;
_startPickerBtn.OnClicked += OnStartPickerClicked;
}
if (_endPickerBtn != null)
{
_endPickerBtn.SetEnabled(!IsReadOnly);
_endPickerBtn.clicked += OnEndPickerClicked;
_endPickerBtn.IsEnabled = !IsReadOnly;
_endPickerBtn.OnClicked += OnEndPickerClicked;
}
}
@@ -131,25 +157,25 @@ namespace UVC.UIToolkit
{
if (_startField != null)
{
_startField.UnregisterValueChangedCallback(OnStartTextChanged);
_startField.OnValueChanged -= OnStartTextChanged;
_startField = null;
}
if (_endField != null)
{
_endField.UnregisterValueChangedCallback(OnEndTextChanged);
_endField.OnValueChanged -= OnEndTextChanged;
_endField = null;
}
if (_startPickerBtn != null)
{
_startPickerBtn.clicked -= OnStartPickerClicked;
_startPickerBtn.OnClicked -= OnStartPickerClicked;
_startPickerBtn = null;
}
if (_endPickerBtn != null)
{
_endPickerBtn.clicked -= OnEndPickerClicked;
_endPickerBtn.OnClicked -= OnEndPickerClicked;
_endPickerBtn = null;
}
@@ -162,18 +188,18 @@ namespace UVC.UIToolkit
if (_startField != null)
{
var formatted = Value.Start.ToString(_dateTimeFormat);
if (_startField.value != formatted)
if (_startField.Value != formatted)
{
_startField.SetValueWithoutNotify(formatted);
_startField.SetValue(formatted, false);
}
}
if (_endField != null)
{
var formatted = Value.End.ToString(_dateTimeFormat);
if (_endField.value != formatted)
if (_endField.Value != formatted)
{
_endField.SetValueWithoutNotify(formatted);
_endField.SetValue(formatted, false);
}
}
}
@@ -184,8 +210,8 @@ namespace UVC.UIToolkit
_startField?.SetEnabled(!IsReadOnly);
_endField?.SetEnabled(!IsReadOnly);
_startPickerBtn?.SetEnabled(!IsReadOnly);
_endPickerBtn?.SetEnabled(!IsReadOnly);
if (_startPickerBtn != null) _startPickerBtn.IsEnabled = !IsReadOnly;
if (_endPickerBtn != null) _endPickerBtn.IsEnabled = !IsReadOnly;
}
#endregion
@@ -241,21 +267,21 @@ namespace UVC.UIToolkit
private void OnPickerClosed() => _currentPicker = null;
private void OnStartTextChanged(ChangeEvent<string> evt)
private void OnStartTextChanged(string newValue)
{
if (DateTime.TryParse(evt.newValue, out DateTime dateTime))
if (DateTime.TryParse(newValue, out DateTime dateTime))
{
var newValue = new UTKDateTimeRange(dateTime, Value.End);
DebounceValueChange(newValue, 500).Forget();
var range = new UTKDateTimeRange(dateTime, Value.End);
DebounceValueChange(range, 500).Forget();
}
}
private void OnEndTextChanged(ChangeEvent<string> evt)
private void OnEndTextChanged(string newValue)
{
if (DateTime.TryParse(evt.newValue, out DateTime dateTime))
if (DateTime.TryParse(newValue, out DateTime dateTime))
{
var newValue = new UTKDateTimeRange(Value.Start, dateTime);
DebounceValueChange(newValue, 500).Forget();
var range = new UTKDateTimeRange(Value.Start, dateTime);
DebounceValueChange(range, 500).Forget();
}
}
#endregion

View File

@@ -13,7 +13,7 @@ namespace UVC.UIToolkit
public class UTKDropdownPropertyItem : UTKPropertyItemBase<string>
{
#region Fields
private DropdownField? _dropdown;
private UTKDropdown? _dropdown;
private List<string> _choices;
#endregion
@@ -29,7 +29,7 @@ namespace UVC.UIToolkit
_choices = value ?? new List<string>();
if (_dropdown != null)
{
_dropdown.choices = _choices;
_dropdown.SetOptions(_choices);
}
}
}
@@ -66,6 +66,25 @@ namespace UVC.UIToolkit
#region Override Methods
public override VisualElement CreateUI()
{
var container = CreateUIFromUxml("UTKDropdownPropertyItem");
if (container == null)
{
return CreateUIFallback();
}
_dropdown = container.Q<UTKDropdown>("dropdown-field");
if (_dropdown != null)
{
_dropdown.SetOptions(_choices);
int selectedIndex = _choices.IndexOf(Value);
_dropdown.SelectedIndex = Math.Max(0, selectedIndex);
}
return container;
}
private VisualElement CreateUIFallback()
{
var container = CreateContainer();
@@ -75,9 +94,11 @@ namespace UVC.UIToolkit
var valueContainer = new VisualElement();
valueContainer.AddToClassList("utk-property-item__value");
int selectedIndex = _choices.IndexOf(Value);
_dropdown = new DropdownField(_choices, Math.Max(0, selectedIndex));
_dropdown = new UTKDropdown();
_dropdown.name = "dropdown-field";
_dropdown.SetOptions(_choices);
int selectedIndex = _choices.IndexOf(Value);
_dropdown.SelectedIndex = Math.Max(0, selectedIndex);
valueContainer.Add(_dropdown);
container.Add(valueContainer);
@@ -89,15 +110,15 @@ namespace UVC.UIToolkit
{
base.BindUI(element);
_dropdown = element.Q<DropdownField>("dropdown-field");
_dropdown = element.Q<UTKDropdown>("dropdown-field");
if (_dropdown != null)
{
_dropdown.choices = _choices;
_dropdown.SetOptions(_choices);
int selectedIndex = _choices.IndexOf(Value);
_dropdown.index = Math.Max(0, selectedIndex);
_dropdown.SetEnabled(!IsReadOnly);
_dropdown.RegisterValueChangedCallback(OnDropdownChanged);
_dropdown.SelectedIndex = Math.Max(0, selectedIndex);
_dropdown.IsEnabled = !IsReadOnly;
_dropdown.OnSelectionChanged += OnDropdownChanged;
}
}
@@ -105,7 +126,7 @@ namespace UVC.UIToolkit
{
if (_dropdown != null)
{
_dropdown.UnregisterValueChangedCallback(OnDropdownChanged);
_dropdown.OnSelectionChanged -= OnDropdownChanged;
_dropdown = null;
}
@@ -114,9 +135,9 @@ namespace UVC.UIToolkit
public override void RefreshUI()
{
if (_dropdown != null && _dropdown.value != Value)
if (_dropdown != null && _dropdown.SelectedValue != Value)
{
_dropdown.SetValueWithoutNotify(Value);
_dropdown.SetSelectedValue(Value, notify: false);
}
}
@@ -126,7 +147,7 @@ namespace UVC.UIToolkit
if (_dropdown != null)
{
_dropdown.SetEnabled(!IsReadOnly);
_dropdown.IsEnabled = !IsReadOnly;
}
}
#endregion
@@ -140,7 +161,7 @@ namespace UVC.UIToolkit
_choices.Add(choice);
if (_dropdown != null)
{
_dropdown.choices = _choices;
_dropdown.AddOption(choice);
}
}
}
@@ -151,7 +172,7 @@ namespace UVC.UIToolkit
bool removed = _choices.Remove(choice);
if (removed && _dropdown != null)
{
_dropdown.choices = _choices;
_dropdown.SetOptions(_choices);
// 현재 선택 값이 제거되면 첫 번째 항목 선택
if (Value == choice && _choices.Count > 0)
@@ -164,9 +185,9 @@ namespace UVC.UIToolkit
#endregion
#region Private Methods
private void OnDropdownChanged(ChangeEvent<string> evt)
private void OnDropdownChanged(int index, string newValue)
{
Value = evt.newValue;
Value = newValue;
}
#endregion
}

View File

@@ -1,21 +1,18 @@
#nullable enable
using System;
using System.Collections.Generic;
using System.Linq;
using UnityEngine.UIElements;
namespace UVC.UIToolkit
{
/// <summary>
/// 열거형 속성 아이템
/// DropdownField를 사용한 열거형 선택
/// UTKEnumDropDown 사용한 열거형 선택
/// </summary>
public class UTKEnumPropertyItem : UTKPropertyItemBase<Enum>
{
#region Fields
private DropdownField? _dropdown;
private UTKEnumDropDown? _enumDropdown;
private Type _enumType;
private List<string> _choices = new();
#endregion
#region Properties
@@ -30,12 +27,28 @@ namespace UVC.UIToolkit
: base(id, name, initialValue ?? throw new ArgumentNullException(nameof(initialValue)))
{
_enumType = initialValue.GetType();
_choices = Enum.GetNames(_enumType).ToList();
}
#endregion
#region Override Methods
public override VisualElement CreateUI()
{
var container = CreateUIFromUxml("UTKEnumPropertyItem");
if (container == null)
{
return CreateUIFallback();
}
_enumDropdown = container.Q<UTKEnumDropDown>("enum-dropdown");
if (_enumDropdown != null)
{
_enumDropdown.Init(Value);
}
return container;
}
private VisualElement CreateUIFallback()
{
var container = CreateContainer();
@@ -45,9 +58,10 @@ namespace UVC.UIToolkit
var valueContainer = new VisualElement();
valueContainer.AddToClassList("utk-property-item__value");
_dropdown = new DropdownField(_choices, _choices.IndexOf(Value.ToString()));
_dropdown.name = "dropdown-field";
valueContainer.Add(_dropdown);
_enumDropdown = new UTKEnumDropDown();
_enumDropdown.name = "enum-dropdown";
_enumDropdown.Init(Value);
valueContainer.Add(_enumDropdown);
container.Add(valueContainer);
@@ -58,23 +72,22 @@ namespace UVC.UIToolkit
{
base.BindUI(element);
_dropdown = element.Q<DropdownField>("dropdown-field");
_enumDropdown = element.Q<UTKEnumDropDown>("enum-dropdown");
if (_dropdown != null)
if (_enumDropdown != null)
{
_dropdown.choices = _choices;
_dropdown.index = _choices.IndexOf(Value.ToString());
_dropdown.SetEnabled(!IsReadOnly);
_dropdown.RegisterValueChangedCallback(OnDropdownChanged);
_enumDropdown.Value = Value;
_enumDropdown.IsEnabled = !IsReadOnly;
_enumDropdown.OnValueChanged += OnEnumDropdownChanged;
}
}
public override void UnbindUI(VisualElement element)
{
if (_dropdown != null)
if (_enumDropdown != null)
{
_dropdown.UnregisterValueChangedCallback(OnDropdownChanged);
_dropdown = null;
_enumDropdown.OnValueChanged -= OnEnumDropdownChanged;
_enumDropdown = null;
}
base.UnbindUI(element);
@@ -82,13 +95,9 @@ namespace UVC.UIToolkit
public override void RefreshUI()
{
if (_dropdown != null)
if (_enumDropdown != null && _enumDropdown.Value != Value)
{
var currentValue = Value.ToString();
if (_dropdown.value != currentValue)
{
_dropdown.SetValueWithoutNotify(currentValue);
}
_enumDropdown.Value = Value;
}
}
@@ -96,19 +105,19 @@ namespace UVC.UIToolkit
{
base.UpdateReadOnlyState();
if (_dropdown != null)
if (_enumDropdown != null)
{
_dropdown.SetEnabled(!IsReadOnly);
_enumDropdown.IsEnabled = !IsReadOnly;
}
}
#endregion
#region Private Methods
private void OnDropdownChanged(ChangeEvent<string> evt)
private void OnEnumDropdownChanged(Enum? newValue)
{
if (Enum.TryParse(_enumType, evt.newValue, out object? result) && result is Enum enumValue)
if (newValue != null)
{
Value = enumValue;
Value = newValue;
}
}
#endregion

View File

@@ -11,8 +11,8 @@ namespace UVC.UIToolkit
public class UTKFloatPropertyItem : UTKPropertyItemBase<float>
{
#region Fields
private FloatField? _floatField;
private Slider? _slider;
private UTKFloatField? _floatField;
private UTKSlider? _slider;
private bool _useSlider;
private float _minValue;
private float _maxValue = 1f;
@@ -74,6 +74,33 @@ namespace UVC.UIToolkit
#region Override Methods
public override VisualElement CreateUI()
{
string uxmlName = _useSlider ? "UTKFloatPropertyItemSlider" : "UTKFloatPropertyItem";
var container = CreateUIFromUxml(uxmlName);
if (container == null)
{
return CreateUIFallback();
}
_floatField = container.Q<UTKFloatField>("value-field");
_slider = container.Q<UTKSlider>("slider-field");
if (_floatField != null)
{
_floatField.Value = Value;
}
if (_slider != null)
{
_slider.lowValue = _minValue;
_slider.highValue = _maxValue;
_slider.Value = Value;
}
return container;
}
private VisualElement CreateUIFallback()
{
var container = CreateContainer();
@@ -90,23 +117,22 @@ namespace UVC.UIToolkit
if (_useSlider)
{
_slider = new Slider(_minValue, _maxValue);
_slider = new UTKSlider("", _minValue, _maxValue, Value);
_slider.name = "slider-field";
_slider.value = Value;
_slider.AddToClassList("utk-property-item__slider");
valueContainer.Add(_slider);
_floatField = new FloatField();
_floatField = new UTKFloatField();
_floatField.name = "value-field";
_floatField.value = Value;
_floatField.Value = Value;
_floatField.AddToClassList("utk-property-item__number-field");
valueContainer.Add(_floatField);
}
else
{
_floatField = new FloatField();
_floatField = new UTKFloatField();
_floatField.name = "value-field";
_floatField.value = Value;
_floatField.Value = Value;
valueContainer.Add(_floatField);
}
@@ -119,23 +145,23 @@ namespace UVC.UIToolkit
{
base.BindUI(element);
_floatField = element.Q<FloatField>("value-field");
_slider = element.Q<Slider>("slider-field");
_floatField = element.Q<UTKFloatField>("value-field");
_slider = element.Q<UTKSlider>("slider-field");
if (_floatField != null)
{
_floatField.value = Value;
_floatField.SetEnabled(!IsReadOnly);
_floatField.RegisterValueChangedCallback(OnFloatFieldChanged);
_floatField.Value = Value;
_floatField.IsEnabled = !IsReadOnly;
_floatField.OnValueChanged += OnFloatFieldChanged;
}
if (_slider != null)
{
_slider.lowValue = _minValue;
_slider.highValue = _maxValue;
_slider.value = Value;
_slider.SetEnabled(!IsReadOnly);
_slider.RegisterValueChangedCallback(OnSliderChanged);
_slider.Value = Value;
_slider.IsEnabled = !IsReadOnly;
_slider.OnValueChanged += OnSliderChanged;
}
}
@@ -143,13 +169,13 @@ namespace UVC.UIToolkit
{
if (_floatField != null)
{
_floatField.UnregisterValueChangedCallback(OnFloatFieldChanged);
_floatField.OnValueChanged -= OnFloatFieldChanged;
_floatField = null;
}
if (_slider != null)
{
_slider.UnregisterValueChangedCallback(OnSliderChanged);
_slider.OnValueChanged -= OnSliderChanged;
_slider = null;
}
@@ -158,12 +184,12 @@ namespace UVC.UIToolkit
public override void RefreshUI()
{
if (_floatField != null && !Mathf.Approximately(_floatField.value, Value))
if (_floatField != null && !Mathf.Approximately(_floatField.Value, Value))
{
_floatField.SetValueWithoutNotify(Value);
}
if (_slider != null && !Mathf.Approximately(_slider.value, Value))
if (_slider != null && !Mathf.Approximately(_slider.Value, Value))
{
_slider.SetValueWithoutNotify(Value);
}
@@ -173,17 +199,20 @@ namespace UVC.UIToolkit
{
base.UpdateReadOnlyState();
_floatField?.SetEnabled(!IsReadOnly);
_slider?.SetEnabled(!IsReadOnly);
if (_floatField != null)
{
_floatField.IsEnabled = !IsReadOnly;
}
if (_slider != null) _slider.IsEnabled = !IsReadOnly;
}
#endregion
#region Private Methods
private void OnFloatFieldChanged(ChangeEvent<float> evt)
private void OnFloatFieldChanged(float newValue)
{
float clampedValue = _useSlider ? Mathf.Clamp(evt.newValue, _minValue, _maxValue) : evt.newValue;
float clampedValue = _useSlider ? Mathf.Clamp(newValue, _minValue, _maxValue) : newValue;
if (_slider != null && !Mathf.Approximately(_slider.value, clampedValue))
if (_slider != null && !Mathf.Approximately(_slider.Value, clampedValue))
{
_slider.SetValueWithoutNotify(clampedValue);
}
@@ -191,15 +220,15 @@ namespace UVC.UIToolkit
DebounceValueChange(clampedValue, 100).Forget();
}
private void OnSliderChanged(ChangeEvent<float> evt)
private void OnSliderChanged(float newValue)
{
if (_floatField != null && !Mathf.Approximately(_floatField.value, evt.newValue))
if (_floatField != null && !Mathf.Approximately(_floatField.Value, newValue))
{
_floatField.SetValueWithoutNotify(evt.newValue);
_floatField.SetValueWithoutNotify(newValue);
}
// 슬라이더는 빠른 반응이 필요하므로 짧은 디바운스
DebounceValueChange(evt.newValue, 50).Forget();
DebounceValueChange(newValue, 50).Forget();
}
#endregion
}

View File

@@ -11,8 +11,8 @@ namespace UVC.UIToolkit
public class UTKFloatRangePropertyItem : UTKPropertyItemBase<UTKFloatRange>
{
#region Fields
private FloatField? _minField;
private FloatField? _maxField;
private UTKFloatField? _minField;
private UTKFloatField? _maxField;
#endregion
#region Properties
@@ -33,6 +33,30 @@ namespace UVC.UIToolkit
#region Override Methods
public override VisualElement CreateUI()
{
var container = CreateUIFromUxml("UTKFloatRangePropertyItem");
if (container == null)
{
return CreateUIFallback();
}
_minField = container.Q<UTKFloatField>("min-field");
_maxField = container.Q<UTKFloatField>("max-field");
if (_minField != null)
{
_minField.Value = Value.Min;
}
if (_maxField != null)
{
_maxField.Value = Value.Max;
}
return container;
}
private VisualElement CreateUIFallback()
{
var container = CreateContainer();
@@ -43,19 +67,19 @@ namespace UVC.UIToolkit
valueContainer.AddToClassList("utk-property-item__value");
valueContainer.style.flexDirection = FlexDirection.Row;
_minField = new FloatField();
_minField = new UTKFloatField();
_minField.name = "min-field";
_minField.value = Value.Min;
_minField.Value = Value.Min;
_minField.style.flexGrow = 1;
valueContainer.Add(_minField);
var separator = new Label("~");
var separator = new UTKLabel("~", UTKLabel.LabelSize.Body2);
separator.AddToClassList("utk-property-item__range-separator");
valueContainer.Add(separator);
_maxField = new FloatField();
_maxField = new UTKFloatField();
_maxField.name = "max-field";
_maxField.value = Value.Max;
_maxField.Value = Value.Max;
_maxField.style.flexGrow = 1;
valueContainer.Add(_maxField);
@@ -68,21 +92,21 @@ namespace UVC.UIToolkit
{
base.BindUI(element);
_minField = element.Q<FloatField>("min-field");
_maxField = element.Q<FloatField>("max-field");
_minField = element.Q<UTKFloatField>("min-field");
_maxField = element.Q<UTKFloatField>("max-field");
if (_minField != null)
{
_minField.value = Value.Min;
_minField.SetEnabled(!IsReadOnly);
_minField.RegisterValueChangedCallback(OnMinChanged);
_minField.Value = Value.Min;
_minField.IsEnabled = !IsReadOnly;
_minField.OnValueChanged += OnMinChanged;
}
if (_maxField != null)
{
_maxField.value = Value.Max;
_maxField.SetEnabled(!IsReadOnly);
_maxField.RegisterValueChangedCallback(OnMaxChanged);
_maxField.Value = Value.Max;
_maxField.IsEnabled = !IsReadOnly;
_maxField.OnValueChanged += OnMaxChanged;
}
}
@@ -90,13 +114,13 @@ namespace UVC.UIToolkit
{
if (_minField != null)
{
_minField.UnregisterValueChangedCallback(OnMinChanged);
_minField.OnValueChanged -= OnMinChanged;
_minField = null;
}
if (_maxField != null)
{
_maxField.UnregisterValueChangedCallback(OnMaxChanged);
_maxField.OnValueChanged -= OnMaxChanged;
_maxField = null;
}
@@ -105,12 +129,12 @@ namespace UVC.UIToolkit
public override void RefreshUI()
{
if (_minField != null && !Mathf.Approximately(_minField.value, Value.Min))
if (_minField != null && !Mathf.Approximately(_minField.Value, Value.Min))
{
_minField.SetValueWithoutNotify(Value.Min);
}
if (_maxField != null && !Mathf.Approximately(_maxField.value, Value.Max))
if (_maxField != null && !Mathf.Approximately(_maxField.Value, Value.Max))
{
_maxField.SetValueWithoutNotify(Value.Max);
}
@@ -120,21 +144,21 @@ namespace UVC.UIToolkit
{
base.UpdateReadOnlyState();
_minField?.SetEnabled(!IsReadOnly);
_maxField?.SetEnabled(!IsReadOnly);
if (_minField != null) _minField.IsEnabled = !IsReadOnly;
if (_maxField != null) _maxField.IsEnabled = !IsReadOnly;
}
#endregion
#region Private Methods
private void OnMinChanged(ChangeEvent<float> evt)
private void OnMinChanged(float newMin)
{
var newValue = new UTKFloatRange(evt.newValue, Value.Max);
var newValue = new UTKFloatRange(newMin, Value.Max);
DebounceValueChange(newValue, 100).Forget();
}
private void OnMaxChanged(ChangeEvent<float> evt)
private void OnMaxChanged(float newMax)
{
var newValue = new UTKFloatRange(Value.Min, evt.newValue);
var newValue = new UTKFloatRange(Value.Min, newMax);
DebounceValueChange(newValue, 100).Forget();
}
#endregion

View File

@@ -11,8 +11,8 @@ namespace UVC.UIToolkit
public class UTKIntPropertyItem : UTKPropertyItemBase<int>
{
#region Fields
private IntegerField? _intField;
private SliderInt? _slider;
private UTKIntegerField? _intField;
private UTKSliderInt? _slider;
private bool _useSlider;
private int _minValue;
private int _maxValue = 100;
@@ -74,6 +74,33 @@ namespace UVC.UIToolkit
#region Override Methods
public override VisualElement CreateUI()
{
string uxmlName = _useSlider ? "UTKIntPropertyItemSlider" : "UTKIntPropertyItem";
var container = CreateUIFromUxml(uxmlName);
if (container == null)
{
return CreateUIFallback();
}
_intField = container.Q<UTKIntegerField>("value-field");
_slider = container.Q<UTKSliderInt>("slider-field");
if (_intField != null)
{
_intField.Value = Value;
}
if (_slider != null)
{
_slider.lowValue = _minValue;
_slider.highValue = _maxValue;
_slider.Value = Value;
}
return container;
}
private VisualElement CreateUIFallback()
{
var container = CreateContainer();
@@ -90,23 +117,22 @@ namespace UVC.UIToolkit
if (_useSlider)
{
_slider = new SliderInt(_minValue, _maxValue);
_slider = new UTKSliderInt("", _minValue, _maxValue, Value);
_slider.name = "slider-field";
_slider.value = Value;
_slider.AddToClassList("utk-property-item__slider");
valueContainer.Add(_slider);
_intField = new IntegerField();
_intField = new UTKIntegerField();
_intField.name = "value-field";
_intField.value = Value;
_intField.Value = Value;
_intField.AddToClassList("utk-property-item__number-field");
valueContainer.Add(_intField);
}
else
{
_intField = new IntegerField();
_intField = new UTKIntegerField();
_intField.name = "value-field";
_intField.value = Value;
_intField.Value = Value;
valueContainer.Add(_intField);
}
@@ -119,23 +145,23 @@ namespace UVC.UIToolkit
{
base.BindUI(element);
_intField = element.Q<IntegerField>("value-field");
_slider = element.Q<SliderInt>("slider-field");
_intField = element.Q<UTKIntegerField>("value-field");
_slider = element.Q<UTKSliderInt>("slider-field");
if (_intField != null)
{
_intField.value = Value;
_intField.SetEnabled(!IsReadOnly);
_intField.RegisterValueChangedCallback(OnIntFieldChanged);
_intField.Value = Value;
_intField.IsEnabled = !IsReadOnly;
_intField.OnValueChanged += OnIntFieldChanged;
}
if (_slider != null)
{
_slider.lowValue = _minValue;
_slider.highValue = _maxValue;
_slider.value = Value;
_slider.SetEnabled(!IsReadOnly);
_slider.RegisterValueChangedCallback(OnSliderChanged);
_slider.Value = Value;
_slider.IsEnabled = !IsReadOnly;
_slider.OnValueChanged += OnSliderChanged;
}
}
@@ -143,13 +169,13 @@ namespace UVC.UIToolkit
{
if (_intField != null)
{
_intField.UnregisterValueChangedCallback(OnIntFieldChanged);
_intField.OnValueChanged -= OnIntFieldChanged;
_intField = null;
}
if (_slider != null)
{
_slider.UnregisterValueChangedCallback(OnSliderChanged);
_slider.OnValueChanged -= OnSliderChanged;
_slider = null;
}
@@ -158,12 +184,12 @@ namespace UVC.UIToolkit
public override void RefreshUI()
{
if (_intField != null && _intField.value != Value)
if (_intField != null && _intField.Value != Value)
{
_intField.SetValueWithoutNotify(Value);
}
if (_slider != null && _slider.value != Value)
if (_slider != null && _slider.Value != Value)
{
_slider.SetValueWithoutNotify(Value);
}
@@ -173,17 +199,20 @@ namespace UVC.UIToolkit
{
base.UpdateReadOnlyState();
_intField?.SetEnabled(!IsReadOnly);
_slider?.SetEnabled(!IsReadOnly);
if (_intField != null)
{
_intField.IsEnabled = !IsReadOnly;
}
if (_slider != null) _slider.IsEnabled = !IsReadOnly;
}
#endregion
#region Private Methods
private void OnIntFieldChanged(ChangeEvent<int> evt)
private void OnIntFieldChanged(int newValue)
{
int clampedValue = _useSlider ? Mathf.Clamp(evt.newValue, _minValue, _maxValue) : evt.newValue;
int clampedValue = _useSlider ? Mathf.Clamp(newValue, _minValue, _maxValue) : newValue;
if (_slider != null && _slider.value != clampedValue)
if (_slider != null && _slider.Value != clampedValue)
{
_slider.SetValueWithoutNotify(clampedValue);
}
@@ -191,15 +220,15 @@ namespace UVC.UIToolkit
DebounceValueChange(clampedValue, 100).Forget();
}
private void OnSliderChanged(ChangeEvent<int> evt)
private void OnSliderChanged(int newValue)
{
if (_intField != null && _intField.value != evt.newValue)
if (_intField != null && _intField.Value != newValue)
{
_intField.SetValueWithoutNotify(evt.newValue);
_intField.SetValueWithoutNotify(newValue);
}
// 슬라이더는 빠른 반응이 필요하므로 짧은 디바운스
DebounceValueChange(evt.newValue, 50).Forget();
DebounceValueChange(newValue, 50).Forget();
}
#endregion
}

View File

@@ -10,8 +10,8 @@ namespace UVC.UIToolkit
public class UTKIntRangePropertyItem : UTKPropertyItemBase<UTKIntRange>
{
#region Fields
private IntegerField? _minField;
private IntegerField? _maxField;
private UTKIntegerField? _minField;
private UTKIntegerField? _maxField;
#endregion
#region Properties
@@ -32,6 +32,30 @@ namespace UVC.UIToolkit
#region Override Methods
public override VisualElement CreateUI()
{
var container = CreateUIFromUxml("UTKIntRangePropertyItem");
if (container == null)
{
return CreateUIFallback();
}
_minField = container.Q<UTKIntegerField>("min-field");
_maxField = container.Q<UTKIntegerField>("max-field");
if (_minField != null)
{
_minField.Value = Value.Min;
}
if (_maxField != null)
{
_maxField.Value = Value.Max;
}
return container;
}
private VisualElement CreateUIFallback()
{
var container = CreateContainer();
@@ -42,19 +66,19 @@ namespace UVC.UIToolkit
valueContainer.AddToClassList("utk-property-item__value");
valueContainer.style.flexDirection = FlexDirection.Row;
_minField = new IntegerField();
_minField = new UTKIntegerField();
_minField.name = "min-field";
_minField.value = Value.Min;
_minField.Value = Value.Min;
_minField.style.flexGrow = 1;
valueContainer.Add(_minField);
var separator = new Label("~");
var separator = new UTKLabel("~", UTKLabel.LabelSize.Body2);
separator.AddToClassList("utk-property-item__range-separator");
valueContainer.Add(separator);
_maxField = new IntegerField();
_maxField = new UTKIntegerField();
_maxField.name = "max-field";
_maxField.value = Value.Max;
_maxField.Value = Value.Max;
_maxField.style.flexGrow = 1;
valueContainer.Add(_maxField);
@@ -67,21 +91,21 @@ namespace UVC.UIToolkit
{
base.BindUI(element);
_minField = element.Q<IntegerField>("min-field");
_maxField = element.Q<IntegerField>("max-field");
_minField = element.Q<UTKIntegerField>("min-field");
_maxField = element.Q<UTKIntegerField>("max-field");
if (_minField != null)
{
_minField.value = Value.Min;
_minField.SetEnabled(!IsReadOnly);
_minField.RegisterValueChangedCallback(OnMinChanged);
_minField.Value = Value.Min;
_minField.IsEnabled = !IsReadOnly;
_minField.OnValueChanged += OnMinChanged;
}
if (_maxField != null)
{
_maxField.value = Value.Max;
_maxField.SetEnabled(!IsReadOnly);
_maxField.RegisterValueChangedCallback(OnMaxChanged);
_maxField.Value = Value.Max;
_maxField.IsEnabled = !IsReadOnly;
_maxField.OnValueChanged += OnMaxChanged;
}
}
@@ -89,13 +113,13 @@ namespace UVC.UIToolkit
{
if (_minField != null)
{
_minField.UnregisterValueChangedCallback(OnMinChanged);
_minField.OnValueChanged -= OnMinChanged;
_minField = null;
}
if (_maxField != null)
{
_maxField.UnregisterValueChangedCallback(OnMaxChanged);
_maxField.OnValueChanged -= OnMaxChanged;
_maxField = null;
}
@@ -104,12 +128,12 @@ namespace UVC.UIToolkit
public override void RefreshUI()
{
if (_minField != null && _minField.value != Value.Min)
if (_minField != null && _minField.Value != Value.Min)
{
_minField.SetValueWithoutNotify(Value.Min);
}
if (_maxField != null && _maxField.value != Value.Max)
if (_maxField != null && _maxField.Value != Value.Max)
{
_maxField.SetValueWithoutNotify(Value.Max);
}
@@ -119,21 +143,21 @@ namespace UVC.UIToolkit
{
base.UpdateReadOnlyState();
_minField?.SetEnabled(!IsReadOnly);
_maxField?.SetEnabled(!IsReadOnly);
if (_minField != null) _minField.IsEnabled = !IsReadOnly;
if (_maxField != null) _maxField.IsEnabled = !IsReadOnly;
}
#endregion
#region Private Methods
private void OnMinChanged(ChangeEvent<int> evt)
private void OnMinChanged(int newMin)
{
var newValue = new UTKIntRange(evt.newValue, Value.Max);
var newValue = new UTKIntRange(newMin, Value.Max);
DebounceValueChange(newValue, 100).Forget();
}
private void OnMaxChanged(ChangeEvent<int> evt)
private void OnMaxChanged(int newMax)
{
var newValue = new UTKIntRange(Value.Min, evt.newValue);
var newValue = new UTKIntRange(Value.Min, newMax);
DebounceValueChange(newValue, 100).Forget();
}
#endregion

View File

@@ -8,12 +8,13 @@ namespace UVC.UIToolkit
{
/// <summary>
/// 라디오 그룹 속성 아이템
/// RadioButton 그룹으로 단일 선택
/// UTKRadioButton 그룹으로 단일 선택
/// </summary>
public class UTKRadioPropertyItem : UTKPropertyItemBase<int>
{
#region Fields
private RadioButtonGroup? _radioGroup;
private VisualElement? _radioContainer;
private List<UTKRadioButton> _radioButtons = new();
private List<string> _choices;
#endregion
@@ -27,10 +28,7 @@ namespace UVC.UIToolkit
set
{
_choices = value ?? new List<string>();
if (_radioGroup != null)
{
_radioGroup.choices = _choices;
}
RebuildRadioButtons();
}
}
@@ -63,6 +61,23 @@ namespace UVC.UIToolkit
#region Override Methods
public override VisualElement CreateUI()
{
var container = CreateUIFromUxml("UTKRadioPropertyItem");
if (container == null)
{
return CreateUIFallback();
}
_radioContainer = container.Q<VisualElement>("radio-container");
if (_radioContainer != null)
{
CreateRadioButtons();
}
return container;
}
private VisualElement CreateUIFallback()
{
var container = CreateContainer();
@@ -72,11 +87,12 @@ namespace UVC.UIToolkit
var valueContainer = new VisualElement();
valueContainer.AddToClassList("utk-property-item__value");
_radioGroup = new RadioButtonGroup();
_radioGroup.name = "radio-group";
_radioGroup.choices = _choices;
_radioGroup.value = Value;
valueContainer.Add(_radioGroup);
_radioContainer = new VisualElement();
_radioContainer.name = "radio-container";
_radioContainer.AddToClassList("utk-property-item__radio-container");
valueContainer.Add(_radioContainer);
CreateRadioButtons();
container.Add(valueContainer);
@@ -87,43 +103,57 @@ namespace UVC.UIToolkit
{
base.BindUI(element);
_radioGroup = element.Q<RadioButtonGroup>("radio-group");
_radioContainer = element.Q<VisualElement>("radio-container");
if (_radioGroup != null)
if (_radioContainer != null)
{
_radioGroup.choices = _choices;
_radioGroup.value = Value;
_radioGroup.SetEnabled(!IsReadOnly);
_radioGroup.RegisterValueChangedCallback(OnRadioChanged);
// 기존 라디오 버튼 찾기 또는 새로 생성
_radioButtons.Clear();
var existingButtons = _radioContainer.Query<UTKRadioButton>().ToList();
if (existingButtons.Count == _choices.Count)
{
// 기존 버튼 재사용
for (int i = 0; i < existingButtons.Count; i++)
{
var radio = existingButtons[i];
_radioButtons.Add(radio);
radio.IsEnabled = !IsReadOnly;
int index = i;
radio.OnValueChanged += (isChecked) => OnRadioChanged(index, isChecked);
}
}
else
{
// 새로 생성
CreateRadioButtons();
}
UpdateSelection();
}
}
public override void UnbindUI(VisualElement element)
{
if (_radioGroup != null)
{
_radioGroup.UnregisterValueChangedCallback(OnRadioChanged);
_radioGroup = null;
}
_radioButtons.Clear();
_radioContainer = null;
base.UnbindUI(element);
}
public override void RefreshUI()
{
if (_radioGroup != null && _radioGroup.value != Value)
{
_radioGroup.SetValueWithoutNotify(Value);
}
UpdateSelection();
}
protected override void UpdateReadOnlyState()
{
base.UpdateReadOnlyState();
if (_radioGroup != null)
foreach (var radio in _radioButtons)
{
_radioGroup.SetEnabled(!IsReadOnly);
radio.IsEnabled = !IsReadOnly;
}
}
#endregion
@@ -141,9 +171,60 @@ namespace UVC.UIToolkit
#endregion
#region Private Methods
private void OnRadioChanged(ChangeEvent<int> evt)
private void CreateRadioButtons()
{
Value = evt.newValue;
if (_radioContainer == null) return;
_radioContainer.Clear();
_radioButtons.Clear();
for (int i = 0; i < _choices.Count; i++)
{
var radio = new UTKRadioButton(_choices[i]);
radio.name = $"radio-{i}";
radio.AddToClassList("utk-property-item__radio");
int index = i;
radio.OnValueChanged += (isChecked) => OnRadioChanged(index, isChecked);
_radioButtons.Add(radio);
_radioContainer.Add(radio);
}
UpdateSelection();
}
private void RebuildRadioButtons()
{
if (_radioContainer != null)
{
CreateRadioButtons();
}
}
private void UpdateSelection()
{
for (int i = 0; i < _radioButtons.Count; i++)
{
_radioButtons[i].SetChecked(i == Value, false);
}
}
private void OnRadioChanged(int index, bool isChecked)
{
if (isChecked && index != Value)
{
// 다른 라디오 버튼 해제
for (int i = 0; i < _radioButtons.Count; i++)
{
if (i != index)
{
_radioButtons[i].SetChecked(false, false);
}
}
Value = index;
}
}
#endregion
}

View File

@@ -10,7 +10,7 @@ namespace UVC.UIToolkit
public class UTKStringPropertyItem : UTKPropertyItemBase<string>
{
#region Fields
private TextField? _textField;
private UTKInputField? _inputField;
private bool _isMultiline;
private int _maxLength;
#endregion
@@ -25,9 +25,9 @@ namespace UVC.UIToolkit
set
{
_isMultiline = value;
if (_textField != null)
if (_inputField != null)
{
_textField.multiline = value;
_inputField.multiline = value;
}
}
}
@@ -39,9 +39,9 @@ namespace UVC.UIToolkit
set
{
_maxLength = value;
if (_textField != null)
if (_inputField != null)
{
_textField.maxLength = value;
_inputField.maxLength = value;
}
}
}
@@ -56,6 +56,28 @@ namespace UVC.UIToolkit
#region Override Methods
public override VisualElement CreateUI()
{
var container = CreateUIFromUxml("UTKStringPropertyItem");
if (container == null)
{
return CreateUIFallback();
}
_inputField = container.Q<UTKInputField>("value-field");
if (_inputField != null)
{
_inputField.Value = Value;
_inputField.multiline = _isMultiline;
if (_maxLength > 0)
{
_inputField.maxLength = _maxLength;
}
}
return container;
}
private VisualElement CreateUIFallback()
{
var container = CreateContainer();
@@ -65,16 +87,16 @@ namespace UVC.UIToolkit
var valueContainer = new VisualElement();
valueContainer.AddToClassList("utk-property-item__value");
_textField = new TextField();
_textField.name = "value-field";
_textField.value = Value;
_textField.multiline = _isMultiline;
_inputField = new UTKInputField();
_inputField.name = "value-field";
_inputField.Value = Value;
_inputField.multiline = _isMultiline;
if (_maxLength > 0)
{
_textField.maxLength = _maxLength;
_inputField.maxLength = _maxLength;
}
valueContainer.Add(_textField);
valueContainer.Add(_inputField);
container.Add(valueContainer);
return container;
@@ -84,21 +106,21 @@ namespace UVC.UIToolkit
{
base.BindUI(element);
_textField = element.Q<TextField>("value-field");
if (_textField != null)
_inputField = element.Q<UTKInputField>("value-field");
if (_inputField != null)
{
_textField.value = Value;
_textField.SetEnabled(!IsReadOnly);
_textField.RegisterValueChangedCallback(OnTextChanged);
_inputField.Value = Value;
_inputField.SetEnabled(!IsReadOnly);
_inputField.OnValueChanged += OnTextChanged;
}
}
public override void UnbindUI(VisualElement element)
{
if (_textField != null)
if (_inputField != null)
{
_textField.UnregisterValueChangedCallback(OnTextChanged);
_textField = null;
_inputField.OnValueChanged -= OnTextChanged;
_inputField = null;
}
base.UnbindUI(element);
@@ -106,9 +128,9 @@ namespace UVC.UIToolkit
public override void RefreshUI()
{
if (_textField != null && _textField.value != Value)
if (_inputField != null && _inputField.Value != Value)
{
_textField.SetValueWithoutNotify(Value);
_inputField.SetValue(Value, false);
}
}
@@ -116,17 +138,17 @@ namespace UVC.UIToolkit
{
base.UpdateReadOnlyState();
if (_textField != null)
if (_inputField != null)
{
_textField.SetEnabled(!IsReadOnly);
_inputField.SetEnabled(!IsReadOnly);
}
}
#endregion
#region Private Methods
private void OnTextChanged(ChangeEvent<string> evt)
private void OnTextChanged(string newValue)
{
DebounceValueChange(evt.newValue).Forget();
DebounceValueChange(newValue).Forget();
}
#endregion
}

View File

@@ -6,13 +6,12 @@ namespace UVC.UIToolkit
{
/// <summary>
/// 2D 벡터 속성 아이템
/// X, Y 두 개의 FloatField 사용
/// UTKVector2Field 사용
/// </summary>
public class UTKVector2PropertyItem : UTKPropertyItemBase<Vector2>
{
#region Fields
private FloatField? _xField;
private FloatField? _yField;
private UTKVector2Field? _vectorField;
#endregion
#region Properties
@@ -28,6 +27,24 @@ namespace UVC.UIToolkit
#region Override Methods
public override VisualElement CreateUI()
{
var container = CreateUIFromUxml("UTKVector2PropertyItem");
if (container == null)
{
return CreateUIFallback();
}
_vectorField = container.Q<UTKVector2Field>("value-field");
if (_vectorField != null)
{
_vectorField.Value = Value;
_vectorField.label = "";
}
return container;
}
private VisualElement CreateUIFallback()
{
var container = CreateContainer();
@@ -37,41 +54,12 @@ namespace UVC.UIToolkit
var valueContainer = new VisualElement();
valueContainer.AddToClassList("utk-property-item__value");
// X 필드
var xContainer = new VisualElement();
xContainer.AddToClassList("utk-property-item__vector-field");
xContainer.style.flexDirection = FlexDirection.Row;
var xLabel = new Label("X");
xLabel.AddToClassList("utk-property-item__vector-label");
xLabel.AddToClassList("utk-vector-label--x");
_xField = new FloatField();
_xField.name = "x-field";
_xField.value = Value.x;
_xField.style.flexGrow = 1;
xContainer.Add(xLabel);
xContainer.Add(_xField);
valueContainer.Add(xContainer);
// Y 필드
var yContainer = new VisualElement();
yContainer.AddToClassList("utk-property-item__vector-field");
yContainer.style.flexDirection = FlexDirection.Row;
var yLabel = new Label("Y");
yLabel.AddToClassList("utk-property-item__vector-label");
yLabel.AddToClassList("utk-vector-label--y");
_yField = new FloatField();
_yField.name = "y-field";
_yField.value = Value.y;
_yField.style.flexGrow = 1;
yContainer.Add(yLabel);
yContainer.Add(_yField);
valueContainer.Add(yContainer);
_vectorField = new UTKVector2Field();
_vectorField.name = "value-field";
_vectorField.label = "";
_vectorField.Value = Value;
_vectorField.AddToClassList("utk-property-item__vector2-field");
valueContainer.Add(_vectorField);
container.Add(valueContainer);
@@ -82,36 +70,23 @@ namespace UVC.UIToolkit
{
base.BindUI(element);
_xField = element.Q<FloatField>("x-field");
_yField = element.Q<FloatField>("y-field");
_vectorField = element.Q<UTKVector2Field>("value-field");
if (_xField != null)
if (_vectorField != null)
{
_xField.value = Value.x;
_xField.SetEnabled(!IsReadOnly);
_xField.RegisterValueChangedCallback(OnXChanged);
}
if (_yField != null)
{
_yField.value = Value.y;
_yField.SetEnabled(!IsReadOnly);
_yField.RegisterValueChangedCallback(OnYChanged);
_vectorField.Value = Value;
_vectorField.label = "";
_vectorField.IsEnabled = !IsReadOnly;
_vectorField.OnValueChanged += OnVectorChanged;
}
}
public override void UnbindUI(VisualElement element)
{
if (_xField != null)
if (_vectorField != null)
{
_xField.UnregisterValueChangedCallback(OnXChanged);
_xField = null;
}
if (_yField != null)
{
_yField.UnregisterValueChangedCallback(OnYChanged);
_yField = null;
_vectorField.OnValueChanged -= OnVectorChanged;
_vectorField = null;
}
base.UnbindUI(element);
@@ -119,14 +94,9 @@ namespace UVC.UIToolkit
public override void RefreshUI()
{
if (_xField != null && !Mathf.Approximately(_xField.value, Value.x))
if (_vectorField != null && _vectorField.Value != Value)
{
_xField.SetValueWithoutNotify(Value.x);
}
if (_yField != null && !Mathf.Approximately(_yField.value, Value.y))
{
_yField.SetValueWithoutNotify(Value.y);
_vectorField.SetValueWithoutNotify(Value);
}
}
@@ -134,21 +104,16 @@ namespace UVC.UIToolkit
{
base.UpdateReadOnlyState();
_xField?.SetEnabled(!IsReadOnly);
_yField?.SetEnabled(!IsReadOnly);
if (_vectorField != null)
{
_vectorField.IsEnabled = !IsReadOnly;
}
}
#endregion
#region Private Methods
private void OnXChanged(ChangeEvent<float> evt)
private void OnVectorChanged(Vector2 newValue)
{
var newValue = new Vector2(evt.newValue, Value.y);
DebounceValueChange(newValue, 100).Forget();
}
private void OnYChanged(ChangeEvent<float> evt)
{
var newValue = new Vector2(Value.x, evt.newValue);
DebounceValueChange(newValue, 100).Forget();
}
#endregion

View File

@@ -6,14 +6,12 @@ namespace UVC.UIToolkit
{
/// <summary>
/// 3D 벡터 속성 아이템
/// X, Y, Z 세 개의 FloatField 사용
/// UTKVector3Field 사용
/// </summary>
public class UTKVector3PropertyItem : UTKPropertyItemBase<Vector3>
{
#region Fields
private FloatField? _xField;
private FloatField? _yField;
private FloatField? _zField;
private UTKVector3Field? _vectorField;
#endregion
#region Properties
@@ -29,6 +27,24 @@ namespace UVC.UIToolkit
#region Override Methods
public override VisualElement CreateUI()
{
var container = CreateUIFromUxml("UTKVector3PropertyItem");
if (container == null)
{
return CreateUIFallback();
}
_vectorField = container.Q<UTKVector3Field>("value-field");
if (_vectorField != null)
{
_vectorField.Value = Value;
_vectorField.label = "";
}
return container;
}
private VisualElement CreateUIFallback()
{
var container = CreateContainer();
@@ -38,59 +54,12 @@ namespace UVC.UIToolkit
var valueContainer = new VisualElement();
valueContainer.AddToClassList("utk-property-item__value");
// X 필드
var xContainer = new VisualElement();
xContainer.AddToClassList("utk-property-item__vector-field");
xContainer.style.flexDirection = FlexDirection.Row;
var xLabel = new Label("X");
xLabel.AddToClassList("utk-property-item__vector-label");
xLabel.AddToClassList("utk-vector-label--x");
_xField = new FloatField();
_xField.name = "x-field";
_xField.value = Value.x;
_xField.style.flexGrow = 1;
xContainer.Add(xLabel);
xContainer.Add(_xField);
valueContainer.Add(xContainer);
// Y 필드
var yContainer = new VisualElement();
yContainer.AddToClassList("utk-property-item__vector-field");
yContainer.style.flexDirection = FlexDirection.Row;
var yLabel = new Label("Y");
yLabel.AddToClassList("utk-property-item__vector-label");
yLabel.AddToClassList("utk-vector-label--y");
_yField = new FloatField();
_yField.name = "y-field";
_yField.value = Value.y;
_yField.style.flexGrow = 1;
yContainer.Add(yLabel);
yContainer.Add(_yField);
valueContainer.Add(yContainer);
// Z 필드
var zContainer = new VisualElement();
zContainer.AddToClassList("utk-property-item__vector-field");
zContainer.style.flexDirection = FlexDirection.Row;
var zLabel = new Label("Z");
zLabel.AddToClassList("utk-property-item__vector-label");
zLabel.AddToClassList("utk-vector-label--z");
_zField = new FloatField();
_zField.name = "z-field";
_zField.value = Value.z;
_zField.style.flexGrow = 1;
zContainer.Add(zLabel);
zContainer.Add(_zField);
valueContainer.Add(zContainer);
_vectorField = new UTKVector3Field();
_vectorField.name = "value-field";
_vectorField.label = "";
_vectorField.Value = Value;
_vectorField.AddToClassList("utk-property-item__vector3-field");
valueContainer.Add(_vectorField);
container.Add(valueContainer);
@@ -101,50 +70,23 @@ namespace UVC.UIToolkit
{
base.BindUI(element);
_xField = element.Q<FloatField>("x-field");
_yField = element.Q<FloatField>("y-field");
_zField = element.Q<FloatField>("z-field");
_vectorField = element.Q<UTKVector3Field>("value-field");
if (_xField != null)
if (_vectorField != null)
{
_xField.value = Value.x;
_xField.SetEnabled(!IsReadOnly);
_xField.RegisterValueChangedCallback(OnXChanged);
}
if (_yField != null)
{
_yField.value = Value.y;
_yField.SetEnabled(!IsReadOnly);
_yField.RegisterValueChangedCallback(OnYChanged);
}
if (_zField != null)
{
_zField.value = Value.z;
_zField.SetEnabled(!IsReadOnly);
_zField.RegisterValueChangedCallback(OnZChanged);
_vectorField.Value = Value;
_vectorField.label = "";
_vectorField.IsEnabled = !IsReadOnly;
_vectorField.OnValueChanged += OnVectorChanged;
}
}
public override void UnbindUI(VisualElement element)
{
if (_xField != null)
if (_vectorField != null)
{
_xField.UnregisterValueChangedCallback(OnXChanged);
_xField = null;
}
if (_yField != null)
{
_yField.UnregisterValueChangedCallback(OnYChanged);
_yField = null;
}
if (_zField != null)
{
_zField.UnregisterValueChangedCallback(OnZChanged);
_zField = null;
_vectorField.OnValueChanged -= OnVectorChanged;
_vectorField = null;
}
base.UnbindUI(element);
@@ -152,19 +94,9 @@ namespace UVC.UIToolkit
public override void RefreshUI()
{
if (_xField != null && !Mathf.Approximately(_xField.value, Value.x))
if (_vectorField != null && _vectorField.Value != Value)
{
_xField.SetValueWithoutNotify(Value.x);
}
if (_yField != null && !Mathf.Approximately(_yField.value, Value.y))
{
_yField.SetValueWithoutNotify(Value.y);
}
if (_zField != null && !Mathf.Approximately(_zField.value, Value.z))
{
_zField.SetValueWithoutNotify(Value.z);
_vectorField.SetValueWithoutNotify(Value);
}
}
@@ -172,28 +104,16 @@ namespace UVC.UIToolkit
{
base.UpdateReadOnlyState();
_xField?.SetEnabled(!IsReadOnly);
_yField?.SetEnabled(!IsReadOnly);
_zField?.SetEnabled(!IsReadOnly);
if (_vectorField != null)
{
_vectorField.IsEnabled = !IsReadOnly;
}
}
#endregion
#region Private Methods
private void OnXChanged(ChangeEvent<float> evt)
private void OnVectorChanged(Vector3 newValue)
{
var newValue = new Vector3(evt.newValue, Value.y, Value.z);
DebounceValueChange(newValue, 100).Forget();
}
private void OnYChanged(ChangeEvent<float> evt)
{
var newValue = new Vector3(Value.x, evt.newValue, Value.z);
DebounceValueChange(newValue, 100).Forget();
}
private void OnZChanged(ChangeEvent<float> evt)
{
var newValue = new Vector3(Value.x, Value.y, evt.newValue);
DebounceValueChange(newValue, 100).Forget();
}
#endregion

View File

@@ -21,7 +21,7 @@ namespace UVC.UIToolkit
#region Fields
private bool _disposed;
private TreeView? _treeView;
private TextField? _searchField;
private UTKInputField? _searchField;
private readonly List<IUTKPropertyEntry> _entries = new();
private readonly Dictionary<string, IUTKPropertyGroup> _groupIndex = new();
@@ -70,23 +70,12 @@ namespace UVC.UIToolkit
var searchContainer = new VisualElement { name = "search-container" };
searchContainer.AddToClassList("utk-property-list__search-container");
_searchField = new TextField { name = "search-field" };
_searchField = new UTKInputField { name = "search-field" };
_searchField.Placeholder = "Search...";
_searchField.AddToClassList("utk-property-list__search-field");
_searchField.RegisterValueChangedCallback(OnSearchChanged);
var searchPlaceholder = new Label("Search...") { name = "search-placeholder" };
searchPlaceholder.AddToClassList("utk-property-list__search-placeholder");
searchPlaceholder.pickingMode = PickingMode.Ignore;
_searchField.RegisterCallback<FocusInEvent>(_ => searchPlaceholder.style.display = DisplayStyle.None);
_searchField.RegisterCallback<FocusOutEvent>(_ =>
{
if (string.IsNullOrEmpty(_searchField.value))
searchPlaceholder.style.display = DisplayStyle.Flex;
});
_searchField.OnValueChanged += OnSearchChanged;
searchContainer.Add(_searchField);
searchContainer.Add(searchPlaceholder);
Add(searchContainer);
// TreeView
@@ -507,13 +496,15 @@ namespace UVC.UIToolkit
var groupElement = new VisualElement();
groupElement.AddToClassList("utk-property-group__header");
var expandIcon = new Label(group.IsExpanded ? "▼" : "▶");
var expandIcon = new UTKLabel(group.IsExpanded ? UTKMaterialIcons.ExpandMore : UTKMaterialIcons.ChevronRight, 16);
expandIcon.AddToClassList("utk-property-group__expand-icon");
var title = new Label(group.GroupName);
var title = new UTKLabel(group.GroupName, UTKLabel.LabelSize.Label1);
title.IsBold = true;
title.AddToClassList("utk-property-group__title");
var count = new Label($"({group.ItemCount})");
var count = new UTKLabel($"({group.ItemCount})", UTKLabel.LabelSize.Caption);
count.Variant = UTKLabel.LabelVariant.Secondary;
count.AddToClassList("utk-property-group__count");
groupElement.Add(expandIcon);
@@ -523,7 +514,7 @@ namespace UVC.UIToolkit
groupElement.RegisterCallback<ClickEvent>(_ =>
{
ToggleGroupExpanded(group.GroupId);
expandIcon.text = group.IsExpanded ? "▼" : "▶";
expandIcon.SetMaterialIcon(group.IsExpanded ? UTKMaterialIcons.ExpandMore : UTKMaterialIcons.ChevronRight, 16);
OnGroupExpandedChanged?.Invoke(group, group.IsExpanded);
});
@@ -573,9 +564,9 @@ namespace UVC.UIToolkit
OnPropertyValueChanged?.Invoke(args);
}
private void OnSearchChanged(ChangeEvent<string> evt)
private void OnSearchChanged(string newValue)
{
_searchText = evt.newValue ?? string.Empty;
_searchText = newValue ?? string.Empty;
RefreshTreeView();
}
#endregion

View File

@@ -1,6 +1,7 @@
#nullable enable
using System;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
using UnityEngine.UIElements;
@@ -14,15 +15,16 @@ namespace UVC.UIToolkit
public partial class UTKPropertyWindow : VisualElement, IDisposable
{
#region Constants
private const string USS_PATH = "UIToolkit/Property/UTKPropertyWindow";
private const string UXML_PATH = "UIToolkit/Property/UTKPropertyWindow";
private const string USS_PATH = "UIToolkit/Property/UTKPropertyWindowUss";
#endregion
#region Fields
private bool _disposed;
private UTKPropertyList? _propertyList;
private VisualElement? _header;
private Label? _titleLabel;
private Button? _closeButton;
private UTKLabel? _titleLabel;
private UTKButton? _closeButton;
private string _title = "Properties";
private bool _showCloseButton = true;
private bool _isDragging;
@@ -40,7 +42,7 @@ namespace UVC.UIToolkit
_title = value;
if (_titleLabel != null)
{
_titleLabel.text = value;
_titleLabel.Text = value;
}
}
}
@@ -93,6 +95,7 @@ namespace UVC.UIToolkit
public UTKPropertyWindow()
{
UTKThemeManager.Instance.ApplyThemeToElement(this);
SubscribeToThemeChanges();
var styleSheet = Resources.Load<StyleSheet>(USS_PATH);
if (styleSheet != null)
@@ -114,29 +117,99 @@ namespace UVC.UIToolkit
{
AddToClassList("utk-property-window");
var asset = Resources.Load<VisualTreeAsset>(UXML_PATH);
if (asset != null)
{
CreateUIFromUxml(asset);
}
else
{
CreateUIFallback();
}
// 드래그 이벤트
if (_header != null)
{
_header.RegisterCallback<PointerDownEvent>(OnHeaderPointerDown);
_header.RegisterCallback<PointerMoveEvent>(OnHeaderPointerMove);
_header.RegisterCallback<PointerUpEvent>(OnHeaderPointerUp);
}
}
private void CreateUIFromUxml(VisualTreeAsset asset)
{
var root = asset.Instantiate();
var windowRoot = root.Q<VisualElement>("window-root");
if (windowRoot != null)
{
// windowRoot의 자식들을 현재 요소로 이동
foreach (var child in windowRoot.Children().ToArray())
{
Add(child);
}
}
else
{
// window-root가 없으면 전체 추가
Add(root);
}
// 요소 참조 가져오기
_header = this.Q<VisualElement>("header");
_titleLabel = this.Q<UTKLabel>("title");
_closeButton = this.Q<UTKButton>("close-btn");
_propertyList = this.Q<UTKPropertyList>("content");
// 타이틀 설정
if (_titleLabel != null)
{
_titleLabel.Text = _title;
_titleLabel.IsBold = true;
_titleLabel.Size = UTKLabel.LabelSize.Label1;
}
// 닫기 버튼 설정
if (_closeButton != null)
{
_closeButton.SetMaterialIcon(UTKMaterialIcons.Close, 14);
_closeButton.IconOnly = true;
_closeButton.OnClicked += () => OnCloseClicked?.Invoke();
_closeButton.style.display = _showCloseButton ? DisplayStyle.Flex : DisplayStyle.None;
}
// PropertyList가 없으면 생성
if (_propertyList == null)
{
_propertyList = new UTKPropertyList();
_propertyList.name = "content";
_propertyList.AddToClassList("utk-property-window__content");
Add(_propertyList);
}
}
private void CreateUIFallback()
{
// 헤더
_header = new VisualElement();
_header.name = "header";
_header.AddToClassList("utk-property-window__header");
_titleLabel = new Label(_title);
_titleLabel = new UTKLabel(_title, UTKLabel.LabelSize.Label1);
_titleLabel.name = "title";
_titleLabel.IsBold = true;
_titleLabel.AddToClassList("utk-property-window__title");
_header.Add(_titleLabel);
_closeButton = new Button { text = "✕" };
_closeButton = new UTKButton("", UTKMaterialIcons.Close, UTKButton.ButtonVariant.Text, 14);
_closeButton.name = "close-btn";
_closeButton.IconOnly = true;
_closeButton.AddToClassList("utk-property-window__close-btn");
_closeButton.clicked += () => OnCloseClicked?.Invoke();
_closeButton.OnClicked += () => OnCloseClicked?.Invoke();
_header.Add(_closeButton);
Add(_header);
// 드래그 이벤트
_header.RegisterCallback<PointerDownEvent>(OnHeaderPointerDown);
_header.RegisterCallback<PointerMoveEvent>(OnHeaderPointerMove);
_header.RegisterCallback<PointerUpEvent>(OnHeaderPointerUp);
// PropertyList
_propertyList = new UTKPropertyList();
_propertyList.AddToClassList("utk-property-window__content");
@@ -242,12 +315,33 @@ namespace UVC.UIToolkit
}
#endregion
#region (Theme)
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;
// 드래그 이벤트 해제
if (_header != null)
{

View File

@@ -102,6 +102,13 @@ namespace UVC.UIToolkit
EnableInClassList("utk-slider--disabled", !value);
}
}
/// <summary>현재 값</summary>
public float Value
{
get => value;
set => this.value = value;
}
#endregion
#region Constructor

View File

@@ -0,0 +1,192 @@
#nullable enable
using System;
using UnityEngine;
using UnityEngine.UIElements;
namespace UVC.UIToolkit
{
/// <summary>
/// 정수 슬라이더 컴포넌트.
/// Unity SliderInt를 래핑하여 커스텀 스타일을 적용합니다.
/// 드래그 또는 클릭으로 범위 내의 정수 값을 선택할 수 있습니다.
/// </summary>
/// <remarks>
/// <para><b>정수 슬라이더(SliderInt)란?</b></para>
/// <para>
/// 정수 슬라이더는 사용자가 지정된 범위 내에서 정수 값을 선택할 수 있는 UI 컨트롤입니다.
/// 레벨, 수량, 페이지 번호 등 이산적인 값 조절에 사용되며, 시각적으로 현재 값의 위치를 확인할 수 있습니다.
/// </para>
///
/// <para><b>주요 속성:</b></para>
/// <list type="bullet">
/// <item><description><c>lowValue</c> - 슬라이더의 최소값 (왼쪽 끝)</description></item>
/// <item><description><c>highValue</c> - 슬라이더의 최대값 (오른쪽 끝)</description></item>
/// <item><description><c>value</c> - 현재 선택된 정수 값</description></item>
/// <item><description><c>showInputField</c> - 숫자 입력 필드 표시 여부 (Unity 기본 속성)</description></item>
/// <item><description><c>direction</c> - 슬라이더 방향 (Horizontal/Vertical)</description></item>
/// </list>
///
/// <para><b>실제 활용 예시:</b></para>
/// <list type="bullet">
/// <item><description>게임 설정 - 난이도 레벨, 라운드 수, 플레이어 수</description></item>
/// <item><description>페이지네이션 - 페이지 번호 선택</description></item>
/// <item><description>수량 조절 - 아이템 개수, 재고 수량</description></item>
/// <item><description>설정 값 - 그리드 크기, 해상도 단계</description></item>
/// </list>
/// </remarks>
/// <example>
/// <para><b>C# 코드에서 사용:</b></para>
/// <code>
/// // 기본 정수 슬라이더 생성
/// var slider = new UTKSliderInt();
/// slider.label = "레벨";
/// slider.lowValue = 1;
/// slider.highValue = 10;
/// slider.value = 5;
///
/// // 값 변경 이벤트
/// slider.OnValueChanged += (value) => {
/// Debug.Log($"선택된 레벨: {value}");
/// SetDifficultyLevel(value);
/// };
///
/// // 생성자로 한 번에 설정
/// var quantitySlider = new UTKSliderInt("수량", 1, 100, 10);
///
/// // 페이지 선택 슬라이더
/// var pageSlider = new UTKSliderInt("페이지", 1, totalPages, 1);
/// pageSlider.OnValueChanged += (page) => {
/// LoadPage(page);
/// };
/// </code>
/// <para><b>UXML에서 사용:</b></para>
/// <code>
/// <!-- 기본 정수 슬라이더 -->
/// <utk:UTKSliderInt label="레벨" low-value="1" high-value="10" value="5" />
///
/// <!-- 값 입력 필드 표시 -->
/// <utk:UTKSliderInt label="수량" low-value="0" high-value="100" value="50"
/// show-input-field="true" />
///
/// <!-- 비활성화 상태 -->
/// <utk:UTKSliderInt label="잠금" is-enabled="false" value="25" />
/// </code>
/// </example>
[UxmlElement]
public partial class UTKSliderInt : SliderInt, IDisposable
{
#region Constants
private const string USS_PATH = "UIToolkit/Slider/UTKSlider";
#endregion
#region Fields
private bool _disposed;
private bool _isEnabled = true;
#endregion
#region Events
/// <summary>값 변경 이벤트</summary>
public event Action<int>? OnValueChanged;
#endregion
#region Properties
/// <summary>활성화 상태</summary>
[UxmlAttribute("is-enabled")]
public bool IsEnabled
{
get => _isEnabled;
set
{
_isEnabled = value;
SetEnabled(value);
EnableInClassList("utk-slider--disabled", !value);
}
}
/// <summary>현재 값</summary>
public int Value
{
get => value;
set => this.value = value;
}
#endregion
#region Constructor
public UTKSliderInt() : base()
{
UTKThemeManager.Instance.ApplyThemeToElement(this);
var uss = Resources.Load<StyleSheet>(USS_PATH);
if (uss != null)
{
styleSheets.Add(uss);
}
SetupStyles();
SetupEvents();
SubscribeToThemeChanges();
}
public UTKSliderInt(string label, int minValue = 0, int maxValue = 100, int defaultValue = 0) : this()
{
this.label = label;
this.lowValue = minValue;
this.highValue = maxValue;
this.value = defaultValue;
}
#endregion
#region Setup
private void SetupStyles()
{
AddToClassList("utk-slider");
AddToClassList("utk-slider-int");
}
private void SetupEvents()
{
this.RegisterValueChangedCallback(OnSliderValueChanged);
}
private void SubscribeToThemeChanges()
{
UTKThemeManager.Instance.OnThemeChanged += OnThemeChanged;
RegisterCallback<DetachFromPanelEvent>(_ =>
{
UTKThemeManager.Instance.OnThemeChanged -= OnThemeChanged;
});
}
private void OnThemeChanged(UTKTheme theme)
{
UTKThemeManager.Instance.ApplyThemeToElement(this);
}
#endregion
#region Public Methods
/// <summary>이벤트 발생 없이 값 설정</summary>
public new void SetValueWithoutNotify(int newValue)
{
base.SetValueWithoutNotify(newValue);
}
#endregion
#region Event Handlers
private void OnSliderValueChanged(ChangeEvent<int> evt)
{
OnValueChanged?.Invoke(evt.newValue);
}
#endregion
#region IDisposable
public void Dispose()
{
if (_disposed) return;
_disposed = true;
UTKThemeManager.Instance.OnThemeChanged -= OnThemeChanged;
OnValueChanged = null;
}
#endregion
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: d2865ae5ce26e204eb34404cb7587fd9

View File

@@ -7,9 +7,148 @@ using UnityEngine.UIElements;
namespace UVC.UIToolkit
{
/// <summary>
/// UIToolkit 테마 관리자
/// 런타임에서 Light/Dark 테마 전환을 지원합니다.
/// UIToolkit 테마 관리자.
/// 런타임에서 Light/Dark 테마 전환을 지원하며, 모든 UTK 컴포넌트에 일관된 스타일을 적용합니다.
/// </summary>
/// <remarks>
/// <para><b>테마 매니저란?</b></para>
/// <para>
/// UTKThemeManager는 애플리케이션 전체의 UI 테마(색상, 스타일)를 중앙에서 관리합니다.
/// 싱글톤 패턴으로 구현되어 어디서든 <c>UTKThemeManager.Instance</c>로 접근 가능합니다.
/// </para>
///
/// <para><b>스타일시트 로드 순서:</b></para>
/// <list type="number">
/// <item><description>UTKVariables.uss - 레이아웃 변수 (spacing, radius, font-size)</description></item>
/// <item><description>UTKThemeDark/Light.uss - 테마별 색상 변수</description></item>
/// <item><description>UTKComponents.uss - 공통 컴포넌트 스타일</description></item>
/// <item><description>UTKDefaultStyle.uss - Unity 기본 요소 오버라이드 (선택)</description></item>
/// </list>
///
/// <para><b>주요 기능:</b></para>
/// <list type="bullet">
/// <item><description><c>RegisterRoot()</c> - UIDocument 루트에 테마 등록</description></item>
/// <item><description><c>SetTheme()</c> - 테마 변경 (Dark/Light)</description></item>
/// <item><description><c>ToggleTheme()</c> - 테마 토글</description></item>
/// <item><description><c>ApplyThemeToElement()</c> - 개별 요소에 테마 적용</description></item>
/// <item><description><c>OnThemeChanged</c> - 테마 변경 이벤트 구독</description></item>
/// </list>
///
/// <para><b>사용 시점:</b></para>
/// <list type="bullet">
/// <item><description>UIDocument 초기화 시 <c>RegisterRoot()</c> 호출</description></item>
/// <item><description>UTK 컴포넌트 내부에서 자동으로 <c>ApplyThemeToElement()</c> 호출</description></item>
/// <item><description>설정 화면에서 테마 전환 버튼 클릭 시 <c>ToggleTheme()</c> 호출</description></item>
/// </list>
/// </remarks>
/// <example>
/// <para><b>기본 사용법 - UIDocument에 테마 등록:</b></para>
/// <code>
/// public class MyUIController : MonoBehaviour
/// {
/// [SerializeField] private UIDocument _uiDocument;
///
/// void Start()
/// {
/// // UIDocument 루트에 테마 스타일시트 등록
/// var root = _uiDocument.rootVisualElement;
/// UTKThemeManager.Instance.RegisterRoot(root);
/// }
///
/// void OnDestroy()
/// {
/// // 정리 (선택사항)
/// var root = _uiDocument.rootVisualElement;
/// UTKThemeManager.Instance.UnregisterRoot(root);
/// }
/// }
/// </code>
///
/// <para><b>테마 전환:</b></para>
/// <code>
/// // 특정 테마로 변경
/// UTKThemeManager.Instance.SetTheme(UTKTheme.Light);
///
/// // 토글 (Dark ↔ Light)
/// UTKThemeManager.Instance.ToggleTheme();
///
/// // 현재 테마 확인
/// if (UTKThemeManager.Instance.IsDarkTheme)
/// {
/// Debug.Log("다크 테마 사용 중");
/// }
/// </code>
///
/// <para><b>테마 변경 이벤트 구독:</b></para>
/// <code>
/// void OnEnable()
/// {
/// UTKThemeManager.Instance.OnThemeChanged += OnThemeChanged;
/// }
///
/// void OnDisable()
/// {
/// UTKThemeManager.Instance.OnThemeChanged -= OnThemeChanged;
/// }
///
/// private void OnThemeChanged(UTKTheme newTheme)
/// {
/// Debug.Log($"테마 변경됨: {newTheme}");
/// // 테마에 따른 추가 로직 (아이콘 변경 등)
/// }
/// </code>
///
/// <para><b>커스텀 스타일 값 가져오기:</b></para>
/// <code>
/// // USS 변수에서 색상 값 읽기
/// var primaryColor = new CustomStyleProperty&lt;Color&gt;("--color-btn-primary");
/// var color = UTKThemeManager.GetColor(myElement, primaryColor, Color.blue);
///
/// // USS 변수에서 float 값 읽기
/// var spacing = new CustomStyleProperty&lt;float&gt;("--space-m");
/// var value = UTKThemeManager.GetFloat(myElement, spacing, 8f);
/// </code>
///
/// <para><b>UTK 컴포넌트에서 사용 (내부 구현):</b></para>
/// <code>
/// [UxmlElement]
/// public partial class MyCustomComponent : VisualElement
/// {
/// public MyCustomComponent()
/// {
/// // 컴포넌트에 테마 스타일 적용
/// UTKThemeManager.Instance.ApplyThemeToElement(this);
///
/// // 테마 변경 구독
/// UTKThemeManager.Instance.OnThemeChanged += OnThemeChanged;
///
/// // 패널에서 분리될 때 구독 해제
/// RegisterCallback&lt;DetachFromPanelEvent&gt;(_ =&gt;
/// {
/// UTKThemeManager.Instance.OnThemeChanged -= OnThemeChanged;
/// });
/// }
///
/// private void OnThemeChanged(UTKTheme theme)
/// {
/// UTKThemeManager.Instance.ApplyThemeToElement(this);
/// }
/// }
/// </code>
///
/// <para><b>테마 전환 버튼 구현:</b></para>
/// <code>
/// var themeToggle = new UTKButton("테마 전환", UTKButtonVariant.Secondary);
/// themeToggle.clicked += () =&gt;
/// {
/// UTKThemeManager.Instance.ToggleTheme();
/// var icon = UTKThemeManager.Instance.IsDarkTheme
/// ? UTKMaterialIcons.DarkMode
/// : UTKMaterialIcons.LightMode;
/// themeToggle.SetMaterialIcon(icon);
/// };
/// </code>
/// </example>
public class UTKThemeManager
{
#region Singleton
@@ -263,11 +402,20 @@ namespace UVC.UIToolkit
}
/// <summary>
/// 지원하는 테마 종류
/// 지원하는 테마 종류.
/// </summary>
/// <remarks>
/// <para>각 테마는 해당 USS 파일의 색상 변수를 사용합니다:</para>
/// <list type="bullet">
/// <item><description><c>Dark</c> - UTKThemeDark.uss (어두운 배경, 밝은 텍스트)</description></item>
/// <item><description><c>Light</c> - UTKThemeLight.uss (밝은 배경, 어두운 텍스트)</description></item>
/// </list>
/// </remarks>
public enum UTKTheme
{
/// <summary>다크 테마 (기본값)</summary>
Dark,
/// <summary>라이트 테마</summary>
Light
}
}