You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 

950 lines
37 KiB

using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using UnityEditor.AnimatedValues;
using UnityEditor.Callbacks;
using UnityEngine;
using UnityEngine.Assertions;
using UnityEngine.Rendering;
namespace UnityEditor.Rendering
{
/// <summary>
/// A custom editor class that draws a <see cref="VolumeComponent"/> in the Inspector. If you do not
/// provide a custom editor for a <see cref="VolumeComponent"/>, Unity uses the default one.
/// You must use a <see cref="CustomEditor"/> to let the editor know which
/// component this drawer is for.
/// </summary>
/// <example>
/// <para>Below is an example of a custom <see cref="VolumeComponent"/>:</para>
/// <code>
/// using UnityEngine.Rendering;
///
/// [Serializable, VolumeComponentMenu("Custom/Example Component")]
/// public class ExampleComponent : VolumeComponent
/// {
/// public ClampedFloatParameter intensity = new ClampedFloatParameter(0f, 0f, 1f);
/// }
/// </code>
/// <para>And its associated editor:</para>
/// <code>
/// using UnityEditor.Rendering;
///
/// [CustomEditor(typeof(ExampleComponent))]
/// class ExampleComponentEditor : VolumeComponentEditor
/// {
/// SerializedDataParameter m_Intensity;
///
/// public override void OnEnable()
/// {
/// var o = new PropertyFetcher&lt;ExampleComponent&gt;(serializedObject);
/// m_Intensity = Unpack(o.Find(x => x.intensity));
/// }
///
/// public override void OnInspectorGUI()
/// {
/// PropertyField(m_Intensity);
/// }
/// }
/// </code>
/// </example>
/// <seealso cref="CustomEditor"/>
[CustomEditor(typeof(VolumeComponent), true)]
public class VolumeComponentEditor : Editor
{
const string k_KeyPrefix = "CoreRP:VolumeComponent:UI_State:";
EditorPrefBool m_EditorPrefBool;
internal string categoryTitle { get; set; }
/// <summary>
/// If the editor for this <see cref="VolumeComponent"/> is expanded or not in the inspector
/// </summary>
public bool expanded
{
get => m_EditorPrefBool.value;
set => m_EditorPrefBool.value = value;
}
internal bool visible { get; private set; }
static class Styles
{
public static readonly GUIContent k_OverrideSettingText = EditorGUIUtility.TrTextContent("", "Override this setting for this volume.");
public static readonly GUIContent k_AllText =
EditorGUIUtility.TrTextContent("ALL", "Toggle all overrides on. To maximize performances you should only toggle overrides that you actually need.");
public static readonly GUIContent k_NoneText = EditorGUIUtility.TrTextContent("NONE", "Toggle all overrides off.");
public static string toggleAllText { get; } = L10n.Tr("Toggle All");
public const int overrideCheckboxWidth = 14;
public const int overrideCheckboxOffset = 9;
}
Vector2? m_OverrideToggleSize;
internal Vector2 overrideToggleSize
{
get
{
if (!m_OverrideToggleSize.HasValue)
m_OverrideToggleSize = CoreEditorStyles.smallTickbox.CalcSize(Styles.k_OverrideSettingText);
return m_OverrideToggleSize.Value;
}
}
/// <summary>
/// Specifies the <see cref="VolumeComponent"/> this editor is drawing.
/// </summary>
public VolumeComponent volumeComponent => target as VolumeComponent;
/// <summary>
/// The copy of the serialized property of the <see cref="VolumeComponent"/> being
/// inspected. Unity uses this to track whether the editor is collapsed in the Inspector or not.
/// </summary>
[Obsolete("Please use expanded property instead. #from(2022.2)", false)]
public SerializedProperty baseProperty { get; internal set; }
/// <summary>
/// The serialized property of <see cref="VolumeComponent.active"/> for the component being
/// inspected.
/// </summary>
public SerializedProperty activeProperty { get; internal set; }
#region Additional Properties
AnimFloat m_AdditionalPropertiesAnimation;
EditorPrefBool m_ShowAdditionalProperties;
List<VolumeParameter> m_VolumeNotAdditionalParameters = new List<VolumeParameter>();
/// <summary>
/// Override this property if your editor makes use of the "Additional Properties" feature.
/// </summary>
public virtual bool hasAdditionalProperties => volumeComponent.parameterList.Count != m_VolumeNotAdditionalParameters.Count;
/// <summary>
/// Set to true to show additional properties.
/// </summary>
public bool showAdditionalProperties
{
get => m_ShowAdditionalProperties.value;
set
{
if (value && !m_ShowAdditionalProperties.value)
{
m_AdditionalPropertiesAnimation.value = 1.0f;
m_AdditionalPropertiesAnimation.target = 0.0f;
}
SetAdditionalPropertiesPreference(value);
}
}
/// <summary>
/// Start a scope for additional properties.
/// This will handle the highlight of the background when toggled on and off.
/// </summary>
/// <returns>True if the additional content should be drawn.</returns>
protected bool BeginAdditionalPropertiesScope()
{
if (hasAdditionalProperties && showAdditionalProperties)
{
CoreEditorUtils.BeginAdditionalPropertiesHighlight(m_AdditionalPropertiesAnimation);
return true;
}
else
{
return false;
}
}
/// <summary>
/// End a scope for additional properties.
/// </summary>
protected void EndAdditionalPropertiesScope()
{
if (hasAdditionalProperties && showAdditionalProperties)
{
CoreEditorUtils.EndAdditionalPropertiesHighlight();
}
}
#endregion
/// <summary>
/// A reference to the parent editor in the Inspector.
/// </summary>
protected Editor m_Inspector;
/// <summary>
/// A reference to the parent editor in the Inspector.
/// </summary>
internal Editor inspector
{
get => m_Inspector;
set => m_Inspector = value;
}
internal void SetVolume(Volume v)
{
volume = v;
}
/// <summary>
/// Obtains the <see cref="Volume"/> that is being edited if editing a scene volume, otherwise null.
/// </summary>
protected Volume volume { get; private set; }
internal void SetVolumeProfile(VolumeProfile p)
{
volumeProfile = p;
}
/// <summary>
/// Obtains the <see cref="VolumeProfile"/> that is being edited.
/// </summary>
VolumeProfile volumeProfile { get; set; }
List<(GUIContent displayName, int displayOrder, SerializedDataParameter param)> m_Parameters;
static Dictionary<Type, VolumeParameterDrawer> s_ParameterDrawers;
SupportedOnRenderPipelineAttribute m_SupportedOnRenderPipelineAttribute;
Type[] m_LegacyPipelineTypes;
static VolumeComponentEditor()
{
s_ParameterDrawers = new Dictionary<Type, VolumeParameterDrawer>();
ReloadDecoratorTypes();
}
[DidReloadScripts]
static void OnEditorReload()
{
ReloadDecoratorTypes();
}
static void ReloadDecoratorTypes()
{
s_ParameterDrawers.Clear();
foreach (var type in TypeCache.GetTypesDerivedFrom<VolumeParameterDrawer>())
{
if (type.IsAbstract)
continue;
var attr = type.GetCustomAttribute<VolumeParameterDrawerAttribute>(false);
if (attr == null)
{
Debug.LogWarning($"{type} is missing the attribute {nameof(VolumeParameterDrawerAttribute)}");
continue;
}
s_ParameterDrawers.Add(attr.parameterType, Activator.CreateInstance(type) as VolumeParameterDrawer);
}
}
/// <summary>
/// Triggers an Inspector repaint event.
/// </summary>
public new void Repaint()
{
// Volume Component Editors can be shown in the Graphics Settings window (default volume profile)
// This will force a repaint of the whole window, otherwise, additional properties highlight animation does not work properly.
SettingsService.RepaintAllSettingsWindow();
base.Repaint();
}
internal static string GetAdditionalPropertiesPreferenceKey(Type type)
{
return $"UI_Show_Additional_Properties_{type}";
}
internal void InitAdditionalPropertiesPreference()
{
string key = GetAdditionalPropertiesPreferenceKey(GetType());
m_ShowAdditionalProperties = new EditorPrefBool(key);
}
internal void SetAdditionalPropertiesPreference(bool value)
{
m_ShowAdditionalProperties.value = value;
}
internal void Init()
{
activeProperty = serializedObject.FindProperty("active");
string inspectorKey = string.Empty;
bool expandedByDefault = true;
if (!enableOverrides)
{
inspectorKey += "default"; // Ensures the default VolumeProfile editor doesn't share expander state with other editors
expandedByDefault = false;
}
m_EditorPrefBool = new EditorPrefBool(k_KeyPrefix + inspectorKey + volumeComponent.GetType().Name, expandedByDefault);
InitAdditionalPropertiesPreference();
m_AdditionalPropertiesAnimation = new AnimFloat(0, Repaint)
{
speed = CoreEditorConstants.additionalPropertiesHightLightSpeed
};
InitParameters();
OnEnable();
var volumeComponentType = volumeComponent.GetType();
m_SupportedOnRenderPipelineAttribute = volumeComponentType.GetCustomAttribute<SupportedOnRenderPipelineAttribute>();
#pragma warning disable CS0618
var supportedOn = volumeComponentType.GetCustomAttribute<VolumeComponentMenuForRenderPipeline>();
m_LegacyPipelineTypes = supportedOn != null ? supportedOn.pipelineTypes : Array.Empty<Type>();
#pragma warning restore CS0618
EditorApplication.contextualPropertyMenu += OnPropertyContextMenu;
}
void OnDestroy()
{
EditorApplication.contextualPropertyMenu -= OnPropertyContextMenu;
}
internal void DetermineVisibility(Type renderPipelineAssetType, Type renderPipelineType)
{
if (renderPipelineAssetType == null)
{
visible = false;
return;
}
if (m_SupportedOnRenderPipelineAttribute != null)
{
visible = m_SupportedOnRenderPipelineAttribute.GetSupportedMode(renderPipelineAssetType) != SupportedOnRenderPipelineAttribute.SupportedMode.Unsupported;
return;
}
if (renderPipelineType != null && m_LegacyPipelineTypes.Length > 0)
{
visible = m_LegacyPipelineTypes.Contains(renderPipelineType);
return;
}
visible = true;
}
void InitParameters()
{
VolumeComponent.FindParameters(target, m_VolumeNotAdditionalParameters, field => field.GetCustomAttribute<AdditionalPropertyAttribute>() == null);
}
void GetFields(object o, List<(FieldInfo, SerializedProperty)> infos, SerializedProperty prop = null)
{
if (o == null)
return;
var fields = o.GetType()
.GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
foreach (var field in fields)
{
if (field.FieldType.IsSubclassOf(typeof(VolumeParameter)))
{
if ((field.GetCustomAttributes(typeof(HideInInspector), false).Length == 0) &&
((field.GetCustomAttributes(typeof(SerializeField), false).Length > 0) ||
(field.IsPublic && field.GetCustomAttributes(typeof(NonSerializedAttribute), false).Length == 0)))
infos.Add((field, prop == null ? serializedObject.FindProperty(field.Name) : prop.FindPropertyRelative(field.Name)));
}
else if (!field.FieldType.IsArray && field.FieldType.IsClass)
GetFields(field.GetValue(o), infos, prop == null ? serializedObject.FindProperty(field.Name) : prop.FindPropertyRelative(field.Name));
}
}
/// <summary>
/// Unity calls this method when the object loads.
/// </summary>
/// <remarks>
/// You can safely override this method and not call <c>base.OnEnable()</c> unless you want
/// Unity to display all the properties from the <see cref="VolumeComponent"/> automatically.
/// </remarks>
public virtual void OnEnable()
{
// Grab all valid serializable field on the VolumeComponent
// TODO: Should only be done when needed / on demand as this can potentially be wasted CPU when a custom editor is in use
var fields = new List<(FieldInfo, SerializedProperty)>();
GetFields(target, fields);
m_Parameters = fields
.Select(t =>
{
var name = "";
var order = 0;
var (fieldInfo, serializedProperty) = t;
var attr = (DisplayInfoAttribute[])fieldInfo.GetCustomAttributes(typeof(DisplayInfoAttribute), true);
if (attr.Length != 0)
{
name = attr[0].name;
order = attr[0].order;
}
var parameter = new SerializedDataParameter(t.Item2);
return (EditorGUIUtility.TrTextContent(name), order, parameter);
})
.OrderBy(t => t.order)
.ToList();
}
/// <summary>
/// Unity calls this method when the object goes out of scope.
/// </summary>
public virtual void OnDisable()
{
}
internal void AddDefaultProfileContextMenuEntries(
GenericMenu menu,
VolumeProfile defaultProfile,
GenericMenu.MenuFunction copyAction)
{
// Host can be either VolumeProfileEditor or VolumeEditor
var profile = volume
? volume.HasInstantiatedProfile() ? volume.profile : volume.sharedProfile
: volumeProfile;
if (defaultProfile != null &&
profile != null &&
defaultProfile != profile)
{
menu.AddItem(EditorGUIUtility.TrTextContent($"Show Default Volume Profile"), false,
() => Selection.activeObject = defaultProfile);
menu.AddItem(EditorGUIUtility.TrTextContent($"Apply Values to Default Volume Profile"), false, copyAction);
}
}
void OnPropertyContextMenu(GenericMenu menu, SerializedProperty property)
{
if (property.serializedObject.targetObject != target)
return;
var targetComponent = property.serializedObject.targetObject as VolumeComponent;
AddDefaultProfileContextMenuEntries(menu, VolumeManager.instance.globalDefaultProfile,
() => VolumeProfileUtils.AssignValuesToProfile(VolumeManager.instance.globalDefaultProfile, targetComponent, property));
}
/// <summary>
/// Unity calls this method after drawing the header for each VolumeComponentEditor
/// </summary>
protected virtual void OnBeforeInspectorGUI()
{
}
internal bool OnInternalInspectorGUI()
{
if (serializedObject == null || serializedObject.targetObject == null)
return false;
serializedObject.Update();
using (new EditorGUILayout.VerticalScope())
{
OnBeforeInspectorGUI();
if (enableOverrides)
TopRowFields();
else
GUILayout.Space(4);
OnInspectorGUI();
EditorGUILayout.Space();
}
return serializedObject.ApplyModifiedProperties();
}
/// <summary>
/// Unity calls this method each time it re-draws the Inspector.
/// </summary>
/// <remarks>
/// You can safely override this method and not call <c>base.OnInspectorGUI()</c> unless you
/// want Unity to display all the properties from the <see cref="VolumeComponent"/>
/// automatically.
/// </remarks>
public override void OnInspectorGUI()
{
// Display every field as-is
foreach (var parameter in m_Parameters)
{
if (!string.IsNullOrEmpty(parameter.displayName.text))
PropertyField(parameter.param, parameter.displayName);
else
PropertyField(parameter.param);
}
}
/// <summary>
/// Sets the label for the component header. Override this method to provide
/// a custom label. If you don't, Unity automatically obtains one from the class name.
/// </summary>
/// <returns>A label to display in the component header.</returns>
public virtual GUIContent GetDisplayTitle()
{
var title = string.IsNullOrEmpty(volumeComponent.displayName) ? ObjectNames.NicifyVariableName(volumeComponent.GetType().Name) : volumeComponent.displayName;
return EditorGUIUtility.TrTextContent(title, string.Empty);
}
void AddToggleState(GUIContent content, bool state)
{
bool allOverridesSameState = AreOverridesTo(state);
if (GUILayout.Toggle(allOverridesSameState, content, CoreEditorStyles.miniLabelButton, GUILayout.ExpandWidth(false)) && !allOverridesSameState)
SetOverridesTo(state);
}
void TopRowFields()
{
using (new EditorGUILayout.HorizontalScope())
{
AddToggleState(Styles.k_AllText, true);
AddToggleState(Styles.k_NoneText, false);
}
}
/// <summary>
/// Checks if all the visible parameters have the given state
/// </summary>
/// <param name="state">The state to check</param>
internal bool AreOverridesTo(bool state)
{
if (hasAdditionalProperties && showAdditionalProperties)
return AreAllOverridesTo(state);
for (int i = 0; i < m_VolumeNotAdditionalParameters.Count; ++i)
{
if (m_VolumeNotAdditionalParameters[i].overrideState != state)
return false;
}
return true;
}
/// <summary>
/// Sets the given state to all the visible parameters
/// </summary>
/// <param name="state">The state to check</param>
internal void SetOverridesTo(bool state)
{
if (hasAdditionalProperties && showAdditionalProperties)
SetAllOverridesTo(state);
else
{
Undo.RecordObject(target, Styles.toggleAllText);
volumeComponent.SetOverridesTo(m_VolumeNotAdditionalParameters, state);
serializedObject.Update();
}
}
internal bool AreAllOverridesTo(bool state)
{
for (int i = 0; i < volumeComponent.parameterList.Count; ++i)
{
if (volumeComponent.parameterList[i].overrideState != state)
return false;
}
return true;
}
internal void SetAllOverridesTo(bool state)
{
Undo.RecordObject(target, Styles.toggleAllText);
volumeComponent.SetAllOverridesTo(state);
serializedObject.Update();
}
/// <summary>
/// Generates and auto-populates a <see cref="SerializedDataParameter"/> from a serialized
/// <see cref="VolumeParameter{T}"/>.
/// </summary>
/// <param name="property">A serialized property holding a <see cref="VolumeParameter{T}"/>
/// </param>
/// <returns>A <see cref="SerializedDataParameter"/> that encapsulates the provided serialized property.</returns>
protected SerializedDataParameter Unpack(SerializedProperty property)
{
Assert.IsNotNull(property);
return new SerializedDataParameter(property);
}
/// <summary>
/// Draws a given <see cref="SerializedDataParameter"/> in the editor.
/// </summary>
/// <param name="property">The property to draw in the editor</param>
/// <returns>true if the property field has been rendered</returns>
protected bool PropertyField(SerializedDataParameter property)
{
var title = EditorGUIUtility.TrTextContent(property.displayName,
property.GetAttribute<TooltipAttribute>()?.tooltip); // avoid property from getting the tooltip of another one with the same name
return PropertyField(property, title);
}
static readonly Dictionary<string, GUIContent> s_HeadersGuiContents = new Dictionary<string, GUIContent>();
/// <summary>
/// Draws a header into the inspector with the given title
/// </summary>
/// <param name="header">The title for the header</param>
protected void DrawHeader(string header)
{
if (!s_HeadersGuiContents.TryGetValue(header, out GUIContent content))
{
content = EditorGUIUtility.TrTextContent(header);
s_HeadersGuiContents.Add(header, content);
}
EditorGUILayout.Space(4);
var rect = EditorGUILayout.GetControlRect(false, EditorGUIUtility.singleLineHeight);
EditorGUI.LabelField(rect, content, EditorStyles.boldLabel);
}
/// <summary>
/// Handles unity built-in decorators (Space, Header, Tooltips, ...) from <see cref="SerializedDataParameter"/> attributes
/// </summary>
/// <param name="property">The property to obtain the attributes and handle the decorators</param>
/// <param name="title">A custom label and/or tooltip that might be updated by <see cref="TooltipAttribute"/> and/or by <see cref="InspectorNameAttribute"/></param>
internal void HandleDecorators(SerializedDataParameter property, GUIContent title)
{
foreach (var attr in property.attributes)
{
if (!(attr is PropertyAttribute))
continue;
switch (attr)
{
case SpaceAttribute spaceAttribute:
EditorGUILayout.GetControlRect(false, spaceAttribute.height);
break;
case HeaderAttribute headerAttribute:
DrawHeader(headerAttribute.header);
break;
case TooltipAttribute tooltipAttribute:
if (string.IsNullOrEmpty(title.tooltip))
title.tooltip = tooltipAttribute.tooltip;
break;
case InspectorNameAttribute inspectorNameAttribute:
title.text = inspectorNameAttribute.displayName;
break;
}
}
}
/// <summary>
/// Get indentation from Indent attribute
/// </summary>
/// <param name="property">The property to obtain the attributes</param>
/// <returns>The relative indent level change</returns>
int HandleRelativeIndentation(SerializedDataParameter property)
{
foreach (var attr in property.attributes)
{
if (attr is VolumeComponent.Indent indent)
return indent.relativeAmount;
}
return 0;
}
/// <summary>
/// Draws a given <see cref="SerializedDataParameter"/> in the editor using a custom label
/// and tooltip.
/// </summary>
/// <param name="property">The property to draw in the editor.</param>
/// <param name="title">A custom label and/or tooltip.</param>
/// <returns>true if the property field has been rendered</returns>
protected bool PropertyField(SerializedDataParameter property, GUIContent title)
{
if (VolumeParameter.IsObjectParameter(property.referenceType))
return DrawEmbeddedField(property, title);
else
return DrawPropertyField(property, title);
}
/// <summary>
/// Draws a given <see cref="SerializedDataParameter"/> in the editor using a custom label
/// and tooltip.
/// </summary>
/// <param name="property">The property to draw in the editor.</param>
/// <param name="title">A custom label and/or tooltip.</param>
private bool DrawPropertyField(SerializedDataParameter property, GUIContent title)
{
using (var scope = new OverridablePropertyScope(property, title, this))
{
if (!scope.displayed)
return false;
// Custom drawer
if (scope.drawer?.OnGUI(property, title) ?? false)
return true;
// Standard Unity drawer
EditorGUILayout.PropertyField(property.value, title);
}
return true;
}
/// <summary>
/// Draws a given <see cref="SerializedDataParameter"/> in the editor using a custom label
/// and tooltip. This variant is only for embedded class / struct
/// </summary>
/// <param name="property">The property to draw in the editor.</param>
/// <param name="title">A custom label and/or tooltip.</param>
private bool DrawEmbeddedField(SerializedDataParameter property, GUIContent title)
{
bool isAdditionalProperty = property.GetAttribute<AdditionalPropertyAttribute>() != null;
bool displayed = !isAdditionalProperty || BeginAdditionalPropertiesScope();
if (!displayed)
return false;
// Custom parameter drawer
s_ParameterDrawers.TryGetValue(property.referenceType, out VolumeParameterDrawer drawer);
if (drawer != null && !drawer.IsAutoProperty())
if (drawer.OnGUI(property, title))
{
if (isAdditionalProperty)
EndAdditionalPropertiesScope();
return true;
}
// Standard Unity drawer
using (new IndentLevelScope())
{
bool expanded = property?.value?.isExpanded ?? true;
expanded = EditorGUILayout.Foldout(expanded, title, true);
if (expanded)
{
// Not the fastest way to do it but that'll do just fine for now
var it = property.value.Copy();
var end = it.GetEndProperty();
bool first = true;
while (it.Next(first) && !SerializedProperty.EqualContents(it, end))
{
PropertyField(Unpack(it));
first = false;
}
}
property.value.isExpanded = expanded;
}
if (isAdditionalProperty)
EndAdditionalPropertiesScope();
return true;
}
/// <summary>
/// Draw a Color Field but convert the color to gamma space before displaying it in the shader.
/// Using SetColor on a material does the conversion, but setting the color as vector3 in a constant buffer doesn't
/// So we have to do it manually, doing it in the UI avoids having to do a migration step for existing fields
/// </summary>
/// <param name="property">The color property</param>
protected void ColorFieldLinear(SerializedDataParameter property)
{
var title = EditorGUIUtility.TrTextContent(property.displayName,
property.GetAttribute<TooltipAttribute>()?.tooltip);
using (var scope = new OverridablePropertyScope(property, title, this))
{
if (!scope.displayed)
return;
// Standard Unity drawer
CoreEditorUtils.ColorFieldLinear(property.value, title);
}
}
/// <summary>
/// Draws the override checkbox used by a property in the editor.
/// </summary>
/// <param name="property">The property to draw the override checkbox for</param>
protected void DrawOverrideCheckbox(SerializedDataParameter property)
{
// Create a rect the height + vspacing of the property that is being overriden
float height = EditorGUI.GetPropertyHeight(property.value) + EditorGUIUtility.standardVerticalSpacing;
var overrideRect = GUILayoutUtility.GetRect(Styles.k_AllText, CoreEditorStyles.miniLabelButton, GUILayout.Height(height),
GUILayout.Width(Styles.overrideCheckboxWidth + Styles.overrideCheckboxOffset), GUILayout.ExpandWidth(false));
// also center vertically the checkbox
overrideRect.yMin += height * 0.5f - overrideToggleSize.y * 0.5f;
overrideRect.xMin += Styles.overrideCheckboxOffset;
property.overrideState.boolValue = GUI.Toggle(overrideRect, property.overrideState.boolValue, Styles.k_OverrideSettingText, CoreEditorStyles.smallTickbox);
}
/// <summary>
/// Scope for property that handle:
/// - Layout decorator (Space, Header)
/// - Naming decorator (Tooltips, InspectorName)
/// - Overridable checkbox if parameter IsAutoProperty
/// - disabled GUI if Overridable checkbox (case above) is unchecked
/// - additional property scope
/// This is automatically used inside PropertyField method
/// </summary>
protected struct OverridablePropertyScope : IDisposable
{
bool isAdditionalProperty;
VolumeComponentEditor editor;
IDisposable disabledScope;
IDisposable indentScope;
internal bool haveCustomOverrideCheckbox { get; private set; }
internal VolumeParameterDrawer drawer { get; private set; }
/// <summary>
/// Either the content property will be displayed or not (can varry with additional property settings)
/// </summary>
public bool displayed { get; private set; }
/// <summary>
/// The title modified regarding attribute used on the field
/// </summary>
public GUIContent label { get; private set; }
/// <summary>
/// Constructor
/// </summary>
/// <param name="property">The property that will be drawn</param>
/// <param name="label">The label of this property</param>
/// <param name="editor">The editor that will draw it</param>
public OverridablePropertyScope(SerializedDataParameter property, GUIContent label, VolumeComponentEditor editor)
{
disabledScope = null;
indentScope = null;
haveCustomOverrideCheckbox = false;
drawer = null;
displayed = false;
isAdditionalProperty = false;
this.label = label;
this.editor = editor;
Init(property, label, editor);
}
/// <summary>
/// Constructor
/// </summary>
/// <param name="property">The property that will be drawn</param>
/// <param name="label">The label of this property</param>
/// <param name="editor">The editor that will draw it</param>
public OverridablePropertyScope(SerializedDataParameter property, string label, VolumeComponentEditor editor)
{
disabledScope = null;
indentScope = null;
haveCustomOverrideCheckbox = false;
drawer = null;
displayed = false;
isAdditionalProperty = false;
this.label = EditorGUIUtility.TrTextContent(label);
this.editor = editor;
Init(property, this.label, editor);
}
void Init(SerializedDataParameter property, GUIContent label, VolumeComponentEditor editor)
{
// Below, 3 is horizontal spacing and there is one between label and field and another between override checkbox and label
EditorGUIUtility.labelWidth -= Styles.overrideCheckboxWidth + Styles.overrideCheckboxOffset + 3 + 3;
isAdditionalProperty = property.GetAttribute<AdditionalPropertyAttribute>() != null;
displayed = !isAdditionalProperty || editor.BeginAdditionalPropertiesScope();
s_ParameterDrawers.TryGetValue(property.referenceType, out VolumeParameterDrawer vpd);
drawer = vpd;
//never draw override for embedded class/struct
haveCustomOverrideCheckbox = (displayed && !(drawer?.IsAutoProperty() ?? true))
|| VolumeParameter.IsObjectParameter(property.referenceType);
if (displayed)
{
editor.HandleDecorators(property, label);
int relativeIndentation = editor.HandleRelativeIndentation(property);
int indent = relativeIndentation * 15;
if (haveCustomOverrideCheckbox)
indent += 15;
if (indent != 0)
indentScope = new IndentLevelScope(indent);
if (!haveCustomOverrideCheckbox)
{
EditorGUILayout.BeginHorizontal();
if (editor.enableOverrides)
editor.DrawOverrideCheckbox(property);
disabledScope = new EditorGUI.DisabledScope(!property.overrideState.boolValue);
}
}
}
/// <summary>
/// Dispose of the class
/// </summary>
void IDisposable.Dispose()
{
disabledScope?.Dispose();
indentScope?.Dispose();
if (!haveCustomOverrideCheckbox && displayed)
EditorGUILayout.EndHorizontal();
if (isAdditionalProperty)
editor.EndAdditionalPropertiesScope();
EditorGUIUtility.labelWidth += Styles.overrideCheckboxWidth + Styles.overrideCheckboxOffset + 3 + 3;
}
}
/// <summary>
/// Like EditorGUI.IndentLevelScope but this one will also indent the override checkboxes.
/// </summary>
protected class IndentLevelScope : GUI.Scope
{
int m_Offset;
/// <summary>
/// Constructor
/// </summary>
/// <param name="offset">[optional] Change the indentation offset</param>
public IndentLevelScope(int offset = 15)
{
m_Offset = offset;
// When using EditorGUI.indentLevel++, the clicking on the checkboxes does not work properly due to some issues on the C++ side.
// This scope is a work-around for this issue.
GUILayout.BeginHorizontal();
EditorGUILayout.Space(offset, false);
GUIStyle style = new GUIStyle();
GUILayout.BeginVertical(style);
EditorGUIUtility.labelWidth -= m_Offset;
}
/// <summary>
/// Closes the scope
/// </summary>
protected override void CloseScope()
{
EditorGUIUtility.labelWidth += m_Offset;
GUILayout.EndVertical();
GUILayout.EndHorizontal();
}
}
/// <summary>
/// Whether to draw the UI elements related to overrides.
/// </summary>
public bool enableOverrides { get; set; } = true;
}
}