UTKInput Validation 기능 추가
This commit is contained in:
@@ -25,6 +25,12 @@ namespace UVC.UIToolkit
|
||||
/// <item><description>증감 단위 설정 (Step)</description></item>
|
||||
/// <item><description>순환 모드 (WrapAround) - 최대에서 최소로, 최소에서 최대로</description></item>
|
||||
/// </list>
|
||||
/// <para><b>Validation (유효성 검사):</b></para>
|
||||
/// <para>
|
||||
/// <see cref="Validation"/> 속성에 검증 함수를 설정하면,
|
||||
/// 포커스 아웃 시 자동으로 유효성 검사를 수행합니다.
|
||||
/// 검증 실패 시 <see cref="ErrorMessage"/>에 설정된 메시지가 표시됩니다.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
/// <example>
|
||||
/// <para><b>C# 코드에서 사용:</b></para>
|
||||
@@ -56,6 +62,12 @@ namespace UVC.UIToolkit
|
||||
/// var readOnlyStepper = new UTKFloatStepper(0f, 10f, 5f, 0.1f);
|
||||
/// readOnlyStepper.IsReadOnly = true;
|
||||
/// </code>
|
||||
/// <para><b>Validation 사용:</b></para>
|
||||
/// <code>
|
||||
/// var volumeStepper = new UTKFloatStepper(0f, 1f, 0.5f, 0.1f);
|
||||
/// volumeStepper.ErrorMessage = "볼륨은 0~1 사이여야 합니다.";
|
||||
/// volumeStepper.Validation = () => volumeStepper.Value >= 0f && volumeStepper.Value <= 1f;
|
||||
/// </code>
|
||||
/// <para><b>UXML에서 사용:</b></para>
|
||||
/// <code><![CDATA[
|
||||
/// <!-- 기본 스테퍼 -->
|
||||
@@ -69,6 +81,9 @@ namespace UVC.UIToolkit
|
||||
///
|
||||
/// <!-- 읽기 전용 -->
|
||||
/// <utk:UTKFloatStepper value="5.0" is-readonly="true" />
|
||||
///
|
||||
/// <!-- 에러 메시지 설정 -->
|
||||
/// <utk:UTKFloatStepper value="0.5" error-message="볼륨은 0~1 사이여야 합니다." />
|
||||
/// ]]></code>
|
||||
/// <para><b>실제 활용 예시 (볼륨 조절):</b></para>
|
||||
/// <code>
|
||||
@@ -142,6 +157,20 @@ namespace UVC.UIToolkit
|
||||
EnableInClassList("utk-number-stepper--readonly", value);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>에러 메시지. 비어있지 않으면 에러 상태로 표시</summary>
|
||||
[UxmlAttribute("error-message")]
|
||||
public string ErrorMessage
|
||||
{
|
||||
get => _errorMessage;
|
||||
set
|
||||
{
|
||||
_errorMessage = value;
|
||||
var hasError = !string.IsNullOrEmpty(value);
|
||||
EnableInClassList("utk-number-stepper--error", hasError);
|
||||
UpdateErrorLabel(hasError ? value : null);
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Fields
|
||||
@@ -154,10 +183,29 @@ namespace UVC.UIToolkit
|
||||
private bool _wrapAround;
|
||||
private bool _isUpdating;
|
||||
private bool _isHovered;
|
||||
private string _errorMessage = "";
|
||||
|
||||
private TextField? _textField;
|
||||
private Button? _upButton;
|
||||
private Button? _downButton;
|
||||
|
||||
/// <summary>유효성 검사 함수. true 반환 시 유효, false 반환 시 에러 표시</summary>
|
||||
private Func<bool>? _validation;
|
||||
|
||||
/// <summary>에러 메시지 표시용 레이블</summary>
|
||||
private Label? _errorLabel;
|
||||
#endregion
|
||||
|
||||
#region Properties
|
||||
/// <summary>
|
||||
/// 유효성 검사 함수.
|
||||
/// 포커스 아웃 시 자동으로 호출되며, false 반환 시 에러 스타일과 메시지가 표시됩니다.
|
||||
/// </summary>
|
||||
public Func<bool>? Validation
|
||||
{
|
||||
get => _validation;
|
||||
set => _validation = value;
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Events
|
||||
@@ -241,6 +289,7 @@ namespace UVC.UIToolkit
|
||||
if (notify)
|
||||
{
|
||||
OnValueChanged?.Invoke(_value);
|
||||
RunValidation();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -288,6 +337,18 @@ namespace UVC.UIToolkit
|
||||
{
|
||||
_textField?.Focus();
|
||||
}
|
||||
|
||||
/// <summary>강제로 Validation을 실행하여 에러 상태를 업데이트합니다.</summary>
|
||||
public bool Validate()
|
||||
{
|
||||
return RunValidation();
|
||||
}
|
||||
|
||||
/// <summary>에러 상태를 수동으로 해제합니다.</summary>
|
||||
public void ClearError()
|
||||
{
|
||||
ErrorMessage = "";
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Private Methods - UI Creation
|
||||
@@ -344,6 +405,7 @@ namespace UVC.UIToolkit
|
||||
|
||||
_textField?.RegisterCallback<ChangeEvent<string>>(OnTextFieldChanged);
|
||||
_textField?.RegisterCallback<KeyDownEvent>(OnTextFieldKeyDown, TrickleDown.TrickleDown);
|
||||
_textField?.RegisterCallback<FocusOutEvent>(OnTextFieldFocusOut);
|
||||
|
||||
RegisterCallback<MouseEnterEvent>(OnMouseEnter);
|
||||
RegisterCallback<MouseLeaveEvent>(OnMouseLeave);
|
||||
@@ -397,6 +459,12 @@ namespace UVC.UIToolkit
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>텍스트 필드 포커스 아웃 시 유효성 검사 실행</summary>
|
||||
private void OnTextFieldFocusOut(FocusOutEvent evt)
|
||||
{
|
||||
RunValidation();
|
||||
}
|
||||
|
||||
private void OnMouseEnter(MouseEnterEvent evt) => _isHovered = true;
|
||||
private void OnMouseLeave(MouseLeaveEvent evt) => _isHovered = false;
|
||||
|
||||
@@ -433,9 +501,25 @@ namespace UVC.UIToolkit
|
||||
|
||||
private float ClampValueInternal(float value)
|
||||
{
|
||||
// Step 소수점 자릿수 기준으로 반올림하여 부동소수점 오차 제거
|
||||
// 예: step=0.1 → digits=1, 0.7+0.1=0.8000001 → 0.8
|
||||
int digits = GetDecimalDigits(_step);
|
||||
if (digits > 0)
|
||||
{
|
||||
value = (float)Math.Round(value, digits);
|
||||
}
|
||||
return Mathf.Clamp(value, _minValue, _maxValue);
|
||||
}
|
||||
|
||||
/// <summary>소수점 이하 유효 자릿수를 반환합니다.</summary>
|
||||
private static int GetDecimalDigits(float value)
|
||||
{
|
||||
// 최대 7자리까지 검사 (float 정밀도 한계)
|
||||
string s = value.ToString("G7");
|
||||
int dotIndex = s.IndexOf('.');
|
||||
return dotIndex < 0 ? 0 : s.Length - dotIndex - 1;
|
||||
}
|
||||
|
||||
private void UpdateReadOnlyState()
|
||||
{
|
||||
if (_textField != null)
|
||||
@@ -453,6 +537,47 @@ namespace UVC.UIToolkit
|
||||
_downButton.SetEnabled(!_isReadOnly);
|
||||
}
|
||||
}
|
||||
|
||||
private bool RunValidation()
|
||||
{
|
||||
if (_validation == null) return true;
|
||||
|
||||
var isValid = _validation.Invoke();
|
||||
if (isValid)
|
||||
{
|
||||
EnableInClassList("utk-number-stepper--error", false);
|
||||
UpdateErrorLabel(null);
|
||||
}
|
||||
else
|
||||
{
|
||||
EnableInClassList("utk-number-stepper--error", true);
|
||||
UpdateErrorLabel(_errorMessage);
|
||||
}
|
||||
return isValid;
|
||||
}
|
||||
|
||||
private void UpdateErrorLabel(string? message)
|
||||
{
|
||||
if (string.IsNullOrEmpty(message))
|
||||
{
|
||||
if (_errorLabel != null)
|
||||
{
|
||||
_errorLabel.style.display = DisplayStyle.None;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (_errorLabel == null)
|
||||
{
|
||||
_errorLabel = new Label();
|
||||
_errorLabel.AddToClassList("utk-number-stepper__error-message");
|
||||
_errorLabel.style.display = DisplayStyle.None;
|
||||
Add(_errorLabel);
|
||||
}
|
||||
|
||||
_errorLabel.text = message;
|
||||
_errorLabel.style.display = DisplayStyle.Flex;
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region IDisposable
|
||||
@@ -471,6 +596,7 @@ namespace UVC.UIToolkit
|
||||
|
||||
_textField?.UnregisterCallback<ChangeEvent<string>>(OnTextFieldChanged);
|
||||
_textField?.UnregisterCallback<KeyDownEvent>(OnTextFieldKeyDown, TrickleDown.TrickleDown);
|
||||
_textField?.UnregisterCallback<FocusOutEvent>(OnTextFieldFocusOut);
|
||||
|
||||
UnregisterCallback<MouseEnterEvent>(OnMouseEnter);
|
||||
UnregisterCallback<MouseLeaveEvent>(OnMouseLeave);
|
||||
@@ -481,6 +607,10 @@ namespace UVC.UIToolkit
|
||||
OnTabPressed = null;
|
||||
OnShiftTabPressed = null;
|
||||
|
||||
// Validation 정리
|
||||
_validation = null;
|
||||
_errorLabel = null;
|
||||
|
||||
// UI 참조 정리
|
||||
_textField = null;
|
||||
_upButton = null;
|
||||
|
||||
Reference in New Issue
Block a user