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 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 } }