296 lines
7.8 KiB
C#
296 lines
7.8 KiB
C#
#nullable enable
|
|
using System;
|
|
using System.Threading;
|
|
using Cysharp.Threading.Tasks;
|
|
using UnityEngine;
|
|
using UnityEngine.UIElements;
|
|
|
|
namespace UVC.UIToolkit
|
|
{
|
|
/// <summary>
|
|
/// 모든 PropertyItem의 기본 추상 클래스
|
|
/// 공통 기능 및 UIToolkit 바인딩 로직 제공
|
|
/// </summary>
|
|
/// <typeparam name="T">속성 값의 타입</typeparam>
|
|
public abstract class UTKPropertyItemBase<T> : IUTKPropertyItem<T>, IDisposable
|
|
{
|
|
#region Constants
|
|
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";
|
|
#endregion
|
|
|
|
#region Fields
|
|
private T _value;
|
|
private bool _isReadOnly;
|
|
private bool _isVisible = true;
|
|
private string? _description;
|
|
private string? _tooltip;
|
|
private string? _groupId;
|
|
private bool _disposed;
|
|
|
|
protected VisualElement? _rootElement;
|
|
protected CancellationTokenSource? _debounceCts;
|
|
#endregion
|
|
|
|
#region Properties
|
|
public string Id { get; }
|
|
public string Name { get; }
|
|
public string DisplayName => Name;
|
|
public bool IsGroup => false;
|
|
public int TreeViewId { get; set; }
|
|
public abstract UTKPropertyType PropertyType { get; }
|
|
|
|
public T Value
|
|
{
|
|
get => _value;
|
|
set
|
|
{
|
|
if (!Equals(_value, value))
|
|
{
|
|
var oldValue = _value;
|
|
_value = value;
|
|
NotifyValueChanged(oldValue, value);
|
|
RefreshUI();
|
|
}
|
|
}
|
|
}
|
|
|
|
public string? Description
|
|
{
|
|
get => _description;
|
|
set => _description = value;
|
|
}
|
|
|
|
public string? Tooltip
|
|
{
|
|
get => _tooltip;
|
|
set
|
|
{
|
|
_tooltip = value;
|
|
UpdateTooltip();
|
|
}
|
|
}
|
|
|
|
public bool IsReadOnly
|
|
{
|
|
get => _isReadOnly;
|
|
set
|
|
{
|
|
if (_isReadOnly != value)
|
|
{
|
|
_isReadOnly = value;
|
|
UpdateReadOnlyState();
|
|
}
|
|
}
|
|
}
|
|
|
|
public bool IsVisible
|
|
{
|
|
get => _isVisible;
|
|
set
|
|
{
|
|
if (_isVisible != value)
|
|
{
|
|
_isVisible = value;
|
|
UpdateVisibility();
|
|
}
|
|
}
|
|
}
|
|
|
|
public string? GroupId
|
|
{
|
|
get => _groupId;
|
|
set => _groupId = value;
|
|
}
|
|
#endregion
|
|
|
|
#region Events
|
|
public event Action<IUTKPropertyItem, object?, object?>? OnValueChanged;
|
|
public event Action<IUTKPropertyItem<T>, T, T>? OnTypedValueChanged;
|
|
#endregion
|
|
|
|
#region Constructor
|
|
protected UTKPropertyItemBase(string id, string name, T initialValue)
|
|
{
|
|
Id = id ?? throw new ArgumentNullException(nameof(id));
|
|
Name = name ?? throw new ArgumentNullException(nameof(name));
|
|
_value = initialValue;
|
|
}
|
|
#endregion
|
|
|
|
#region Public Methods
|
|
public object? GetValue() => _value;
|
|
|
|
public void SetValue(object? value)
|
|
{
|
|
if (value == null)
|
|
{
|
|
if (default(T) == null)
|
|
{
|
|
Value = default!;
|
|
}
|
|
}
|
|
else if (value is T typedValue)
|
|
{
|
|
Value = typedValue;
|
|
}
|
|
else
|
|
{
|
|
try
|
|
{
|
|
Value = (T)Convert.ChangeType(value, typeof(T));
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Debug.LogWarning($"[UTKPropertyItem] Failed to convert value '{value}' to type {typeof(T)}: {ex.Message}");
|
|
}
|
|
}
|
|
}
|
|
|
|
public abstract VisualElement CreateUI();
|
|
|
|
public virtual void BindUI(VisualElement element)
|
|
{
|
|
_rootElement = element;
|
|
UpdateReadOnlyState();
|
|
UpdateVisibility();
|
|
UpdateTooltip();
|
|
}
|
|
|
|
public virtual void UnbindUI(VisualElement element)
|
|
{
|
|
CancelDebounce();
|
|
|
|
if (_rootElement != null)
|
|
{
|
|
_rootElement.ClearTooltip();
|
|
}
|
|
|
|
_rootElement = null;
|
|
}
|
|
|
|
public virtual void RefreshUI()
|
|
{
|
|
// 하위 클래스에서 오버라이드하여 UI 갱신
|
|
}
|
|
#endregion
|
|
|
|
#region Protected Methods
|
|
protected void NotifyValueChanged(T oldValue, T newValue)
|
|
{
|
|
OnTypedValueChanged?.Invoke(this, oldValue, newValue);
|
|
OnValueChanged?.Invoke(this, oldValue, newValue);
|
|
}
|
|
|
|
protected async UniTaskVoid DebounceValueChange(T newValue, int delayMs = DEFAULT_DEBOUNCE_MS)
|
|
{
|
|
CancelDebounce();
|
|
_debounceCts = new CancellationTokenSource();
|
|
|
|
try
|
|
{
|
|
await UniTask.Delay(delayMs, cancellationToken: _debounceCts.Token);
|
|
Value = newValue;
|
|
}
|
|
catch (OperationCanceledException)
|
|
{
|
|
// 디바운스 취소됨 - 정상 동작
|
|
}
|
|
}
|
|
|
|
protected void CancelDebounce()
|
|
{
|
|
_debounceCts?.Cancel();
|
|
_debounceCts?.Dispose();
|
|
_debounceCts = null;
|
|
}
|
|
|
|
protected virtual void UpdateReadOnlyState()
|
|
{
|
|
if (_rootElement == null) return;
|
|
|
|
if (_isReadOnly)
|
|
{
|
|
_rootElement.AddToClassList(USS_CLASS_READONLY);
|
|
}
|
|
else
|
|
{
|
|
_rootElement.RemoveFromClassList(USS_CLASS_READONLY);
|
|
}
|
|
}
|
|
|
|
protected virtual void UpdateVisibility()
|
|
{
|
|
if (_rootElement == null) return;
|
|
|
|
_rootElement.style.display = _isVisible ? DisplayStyle.Flex : DisplayStyle.None;
|
|
|
|
if (_isVisible)
|
|
{
|
|
_rootElement.RemoveFromClassList(USS_CLASS_HIDDEN);
|
|
}
|
|
else
|
|
{
|
|
_rootElement.AddToClassList(USS_CLASS_HIDDEN);
|
|
}
|
|
}
|
|
|
|
protected virtual void UpdateTooltip()
|
|
{
|
|
if (_rootElement == null || string.IsNullOrEmpty(_tooltip)) return;
|
|
|
|
_rootElement.SetTooltip(_tooltip);
|
|
}
|
|
|
|
protected Label CreateNameLabel()
|
|
{
|
|
var label = new Label(Name);
|
|
label.AddToClassList("utk-property-item__label");
|
|
return label;
|
|
}
|
|
|
|
protected VisualElement CreateContainer()
|
|
{
|
|
var container = new VisualElement();
|
|
container.AddToClassList("utk-property-item");
|
|
container.AddToClassList($"utk-property-item--{PropertyType.ToString().ToLower()}");
|
|
|
|
if (!string.IsNullOrEmpty(Description))
|
|
{
|
|
container.AddToClassList("utk-property-item--has-description");
|
|
}
|
|
|
|
return container;
|
|
}
|
|
#endregion
|
|
|
|
#region IDisposable
|
|
public void Dispose()
|
|
{
|
|
Dispose(true);
|
|
GC.SuppressFinalize(this);
|
|
}
|
|
|
|
protected virtual void Dispose(bool disposing)
|
|
{
|
|
if (_disposed) return;
|
|
_disposed = true;
|
|
|
|
if (disposing)
|
|
{
|
|
CancelDebounce();
|
|
|
|
if (_rootElement != null)
|
|
{
|
|
UnbindUI(_rootElement);
|
|
}
|
|
|
|
OnValueChanged = null;
|
|
OnTypedValueChanged = null;
|
|
}
|
|
}
|
|
#endregion
|
|
}
|
|
}
|