#nullable enable using System; using UnityEngine; using UnityEngine.UIElements; namespace UVC.UIToolkit { /// /// String 속성 View 클래스입니다. /// UTKInputField를 사용하여 문자열 값을 표시/편집합니다. /// /// 사용법 (단독 사용): /// /// // C# 코드에서 생성 /// var view = new UTKStringPropertyItemView(); /// view.Label = "이름"; /// view.Value = "홍길동"; /// view.IsMultiline = false; /// parent.Add(view); /// /// // UXML에서 사용 /// <utk:UTKStringPropertyItemView label="이름" value="홍길동" is-multiline="false" /> /// /// [UxmlElement] public partial class UTKStringPropertyItemView : UTKPropertyItemViewBase, IUTKPropertyItemView { #region Fields private UTKInputField? _inputField; private string _value = string.Empty; private bool _isMultiline = false; private int _maxLength = -1; private IUTKPropertyItem? _boundData; #endregion #region Properties protected override string ViewTypeName => "UTKStringPropertyItemView"; /// 현재 값 [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; } } } } /// 멀티라인 모드 여부 [UxmlAttribute("is-multiline")] public bool IsMultiline { get => _isMultiline; set { _isMultiline = value; if (_inputField != null) { _inputField.multiline = value; } } } /// 최대 문자 길이 (-1 = 무제한) [UxmlAttribute("max-length")] public int MaxLength { get => _maxLength; set { _maxLength = value; if (_inputField != null) { _inputField.maxLength = value; } } } #endregion #region Events public event Action? OnValueChanged; #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("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 Data Binding public void Bind(IUTKPropertyItem data) { if (data is IUTKPropertyItem stringData) { Bind(stringData); } else { Debug.LogWarning($"[UTKStringPropertyItemView] Cannot bind to non-string data: {data.GetType().Name}"); } } public void Bind(IUTKPropertyItem data) { Unbind(); _boundData = data; Label = data.Name; _value = data.Value ?? string.Empty; IsReadOnly = data.IsReadOnly; IsVisible = data.IsVisible; TooltipText = data.Tooltip; if (data is UTKStringPropertyItem stringItem) { _isMultiline = stringItem.IsMultiline; _maxLength = stringItem.MaxLength; if (_inputField != null) { _inputField.multiline = _isMultiline; _inputField.maxLength = _maxLength; } } data.OnTypedValueChanged += OnDataValueChanged; UpdateValueUI(); UpdateReadOnlyState(); } public void Unbind() { if (_boundData != null) { _boundData.OnTypedValueChanged -= OnDataValueChanged; _boundData = null; } } private void OnDataValueChanged(IUTKPropertyItem 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(); OnValueChanged = null; _inputField = null; } base.Dispose(disposing); } #endregion } }