381 lines
21 KiB
C#
381 lines
21 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using UnityEditor.Rendering;
|
|
using UnityEngine;
|
|
using UnityEngine.Rendering.HighDefinition;
|
|
using UnityEngine.Rendering;
|
|
using UnityEngine.Experimental.Rendering;
|
|
using System.Reflection;
|
|
using System.Linq;
|
|
|
|
namespace UnityEditor.Rendering.HighDefinition
|
|
{
|
|
internal struct OverridableFrameSettingsArea
|
|
{
|
|
static readonly GUIContent overrideTooltip = EditorGUIUtility.TrTextContent("", "Override this setting in component.");
|
|
static readonly Dictionary<FrameSettingsField, FrameSettingsFieldAttribute> attributes;
|
|
static Dictionary<int, IOrderedEnumerable<KeyValuePair<FrameSettingsField, FrameSettingsFieldAttribute>>> attributesGroup = new Dictionary<int, IOrderedEnumerable<KeyValuePair<FrameSettingsField, FrameSettingsFieldAttribute>>>();
|
|
|
|
/// <summary>Enumerates the keywords corresponding to frame settings properties.</summary>
|
|
internal static readonly string[] frameSettingsKeywords;
|
|
|
|
FrameSettings defaultFrameSettings;
|
|
SerializedFrameSettings serializedFrameSettings;
|
|
|
|
static OverridableFrameSettingsArea()
|
|
{
|
|
attributes = new Dictionary<FrameSettingsField, FrameSettingsFieldAttribute>();
|
|
attributesGroup = new Dictionary<int, IOrderedEnumerable<KeyValuePair<FrameSettingsField, FrameSettingsFieldAttribute>>>();
|
|
Dictionary<FrameSettingsField, string> frameSettingsEnumNameMap = FrameSettingsFieldAttribute.GetEnumNameMap();
|
|
Type type = typeof(FrameSettingsField);
|
|
foreach (FrameSettingsField enumVal in frameSettingsEnumNameMap.Keys)
|
|
{
|
|
attributes[enumVal] = type.GetField(frameSettingsEnumNameMap[enumVal]).GetCustomAttribute<FrameSettingsFieldAttribute>();
|
|
}
|
|
|
|
frameSettingsKeywords = attributes
|
|
.Values.Where(v => !string.IsNullOrEmpty(v?.displayedName))
|
|
.Select(v => v.displayedName?.ToLowerInvariant()).ToArray();
|
|
}
|
|
|
|
private struct Field
|
|
{
|
|
public FrameSettingsField field;
|
|
public Func<bool> overrideable;
|
|
public bool ignoreDependencies;
|
|
public Func<object> customGetter;
|
|
public Action<object> customSetter;
|
|
public object overridedDefaultValue;
|
|
/// <summary>
|
|
/// Use this field to force displaying mixed values in the UI.
|
|
///
|
|
/// By default the drawer will displayed mixed values if a bit has different values, but some frame settings
|
|
/// relies on other data, like material quality level. In that case, the other data may have mixed values
|
|
/// and we draw the UI accordingly.
|
|
/// </summary>
|
|
public bool hasMixedValues;
|
|
public GUIContent label => EditorGUIUtility.TrTextContent(attributes[field].displayedName, attributes[field].tooltip);
|
|
public bool IsOverrideableWithDependencies(SerializedFrameSettings serialized, FrameSettings defaultFrameSettings)
|
|
{
|
|
FrameSettingsFieldAttribute attribute = attributes[field];
|
|
bool locallyOverrideable = overrideable == null || overrideable();
|
|
FrameSettingsField[] dependencies = attribute.dependencies;
|
|
if (dependencies == null || ignoreDependencies || !locallyOverrideable)
|
|
return locallyOverrideable;
|
|
|
|
bool dependenciesOverrideable = true;
|
|
for (int index = dependencies.Length - 1; index >= 0 && dependenciesOverrideable; --index)
|
|
{
|
|
FrameSettingsField depency = dependencies[index];
|
|
dependenciesOverrideable &= EvaluateBoolWithOverride(depency, this, defaultFrameSettings, serialized, attribute.IsNegativeDependency(depency));
|
|
}
|
|
return dependenciesOverrideable;
|
|
}
|
|
}
|
|
private List<Field> fields;
|
|
|
|
public OverridableFrameSettingsArea(int capacity, FrameSettings defaultFrameSettings, SerializedFrameSettings serializedFrameSettings)
|
|
{
|
|
fields = new List<Field>(capacity);
|
|
this.defaultFrameSettings = defaultFrameSettings;
|
|
this.serializedFrameSettings = serializedFrameSettings;
|
|
}
|
|
|
|
public static OverridableFrameSettingsArea GetGroupContent(int groupIndex, FrameSettings defaultFrameSettings, SerializedFrameSettings serializedFrameSettings)
|
|
{
|
|
if (!attributesGroup.ContainsKey(groupIndex) || attributesGroup[groupIndex] == null)
|
|
attributesGroup[groupIndex] = attributes?.Where(pair => pair.Value?.group == groupIndex)?.OrderBy(pair => pair.Value.orderInGroup);
|
|
if (!attributesGroup.ContainsKey(groupIndex))
|
|
throw new ArgumentException("Unknown groupIndex");
|
|
|
|
var area = new OverridableFrameSettingsArea(attributesGroup[groupIndex].Count(), defaultFrameSettings, serializedFrameSettings);
|
|
foreach (var field in attributesGroup[groupIndex])
|
|
{
|
|
area.Add(field.Key);
|
|
}
|
|
return area;
|
|
}
|
|
|
|
public void AmmendInfo(FrameSettingsField field, Func<bool> overrideable = null, bool ignoreDependencies = false, Func<object> customGetter = null, Action<object> customSetter = null, object overridedDefaultValue = null, string labelOverride = null, bool hasMixedValues = false)
|
|
{
|
|
var matchIndex = fields.FindIndex(f => f.field == field);
|
|
|
|
if (matchIndex == -1)
|
|
throw new FrameSettingsNotFoundInGroupException("This FrameSettings' group do not contain this field. Be sure that the group parameter of the FrameSettingsFieldAttribute match this OverridableFrameSettingsArea groupIndex.");
|
|
|
|
var match = fields[matchIndex];
|
|
if (overrideable != null)
|
|
match.overrideable = overrideable;
|
|
match.ignoreDependencies = ignoreDependencies;
|
|
if (customGetter != null)
|
|
match.customGetter = customGetter;
|
|
if (customSetter != null)
|
|
match.customSetter = customSetter;
|
|
if (overridedDefaultValue != null)
|
|
match.overridedDefaultValue = overridedDefaultValue;
|
|
if (labelOverride != null)
|
|
match.label.text = labelOverride;
|
|
match.hasMixedValues = hasMixedValues;
|
|
fields[matchIndex] = match;
|
|
}
|
|
|
|
static bool EvaluateBoolWithOverride(FrameSettingsField field, Field forField, FrameSettings defaultFrameSettings, SerializedFrameSettings serializedFrameSettings, bool negative)
|
|
{
|
|
bool value;
|
|
if (serializedFrameSettings.GetOverrides(field))
|
|
value = serializedFrameSettings.IsEnabled(field) ?? false;
|
|
else
|
|
value = defaultFrameSettings.IsEnabled(field);
|
|
return value ^ negative;
|
|
}
|
|
|
|
/// <summary>Add an overrideable field to be draw when Draw(bool) will be called.</summary>
|
|
/// <param name="serializedFrameSettings">The overrideable property to draw in inspector</param>
|
|
/// <param name="field">The field drawn</param>
|
|
/// <param name="overrideable">The enabler will be used to check if this field could be overrided. If null or have a return value at true, it will be overrided.</param>
|
|
/// <param name="overridedDefaultValue">The value to display when the property is not overrided. If null, use the actual value of it.</param>
|
|
/// <param name="indent">Add this value number of indent when drawing this field.</param>
|
|
void Add(FrameSettingsField field, Func<bool> overrideable = null, Func<object> customGetter = null, Action<object> customSetter = null, object overridedDefaultValue = null)
|
|
=> fields.Add(new Field { field = field, overrideable = overrideable, overridedDefaultValue = overridedDefaultValue, customGetter = customGetter, customSetter = customSetter });
|
|
|
|
public void Draw(bool withOverride)
|
|
{
|
|
if (fields == null)
|
|
throw new ArgumentOutOfRangeException("Cannot be used without using the constructor with a capacity initializer.");
|
|
if (withOverride & GUI.enabled)
|
|
OverridesHeaders();
|
|
for (int i = 0; i < fields.Count; ++i)
|
|
DrawField(fields[i], withOverride);
|
|
}
|
|
|
|
void DrawField(Field field, bool withOverride)
|
|
{
|
|
int indentLevel = attributes[field.field].indentLevel;
|
|
if (indentLevel == 0)
|
|
--EditorGUI.indentLevel; //alignment provided by the space for override checkbox
|
|
else
|
|
{
|
|
for (int i = indentLevel - 1; i > 0; --i)
|
|
++EditorGUI.indentLevel;
|
|
}
|
|
bool enabled = field.IsOverrideableWithDependencies(serializedFrameSettings, defaultFrameSettings);
|
|
withOverride &= enabled & GUI.enabled;
|
|
bool shouldBeDisabled = withOverride || !enabled || !GUI.enabled;
|
|
|
|
const int k_IndentPerLevel = 15;
|
|
const int k_CheckBoxWidth = 15;
|
|
const int k_CheckboxLabelSeparator = 5;
|
|
const int k_LabelFieldSeparator = 2;
|
|
float indentValue = k_IndentPerLevel * EditorGUI.indentLevel;
|
|
|
|
Rect lineRect = GUILayoutUtility.GetRect(1, EditorGUIUtility.singleLineHeight);
|
|
Rect overrideRect = lineRect;
|
|
overrideRect.width = k_CheckBoxWidth;
|
|
Rect labelRect = lineRect;
|
|
labelRect.x += k_CheckBoxWidth + k_CheckboxLabelSeparator;
|
|
labelRect.width = EditorGUIUtility.labelWidth - indentValue;
|
|
Rect fieldRect = lineRect;
|
|
fieldRect.x = labelRect.xMax + k_LabelFieldSeparator;
|
|
fieldRect.width -= fieldRect.x - lineRect.x;
|
|
|
|
if (withOverride)
|
|
{
|
|
int currentIndent = EditorGUI.indentLevel;
|
|
EditorGUI.indentLevel = 0;
|
|
|
|
bool mixedValue = serializedFrameSettings.HaveMultipleOverride(field.field);
|
|
bool originalValue = serializedFrameSettings.GetOverrides(field.field) && !mixedValue;
|
|
overrideRect.yMin += 4f;
|
|
|
|
// MixedValueState is handled by style for small tickbox for strange reason
|
|
//EditorGUI.showMixedValue = mixedValue;
|
|
bool modifiedValue = EditorGUI.Toggle(overrideRect, overrideTooltip, originalValue, mixedValue ? CoreEditorStyles.smallMixedTickbox : CoreEditorStyles.smallTickbox);
|
|
//EditorGUI.showMixedValue = false;
|
|
|
|
if (originalValue ^ modifiedValue)
|
|
serializedFrameSettings.SetOverrides(field.field, modifiedValue);
|
|
|
|
shouldBeDisabled = !modifiedValue;
|
|
EditorGUI.indentLevel = currentIndent;
|
|
}
|
|
|
|
using (new SerializedFrameSettings.TitleDrawingScope(labelRect, field.label, serializedFrameSettings))
|
|
{
|
|
HDEditorUtils.HandlePrefixLabelWithIndent(lineRect, labelRect, field.label);
|
|
}
|
|
|
|
using (new EditorGUI.DisabledScope(shouldBeDisabled))
|
|
{
|
|
EditorGUI.showMixedValue = serializedFrameSettings.HaveMultipleValue(field.field) || field.hasMixedValues;
|
|
using (new EditorGUILayout.VerticalScope())
|
|
{
|
|
//the following block will display a default value if provided instead of actual value (case if(true))
|
|
if (shouldBeDisabled)
|
|
{
|
|
if (field.overridedDefaultValue == null)
|
|
{
|
|
switch (attributes[field.field].type)
|
|
{
|
|
case FrameSettingsFieldAttribute.DisplayType.BoolAsCheckbox:
|
|
DrawFieldShape(fieldRect, defaultFrameSettings.IsEnabled(field.field));
|
|
break;
|
|
case FrameSettingsFieldAttribute.DisplayType.BoolAsEnumPopup:
|
|
//shame but it is not possible to use Convert.ChangeType to convert int into enum in current C#
|
|
//rely on string parsing for the moment
|
|
var oldEnumValue = Enum.Parse(attributes[field.field].targetType, defaultFrameSettings.IsEnabled(field.field) ? "1" : "0");
|
|
DrawFieldShape(fieldRect, oldEnumValue);
|
|
break;
|
|
case FrameSettingsFieldAttribute.DisplayType.Others:
|
|
var oldValue = field.customGetter();
|
|
DrawFieldShape(fieldRect, oldValue);
|
|
break;
|
|
default:
|
|
throw new ArgumentException("Unknown FrameSettingsFieldAttribute");
|
|
}
|
|
}
|
|
else
|
|
DrawFieldShape(fieldRect, field.overridedDefaultValue);
|
|
}
|
|
else
|
|
{
|
|
switch (attributes[field.field].type)
|
|
{
|
|
case FrameSettingsFieldAttribute.DisplayType.BoolAsCheckbox:
|
|
bool oldBool = serializedFrameSettings.IsEnabled(field.field) ?? false;
|
|
bool newBool = (bool)DrawFieldShape(fieldRect, oldBool);
|
|
if (oldBool ^ newBool)
|
|
{
|
|
Undo.RecordObject(serializedFrameSettings.serializedObject.targetObject, "Changed FrameSettings " + field.field);
|
|
serializedFrameSettings.SetEnabled(field.field, newBool);
|
|
}
|
|
break;
|
|
case FrameSettingsFieldAttribute.DisplayType.BoolAsEnumPopup:
|
|
//shame but it is not possible to use Convert.ChangeType to convert int into enum in current C#
|
|
//Also, Enum.Equals and Enum operator!= always send true here. As it seams to compare object reference instead of value.
|
|
var oldBoolValue = serializedFrameSettings.IsEnabled(field.field);
|
|
int oldEnumIntValue = -1;
|
|
int newEnumIntValue;
|
|
object newEnumValue;
|
|
if (oldBoolValue.HasValue)
|
|
{
|
|
var oldEnumValue = Enum.GetValues(attributes[field.field].targetType).GetValue(oldBoolValue.Value ? 1 : 0);
|
|
newEnumValue = Convert.ChangeType(DrawFieldShape(fieldRect, oldEnumValue), attributes[field.field].targetType);
|
|
oldEnumIntValue = ((IConvertible)oldEnumValue).ToInt32(System.Globalization.CultureInfo.CurrentCulture);
|
|
newEnumIntValue = ((IConvertible)newEnumValue).ToInt32(System.Globalization.CultureInfo.CurrentCulture);
|
|
}
|
|
else //in multi edition, do not assume any previous value
|
|
{
|
|
newEnumIntValue = EditorGUI.Popup(fieldRect, -1, Enum.GetNames(attributes[field.field].targetType));
|
|
newEnumValue = newEnumIntValue < 0 ? null : Enum.GetValues(attributes[field.field].targetType).GetValue(newEnumIntValue);
|
|
}
|
|
if (oldEnumIntValue != newEnumIntValue)
|
|
{
|
|
Undo.RecordObject(serializedFrameSettings.serializedObject.targetObject, "Changed FrameSettings " + field.field);
|
|
serializedFrameSettings.SetEnabled(field.field, Convert.ToInt32(newEnumValue) == 1);
|
|
}
|
|
break;
|
|
case FrameSettingsFieldAttribute.DisplayType.Others:
|
|
var oldValue = field.customGetter();
|
|
EditorGUI.BeginChangeCheck();
|
|
var newValue = DrawFieldShape(fieldRect, oldValue);
|
|
// We need an extensive check here, otherwise in some case with boxing or polymorphism
|
|
// the != operator won't be accurate. (This is the case for enum types).
|
|
var valuesAreEquals = oldValue == null && newValue == null || oldValue != null && oldValue.Equals(newValue);
|
|
// If the UI reported a change, we also assign values.
|
|
// When assigning to a multiple selection, the equals check may fail while there was indeed a change.
|
|
if (EditorGUI.EndChangeCheck() || !valuesAreEquals)
|
|
{
|
|
Undo.RecordObject(serializedFrameSettings.serializedObject.targetObject, "Changed FrameSettings " + field.field);
|
|
field.customSetter(newValue);
|
|
}
|
|
break;
|
|
default:
|
|
throw new ArgumentException("Unknown FrameSettingsFieldAttribute");
|
|
}
|
|
}
|
|
}
|
|
EditorGUI.showMixedValue = false;
|
|
}
|
|
|
|
if (indentLevel == 0)
|
|
{
|
|
++EditorGUI.indentLevel;
|
|
}
|
|
else
|
|
{
|
|
for (int i = indentLevel - 1; i > 0; --i)
|
|
{
|
|
--EditorGUI.indentLevel;
|
|
}
|
|
}
|
|
}
|
|
|
|
object DrawFieldShape(Rect rect, object field)
|
|
{
|
|
if (field is GUIContent)
|
|
{
|
|
EditorGUI.LabelField(rect, (GUIContent)field);
|
|
return null;
|
|
}
|
|
else if (field is string)
|
|
return EditorGUI.TextField(rect, (string)field);
|
|
else if (field is bool)
|
|
return EditorGUI.Toggle(rect, (bool)field);
|
|
else if (field is int)
|
|
return EditorGUI.IntField(rect, (int)field);
|
|
else if (field is float)
|
|
return EditorGUI.FloatField(rect, (float)field);
|
|
else if (field is Color)
|
|
return EditorGUI.ColorField(rect, (Color)field);
|
|
else if (field is Enum)
|
|
return EditorGUI.EnumPopup(rect, (Enum)field);
|
|
else if (field is LayerMask)
|
|
return EditorGUI.MaskField(rect, (LayerMask)field, GraphicsSettings.currentRenderPipeline.renderingLayerMaskNames);
|
|
else if (field is UnityEngine.Object)
|
|
return EditorGUI.ObjectField(rect, (UnityEngine.Object)field, field.GetType(), true);
|
|
else if (field is SerializedProperty)
|
|
return EditorGUI.PropertyField(rect, (SerializedProperty)field, includeChildren: true);
|
|
else
|
|
{
|
|
EditorGUI.LabelField(rect, new GUIContent("Unsupported type"));
|
|
Debug.LogError("Unsupported format " + field.GetType() + " in OverridableSettingsArea.cs. Please add it!");
|
|
return null;
|
|
}
|
|
}
|
|
|
|
void OverridesHeaders()
|
|
{
|
|
using (new EditorGUILayout.HorizontalScope())
|
|
{
|
|
GUILayoutUtility.GetRect(0f, 17f, GUILayout.ExpandWidth(false));
|
|
if (GUILayout.Button(EditorGUIUtility.TrTextContent("All", "Toggle all overrides on. To maximize performances you should only toggle overrides that you actually need."), CoreEditorStyles.miniLabelButton, GUILayout.Width(17f), GUILayout.ExpandWidth(false)))
|
|
{
|
|
foreach (var field in fields)
|
|
{
|
|
if (field.IsOverrideableWithDependencies(serializedFrameSettings, defaultFrameSettings))
|
|
serializedFrameSettings.SetOverrides(field.field, true);
|
|
}
|
|
}
|
|
|
|
if (GUILayout.Button(EditorGUIUtility.TrTextContent("None", "Toggle all overrides off."), CoreEditorStyles.miniLabelButton, GUILayout.Width(32f), GUILayout.ExpandWidth(false)))
|
|
{
|
|
foreach (var field in fields)
|
|
{
|
|
if (field.IsOverrideableWithDependencies(serializedFrameSettings, defaultFrameSettings))
|
|
serializedFrameSettings.SetOverrides(field.field, false);
|
|
}
|
|
}
|
|
|
|
GUILayout.FlexibleSpace();
|
|
}
|
|
}
|
|
}
|
|
|
|
class FrameSettingsNotFoundInGroupException : Exception
|
|
{
|
|
public FrameSettingsNotFoundInGroupException(string message)
|
|
: base(message)
|
|
{}
|
|
}
|
|
}
|