EnergyEfficiencyManagement/Assets/Zion/Scripts/NumberInputField.cs

411 lines
11 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

using UnityEngine;
using UnityEngine.UI;
using System.Collections;
#if TMP_PRESENT
using TMPro;
#endif
/// <summary>
/// 限制InputField只能输入数字和小数
/// 支持Unity原生InputField和TextMeshPro的TMP_InputField
/// </summary>
public class NumberInputField : MonoBehaviour
{
[Header("组件设置")]
[Tooltip("拖入一个InputField或TMP_InputField组件。如果不指定会自动获取当前物体上的组件。")]
public MonoBehaviour targetInputField;
[Header("输入规则")]
[Tooltip("是否允许输入小数(小数点)")]
public bool allowDecimal = true;
[Tooltip("是否允许输入负数")]
public bool allowNegative = false;
[Tooltip("是否允许为空")]
public bool allowEmpty = true;
[Tooltip("小数点后最多几位0表示不限制")]
[Range(0, 10)]
public int maxDecimalPlaces = 2;
[Header("范围限制")]
[Tooltip("允许输入的最小值。如果最小值大于最大值,则无下限限制。")]
public float minValue = float.MinValue; // 默认无下限
[Tooltip("允许输入的最大值。如果最大值小于最小值,则无上限限制。")]
public float maxValue = float.MaxValue; // 默认无上限
private string previousValidText = "";
private InputField legacyInputField;
#if TMP_PRESENT
private TMP_InputField tmpInputField;
#endif
private bool isInitialized = false;
void Start()
{
InitializeInputField();
}
void OnEnable()
{
if (isInitialized)
{
RestorePreviousValidText();
}
}
/// <summary>
/// 初始化输入框组件
/// </summary>
public void InitializeInputField()
{
if (targetInputField == null)
{
legacyInputField = GetComponent<InputField>();
if (legacyInputField != null)
{
targetInputField = legacyInputField;
}
#if TMP_PRESENT
else
{
tmpInputField = GetComponent<TMP_InputField>();
if (tmpInputField != null)
{
targetInputField = tmpInputField;
}
}
#endif
}
else
{
legacyInputField = targetInputField.GetComponent<InputField>();
#if TMP_PRESENT
if (legacyInputField == null)
{
tmpInputField = targetInputField.GetComponent<TMP_InputField>();
}
#endif
}
if (targetInputField == null)
{
Debug.LogError("NumberInputField: 未找到InputField或TMP_InputField组件", this);
return;
}
// 保存初始文本
if (legacyInputField != null)
{
previousValidText = legacyInputField.text;
legacyInputField.onEndEdit.AddListener(OnInputValueChanged);
}
#if TMP_PRESENT
else if (tmpInputField != null)
{
previousValidText = tmpInputField.text;
tmpInputField.onEndEdit.AddListener(OnInputValueChanged);
}
#endif
isInitialized = true;
}
/// <summary>
/// 当输入框值改变时调用
/// </summary>
private void OnInputValueChanged(string newText)
{
if (string.IsNullOrEmpty(newText))
{
if (allowEmpty)
{
previousValidText = "";
return;
}
else
{
// 不允许为空,恢复到上一个有效值
SetInputFieldText(previousValidText);
return;
}
}
// 检查是否为有效数字格式
if (IsValidNumber(newText))
{
// 尝试将文本解析为数字,以进行范围检查
if (float.TryParse(newText, out float currentValue))
{
// 进行范围检查
if (currentValue < minValue || currentValue > maxValue)
{
// 数值超出范围,恢复到上一个有效文本
StartCoroutine(RestoreTextNextFrame(previousValidText));
return;
}
}
// 检查小数位数限制
if (maxDecimalPlaces > 0 && HasTooManyDecimalPlaces(newText))
{
// 小数位数过多,截断
string truncatedText = TruncateDecimalPlaces(newText);
SetInputFieldText(truncatedText);
previousValidText = truncatedText;
}
else
{
// 新文本有效更新previousValidText
previousValidText = newText;
}
}
else
{
// 新文本无效,将文本回滚到上一次有效的状态
// 使用协程延迟设置,避免在事件内部直接修改可能引起的冲突
StartCoroutine(RestoreTextNextFrame(previousValidText));
}
}
/// <summary>
/// 验证字符串是否为有效的数字格式
/// </summary>
private bool IsValidNumber(string str)
{
// 1. 检查是否为空(已经在调用处处理)
if (string.IsNullOrEmpty(str))
{
return allowEmpty;
}
// 2. 检查负号
if (str.StartsWith("-"))
{
if (!allowNegative) return false;
// 如果只有一个负号,允许(用户可能正在输入)
if (str == "-") return true;
// 负号必须在开头且只能有一个
if (str.IndexOf("-", 1) != -1) return false;
}
else if (str.StartsWith("+"))
{
// 处理正号
if (str == "+") return true;
if (str.IndexOf("+", 1) != -1) return false;
}
// 3. 检查小数点和数字
bool hasDecimalPoint = false;
int decimalPlaces = 0;
for (int i = 0; i < str.Length; i++)
{
char c = str[i];
// 跳过开头的负号/正号
if (i == 0 && (c == '-' || c == '+')) continue;
if (c == '.')
{
if (!allowDecimal) return false; // 不允许小数
if (hasDecimalPoint) return false; // 已经有一个小数点了
hasDecimalPoint = true;
// 小数点不能是唯一字符(除非前面有符号)
if (i == 0 || (i == 1 && (str[0] == '-' || str[0] == '+')))
{
// 允许这种情况,用户可能先输入小数点
}
}
else if (c >= '0' && c <= '9')
{
// 如果是数字,并且已经遇到小数点,计数小数位数
if (hasDecimalPoint)
{
decimalPlaces++;
}
}
else
{
// 不是数字也不是小数点,非法字符
return false;
}
}
// 最终检查:不能只有一个小数点(例如"."或"-."),除非允许这种情况
string numWithoutSign = str.TrimStart('-', '+');
if (numWithoutSign == ".") return false;
return true;
}
/// <summary>
/// 检查小数位数是否过多
/// </summary>
private bool HasTooManyDecimalPlaces(string str)
{
if (maxDecimalPlaces <= 0 || !allowDecimal) return false;
int decimalIndex = str.IndexOf('.');
if (decimalIndex == -1) return false;
int decimalPlaces = str.Length - decimalIndex - 1;
return decimalPlaces > maxDecimalPlaces;
}
/// <summary>
/// 截断过多的小数位数
/// </summary>
private string TruncateDecimalPlaces(string str)
{
if (maxDecimalPlaces <= 0) return str;
int decimalIndex = str.IndexOf('.');
if (decimalIndex == -1) return str;
if (decimalIndex + 1 + maxDecimalPlaces < str.Length)
{
return str.Substring(0, decimalIndex + 1 + maxDecimalPlaces);
}
return str;
}
/// <summary>
/// 在下一帧恢复文本
/// </summary>
private IEnumerator RestoreTextNextFrame(string textToRestore)
{
yield return null;
SetInputFieldText(textToRestore);
}
/// <summary>
/// 设置输入框文本
/// </summary>
private void SetInputFieldText(string text)
{
if (legacyInputField != null)
{
legacyInputField.text = text;
}
#if TMP_PRESENT
else if (tmpInputField != null)
{
tmpInputField.text = text;
}
#endif
}
/// <summary>
/// 恢复上一次有效的文本
/// </summary>
private void RestorePreviousValidText()
{
SetInputFieldText(previousValidText);
}
/// <summary>
/// 获取当前输入框的浮点数值如果为空或无效则返回defaultValue
/// </summary>
public float GetNumber(float defaultValue = 0)
{
string text = "";
if (legacyInputField != null)
{
text = legacyInputField.text;
}
#if TMP_PRESENT
else if (tmpInputField != null)
{
text = tmpInputField.text;
}
#endif
if (string.IsNullOrEmpty(text) || text == "-" || text == "+" || text == ".")
{
return defaultValue;
}
if (float.TryParse(text, out float result))
{
return result;
}
return defaultValue;
}
/// <summary>
/// 获取当前输入框的整型值会截断小数部分如果为空或无效则返回defaultValue
/// </summary>
public int GetInt(int defaultValue = 0)
{
float num = GetNumber(defaultValue);
return Mathf.RoundToInt(num);
}
/// <summary>
/// 设置输入框的值
/// </summary>
public void SetNumber(float value)
{
// 确保设置的值在允许的范围内
float clampedValue = Mathf.Clamp(value, minValue, maxValue);
string text = clampedValue.ToString();
if (!allowDecimal)
{
text = Mathf.RoundToInt(clampedValue).ToString();
}
else if (maxDecimalPlaces > 0)
{
text = clampedValue.ToString("F" + maxDecimalPlaces);
}
SetInputFieldText(text);
previousValidText = text;
}
/// <summary>
/// 清空输入框
/// </summary>
public void Clear()
{
SetInputFieldText("");
previousValidText = "";
}
/// <summary>
/// 在编辑器中手动初始化绑定
/// </summary>
[ContextMenu("初始化绑定")]
private void EditorInitialize()
{
InitializeInputField();
if (targetInputField != null)
{
Debug.Log("NumberInputField: 成功绑定到 " + targetInputField.GetType().Name, this);
}
}
/// <summary>
/// 在销毁时取消事件监听
/// </summary>
void OnDestroy()
{
if (legacyInputField != null)
{
legacyInputField.onValueChanged.RemoveListener(OnInputValueChanged);
}
#if TMP_PRESENT
if (tmpInputField != null)
{
tmpInputField.onValueChanged.RemoveListener(OnInputValueChanged);
}
#endif
}
}