Files
XRLib/Assets/Scripts/UVC/UIToolkit/Property/Views/UTKDateTimeRangePropertyItemView.cs
2026-02-10 20:48:49 +09:00

441 lines
14 KiB
C#

#nullable enable
using System;
using UnityEngine;
using UnityEngine.UIElements;
namespace UVC.UIToolkit
{
/// <summary>
/// DateTimeRange 속성 View 클래스입니다.
/// 시작, 종료 두 개의 DateTimePicker를 사용하여 날짜시간 범위를 표시/편집합니다.
/// </summary>
[UxmlElement]
public partial class UTKDateTimeRangePropertyItemView : UTKPropertyItemViewBase, IUTKPropertyItemView<UTKDateTimeRange>
{
#region Fields
private UTKInputField? _startField;
private UTKInputField? _endField;
private UTKButton? _startPickerBtn;
private UTKButton? _endPickerBtn;
private UTKDatePicker? _currentPicker;
private bool _isEditingStart;
private UTKDateTimeRange _value;
private string _dateTimeFormat = "yyyy-MM-dd HH:mm";
private IUTKPropertyItem<UTKDateTimeRange>? _boundData;
#endregion
#region Properties
protected override string ViewTypeName => "UTKDateTimeRangePropertyItemView";
/// <summary>현재 값</summary>
public UTKDateTimeRange Value
{
get => _value;
set
{
if (!_value.Equals(value))
{
_value = value;
UpdateValueUI();
OnValueChanged?.Invoke(value);
if (_boundData != null && !_boundData.Value.Equals(value))
{
_boundData.Value = value;
}
}
}
}
/// <summary>날짜시간 표시 형식</summary>
[UxmlAttribute("datetime-format")]
public string DateTimeFormat
{
get => _dateTimeFormat;
set
{
_dateTimeFormat = value ?? "yyyy-MM-dd HH:mm";
UpdateValueUI();
}
}
#endregion
#region Events
public event Action<UTKDateTimeRange>? OnValueChanged;
#endregion
#region Constructor
public UTKDateTimeRangePropertyItemView() : base()
{
_value = new UTKDateTimeRange(DateTime.Now, DateTime.Now);
InitializeUI();
}
public UTKDateTimeRangePropertyItemView(string label, UTKDateTimeRange value = default) : base()
{
_value = value.Start == default ? new UTKDateTimeRange(DateTime.Now, DateTime.Now) : value;
Label = label;
InitializeUI();
}
public UTKDateTimeRangePropertyItemView(string label, DateTime start, DateTime end) : base()
{
_value = new UTKDateTimeRange(start, end);
Label = label;
InitializeUI();
}
#endregion
#region Initialization
private void InitializeUI()
{
AddToClassList("utk-property-item-view");
AddToClassList("utk-property-item-view--datetime-range");
if (!CreateUIFromUxml())
{
CreateUIFallback();
}
// UXML에서 요소 가져오기
QueryUIElements();
// 이벤트 등록
RegisterEvents();
UpdateValueUI();
UpdateReadOnlyState();
}
private void QueryUIElements()
{
_startField = this.Q<UTKInputField>("start-field");
_startPickerBtn = this.Q<UTKButton>("start-picker-btn");
_endField = this.Q<UTKInputField>("end-field");
_endPickerBtn = this.Q<UTKButton>("end-picker-btn");
// Fallback: UXML에서 못 찾으면 생성
if (_valueContainer != null)
{
if (_startField == null)
{
_startField = new UTKInputField { name = "start-field" };
_startField.style.flexGrow = 1;
_startField.AddToClassList("utk-property-item-view__field");
_valueContainer.Add(_startField);
}
if (_startPickerBtn == null)
{
_startPickerBtn = new UTKButton("...", "", UTKButton.ButtonVariant.Secondary)
{
name = "start-picker-btn"
};
_startPickerBtn.AddToClassList("utk-property-item-view__picker-btn");
_valueContainer.Add(_startPickerBtn);
}
// Separator 확인 및 생성
if (this.Q<UTKLabel>(className: "utk-property-item-view__range-separator") == null)
{
var separator = new UTKLabel("~", UTKLabel.LabelSize.Body2);
separator.AddToClassList("utk-property-item-view__range-separator");
_valueContainer.Add(separator);
}
if (_endField == null)
{
_endField = new UTKInputField { name = "end-field" };
_endField.style.flexGrow = 1;
_endField.AddToClassList("utk-property-item-view__field");
_valueContainer.Add(_endField);
}
if (_endPickerBtn == null)
{
_endPickerBtn = new UTKButton("...", "", UTKButton.ButtonVariant.Secondary)
{
name = "end-picker-btn"
};
_endPickerBtn.AddToClassList("utk-property-item-view__picker-btn");
_valueContainer.Add(_endPickerBtn);
}
}
// 초기 값 설정
if (_startField != null)
{
_startField.SetValue(_value.Start.ToString(_dateTimeFormat), false);
_startField.isReadOnly = true;//IsReadOnly와 상관 없이 편집 불가
}
if (_endField != null)
{
_endField.SetValue(_value.End.ToString(_dateTimeFormat), false);
_endField.isReadOnly = true;//IsReadOnly와 상관 없이 편집 불가
}
if (_startPickerBtn != null)
{
_startPickerBtn.IsEnabled = !IsReadOnly;
}
if (_endPickerBtn != null)
{
_endPickerBtn.IsEnabled = !IsReadOnly;
}
}
private void RegisterEvents()
{
if (_startField != null)
{
_startField.OnValueChanged += OnStartTextChanged;
}
if (_endField != null)
{
_endField.OnValueChanged += OnEndTextChanged;
}
if (_startPickerBtn != null)
{
_startPickerBtn.OnClicked += OnStartPickerClicked;
}
if (_endPickerBtn != null)
{
_endPickerBtn.OnClicked += OnEndPickerClicked;
}
}
private void UnregisterEvents()
{
if (_startField != null)
{
_startField.OnValueChanged -= OnStartTextChanged;
}
if (_endField != null)
{
_endField.OnValueChanged -= OnEndTextChanged;
}
if (_startPickerBtn != null)
{
_startPickerBtn.OnClicked -= OnStartPickerClicked;
}
if (_endPickerBtn != null)
{
_endPickerBtn.OnClicked -= OnEndPickerClicked;
}
}
#endregion
#region Override Methods
protected override void CreateValueUI(VisualElement container)
{
// UXML/QueryUIElements 기반으로 생성하므로 여기서는 생성하지 않음
}
public override void RefreshUI()
{
UpdateValueUI();
}
protected override void OnReadOnlyStateChanged(bool isReadOnly)
{
if (_startField != null) _startField.isReadOnly = isReadOnly;
if (_endField != null) _endField.isReadOnly = isReadOnly;
if (_startPickerBtn != null) _startPickerBtn.style.display = isReadOnly ? DisplayStyle.None : DisplayStyle.Flex;
if (_endPickerBtn != null) _endPickerBtn.style.display = isReadOnly ? DisplayStyle.None : DisplayStyle.Flex;
}
#endregion
#region Event Handling
private void OnStartPickerClicked()
{
_isEditingStart = true;
OpenPicker(_value.Start);
}
private void OnEndPickerClicked()
{
_isEditingStart = false;
OpenPicker(_value.End);
}
private void OnStartTextChanged(string newValue)
{
if (DateTime.TryParse(newValue, out DateTime dateTime))
{
var range = new UTKDateTimeRange(dateTime, _value.End);
if (!_value.Equals(range))
{
_value = range;
OnValueChanged?.Invoke(range);
if (_boundData != null && !_boundData.Value.Equals(range))
{
_boundData.Value = range;
}
}
}
}
private void OnEndTextChanged(string newValue)
{
if (DateTime.TryParse(newValue, out DateTime dateTime))
{
var range = new UTKDateTimeRange(_value.Start, dateTime);
if (!_value.Equals(range))
{
_value = range;
OnValueChanged?.Invoke(range);
if (_boundData != null && !_boundData.Value.Equals(range))
{
_boundData.Value = range;
}
}
}
}
private void OpenPicker(DateTime initialDateTime)
{
if (_currentPicker != null) return;
var root = this as VisualElement;
while (root.parent != null) root = root.parent;
string title = _isEditingStart ? $"{Label} - 시작" : $"{Label} - 종료";
_currentPicker = UTKDatePicker.Show(root, initialDateTime, UTKDatePicker.PickerMode.DateAndTime, title);
_currentPicker.OnDateSelected += OnPickerDateSelected;
_currentPicker.OnClosed += OnPickerClosed;
}
private void ClosePicker()
{
if (_currentPicker != null)
{
_currentPicker.OnDateSelected -= OnPickerDateSelected;
_currentPicker.OnClosed -= OnPickerClosed;
_currentPicker.Close();
_currentPicker = null;
}
}
private void OnPickerDateSelected(DateTime dateTime)
{
if (_isEditingStart)
{
Value = new UTKDateTimeRange(dateTime, _value.End);
}
else
{
Value = new UTKDateTimeRange(_value.Start, dateTime);
}
ClosePicker();
}
private void OnPickerClosed() => _currentPicker = null;
#endregion
#region Value Update
private void UpdateValueUI()
{
if (_startField != null)
{
var formatted = _value.Start.ToString(_dateTimeFormat);
if (_startField.Value != formatted)
{
_startField.SetValue(formatted, false);
}
}
if (_endField != null)
{
var formatted = _value.End.ToString(_dateTimeFormat);
if (_endField.Value != formatted)
{
_endField.SetValue(formatted, false);
}
}
}
#endregion
#region Data Binding
public void Bind(IUTKPropertyItem data)
{
if (data is IUTKPropertyItem<UTKDateTimeRange> rangeData)
{
Bind(rangeData);
}
else
{
Debug.LogWarning($"[UTKDateTimeRangePropertyItemView] Cannot bind to non-DateTimeRange data: {data.GetType().Name}");
}
}
public void Bind(IUTKPropertyItem<UTKDateTimeRange> data)
{
Unbind();
_boundData = data;
BindBase(data);
Label = data.Name;
_value = data.Value;
IsVisible = data.IsVisible;
TooltipText = data.Tooltip;
ShowLabel = data.ShowLabel;
if (data is UTKDateTimeRangePropertyItem dateTimeRangeItem)
{
_dateTimeFormat = dateTimeRangeItem.DateTimeFormat;
}
data.OnTypedValueChanged += OnDataValueChanged;
UpdateValueUI();
IsReadOnly = data.IsReadOnly;
}
public void Unbind()
{
if (_boundData != null)
{
_boundData.OnTypedValueChanged -= OnDataValueChanged;
UnbindBase();
_boundData = null;
}
}
private void OnDataValueChanged(IUTKPropertyItem<UTKDateTimeRange> item, UTKDateTimeRange oldValue, UTKDateTimeRange newValue)
{
if (!_value.Equals(newValue))
{
_value = newValue;
UpdateValueUI();
}
}
#endregion
#region Dispose
protected override void Dispose(bool disposing)
{
if (_disposed) return;
if (disposing)
{
ClosePicker();
UnregisterEvents();
Unbind();
OnValueChanged = null;
_startField = null;
_endField = null;
_startPickerBtn = null;
_endPickerBtn = null;
}
base.Dispose(disposing);
}
#endregion
}
}