123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383 |
- using System.Collections.Generic;
- using System.Text;
- using System.Linq;
- using UnityEngine;
- using UnityEngine.UI;
- using UnityEditor.AnimatedValues;
-
- namespace UnityEditor.UI
- {
- [CustomEditor(typeof(Selectable), true)]
- /// <summary>
- /// Custom Editor for the Selectable Component.
- /// Extend this class to write a custom editor for a component derived from Selectable.
- /// </summary>
- public class SelectableEditor : Editor
- {
- SerializedProperty m_Script;
- SerializedProperty m_InteractableProperty;
- SerializedProperty m_TargetGraphicProperty;
- SerializedProperty m_TransitionProperty;
- SerializedProperty m_ColorBlockProperty;
- SerializedProperty m_SpriteStateProperty;
- SerializedProperty m_AnimTriggerProperty;
- SerializedProperty m_NavigationProperty;
-
- GUIContent m_VisualizeNavigation = EditorGUIUtility.TrTextContent("Visualize", "Show navigation flows between selectable UI elements.");
-
- AnimBool m_ShowColorTint = new AnimBool();
- AnimBool m_ShowSpriteTrasition = new AnimBool();
- AnimBool m_ShowAnimTransition = new AnimBool();
-
- private static List<SelectableEditor> s_Editors = new List<SelectableEditor>();
- private static bool s_ShowNavigation = false;
- private static string s_ShowNavigationKey = "SelectableEditor.ShowNavigation";
-
- // Whenever adding new SerializedProperties to the Selectable and SelectableEditor
- // Also update this guy in OnEnable. This makes the inherited classes from Selectable not require a CustomEditor.
- private string[] m_PropertyPathToExcludeForChildClasses;
-
- protected virtual void OnEnable()
- {
- m_Script = serializedObject.FindProperty("m_Script");
- m_InteractableProperty = serializedObject.FindProperty("m_Interactable");
- m_TargetGraphicProperty = serializedObject.FindProperty("m_TargetGraphic");
- m_TransitionProperty = serializedObject.FindProperty("m_Transition");
- m_ColorBlockProperty = serializedObject.FindProperty("m_Colors");
- m_SpriteStateProperty = serializedObject.FindProperty("m_SpriteState");
- m_AnimTriggerProperty = serializedObject.FindProperty("m_AnimationTriggers");
- m_NavigationProperty = serializedObject.FindProperty("m_Navigation");
-
- m_PropertyPathToExcludeForChildClasses = new[]
- {
- m_Script.propertyPath,
- m_NavigationProperty.propertyPath,
- m_TransitionProperty.propertyPath,
- m_ColorBlockProperty.propertyPath,
- m_SpriteStateProperty.propertyPath,
- m_AnimTriggerProperty.propertyPath,
- m_InteractableProperty.propertyPath,
- m_TargetGraphicProperty.propertyPath,
- };
-
- var trans = GetTransition(m_TransitionProperty);
- m_ShowColorTint.value = (trans == Selectable.Transition.ColorTint);
- m_ShowSpriteTrasition.value = (trans == Selectable.Transition.SpriteSwap);
- m_ShowAnimTransition.value = (trans == Selectable.Transition.Animation);
-
- m_ShowColorTint.valueChanged.AddListener(Repaint);
- m_ShowSpriteTrasition.valueChanged.AddListener(Repaint);
-
- s_Editors.Add(this);
- RegisterStaticOnSceneGUI();
-
- s_ShowNavigation = EditorPrefs.GetBool(s_ShowNavigationKey);
- }
-
- protected virtual void OnDisable()
- {
- m_ShowColorTint.valueChanged.RemoveListener(Repaint);
- m_ShowSpriteTrasition.valueChanged.RemoveListener(Repaint);
-
- s_Editors.Remove(this);
- RegisterStaticOnSceneGUI();
- }
-
- private void RegisterStaticOnSceneGUI()
- {
- SceneView.duringSceneGui -= StaticOnSceneGUI;
- if (s_Editors.Count > 0)
- SceneView.duringSceneGui += StaticOnSceneGUI;
- }
-
- static Selectable.Transition GetTransition(SerializedProperty transition)
- {
- return (Selectable.Transition)transition.enumValueIndex;
- }
-
- public override void OnInspectorGUI()
- {
- serializedObject.Update();
-
- EditorGUILayout.PropertyField(m_InteractableProperty);
-
- var trans = GetTransition(m_TransitionProperty);
-
- var graphic = m_TargetGraphicProperty.objectReferenceValue as Graphic;
- if (graphic == null)
- graphic = (target as Selectable).GetComponent<Graphic>();
-
- var animator = (target as Selectable).GetComponent<Animator>();
- m_ShowColorTint.target = (!m_TransitionProperty.hasMultipleDifferentValues && trans == Button.Transition.ColorTint);
- m_ShowSpriteTrasition.target = (!m_TransitionProperty.hasMultipleDifferentValues && trans == Button.Transition.SpriteSwap);
- m_ShowAnimTransition.target = (!m_TransitionProperty.hasMultipleDifferentValues && trans == Button.Transition.Animation);
-
- EditorGUILayout.PropertyField(m_TransitionProperty);
-
- ++EditorGUI.indentLevel;
- {
- if (trans == Selectable.Transition.ColorTint || trans == Selectable.Transition.SpriteSwap)
- {
- EditorGUILayout.PropertyField(m_TargetGraphicProperty);
- }
-
- switch (trans)
- {
- case Selectable.Transition.ColorTint:
- if (graphic == null)
- EditorGUILayout.HelpBox("You must have a Graphic target in order to use a color transition.", MessageType.Warning);
- break;
-
- case Selectable.Transition.SpriteSwap:
- if (graphic as Image == null)
- EditorGUILayout.HelpBox("You must have a Image target in order to use a sprite swap transition.", MessageType.Warning);
- break;
- }
-
- if (EditorGUILayout.BeginFadeGroup(m_ShowColorTint.faded))
- {
- EditorGUILayout.PropertyField(m_ColorBlockProperty);
- }
- EditorGUILayout.EndFadeGroup();
-
- if (EditorGUILayout.BeginFadeGroup(m_ShowSpriteTrasition.faded))
- {
- EditorGUILayout.PropertyField(m_SpriteStateProperty);
- }
- EditorGUILayout.EndFadeGroup();
-
- if (EditorGUILayout.BeginFadeGroup(m_ShowAnimTransition.faded))
- {
- EditorGUILayout.PropertyField(m_AnimTriggerProperty);
-
- if (animator == null || animator.runtimeAnimatorController == null)
- {
- Rect buttonRect = EditorGUILayout.GetControlRect();
- buttonRect.xMin += EditorGUIUtility.labelWidth;
- if (GUI.Button(buttonRect, "Auto Generate Animation", EditorStyles.miniButton))
- {
- var controller = GenerateSelectableAnimatorContoller((target as Selectable).animationTriggers, target as Selectable);
- if (controller != null)
- {
- if (animator == null)
- animator = (target as Selectable).gameObject.AddComponent<Animator>();
-
- Animations.AnimatorController.SetAnimatorController(animator, controller);
- }
- }
- }
- }
- EditorGUILayout.EndFadeGroup();
- }
- --EditorGUI.indentLevel;
-
- EditorGUILayout.Space();
-
- EditorGUILayout.PropertyField(m_NavigationProperty);
-
- EditorGUI.BeginChangeCheck();
- Rect toggleRect = EditorGUILayout.GetControlRect();
- toggleRect.xMin += EditorGUIUtility.labelWidth;
- s_ShowNavigation = GUI.Toggle(toggleRect, s_ShowNavigation, m_VisualizeNavigation, EditorStyles.miniButton);
- if (EditorGUI.EndChangeCheck())
- {
- EditorPrefs.SetBool(s_ShowNavigationKey, s_ShowNavigation);
- SceneView.RepaintAll();
- }
-
- // We do this here to avoid requiring the user to also write a Editor for their Selectable-derived classes.
- // This way if we are on a derived class we dont draw anything else, otherwise draw the remaining properties.
- ChildClassPropertiesGUI();
-
- serializedObject.ApplyModifiedProperties();
- }
-
- // Draw the extra SerializedProperties of the child class.
- // We need to make sure that m_PropertyPathToExcludeForChildClasses has all the Selectable properties and in the correct order.
- // TODO: find a nicer way of doing this. (creating a InheritedEditor class that automagically does this)
- private void ChildClassPropertiesGUI()
- {
- if (IsDerivedSelectableEditor())
- return;
-
- DrawPropertiesExcluding(serializedObject, m_PropertyPathToExcludeForChildClasses);
- }
-
- private bool IsDerivedSelectableEditor()
- {
- return GetType() != typeof(SelectableEditor);
- }
-
- private static Animations.AnimatorController GenerateSelectableAnimatorContoller(AnimationTriggers animationTriggers, Selectable target)
- {
- if (target == null)
- return null;
-
- // Where should we create the controller?
- var path = GetSaveControllerPath(target);
- if (string.IsNullOrEmpty(path))
- return null;
-
- // figure out clip names
- 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;
-
- // Create controller and hook up transitions.
- var controller = 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 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);
- }
-
- private static void SetUpCurves(AnimationClip highlightedClip, AnimationClip pressedClip, string animationPath)
- {
- string[] channels = { "m_LocalScale.x", "m_LocalScale.y", "m_LocalScale.z" };
-
- var highlightedKeys = new[] { new Keyframe(0f, 1f), new Keyframe(0.5f, 1.1f), new Keyframe(1f, 1f) };
- var highlightedCurve = new AnimationCurve(highlightedKeys);
- foreach (var channel in channels)
- AnimationUtility.SetEditorCurve(highlightedClip, EditorCurveBinding.FloatCurve(animationPath, typeof(Transform), channel), highlightedCurve);
-
- var pressedKeys = new[] { new Keyframe(0f, 1.15f) };
- var pressedCurve = new AnimationCurve(pressedKeys);
- foreach (var channel in channels)
- AnimationUtility.SetEditorCurve(pressedClip, EditorCurveBinding.FloatCurve(animationPath, typeof(Transform), channel), pressedCurve);
- }
-
- private static string BuildAnimationPath(Selectable target)
- {
- // if no target don't hook up any curves.
- var highlight = target.targetGraphic;
- if (highlight == null)
- return string.Empty;
-
- var startGo = highlight.gameObject;
- var toFindGo = target.gameObject;
-
- var pathComponents = new Stack<string>();
- while (toFindGo != startGo)
- {
- pathComponents.Push(startGo.name);
-
- // didn't exist in hierarchy!
- if (startGo.transform.parent == null)
- return string.Empty;
-
- startGo = startGo.transform.parent.gameObject;
- }
-
- // calculate path
- var animPath = new StringBuilder();
- if (pathComponents.Count > 0)
- animPath.Append(pathComponents.Pop());
-
- while (pathComponents.Count > 0)
- animPath.Append("/").Append(pathComponents.Pop());
-
- return animPath.ToString();
- }
-
- private static AnimationClip GenerateTriggerableTransition(string name, Animations.AnimatorController controller)
- {
- // Create the clip
- var clip = 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(Animations.AnimatorConditionMode.If, 0, name);
- return clip;
- }
-
- private static void StaticOnSceneGUI(SceneView view)
- {
- if (!s_ShowNavigation)
- return;
-
- Selectable[] selectables = Selectable.allSelectablesArray;
-
- for (int i = 0; i < selectables.Length; i++)
- {
- Selectable s = selectables[i];
- if (SceneManagement.StageUtility.IsGameObjectRenderedByCamera(s.gameObject, Camera.current))
- DrawNavigationForSelectable(s);
- }
- }
-
- private static void DrawNavigationForSelectable(Selectable 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, Selectable fromObj, Selectable 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;
- }
- }
- }
|