diff --git a/Packages/com.bywaystudios.meowmentdebugtool/Runtime/UniversalDebugTool.cs b/Packages/com.bywaystudios.meowmentdebugtool/Runtime/UniversalDebugTool.cs index f37ea28..7811ddd 100644 --- a/Packages/com.bywaystudios.meowmentdebugtool/Runtime/UniversalDebugTool.cs +++ b/Packages/com.bywaystudios.meowmentdebugtool/Runtime/UniversalDebugTool.cs @@ -933,16 +933,69 @@ namespace MeowmentDebugTool } else { + Dictionary>> linkedFieldCallbacks = new Dictionary>>(); + List> initialLinkedValues = new List>(); + foreach (ParameterInfo parameter in parameters) { - Func reader = CreateInputDialogField(parameter); - if (reader == null) - { - Debug.LogWarning($"[MeowmentDebugTool] 暂不支持参数类型: {parameter.ParameterType.Name}"); - return; - } + DebugInputParameterAttribute paramAttr = parameter.GetCustomAttribute(); - readers.Add(reader); + if (paramAttr != null && paramAttr.ReadOnly) + { + string readOnlyLabel = !string.IsNullOrEmpty(paramAttr.Label) ? paramAttr.Label : parameter.Name; + Func displayProvider = ResolveDisplayValueProvider(parameter.Member.DeclaringType, paramAttr.DisplayValueProvider); + + Func readOnlyReader; + Action readOnlyUpdater; + CreateReadOnlyDisplayField(readOnlyLabel, string.Empty, out readOnlyReader, out readOnlyUpdater); + + if (!string.IsNullOrEmpty(paramAttr.LinkedTo)) + { + string linkedTo = paramAttr.LinkedTo; + if (!linkedFieldCallbacks.ContainsKey(linkedTo)) + linkedFieldCallbacks[linkedTo] = new List>(); + + linkedFieldCallbacks[linkedTo].Add(selectedValue => + { + string displayValue = displayProvider != null ? displayProvider(selectedValue) : selectedValue; + readOnlyUpdater(displayValue); + }); + } + + readers.Add(readOnlyReader); + } + else + { + string paramName = parameter.Name; + Action onSelectionChanged = selectedValue => + { + if (linkedFieldCallbacks.TryGetValue(paramName, out List> callbacks)) + foreach (Action cb in callbacks) + cb(selectedValue); + }; + + Func reader = CreateInputDialogField(parameter, onSelectionChanged); + if (reader == null) + { + Debug.LogWarning($"[MeowmentDebugTool] 暂不支持参数类型: {parameter.ParameterType.Name}"); + return; + } + + readers.Add(reader); + + object defaultVal = GetParameterDefaultValue(parameter, paramAttr, out bool hasDefault); + if (hasDefault && defaultVal != null) + { + initialLinkedValues.Add(new KeyValuePair(paramName, defaultVal.ToString())); + } + } + } + + foreach (KeyValuePair kvp in initialLinkedValues) + { + if (linkedFieldCallbacks.TryGetValue(kvp.Key, out List> callbacks)) + foreach (Action cb in callbacks) + cb(kvp.Value); } } @@ -1020,16 +1073,72 @@ namespace MeowmentDebugTool List parameters = definition.Parameters ?? new List(); List> readers = new List>(); + Dictionary>> linkedFieldCallbacks = new Dictionary>>(); + List> initialLinkedValues = new List>(); + foreach (RuntimeDebugInputParameterDefinition parameter in parameters) { - Func reader = CreateRuntimeInputDialogField(parameter); - if (reader == null) + if (parameter != null && parameter.ReadOnly) { - Debug.LogWarning($"[MeowmentDebugTool] 暂不支持运行时输入参数类型: {parameter?.ParameterType?.Name}"); - return; - } + string readOnlyLabel = string.IsNullOrEmpty(parameter.Label) + ? (string.IsNullOrEmpty(parameter.Key) ? "display" : parameter.Key) + : parameter.Label; + Func displayProvider = parameter.DisplayValueProvider; - readers.Add(reader); + Func readOnlyReader; + Action readOnlyUpdater; + CreateReadOnlyDisplayField(readOnlyLabel, string.Empty, out readOnlyReader, out readOnlyUpdater); + + if (!string.IsNullOrEmpty(parameter.LinkedTo)) + { + string linkedTo = parameter.LinkedTo; + if (!linkedFieldCallbacks.ContainsKey(linkedTo)) + linkedFieldCallbacks[linkedTo] = new List>(); + + linkedFieldCallbacks[linkedTo].Add(selectedValue => + { + string displayValue = displayProvider != null ? displayProvider(selectedValue) : selectedValue; + readOnlyUpdater(displayValue); + }); + } + + readers.Add(readOnlyReader); + } + else + { + string paramKey = parameter != null && !string.IsNullOrEmpty(parameter.Key) ? parameter.Key : $"param{readers.Count}"; + Action onSelectionChanged = selectedValue => + { + if (linkedFieldCallbacks.TryGetValue(paramKey, out List> callbacks)) + foreach (Action cb in callbacks) + cb(selectedValue); + }; + + Func reader = CreateRuntimeInputDialogField(parameter, onSelectionChanged); + if (reader == null) + { + Debug.LogWarning($"[MeowmentDebugTool] 暂不支持运行时输入参数类型: {parameter?.ParameterType?.Name}"); + return; + } + + readers.Add(reader); + + if (parameter != null) + { + object defaultVal = GetRuntimeParameterDefaultValue(parameter, out bool hasDefault); + if (hasDefault && defaultVal != null) + { + initialLinkedValues.Add(new KeyValuePair(paramKey, defaultVal.ToString())); + } + } + } + } + + foreach (KeyValuePair kvp in initialLinkedValues) + { + if (linkedFieldCallbacks.TryGetValue(kvp.Key, out List> callbacks)) + foreach (Action cb in callbacks) + cb(kvp.Value); } inputDialogConfirmBtn.onClick.RemoveAllListeners(); @@ -1096,7 +1205,7 @@ namespace MeowmentDebugTool inputDialogGeneratedObjects.Clear(); } - private Func CreateInputDialogField(ParameterInfo parameter) + private Func CreateInputDialogField(ParameterInfo parameter, Action onSelectionChanged = null) { Type parameterType = parameter.ParameterType; DebugInputParameterAttribute parameterAttribute = parameter.GetCustomAttribute(); @@ -1120,7 +1229,7 @@ namespace MeowmentDebugTool return () => CreateInputError(label, $"{label} 当前没有可选项"); } - return CreateDynamicOptionField(fieldRoot.transform, parameterType, label, parameterAttribute, dynamicOptions, defaultValue, hasDefaultValue); + return CreateDynamicOptionField(fieldRoot.transform, parameterType, label, parameterAttribute, dynamicOptions, defaultValue, hasDefaultValue, onSelectionChanged); } if (parameterType == typeof(string)) @@ -1172,7 +1281,7 @@ namespace MeowmentDebugTool return CreateFlagsEnumField(fieldRoot.transform, parameterType, label, parameterAttribute, defaultValue, hasDefaultValue); } - return CreateEnumField(fieldRoot.transform, parameterType, label, parameterAttribute, defaultValue, hasDefaultValue); + return CreateEnumField(fieldRoot.transform, parameterType, label, parameterAttribute, defaultValue, hasDefaultValue, onSelectionChanged); } GameObject unsupported = CreateDialogHint(fieldRoot.transform, $"暂不支持类型: {parameterType.Name}"); @@ -1180,7 +1289,7 @@ namespace MeowmentDebugTool return null; } - private Func CreateRuntimeInputDialogField(RuntimeDebugInputParameterDefinition parameterDefinition) + private Func CreateRuntimeInputDialogField(RuntimeDebugInputParameterDefinition parameterDefinition, Action onSelectionChanged = null) { if (parameterDefinition == null || parameterDefinition.ParameterType == null) { @@ -1208,7 +1317,7 @@ namespace MeowmentDebugTool return () => CreateInputError(label, $"{label} 当前没有可选项"); } - return CreateDynamicOptionField(fieldRoot.transform, parameterType, label, parameterDefinition.Required, dynamicOptions, defaultValue, hasDefaultValue); + return CreateDynamicOptionField(fieldRoot.transform, parameterType, label, parameterDefinition.Required, dynamicOptions, defaultValue, hasDefaultValue, onSelectionChanged); } if (parameterType == typeof(string)) @@ -1260,7 +1369,7 @@ namespace MeowmentDebugTool return CreateFlagsEnumField(fieldRoot.transform, parameterType, label, parameterDefinition.Required, defaultValue, hasDefaultValue); } - return CreateEnumField(fieldRoot.transform, parameterType, label, parameterDefinition.Required, defaultValue, hasDefaultValue); + return CreateEnumField(fieldRoot.transform, parameterType, label, parameterDefinition.Required, defaultValue, hasDefaultValue, onSelectionChanged); } GameObject unsupported = CreateDialogHint(fieldRoot.transform, $"暂不支持类型: {parameterType.Name}"); @@ -1500,7 +1609,7 @@ namespace MeowmentDebugTool } private Func CreateDynamicOptionField(Transform parent, Type parameterType, string label, - DebugInputParameterAttribute parameterAttribute, List options, object defaultValue, bool hasDefaultValue) + DebugInputParameterAttribute parameterAttribute, List options, object defaultValue, bool hasDefaultValue, Action onSelectionChanged = null) { GameObject optionContainer = new GameObject("DynamicOptions"); optionContainer.transform.SetParent(parent, false); @@ -1530,6 +1639,7 @@ namespace MeowmentDebugTool { selectedIndex = currentIndex; UpdateEnumOptionButtonStyles(buttons, selectedIndex); + onSelectionChanged?.Invoke(currentOption.Value?.ToString() ?? string.Empty); }); buttons.Add(optionButton); } @@ -1558,7 +1668,7 @@ namespace MeowmentDebugTool } private Func CreateDynamicOptionField(Transform parent, Type parameterType, string label, - bool required, List options, object defaultValue, bool hasDefaultValue) + bool required, List options, object defaultValue, bool hasDefaultValue, Action onSelectionChanged = null) { GameObject optionContainer = new GameObject("DynamicOptions"); optionContainer.transform.SetParent(parent, false); @@ -1588,6 +1698,7 @@ namespace MeowmentDebugTool { selectedIndex = currentIndex; UpdateEnumOptionButtonStyles(buttons, selectedIndex); + onSelectionChanged?.Invoke(currentOption.Value?.ToString() ?? string.Empty); }); buttons.Add(optionButton); } @@ -1631,7 +1742,7 @@ namespace MeowmentDebugTool } private Func CreateEnumField(Transform parent, Type enumType, string label, - DebugInputParameterAttribute parameterAttribute, object defaultValue, bool hasDefaultValue) + DebugInputParameterAttribute parameterAttribute, object defaultValue, bool hasDefaultValue, Action onSelectionChanged = null) { GameObject optionContainer = new GameObject("Options"); optionContainer.transform.SetParent(parent, false); @@ -1658,6 +1769,7 @@ namespace MeowmentDebugTool { selectedIndex = currentIndex; UpdateEnumOptionButtonStyles(buttons, selectedIndex); + onSelectionChanged?.Invoke(optionValue.ToString()); }); buttons.Add(optionButton); } @@ -1685,7 +1797,7 @@ namespace MeowmentDebugTool } private Func CreateEnumField(Transform parent, Type enumType, string label, - bool required, object defaultValue, bool hasDefaultValue) + bool required, object defaultValue, bool hasDefaultValue, Action onSelectionChanged = null) { GameObject optionContainer = new GameObject("Options"); optionContainer.transform.SetParent(parent, false); @@ -1712,6 +1824,7 @@ namespace MeowmentDebugTool { selectedIndex = currentIndex; UpdateEnumOptionButtonStyles(buttons, selectedIndex); + onSelectionChanged?.Invoke(optionValue.ToString()); }); buttons.Add(optionButton); } @@ -2004,6 +2117,76 @@ namespace MeowmentDebugTool }; } + private void CreateReadOnlyDisplayField(string label, string initialValue, out Func reader, out Action updater) + { + GameObject fieldRoot = CreateInputFieldRoot(label, false); + inputDialogGeneratedObjects.Add(fieldRoot); + + GameObject displayObj = new GameObject("ReadOnlyDisplay"); + displayObj.transform.SetParent(fieldRoot.transform, false); + + Image displayBg = displayObj.AddComponent(); + displayBg.color = new Color(0.14f, 0.14f, 0.14f, 1f); + + LayoutElement layoutEl = displayObj.AddComponent(); + layoutEl.preferredHeight = 64f; + layoutEl.flexibleWidth = 1f; + + GameObject textContainer = new GameObject("TextContainer"); + textContainer.transform.SetParent(displayObj.transform, false); + RectTransform containerRect = textContainer.AddComponent(); + containerRect.anchorMin = Vector2.zero; + containerRect.anchorMax = Vector2.one; + containerRect.offsetMin = new Vector2(20, 0); + containerRect.offsetMax = new Vector2(-20, 0); + + TextMeshProUGUI displayText = textContainer.AddComponent(); + displayText.text = string.IsNullOrEmpty(initialValue) ? "—" : initialValue; + displayText.fontSize = 28; + displayText.color = new Color(1f, 1f, 1f, 0.6f); + displayText.alignment = TextAlignmentOptions.MidlineLeft; + ApplySavedFont(displayText); + + string currentValue = initialValue ?? string.Empty; + + reader = () => new InputDialogValueResult { Success = true, Value = currentValue }; + + updater = value => + { + currentValue = value ?? string.Empty; + displayText.text = string.IsNullOrEmpty(value) ? "—" : value; + }; + } + + private Func ResolveDisplayValueProvider(Type declaringType, string methodName) + { + if (declaringType == null || string.IsNullOrEmpty(methodName)) + { + return null; + } + + MethodInfo method = declaringType.GetMethod( + methodName, + BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic, + null, + new Type[] { typeof(string) }, + null); + + if (method == null) + { + Debug.LogWarning($"[MeowmentDebugTool] 未找到显示值提供方法: {declaringType.Name}.{methodName}(string)"); + return null; + } + + if (method.ReturnType != typeof(string)) + { + Debug.LogWarning($"[MeowmentDebugTool] 显示值提供方法 {methodName} 必须返回 string"); + return null; + } + + return selectedValue => method.Invoke(null, new object[] { selectedValue }) as string ?? string.Empty; + } + private GameObject CreateInputFieldRoot(string label, bool required) { GameObject fieldRoot = new GameObject($"Field_{label}"); @@ -2634,6 +2817,9 @@ public class DebugInputParameterAttribute : Attribute public bool Required { get; set; } public float Min { get; set; } = float.NaN; public float Max { get; set; } = float.NaN; + public bool ReadOnly { get; set; } + public string LinkedTo { get; set; } + public string DisplayValueProvider { get; set; } public DebugInputParameterAttribute(string label = "") { @@ -2642,6 +2828,8 @@ public class DebugInputParameterAttribute : Attribute DefaultValue = string.Empty; OptionsProvider = string.Empty; Required = false; + LinkedTo = string.Empty; + DisplayValueProvider = string.Empty; } } @@ -2694,6 +2882,9 @@ public class RuntimeDebugInputParameterDefinition public float Min { get; set; } = float.NaN; public float Max { get; set; } = float.NaN; public Func OptionsProvider { get; set; } + public bool ReadOnly { get; set; } + public string LinkedTo { get; set; } + public Func DisplayValueProvider { get; set; } public RuntimeDebugInputParameterDefinition(string key, Type parameterType, string label = "") { @@ -2703,6 +2894,7 @@ public class RuntimeDebugInputParameterDefinition Placeholder = string.Empty; DefaultValue = string.Empty; Required = false; + LinkedTo = string.Empty; } } diff --git a/Packages/com.bywaystudios.meowmentdebugtool/package.json b/Packages/com.bywaystudios.meowmentdebugtool/package.json index de39419..f94fdb1 100644 --- a/Packages/com.bywaystudios.meowmentdebugtool/package.json +++ b/Packages/com.bywaystudios.meowmentdebugtool/package.json @@ -1,7 +1,7 @@ { "name": "com.bywaystudios.meowmentdebugtool", "displayName": "MeowmentDebugTool", - "version": "0.5.2", + "version": "0.5.3", "description": "\u8c03\u8bd5\u5de5\u5177\uff0c\u96c6\u6210\u4e86\u5ba2\u6237\u7aef\u6d4b\u8bd5\u65f6\u7684\u5e38\u7528\u529f\u80fd\uff0c\u652f\u6301\u6269\u5c55\u81ea\u5b9a\u4e49\u6309\u94ae\uff0c\u65b9\u4fbf\u5f00\u53d1\u8005\u8c03\u8bd5\u548c\u6d4b\u8bd5\u3002", "samples": [ {