StringAssetDrawer.cs 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274
  1. // Animancer // https://kybernetik.com.au/animancer // Copyright 2018-2024 Kybernetik //
  2. #if UNITY_EDITOR
  3. using System;
  4. using System.IO;
  5. using UnityEditor;
  6. using UnityEngine;
  7. using static Animancer.Editor.AnimancerGUI;
  8. using Object = UnityEngine.Object;
  9. namespace Animancer.Editor
  10. {
  11. /// <summary>[Editor-Only] A custom Inspector for <see cref="StringAsset"/> fields.</summary>
  12. /// https://kybernetik.com.au/animancer/api/Animancer.Editor/StringAssetDrawer
  13. [CustomPropertyDrawer(typeof(StringAsset), true)]
  14. public class StringAssetDrawer : PropertyDrawer
  15. {
  16. /************************************************************************************************************************/
  17. /// <inheritdoc/>
  18. public override float GetPropertyHeight(SerializedProperty property, GUIContent label)
  19. => LineHeight;
  20. /************************************************************************************************************************/
  21. /// <inheritdoc/>
  22. public override void OnGUI(Rect area, SerializedProperty property, GUIContent label)
  23. {
  24. label = EditorGUI.BeginProperty(area, label, property);
  25. property.objectReferenceValue = DrawGUI(
  26. area,
  27. label,
  28. property,
  29. out var exitGUI);
  30. if (exitGUI)
  31. {
  32. property.serializedObject.ApplyModifiedProperties();
  33. GUIUtility.ExitGUI();
  34. }
  35. EditorGUI.EndProperty();
  36. }
  37. /************************************************************************************************************************/
  38. private static readonly Func<Object[]> GetCurrentPropertyValues =
  39. () => _CurrentProperty != null ? Serialization.GetValues<Object>(_CurrentProperty) : null;
  40. private static SerializedProperty _CurrentProperty;
  41. /// <summary>Draws the GUI for a <see cref="StringAsset"/>.</summary>
  42. public static Object DrawGUI(
  43. Rect area,
  44. GUIContent label,
  45. SerializedProperty property,
  46. out bool exitGUI)
  47. {
  48. var showMixedValue = EditorGUI.showMixedValue;
  49. if (property != null && property.hasMultipleDifferentValues)
  50. EditorGUI.showMixedValue = true;
  51. _CurrentProperty = property;
  52. var value = DrawGUI(
  53. area,
  54. label,
  55. property?.objectReferenceValue,
  56. property?.serializedObject?.targetObject,
  57. out exitGUI,
  58. GetCurrentPropertyValues);
  59. _CurrentProperty = null;
  60. EditorGUI.showMixedValue = showMixedValue;
  61. return value;
  62. }
  63. /************************************************************************************************************************/
  64. private static readonly int ButtonHash = "Button".GetHashCode();
  65. private static readonly int ObjectFieldHash = "s_ObjectFieldHash".GetHashCode();
  66. /// <summary>Draws the GUI for a <see cref="StringAsset"/>.</summary>
  67. public static Object DrawGUI(
  68. Rect area,
  69. GUIContent label,
  70. Object value,
  71. Object context,
  72. out bool exitGUI,
  73. Func<Object[]> getAllValues = null)
  74. {
  75. var currentEvent = Event.current;
  76. if (currentEvent.type == EventType.Repaint &&
  77. !area.Contains(currentEvent.mousePosition) &&
  78. !IsDraggingStringAsset())
  79. {
  80. GUIUtility.GetControlID(ButtonHash, FocusType.Passive);// Button.
  81. GUIUtility.GetControlID(ButtonHash, FocusType.Passive);// Button.
  82. GUIUtility.GetControlID(DragHint, FocusType.Passive, area);// DragAndDrop.
  83. var iconSize = EditorGUIUtility.GetIconSize();
  84. var newIconSize = LineHeight * 2 / 3;
  85. EditorGUIUtility.SetIconSize(new(newIconSize, newIconSize));
  86. var controlID = GUIUtility.GetControlID(ObjectFieldHash, FocusType.Keyboard, area);// Object.
  87. var valueArea = EditorGUI.PrefixLabel(area, controlID, label);
  88. using (var content = PooledGUIContent.Acquire())
  89. {
  90. if (value != null)
  91. {
  92. content.text = value.name;
  93. content.image = AnimancerIcons.ScriptableObject;
  94. }
  95. else
  96. {
  97. content.text = "";
  98. content.image = AssetPreview.GetMiniTypeThumbnail(typeof(StringAsset));
  99. }
  100. EditorStyles.objectField.Draw(valueArea, content, false, false, false, false);
  101. }
  102. EditorGUIUtility.SetIconSize(iconSize);
  103. exitGUI = false;
  104. return value;
  105. }
  106. if (value == null)
  107. {
  108. var buttonArea = StealFromRight(ref area, area.height, StandardSpacing);
  109. var content = AnimancerIcons.AddIcon("Create and save a new String Asset");
  110. if (GUI.Button(buttonArea, content, NoPaddingButtonStyle))
  111. {
  112. exitGUI = true;
  113. return CreateNewInstance(label.text, context);
  114. }
  115. GUIUtility.GetControlID(ButtonHash, FocusType.Passive);
  116. }
  117. else
  118. {
  119. var clearArea = StealFromRight(ref area, area.height, StandardSpacing);
  120. var copyArea = StealFromRight(ref area, area.height, StandardSpacing);
  121. var content = AnimancerIcons.CopyIcon("Copy string to clipboard");
  122. if (GUI.Button(copyArea, content, NoPaddingButtonStyle))
  123. GUIUtility.systemCopyBuffer = value.name;
  124. content = AnimancerIcons.ClearIcon("Clear reference");
  125. if (GUI.Button(clearArea, content, NoPaddingButtonStyle))
  126. value = null;
  127. }
  128. HandleDragAndDrop(area, currentEvent, value, getAllValues);
  129. exitGUI = false;
  130. return EditorGUI.ObjectField(area, label, value, typeof(StringAsset), false);
  131. }
  132. /************************************************************************************************************************/
  133. private static readonly int DragHint = "Drag".GetHashCode();
  134. private static void HandleDragAndDrop(
  135. Rect area,
  136. Event currentEvent,
  137. Object value,
  138. Func<Object[]> getAllValues)
  139. {
  140. var id = GUIUtility.GetControlID(DragHint, FocusType.Passive, area);
  141. switch (currentEvent.type)
  142. {
  143. // Drag out of object field.
  144. case EventType.MouseDrag:
  145. if (GUIUtility.keyboardControl == id + 1 &&
  146. currentEvent.button == 0 &&
  147. area.Contains(currentEvent.mousePosition) &&
  148. value != null)
  149. {
  150. var values = getAllValues?.Invoke() ?? new Object[] { value };
  151. DragAndDrop.PrepareStartDrag();
  152. DragAndDrop.objectReferences = values;
  153. DragAndDrop.StartDrag("Objects");
  154. currentEvent.Use();
  155. }
  156. break;
  157. }
  158. }
  159. /// <summary>Is a <see cref="StringAsset"/> currently being dragged?</summary>
  160. private static bool IsDraggingStringAsset()
  161. {
  162. var dragging = DragAndDrop.objectReferences;
  163. if (dragging.IsNullOrEmpty())
  164. return false;
  165. for (int i = 0; i < dragging.Length; i++)
  166. if (dragging[i] is not StringAsset)
  167. return false;
  168. return true;
  169. }
  170. /************************************************************************************************************************/
  171. private const string FolderPathKey = nameof(StringAsset) + ".FolderPath";
  172. /// <summary>Asks where to save a new <see cref="StringAsset"/>.</summary>
  173. private static Object CreateNewInstance(string name, Object targetObject)
  174. {
  175. var folderPath = GetSaveFolder(targetObject);
  176. var path = EditorUtility.SaveFilePanelInProject(
  177. "Create String Asset",
  178. name,
  179. "asset",
  180. "Where yould you like to save the new String Asset?",
  181. folderPath);
  182. if (string.IsNullOrEmpty(path))
  183. return null;
  184. EditorPrefs.SetString(FolderPathKey, Path.GetDirectoryName(path));
  185. var instance = ScriptableObject.CreateInstance<StringAsset>();
  186. AssetDatabase.CreateAsset(instance, path);
  187. return instance;
  188. }
  189. private static string GetSaveFolder(Object targetObject)
  190. {
  191. var getActiveFolderPath = typeof(ProjectWindowUtil).GetMethod(
  192. "GetActiveFolderPath",
  193. AnimancerReflection.StaticBindings,
  194. null,
  195. Type.EmptyTypes,
  196. null);
  197. if (getActiveFolderPath != null &&
  198. getActiveFolderPath.ReturnType == typeof(string))
  199. {
  200. var activeFolderPath = getActiveFolderPath.Invoke(null, Array.Empty<object>())?.ToString();
  201. if (!string.IsNullOrEmpty(activeFolderPath))
  202. return activeFolderPath;
  203. }
  204. var folderPath = AssetDatabase.GetAssetPath(targetObject);
  205. if (!string.IsNullOrEmpty(folderPath))
  206. folderPath = Path.GetDirectoryName(folderPath);
  207. if (string.IsNullOrEmpty(folderPath))
  208. {
  209. folderPath = EditorPrefs.GetString(FolderPathKey);
  210. if (folderPath == null || !folderPath.StartsWith("Assets/"))
  211. folderPath = "Assets/";
  212. }
  213. return folderPath;
  214. }
  215. /************************************************************************************************************************/
  216. }
  217. }
  218. #endif