using UnityEditor;
using UnityEngine;
namespace OM.Editor
{
///
/// Custom PropertyDrawer for the EaseData struct.
/// Provides a specialized Inspector GUI that allows selecting between standard Easing Functions
/// and custom Animation Curves via a dropdown menu, displaying the relevant field accordingly.
/// Relies on EasingHelper and CurvesLibrary for populating the dropdown menu.
///
[CustomPropertyDrawer(typeof(EaseData), true)] // Draw for EaseData and derived classes (if any).
public class EasingDataPropertyDrawer : PropertyDrawer
{
///
/// Override to render the custom GUI for the EaseData property.
///
/// Rectangle on the screen to use for the property GUI.
/// The SerializedProperty representing the EaseData instance.
/// The label of this property (usually the field name).
public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
{
// Get references to the child properties within the EaseData struct.
var easingDataTypeProperty = property.FindPropertyRelative("easeDataType"); // Enum (Ease or AnimationCurve)
var easingTypeProperty = property.FindPropertyRelative("easeType"); // EasingFunction enum
var animationCurveProperty = property.FindPropertyRelative("animationCurve"); // AnimationCurve data
// --- Calculate Rects ---
var propertyRect = new Rect(position);
propertyRect.height = EditorGUIUtility.singleLineHeight; // Ensure single line height for the main field part.
propertyRect.width -= 20; // Make space for the dropdown button.
var buttonRect = new Rect(propertyRect)
{
x = propertyRect.xMax, // Position button immediately after the property field.
width = 20, // Standard width for small buttons/icons.
height = EditorGUIUtility.singleLineHeight,
};
// --- Draw Button ---
// Style for button hover effect (currently using grayTexture which might need definition or replacement).
var buttonStyle = new GUIStyle(GUI.skin.button) // Start with default button style
{
// Define hover state visual appearance.
hover = new GUIStyleState { background = Texture2D.grayTexture } // Basic gray background on hover. Consider a more subtle/themed texture.
,
// Remove padding to make icon fill the button better
// padding = new RectOffset(0, 0, 0, 0)
};
// Use a Unity built-in icon for the dropdown arrow.
//GUIContent buttonIcon = new GUIContent(EditorGUIUtility.IconContent("d_Preset.Context@2x")); // Icon often used for dropdowns/pickers.
// Draw the button and check if it was clicked.
if (GUI.Button(buttonRect,new GUIContent("*"), buttonStyle))
{
// If clicked, open the generic menu for selecting easing types/curves.
OpenGenericMenu(easingTypeProperty, easingDataTypeProperty, property, animationCurveProperty);
}
// --- Draw Main Property Field ---
// Determine which property to display based on the current easeDataType.
SerializedProperty propertyToDraw = easingDataTypeProperty.intValue == (int)EaseDataType.Ease
? easingTypeProperty // Show EasingFunction enum if type is Ease.
: animationCurveProperty; // Show AnimationCurve field if type is AnimationCurve.
// Draw the appropriate property field using the original label.
EditorGUI.PropertyField(propertyRect, propertyToDraw, new GUIContent(label)); // Pass label to ensure field name is shown.
}
///
/// Creates and displays a GenericMenu (context menu) populated with options
/// to select standard easing functions, predefined animation curves from CurvesLibrary,
/// or switch to a custom animation curve.
///
/// SerializedProperty for the 'easeType' field (EasingFunction).
/// SerializedProperty for the 'easeDataType' field (Ease/AnimationCurve enum).
/// The parent SerializedProperty representing the entire EaseData struct.
/// SerializedProperty for the 'animationCurve' field.
private void OpenGenericMenu(SerializedProperty easingTypeProperty, SerializedProperty easingDataTypeProperty,
SerializedProperty property, SerializedProperty animationCurveProperty)
{
// Record the current state of the target object for Undo functionality.
Undo.RecordObject(property.serializedObject.targetObject, "Switch Ease Type");
var genericMenu = new GenericMenu();
// Determine the current selection state.
var currentDataType = (EaseDataType)easingDataTypeProperty.enumValueIndex;
var isEaseSelected = currentDataType == EaseDataType.Ease;
var isCurveSelected = currentDataType == EaseDataType.AnimationCurve;
var currentEaseFunction = (EasingFunction)easingTypeProperty.enumValueIndex; // Store current ease func for checkmark logic
// --- Add Standard Easing Functions (Lerp/Idle Category) ---
// Use EasingHelper to iterate through available 'Idle' (non-PingPong) easing functions.
foreach (var functionIdle in EasingHelper.LoopThroughEasingFunctionsIdle())
{
var functionEnum = functionIdle.CastToEasingFunction(); // Cast Idle enum to main EasingFunction enum
// Add item: "Lerp/FunctionName", checkmark if it's the currently selected Ease function.
genericMenu.AddItem(new GUIContent("Lerp/" + functionIdle.ToString()),
isEaseSelected && currentEaseFunction == functionEnum, // Checkmark logic
() => // Action executed when this item is selected:
{
easingTypeProperty.enumValueIndex = (int)functionEnum; // Set the ease function.
easingDataTypeProperty.intValue = (int)EaseDataType.Ease; // Set data type to Ease.
property.serializedObject.ApplyModifiedProperties(); // Apply changes to the serialized object.
});
}
// --- Add PingPong Easing Functions ---
genericMenu.AddSeparator(""); // Visual separator in the menu.
// Use EasingHelper to iterate through available PingPong easing functions.
foreach (var functionPingPong in EasingHelper.LoopThroughEasingFunctionsPingPong())
{
var functionEnum = functionPingPong.CastToEasingFunction(); // Cast PingPong enum to main EasingFunction enum
// Add item: "PingPong/FunctionName", checkmark if it's the currently selected Ease function.
genericMenu.AddItem(new GUIContent("PingPong/" + functionPingPong.ToString()),
isEaseSelected && currentEaseFunction == functionEnum, // Checkmark logic
() => // Action executed when this item is selected:
{
easingTypeProperty.enumValueIndex = (int)functionEnum; // Set the ease function.
easingDataTypeProperty.intValue = (int)EaseDataType.Ease; // Set data type to Ease.
property.serializedObject.ApplyModifiedProperties(); // Apply changes.
});
}
// --- Add Predefined Animation Curves from Library ---
genericMenu.AddSeparator(""); // Visual separator.
// Access the singleton instance of the CurvesLibrary.
var curvesLibrary = CurvesLibrary.Instance;
if (curvesLibrary != null) // Check if the library asset exists.
{
// Add Lerp Curves from Library
foreach (var curve in curvesLibrary.LerpCurves)
{
// Checkmark logic: Is the type AnimationCurve AND is the current curve value equal to this library curve?
bool isCurrentCurve = isCurveSelected && AnimationCurveClamper.AreCurvesEqual(animationCurveProperty.animationCurveValue, curve.Curve);
genericMenu.AddItem(new GUIContent("Curve Lerp/" + curve.Name),
isCurrentCurve, // Checkmark logic
() => // Action executed when selected:
{
animationCurveProperty.animationCurveValue = curve.Curve; // Set the curve value.
easingDataTypeProperty.intValue = (int)EaseDataType.AnimationCurve; // Set data type to AnimationCurve.
property.serializedObject.ApplyModifiedProperties(); // Apply changes.
});
}
// Add Bounce Curves from Library
foreach (var curve in curvesLibrary.BounceCurves)
{
bool isCurrentCurve = isCurveSelected && AnimationCurveClamper.AreCurvesEqual(animationCurveProperty.animationCurveValue, curve.Curve);
genericMenu.AddItem(new GUIContent("Curve Bounce/" + curve.Name),
isCurrentCurve, // Checkmark logic
() => // Action executed when selected:
{
animationCurveProperty.animationCurveValue = curve.Curve;
easingDataTypeProperty.intValue = (int)EaseDataType.AnimationCurve;
property.serializedObject.ApplyModifiedProperties();
});
}
// Add Custom Curves from Library (if any)
if (curvesLibrary.CustomCurves.Count > 0)
{
genericMenu.AddSeparator("Curve Custom/"); // Separator specific to custom curves category
foreach (var curve in curvesLibrary.CustomCurves)
{
bool isCurrentCurve = isCurveSelected && AnimationCurveClamper.AreCurvesEqual(animationCurveProperty.animationCurveValue, curve.Curve);
genericMenu.AddItem(new GUIContent("Curve Custom/" + curve.Name),
isCurrentCurve, // Checkmark logic
() => // Action executed when selected:
{
animationCurveProperty.animationCurveValue = curve.Curve;
easingDataTypeProperty.intValue = (int)EaseDataType.AnimationCurve;
property.serializedObject.ApplyModifiedProperties();
});
}
}
}
else
{
genericMenu.AddDisabledItem(new GUIContent("CurvesLibrary not found in Resources"));
}
// --- Add General Animation Curve Options ---
genericMenu.AddSeparator("");
// Option to switch to Custom Animation Curve mode.
genericMenu.AddItem(new GUIContent("Custom Animation Curve"),
isCurveSelected, // Checkmark if currently in AnimationCurve mode.
() =>
{
easingDataTypeProperty.intValue = (int)EaseDataType.AnimationCurve;
// Optionally set a default curve if switching to custom.
// animationCurveProperty.animationCurveValue = AnimationCurve.Linear(0, 0, 1, 1);
property.serializedObject.ApplyModifiedProperties();
});
genericMenu.AddSeparator("");
// Option to save the currently edited Animation Curve to the library.
if (isCurveSelected && curvesLibrary != null) // Only enable if in AnimationCurve mode and library exists.
{
genericMenu.AddItem(new GUIContent("Save Current Curve to Library"),
false, // Never checkmarked by default.
() =>
{
// Prompt library to add the current curve.
curvesLibrary.AddCurve("New Custom Curve", animationCurveProperty.animationCurveValue);
// No need to ApplyModifiedProperties here as AddCurve handles saving/refreshing.
});
}
else // Disable the save option otherwise.
{
genericMenu.AddDisabledItem(new GUIContent("Save Current Curve to Library"));
}
// Option to open the CurvesLibrary asset in the Inspector.
if (curvesLibrary != null)
{
genericMenu.AddItem(new GUIContent("Open CurveLibrary Asset"),
false,
() =>
{
Selection.activeObject = curvesLibrary; // Select the library asset.
EditorGUIUtility.PingObject(curvesLibrary); // Highlight it in the Project window.
});
}
else
{
genericMenu.AddDisabledItem(new GUIContent("Open CurveLibrary Asset"));
}
// Display the menu at the current mouse position.
genericMenu.ShowAsContext();
}
// Optional Helper for comparing AnimationCurves (Unity doesn't provide a built-in reliable equality check)
// This is a basic implementation; more robust checking might be needed.
private static class AnimationCurveClamper
{
public static bool AreCurvesEqual(AnimationCurve curve1, AnimationCurve curve2)
{
if (curve1 == null && curve2 == null) return true;
if (curve1 == null || curve2 == null) return false;
if (curve1.keys.Length != curve2.keys.Length) return false;
if (curve1.preWrapMode != curve2.preWrapMode || curve1.postWrapMode != curve2.postWrapMode) return false;
for (int i = 0; i < curve1.keys.Length; i++)
{
Keyframe key1 = curve1.keys[i];
Keyframe key2 = curve2.keys[i];
// Compare keyframe properties with a small tolerance for floating-point inaccuracies.
if (Mathf.Abs(key1.time - key2.time) > 0.0001f ||
Mathf.Abs(key1.value - key2.value) > 0.0001f ||
Mathf.Abs(key1.inTangent - key2.inTangent) > 0.001f || // Tangents might need larger tolerance
Mathf.Abs(key1.outTangent - key2.outTangent) > 0.001f ||
key1.weightedMode != key2.weightedMode || // Weighted mode must match exactly
(key1.weightedMode != WeightedMode.None && // Only compare weights if weighted mode is active
(Mathf.Abs(key1.inWeight - key2.inWeight) > 0.001f ||
Mathf.Abs(key1.outWeight - key2.outWeight) > 0.001f)))
{
return false;
}
}
return true;
}
}
}
}