using System; using System.Collections.Generic; using System.Linq; using AlicizaX.UI.Extension; using UnityEditor; using UnityEditor.SceneManagement; using UnityEditorInternal; using UnityEditor.UI; using UnityEngine; using UnityEngine.UI; using AnimatorControllerParameterType = UnityEngine.AnimatorControllerParameterType; [CanEditMultipleObjects] [CustomEditor(typeof(UXButton), true)] internal class UXButtonEditor : UXSelectableEditor { private enum TabType { Image, Sound, Event } private SerializedProperty m_Mode; private SerializedProperty m_OnValueChanged; private SerializedProperty m_OnClick; private SerializedProperty m_UXGroup; private SerializedProperty m_ChildTransitions; private ReorderableList m_ChildTransitionList; private static Color darkZebraEven = new Color(0.22f, 0.22f, 0.22f); private static Color darkZebraOdd = new Color(0.27f, 0.27f, 0.27f); private SerializedProperty hoverAudioClip; private SerializedProperty clickAudioClip; private UXButton mTarget; protected override void OnEnable() { base.OnEnable(); mTarget = (UXButton)target; m_UXGroup = serializedObject.FindProperty("m_UXGroup"); m_Mode = serializedObject.FindProperty("m_Mode"); m_OnValueChanged = serializedObject.FindProperty("m_OnValueChanged"); m_OnClick = serializedObject.FindProperty("m_OnClick"); m_ChildTransitions = serializedObject.FindProperty("m_ChildTransitions"); hoverAudioClip = serializedObject.FindProperty("hoverAudioClip"); clickAudioClip = serializedObject.FindProperty("clickAudioClip"); CreateChildTransitionList(); // 不再 Register Image(基类已经创建),改为 Append 到 base 的 Image 页签 AppendToTab("Image", DrawImageHead, false); AppendToTab("Image", DrawImageBottom); // 另外注册独立的 Sound / Event 页签 RegisterTab("Sound", "d_AudioSource Icon", DrawSoundTab); RegisterTab("Event", "EventTrigger Icon", DrawEventTab); } protected override void OnDisable() { // 移除之前追加到 Image 页签的回调,避免残留 RemoveCallbackFromTab("Image", DrawImageHead); UnregisterTab("Sound"); UnregisterTab("Event"); base.OnDisable(); } private void CreateChildTransitionList() { m_ChildTransitionList = new ReorderableList(serializedObject, m_ChildTransitions, true, false, true, true); m_ChildTransitionList.drawElementBackgroundCallback = (rect, index, isActive, isFocused) => { var background = index % 2 == 0 ? darkZebraEven : darkZebraOdd; EditorGUI.DrawRect(rect, background); }; m_ChildTransitionList.drawElementCallback = (rect, index, isActive, isFocused) => { var element = m_ChildTransitionList.serializedProperty.GetArrayElementAtIndex(index); rect.y += 2; string elementTitle = $"Null Transition"; var targetProp = element.FindPropertyRelative("targetGraphic"); if (targetProp.objectReferenceValue != null) elementTitle = targetProp.objectReferenceValue.name; EditorGUI.LabelField(new Rect(rect.x, rect.y, rect.width, EditorGUIUtility.singleLineHeight), elementTitle, EditorStyles.boldLabel); rect.y += EditorGUIUtility.singleLineHeight + 2; DrawTransitionData(new Rect(rect.x, rect.y, rect.width, 0), element, true); }; m_ChildTransitionList.elementHeightCallback = (index) => { return EditorGUIUtility.singleLineHeight + CalculateTransitionDataHeight(m_ChildTransitionList.serializedProperty.GetArrayElementAtIndex(index)) + 10; }; m_ChildTransitionList.onAddCallback = (list) => { list.serializedProperty.arraySize++; serializedObject.ApplyModifiedProperties(); }; m_ChildTransitionList.onRemoveCallback = (list) => { ReorderableList.defaultBehaviours.DoRemoveButton(list); }; } // 追加到 base Image 页签的内容 private void DrawImageHead() { EditorGUI.BeginDisabledGroup(EditorApplication.isPlaying); var modeType = (ButtonModeType)EditorGUILayout.EnumPopup("Mode", (ButtonModeType)m_Mode.enumValueIndex); if (modeType != (ButtonModeType)m_Mode.enumValueIndex) { if (modeType == ButtonModeType.Normal) { ResetEventProperty(m_OnValueChanged); m_UXGroup.objectReferenceValue = null; } else { ResetEventProperty(m_OnClick); } m_Mode.enumValueIndex = (int)modeType; } EditorGUI.EndDisabledGroup(); } private void DrawImageBottom() { GUILayout.Space(5); DrawChildTransitions(); GUILayout.Space(1); DrawBasicSettings(); } private void DrawSoundTab() { GUILayoutHelper.DrawProperty(hoverAudioClip, customSkin, "Hover Sound", "Play", () => { if (hoverAudioClip.objectReferenceValue != null) { PlayAudio((AudioClip)hoverAudioClip.objectReferenceValue); } }); GUILayoutHelper.DrawProperty(clickAudioClip, customSkin, "Click Sound", "Play", () => { if (clickAudioClip.objectReferenceValue != null) { PlayAudio((AudioClip)clickAudioClip.objectReferenceValue); } }); } protected virtual void DrawEventTab() { if (m_Mode.enumValueIndex == (int)ButtonModeType.Toggle) { EditorGUILayout.Space(); EditorGUILayout.PropertyField(m_OnValueChanged); } else { EditorGUILayout.Space(); EditorGUILayout.PropertyField(m_OnClick); } EditorGUILayout.Separator(); } // helpers kept from original file private void ResetEventProperty(SerializedProperty property) { SerializedProperty persistentCalls = property.FindPropertyRelative("m_PersistentCalls"); SerializedProperty calls = persistentCalls.FindPropertyRelative("m_Calls"); calls.arraySize = 0; property.serializedObject.ApplyModifiedProperties(); } private void PlayAudio(AudioClip clip) { if (clip != null) { ExtensionHelper.PreviewAudioClip(clip); } } private void DrawBasicSettings() { if (m_Mode.enumValueIndex == (int)ButtonModeType.Toggle) { GUILayoutHelper.DrawProperty(m_UXGroup, customSkin, "UXGroup", (oldValue, newValue) => { UXButton self = target as UXButton; if (oldValue != null) { oldValue.UnregisterButton(self); } if (newValue != null) { newValue.RegisterButton(self); } }); } } private void DrawChildTransitions() { m_ChildTransitionList.DoLayoutList(); } // CalculateTransitionDataHeight & DrawTransitionData remain the same as earlier to preserve child-only Animation restriction and warnings. private float CalculateTransitionDataHeight(SerializedProperty transitionData) { float height = 0; SerializedProperty transition = transitionData.FindPropertyRelative("transition"); var currentTransition = GetTransition(transition); bool isChild = m_ChildTransitions != null && !string.IsNullOrEmpty(m_ChildTransitions.propertyPath) && transitionData.propertyPath.StartsWith(m_ChildTransitions.propertyPath); if (isChild && currentTransition == Selectable.Transition.Animation) { currentTransition = Selectable.Transition.ColorTint; } height += EditorGUIUtility.singleLineHeight * 1.5f; height += EditorGUIUtility.singleLineHeight; SerializedProperty targetGraphic = transitionData.FindPropertyRelative("targetGraphic"); var graphic = targetGraphic.objectReferenceValue as Graphic; var animator = graphic != null ? graphic.GetComponent() : null; switch (currentTransition) { case Selectable.Transition.ColorTint: if (graphic == null) height += EditorGUIUtility.singleLineHeight; break; case Selectable.Transition.SpriteSwap: if (!(graphic is Image)) height += EditorGUIUtility.singleLineHeight; break; case Selectable.Transition.Animation: if (animator == null) height += EditorGUIUtility.singleLineHeight; break; } switch (currentTransition) { case Selectable.Transition.ColorTint: height += EditorGUI.GetPropertyHeight(transitionData.FindPropertyRelative("colors")); break; case Selectable.Transition.SpriteSwap: height += EditorGUI.GetPropertyHeight(transitionData.FindPropertyRelative("spriteState")); break; case Selectable.Transition.Animation: height += EditorGUI.GetPropertyHeight(transitionData.FindPropertyRelative("animationTriggers")); break; } return height; } private void DrawTransitionData(Rect position, SerializedProperty transitionData, bool isChild = false) { SerializedProperty targetGraphic = transitionData.FindPropertyRelative("targetGraphic"); SerializedProperty transition = transitionData.FindPropertyRelative("transition"); SerializedProperty colorBlock = transitionData.FindPropertyRelative("colors"); SerializedProperty spriteState = transitionData.FindPropertyRelative("spriteState"); SerializedProperty animationTriggers = transitionData.FindPropertyRelative("animationTriggers"); EditorGUI.indentLevel++; float lineHeight = EditorGUIUtility.singleLineHeight; float spacing = 2f; float y = position.y; var currentTransition = GetTransition(transition); if (isChild && currentTransition == Selectable.Transition.Animation) { Rect warningRect = new Rect(position.x, y, position.width, lineHeight); EditorGUI.HelpBox(warningRect, "Animation 过渡仅允许用于 Main Transition,子 Transition 不支持 Animation(已显示为 ColorTint)", MessageType.Warning); y += lineHeight + spacing; currentTransition = Selectable.Transition.ColorTint; } switch (currentTransition) { case Selectable.Transition.ColorTint: case Selectable.Transition.SpriteSwap: Rect targetRect = new Rect(position.x, y, position.width, lineHeight); EditorGUI.PropertyField(targetRect, targetGraphic); y += lineHeight + spacing; break; } Rect transitionRect = new Rect(position.x, y, position.width, lineHeight); if (isChild) { string[] options = new[] { "None", "Color Tint", "Sprite Swap" }; int[] values = new[] { (int)Selectable.Transition.None, (int)Selectable.Transition.ColorTint, (int)Selectable.Transition.SpriteSwap }; int curVal = transition.enumValueIndex; int selIdx = Array.IndexOf(values, curVal); if (selIdx < 0) selIdx = 0; selIdx = EditorGUI.Popup(transitionRect, "Transition", selIdx, options); transition.enumValueIndex = values[selIdx]; currentTransition = GetTransition(transition); y += lineHeight + spacing; } else { EditorGUI.PropertyField(transitionRect, transition); y += lineHeight + spacing; } var graphic = targetGraphic.objectReferenceValue as Graphic; var animator = graphic != null ? graphic.GetComponent() : null; switch (currentTransition) { case Selectable.Transition.ColorTint: if (graphic == null) { Rect warningRect = new Rect(position.x, y, position.width, lineHeight); EditorGUI.HelpBox(warningRect, "需要Graphic组件来使用颜色过渡", MessageType.Warning); y += lineHeight + spacing; } break; case Selectable.Transition.SpriteSwap: if (!(graphic is Image)) { Rect warningRect = new Rect(position.x, y, position.width, lineHeight); EditorGUI.HelpBox(warningRect, "需要Image组件来使用精灵切换", MessageType.Warning); y += lineHeight + spacing; } break; case Selectable.Transition.Animation: if (animator == null) { Rect warningRect = new Rect(position.x, y, position.width, lineHeight); EditorGUI.HelpBox(warningRect, "需要Animator组件来使用动画切换", MessageType.Warning); y += lineHeight + spacing; } break; } switch (currentTransition) { case Selectable.Transition.ColorTint: CheckAndSetColorDefaults(colorBlock, targetGraphic); Rect colorRect = new Rect(position.x, y, position.width, EditorGUI.GetPropertyHeight(colorBlock)); EditorGUI.PropertyField(colorRect, colorBlock); break; case Selectable.Transition.SpriteSwap: CheckAndSetColorDefaults(colorBlock, targetGraphic); Rect spriteRect = new Rect(position.x, y, position.width, EditorGUI.GetPropertyHeight(spriteState)); EditorGUI.PropertyField(spriteRect, spriteState); break; case Selectable.Transition.Animation: Rect animRect = new Rect(position.x, y, position.width, EditorGUI.GetPropertyHeight(animationTriggers)); EditorGUI.PropertyField(animRect, animationTriggers); break; } if (graphic != null && currentTransition != (Selectable.Transition)transition.enumValueIndex && ((Selectable.Transition)transition.enumValueIndex == Selectable.Transition.Animation || (Selectable.Transition)transition.enumValueIndex == Selectable.Transition.None)) { graphic.canvasRenderer.SetColor(Color.white); } EditorGUI.indentLevel--; } }