using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Linq.Expressions; using System.Reflection; using System.Text; using UnityEditor.AnimatedValues; using UnityEngine; using UnityEngine.Rendering; namespace UnityEditor.Rendering { using UnityObject = UnityEngine.Object; /// Utility class for Editor public static class CoreEditorUtils { static GraphicsDeviceType[] m_BuildTargets; /// Build targets public static GraphicsDeviceType[] buildTargets => m_BuildTargets ?? (m_BuildTargets = PlayerSettings.GetGraphicsAPIs(EditorUserBuildSettings.activeBuildTarget)); static CoreEditorUtils() { LoadSkinAndIconMethods(); } // Serialization helpers /// /// To use with extreme caution. It not really get the property but try to find a field with similar name /// Hence inheritance override of property is not supported. /// Also variable rename will silently break the search. /// /// Entry type of expr /// Type of the value /// Expression returning the value seeked /// serialization path of the seeked property public static string FindProperty(Expression> expr) { // Get the field path as a string MemberExpression me; switch (expr.Body.NodeType) { case ExpressionType.MemberAccess: me = expr.Body as MemberExpression; break; default: throw new InvalidOperationException(); } var members = new List(); while (me != null) { // For field, get the field name // For properties, get the name of the backing field var name = me.Member is FieldInfo ? me.Member.Name : "m_" + me.Member.Name.Substring(0, 1).ToUpperInvariant() + me.Member.Name.Substring(1); members.Add(name); me = me.Expression as MemberExpression; } var sb = new StringBuilder(); for (int i = members.Count - 1; i >= 0; i--) { sb.Append(members[i]); if (i > 0) sb.Append('.'); } return sb.ToString(); } // UI Helpers /// Creates a 1x1 with a plain /// The color to fill the texture /// The name of the texture /// a public static Texture2D CreateColoredTexture2D(Color color, string textureName) { Texture2D tex2 = new Texture2D(1, 1) { hideFlags = HideFlags.HideAndDontSave, name = textureName }; tex2.SetPixel(1, 1, color); tex2.Apply(); return tex2; } /// Draw a help box with the Fix button. /// The message text. /// When the user clicks the button, Unity performs this action. public static void DrawFixMeBox(string text, Action action) { DrawFixMeBox(text, MessageType.Warning, action); } const float k_IndentMargin = 15.0f; /// Draw a help box with the Fix button. /// The message with icon if needed. /// The button text. /// When the user clicks the button, Unity performs this action. public static void DrawFixMeBox(GUIContent message, string buttonLabel, Action action) { EditorGUILayout.BeginHorizontal(); float indent = EditorGUI.indentLevel * k_IndentMargin - EditorStyles.helpBox.margin.left; GUILayoutUtility.GetRect(indent, EditorGUIUtility.singleLineHeight, EditorStyles.helpBox, GUILayout.ExpandWidth(false)); Rect leftRect = GUILayoutUtility.GetRect(new GUIContent(buttonLabel), EditorStyles.miniButton, GUILayout.MinWidth(60)); Rect rect = GUILayoutUtility.GetRect(message, EditorStyles.helpBox); Rect boxRect = new Rect(leftRect.x, rect.y, rect.xMax - leftRect.xMin, rect.height); int oldIndent = EditorGUI.indentLevel; EditorGUI.indentLevel = 0; if (Event.current.type == EventType.Repaint) EditorStyles.helpBox.Draw(boxRect, false, false, false, false); Rect labelRect = new Rect(boxRect.x + 4, boxRect.y + 3, rect.width - 8, rect.height); EditorGUI.LabelField(labelRect, message, CoreEditorStyles.helpBox); var buttonRect = leftRect; buttonRect.x += rect.width - 2; buttonRect.y = rect.yMin + (rect.height - EditorGUIUtility.singleLineHeight) / 2; bool clicked = GUI.Button(buttonRect, buttonLabel); EditorGUI.indentLevel = oldIndent; EditorGUILayout.EndHorizontal(); if (clicked) action(); } // UI Helpers /// Draw a help box with the Fix button. /// The message with icon if need. /// When the user clicks the button, Unity performs this action. public static void DrawFixMeBox(GUIContent message, Action action) { GUILayout.Space(2); using (new EditorGUILayout.HorizontalScope(EditorStyles.helpBox)) { EditorGUILayout.LabelField(message, CoreEditorStyles.helpBoxLabelStyle); GUILayout.FlexibleSpace(); using (new EditorGUILayout.VerticalScope()) { GUILayout.FlexibleSpace(); if (GUILayout.Button("Fix", GUILayout.Width(60))) action(); GUILayout.FlexibleSpace(); } } GUILayout.Space(5); } /// Draw a help box with the Fix button. /// The message text. /// The type of the message. /// When the user clicks the button, Unity performs this action. public static void DrawFixMeBox(string text, MessageType messageType, Action action) { DrawFixMeBox(EditorGUIUtility.TrTextContentWithIcon(text, CoreEditorStyles.GetMessageTypeIcon(messageType)), action); } /// /// Draw a multiple field property /// /// Label of the whole /// Properties /// Sub-labels public static void DrawMultipleFields(string label, SerializedProperty[] ppts, GUIContent[] labels) => DrawMultipleFields(EditorGUIUtility.TrTextContent(label), ppts, labels); private static float GetLongestLabelWidth(GUIContent[] labels) { float labelWidth = 0.0f; for (var i = 0; i < labels.Length; ++i) labelWidth = Mathf.Max(EditorStyles.label.CalcSize(labels[i]).x, labelWidth); return labelWidth; } /// /// Draws an for the given property /// /// /// The rect where the drop down will be drawn /// The label for the drop down /// The to modify public static void DrawEnumPopup(Rect rect, GUIContent label, SerializedProperty serializedProperty) where TEnum : Enum { EditorGUI.BeginChangeCheck(); var newValue = (TEnum)EditorGUI.EnumPopup(rect, label, serializedProperty.GetEnumValue()); if (EditorGUI.EndChangeCheck()) serializedProperty.SetEnumValue(newValue); EditorGUI.EndProperty(); } /// /// Draw a multiple field property /// /// Label of the whole /// Properties /// Sub-labels public static void DrawMultipleFields(GUIContent label, SerializedProperty[] ppts, GUIContent[] labels) { var labelWidth = EditorGUIUtility.labelWidth; using (new EditorGUILayout.HorizontalScope()) { EditorGUILayout.PrefixLabel(label); using (new EditorGUILayout.VerticalScope()) { EditorGUIUtility.labelWidth = GetLongestLabelWidth(labels) + CoreEditorConstants.standardHorizontalSpacing; int oldIndentLevel = EditorGUI.indentLevel; EditorGUI.indentLevel = 0; for (var i = 0; i < ppts.Length; ++i) EditorGUILayout.PropertyField(ppts[i], labels[i]); EditorGUI.indentLevel = oldIndentLevel; } } EditorGUIUtility.labelWidth = labelWidth; } /// /// Draw a multiple field property /// /// Label of the whole /// The labels mapping the values /// The values to be displayed /// The serialized properties public static void DrawMultipleFields(GUIContent label, GUIContent[] labels, T[] values) where T : struct { var labelWidth = EditorGUIUtility.labelWidth; using (new EditorGUILayout.HorizontalScope()) { EditorGUILayout.PrefixLabel(label); using (new EditorGUILayout.VerticalScope()) { EditorGUIUtility.labelWidth = GetLongestLabelWidth(labels) + CoreEditorConstants.standardHorizontalSpacing; int oldIndentLevel = EditorGUI.indentLevel; EditorGUI.indentLevel = 0; for (var i = 0; i < values.Length; ++i) { // Draw the right field depending on its type. if (typeof(T) == typeof(int)) values[i] = (T)(object)EditorGUILayout.DelayedIntField(labels[i], (int)(object)values[i]); else if (typeof(T) == typeof(bool)) values[i] = (T)(object)EditorGUILayout.Toggle(labels[i], (bool)(object)values[i]); else if (typeof(T) == typeof(float)) values[i] = (T)(object)EditorGUILayout.FloatField(labels[i], (float)(object)values[i]); else if (typeof(T).IsEnum) values[i] = (T)(object)EditorGUILayout.EnumPopup(labels[i], (Enum)(object)values[i]); else throw new ArgumentOutOfRangeException($"<{typeof(T)}> is not a supported type for multi field"); } EditorGUI.indentLevel = oldIndentLevel; } } EditorGUIUtility.labelWidth = labelWidth; } /// Draw a splitter separator /// [Optional] add margin if the splitter is boxed public static void DrawSplitter(bool isBoxed = false) { var rect = GUILayoutUtility.GetRect(1f, 1f); float xMin = rect.xMin; // Splitter rect should be full-width rect.xMin = 0f; rect.width += 4f; if (isBoxed) { rect.xMin = xMin == 7.0 ? 4.0f : EditorGUIUtility.singleLineHeight; rect.width -= 1; } if (Event.current.type != EventType.Repaint) return; EditorGUI.DrawRect(rect, !EditorGUIUtility.isProSkin ? new Color(0.6f, 0.6f, 0.6f, 1.333f) : new Color(0.12f, 0.12f, 0.12f, 1.333f)); } /// Draw a header /// Title of the header public static void DrawHeader(string title) => DrawHeader(EditorGUIUtility.TrTextContent(title)); /// Draw a header /// Title of the header public static void DrawHeader(GUIContent title) { var backgroundRect = GUILayoutUtility.GetRect(1f, 17f); var labelRect = backgroundRect; labelRect.xMin += 16f; labelRect.xMax -= 20f; var foldoutRect = backgroundRect; foldoutRect.y += 1f; foldoutRect.width = 13f; foldoutRect.height = 13f; // Background rect should be full-width backgroundRect.xMin = 0f; backgroundRect.width += 4f; // Background float backgroundTint = EditorGUIUtility.isProSkin ? 0.1f : 1f; EditorGUI.DrawRect(backgroundRect, new Color(backgroundTint, backgroundTint, backgroundTint, 0.2f)); // Title EditorGUI.LabelField(labelRect, title, EditorStyles.boldLabel); } /// Draw a foldout header /// The title of the header /// The state of the header /// [optional] is the eader contained in a box style ? /// [optional] Delegate used to draw the right state of the advanced button. If null, no button drawn. /// [optional] Callback call when advanced button clicked. Should be used to toggle its state. /// return the state of the foldout header public static bool DrawHeaderFoldout(string title, bool state, bool isBoxed = false, Func hasMoreOptions = null, Action toggleMoreOption = null) => DrawHeaderFoldout(EditorGUIUtility.TrTextContent(title), state, isBoxed, hasMoreOptions, toggleMoreOption); /// Draw a foldout header /// The title of the header /// The state of the header /// [optional] is the eader contained in a box style ? /// [optional] Delegate used to draw the right state of the advanced button. If null, no button drawn. /// [optional] Callback call when advanced button clicked. Should be used to toggle its state. /// [optional] The URL that the Unity Editor opens when the user presses the help button on the header. /// [optional] The callback that the Unity Editor executes when the user presses the burger menu on the header. /// return the state of the foldout header public static bool DrawHeaderFoldout(GUIContent title, bool state, bool isBoxed = false, Func hasMoreOptions = null, Action toggleMoreOptions = null, string documentationURL = "", Action contextAction = null) { const float height = 17f; var backgroundRect = GUILayoutUtility.GetRect(1f, height); float xMin = backgroundRect.xMin; var labelRect = backgroundRect; labelRect.xMin += 16f; labelRect.xMax -= 20f; var foldoutRect = backgroundRect; foldoutRect.y += 1f; foldoutRect.width = 13f; foldoutRect.height = 13f; foldoutRect.x = labelRect.xMin + 15 * (EditorGUI.indentLevel - 1); //fix for presset // Background rect should be full-width backgroundRect.xMin = 0f; backgroundRect.width += 4f; if (isBoxed) { labelRect.xMin += 5; foldoutRect.xMin += 5; backgroundRect.xMin = xMin == 7.0 ? 4.0f : EditorGUIUtility.singleLineHeight; backgroundRect.width -= 1; } // Background float backgroundTint = EditorGUIUtility.isProSkin ? 0.1f : 1f; EditorGUI.DrawRect(backgroundRect, new Color(backgroundTint, backgroundTint, backgroundTint, 0.2f)); // Title EditorGUI.LabelField(labelRect, title, EditorStyles.boldLabel); // Active checkbox state = GUI.Toggle(foldoutRect, state, GUIContent.none, EditorStyles.foldout); // Context menu var menuIcon = CoreEditorStyles.paneOptionsIcon; var menuRect = new Rect(labelRect.xMax + 3f, labelRect.y + 1f, 16, 16); // Add context menu for "Additional Properties" if (contextAction == null && hasMoreOptions != null) { contextAction = pos => OnContextClick(pos, hasMoreOptions, toggleMoreOptions); } if (contextAction != null) { if (GUI.Button(menuRect, CoreEditorStyles.contextMenuIcon, CoreEditorStyles.contextMenuStyle)) contextAction(new Vector2(menuRect.x, menuRect.yMax)); } // Documentation button ShowHelpButton(menuRect, documentationURL, title); var e = Event.current; if (e.type == EventType.MouseDown) { if (backgroundRect.Contains(e.mousePosition)) { if (e.button != 0 && contextAction != null) contextAction(e.mousePosition); else if (e.button == 0) { state = !state; e.Use(); } e.Use(); } } return state; } /// Draw a foldout header /// The title of the header /// The state of the header /// [optional] is the eader contained in a box style ? /// [optional] Delegate used to draw the right state of the advanced button. If null, no button drawn. /// [optional] Callback call when advanced button clicked. Should be used to toggle its state. /// return the state of the sub foldout header [Obsolete("'More Options' versions of DrawSubHeaderFoldout are obsolete. Please use DrawSubHeaderFoldout without 'More Options'")] public static bool DrawSubHeaderFoldout(string title, bool state, bool isBoxed = false, Func hasMoreOptions = null, Action toggleMoreOptions = null) => DrawSubHeaderFoldout(EditorGUIUtility.TrTextContent(title), state, isBoxed); /// Draw a foldout header /// The title of the header /// The state of the header /// [optional] is the eader contained in a box style ? /// [optional] Delegate used to draw the right state of the advanced button. If null, no button drawn. /// [optional] Callback call when advanced button clicked. Should be used to toggle its state. /// return the state of the foldout header [Obsolete("'More Options' versions of DrawSubHeaderFoldout are obsolete. Please use DrawSubHeaderFoldout without 'More Options'")] public static bool DrawSubHeaderFoldout(GUIContent title, bool state, bool isBoxed = false, Func hasMoreOptions = null, Action toggleMoreOptions = null) => DrawSubHeaderFoldout(title, state, isBoxed); /// /// Draw a foldout sub header /// /// The title of the header /// The state of the header /// [optional] is the eader contained in a box style ? /// return the state of the sub foldout header public static bool DrawSubHeaderFoldout(string title, bool state, bool isBoxed = false) => DrawSubHeaderFoldout(EditorGUIUtility.TrTextContent(title), state, isBoxed); /// /// Draw a foldout sub header /// /// The title of the header /// The state of the header /// [optional] is the eader contained in a box style ? /// return the state of the sub foldout header public static bool DrawSubHeaderFoldout(GUIContent title, bool state, bool isBoxed = false) { const float height = 17f; var backgroundRect = GUILayoutUtility.GetRect(1f, height); var labelRect = backgroundRect; labelRect.xMin += 16f; labelRect.xMax -= 20f; var foldoutRect = backgroundRect; foldoutRect.y += 1f; foldoutRect.x += 15 * EditorGUI.indentLevel; //GUI do not handle indent. Handle it here foldoutRect.width = 13f; foldoutRect.height = 13f; // Background rect should be full-width backgroundRect.xMin = 0f; backgroundRect.width += 4f; if (isBoxed) { labelRect.xMin += 5; foldoutRect.xMin += 5; backgroundRect.xMin = EditorGUIUtility.singleLineHeight; backgroundRect.width -= 3; } // Title EditorGUI.LabelField(labelRect, title, EditorStyles.boldLabel); // Active checkbox state = GUI.Toggle(foldoutRect, state, GUIContent.none, EditorStyles.foldout); var e = Event.current; if (e.type == EventType.MouseDown && backgroundRect.Contains(e.mousePosition) && e.button == 0) { state = !state; e.Use(); } return state; } /// Draw a header toggle like in Volumes /// The title of the header /// The group of the header /// The active field /// The context action /// Delegate saying if we have MoreOptions /// Callback called when the MoreOptions is toggled /// return the state of the foldout header public static bool DrawHeaderToggle(string title, SerializedProperty group, SerializedProperty activeField, Action contextAction = null, Func hasMoreOptions = null, Action toggleMoreOptions = null) => DrawHeaderToggle(EditorGUIUtility.TrTextContent(title), group, activeField, contextAction, hasMoreOptions, toggleMoreOptions, null); /// Draw a header toggle like in Volumes /// The title of the header /// The group of the header /// The active field /// The context action /// Delegate saying if we have MoreOptions /// Callback called when the MoreOptions is toggled /// return the state of the foldout header public static bool DrawHeaderToggle(GUIContent title, SerializedProperty group, SerializedProperty activeField, Action contextAction = null, Func hasMoreOptions = null, Action toggleMoreOptions = null) => DrawHeaderToggle(title, group, activeField, contextAction, hasMoreOptions, toggleMoreOptions, null); /// Draw a header toggle like in Volumes /// The title of the header /// The group of the header /// The active field /// The context action /// Delegate saying if we have MoreOptions /// Callback called when the MoreOptions is toggled /// Documentation URL /// return the state of the foldout header public static bool DrawHeaderToggle(string title, SerializedProperty group, SerializedProperty activeField, Action contextAction, Func hasMoreOptions, Action toggleMoreOptions, string documentationURL) => DrawHeaderToggle(EditorGUIUtility.TrTextContent(title), group, activeField, contextAction, hasMoreOptions, toggleMoreOptions, documentationURL); /// Draw a header toggle like in Volumes /// The title of the header /// The group of the header /// The active field /// The context action /// Delegate saying if we have MoreOptions /// Callback called when the MoreOptions is toggled /// Documentation URL /// return the state of the foldout header public static bool DrawHeaderToggle(GUIContent title, SerializedProperty group, SerializedProperty activeField, Action contextAction, Func hasMoreOptions, Action toggleMoreOptions, string documentationURL) { var backgroundRect = EditorGUI.IndentedRect(GUILayoutUtility.GetRect(1f, 17f)); var labelRect = backgroundRect; labelRect.xMin += 32f; labelRect.xMax -= 20f + 16 + 5; var foldoutRect = backgroundRect; foldoutRect.y += 1f; foldoutRect.width = 13f; foldoutRect.height = 13f; var toggleRect = backgroundRect; toggleRect.x += 16f; toggleRect.y += 2f; toggleRect.width = 13f; toggleRect.height = 13f; // Background rect should be full-width backgroundRect.xMin = 0f; backgroundRect.width += 4f; // Background float backgroundTint = EditorGUIUtility.isProSkin ? 0.1f : 1f; EditorGUI.DrawRect(backgroundRect, new Color(backgroundTint, backgroundTint, backgroundTint, 0.2f)); // Title using (new EditorGUI.DisabledScope(!activeField.boolValue)) EditorGUI.LabelField(labelRect, title, EditorStyles.boldLabel); // Foldout group.serializedObject.Update(); group.isExpanded = GUI.Toggle(foldoutRect, group.isExpanded, GUIContent.none, EditorStyles.foldout); group.serializedObject.ApplyModifiedProperties(); // Active checkbox activeField.serializedObject.Update(); activeField.boolValue = GUI.Toggle(toggleRect, activeField.boolValue, GUIContent.none, CoreEditorStyles.smallTickbox); activeField.serializedObject.ApplyModifiedProperties(); // Context menu var contextMenuIcon = CoreEditorStyles.contextMenuIcon.image; var contextMenuRect = new Rect(labelRect.xMax + 3f + 16 + 5, labelRect.y + 1f, 16, 16); if (contextAction == null && hasMoreOptions != null) { // If no contextual menu add one for the additional properties. contextAction = pos => OnContextClick(pos, hasMoreOptions, toggleMoreOptions); } if (contextAction != null) { if (GUI.Button(contextMenuRect, CoreEditorStyles.contextMenuIcon, CoreEditorStyles.contextMenuStyle)) contextAction(new Vector2(contextMenuRect.x, contextMenuRect.yMax)); } // Documentation button ShowHelpButton(contextMenuRect, documentationURL, title); // Handle events var e = Event.current; if (e.type == EventType.MouseDown) { if (backgroundRect.Contains(e.mousePosition)) { // Left click: Expand/Collapse if (e.button == 0) group.isExpanded = !group.isExpanded; // Right click: Context menu else if (contextAction != null) contextAction(e.mousePosition); e.Use(); } } return group.isExpanded; } /// Draw a header section like in Global Settings /// The title of the header /// Documentation URL /// The context action /// Delegate saying if we have MoreOptions /// Callback called when the MoreOptions is toggled public static void DrawSectionHeader(GUIContent title, string documentationURL = null, Action contextAction = null, Func hasMoreOptions = null, Action toggleMoreOptions = null) { var backgroundRect = EditorGUI.IndentedRect(GUILayoutUtility.GetRect(1f, 17f)); float iconSize = 16f; var contextMenuRect = new Rect(backgroundRect.xMax - (iconSize + 5), backgroundRect.y + iconSize + 8f, iconSize, iconSize); using (new EditorGUILayout.HorizontalScope()) { EditorGUILayout.LabelField(title, CoreEditorStyles.sectionHeaderStyle); // Context menu var contextMenuIcon = CoreEditorStyles.contextMenuIcon.image; if (contextAction != null) { if (GUI.Button(contextMenuRect, CoreEditorStyles.contextMenuIcon, CoreEditorStyles.contextMenuStyle)) contextAction(new Vector2(contextMenuRect.x, contextMenuRect.yMax)); } ShowHelpButton(contextMenuRect, documentationURL, title); } // Handle events var e = Event.current; if (e.type == EventType.MouseDown) { if (contextMenuRect.Contains(e.mousePosition)) { // Right click: Context menu contextAction?.Invoke(new Vector2(contextMenuRect.x, contextMenuRect.yMax)); e.Use(); } } } static void ShowHelpButton(Rect contextMenuRect, string documentationURL, GUIContent title) { if (string.IsNullOrEmpty(documentationURL)) return; var documentationRect = contextMenuRect; documentationRect.x -= 16 + 2; var documentationIcon = new GUIContent(CoreEditorStyles.iconHelp, $"Open Reference for {title.text}."); if (GUI.Button(documentationRect, documentationIcon, CoreEditorStyles.iconHelpStyle)) Help.BrowseURL(documentationURL); } static void OnContextClick(Vector2 position, Func hasMoreOptions, Action toggleMoreOptions) { var menu = new GenericMenu(); menu.AddItem(EditorGUIUtility.TrTextContent("Show Additional Properties"), hasMoreOptions.Invoke(), () => toggleMoreOptions.Invoke()); menu.AddItem(EditorGUIUtility.TrTextContent("Show All Additional Properties..."), false, () => CoreRenderPipelinePreferences.Open()); menu.DropDown(new Rect(position, Vector2.zero)); } static readonly GUIContent[][] k_DrawVector6_Label = { new[] { new GUIContent(" X"), new GUIContent(" Y"), new GUIContent(" Z"), }, new[] { new GUIContent("-X"), new GUIContent("-Y"), new GUIContent("-Z"), }, }; const int k_DrawVector6Slider_LabelSize = 60; const int k_DrawVector6Slider_FieldSize = 80; /// /// Draw a Vector6 field /// /// The label /// The data for +X, +Y and +Z /// The data for -X, -Y and -Z /// Min clamping value along axis /// Max clamping value along axis /// [Optional] Color marks to use /// [Optional] multiplicator on the datas /// [Optional] Allow the face positive values to be smaller than negative ones and vice versa public static void DrawVector6(GUIContent label, SerializedProperty positive, SerializedProperty negative, Vector3 min, Vector3 max, Color[] colors = null, SerializedProperty multiplicator = null, bool allowIntersection = true) { if (colors != null && (colors.Length != 6)) throw new System.ArgumentException("Colors must be a 6 element array. [+X, +Y, +X, -X, -Y, -Z]"); GUILayout.BeginVertical(); const int interline = 2; const int fixAlignSubVector3Labels = -1; Rect wholeArea = EditorGUILayout.GetControlRect(true, 2 * EditorGUIUtility.singleLineHeight + interline); Rect firstLineRect = wholeArea; firstLineRect.height = EditorGUIUtility.singleLineHeight; Rect secondLineRect = firstLineRect; secondLineRect.y += firstLineRect.height + interline; Rect labelRect = firstLineRect; labelRect.width = EditorGUIUtility.labelWidth; Rect firstVectorValueRect = firstLineRect; firstVectorValueRect.xMin += labelRect.width + fixAlignSubVector3Labels; EditorGUI.BeginProperty(wholeArea, label, positive); EditorGUI.BeginProperty(wholeArea, label, negative); { EditorGUI.LabelField(labelRect, label); } EditorGUI.EndProperty(); EditorGUI.EndProperty(); if (!allowIntersection) { max = negative.vector3Value; max.x = 1 - max.x; max.y = 1 - max.y; max.z = 1 - max.z; } DrawVector3(firstVectorValueRect, k_DrawVector6_Label[0], positive, min, max, false, colors == null ? null : new Color[] { colors[0], colors[1], colors[2] }, multiplicator); Rect secondVectorValueRect = secondLineRect; secondVectorValueRect.xMin = firstVectorValueRect.xMin; secondVectorValueRect.xMax = firstVectorValueRect.xMax; if (!allowIntersection) { max = positive.vector3Value; max.x = 1 - max.x; max.y = 1 - max.y; max.z = 1 - max.z; } DrawVector3(secondVectorValueRect, k_DrawVector6_Label[1], negative, min, max, true, colors == null ? null : new Color[] { colors[3], colors[4], colors[5] }, multiplicator); GUILayout.EndVertical(); } static void DrawVector3(Rect rect, GUIContent[] labels, SerializedProperty value, Vector3 min, Vector3 max, bool minusPrefix, Color[] colors, SerializedProperty multiplicator = null) { float[] multifloat = multiplicator == null ? new float[] { value.vector3Value.x, value.vector3Value.y, value.vector3Value.z } : new float[] { value.vector3Value.x * multiplicator.vector3Value.x, value.vector3Value.y * multiplicator.vector3Value.y, value.vector3Value.z * multiplicator.vector3Value.z }; float fieldWidth = rect.width / 3f; const int subLabelWidth = 13; const int colorWidth = 2; const int colorStartDecal = 1; const int valuesSeparator = 2; SerializedProperty[] values = new[] { value.FindPropertyRelative("x"), value.FindPropertyRelative("y"), value.FindPropertyRelative("z"), }; int oldIndentLevel = EditorGUI.indentLevel; EditorGUI.indentLevel = 0; float oldLabelWidth = EditorGUIUtility.labelWidth; EditorGUIUtility.labelWidth = subLabelWidth; for (int i = 0; i < 3; ++i) { Rect localRect = rect; localRect.xMin += i * fieldWidth;// + (i > 0 ? valuesSeparator : 0); localRect.xMax -= (2 - i) * fieldWidth + (i < 2 ? valuesSeparator : 0); Rect colorRect = localRect; colorRect.x = localRect.x + subLabelWidth + colorStartDecal; colorRect.width = colorWidth; colorRect.yMin += 2; colorRect.yMax -= 2; if (minusPrefix) { localRect.xMin -= 3; EditorGUIUtility.labelWidth = subLabelWidth + 3; } else EditorGUIUtility.labelWidth = subLabelWidth; if (multiplicator == null) { EditorGUI.BeginProperty(localRect, labels[i], values[i]); EditorGUI.BeginChangeCheck(); EditorGUI.PropertyField(localRect, values[i], labels[i]); if (EditorGUI.EndChangeCheck()) { values[i].floatValue = Mathf.Clamp(values[i].floatValue, min[i], max[i]); } EditorGUI.EndProperty(); } else { EditorGUI.BeginProperty(localRect, labels[i], values[i]); EditorGUI.BeginChangeCheck(); float localMultiplicator = multiplicator.vector3Value[i]; float multipliedValue = values[i].floatValue * localMultiplicator; multipliedValue = EditorGUI.FloatField(localRect, labels[i], multipliedValue); if (EditorGUI.EndChangeCheck()) { values[i].floatValue = Mathf.Clamp((localMultiplicator < -0.00001 || 0.00001 < localMultiplicator) ? multipliedValue / localMultiplicator : 0f, min[i], max[i]); } EditorGUI.EndProperty(); } EditorGUI.DrawRect(colorRect, colors[i]); } EditorGUIUtility.labelWidth = oldLabelWidth; EditorGUI.indentLevel = oldIndentLevel; } static void DrawVector3_(Rect rect, GUIContent[] labels, SerializedProperty value, Vector3 min, Vector3 max, bool addMinusPrefix, Color[] colors, SerializedProperty multiplicator = null) { float[] multifloat = multiplicator == null ? new float[] { value.vector3Value.x, value.vector3Value.y, value.vector3Value.z } : new float[] { value.vector3Value.x * multiplicator.vector3Value.x, value.vector3Value.y * multiplicator.vector3Value.y, value.vector3Value.z * multiplicator.vector3Value.z }; float fieldWidth = rect.width / 3f; EditorGUI.showMixedValue = value.hasMultipleDifferentValues; EditorGUI.BeginChangeCheck(); EditorGUI.MultiFloatField(rect, labels, multifloat); if (EditorGUI.EndChangeCheck()) { value.vector3Value = multiplicator == null ? new Vector3( Mathf.Clamp(multifloat[0], min.x, max.x), Mathf.Clamp(multifloat[1], min.y, max.y), Mathf.Clamp(multifloat[2], min.z, max.z) ) : new Vector3( Mathf.Clamp((multiplicator.vector3Value.x < -0.00001 || 0.00001 < multiplicator.vector3Value.x) ? multifloat[0] / multiplicator.vector3Value.x : 0f, min.x, max.x), Mathf.Clamp((multiplicator.vector3Value.y < -0.00001 || 0.00001 < multiplicator.vector3Value.y) ? multifloat[1] / multiplicator.vector3Value.y : 0f, min.y, max.y), Mathf.Clamp((multiplicator.vector3Value.z < -0.00001 || 0.00001 < multiplicator.vector3Value.z) ? multifloat[2] / multiplicator.vector3Value.z : 0f, min.z, max.z) ); } EditorGUI.showMixedValue = false; //Suffix is a hack as sublabel only work with 1 character if (addMinusPrefix) { Rect suffixRect = new Rect(rect.x - 4 - 15 * EditorGUI.indentLevel, rect.y, 100, rect.height); for (int i = 0; i < 3; ++i) { EditorGUI.LabelField(suffixRect, "-"); suffixRect.x += fieldWidth + .33f; } } //Color is a hack as nothing is done to handle this at the moment if (colors != null) { if (colors.Length != 3) throw new System.ArgumentException("colors must have 3 elements."); Rect suffixRect = new Rect(rect.x + 7 - 15 * EditorGUI.indentLevel, rect.y, 100, rect.height); GUIStyle colorMark = new GUIStyle(EditorStyles.label); colorMark.normal.textColor = colors[0]; EditorGUI.LabelField(suffixRect, "|", colorMark); suffixRect.x += 1; EditorGUI.LabelField(suffixRect, "|", colorMark); suffixRect.x += fieldWidth + 0.33f; colorMark.normal.textColor = colors[1]; EditorGUI.LabelField(suffixRect, "|", colorMark); suffixRect.x += 1; EditorGUI.LabelField(suffixRect, "|", colorMark); suffixRect.x += fieldWidth + .33f; colorMark.normal.textColor = colors[2]; EditorGUI.LabelField(suffixRect, "|", colorMark); suffixRect.x += 1; EditorGUI.LabelField(suffixRect, "|", colorMark); } } /// Draw a popup /// the label /// The data displayed /// Options of the dropdown public static void DrawPopup(GUIContent label, SerializedProperty property, string[] options) { var mode = property.intValue; EditorGUI.BeginChangeCheck(); if (mode >= options.Length) Debug.LogError(string.Format("Invalid option while trying to set {0}", label.text)); mode = EditorGUILayout.Popup(label, mode, options); if (EditorGUI.EndChangeCheck()) property.intValue = mode; } /// /// Draw an EnumPopup handling multiEdition /// /// The data displayed /// Type of the property /// The label public static void DrawEnumPopup(SerializedProperty property, System.Type type, GUIContent label = null) { EditorGUI.showMixedValue = property.hasMultipleDifferentValues; EditorGUI.BeginChangeCheck(); var name = System.Enum.GetName(type, property.intValue); var index = System.Array.FindIndex(System.Enum.GetNames(type), n => n == name); var input = (System.Enum)System.Enum.GetValues(type).GetValue(index); var rawResult = EditorGUILayout.EnumPopup(label ?? EditorGUIUtility.TrTextContent(ObjectNames.NicifyVariableName(property.name)), input); var result = ((System.IConvertible)rawResult).ToInt32(System.Globalization.CultureInfo.CurrentCulture); if (EditorGUI.EndChangeCheck()) property.intValue = result; EditorGUI.showMixedValue = false; } /// Remove the keywords on the given materials /// The material to edit public static void RemoveMaterialKeywords(Material material) => material.shaderKeywords = null; /// Get the AdditionalData of the given component /// The type of the AdditionalData component /// The object to seek for AdditionalData /// [Optional] The default value to use if there is no AdditionalData /// return an AdditionalData component public static T[] GetAdditionalData(UnityEngine.Object[] targets, Action initDefault = null) where T : Component { // Handles multi-selection var data = targets.Cast() .Select(t => t.GetComponent()) .ToArray(); for (int i = 0; i < data.Length; i++) { if (data[i] == null) { data[i] = Undo.AddComponent(((Component)targets[i]).gameObject); if (initDefault != null) { initDefault(data[i]); } } } return data; } /// Add the appropriate AdditionalData to the given GameObject and its children containing the original component /// The type of the original component /// The type of the AdditionalData component /// The root object to update /// [Optional] The default value to use if there is no AdditionalData public static void AddAdditionalData(GameObject go, Action initDefault = null) where T : Component where AdditionalT : Component { var components = go.GetComponentsInChildren(typeof(T), true); foreach (var c in components) { if (!c.TryGetComponent(out _)) { var hd = c.gameObject.AddComponent(); if (initDefault != null) initDefault(hd); } } } /// Create a game object /// The parent /// The wanted name (can be updated with a number if a sibling with same name exist /// Required component on this object in addition to Transform /// The created object public static GameObject CreateGameObject(GameObject parent, string name, params Type[] types) => ObjectFactory.CreateGameObject(GameObjectUtility.GetUniqueNameForSibling(parent != null ? parent.transform : null, name), types); /// /// Creates a new GameObject and set it's position to the current view /// /// the name of the new gameobject /// the parent of the gameobject /// the created GameObject public static GameObject CreateGameObject(string name, UnityEngine.Object context) { var parent = context as GameObject; var go = CoreEditorUtils.CreateGameObject(parent, name); GameObjectUtility.SetParentAndAlign(go, context as GameObject); Undo.RegisterCreatedObjectUndo(go, "Create " + go.name); Selection.activeObject = go; if (parent != null) go.transform.localPosition = Vector3.zero; else { if (EditorPrefs.GetBool("Create3DObject.PlaceAtWorldOrigin", false)) go.transform.localPosition = Vector3.zero; else EditorApplication.ExecuteMenuItem("GameObject/Move To View"); } return go; } /// Parse and return current project version /// The version static public string GetCurrentProjectVersion() { string[] readText = File.ReadAllLines("ProjectSettings/ProjectVersion.txt"); // format is m_EditorVersion: 2018.2.0b7 string[] versionText = readText[0].Split(' '); return versionText[1]; } /// /// /// static public void CheckOutFile(bool VCSEnabled, UnityObject mat) { if (VCSEnabled) { UnityEditor.VersionControl.Task task = UnityEditor.VersionControl.Provider.Checkout(mat, UnityEditor.VersionControl.CheckoutMode.Both); if (!task.success) { Debug.Log(task.text + " " + task.resultCode); } } } #region IconAndSkin internal enum Skin { Auto, Personnal, Professional, } static Func GetInternalSkinIndex; static Func GetGUIStatePixelsPerPoint; static Func GetTexturePixelPerPoint; static Action SetTexturePixelPerPoint; static void LoadSkinAndIconMethods() { var internalSkinIndexInfo = typeof(EditorGUIUtility).GetProperty("skinIndex", BindingFlags.NonPublic | BindingFlags.Static); var internalSkinIndexLambda = Expression.Lambda>(Expression.Property(null, internalSkinIndexInfo)); GetInternalSkinIndex = internalSkinIndexLambda.Compile(); var guiStatePixelsPerPointInfo = typeof(GUIUtility).GetProperty("pixelsPerPoint", BindingFlags.NonPublic | BindingFlags.Static); var guiStatePixelsPerPointLambda = Expression.Lambda>(Expression.Property(null, guiStatePixelsPerPointInfo)); GetGUIStatePixelsPerPoint = guiStatePixelsPerPointLambda.Compile(); var pixelPerPointParam = Expression.Parameter(typeof(float), "pixelPerPoint"); var texture2DProperty = Expression.Parameter(typeof(Texture2D), "texture2D"); var texture2DPixelsPerPointInfo = typeof(Texture2D).GetProperty("pixelsPerPoint", BindingFlags.NonPublic | BindingFlags.Instance); var texture2DPixelsPerPointProperty = Expression.Property(texture2DProperty, texture2DPixelsPerPointInfo); var texture2DGetPixelsPerPointLambda = Expression.Lambda>(texture2DPixelsPerPointProperty, texture2DProperty); GetTexturePixelPerPoint = texture2DGetPixelsPerPointLambda.Compile(); var texture2DSetPixelsPerPointLambda = Expression.Lambda>(Expression.Assign(texture2DPixelsPerPointProperty, pixelPerPointParam), texture2DProperty, pixelPerPointParam); SetTexturePixelPerPoint = texture2DSetPixelsPerPointLambda.Compile(); } /// Get the skin currently in use static Skin currentSkin => GetInternalSkinIndex() == 0 ? Skin.Personnal : Skin.Professional; // /!\ UIElement do not support well pixel per point at the moment. For this, use the hack forceLowRes /// /// Load an icon regarding skin and editor resolution. /// Icon should be stored as legacy icon resources: /// - "d_" prefix for Professional theme /// - "@2x" suffix for high resolution /// /// Path to seek the icon from Assets/ folder /// Icon name without suffix, prefix or extention /// [Optional] Extention of file (png per default) /// The loaded texture public static Texture2D LoadIcon(string path, string name, string extention = ".png") => LoadIcon(path, name, extention, false); //forceLowRes should be deprecated as soon as this is fixed in UIElement internal static Texture2D LoadIcon(string path, string name, string extention = ".png", bool forceLowRes = false) { if (string.IsNullOrEmpty(path) || string.IsNullOrEmpty(name)) return null; string prefix = ""; var skin = currentSkin; if (skin == Skin.Professional) prefix = "d_"; Texture2D icon = null; float pixelsPerPoint = GetGUIStatePixelsPerPoint(); if (pixelsPerPoint > 1.0f && !forceLowRes) { icon = EditorGUIUtility.Load($"{path}/{prefix}{name}@2x{extention}") as Texture2D; if (icon == null && !string.IsNullOrEmpty(prefix)) icon = EditorGUIUtility.Load($"{path}/{name}@2x{extention}") as Texture2D; if (icon != null) SetTexturePixelPerPoint(icon, 2.0f); } if (icon == null) icon = EditorGUIUtility.Load($"{path}/{prefix}{name}{extention}") as Texture2D; if (icon == null && !string.IsNullOrEmpty(prefix)) icon = EditorGUIUtility.Load($"{path}/{name}{extention}") as Texture2D; TryToFixFilterMode(pixelsPerPoint, icon); return icon; } internal static Texture2D FindTexture(string name) { float pixelsPerPoint = GetGUIStatePixelsPerPoint(); Texture2D icon = pixelsPerPoint > 1.0f ? EditorGUIUtility.FindTexture($"{name}@2x") : EditorGUIUtility.FindTexture(name); TryToFixFilterMode(pixelsPerPoint, icon); return icon; } internal static void TryToFixFilterMode(float pixelsPerPoint, Texture2D icon) { if (icon != null && !Mathf.Approximately(GetTexturePixelPerPoint(icon), pixelsPerPoint) && //scaling are different !Mathf.Approximately(pixelsPerPoint % 1, 0)) //screen scaling is non-integer { icon.filterMode = FilterMode.Bilinear; } } #endregion internal static void BeginAdditionalPropertiesHighlight(AnimFloat animation) { var oldColor = GUI.color; GUI.color = Color.Lerp(CoreEditorStyles.backgroundColor, CoreEditorStyles.backgroundHighlightColor, animation.value); EditorGUILayout.BeginVertical(CoreEditorStyles.additionalPropertiesHighlightStyle); GUI.color = oldColor; } internal static void EndAdditionalPropertiesHighlight() { EditorGUILayout.EndVertical(); } } }