Files
EnglewoodLAB/Assets/Scripts/UVC/UIToolkit/Property/Views/UTKStringPropertyItemView.cs

403 lines
12 KiB
C#

#nullable enable
using System;
using UnityEngine;
using UnityEngine.UIElements;
namespace UVC.UIToolkit
{
/// <summary>
/// String 속성 View 클래스입니다.
/// UTKInputField를 사용하여 문자열 값을 표시/편집합니다.
///
/// <para><b>사용법 (단독 사용):</b></para>
/// <code>
/// // C# 코드에서 생성
/// var view = new UTKStringPropertyItemView();
/// view.Label = "이름";
/// view.Value = "홍길동";
/// view.IsMultiline = false;
/// parent.Add(view);
///
/// // UXML에서 사용
/// <utk:UTKStringPropertyItemView label="이름" value="홍길동" is-multiline="false" />
/// </code>
/// </summary>
[UxmlElement]
public partial class UTKStringPropertyItemView : UTKPropertyItemViewBase, IUTKPropertyItemView<string>
{
#region Fields
private UTKInputField? _inputField;
private UTKButton? _actionButton;
private Action? _actionButtonClickHandler; // 이벤트 핸들러 참조 보관
private string _value = string.Empty;
private bool _isMultiline = false;
private int _maxLength = -1;
private IUTKPropertyItem<string>? _boundData;
#endregion
#region Properties
protected override string ViewTypeName => "UTKStringPropertyItemView";
/// <summary>현재 값</summary>
[UxmlAttribute("value")]
public string Value
{
get => _value;
set
{
var newValue = value ?? string.Empty;
if (_value != newValue)
{
_value = newValue;
UpdateValueUI();
OnValueChanged?.Invoke(newValue);
if (_boundData != null && _boundData.Value != newValue)
{
_boundData.Value = newValue;
}
}
}
}
/// <summary>멀티라인 모드 여부</summary>
[UxmlAttribute("is-multiline")]
public bool IsMultiline
{
get => _isMultiline;
set
{
_isMultiline = value;
if (_inputField != null)
{
_inputField.multiline = value;
}
}
}
/// <summary>최대 문자 길이 (-1 = 무제한)</summary>
[UxmlAttribute("max-length")]
public int MaxLength
{
get => _maxLength;
set
{
_maxLength = value;
if (_inputField != null)
{
_inputField.maxLength = value;
}
}
}
#endregion
#region Events
public event Action<string>? OnValueChanged;
/// <summary>액션 버튼 클릭 이벤트 (actionName 전달)</summary>
public event Action<string>? OnActionButtonClicked;
#endregion
#region Constructor
public UTKStringPropertyItemView() : base()
{
InitializeUI();
}
public UTKStringPropertyItemView(UTKStringPropertyItem item) : base()
{
_value = item.Value ?? string.Empty;
_isMultiline = item.IsMultiline;
_maxLength = item.MaxLength;
Label = item.Name;
_isReadOnly = item.IsReadOnly;
InitializeUI();
Bind(item);
}
public UTKStringPropertyItemView(string label, string value = "", bool isMultiline = false, int maxLength = -1, bool isReadOnly = false) : base()
{
_value = value ?? string.Empty;
_isMultiline = isMultiline;
_maxLength = maxLength;
Label = label;
_isReadOnly = isReadOnly;
InitializeUI();
}
#endregion
#region Initialization
private void InitializeUI()
{
AddToClassList("utk-property-item-view");
AddToClassList("utk-property-item-view--string");
if (!CreateUIFromUxml())
{
CreateUIFallback();
}
// UXML에서 요소 가져오기
QueryUIElements();
// 이벤트 등록
RegisterEvents();
UpdateValueUI();
UpdateReadOnlyState();
}
private void QueryUIElements()
{
_inputField = this.Q<UTKInputField>("value-field");
// Fallback: UXML에서 못 찾으면 생성
if (_valueContainer != null && _inputField == null)
{
_inputField = new UTKInputField { name = "value-field" };
_inputField.AddToClassList("utk-property-item-view__field");
_valueContainer.Add(_inputField);
}
// 초기 값 설정
if (_inputField != null)
{
_inputField.SetValue(_value, false);
_inputField.multiline = _isMultiline;
_inputField.isReadOnly = IsReadOnly;
if (_maxLength >= 0)
{
_inputField.maxLength = _maxLength;
}
}
}
private void RegisterEvents()
{
if (_inputField != null)
{
_inputField.OnValueChanged += OnInputChanged;
}
}
private void UnregisterEvents()
{
if (_inputField != null)
{
_inputField.OnValueChanged -= OnInputChanged;
}
}
#endregion
#region Override Methods
protected override void CreateValueUI(VisualElement container)
{
// UXML/QueryUIElements 기반으로 생성하므로 여기서는 생성하지 않음
}
public override void RefreshUI()
{
UpdateValueUI();
}
protected override void OnReadOnlyStateChanged(bool isReadOnly)
{
if (_inputField != null)
{
_inputField.isReadOnly = isReadOnly;
}
}
#endregion
#region Event Handling
private void OnInputChanged(string newValue)
{
if (_value != newValue)
{
_value = newValue;
OnValueChanged?.Invoke(newValue);
if (_boundData != null && _boundData.Value != newValue)
{
_boundData.Value = newValue;
}
}
}
#endregion
#region Value Update
private void UpdateValueUI()
{
if (_inputField != null && _inputField.Value != _value)
{
_inputField.SetValue(_value, false);
}
}
#endregion
#region Action Button
private void CreateActionButton(UTKButtonItem buttonItem)
{
// 기존 버튼 제거
RemoveActionButton();
// 새 버튼 생성
_actionButton = new UTKButton
{
name = buttonItem.Name,
Text = buttonItem.Text,
Icon = buttonItem.Icon,
IconSize = buttonItem.IconSize,
Variant = buttonItem.Variant,
Size = buttonItem.Size,
IconOnly = buttonItem.IconOnly
};
if(buttonItem.IconOnly == false){
_actionButton.AddToClassList("utk-property-item-view__action-button");
}
else
{
_actionButton.AddToClassList("utk-property-item-view__action-button-icon");
}
if (buttonItem.BackgroundColor.HasValue)
{
_actionButton.BackgroundColor = buttonItem.BackgroundColor;
}
if (buttonItem.BorderWidth >= 0)
{
_actionButton.BorderWidth = buttonItem.BorderWidth;
}
// 버튼 클릭 이벤트 등록 (핸들러 참조 보관)
_actionButtonClickHandler = () => OnActionButtonClicked?.Invoke(buttonItem.ActionName);
_actionButton.OnClicked += _actionButtonClickHandler;
// value-container에 추가 (InputField 다음)
_valueContainer?.Add(_actionButton);
}
private void RemoveActionButton()
{
if (_actionButton != null)
{
// 보관된 핸들러로 이벤트 해제
if (_actionButtonClickHandler != null)
{
_actionButton.OnClicked -= _actionButtonClickHandler;
_actionButtonClickHandler = null;
}
_actionButton.RemoveFromHierarchy();
_actionButton.Dispose();
_actionButton = null;
}
}
#endregion
#region Data Binding
public void Bind(IUTKPropertyItem data)
{
if (data is IUTKPropertyItem<string> stringData)
{
Bind(stringData);
}
else
{
Debug.LogWarning($"[UTKStringPropertyItemView] Cannot bind to non-string data: {data.GetType().Name}");
}
}
public void Bind(IUTKPropertyItem<string> data)
{
Unbind();
_boundData = data;
BindBase(data);
Label = data.Name;
_value = data.Value ?? string.Empty;
IsVisible = data.IsVisible;
TooltipText = data.Tooltip;
ShowLabel = data.ShowLabel;
if (data is UTKStringPropertyItem stringItem)
{
_isMultiline = stringItem.IsMultiline;
_maxLength = stringItem.MaxLength;
if (_inputField != null)
{
_inputField.multiline = _isMultiline;
_inputField.maxLength = _maxLength;
}
// ActionButton이 있으면 버튼 생성
if (stringItem.ActionButton != null && _valueContainer != null)
{
CreateActionButton(stringItem.ActionButton);
}
else
{
RemoveActionButton();
}
}
data.OnTypedValueChanged += OnDataValueChanged;
UpdateValueUI();
IsReadOnly = data.IsReadOnly;
}
public void Unbind()
{
if (_boundData != null)
{
_boundData.OnTypedValueChanged -= OnDataValueChanged;
UnbindBase();
_boundData = null;
}
// ActionButton 제거
RemoveActionButton();
}
private void OnDataValueChanged(IUTKPropertyItem<string> item, string oldValue, string newValue)
{
if (_value != newValue)
{
_value = newValue ?? string.Empty;
UpdateValueUI();
}
}
#endregion
#region Dispose
protected override void Dispose(bool disposing)
{
if (_disposed) return;
if (disposing)
{
UnregisterEvents();
Unbind();
// UTKInputField Dispose 호출
if (_inputField is IDisposable disposableField)
{
disposableField.Dispose();
}
OnValueChanged = null;
OnActionButtonClicked = null;
_inputField = null;
}
base.Dispose(disposing);
}
#endregion
}
}