diff --git a/Packages/Core/Editor/Drawers/AtomBaseReferenceDrawer.cs b/Packages/Core/Editor/Drawers/AtomBaseReferenceDrawer.cs index f1b82f5a..0db4bb1c 100644 --- a/Packages/Core/Editor/Drawers/AtomBaseReferenceDrawer.cs +++ b/Packages/Core/Editor/Drawers/AtomBaseReferenceDrawer.cs @@ -7,7 +7,6 @@ namespace UnityAtoms.Editor /// /// A custom property drawer for References (Events and regular). Makes it possible to reference a resources (Variable or Event) through multiple options. /// - public abstract class AtomBaseReferenceDrawer : PropertyDrawer { protected abstract class UsageData @@ -16,17 +15,60 @@ namespace UnityAtoms.Editor public abstract string PropertyName { get; } public abstract string DisplayName { get; } } + private const string USAGE_PROPERTY_NAME = "_usage"; protected abstract UsageData[] GetUsages(SerializedProperty prop = null); private string[] GetPopupOptions(SerializedProperty prop = null) => GetUsages(prop).Select(u => u.DisplayName).ToArray(); private static GUIStyle _popupStyle; + public override void OnGUI(Rect position, SerializedProperty property, GUIContent label) + { + GuiData guiData = new GuiData() + { + Position = position, + Property = property, + Label = label + }; + + if (_popupStyle == null) + { + _popupStyle = new GUIStyle(GUI.skin.GetStyle("PaneOptions")) + { + imagePosition = ImagePosition.ImageOnly + }; + } + + using (var scope = new EditorGUI.PropertyScope(position, label, property)) + { + guiData.Label = scope.content; + guiData.Position = EditorGUI.PrefixLabel(position, label); + // Store old indent level and set it to 0, the PrefixLabel takes care of it + int indent = EditorGUI.indentLevel; + EditorGUI.indentLevel = 0; + { + EditorGUI.BeginChangeCheck(); + { + DetermineDragAndDropFieldReferenceType(guiData); + DrawConfigurationButton(ref guiData); + string currentUsageTypePropertyName = GetUsages(property)[GetUsageIndex(property)].PropertyName; + DrawField(currentUsageTypePropertyName, guiData, position); + } + if (EditorGUI.EndChangeCheck()) + { + property.serializedObject.ApplyModifiedProperties(); + } + } + EditorGUI.indentLevel = indent; + } + } + public override float GetPropertyHeight(SerializedProperty property, GUIContent label) { - var usageIntVal = property.FindPropertyRelative("_usage").intValue; + var usageIntVal = GetUsageIndex(property); var usageData = GetUsages(property)[0]; - for (var i = 0; i < GetUsages(property).Length; ++i) + + for (int i = 0; i < GetUsages(property).Length; ++i) { if (GetUsages(property)[i].Value == usageIntVal) { @@ -36,53 +78,44 @@ namespace UnityAtoms.Editor } var innerProperty = property.FindPropertyRelative(usageData.PropertyName); + return innerProperty == null ? EditorGUIUtility.singleLineHeight : EditorGUI.GetPropertyHeight(innerProperty, label); } - public override void OnGUI(Rect position, SerializedProperty property, GUIContent label) + private void DrawConfigurationButton(ref GuiData guiData) { - if (_popupStyle == null) - { - _popupStyle = new GUIStyle(GUI.skin.GetStyle("PaneOptions")); - _popupStyle.imagePosition = ImagePosition.ImageOnly; - } + Rect button = new Rect(guiData.Position); + button.yMin += _popupStyle.margin.top; + button.yMax = button.yMin + EditorGUIUtility.singleLineHeight; + button.width = _popupStyle.fixedWidth + _popupStyle.margin.right; + guiData.Position.xMin = button.xMax; - Rect originalPosition = new Rect(position); + var currentUsageIndex = GetUsageIndex(guiData.Property); + var newUsageValue = EditorGUI.Popup(button, currentUsageIndex, GetPopupOptions(guiData.Property), _popupStyle); + SetUsageIndex(guiData.Property, newUsageValue); + } - label = EditorGUI.BeginProperty(position, label, property); - position = EditorGUI.PrefixLabel(position, label); - - EditorGUI.BeginChangeCheck(); - - // Calculate rect for configuration button - Rect buttonRect = new Rect(position); - buttonRect.yMin += _popupStyle.margin.top; - buttonRect.yMax = buttonRect.yMin + EditorGUIUtility.singleLineHeight; - buttonRect.width = _popupStyle.fixedWidth + _popupStyle.margin.right; - position.xMin = buttonRect.xMax; - - // Store old indent level and set it to 0, the PrefixLabel takes care of it - int indent = EditorGUI.indentLevel; - EditorGUI.indentLevel = 0; - - var currentUsage = property.FindPropertyRelative("_usage"); - var newUsageValue = EditorGUI.Popup(buttonRect, currentUsage.intValue, GetPopupOptions(property), _popupStyle); - currentUsage.intValue = newUsageValue; - - var usageTypePropertyName = GetUsages(property)[newUsageValue].PropertyName; - var usageTypeProperty = property.FindPropertyRelative(usageTypePropertyName); + private static void DrawField(string usageTypePropertyName, in GuiData guiData, in Rect originalPosition) + { + var usageTypeProperty = guiData.Property.FindPropertyRelative(usageTypePropertyName); if (usageTypeProperty == null) { - EditorGUI.LabelField(position, "[Non serialized value]"); + EditorGUI.LabelField(guiData.Position, "[Non serialized value]"); } else { var expanded = usageTypeProperty.isExpanded; usageTypeProperty.isExpanded = true; - var valueFieldHeight = EditorGUI.GetPropertyHeight(usageTypeProperty, label); + var valueFieldHeight = usageTypeProperty.propertyType == SerializedPropertyType.Quaternion ? + // In versions prior to 2022.3 GetPropertyHeight returns the wrong value for "SerializedPropertyType.Quaternion" + // In later versions, the fix is introduced _but only_ when using the SerializedPropertyType parameter, not when using the SerializedProperty parameter version. + // ALSO the SerializedPropertyType parameter version does not work with the isExpanded flag which we set to true exactly for this reason a (few) lines above. + EditorGUI.GetPropertyHeight(SerializedPropertyType.Vector3, guiData.Label) : + EditorGUI.GetPropertyHeight(usageTypeProperty, guiData.Label); + usageTypeProperty.isExpanded = expanded; if (usageTypePropertyName == "_value" && (valueFieldHeight > EditorGUIUtility.singleLineHeight + 2)) @@ -91,14 +124,127 @@ namespace UnityAtoms.Editor } else { - EditorGUI.PropertyField(position, usageTypeProperty, GUIContent.none); + EditorGUI.PropertyField(guiData.Position, usageTypeProperty, GUIContent.none); } } - if (EditorGUI.EndChangeCheck()) - property.serializedObject.ApplyModifiedProperties(); - - EditorGUI.indentLevel = indent; - EditorGUI.EndProperty(); } + + private static void SetUsageIndex(SerializedProperty property, int index) + { + property.FindPropertyRelative(USAGE_PROPERTY_NAME).intValue = index; + } + + private static int GetUsageIndex(SerializedProperty property) + { + return property.FindPropertyRelative(USAGE_PROPERTY_NAME).intValue; + } + + + #region Auto Drag And Drop Usage Type Detection + private void DetermineDragAndDropFieldReferenceType(in GuiData guiData) + { + EventType mouseEventType = Event.current.type; + + if (mouseEventType != EventType.DragUpdated && mouseEventType != EventType.DragPerform) + { + return; + } + + if (!IsMouseHoveringOverProperty(guiData.Position)) + { + return; + } + + var draggedObjects = DragAndDrop.objectReferences; + if (draggedObjects.Length < 1) + { + return; + } + + Object draggedObject = draggedObjects[0]; + + if (draggedObject is GameObject gameObject) + { + object[] instancers = gameObject.GetComponents(); + UpdateUsageConfigurationOption(guiData.Property, instancers); + } + else + { + UpdateUsageConfigurationOption(guiData.Property, draggedObject); + } + } + + private void UpdateUsageConfigurationOption(SerializedProperty property, params object[] draggedObjects) + { + if (draggedObjects == null || draggedObjects.Length < 1) + { + return; + } + + var usages = GetUsages(property); + int currentUsageIndex = GetUsageIndex(property); + int newUsageIndex = -1; + + foreach (object draggedObject in draggedObjects) + { + for (int index = 0; index < usages.Length; index++) + { + var usage = usages[index]; + var usageProperty = property.FindPropertyRelative(usage.PropertyName); + bool isDraggedTypeSameAsUsageType = AreTypesEqual(usageProperty, draggedObject); + + if (isDraggedTypeSameAsUsageType) + { + bool isUsageSetByUser = currentUsageIndex == index; + + if (isUsageSetByUser) + { + return; + } + + bool isNewUsageIndexSet = newUsageIndex > -1; + if (!isNewUsageIndexSet) + { + newUsageIndex = index; + } + + break; + } + } + } + + if (newUsageIndex > -1) + { + SetUsageIndex(property, newUsageIndex); + } + } + + private static bool AreTypesEqual(SerializedProperty property, object otherObject) + { + string otherObjectTypeName = otherObject.GetType().Name; + string propertyObjectTypeName = GetPropertyTypeName(property); + return otherObjectTypeName == propertyObjectTypeName; + } + + private static readonly string PPTR_GENERIC_PREFIX = "PPtr<$"; + private static string GetPropertyTypeName(SerializedProperty property) + { + if (!property.type.StartsWith(PPTR_GENERIC_PREFIX)) + { + return property.type; + } + string fieldPropertyType = property.type.Replace(PPTR_GENERIC_PREFIX, ""); + return fieldPropertyType.Remove(fieldPropertyType.Length - 1); + } + + private static bool IsMouseHoveringOverProperty(in Rect rectPosition) + { + const int HEIGHT_OFFSET_TO_AVOID_OVERLAP = 1; + Rect controlRect = rectPosition; + controlRect.height -= HEIGHT_OFFSET_TO_AVOID_OVERLAP; + + return controlRect.Contains(Event.current.mousePosition); + } + #endregion } } diff --git a/Packages/Core/Editor/Utils/GuiData.cs b/Packages/Core/Editor/Utils/GuiData.cs new file mode 100644 index 00000000..9f2a1ea5 --- /dev/null +++ b/Packages/Core/Editor/Utils/GuiData.cs @@ -0,0 +1,12 @@ +using UnityEditor; +using UnityEngine; + +namespace UnityAtoms.Editor +{ + public struct GuiData + { + public Rect Position; + public SerializedProperty Property; + public GUIContent Label; + } +} \ No newline at end of file diff --git a/Packages/Core/Editor/Utils/GuiData.cs.meta b/Packages/Core/Editor/Utils/GuiData.cs.meta new file mode 100644 index 00000000..5371731a --- /dev/null +++ b/Packages/Core/Editor/Utils/GuiData.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: a1c20882cb5b7ab4e8c3e8fb77d1ebf8 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/Core/Runtime/EventInstancers/AtomEventInstancer.cs b/Packages/Core/Runtime/EventInstancers/AtomEventInstancer.cs index ad6ebdae..d3c3c322 100644 --- a/Packages/Core/Runtime/EventInstancers/AtomEventInstancer.cs +++ b/Packages/Core/Runtime/EventInstancers/AtomEventInstancer.cs @@ -13,7 +13,7 @@ namespace UnityAtoms /// Event of type T. [EditorIcon("atom-icon-sign-blue")] [DefaultExecutionOrder(Runtime.ExecutionOrder.VARIABLE_INSTANCER)] - public abstract class AtomEventInstancer : MonoBehaviour, IGetEvent, ISetEvent + public abstract class AtomEventInstancer : MonoBehaviour, IGetEvent, ISetEvent, IAtomInstancer where E : AtomEvent { public T InspectorRaiseValue { get => _inspectorRaiseValue; } diff --git a/Packages/Core/Runtime/VariableInstancers/AtomBaseVariableInstancer.cs b/Packages/Core/Runtime/VariableInstancers/AtomBaseVariableInstancer.cs index fd38a01e..d0affbe0 100644 --- a/Packages/Core/Runtime/VariableInstancers/AtomBaseVariableInstancer.cs +++ b/Packages/Core/Runtime/VariableInstancers/AtomBaseVariableInstancer.cs @@ -17,7 +17,7 @@ namespace UnityAtoms /// Function of type T => T [EditorIcon("atom-icon-hotpink")] [DefaultExecutionOrder(Runtime.ExecutionOrder.VARIABLE_INSTANCER)] - public abstract class AtomBaseVariableInstancer : MonoBehaviour, IVariable + public abstract class AtomBaseVariableInstancer : MonoBehaviour, IVariable, IAtomInstancer where V : AtomBaseVariable { /// diff --git a/Packages/Core/Runtime/VariableInstancers/IAtomInstancer.cs b/Packages/Core/Runtime/VariableInstancers/IAtomInstancer.cs new file mode 100644 index 00000000..f9ce25e5 --- /dev/null +++ b/Packages/Core/Runtime/VariableInstancers/IAtomInstancer.cs @@ -0,0 +1,5 @@ +namespace UnityAtoms +{ + // Currently only used in AtomBaseReferenceDrawer in order to auto set usage type on drag and drop + public interface IAtomInstancer { } +} diff --git a/Packages/Core/Runtime/VariableInstancers/IAtomInstancer.cs.meta b/Packages/Core/Runtime/VariableInstancers/IAtomInstancer.cs.meta new file mode 100644 index 00000000..685a28ae --- /dev/null +++ b/Packages/Core/Runtime/VariableInstancers/IAtomInstancer.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 31198ba3f24bc6c49955d939f8ac29c9 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: