using System; using System.Linq; using UnityEditor; using UnityEngine; using UnityEngine.UI; namespace Dott.Editor { [CustomEditor(typeof(DOTweenFrame))] public class DOTweenFrameEditor : UnityEditor.Editor { public override void OnInspectorGUI() { serializedObject.Update(); var idProperty = serializedObject.FindProperty("id"); var delayProperty = serializedObject.FindProperty("delay"); var properties = serializedObject.FindProperty("properties"); EditorGUILayout.Space(); EditorGUILayout.PropertyField(idProperty, new GUIContent("ID")); EditorGUILayout.PropertyField(delayProperty); EditorGUILayout.Space(); for (var i = 0; i < properties.arraySize; i++) { OnPropertyGUI(properties, i); } if (GUILayout.Button("Add")) { properties.arraySize++; // Unity duplicates the last element when increasing arraySize, so reset the new one manually serializedObject.ApplyModifiedProperties(); ((DOTweenFrame)target).Properties[properties.arraySize - 1] = new DOTweenFrame.FrameProperty(); } serializedObject.ApplyModifiedProperties(); } private static void OnPropertyGUI(SerializedProperty properties, int index) { var boxStyle = new GUIStyle(GUI.skin.box) { padding = new RectOffset(4, 0, 6, 6), margin = new RectOffset(0, 0, 0, 8) }; var current = new CurrentProperty(properties, index); Rect targetRect; using (new GUILayout.VerticalScope(boxStyle)) using (new LabelWidthScope(NARROW_LABEL_WIDTH)) { Component targetComponent; using (new EditorGUILayout.HorizontalScope()) { EditorGUILayout.PropertyField(current.TargetGameObjectProp, new GUIContent("Target")); targetRect = GUILayoutUtility.GetLastRect(); targetComponent = TargetComponent(current); } if (current.TargetGameObject != null) { EditorGUILayout.PropertyField(current.PropertyTypeProp); } EndValue(current, targetComponent); } MenuButton(current, targetRect, boxStyle.padding); } private static void MenuButton(CurrentProperty current, Rect targetRect, RectOffset boxPadding) { var icon = EditorGUIUtility.IconContent("_Menu"); var removeButtonRect = new Rect(targetRect.xMin - boxPadding.left - MENU_BUTTON_SIZE, targetRect.y, MENU_BUTTON_SIZE, MENU_BUTTON_SIZE); if (GUI.Button(removeButtonRect, icon, EditorStyles.iconButton)) { var menu = new GenericMenu(); menu.AddItem(new GUIContent("Remove"), false, () => { current.Properties.DeleteArrayElementAtIndex(current.Index); current.Properties.serializedObject.ApplyModifiedProperties(); }); menu.ShowAsContext(); } } private static Component TargetComponent(CurrentProperty current) { var targetProperty = current.TargetProp; var targetGameObject = current.TargetGameObject; var propertyType = current.PropertyType; if (targetGameObject == null || propertyType == DOTweenFrame.FrameProperty.PropertyType.None) { targetProperty.objectReferenceValue = null; return null; } var components = FindTargetComponents(targetGameObject, propertyType); if (components.Length == 0) { EditorGUILayout.HelpBox("No suitable target component found", MessageType.Error); targetProperty.objectReferenceValue = null; return null; } Component component; var showPopupForSingleComponent = propertyType == DOTweenFrame.FrameProperty.PropertyType.Enabled; if (components.Length == 1 && !showPopupForSingleComponent) { targetProperty.objectReferenceValue = component = components[0]; return component; } var componentNames = components.Select(c => c.GetType().Name).ToArray(); var selectedIndex = Array.IndexOf(components, (Component)targetProperty.objectReferenceValue); if (selectedIndex == -1) { selectedIndex = 0; } selectedIndex = EditorGUILayout.Popup(selectedIndex, componentNames, GUILayout.MaxWidth(104f)); targetProperty.objectReferenceValue = component = components[selectedIndex]; return component; } private static void EndValue(CurrentProperty current, Component targetComponent) { if (targetComponent == null) { return; } var vector3Property = current.EndValueVector3Prop; var floatProperty = current.EndValueFloatProp; var colorProperty = current.EndValueColorProp; switch (current.PropertyType) { case DOTweenFrame.FrameProperty.PropertyType.Position: case DOTweenFrame.FrameProperty.PropertyType.LocalPosition: { EditorGUILayout.PropertyField(vector3Property, new GUIContent("Position")); break; } case DOTweenFrame.FrameProperty.PropertyType.Scale: { ScalePropertyField(current); break; } case DOTweenFrame.FrameProperty.PropertyType.Fade: { EditorGUILayout.PropertyField(floatProperty, new GUIContent("Alpha")); floatProperty.floatValue = Mathf.Clamp(floatProperty.floatValue, 0f, 1f); break; } case DOTweenFrame.FrameProperty.PropertyType.Color: EditorGUILayout.PropertyField(colorProperty, new GUIContent("Color")); break; case DOTweenFrame.FrameProperty.PropertyType.Active: case DOTweenFrame.FrameProperty.PropertyType.Enabled: EditorGUILayout.PropertyField(current.OptionalBoolProp, new GUIContent("Active")); break; case DOTweenFrame.FrameProperty.PropertyType.None: break; default: throw new ArgumentOutOfRangeException(nameof(current.PropertyType), current.PropertyType, null); } IsRelativeField(current); } private static void IsRelativeField(CurrentProperty current) { switch (current.PropertyType) { case DOTweenFrame.FrameProperty.PropertyType.Position: case DOTweenFrame.FrameProperty.PropertyType.LocalPosition: case DOTweenFrame.FrameProperty.PropertyType.Scale: EditorGUI.indentLevel++; EditorGUILayout.PropertyField(current.IsRelativeProp); EditorGUI.indentLevel--; break; case DOTweenFrame.FrameProperty.PropertyType.None: case DOTweenFrame.FrameProperty.PropertyType.Fade: case DOTweenFrame.FrameProperty.PropertyType.Color: case DOTweenFrame.FrameProperty.PropertyType.Active: case DOTweenFrame.FrameProperty.PropertyType.Enabled: break; default: throw new ArgumentOutOfRangeException(nameof(current.PropertyType), current.PropertyType, null); } } private static void ScalePropertyField(CurrentProperty current) { var uniformScaleButtonStyle = new GUIStyle("FloatFieldLinkButton"); var flexibleWidthOption = GUILayout.MinWidth(0f); var uniformProperty = current.OptionalBoolProp; var vector3Property = current.EndValueVector3Prop; var uniformScale = uniformProperty.boolValue; EditorGUILayout.BeginHorizontal(); GUILayout.Label("Scale", GUILayout.MaxWidth(NARROW_LABEL_WIDTH)); var lastRect = GUILayoutUtility.GetLastRect(); var toggleRect = new Rect(lastRect.xMax - UNIFORM_SCALE_BUTTON_WIDTH, lastRect.y, UNIFORM_SCALE_BUTTON_WIDTH, lastRect.height); uniformScale = EditorGUI.Toggle(toggleRect, uniformScale, uniformScaleButtonStyle); uniformProperty.boolValue = uniformScale; if (!uniformScale) { EditorGUILayout.PropertyField(vector3Property, GUIContent.none); EditorGUILayout.EndHorizontal(); return; } using (new LabelWidthScope(AXIS_LABEL_WIDTH)) { var scale = vector3Property.vector3Value.x; scale = EditorGUILayout.FloatField("X", scale, flexibleWidthOption); vector3Property.vector3Value = Vector3.one * scale; using (new EditorGUI.DisabledScope(true)) { EditorGUILayout.FloatField("Y", scale, flexibleWidthOption); EditorGUILayout.FloatField("Z", scale, flexibleWidthOption); } } EditorGUILayout.EndHorizontal(); } private static Component[] FindTargetComponents(GameObject targetGameObject, DOTweenFrame.FrameProperty.PropertyType propertyType) { switch (propertyType) { case DOTweenFrame.FrameProperty.PropertyType.Position: case DOTweenFrame.FrameProperty.PropertyType.LocalPosition: case DOTweenFrame.FrameProperty.PropertyType.Scale: case DOTweenFrame.FrameProperty.PropertyType.Active: return new Component[] { targetGameObject.GetComponent() }; case DOTweenFrame.FrameProperty.PropertyType.Fade: { var graphic = targetGameObject.GetComponent(); var canvasGroup = targetGameObject.GetComponent(); return new Component[] { graphic, canvasGroup }.Where(c => c != null).ToArray(); } case DOTweenFrame.FrameProperty.PropertyType.Color: { var graphic = targetGameObject.GetComponent(); var camera = targetGameObject.GetComponent(); return new Component[] { graphic, camera }.Where(c => c != null).ToArray(); } case DOTweenFrame.FrameProperty.PropertyType.Enabled: return targetGameObject.GetComponents().Cast().ToArray(); case DOTweenFrame.FrameProperty.PropertyType.None: default: return Array.Empty(); } } private struct CurrentProperty { public readonly SerializedProperty Properties; public readonly int Index; public readonly SerializedProperty TargetGameObjectProp; public readonly SerializedProperty PropertyTypeProp; public readonly SerializedProperty TargetProp; public readonly SerializedProperty IsRelativeProp; public readonly SerializedProperty EndValueVector3Prop; public readonly SerializedProperty EndValueFloatProp; public readonly SerializedProperty EndValueColorProp; public readonly SerializedProperty OptionalBoolProp; public readonly DOTweenFrame.FrameProperty.PropertyType PropertyType => (DOTweenFrame.FrameProperty.PropertyType)PropertyTypeProp.intValue; public readonly GameObject TargetGameObject => TargetGameObjectProp.objectReferenceValue as GameObject; public CurrentProperty(SerializedProperty properties, int index) { Properties = properties; Index = index; var property = properties.GetArrayElementAtIndex(index); TargetGameObjectProp = property.FindPropertyRelative("TargetGameObject"); PropertyTypeProp = property.FindPropertyRelative("Property"); TargetProp = property.FindPropertyRelative("Target"); IsRelativeProp = property.FindPropertyRelative("IsRelative"); EndValueVector3Prop = property.FindPropertyRelative("EndValueVector3"); EndValueFloatProp = property.FindPropertyRelative("EndValueFloat"); EndValueColorProp = property.FindPropertyRelative("EndValueColor"); OptionalBoolProp = property.FindPropertyRelative("OptionalBool"); } } #region Styles private const float NARROW_LABEL_WIDTH = 90f; private const int AXIS_LABEL_WIDTH = 12; private const float UNIFORM_SCALE_BUTTON_WIDTH = 18f; private const float MENU_BUTTON_SIZE = 16f; #endregion #region GUI private class LabelWidthScope : GUI.Scope { private readonly float prevLabelWidth; public LabelWidthScope(float labelWidth) { prevLabelWidth = EditorGUIUtility.labelWidth; EditorGUIUtility.labelWidth = labelWidth; } protected override void CloseScope() { EditorGUIUtility.labelWidth = prevLabelWidth; } } #endregion } }