diff options
author | chai <215380520@qq.com> | 2024-05-23 10:08:29 +0800 |
---|---|---|
committer | chai <215380520@qq.com> | 2024-05-23 10:08:29 +0800 |
commit | 8722a9920c1f6119bf6e769cba270e63097f8e25 (patch) | |
tree | 2eaf9865de7fb1404546de4a4296553d8f68cc3b /Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/PackageTools/Editor/EditorBase.cs | |
parent | 3ba4020b69e5971bb0df7ee08b31d10ea4d01937 (diff) |
+ astar project
Diffstat (limited to 'Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/PackageTools/Editor/EditorBase.cs')
-rw-r--r-- | Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/PackageTools/Editor/EditorBase.cs | 300 |
1 files changed, 300 insertions, 0 deletions
diff --git a/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/PackageTools/Editor/EditorBase.cs b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/PackageTools/Editor/EditorBase.cs new file mode 100644 index 0000000..19efef1 --- /dev/null +++ b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/PackageTools/Editor/EditorBase.cs @@ -0,0 +1,300 @@ +using UnityEditor; +using UnityEngine; +using System.Collections.Generic; +using System.Linq; + +namespace Pathfinding { + /// <summary>Helper for creating editors</summary> + [CustomEditor(typeof(VersionedMonoBehaviour), true)] + [CanEditMultipleObjects] + public class EditorBase : Editor { + static System.Collections.Generic.Dictionary<string, string> cachedTooltips; + static System.Collections.Generic.Dictionary<string, string> cachedURLs; + Dictionary<string, SerializedProperty> props = new Dictionary<string, SerializedProperty>(); + + static GUIContent content = new GUIContent(); + static GUIContent showInDocContent = new GUIContent("Show in online documentation", ""); + static GUILayoutOption[] noOptions = new GUILayoutOption[0]; + public static System.Func<string> getDocumentationURL; + + protected HashSet<string> remainingUnhandledProperties; + + + static void LoadMeta () { + if (cachedTooltips == null) { + var filePath = EditorResourceHelper.editorAssets + "/tooltips.tsv"; + + try { + var lines = System.IO.File.ReadAllLines(filePath).Select(l => l.Split('\t', 3)).Where(l => l.Length == 3).ToArray(); + cachedURLs = lines.ToDictionary(l => l[0], l => l[1]); + cachedTooltips = lines.ToDictionary(l => l[0], l => l[2].Replace("\\n", "\n")); + } catch (System.Exception e) { + Debug.LogWarning("Could not load tooltips from " + filePath + "\n" + e); + cachedURLs = new System.Collections.Generic.Dictionary<string, string>(); + cachedTooltips = new System.Collections.Generic.Dictionary<string, string>(); + } + } + } + + + static string LookupPath (System.Type type, string path, Dictionary<string, string> lookupData) { + // Find the correct type if the path was not an immediate member of #type + while (true) { + var index = path.IndexOf('.'); + if (index == -1) break; + var fieldName = path.Substring(0, index); + var remaining = path.Substring(index + 1); + var field = type.GetField(fieldName, System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.NonPublic); + if (field != null) { + type = field.FieldType; + path = remaining; + } else { + // Could not find the correct field + return null; + } + } + + // Find a documentation entry for the field, fall back to parent classes if necessary + while (type != null) { + if (lookupData.TryGetValue(type.FullName + "." + path, out var value)) { + return value; + } + type = type.BaseType; + } + return null; + } + + string FindTooltip (string path) { + LoadMeta(); + return LookupPath(target.GetType(), path, cachedTooltips); + } + + protected virtual void OnEnable () { + foreach (var target in targets) if (target != null) (target as IVersionedMonoBehaviourInternal).UpgradeFromUnityThread(); + EditorApplication.contextualPropertyMenu += OnContextMenu; + } + + protected virtual void OnDisable () { + EditorApplication.contextualPropertyMenu -= OnContextMenu; + } + + void OnContextMenu (GenericMenu menu, SerializedProperty property) { + if (property.serializedObject != this.serializedObject) return; + + LoadMeta(); + var url = LookupPath(target.GetType(), property.propertyPath, cachedURLs); + + if (url != null && getDocumentationURL != null) { + menu.AddItem(showInDocContent, false, () => Application.OpenURL(getDocumentationURL() + url)); + } + } + + public sealed override void OnInspectorGUI () { + EditorGUI.indentLevel = 0; + serializedObject.Update(); + try { + Inspector(); + InspectorForRemainingAttributes(false, true); + } catch (System.Exception e) { + // This exception type should never be caught. See https://docs.unity3d.com/ScriptReference/ExitGUIException.html + if (e is ExitGUIException) throw e; + Debug.LogException(e, target); + } + serializedObject.ApplyModifiedProperties(); + if (targets.Length == 1 && (target as MonoBehaviour).enabled) { + var attr = target.GetType().GetCustomAttributes(typeof(UniqueComponentAttribute), true); + for (int i = 0; i < attr.Length; i++) { + string tag = (attr[i] as UniqueComponentAttribute).tag; + foreach (var other in (target as MonoBehaviour).GetComponents<MonoBehaviour>()) { + // Note: other can be null if some scripts are missing references + if (other == null || !other.enabled || other == target) continue; + if (other.GetType().GetCustomAttributes(typeof(UniqueComponentAttribute), true).Where(c => (c as UniqueComponentAttribute).tag == tag).Any()) { + EditorGUILayout.HelpBox("This component and " + other.GetType().Name + " cannot be used at the same time", MessageType.Warning); + } + } + } + } + } + + + protected virtual void Inspector () { + InspectorForRemainingAttributes(true, false); + } + + /// <summary>Draws an inspector for all fields that are likely not handled by the editor script itself</summary> + protected virtual void InspectorForRemainingAttributes (bool showHandled, bool showUnhandled) { + if (remainingUnhandledProperties == null) { + remainingUnhandledProperties = new HashSet<string>(); + + var tp = serializedObject.targetObject.GetType(); + var handledAssemblies = new List<System.Reflection.Assembly>(); + + // Find all types for which we have a [CustomEditor(type)] attribute. + // Unity hides this field, so we have to use reflection to get it. + var customEditorAttrs = this.GetType().GetCustomAttributes(typeof(CustomEditor), true).Cast<CustomEditor>().ToArray(); + foreach (var attr in customEditorAttrs) { + var inspectedTypeField = attr.GetType().GetField("m_InspectedType", System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance); + var inspectedType = inspectedTypeField.GetValue(attr) as System.Type; + if (!handledAssemblies.Contains(inspectedType.Assembly)) { + handledAssemblies.Add(inspectedType.Assembly); + } + } + bool enterChildren = true; + for (var prop = serializedObject.GetIterator(); prop.NextVisible(enterChildren); enterChildren = false) { + var name = prop.propertyPath; + var field = tp.GetField(name, System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance); + if (field == null) { + // Can happen for some built-in Unity fields. They are not important + continue; + } else { + var declaringType = field.DeclaringType; + var foundOtherAssembly = false; + var foundThisAssembly = false; + while (declaringType != null) { + if (handledAssemblies.Contains(declaringType.Assembly)) { + foundThisAssembly = true; + break; + } else { + foundOtherAssembly = true; + } + declaringType = declaringType.BaseType; + } + if (foundOtherAssembly && foundThisAssembly) { + // This is a field in a class in a different assembly, which inherits from a class in one of the handled assemblies. + // That probably means the editor script doesn't explicitly know about that field and we should show it anyway. + remainingUnhandledProperties.Add(prop.propertyPath); + } + } + } + } + + // Basically the same as DrawDefaultInspector, but with tooltips + bool enterChildren2 = true; + + for (var prop = serializedObject.GetIterator(); prop.NextVisible(enterChildren2); enterChildren2 = false) { + var handled = !remainingUnhandledProperties.Contains(prop.propertyPath); + if ((showHandled && handled) || (showUnhandled && !handled)) { + PropertyField(prop.propertyPath); + } + } + } + + protected SerializedProperty FindProperty (string name) { + if (!props.TryGetValue(name, out SerializedProperty res)) res = props[name] = serializedObject.FindProperty(name); + if (res == null) throw new System.ArgumentException(name); + return res; + } + + protected void Section (string label) { + EditorGUILayout.Separator(); + EditorGUILayout.LabelField(label, EditorStyles.boldLabel); + } + + protected bool SectionEnableable (string label, string enabledProperty) { + EditorGUILayout.Separator(); + var v = EditorGUILayout.ToggleLeft(label, FindProperty(enabledProperty).boolValue, EditorStyles.boldLabel); + FindProperty(enabledProperty).boolValue = v; + return v; + } + + /// <summary>Bounds field using center/size instead of center/extent</summary> + protected void BoundsField (string propertyPath) { + PropertyField(propertyPath + ".m_Center", "Center"); + var extentsProp = FindProperty(propertyPath + ".m_Extent"); + var r = EditorGUILayout.GetControlRect(); + var label = EditorGUI.BeginProperty(r, new GUIContent("Size"), extentsProp); + extentsProp.vector3Value = 0.5f * EditorGUI.Vector3Field(r, label, extentsProp.vector3Value * 2.0f); + EditorGUI.EndProperty(); + } + + protected void FloatField (string propertyPath, string label = null, string tooltip = null, float min = float.NegativeInfinity, float max = float.PositiveInfinity) { + PropertyField(propertyPath, label, tooltip); + Clamp(propertyPath, min, max); + } + + protected void FloatField (SerializedProperty prop, string label = null, string tooltip = null, float min = float.NegativeInfinity, float max = float.PositiveInfinity) { + PropertyField(prop, label, tooltip); + Clamp(prop, min, max); + } + + protected bool PropertyField (string propertyPath, string label = null, string tooltip = null) { + return PropertyField(FindProperty(propertyPath), label, tooltip, propertyPath); + } + + protected bool PropertyField (SerializedProperty prop, string label = null, string tooltip = null) { + return PropertyField(prop, label, tooltip, prop.propertyPath); + } + + bool PropertyField (SerializedProperty prop, string label, string tooltip, string propertyPath) { + content.text = label ?? prop.displayName; + content.tooltip = tooltip ?? FindTooltip(propertyPath); + EditorGUILayout.PropertyField(prop, content, true, noOptions); + return prop.propertyType == SerializedPropertyType.Boolean ? !prop.hasMultipleDifferentValues && prop.boolValue : true; + } + + protected void Popup (string propertyPath, GUIContent[] options, string label = null) { + var prop = FindProperty(propertyPath); + + content.text = label ?? prop.displayName; + content.tooltip = FindTooltip(propertyPath); + EditorGUI.BeginChangeCheck(); + var r = EditorGUILayout.GetControlRect(true, EditorGUIUtility.singleLineHeight, EditorStyles.popup); + r = EditorGUI.PrefixLabel(r, EditorGUI.BeginProperty(r, content, prop)); + var tmpIndent = EditorGUI.indentLevel; + EditorGUI.indentLevel = 0; + int newVal = EditorGUI.Popup(r, prop.propertyType == SerializedPropertyType.Enum ? prop.enumValueIndex : prop.intValue, options); + EditorGUI.indentLevel = tmpIndent; + if (EditorGUI.EndChangeCheck()) { + if (prop.propertyType == SerializedPropertyType.Enum) prop.enumValueIndex = newVal; + else prop.intValue = newVal; + } + EditorGUI.EndProperty(); + } + + protected void IntSlider (string propertyPath, int left, int right) { + var prop = FindProperty(propertyPath); + + content.text = prop.displayName; + content.tooltip = FindTooltip(propertyPath); + EditorGUILayout.IntSlider(prop, left, right, content, noOptions); + } + + protected void Slider (string propertyPath, float left, float right) { + var prop = FindProperty(propertyPath); + + content.text = prop.displayName; + content.tooltip = FindTooltip(propertyPath); + EditorGUILayout.Slider(prop, left, right, content, noOptions); + } + + protected bool ByteAsToggle (string propertyPath, string label) { + var prop = FindProperty(propertyPath); + + content.text = label; + content.tooltip = FindTooltip(propertyPath); + EditorGUI.BeginChangeCheck(); + var r = EditorGUILayout.GetControlRect(true, EditorGUIUtility.singleLineHeight, EditorStyles.popup); + r = EditorGUI.PrefixLabel(r, EditorGUI.BeginProperty(r, content, prop)); + var tmpIndent = EditorGUI.indentLevel; + EditorGUI.indentLevel = 0; + prop.intValue = EditorGUI.Toggle(r, prop.intValue != 0) ? 1 : 0; + EditorGUI.indentLevel = tmpIndent; + EditorGUI.EndProperty(); + return prop.intValue != 0; + } + + protected void Clamp (SerializedProperty prop, float min, float max = float.PositiveInfinity) { + if (!prop.hasMultipleDifferentValues) prop.floatValue = Mathf.Clamp(prop.floatValue, min, max); + } + + protected void Clamp (string name, float min, float max = float.PositiveInfinity) { + Clamp(FindProperty(name), min, max); + } + + protected void ClampInt (string name, int min, int max = int.MaxValue) { + var prop = FindProperty(name); + + if (!prop.hasMultipleDifferentValues) prop.intValue = Mathf.Clamp(prop.intValue, min, max); + } + } +} |