438 lines
14 KiB
C#
438 lines
14 KiB
C#
#nullable enable
|
|
using System;
|
|
using UnityEngine;
|
|
using UnityEngine.UIElements;
|
|
|
|
namespace UVC.UIToolkit
|
|
{
|
|
/// <summary>
|
|
/// DateRange 속성 View 클래스입니다.
|
|
/// 시작일, 종료일 두 개의 DatePicker를 사용하여 날짜 범위를 표시/편집합니다.
|
|
/// </summary>
|
|
[UxmlElement]
|
|
public partial class UTKDateRangePropertyItemView : UTKPropertyItemViewBase, IUTKPropertyItemView<UTKDateRange>
|
|
{
|
|
#region Fields
|
|
private UTKInputField? _startField;
|
|
private UTKInputField? _endField;
|
|
private UTKButton? _startPickerBtn;
|
|
private UTKButton? _endPickerBtn;
|
|
private UTKDatePicker? _currentPicker;
|
|
private bool _isEditingStart;
|
|
|
|
private UTKDateRange _value;
|
|
private string _dateFormat = "yyyy-MM-dd";
|
|
|
|
private IUTKPropertyItem<UTKDateRange>? _boundData;
|
|
#endregion
|
|
|
|
#region Properties
|
|
protected override string ViewTypeName => "UTKDateRangePropertyItemView";
|
|
|
|
/// <summary>현재 값</summary>
|
|
public UTKDateRange 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("date-format")]
|
|
public string DateFormat
|
|
{
|
|
get => _dateFormat;
|
|
set
|
|
{
|
|
_dateFormat = value ?? "yyyy-MM-dd";
|
|
UpdateValueUI();
|
|
}
|
|
}
|
|
#endregion
|
|
|
|
#region Events
|
|
public event Action<UTKDateRange>? OnValueChanged;
|
|
#endregion
|
|
|
|
#region Constructor
|
|
public UTKDateRangePropertyItemView() : base()
|
|
{
|
|
_value = new UTKDateRange(DateTime.Today, DateTime.Today);
|
|
InitializeUI();
|
|
}
|
|
|
|
public UTKDateRangePropertyItemView(string label, UTKDateRange value = default) : base()
|
|
{
|
|
_value = value.Start == default ? new UTKDateRange(DateTime.Today, DateTime.Today) : value;
|
|
Label = label;
|
|
InitializeUI();
|
|
}
|
|
|
|
public UTKDateRangePropertyItemView(string label, DateTime start, DateTime end) : base()
|
|
{
|
|
_value = new UTKDateRange(start, end);
|
|
Label = label;
|
|
InitializeUI();
|
|
}
|
|
#endregion
|
|
|
|
#region Initialization
|
|
private void InitializeUI()
|
|
{
|
|
AddToClassList("utk-property-item-view");
|
|
AddToClassList("utk-property-item-view--date-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(_dateFormat), false);
|
|
_startField.isReadOnly = true;//IsReadOnly와 상관 없이 편집 불가
|
|
}
|
|
if (_endField != null)
|
|
{
|
|
_endField.SetValue(_value.End.ToString(_dateFormat), 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 date))
|
|
{
|
|
var range = new UTKDateRange(date.Date, _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 date))
|
|
{
|
|
var range = new UTKDateRange(_value.Start, date.Date);
|
|
if (!_value.Equals(range))
|
|
{
|
|
_value = range;
|
|
OnValueChanged?.Invoke(range);
|
|
|
|
if (_boundData != null && !_boundData.Value.Equals(range))
|
|
{
|
|
_boundData.Value = range;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private void OpenPicker(DateTime initialDate)
|
|
{
|
|
if (_currentPicker != null) return;
|
|
|
|
string title = _isEditingStart ? $"{Label} - 시작일" : $"{Label} - 종료일";
|
|
_currentPicker = UTKDatePicker.Show(initialDate, UTKDatePicker.PickerMode.DateOnly, 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 date)
|
|
{
|
|
if (_isEditingStart)
|
|
{
|
|
Value = new UTKDateRange(date.Date, _value.End);
|
|
}
|
|
else
|
|
{
|
|
Value = new UTKDateRange(_value.Start, date.Date);
|
|
}
|
|
ClosePicker();
|
|
}
|
|
|
|
private void OnPickerClosed() => _currentPicker = null;
|
|
#endregion
|
|
|
|
#region Value Update
|
|
private void UpdateValueUI()
|
|
{
|
|
if (_startField != null)
|
|
{
|
|
var formatted = _value.Start.ToString(_dateFormat);
|
|
if (_startField.Value != formatted)
|
|
{
|
|
_startField.SetValue(formatted, false);
|
|
}
|
|
}
|
|
|
|
if (_endField != null)
|
|
{
|
|
var formatted = _value.End.ToString(_dateFormat);
|
|
if (_endField.Value != formatted)
|
|
{
|
|
_endField.SetValue(formatted, false);
|
|
}
|
|
}
|
|
}
|
|
#endregion
|
|
|
|
#region Data Binding
|
|
public void Bind(IUTKPropertyItem data)
|
|
{
|
|
if (data is IUTKPropertyItem<UTKDateRange> rangeData)
|
|
{
|
|
Bind(rangeData);
|
|
}
|
|
else
|
|
{
|
|
Debug.LogWarning($"[UTKDateRangePropertyItemView] Cannot bind to non-DateRange data: {data.GetType().Name}");
|
|
}
|
|
}
|
|
|
|
public void Bind(IUTKPropertyItem<UTKDateRange> data)
|
|
{
|
|
Unbind();
|
|
|
|
_boundData = data;
|
|
BindBase(data);
|
|
|
|
Label = data.Name;
|
|
_value = data.Value;
|
|
IsVisible = data.IsVisible;
|
|
TooltipText = data.Tooltip;
|
|
ShowLabel = data.ShowLabel;
|
|
|
|
if (data is UTKDateRangePropertyItem dateRangeItem)
|
|
{
|
|
_dateFormat = dateRangeItem.DateFormat;
|
|
}
|
|
|
|
data.OnTypedValueChanged += OnDataValueChanged;
|
|
|
|
UpdateValueUI();
|
|
IsReadOnly = data.IsReadOnly;
|
|
}
|
|
|
|
public void Unbind()
|
|
{
|
|
if (_boundData != null)
|
|
{
|
|
_boundData.OnTypedValueChanged -= OnDataValueChanged;
|
|
UnbindBase();
|
|
_boundData = null;
|
|
}
|
|
}
|
|
|
|
private void OnDataValueChanged(IUTKPropertyItem<UTKDateRange> item, UTKDateRange oldValue, UTKDateRange 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
|
|
}
|
|
}
|