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
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<ExampleComponent>(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;
|
|
}
|
|
}
|