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 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 => AdvancedProperties.enabled; set => AdvancedProperties.enabled = 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 (!showAdditionalProperties || !hasAdditionalProperties) return false; AdvancedProperties.BeginGroup(); return true; } /// <summary> /// End a scope for additional properties. /// </summary> protected void EndAdditionalPropertiesScope() { if (hasAdditionalProperties && showAdditionalProperties) AdvancedProperties.EndGroup(); } #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()); AdvancedProperties.UpdateShowAdvancedProperties(key, EditorPrefs.HasKey(key) && EditorPrefs.GetBool(key)); } 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(); 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; } }