This commit is contained in:
2026-05-02 21:08:13 +08:00
parent e02c7f5e89
commit 91bc3a269b
27 changed files with 212553 additions and 18619 deletions

View File

@@ -0,0 +1,225 @@
using System.Collections;
using System.Collections.Generic;
using Ichni.RhythmGame;
using TMPro;
using UnityEngine;
using UnityEngine.Events;
using UnityEngine.UI;
namespace Ichni.Editor
{
public class DynamicUISlider : DynamicUIElement
{
public Slider slider;
public TMP_InputField valueInputField;
public TMP_InputField minInputField;
public TMP_InputField maxInputField;
private UnityAction<float> customAction;
private bool updatingInternally; // 防止输入框和滑块互相递归更新
#region [] Initialize
public override void Initialize(IBaseElement baseElement, string title, string parameterName)
{
Initialize(baseElement, title, parameterName, 0f, 1f, false);
}
public void Initialize(IBaseElement baseElement, string title, string parameterName,
float min, float max, bool wholeNumbers = false)
{
slider.minValue = min;
slider.maxValue = max;
slider.wholeNumbers = wholeNumbers;
// [对象池安全] 精准解绑业务代理
slider.onValueChanged.RemoveListener(OnSliderValueChanged);
valueInputField?.onEndEdit.RemoveListener(OnValueInputEndEdit);
minInputField?.onEndEdit.RemoveListener(OnMinInputEndEdit);
maxInputField?.onEndEdit.RemoveListener(OnMaxInputEndEdit);
customAction = null;
base.Initialize(baseElement, title, parameterName);
if (parameterName != string.Empty)
{
var val = ReflectionHelper.GetDeepValue(connectedBaseElement, parameterName);
if (val != null)
{
slider.SetValueWithoutNotify(System.Convert.ToSingle(val));
}
else
{
Debug.LogWarning($"[DynamicUI] 尝试绑定 {title} ({parameterName}) 失败,由于其值或路径无效。");
}
slider.onValueChanged.AddListener(OnSliderValueChanged);
}
else
{
slider.onValueChanged.AddListener(OnSliderValueChanged);
}
SyncAllInputFields();
}
#endregion
#region [] Slider Callback
private void OnSliderValueChanged(float value)
{
if (updatingInternally) return;
updatingInternally = true;
UpdateValueInputText(value);
if (parameterName != string.Empty)
{
ApplyParameters(value);
}
customAction?.Invoke(value);
updatingInternally = false;
}
#endregion
#region [] Input Field Callbacks
private void OnValueInputEndEdit(string text)
{
if (updatingInternally) return;
if (!float.TryParse(text, out float val)) return;
val = Mathf.Clamp(val, slider.minValue, slider.maxValue);
updatingInternally = true;
slider.SetValueWithoutNotify(val);
UpdateValueInputText(val);
if (parameterName != string.Empty)
{
ApplyParameters(val);
}
customAction?.Invoke(val);
updatingInternally = false;
}
private void OnMinInputEndEdit(string text)
{
if (updatingInternally) return;
if (!float.TryParse(text, out float val)) return;
if (val >= slider.maxValue) val = slider.maxValue - 0.0001f;
slider.minValue = val;
if (slider.value < val)
{
slider.value = val;
}
UpdateMinMaxInputTexts();
UpdateValueInputText(slider.value);
}
private void OnMaxInputEndEdit(string text)
{
if (updatingInternally) return;
if (!float.TryParse(text, out float val)) return;
if (val <= slider.minValue) val = slider.minValue + 0.0001f;
slider.maxValue = val;
if (slider.value > val)
{
slider.value = val;
}
UpdateMinMaxInputTexts();
UpdateValueInputText(slider.value);
}
#endregion
#region [] Apply Parameters
private void ApplyParameters(float value)
{
object convertedValue;
if (slider.wholeNumbers)
{
convertedValue = Mathf.RoundToInt(value);
}
else
{
convertedValue = value;
}
Ichni.Editor.Commands.CommandManager.ExecuteCommand(
new Ichni.Editor.Commands.ChangeValueCommand(connectedBaseElement, parameterName, convertedValue));
connectedBaseElement.Refresh();
}
#endregion
#region [UI ] UI Sync Helpers
private void SyncAllInputFields()
{
UpdateValueInputText(slider.value);
UpdateMinMaxInputTexts();
}
private void UpdateValueInputText(float value)
{
if (valueInputField != null)
{
valueInputField.SetTextWithoutNotify(slider.wholeNumbers
? Mathf.RoundToInt(value).ToString()
: value.ToString("F3"));
}
}
private void UpdateMinMaxInputTexts()
{
string FormatRangeVal(float v)
{
return slider.wholeNumbers ? Mathf.RoundToInt(v).ToString() : v.ToString("F3");
}
if (minInputField != null)
{
minInputField.SetTextWithoutNotify(FormatRangeVal(slider.minValue));
}
if (maxInputField != null)
{
maxInputField.SetTextWithoutNotify(FormatRangeVal(slider.maxValue));
}
}
#endregion
#region [Public API]
public void SetRange(float min, float max)
{
slider.minValue = min;
slider.maxValue = max;
UpdateMinMaxInputTexts();
UpdateValueInputText(slider.value);
}
public void SetWholeNumbers(bool wholeNumbers)
{
slider.wholeNumbers = wholeNumbers;
SyncAllInputFields();
}
public override DynamicUIElement AddListenerFunction(UnityAction action)
{
customAction += _ => action();
return this;
}
#endregion
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant: