2025-05-09 15:40:34 +08:00

275 lines
10 KiB
C#

// Animancer // https://kybernetik.com.au/animancer // Copyright 2018-2024 Kybernetik //
#if UNITY_EDITOR
using System;
using System.IO;
using UnityEditor;
using UnityEngine;
using static Animancer.Editor.AnimancerGUI;
using Object = UnityEngine.Object;
namespace Animancer.Editor
{
/// <summary>[Editor-Only] A custom Inspector for <see cref="StringAsset"/> fields.</summary>
/// https://kybernetik.com.au/animancer/api/Animancer.Editor/StringAssetDrawer
[CustomPropertyDrawer(typeof(StringAsset), true)]
public class StringAssetDrawer : PropertyDrawer
{
/************************************************************************************************************************/
/// <inheritdoc/>
public override float GetPropertyHeight(SerializedProperty property, GUIContent label)
=> LineHeight;
/************************************************************************************************************************/
/// <inheritdoc/>
public override void OnGUI(Rect area, SerializedProperty property, GUIContent label)
{
label = EditorGUI.BeginProperty(area, label, property);
property.objectReferenceValue = DrawGUI(
area,
label,
property,
out var exitGUI);
if (exitGUI)
{
property.serializedObject.ApplyModifiedProperties();
GUIUtility.ExitGUI();
}
EditorGUI.EndProperty();
}
/************************************************************************************************************************/
private static readonly Func<Object[]> GetCurrentPropertyValues =
() => _CurrentProperty != null ? Serialization.GetValues<Object>(_CurrentProperty) : null;
private static SerializedProperty _CurrentProperty;
/// <summary>Draws the GUI for a <see cref="StringAsset"/>.</summary>
public static Object DrawGUI(
Rect area,
GUIContent label,
SerializedProperty property,
out bool exitGUI)
{
var showMixedValue = EditorGUI.showMixedValue;
if (property != null && property.hasMultipleDifferentValues)
EditorGUI.showMixedValue = true;
_CurrentProperty = property;
var value = DrawGUI(
area,
label,
property?.objectReferenceValue,
property?.serializedObject?.targetObject,
out exitGUI,
GetCurrentPropertyValues);
_CurrentProperty = null;
EditorGUI.showMixedValue = showMixedValue;
return value;
}
/************************************************************************************************************************/
private static readonly int ButtonHash = "Button".GetHashCode();
private static readonly int ObjectFieldHash = "s_ObjectFieldHash".GetHashCode();
/// <summary>Draws the GUI for a <see cref="StringAsset"/>.</summary>
public static Object DrawGUI(
Rect area,
GUIContent label,
Object value,
Object context,
out bool exitGUI,
Func<Object[]> getAllValues = null)
{
var currentEvent = Event.current;
if (currentEvent.type == EventType.Repaint &&
!area.Contains(currentEvent.mousePosition) &&
!IsDraggingStringAsset())
{
GUIUtility.GetControlID(ButtonHash, FocusType.Passive);// Button.
GUIUtility.GetControlID(ButtonHash, FocusType.Passive);// Button.
GUIUtility.GetControlID(DragHint, FocusType.Passive, area);// DragAndDrop.
var iconSize = EditorGUIUtility.GetIconSize();
var newIconSize = LineHeight * 2 / 3;
EditorGUIUtility.SetIconSize(new(newIconSize, newIconSize));
var controlID = GUIUtility.GetControlID(ObjectFieldHash, FocusType.Keyboard, area);// Object.
var valueArea = EditorGUI.PrefixLabel(area, controlID, label);
using (var content = PooledGUIContent.Acquire())
{
if (value != null)
{
content.text = value.name;
content.image = AnimancerIcons.ScriptableObject;
}
else
{
content.text = "";
content.image = AssetPreview.GetMiniTypeThumbnail(typeof(StringAsset));
}
EditorStyles.objectField.Draw(valueArea, content, false, false, false, false);
}
EditorGUIUtility.SetIconSize(iconSize);
exitGUI = false;
return value;
}
if (value == null)
{
var buttonArea = StealFromRight(ref area, area.height, StandardSpacing);
var content = AnimancerIcons.AddIcon("Create and save a new String Asset");
if (GUI.Button(buttonArea, content, NoPaddingButtonStyle))
{
exitGUI = true;
return CreateNewInstance(label.text, context);
}
GUIUtility.GetControlID(ButtonHash, FocusType.Passive);
}
else
{
var clearArea = StealFromRight(ref area, area.height, StandardSpacing);
var copyArea = StealFromRight(ref area, area.height, StandardSpacing);
var content = AnimancerIcons.CopyIcon("Copy string to clipboard");
if (GUI.Button(copyArea, content, NoPaddingButtonStyle))
GUIUtility.systemCopyBuffer = value.name;
content = AnimancerIcons.ClearIcon("Clear reference");
if (GUI.Button(clearArea, content, NoPaddingButtonStyle))
value = null;
}
HandleDragAndDrop(area, currentEvent, value, getAllValues);
exitGUI = false;
return EditorGUI.ObjectField(area, label, value, typeof(StringAsset), false);
}
/************************************************************************************************************************/
private static readonly int DragHint = "Drag".GetHashCode();
private static void HandleDragAndDrop(
Rect area,
Event currentEvent,
Object value,
Func<Object[]> getAllValues)
{
var id = GUIUtility.GetControlID(DragHint, FocusType.Passive, area);
switch (currentEvent.type)
{
// Drag out of object field.
case EventType.MouseDrag:
if (GUIUtility.keyboardControl == id + 1 &&
currentEvent.button == 0 &&
area.Contains(currentEvent.mousePosition) &&
value != null)
{
var values = getAllValues?.Invoke() ?? new Object[] { value };
DragAndDrop.PrepareStartDrag();
DragAndDrop.objectReferences = values;
DragAndDrop.StartDrag("Objects");
currentEvent.Use();
}
break;
}
}
/// <summary>Is a <see cref="StringAsset"/> currently being dragged?</summary>
private static bool IsDraggingStringAsset()
{
var dragging = DragAndDrop.objectReferences;
if (dragging.IsNullOrEmpty())
return false;
for (int i = 0; i < dragging.Length; i++)
if (dragging[i] is not StringAsset)
return false;
return true;
}
/************************************************************************************************************************/
private const string FolderPathKey = nameof(StringAsset) + ".FolderPath";
/// <summary>Asks where to save a new <see cref="StringAsset"/>.</summary>
private static Object CreateNewInstance(string name, Object targetObject)
{
var folderPath = GetSaveFolder(targetObject);
var path = EditorUtility.SaveFilePanelInProject(
"Create String Asset",
name,
"asset",
"Where yould you like to save the new String Asset?",
folderPath);
if (string.IsNullOrEmpty(path))
return null;
EditorPrefs.SetString(FolderPathKey, Path.GetDirectoryName(path));
var instance = ScriptableObject.CreateInstance<StringAsset>();
AssetDatabase.CreateAsset(instance, path);
return instance;
}
private static string GetSaveFolder(Object targetObject)
{
var getActiveFolderPath = typeof(ProjectWindowUtil).GetMethod(
"GetActiveFolderPath",
AnimancerReflection.StaticBindings,
null,
Type.EmptyTypes,
null);
if (getActiveFolderPath != null &&
getActiveFolderPath.ReturnType == typeof(string))
{
var activeFolderPath = getActiveFolderPath.Invoke(null, Array.Empty<object>())?.ToString();
if (!string.IsNullOrEmpty(activeFolderPath))
return activeFolderPath;
}
var folderPath = AssetDatabase.GetAssetPath(targetObject);
if (!string.IsNullOrEmpty(folderPath))
folderPath = Path.GetDirectoryName(folderPath);
if (string.IsNullOrEmpty(folderPath))
{
folderPath = EditorPrefs.GetString(FolderPathKey);
if (folderPath == null || !folderPath.StartsWith("Assets/"))
folderPath = "Assets/";
}
return folderPath;
}
/************************************************************************************************************************/
}
}
#endif