#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에서 사용 /// /// /// [UxmlElement] public partial class UTKStringPropertyItemView : UTKPropertyItemViewBase, IUTKPropertyItemView { #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? _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; /// 액션 버튼 클릭 이벤트 (actionName 전달) public event Action? 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("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 stringData) { Bind(stringData); } else { Debug.LogWarning($"[UTKStringPropertyItemView] Cannot bind to non-string data: {data.GetType().Name}"); } } public void Bind(IUTKPropertyItem 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 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 } }