2025-12-09 15:08:41 +08:00
|
|
|
|
using System;
|
|
|
|
|
|
using System.Collections.Generic;
|
|
|
|
|
|
using System.Linq;
|
|
|
|
|
|
using AlicizaX.UI.Extension;
|
|
|
|
|
|
using UnityEditor;
|
|
|
|
|
|
using UnityEditor.SceneManagement;
|
|
|
|
|
|
using UnityEngine;
|
|
|
|
|
|
using UnityEngine.UI;
|
|
|
|
|
|
using AnimatorControllerParameterType = UnityEngine.AnimatorControllerParameterType;
|
|
|
|
|
|
|
|
|
|
|
|
namespace UnityEngine.UI
|
|
|
|
|
|
{
|
|
|
|
|
|
[CanEditMultipleObjects]
|
|
|
|
|
|
[CustomEditor(typeof(UXSelectable), true)]
|
|
|
|
|
|
internal class UXSelectableEditor : Editor
|
|
|
|
|
|
{
|
|
|
|
|
|
private static bool s_ShowNavigation = false;
|
|
|
|
|
|
public static string s_ShowNavigationKey = "SelectableEditor.ShowNavigation";
|
|
|
|
|
|
GUIContent m_VisualizeNavigation = EditorGUIUtility.TrTextContent("Visualize", "Show navigation flows between selectable UI elements.");
|
|
|
|
|
|
private static List<UXSelectableEditor> s_Editors = new List<UXSelectableEditor>();
|
|
|
|
|
|
|
|
|
|
|
|
// 序列化属性(基类需要)
|
|
|
|
|
|
protected SerializedProperty m_Navigation;
|
|
|
|
|
|
protected SerializedProperty m_MainTransition;
|
|
|
|
|
|
protected SerializedProperty m_Interactable;
|
2025-12-15 13:42:08 +08:00
|
|
|
|
protected SerializedProperty m_animator;
|
2025-12-09 15:08:41 +08:00
|
|
|
|
|
|
|
|
|
|
// 抽象皮肤(现在放在基类)
|
|
|
|
|
|
protected GUISkin customSkin;
|
|
|
|
|
|
|
|
|
|
|
|
// Tab 管理结构
|
|
|
|
|
|
protected class EditorTab
|
|
|
|
|
|
{
|
|
|
|
|
|
public string title;
|
|
|
|
|
|
public string iconName;
|
|
|
|
|
|
public List<Action> callbacks = new List<Action>();
|
|
|
|
|
|
|
|
|
|
|
|
public EditorTab(string t, string icon)
|
|
|
|
|
|
{
|
|
|
|
|
|
title = t;
|
|
|
|
|
|
iconName = icon;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private List<EditorTab> _tabs = new List<EditorTab>();
|
|
|
|
|
|
private int _currentTabIndex = 0;
|
|
|
|
|
|
|
|
|
|
|
|
protected virtual void OnEnable()
|
|
|
|
|
|
{
|
|
|
|
|
|
s_ShowNavigation = EditorPrefs.GetBool(s_ShowNavigationKey);
|
|
|
|
|
|
s_Editors.Add(this);
|
|
|
|
|
|
RegisterStaticOnSceneGUI();
|
|
|
|
|
|
|
|
|
|
|
|
m_Navigation = serializedObject.FindProperty("m_Navigation");
|
|
|
|
|
|
m_MainTransition = serializedObject.FindProperty("m_MainTransition");
|
|
|
|
|
|
m_Interactable = serializedObject.FindProperty("m_Interactable");
|
2025-12-15 13:42:08 +08:00
|
|
|
|
m_animator = serializedObject.FindProperty("_animator");
|
2025-12-09 15:08:41 +08:00
|
|
|
|
// load customSkin once in base; 调整路径为你项目中 GUISkin 的实际路径(必要时修改)
|
|
|
|
|
|
customSkin = AssetDatabase.LoadAssetAtPath<GUISkin>("Packages/com.alicizax.unity.ui.extension/Editor/Res/GUISkin/UIExtensionGUISkin.guiskin");
|
|
|
|
|
|
|
|
|
|
|
|
// Ensure default Image tab exists and contains DrawSelectableInspector
|
|
|
|
|
|
EnsureDefaultImageTab();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
protected virtual void OnDisable()
|
|
|
|
|
|
{
|
|
|
|
|
|
s_Editors.Remove(this);
|
|
|
|
|
|
RegisterStaticOnSceneGUI();
|
|
|
|
|
|
_tabs.Clear();
|
|
|
|
|
|
_currentTabIndex = 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
#region Tab API (Register / Append / Remove / Unregister)
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 注册一个新 tab(如果已存在同名 tab,则替换其 icon 和清空回调,再加入 drawCallback)
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
protected void RegisterTab(string title, string iconName, Action drawCallback)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (string.IsNullOrEmpty(title)) return;
|
|
|
|
|
|
var tab = _tabs.Find(t => t.title == title);
|
|
|
|
|
|
if (tab == null)
|
|
|
|
|
|
{
|
|
|
|
|
|
tab = new EditorTab(title, iconName);
|
|
|
|
|
|
_tabs.Add(tab);
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
tab.iconName = iconName;
|
|
|
|
|
|
tab.callbacks.Clear();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (drawCallback != null)
|
|
|
|
|
|
tab.callbacks.Add(drawCallback);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 在已存在 tab 后追加一个 callback;若 tab 不存在则创建并追加。
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
protected void AppendToTab(string title, Action drawCallback, bool last = true)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (string.IsNullOrEmpty(title) || drawCallback == null) return;
|
|
|
|
|
|
var tab = _tabs.Find(t => t.title == title);
|
|
|
|
|
|
if (tab == null)
|
|
|
|
|
|
{
|
|
|
|
|
|
tab = new EditorTab(title, "d_DefaultAsset Icon");
|
|
|
|
|
|
_tabs.Add(tab);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 避免重复追加(简单判断)
|
|
|
|
|
|
if (!tab.callbacks.Contains(drawCallback))
|
|
|
|
|
|
{
|
|
|
|
|
|
if (last)
|
|
|
|
|
|
{
|
|
|
|
|
|
tab.callbacks.Add(drawCallback);
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
tab.callbacks.Insert(0, drawCallback);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 从指定 tab 中移除某个 callback(子类在 OnDisable 应该调用以清理)
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
protected void RemoveCallbackFromTab(string title, Action drawCallback)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (string.IsNullOrEmpty(title) || drawCallback == null) return;
|
|
|
|
|
|
var tab = _tabs.Find(t => t.title == title);
|
|
|
|
|
|
if (tab == null) return;
|
|
|
|
|
|
tab.callbacks.RemoveAll(cb => cb == drawCallback);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 卸载整个 tab(移除该 title 的所有 callbacks 与 tab 本身)。
|
|
|
|
|
|
/// 如果当前选中的索引超出范围,会自动修正为合法值。
|
|
|
|
|
|
/// 注意:基类默认创建的 "Image" 页签通常不应被移除,若需要保护可以在此加判断。
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
protected void UnregisterTab(string title)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (string.IsNullOrEmpty(title)) return;
|
|
|
|
|
|
_tabs.RemoveAll(t => t.title == title);
|
|
|
|
|
|
// 修正当前索引,避免越界
|
|
|
|
|
|
if (_tabs.Count == 0)
|
|
|
|
|
|
{
|
|
|
|
|
|
_currentTabIndex = 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
else if (_currentTabIndex >= _tabs.Count)
|
|
|
|
|
|
{
|
|
|
|
|
|
_currentTabIndex = Mathf.Max(0, _tabs.Count - 1);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private void EnsureDefaultImageTab()
|
|
|
|
|
|
{
|
|
|
|
|
|
// 如果还没有 Image tab,创建并把 DrawSelectableInspector 放到第一个 callback
|
|
|
|
|
|
var tab = _tabs.Find(t => t.title == "Image");
|
|
|
|
|
|
if (tab == null)
|
|
|
|
|
|
{
|
|
|
|
|
|
tab = new EditorTab("Image", "d_Texture Icon");
|
|
|
|
|
|
_tabs.Insert(0, tab);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 确保基类绘制在 Image 页签的第一个 callback (避免重复)
|
|
|
|
|
|
if (!tab.callbacks.Contains(DrawSelectableInspector))
|
|
|
|
|
|
tab.callbacks.Insert(0, DrawSelectableInspector);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
|
|
|
|
protected void DrawToggleShowNavigation()
|
|
|
|
|
|
{
|
|
|
|
|
|
Rect toggleRect = EditorGUILayout.GetControlRect();
|
|
|
|
|
|
toggleRect.xMin += EditorGUIUtility.labelWidth;
|
|
|
|
|
|
EditorGUI.BeginChangeCheck();
|
|
|
|
|
|
s_ShowNavigation = GUI.Toggle(toggleRect, s_ShowNavigation, m_VisualizeNavigation, EditorStyles.miniButton);
|
|
|
|
|
|
if (EditorGUI.EndChangeCheck())
|
|
|
|
|
|
{
|
|
|
|
|
|
EditorPrefs.SetBool(s_ShowNavigationKey, s_ShowNavigation);
|
|
|
|
|
|
SceneView.RepaintAll();
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
#region Scene GUI visualization (unchanged)
|
|
|
|
|
|
|
|
|
|
|
|
private void RegisterStaticOnSceneGUI()
|
|
|
|
|
|
{
|
|
|
|
|
|
SceneView.duringSceneGui -= StaticOnSceneGUI;
|
|
|
|
|
|
if (s_Editors.Count > 0)
|
|
|
|
|
|
SceneView.duringSceneGui += StaticOnSceneGUI;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private static void StaticOnSceneGUI(SceneView view)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (!s_ShowNavigation)
|
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
|
|
UXSelectable[] selectables = UXSelectable.allSelectablesArray;
|
|
|
|
|
|
|
|
|
|
|
|
for (int i = 0; i < selectables.Length; i++)
|
|
|
|
|
|
{
|
|
|
|
|
|
UXSelectable s = selectables[i];
|
|
|
|
|
|
if (StageUtility.IsGameObjectRenderedByCamera(s.gameObject, Camera.current))
|
|
|
|
|
|
DrawNavigationForSelectable(s);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private static void DrawNavigationForSelectable(UXSelectable sel)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (sel == null)
|
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
|
|
Transform transform = sel.transform;
|
|
|
|
|
|
bool active = Selection.transforms.Any(e => e == transform);
|
|
|
|
|
|
|
|
|
|
|
|
Handles.color = new Color(1.0f, 0.6f, 0.2f, active ? 1.0f : 0.4f);
|
|
|
|
|
|
DrawNavigationArrow(-Vector2.right, sel, sel.FindSelectableOnLeft());
|
|
|
|
|
|
DrawNavigationArrow(Vector2.up, sel, sel.FindSelectableOnUp());
|
|
|
|
|
|
|
|
|
|
|
|
Handles.color = new Color(1.0f, 0.9f, 0.1f, active ? 1.0f : 0.4f);
|
|
|
|
|
|
DrawNavigationArrow(Vector2.right, sel, sel.FindSelectableOnRight());
|
|
|
|
|
|
DrawNavigationArrow(-Vector2.up, sel, sel.FindSelectableOnDown());
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const float kArrowThickness = 2.5f;
|
|
|
|
|
|
const float kArrowHeadSize = 1.2f;
|
|
|
|
|
|
|
|
|
|
|
|
private static void DrawNavigationArrow(Vector2 direction, UXSelectable fromObj, UXSelectable toObj)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (fromObj == null || toObj == null)
|
|
|
|
|
|
return;
|
|
|
|
|
|
Transform fromTransform = fromObj.transform;
|
|
|
|
|
|
Transform toTransform = toObj.transform;
|
|
|
|
|
|
|
|
|
|
|
|
Vector2 sideDir = new Vector2(direction.y, -direction.x);
|
|
|
|
|
|
Vector3 fromPoint = fromTransform.TransformPoint(GetPointOnRectEdge(fromTransform as RectTransform, direction));
|
|
|
|
|
|
Vector3 toPoint = toTransform.TransformPoint(GetPointOnRectEdge(toTransform as RectTransform, -direction));
|
|
|
|
|
|
float fromSize = HandleUtility.GetHandleSize(fromPoint) * 0.05f;
|
|
|
|
|
|
float toSize = HandleUtility.GetHandleSize(toPoint) * 0.05f;
|
|
|
|
|
|
fromPoint += fromTransform.TransformDirection(sideDir) * fromSize;
|
|
|
|
|
|
toPoint += toTransform.TransformDirection(sideDir) * toSize;
|
|
|
|
|
|
float length = Vector3.Distance(fromPoint, toPoint);
|
|
|
|
|
|
Vector3 fromTangent = fromTransform.rotation * direction * length * 0.3f;
|
|
|
|
|
|
Vector3 toTangent = toTransform.rotation * -direction * length * 0.3f;
|
|
|
|
|
|
|
|
|
|
|
|
Handles.DrawBezier(fromPoint, toPoint, fromPoint + fromTangent, toPoint + toTangent, Handles.color, null, kArrowThickness);
|
|
|
|
|
|
Handles.DrawAAPolyLine(kArrowThickness, toPoint, toPoint + toTransform.rotation * (-direction - sideDir) * toSize * kArrowHeadSize);
|
|
|
|
|
|
Handles.DrawAAPolyLine(kArrowThickness, toPoint, toPoint + toTransform.rotation * (-direction + sideDir) * toSize * kArrowHeadSize);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private static Vector3 GetPointOnRectEdge(RectTransform rect, Vector2 dir)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (rect == null)
|
|
|
|
|
|
return Vector3.zero;
|
|
|
|
|
|
if (dir != Vector2.zero)
|
|
|
|
|
|
dir /= Mathf.Max(Mathf.Abs(dir.x), Mathf.Abs(dir.y));
|
|
|
|
|
|
dir = rect.rect.center + Vector2.Scale(rect.rect.size, dir * 0.5f);
|
|
|
|
|
|
return dir;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 子类调用:绘制基类的通用 Inspector(Navigation / Interactable / Main Transition)
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
protected void DrawSelectableInspector()
|
|
|
|
|
|
{
|
|
|
|
|
|
if (m_Navigation != null)
|
|
|
|
|
|
EditorGUILayout.PropertyField(m_Navigation.FindPropertyRelative("m_Mode"), new GUIContent("Navigation"));
|
|
|
|
|
|
|
|
|
|
|
|
DrawToggleShowNavigation();
|
|
|
|
|
|
|
|
|
|
|
|
if (customSkin != null)
|
|
|
|
|
|
{
|
|
|
|
|
|
var interactable = GUILayoutHelper.DrawToggle(m_Interactable.boolValue, customSkin, "Interactable");
|
|
|
|
|
|
if (interactable != m_Interactable.boolValue)
|
|
|
|
|
|
{
|
|
|
|
|
|
(target as UXSelectable).Interactable = interactable;
|
|
|
|
|
|
m_Interactable.boolValue = interactable;
|
|
|
|
|
|
// keep selection state consistent
|
|
|
|
|
|
var selProp = serializedObject.FindProperty("m_SelectionState");
|
|
|
|
|
|
if (selProp != null)
|
|
|
|
|
|
{
|
|
|
|
|
|
selProp.enumValueIndex = interactable ? (int)UXSelectable.SelectionState.Normal : (int)UXSelectable.SelectionState.Disabled;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
GUILayout.Space(1);
|
|
|
|
|
|
serializedObject.ApplyModifiedProperties();
|
|
|
|
|
|
DrawSelfTransition();
|
|
|
|
|
|
GUILayout.Space(5);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
#region MainTransition helpers (moved to base)
|
|
|
|
|
|
|
|
|
|
|
|
protected virtual void DrawSelfTransition()
|
|
|
|
|
|
{
|
|
|
|
|
|
if (m_MainTransition == null)
|
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
|
|
GUILayout.BeginVertical(EditorStyles.helpBox);
|
|
|
|
|
|
EditorGUILayout.LabelField("Main Transition", EditorStyles.boldLabel);
|
|
|
|
|
|
SerializedProperty targetGraphic = m_MainTransition.FindPropertyRelative("targetGraphic");
|
|
|
|
|
|
var graphic = targetGraphic.objectReferenceValue as Graphic;
|
|
|
|
|
|
if (graphic == null)
|
|
|
|
|
|
{
|
|
|
|
|
|
graphic = (target as UXSelectable).GetComponent<Graphic>();
|
|
|
|
|
|
if (targetGraphic != null)
|
|
|
|
|
|
targetGraphic.objectReferenceValue = graphic;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
SerializedProperty transition = m_MainTransition.FindPropertyRelative("transition");
|
|
|
|
|
|
var currentTransition = GetTransition(transition);
|
|
|
|
|
|
|
|
|
|
|
|
switch (currentTransition)
|
|
|
|
|
|
{
|
|
|
|
|
|
case Selectable.Transition.ColorTint:
|
|
|
|
|
|
case Selectable.Transition.SpriteSwap:
|
|
|
|
|
|
EditorGUILayout.PropertyField(targetGraphic);
|
|
|
|
|
|
break;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
EditorGUILayout.PropertyField(transition);
|
|
|
|
|
|
|
2025-12-15 13:42:08 +08:00
|
|
|
|
Animator animator = null;
|
|
|
|
|
|
if (target is UXSelectable anima)
|
|
|
|
|
|
{
|
|
|
|
|
|
animator = anima.GetComponent<Animator>();
|
|
|
|
|
|
}
|
|
|
|
|
|
else if (m_animator.objectReferenceValue != null)
|
|
|
|
|
|
{
|
|
|
|
|
|
animator = (Animator)m_animator.objectReferenceValue;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-09 15:08:41 +08:00
|
|
|
|
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("需要Animator组件来使用动画切换", MessageType.Warning);
|
|
|
|
|
|
break;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
switch (currentTransition)
|
|
|
|
|
|
{
|
|
|
|
|
|
case Selectable.Transition.ColorTint:
|
|
|
|
|
|
CheckAndSetColorDefaults(m_MainTransition.FindPropertyRelative("colors"), targetGraphic);
|
|
|
|
|
|
EditorGUILayout.PropertyField(m_MainTransition.FindPropertyRelative("colors"));
|
|
|
|
|
|
break;
|
|
|
|
|
|
case Selectable.Transition.SpriteSwap:
|
|
|
|
|
|
EditorGUILayout.PropertyField(m_MainTransition.FindPropertyRelative("spriteState"));
|
|
|
|
|
|
break;
|
|
|
|
|
|
case Selectable.Transition.Animation:
|
|
|
|
|
|
EditorGUILayout.PropertyField(m_MainTransition.FindPropertyRelative("animationTriggers"));
|
|
|
|
|
|
if (animator == null || animator.runtimeAnimatorController == null)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (GUILayout.Button("Auto Generate Animation"))
|
|
|
|
|
|
{
|
|
|
|
|
|
var animationTrigger = m_MainTransition.FindPropertyRelative("animationTriggers");
|
|
|
|
|
|
var controller = GenerateSelectableAnimatorContoller(animationTrigger, target);
|
|
|
|
|
|
if (controller != null)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (animator == null)
|
|
|
|
|
|
animator = (target as UXSelectable).gameObject.AddComponent<Animator>();
|
|
|
|
|
|
|
|
|
|
|
|
UnityEditor.Animations.AnimatorController.SetAnimatorController(animator, controller);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
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);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
EditorGUILayout.Space();
|
|
|
|
|
|
GUILayout.EndVertical();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
protected void CheckAndSetColorDefaults(SerializedProperty colorBlock, SerializedProperty targetGraphic)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (colorBlock == null) return;
|
|
|
|
|
|
|
|
|
|
|
|
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);
|
|
|
|
|
|
if (prop == null) continue;
|
|
|
|
|
|
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)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (m_ColorMultiplier != null) m_ColorMultiplier.floatValue = 1f;
|
|
|
|
|
|
if (fadeDuration != null) fadeDuration.floatValue = 0.1f;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
var graphic = targetGraphic != null ? targetGraphic.objectReferenceValue as Graphic : null;
|
|
|
|
|
|
if (graphic != null)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (!EditorApplication.isPlaying)
|
|
|
|
|
|
{
|
|
|
|
|
|
SerializedProperty normalColorProp = colorBlock.FindPropertyRelative("m_NormalColor");
|
|
|
|
|
|
if (normalColorProp != null)
|
|
|
|
|
|
{
|
|
|
|
|
|
Color color = normalColorProp.colorValue;
|
|
|
|
|
|
graphic.canvasRenderer.SetColor(color);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
protected static UnityEditor.Animations.AnimatorController GenerateSelectableAnimatorContoller(SerializedProperty property, UnityEngine.Object targetObj)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (targetObj == null || property == null)
|
|
|
|
|
|
return null;
|
|
|
|
|
|
|
|
|
|
|
|
var targetAsGO = (targetObj as UXSelectable);
|
|
|
|
|
|
var path = GetSaveControllerPath(targetAsGO);
|
|
|
|
|
|
if (string.IsNullOrEmpty(path))
|
|
|
|
|
|
return null;
|
|
|
|
|
|
SerializedProperty normalTrigger = property.FindPropertyRelative("m_NormalTrigger");
|
|
|
|
|
|
SerializedProperty highlightedTrigger = property.FindPropertyRelative("m_HighlightedTrigger");
|
|
|
|
|
|
SerializedProperty pressedTrigger = property.FindPropertyRelative("m_PressedTrigger");
|
|
|
|
|
|
SerializedProperty selectedTrigger = property.FindPropertyRelative("m_SelectedTrigger");
|
|
|
|
|
|
SerializedProperty disabledTrigger = property.FindPropertyRelative("m_DisabledTrigger");
|
|
|
|
|
|
|
|
|
|
|
|
var normalName = string.IsNullOrEmpty(normalTrigger.stringValue) ? "Normal" : normalTrigger.stringValue;
|
|
|
|
|
|
var highlightedName = string.IsNullOrEmpty(highlightedTrigger.stringValue) ? "Highlighted" : highlightedTrigger.stringValue;
|
|
|
|
|
|
var pressedName = string.IsNullOrEmpty(pressedTrigger.stringValue) ? "Pressed" : pressedTrigger.stringValue;
|
|
|
|
|
|
var selectedName = string.IsNullOrEmpty(selectedTrigger.stringValue) ? "Selected" : selectedTrigger.stringValue;
|
|
|
|
|
|
var disabledName = string.IsNullOrEmpty(disabledTrigger.stringValue) ? "Disabled" : disabledTrigger.stringValue;
|
|
|
|
|
|
|
|
|
|
|
|
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(UXSelectable target)
|
|
|
|
|
|
{
|
|
|
|
|
|
var defaultName = target != null ? target.gameObject.name : "NewController";
|
|
|
|
|
|
var message = string.Format("Create a new animator for the game object '{0}':", defaultName);
|
|
|
|
|
|
return EditorUtility.SaveFilePanelInProject("New Animation Contoller", defaultName, "controller", message);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
protected static Selectable.Transition GetTransition(SerializedProperty transition)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (transition == null) return Selectable.Transition.None;
|
|
|
|
|
|
return (Selectable.Transition)transition.enumValueIndex;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
|
|
|
|
#region Tab drawing entry
|
|
|
|
|
|
|
|
|
|
|
|
public override void OnInspectorGUI()
|
|
|
|
|
|
{
|
|
|
|
|
|
serializedObject.Update();
|
|
|
|
|
|
DrawTabs();
|
|
|
|
|
|
serializedObject.ApplyModifiedProperties();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
protected void DrawTabs()
|
|
|
|
|
|
{
|
|
|
|
|
|
if (_tabs == null || _tabs.Count == 0)
|
|
|
|
|
|
{
|
|
|
|
|
|
// fallback: draw base directly
|
|
|
|
|
|
DrawSelectableInspector();
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
EditorGUILayout.BeginHorizontal();
|
|
|
|
|
|
|
|
|
|
|
|
for (int i = 0; i < _tabs.Count; i++)
|
|
|
|
|
|
{
|
|
|
|
|
|
var tab = _tabs[i];
|
|
|
|
|
|
bool isActive = (i == _currentTabIndex);
|
|
|
|
|
|
var style = new GUIStyle(EditorStyles.toolbarButton) { fixedHeight = 25, fontSize = 11, fontStyle = isActive ? FontStyle.Bold : FontStyle.Normal };
|
|
|
|
|
|
|
|
|
|
|
|
var content = new GUIContent(EditorGUIUtility.IconContent(tab.iconName).image, tab.title);
|
|
|
|
|
|
if (GUILayout.Button(content, style))
|
|
|
|
|
|
{
|
|
|
|
|
|
_currentTabIndex = i;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
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));
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
EditorGUILayout.EndHorizontal();
|
|
|
|
|
|
GUILayout.Space(4);
|
|
|
|
|
|
|
|
|
|
|
|
// draw current tab content (call all callbacks registered for the tab)
|
|
|
|
|
|
var callbacks = _tabs[_currentTabIndex].callbacks;
|
|
|
|
|
|
if (callbacks != null)
|
|
|
|
|
|
{
|
|
|
|
|
|
foreach (var cb in callbacks)
|
|
|
|
|
|
{
|
|
|
|
|
|
try
|
|
|
|
|
|
{
|
|
|
|
|
|
cb?.Invoke();
|
|
|
|
|
|
}
|
|
|
|
|
|
catch (UnityEngine.ExitGUIException)
|
|
|
|
|
|
{
|
|
|
|
|
|
// Unity 用 ExitGUIException 来中断 GUI 流程 —— 需要重新抛出,让 Unity 处理
|
|
|
|
|
|
throw;
|
|
|
|
|
|
}
|
|
|
|
|
|
catch (Exception e)
|
|
|
|
|
|
{
|
|
|
|
|
|
// 仅记录真正的异常
|
|
|
|
|
|
Debug.LogException(e);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|