#nullable enable
using System;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UIElements;
namespace UVC.UIToolkit
{
///
/// 모든 PropertyItem View의 기본 추상 클래스입니다.
/// VisualElement를 상속하여 UXML/C#에서 단독 사용이 가능합니다.
///
/// 주요 기능:
///
/// - 테마 자동 적용 (UTKThemeManager)
/// - 공통/개별 USS 자동 로드
/// - Label + Value 레이아웃 제공
/// - Data 클래스 바인딩 지원
/// - 단독 사용 시 UXML 속성 지원
///
///
/// 관련 리소스:
///
/// - Resources/UIToolkit/Property/Views/{ViewName}.uxml - 각 뷰 UXML
/// - Resources/UIToolkit/Property/Views/{ViewName}Uss.uss - 각 뷰 USS
/// - Resources/UIToolkit/Property/Views/UTKPropertyItemViewCommonUss.uss - 공통 USS
///
///
public abstract class UTKPropertyItemViewBase : VisualElement, IDisposable
{
#region Constants
protected const string UXML_BASE_PATH = "UIToolkit/Property/Views/";
protected const string USS_BASE_PATH = "UIToolkit/Property/Views/";
protected const string USS_COMMON_PATH = "UIToolkit/Property/Views/UTKPropertyItemViewCommonUss";
protected const string USS_CLASS_READONLY = "utk-property-item-view--readonly";
protected const string USS_CLASS_HIDDEN = "utk-property-item-view--hidden";
protected const string USS_CLASS_NO_LABEL = "utk-property-item-view--no-label";
#endregion
#region Static Cache
private static readonly Dictionary _uxmlCache = new();
private static readonly Dictionary _ussCache = new();
private static StyleSheet? _commonUssCache;
#endregion
#region Fields
protected bool _disposed;
protected UTKLabel? _labelElement;
protected VisualElement? _valueContainer;
protected string _label = string.Empty;
protected bool _isReadOnly = false;
protected bool _isVisible = true;
protected bool _showLabel = true;
protected string? _tooltipText;
#endregion
#region Properties
/// 라벨 텍스트
[UxmlAttribute("label")]
public string Label
{
get => _label;
set
{
_label = value;
if (_labelElement != null)
{
_labelElement.Text = value;
}
}
}
/// 읽기 전용 여부
[UxmlAttribute("is-read-only")]
public bool IsReadOnly
{
get => _isReadOnly;
set
{
if (_isReadOnly != value)
{
_isReadOnly = value;
UpdateReadOnlyState();
}
}
}
/// 표시 여부
[UxmlAttribute("is-visible")]
public bool IsVisible
{
get => _isVisible;
set
{
if (_isVisible != value)
{
_isVisible = value;
UpdateVisibility();
}
}
}
/// 툴팁 텍스트
[UxmlAttribute("tooltip-text")]
public string? TooltipText
{
get => _tooltipText;
set
{
_tooltipText = value;
UpdateTooltip();
}
}
/// 라벨 표시 여부 (false면 value가 전체 너비 사용)
[UxmlAttribute("show-label")]
public bool ShowLabel
{
get => _showLabel;
set
{
if (_showLabel != value)
{
_showLabel = value;
UpdateShowLabelState();
}
}
}
#endregion
#region Constructor
protected UTKPropertyItemViewBase()
{
// 1. 테마 적용
UTKThemeManager.Instance.ApplyThemeToElement(this);
// 2. 공통 USS 로드
ApplyCommonStyleSheet();
// 3. 테마 변경 구독
SubscribeToThemeChanges();
}
#endregion
#region Abstract Methods
/// View 타입 이름 (UXML/USS 파일명에 사용)
protected abstract string ViewTypeName { get; }
/// 값 영역 UI 생성 (하위 클래스에서 구현)
protected abstract void CreateValueUI(VisualElement container);
/// UI 상태 갱신 (하위 클래스에서 구현)
public abstract void RefreshUI();
/// 읽기 전용 상태 업데이트 (하위 클래스에서 오버라이드)
protected abstract void OnReadOnlyStateChanged(bool isReadOnly);
#endregion
#region Protected Methods
///
/// UXML 템플릿에서 UI를 생성합니다.
///
/// 성공 여부
protected bool CreateUIFromUxml()
{
var asset = LoadUxmlAsset(ViewTypeName);
if (asset == null)
{
return false;
}
var root = asset.Instantiate();
Add(root);
// 개별 USS 로드
ApplyStyleSheet(ViewTypeName + "Uss");
// 공통 요소 참조 획득
_labelElement = this.Q("label");
_valueContainer = this.Q("value-container");
if (_labelElement != null)
{
_labelElement.Text = _label;
}
return true;
}
///
/// 코드로 기본 UI 레이아웃을 생성합니다 (Fallback).
///
protected void CreateUIFallback()
{
AddToClassList("utk-property-item-view");
// 개별 USS 로드
ApplyStyleSheet(ViewTypeName + "Uss");
// Label
_labelElement = new UTKLabel(_label, UTKLabel.LabelSize.Body2)
{
name = "label"
};
_labelElement.AddToClassList("utk-property-item-view__label");
Add(_labelElement);
// Value Container
_valueContainer = new VisualElement { name = "value-container" };
_valueContainer.AddToClassList("utk-property-item-view__value");
Add(_valueContainer);
// 하위 클래스에서 값 UI 생성
CreateValueUI(_valueContainer);
}
/// 읽기 전용 상태 업데이트
protected virtual void UpdateReadOnlyState()
{
if (_isReadOnly)
{
AddToClassList(USS_CLASS_READONLY);
}
else
{
RemoveFromClassList(USS_CLASS_READONLY);
}
OnReadOnlyStateChanged(_isReadOnly);
}
/// 표시 상태 업데이트
protected virtual void UpdateVisibility()
{
style.display = _isVisible ? DisplayStyle.Flex : DisplayStyle.None;
if (_isVisible)
{
RemoveFromClassList(USS_CLASS_HIDDEN);
}
else
{
AddToClassList(USS_CLASS_HIDDEN);
}
}
/// 툴팁 업데이트
protected virtual void UpdateTooltip()
{
if (!string.IsNullOrEmpty(_tooltipText))
{
this.SetTooltip(_tooltipText);
}
else
{
this.ClearTooltip();
}
}
/// 라벨 표시 상태 업데이트
protected virtual void UpdateShowLabelState()
{
if (_showLabel)
{
RemoveFromClassList(USS_CLASS_NO_LABEL);
if (_labelElement != null)
{
_labelElement.style.display = DisplayStyle.Flex;
}
}
else
{
AddToClassList(USS_CLASS_NO_LABEL);
if (_labelElement != null)
{
_labelElement.style.display = DisplayStyle.None;
}
}
}
#endregion
#region Theme
private void SubscribeToThemeChanges()
{
UTKThemeManager.Instance.OnThemeChanged += OnThemeChanged;
RegisterCallback(_ =>
{
UTKThemeManager.Instance.OnThemeChanged -= OnThemeChanged;
});
}
private void OnThemeChanged(UTKTheme theme)
{
UTKThemeManager.Instance.ApplyThemeToElement(this);
}
#endregion
#region StyleSheet Loading
/// 공통 USS 스타일시트를 적용합니다.
protected void ApplyCommonStyleSheet()
{
var commonUss = LoadCommonUssAsset();
if (commonUss != null)
{
styleSheets.Add(commonUss);
}
}
/// 개별 USS 스타일시트를 적용합니다.
protected void ApplyStyleSheet(string ussName)
{
var uss = LoadUssAsset(ussName);
if (uss != null)
{
styleSheets.Add(uss);
}
}
/// UXML 에셋 로드 (캐시 사용)
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(path);
if (asset != null)
{
_uxmlCache[path] = asset;
}
return asset;
}
/// 공통 USS 에셋 로드 (캐시 사용)
protected static StyleSheet? LoadCommonUssAsset()
{
if (_commonUssCache != null)
{
return _commonUssCache;
}
_commonUssCache = Resources.Load(USS_COMMON_PATH);
return _commonUssCache;
}
/// USS 에셋 로드 (캐시 사용)
protected static StyleSheet? LoadUssAsset(string ussName)
{
string path = USS_BASE_PATH + ussName;
if (_ussCache.TryGetValue(path, out var cached))
{
return cached;
}
var asset = Resources.Load(path);
if (asset != null)
{
_ussCache[path] = asset;
}
return asset;
}
/// 모든 캐시 클리어 (에디터 용도)
public static void ClearAllCache()
{
_uxmlCache.Clear();
_ussCache.Clear();
_commonUssCache = null;
}
#endregion
#region Helper Methods
///
/// PropertyItem의 공통 속성을 View에 적용합니다.
/// 하위 클래스의 Bind 메서드에서 호출하세요.
///
protected void ApplyCommonProperties(IUTKPropertyItem item)
{
Label = item.Name;
IsReadOnly = item.IsReadOnly;
IsVisible = item.IsVisible;
TooltipText = item.Tooltip;
ShowLabel = item.ShowLabel;
}
#endregion
#region IDisposable
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
if (_disposed) return;
_disposed = true;
if (disposing)
{
UTKThemeManager.Instance.OnThemeChanged -= OnThemeChanged;
this.ClearTooltip();
_labelElement = null;
_valueContainer = null;
}
}
#endregion
}
}