using System;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Rendering.Universal.PostProcessing;
using UnityEditorInternal;
namespace UnityEditor.Rendering.Universal.PostProcessing {
///
/// A custom property drawer for the settings for custom post-processing feature
///
[CustomPropertyDrawer(typeof(QuibliPostProcess.Settings), true)]
internal class QSettingsEditor : PropertyDrawer {
///
/// This will contain a list of all available renderers for each injection point.
///
private Dictionary> _availableRenderers;
///
/// Contains 3 Reorderable list for each settings property.
///
private struct DrawerState {
public ReorderableList listAfterOpaqueAndSky;
public ReorderableList listBeforePostProcess;
public ReorderableList listAfterPostProcess;
}
///
/// Since the drawer is shared for multiple properties, we need to store the reorderable lists for each property by path.
///
private readonly Dictionary _propertyStates = new Dictionary();
///
/// Get the renderer name from the attached custom post-process attribute.
///
///
///
private string GetName(Type type) {
return CompoundRendererFeatureAttribute.GetAttribute(type)?.Name ?? type?.Name;
}
// This code is mostly copied from Unity's HDRP repository
///
/// Intialize a reoderable list
///
void InitList(ref ReorderableList reorderableList, List elements, string headerName,
InjectionPoint injectionPoint, QuibliPostProcess feature) {
reorderableList = new ReorderableList(elements, typeof(string), true, true, true, true);
reorderableList.drawHeaderCallback = (rect) => EditorGUI.LabelField(rect, headerName, EditorStyles.boldLabel);
reorderableList.drawElementCallback = (rect, index, isActive, isFocused) => {
rect.height = EditorGUIUtility.singleLineHeight;
var elemType = Type.GetType(elements[index]);
EditorGUI.LabelField(rect, GetName(elemType), EditorStyles.boldLabel);
};
reorderableList.onAddCallback = (list) => { var menu = new GenericMenu();
foreach (var type in _availableRenderers[injectionPoint]) {
if (!elements.Contains(type.AssemblyQualifiedName))
menu.AddItem(new GUIContent(GetName(type)), false, () => {
Undo.RegisterCompleteObjectUndo(feature, $"Added {type} Custom Post Process");
elements.Add(type.AssemblyQualifiedName);
forceRecreate(feature); // This is done since OnValidate doesn't get called.
});
}
if (menu.GetItemCount() == 0) menu.AddDisabledItem(new GUIContent("No Custom Post Process Available"));
menu.ShowAsContext();
EditorUtility.SetDirty(feature);
};
reorderableList.onRemoveCallback = (list) => {
Undo.RegisterCompleteObjectUndo(feature, $"Removed {list.list[list.index]} Custom Post Process");
elements.RemoveAt(list.index);
EditorUtility.SetDirty(feature);
forceRecreate(feature); // This is done since OnValidate doesn't get called.
};
reorderableList.elementHeightCallback = _ =>
EditorGUIUtility.singleLineHeight + EditorGUIUtility.standardVerticalSpacing;
reorderableList.onReorderCallback = (list) => {
EditorUtility.SetDirty(feature);
forceRecreate(feature); // This is done since OnValidate doesn't get called.
};
}
///
/// Initialize a drawer state for the giver property if none already exists.
///
/// The property that will be edited/displayed
private void Init(SerializedProperty property) {
var path = property.propertyPath;
if (!_propertyStates.ContainsKey(path)) {
var state = new DrawerState();
var feature = property.serializedObject.targetObject as QuibliPostProcess;
Debug.Assert(feature != null, nameof(feature) + " != null");
InitList(ref state.listAfterOpaqueAndSky, feature.settings.renderersAfterOpaqueAndSky,
"After Opaque and Sky", InjectionPoint.AfterOpaqueAndSky, feature);
InitList(ref state.listBeforePostProcess, feature.settings.renderersBeforePostProcess,
"Effects", InjectionPoint.BeforePostProcess, feature); // "Before Post Process"
InitList(ref state.listAfterPostProcess, feature.settings.renderersAfterPostProcess, "After Post Process",
InjectionPoint.AfterPostProcess, feature); // "After Post Process"
_propertyStates.Add(path, state);
}
}
///
/// Present the property on the Editor GUI
///
public override void OnGUI(Rect position, SerializedProperty property, GUIContent label) {
PopulateRenderers();
EditorGUI.BeginProperty(position, label, property);
Init(property);
DrawerState state = _propertyStates[property.propertyPath];
EditorGUI.BeginChangeCheck();
#if Q_AFTER_OPAQUE_AND_SKY
state.listAfterOpaqueAndSky.DoLayoutList();
#endif
// #if Q_BEFORE_POST_PROCESS
EditorGUILayout.Space();
state.listBeforePostProcess.DoLayoutList();
// #endif
#if Q_AFTER_POST_PROCESS
EditorGUILayout.Space();
state.listAfterPostProcess.DoLayoutList();
#endif
if (EditorGUI.EndChangeCheck()) {
property.serializedObject.ApplyModifiedProperties();
}
EditorGUI.EndProperty();
EditorUtility.SetDirty(property.serializedObject.targetObject);
}
///
/// Force recreating the render feature
///
/// The render feature to recreate
private void forceRecreate(QuibliPostProcess feature) {
feature.Create();
}
///
/// Finds all the custom post-processing renderer classes and categorizes them by injection point
///
private void PopulateRenderers() {
if (_availableRenderers != null) return;
_availableRenderers = new Dictionary>()
{
{ InjectionPoint.AfterOpaqueAndSky, new List() },
{ InjectionPoint.BeforePostProcess, new List() },
{ InjectionPoint.AfterPostProcess, new List() }
};
foreach (var type in TypeCache.GetTypesDerivedFrom()) {
if (type.IsAbstract) continue;
var attributes = type.GetCustomAttributes(typeof(CompoundRendererFeatureAttribute), false);
if (attributes.Length != 1) continue;
var rendererFeatureAttribute = attributes[0] as CompoundRendererFeatureAttribute;
Debug.Assert(rendererFeatureAttribute != null, nameof(rendererFeatureAttribute) + " != null");
if (rendererFeatureAttribute.InjectionPoint.HasFlag(InjectionPoint.AfterOpaqueAndSky))
_availableRenderers[InjectionPoint.AfterOpaqueAndSky].Add(type);
if (rendererFeatureAttribute.InjectionPoint.HasFlag(InjectionPoint.BeforePostProcess))
_availableRenderers[InjectionPoint.BeforePostProcess].Add(type);
if (rendererFeatureAttribute.InjectionPoint.HasFlag(InjectionPoint.AfterPostProcess))
_availableRenderers[InjectionPoint.AfterPostProcess].Add(type);
}
}
}
}