com.alicizax.unity.ui.exten.../Editor/UX/UXButtonEditor.cs
2025-07-29 11:19:07 +08:00

588 lines
22 KiB
C#

using System;
using System.Collections.Generic;
using AlicizaX.UI.Extension.Editor;
using UnityEditor;
using UnityEditorInternal;
using UnityEditor.UI;
using UnityEngine;
using UnityEngine.UI;
using AnimatorControllerParameterType = UnityEngine.AnimatorControllerParameterType;
[CanEditMultipleObjects]
[CustomEditor(typeof(UXButton), true)]
internal class UXButtonEditor : Editor
{
private enum TabType
{
Image,
Sound,
Event
}
private SerializedProperty m_Interactable;
private SerializedProperty m_Mode;
private SerializedProperty m_OnValueChanged;
private SerializedProperty m_OnClick;
private SerializedProperty m_UXGroup;
private SerializedProperty m_TransitionData;
private SerializedProperty m_ChildTransitions;
private UXGroup group;
private int m_ButtonMode;
private SerializedProperty m_SelectionState;
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 TabType currentTab = TabType.Image;
private GUISkin customSkin;
private SerializedProperty hoverAudioClip;
private SerializedProperty clickAudioClip;
private void OnEnable()
{
customSkin = AssetDatabase.LoadAssetAtPath<GUISkin>("Packages/com.alicizax.unity.ui.extension/Editor/Res/GUISkin/UIExtensionGUISkin.guiskin");
m_Interactable = serializedObject.FindProperty("m_Interactable");
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_TransitionData = serializedObject.FindProperty("m_TransitionData");
m_ChildTransitions = serializedObject.FindProperty("m_ChildTransitions");
m_SelectionState = serializedObject.FindProperty("m_SelectionState");
group = (UXGroup)m_UXGroup.objectReferenceValue;
m_ButtonMode = m_Mode.enumValueIndex;
hoverAudioClip = serializedObject.FindProperty("hoverAudioClip");
clickAudioClip = serializedObject.FindProperty("clickAudioClip");
CreateChildTransitionList();
}
private void CreateChildTransitionList()
{
m_ChildTransitionList = new ReorderableList(serializedObject, m_ChildTransitions, true, false, true, true);
// m_ChildTransitionList.drawHeaderCallback = (rect) => { EditorGUI.LabelField(rect, "Other Transitions"); };
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); };
}
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 DrawTabButton(TabType tabType, string label, string iconName)
{
bool isActive = currentTab == tabType;
var style = new GUIStyle(EditorStyles.toolbarButton)
{
fixedHeight = 25,
fontSize = 11,
fontStyle = isActive ? FontStyle.Bold : FontStyle.Normal
};
var content = new GUIContent(
EditorGUIUtility.IconContent(iconName).image,
label
);
if (GUILayout.Button(content, style))
{
currentTab = tabType;
}
// 高亮当前选中的标签页
if (isActive)
{
Rect rect = GUILayoutUtility.GetLastRect();
EditorGUI.DrawRect(new Rect(rect.x, rect.yMax - 2, rect.width, 2),
new Color(0.1f, 0.5f, 0.9f));
}
}
public override void OnInspectorGUI()
{
serializedObject.Update();
EditorGUILayout.BeginHorizontal();
// 图像标签页按钮
DrawTabButton(TabType.Image, "Image", "d_Texture Icon");
// 声音标签页按钮
DrawTabButton(TabType.Sound, "Sound", "d_AudioSource Icon");
// 事件标签页按钮
DrawTabButton(TabType.Event, "Event", "EventTrigger Icon");
EditorGUILayout.EndHorizontal();
switch (currentTab)
{
case TabType.Image:
DrawGraphicsTab();
break;
case TabType.Sound:
DrawAudioTab();
break;
case TabType.Event:
DrawEventTab();
break;
}
serializedObject.ApplyModifiedProperties();
}
private void DrawGraphicsTab()
{
EditorGUI.BeginDisabledGroup(EditorApplication.isPlaying);
EditorGUILayout.PropertyField(m_Mode);
EditorGUI.EndDisabledGroup();
var interactable = GUILayoutHelper.DrawToggle(m_Interactable.boolValue, customSkin, "Interactable");
m_Interactable.boolValue = interactable;
GUILayout.Space(1);
DrawSelfTransition();
GUILayout.Space(5);
DrawChildTransitions();
GUILayout.Space(1);
DrawBasicSettings();
}
private void DrawEventTab()
{
if (m_Mode.enumValueIndex == (int)ButtonModeType.Toggle)
{
EditorGUILayout.Space();
EditorGUILayout.PropertyField(m_OnValueChanged);
}
else
{
EditorGUILayout.Space();
EditorGUILayout.PropertyField(m_OnClick);
}
}
private void DrawAudioTab()
{
GUILayoutHelper.DrawProperty(hoverAudioClip, customSkin, "Hover Sound", "Play", () =>
{
if (hoverAudioClip.objectReferenceValue != null)
{
PlayAudio((AudioClip)hoverAudioClip.objectReferenceValue);
}
});
GUILayoutHelper.DrawProperty(clickAudioClip, customSkin, "Click Sound", "Play", () =>
{
if (hoverAudioClip.objectReferenceValue != null)
{
PlayAudio((AudioClip)hoverAudioClip.objectReferenceValue);
}
});
}
private void DrawBasicSettings()
{
if (m_Mode.enumValueIndex != m_ButtonMode)
{
if (m_ButtonMode == (int)ButtonModeType.Normal)
{
ResetEventProperty(m_OnValueChanged);
m_UXGroup.objectReferenceValue = null;
}
else
{
ResetEventProperty(m_OnClick);
}
m_ButtonMode = m_Mode.enumValueIndex;
}
if (m_Mode.enumValueIndex == (int)ButtonModeType.Toggle)
{
GUILayoutHelper.DrawProperty(m_UXGroup, customSkin, "UXGroup");
UXGroup newGroup = (UXGroup)m_UXGroup.objectReferenceValue;
if (newGroup != group)
{
UXButton self = target as UXButton;
if (group != null)
{
group.UnregisterButton(self);
}
group = newGroup;
if (newGroup != null)
{
newGroup.RegisterButton(self);
}
}
}
}
private void PlayAudio(AudioClip clip)
{
if (clip != null)
{
ExtensionHelper.PreviewAudioClip(clip);
}
}
private void DrawChildTransitions()
{
m_ChildTransitionList.DoLayoutList();
}
private float CalculateTransitionDataHeight(SerializedProperty transitionData)
{
float height = 0;
SerializedProperty transition = transitionData.FindPropertyRelative("transition");
var currentTransition = GetTransition(transition);
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<Animator>() : 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;
Rect targetRect = new Rect(position.x, y, position.width, lineHeight);
EditorGUI.PropertyField(targetRect, targetGraphic);
y += lineHeight + spacing;
var currentTransition = GetTransition(transition);
Rect transitionRect = new Rect(position.x, y, position.width, lineHeight);
EditorGUI.PropertyField(transitionRect, transition);
y += lineHeight + spacing;
var graphic = targetGraphic.objectReferenceValue as Graphic;
var animator = graphic != null ? graphic.GetComponent<Animator>() : 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, "需要Animation || 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:
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;
}
}
private void DrawSelfTransition()
{
GUILayout.BeginVertical(EditorStyles.helpBox);
EditorGUILayout.LabelField("Main Transition", EditorStyles.boldLabel);
SerializedProperty targetGraphic = m_TransitionData.FindPropertyRelative("targetGraphic");
var graphic = targetGraphic.objectReferenceValue as Graphic;
if (graphic == null)
{
graphic = (target as UXButton).GetComponent<Graphic>();
targetGraphic.objectReferenceValue = graphic;
}
EditorGUILayout.PropertyField(targetGraphic);
SerializedProperty transition = m_TransitionData.FindPropertyRelative("transition");
EditorGUILayout.PropertyField(transition);
var currentTransition = GetTransition(transition);
var animator = graphic != null ? graphic.GetComponent<Animator>() : null;
switch (currentTransition)
{
case Selectable.Transition.ColorTint:
if (graphic == null)
EditorGUILayout.HelpBox("需要Graphic组件来使用颜色过渡", MessageType.Warning);
break;
case Selectable.Transition.SpriteSwap:
if (!(graphic is Image))
EditorGUILayout.HelpBox("需要Image组件来使用精灵切换", MessageType.Warning);
break;
case Selectable.Transition.Animation:
if (animator == null)
EditorGUILayout.HelpBox("需要Animation || Animator组件来使用动画切换", MessageType.Warning);
break;
}
switch (currentTransition)
{
case Selectable.Transition.ColorTint:
CheckAndSetColorDefaults(m_TransitionData.FindPropertyRelative("colors"), targetGraphic);
EditorGUILayout.PropertyField(m_TransitionData.FindPropertyRelative("colors"));
break;
case Selectable.Transition.SpriteSwap:
EditorGUILayout.PropertyField(m_TransitionData.FindPropertyRelative("spriteState"));
break;
case Selectable.Transition.Animation:
EditorGUILayout.PropertyField(m_TransitionData.FindPropertyRelative("animationTriggers"));
if (animator == null || animator.runtimeAnimatorController == null)
{
if (GUILayout.Button("Auto Generate Animation"))
{
var controller = GenerateSelectableAnimatorContoller((target as Selectable).animationTriggers, target as Selectable);
if (controller != null)
{
if (animator == null)
animator = (target as Selectable).gameObject.AddComponent<Animator>();
UnityEditor.Animations.AnimatorController.SetAnimatorController(animator, controller);
}
}
}
break;
}
EditorGUI.indentLevel--;
EditorGUILayout.Space();
GUILayout.EndVertical();
}
private static UnityEditor.Animations.AnimatorController GenerateSelectableAnimatorContoller(AnimationTriggers animationTriggers, Selectable target)
{
if (target == null)
return null;
var path = GetSaveControllerPath(target);
if (string.IsNullOrEmpty(path))
return null;
var normalName = string.IsNullOrEmpty(animationTriggers.normalTrigger) ? "Normal" : animationTriggers.normalTrigger;
var highlightedName = string.IsNullOrEmpty(animationTriggers.highlightedTrigger) ? "Highlighted" : animationTriggers.highlightedTrigger;
var pressedName = string.IsNullOrEmpty(animationTriggers.pressedTrigger) ? "Pressed" : animationTriggers.pressedTrigger;
var selectedName = string.IsNullOrEmpty(animationTriggers.selectedTrigger) ? "Selected" : animationTriggers.selectedTrigger;
var disabledName = string.IsNullOrEmpty(animationTriggers.disabledTrigger) ? "Disabled" : animationTriggers.disabledTrigger;
var controller = UnityEditor.Animations.AnimatorController.CreateAnimatorControllerAtPath(path);
GenerateTriggerableTransition(normalName, controller);
GenerateTriggerableTransition(highlightedName, controller);
GenerateTriggerableTransition(pressedName, controller);
GenerateTriggerableTransition(selectedName, controller);
GenerateTriggerableTransition(disabledName, controller);
AssetDatabase.ImportAsset(path);
return controller;
}
private static AnimationClip GenerateTriggerableTransition(string name, UnityEditor.Animations.AnimatorController controller)
{
// Create the clip
var clip = UnityEditor.Animations.AnimatorController.AllocateAnimatorClip(name);
AssetDatabase.AddObjectToAsset(clip, controller);
// Create a state in the animatior controller for this clip
var state = controller.AddMotion(clip);
// Add a transition property
controller.AddParameter(name, AnimatorControllerParameterType.Trigger);
// Add an any state transition
var stateMachine = controller.layers[0].stateMachine;
var transition = stateMachine.AddAnyStateTransition(state);
transition.AddCondition(UnityEditor.Animations.AnimatorConditionMode.If, 0, name);
return clip;
}
private static string GetSaveControllerPath(Selectable target)
{
var defaultName = target.gameObject.name;
var message = string.Format("Create a new animator for the game object '{0}':", defaultName);
return EditorUtility.SaveFilePanelInProject("New Animation Contoller", defaultName, "controller", message);
}
void CheckAndSetColorDefaults(SerializedProperty colorBlock, SerializedProperty targetGraphic)
{
bool isDirty = false;
string[] colorProps = new string[] { "m_NormalColor", "m_HighlightedColor", "m_PressedColor", "m_SelectedColor", "m_DisabledColor" };
foreach (var propName in colorProps)
{
SerializedProperty prop = colorBlock.FindPropertyRelative(propName);
Color color = prop.colorValue;
if (color.r == 0 && color.g == 0 && color.b == 0 && color.a == 0)
{
isDirty = true;
if (prop.name == "m_PressedColor")
{
prop.colorValue = new Color(0.7843137f, 0.7843137f, 0.7843137f, 1.0f);
}
else if (prop.name == "m_DisabledColor")
{
prop.colorValue = new Color(0.7843137f, 0.7843137f, 0.7843137f, 0.5f);
}
else
{
prop.colorValue = Color.white;
}
}
}
SerializedProperty fadeDuration = colorBlock.FindPropertyRelative("m_FadeDuration");
SerializedProperty m_ColorMultiplier = colorBlock.FindPropertyRelative("m_ColorMultiplier");
if (isDirty)
{
m_ColorMultiplier.floatValue = 1f;
fadeDuration.floatValue = 0.1f;
}
var graphic = targetGraphic.objectReferenceValue as Graphic;
if (graphic != null)
{
if (!EditorApplication.isPlaying)
{
Color color = colorBlock.FindPropertyRelative("m_NormalColor").colorValue;
graphic.canvasRenderer.SetColor(color);
}
else if (m_SelectionState.enumValueIndex == 0)
{
Color color = colorBlock.FindPropertyRelative("m_NormalColor").colorValue;
graphic.canvasRenderer.SetColor(color);
}
}
}
static Selectable.Transition GetTransition(SerializedProperty transition)
{
return (Selectable.Transition)transition.enumValueIndex;
}
}