461 lines
14 KiB
C#
461 lines
14 KiB
C#
#nullable enable
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using UnityEngine;
|
|
using UnityEngine.UIElements;
|
|
|
|
namespace UVC.UIToolkit
|
|
{
|
|
/// <summary>
|
|
/// 모든 PropertyItem View의 기본 추상 클래스입니다.
|
|
/// VisualElement를 상속하여 UXML/C#에서 단독 사용이 가능합니다.
|
|
///
|
|
/// <para><b>주요 기능:</b></para>
|
|
/// <list type="bullet">
|
|
/// <item>테마 자동 적용 (UTKThemeManager)</item>
|
|
/// <item>공통/개별 USS 자동 로드</item>
|
|
/// <item>Label + Value 레이아웃 제공</item>
|
|
/// <item>Data 클래스 바인딩 지원</item>
|
|
/// <item>단독 사용 시 UXML 속성 지원</item>
|
|
/// </list>
|
|
///
|
|
/// <para><b>관련 리소스:</b></para>
|
|
/// <list type="bullet">
|
|
/// <item>Resources/UIToolkit/Property/Views/{ViewName}.uxml - 각 뷰 UXML</item>
|
|
/// <item>Resources/UIToolkit/Property/Views/{ViewName}Uss.uss - 각 뷰 USS</item>
|
|
/// <item>Resources/UIToolkit/Property/Views/UTKPropertyItemViewCommonUss.uss - 공통 USS</item>
|
|
/// </list>
|
|
/// </summary>
|
|
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<string, VisualTreeAsset> _uxmlCache = new();
|
|
private static readonly Dictionary<string, StyleSheet> _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;
|
|
|
|
private IUTKPropertyItem? _boundItemBase;
|
|
#endregion
|
|
|
|
#region Properties
|
|
/// <summary>라벨 텍스트</summary>
|
|
[UxmlAttribute("label")]
|
|
public string Label
|
|
{
|
|
get => _label;
|
|
set
|
|
{
|
|
_label = value;
|
|
if (_labelElement != null)
|
|
{
|
|
_labelElement.Text = value;
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>읽기 전용 여부</summary>
|
|
[UxmlAttribute("is-read-only")]
|
|
public bool IsReadOnly
|
|
{
|
|
get => _isReadOnly;
|
|
set
|
|
{
|
|
if (_isReadOnly != value)
|
|
{
|
|
_isReadOnly = value;
|
|
UpdateReadOnlyState();
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>표시 여부</summary>
|
|
[UxmlAttribute("is-visible")]
|
|
public bool IsVisible
|
|
{
|
|
get => _isVisible;
|
|
set
|
|
{
|
|
if (_isVisible != value)
|
|
{
|
|
_isVisible = value;
|
|
UpdateVisibility();
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>툴팁 텍스트</summary>
|
|
[UxmlAttribute("tooltip-text")]
|
|
public string? TooltipText
|
|
{
|
|
get => _tooltipText;
|
|
set
|
|
{
|
|
_tooltipText = value;
|
|
UpdateTooltip();
|
|
}
|
|
}
|
|
|
|
/// <summary>라벨 표시 여부 (false면 value가 전체 너비 사용)</summary>
|
|
[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
|
|
/// <summary>View 타입 이름 (UXML/USS 파일명에 사용)</summary>
|
|
protected abstract string ViewTypeName { get; }
|
|
|
|
/// <summary>값 영역 UI 생성 (하위 클래스에서 구현)</summary>
|
|
protected abstract void CreateValueUI(VisualElement container);
|
|
|
|
/// <summary>UI 상태 갱신 (하위 클래스에서 구현)</summary>
|
|
public abstract void RefreshUI();
|
|
|
|
/// <summary>읽기 전용 상태 업데이트 (하위 클래스에서 오버라이드)</summary>
|
|
protected abstract void OnReadOnlyStateChanged(bool isReadOnly);
|
|
#endregion
|
|
|
|
#region Protected Methods
|
|
/// <summary>
|
|
/// UXML 템플릿에서 UI를 생성합니다.
|
|
/// </summary>
|
|
/// <returns>성공 여부</returns>
|
|
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<UTKLabel>("label");
|
|
_valueContainer = this.Q<VisualElement>("value-container");
|
|
|
|
if (_labelElement != null)
|
|
{
|
|
_labelElement.Text = _label;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 코드로 기본 UI 레이아웃을 생성합니다 (Fallback).
|
|
/// </summary>
|
|
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);
|
|
}
|
|
|
|
/// <summary>읽기 전용 상태 업데이트</summary>
|
|
protected virtual void UpdateReadOnlyState()
|
|
{
|
|
if (_isReadOnly)
|
|
{
|
|
AddToClassList(USS_CLASS_READONLY);
|
|
}
|
|
else
|
|
{
|
|
RemoveFromClassList(USS_CLASS_READONLY);
|
|
}
|
|
|
|
OnReadOnlyStateChanged(_isReadOnly);
|
|
}
|
|
|
|
/// <summary>표시 상태 업데이트</summary>
|
|
protected virtual void UpdateVisibility()
|
|
{
|
|
style.display = _isVisible ? DisplayStyle.Flex : DisplayStyle.None;
|
|
|
|
if (_isVisible)
|
|
{
|
|
RemoveFromClassList(USS_CLASS_HIDDEN);
|
|
}
|
|
else
|
|
{
|
|
AddToClassList(USS_CLASS_HIDDEN);
|
|
}
|
|
}
|
|
|
|
/// <summary>툴팁 업데이트</summary>
|
|
protected virtual void UpdateTooltip()
|
|
{
|
|
if (!string.IsNullOrEmpty(_tooltipText))
|
|
{
|
|
this.SetTooltip(_tooltipText);
|
|
}
|
|
else
|
|
{
|
|
this.ClearTooltip();
|
|
}
|
|
}
|
|
|
|
/// <summary>라벨 표시 상태 업데이트</summary>
|
|
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<AttachToPanelEvent>(OnAttachToPanel);
|
|
RegisterCallback<DetachFromPanelEvent>(OnDetachFromPanel);
|
|
}
|
|
|
|
private void OnAttachToPanel(AttachToPanelEvent evt)
|
|
{
|
|
UTKThemeManager.Instance.OnThemeChanged -= OnThemeChanged;
|
|
UTKThemeManager.Instance.OnThemeChanged += OnThemeChanged;
|
|
}
|
|
|
|
private void OnDetachFromPanel(DetachFromPanelEvent evt)
|
|
{
|
|
UTKThemeManager.Instance.OnThemeChanged -= OnThemeChanged;
|
|
}
|
|
|
|
private void OnThemeChanged(UTKTheme theme)
|
|
{
|
|
UTKThemeManager.Instance.ApplyThemeToElement(this);
|
|
}
|
|
#endregion
|
|
|
|
#region StyleSheet Loading
|
|
/// <summary>공통 USS 스타일시트를 적용합니다.</summary>
|
|
protected void ApplyCommonStyleSheet()
|
|
{
|
|
var commonUss = LoadCommonUssAsset();
|
|
if (commonUss != null)
|
|
{
|
|
styleSheets.Add(commonUss);
|
|
}
|
|
}
|
|
|
|
/// <summary>개별 USS 스타일시트를 적용합니다.</summary>
|
|
protected void ApplyStyleSheet(string ussName)
|
|
{
|
|
var uss = LoadUssAsset(ussName);
|
|
if (uss != null)
|
|
{
|
|
styleSheets.Add(uss);
|
|
}
|
|
}
|
|
|
|
/// <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>공통 USS 에셋 로드 (캐시 사용)</summary>
|
|
protected static StyleSheet? LoadCommonUssAsset()
|
|
{
|
|
if (_commonUssCache != null)
|
|
{
|
|
return _commonUssCache;
|
|
}
|
|
|
|
_commonUssCache = Resources.Load<StyleSheet>(USS_COMMON_PATH);
|
|
return _commonUssCache;
|
|
}
|
|
|
|
/// <summary>USS 에셋 로드 (캐시 사용)</summary>
|
|
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<StyleSheet>(path);
|
|
if (asset != null)
|
|
{
|
|
_ussCache[path] = asset;
|
|
}
|
|
|
|
return asset;
|
|
}
|
|
|
|
/// <summary>모든 캐시 클리어 (에디터 용도)</summary>
|
|
public static void ClearAllCache()
|
|
{
|
|
_uxmlCache.Clear();
|
|
_ussCache.Clear();
|
|
_commonUssCache = null;
|
|
}
|
|
#endregion
|
|
|
|
#region Helper Methods
|
|
/// <summary>
|
|
/// PropertyItem의 공통 속성을 View에 적용합니다.
|
|
/// 하위 클래스의 Bind 메서드에서 호출하세요.
|
|
/// </summary>
|
|
protected void ApplyCommonProperties(IUTKPropertyItem item)
|
|
{
|
|
Label = item.Name;
|
|
IsReadOnly = item.IsReadOnly;
|
|
IsVisible = item.IsVisible;
|
|
TooltipText = item.Tooltip;
|
|
ShowLabel = item.ShowLabel;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 상태 변경 이벤트를 구독합니다. 하위 View의 Bind에서 호출하세요.
|
|
/// </summary>
|
|
protected void BindBase(IUTKPropertyItem item)
|
|
{
|
|
UnbindBase();
|
|
_boundItemBase = item;
|
|
_boundItemBase.OnStateChanged += OnItemStateChanged;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 상태 변경 이벤트 구독을 해제합니다. 하위 View의 Unbind에서 호출하세요.
|
|
/// </summary>
|
|
protected void UnbindBase()
|
|
{
|
|
if (_boundItemBase != null)
|
|
{
|
|
_boundItemBase.OnStateChanged -= OnItemStateChanged;
|
|
_boundItemBase = null;
|
|
}
|
|
}
|
|
|
|
private void OnItemStateChanged(IUTKPropertyItem item)
|
|
{
|
|
IsReadOnly = item.IsReadOnly;
|
|
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)
|
|
{
|
|
UnbindBase();
|
|
UTKThemeManager.Instance.OnThemeChanged -= OnThemeChanged;
|
|
UnregisterCallback<AttachToPanelEvent>(OnAttachToPanel);
|
|
UnregisterCallback<DetachFromPanelEvent>(OnDetachFromPanel);
|
|
this.ClearTooltip();
|
|
|
|
_labelElement = null;
|
|
_valueContainer = null;
|
|
}
|
|
}
|
|
#endregion
|
|
}
|
|
}
|