This commit is contained in:
2024-09-20 20:30:10 +02:00
commit 4fabf1a6fd
29169 changed files with 1706941 additions and 0 deletions

View File

@@ -0,0 +1,251 @@
using System;
using UnityEngine;
using UnityEngine.Rendering;
namespace UnityEditor.Rendering
{
/// <summary>
/// Serialized state of a Debug Item.
/// </summary>
[Serializable]
public abstract class DebugState : ScriptableObject
{
/// <summary>
/// Path of the Debug Item.
/// </summary>
[SerializeField]
protected string m_QueryPath;
// We need this to keep track of the state modified in the current frame.
// This helps reduces the cost of re-applying states to original widgets and is also needed
// when two states point to the same value (e.g. when using split enums like HDRP does for
// the `fullscreenDebugMode`.
internal static DebugState m_CurrentDirtyState;
/// <summary>
/// Path of the Debug Item.
/// </summary>
public string queryPath
{
get { return m_QueryPath; }
internal set { m_QueryPath = value; }
}
/// <summary>
/// Returns the value of the Debug Item.
/// </summary>
/// <returns>Value of the Debug Item.</returns>
public abstract object GetValue();
/// <summary>
/// Set the value of the Debug Item.
/// </summary>
/// <param name="value">Input value.</param>
/// <param name="field">Debug Item field.</param>
public abstract void SetValue(object value, DebugUI.IValueField field);
/// <summary>
/// OnEnable implementation.
/// </summary>
public virtual void OnEnable()
{
hideFlags = HideFlags.HideAndDontSave;
}
}
/// <summary>
/// Generic serialized state of a Debug Item.
/// </summary>
/// <typeparam name="T"></typeparam>
[Serializable]
public class DebugState<T> : DebugState
{
/// <summary>
/// Value of the Debug Item.
/// </summary>
[SerializeField]
protected T m_Value;
/// <summary>
/// Value of the Debug Item
/// </summary>
public virtual T value
{
get { return m_Value; }
set { m_Value = value; }
}
/// <summary>
/// Returns the value of the Debug Item.
/// </summary>
/// <returns>Value of the Debug Item.</returns>
public override object GetValue()
{
return value;
}
/// <summary>
/// Set the value of the Debug Item.
/// </summary>
/// <param name="value">Input value.</param>
/// <param name="field">Debug Item field.</param>
public override void SetValue(object value, DebugUI.IValueField field)
{
this.value = (T)field.ValidateValue(value);
}
/// <summary>
/// Returns the hash code of the Debug Item.
/// </summary>
/// <returns>Hash code of the Debug Item</returns>
public override int GetHashCode()
{
unchecked
{
int hash = 13;
hash = hash * 23 + m_QueryPath.GetHashCode();
if (value != null)
hash = hash * 23 + value.GetHashCode();
return hash;
}
}
}
/// <summary>
/// Attribute specifying which types should be save as this Debug State.
/// </summary>
public sealed class DebugStateAttribute : Attribute
{
internal readonly Type[] types;
/// <summary>
/// Debug State Attribute constructor
/// </summary>
/// <param name="types">List of types of the Debug State.</param>
public DebugStateAttribute(params Type[] types)
{
this.types = types;
}
}
// Builtins
/// <summary>
/// Boolean Debug State.
/// </summary>
[Serializable, DebugState(typeof(DebugUI.BoolField), typeof(DebugUI.Foldout), typeof(DebugUI.HistoryBoolField))]
public sealed class DebugStateBool : DebugState<bool> { }
/// <summary>
/// Enums Debug State.
/// </summary>
[Serializable, DebugState(typeof(DebugUI.EnumField), typeof(DebugUI.HistoryEnumField))]
public sealed class DebugStateEnum : DebugState<int>
{
DebugUI.EnumField m_EnumField;
/// <summary>
/// Set the value of the Debug Item.
/// </summary>
/// <param name="value">Input value.</param>
/// <param name="field">Debug Item field.</param>
public override void SetValue(object value, DebugUI.IValueField field)
{
m_EnumField = field as DebugUI.EnumField;
base.SetValue(value, field);
}
/// <summary>
/// On Enable method from <see cref="ScriptableObject"/>
/// </summary>
public override void OnEnable()
{
base.OnEnable();
if (m_EnumField == null)
return;
m_EnumField.SetValue(value);
base.SetValue(value, m_EnumField);
}
}
/// <summary>
/// Integer Debug State.
/// </summary>
[Serializable, DebugState(typeof(DebugUI.IntField))]
public sealed class DebugStateInt : DebugState<int> { }
/// <summary>
/// Object Debug State.
/// </summary>
[Serializable, DebugState(typeof(DebugUI.ObjectPopupField))]
public sealed class DebugStateObject : DebugState<UnityEngine.Object> { }
/// <summary>
/// Flags Debug State.
/// </summary>
[Serializable, DebugState(typeof(DebugUI.BitField))]
public sealed class DebugStateFlags : DebugState<Enum>
{
[SerializeField]
private SerializableEnum m_SerializableEnum;
/// <summary>
/// Value of the Debug Item
/// </summary>
public override Enum value
{
get => m_SerializableEnum?.value ?? default;
set => m_SerializableEnum.value = value;
}
/// <summary>
/// Set the value of the Debug Item.
/// </summary>
/// <param name="value">Input value.</param>
/// <param name="field">Debug Item field.</param>
public override void SetValue(object value, DebugUI.IValueField field)
{
if (m_SerializableEnum == null)
m_SerializableEnum = new SerializableEnum((field as DebugUI.BitField).enumType);
base.SetValue(value, field);
}
}
/// <summary>
/// Unsigned Integer Debug State.
/// </summary>
[Serializable, DebugState(typeof(DebugUI.UIntField))]
public sealed class DebugStateUInt : DebugState<uint> { }
/// <summary>
/// Float Debug State.
/// </summary>
[Serializable, DebugState(typeof(DebugUI.FloatField))]
public sealed class DebugStateFloat : DebugState<float> { }
/// <summary>
/// Color Debug State.
/// </summary>
[Serializable, DebugState(typeof(DebugUI.ColorField))]
public sealed class DebugStateColor : DebugState<Color> { }
/// <summary>
/// Vector2 Debug State.
/// </summary>
[Serializable, DebugState(typeof(DebugUI.Vector2Field))]
public sealed class DebugStateVector2 : DebugState<Vector2> { }
/// <summary>
/// Vector3 Debug State.
/// </summary>
[Serializable, DebugState(typeof(DebugUI.Vector3Field))]
public sealed class DebugStateVector3 : DebugState<Vector3> { }
/// <summary>
/// Vector4 Debug State.
/// </summary>
[Serializable, DebugState(typeof(DebugUI.Vector4Field))]
public sealed class DebugStateVector4 : DebugState<Vector4> { }
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 7270f3dffc138834da5642e5943f5072
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,897 @@
using System;
using System.Linq;
using UnityEngine;
using UnityEngine.Rendering;
namespace UnityEditor.Rendering
{
/// <summary>
/// Builtin Drawer for Value Debug Items.
/// </summary>
[DebugUIDrawer(typeof(DebugUI.Value))]
public sealed class DebugUIDrawerValue : DebugUIWidgetDrawer<DebugUI.Value>
{
/// <summary>
/// Does the field of the given type
/// </summary>
/// <param name="rect">The rect to draw the field</param>
/// <param name="label">The label for the field</param>
/// <param name="field">The widget</param>
protected override void DoGUI(Rect rect, GUIContent label, DebugUI.Value field)
{
EditorGUI.LabelField(rect, label, EditorGUIUtility.TrTextContent(field.FormatString(field.GetValue())));
}
}
/// <summary>
/// Builtin Drawer for ValueTuple Debug Items.
/// </summary>
[DebugUIDrawer(typeof(DebugUI.ValueTuple))]
public sealed class DebugUIDrawerValueTuple : DebugUIWidgetDrawer<DebugUI.ValueTuple>
{
/// <summary>
/// Does the field of the given type
/// </summary>
/// <param name="rect">The rect to draw the field</param>
/// <param name="label">The label for the field</param>
/// <param name="field">The widget</param>
protected override void DoGUI(Rect rect, GUIContent label, DebugUI.ValueTuple field)
{
var labelRect = PrepareControlRect();
EditorGUI.PrefixLabel(labelRect, label);
// Following layout should match DebugUIDrawerFoldout to make column labels align
Rect drawRect = GUILayoutUtility.GetLastRect();
int indent = EditorGUI.indentLevel;
EditorGUI.indentLevel = 0; //be at left of rects
for (int i = 0; i < field.numElements; i++)
{
var columnRect = drawRect;
columnRect.x += EditorGUIUtility.labelWidth + i * DebugWindow.Styles.foldoutColumnWidth;
columnRect.width = DebugWindow.Styles.foldoutColumnWidth;
var value = field.values[i].GetValue();
EditorGUI.LabelField(columnRect, field.values[i].FormatString(value));
}
EditorGUI.indentLevel = indent;
}
}
/// <summary>
/// Builtin Drawer for ProgressBarValue Debug Items.
/// </summary>
[DebugUIDrawer(typeof(DebugUI.ProgressBarValue))]
public sealed class DebugUIDrawerProgressBarValue : DebugUIWidgetDrawer<DebugUI.ProgressBarValue>
{
/// <summary>
/// Does the field of the given type
/// </summary>
/// <param name="rect">The rect to draw the field</param>
/// <param name="label">The label for the field</param>
/// <param name="field">The widget</param>
protected override void DoGUI(Rect rect, GUIContent label, DebugUI.ProgressBarValue field)
{
var labelRect = PrepareControlRect();
var progressBarRect = EditorGUI.PrefixLabel(labelRect, label);
float value = (float)field.GetValue();
EditorGUI.ProgressBar(progressBarRect, value, field.FormatString(value));
}
}
/// <summary>
/// Builtin Drawer for Button Debug Items.
/// </summary>
[DebugUIDrawer(typeof(DebugUI.Button))]
public sealed class DebugUIDrawerButton : DebugUIWidgetDrawer<DebugUI.Button>
{
/// <summary>
/// Does the field of the given type
/// </summary>
/// <param name="rect">The rect to draw the field</param>
/// <param name="label">The label for the field</param>
/// <param name="field">The widget</param>
protected override void DoGUI(Rect rect, GUIContent label, DebugUI.Button field)
{
rect = EditorGUI.IndentedRect(rect);
if (GUI.Button(rect, label, EditorStyles.miniButton))
{
if (field.action != null)
field.action();
}
}
}
/// <summary>
/// Builtin Drawer for Boolean Debug Items.
/// </summary>
[DebugUIDrawer(typeof(DebugUI.BoolField))]
public sealed class DebugUIDrawerBoolField : DebugUIFieldDrawer<bool, DebugUI.BoolField, DebugStateBool>
{
/// <summary>
/// Does the field of the given type
/// </summary>
/// <param name="rect">The rect to draw the field</param>
/// <param name="label">The label for the field</param>
/// <param name="field">The field</param>
/// <param name="state">The state</param>
/// <returns>The value</returns>
protected override bool DoGUI(Rect rect, GUIContent label, DebugUI.BoolField field, DebugStateBool state)
{
return EditorGUI.Toggle(rect, label, field.GetValue());
}
}
/// <summary>
/// Builtin Drawer for History Boolean Debug Items.
/// </summary>
[DebugUIDrawer(typeof(DebugUI.HistoryBoolField))]
public sealed class DebugUIDrawerHistoryBoolField : DebugUIFieldDrawer<bool, DebugUI.HistoryBoolField, DebugStateBool>
{
/// <summary>
/// Does the field of the given type
/// </summary>
/// <param name="rect">The rect to draw the field</param>
/// <param name="label">The label for the field</param>
/// <param name="field">The field</param>
/// <param name="state">The state</param>
/// <returns>The current value from the UI</returns>
protected override bool DoGUI(Rect rect, GUIContent label, DebugUI.HistoryBoolField field, DebugStateBool state)
{
var labelRect = rect;
labelRect.width = EditorGUIUtility.labelWidth;
const int oneValueWidth = 70;
var valueRects = new Rect[field.historyDepth + 1];
for (int i = 0; i < field.historyDepth + 1; i++)
{
valueRects[i] = rect;
valueRects[i].x += EditorGUIUtility.labelWidth + i * oneValueWidth;
valueRects[i].width = oneValueWidth;
}
EditorGUI.LabelField(labelRect, label);
int indent = EditorGUI.indentLevel;
EditorGUI.indentLevel = 0; //be at left of rects
bool value = EditorGUI.Toggle(valueRects[0], field.GetValue());
using (new EditorGUI.DisabledScope(true))
{
for (int i = 0; i < field.historyDepth; i++)
EditorGUI.Toggle(valueRects[i + 1], field.GetHistoryValue(i));
}
EditorGUI.indentLevel = indent;
return value;
}
}
/// <summary>
/// Builtin Drawer for Integer Debug Items.
/// </summary>
[DebugUIDrawer(typeof(DebugUI.IntField))]
public sealed class DebugUIDrawerIntField : DebugUIFieldDrawer<int, DebugUI.IntField, DebugStateInt>
{
/// <summary>
/// Does the field of the given type
/// </summary>
/// <param name="rect">The rect to draw the field</param>
/// <param name="label">The label for the field</param>
/// <param name="field">The field</param>
/// <param name="state">The state</param>
/// <returns>The current value from the UI</returns>
protected override int DoGUI(Rect rect, GUIContent label, DebugUI.IntField field, DebugStateInt state)
{
return field.min != null && field.max != null
? EditorGUI.IntSlider(rect, label, field.GetValue(), field.min(), field.max())
: EditorGUI.IntField(rect, label, field.GetValue());
}
}
/// <summary>
/// Builtin Drawer for Unsigned Integer Debug Items.
/// </summary>
[DebugUIDrawer(typeof(DebugUI.UIntField))]
public sealed class DebugUIDrawerUIntField : DebugUIFieldDrawer<uint, DebugUI.UIntField, DebugStateUInt>
{
/// <summary>
/// Does the field of the given type
/// </summary>
/// <param name="rect">The rect to draw the field</param>
/// <param name="label">The label for the field</param>
/// <param name="field">The field</param>
/// <param name="state">The state</param>
/// <returns>The current value from the UI</returns>
protected override uint DoGUI(Rect rect, GUIContent label, DebugUI.UIntField field, DebugStateUInt state)
{
// No UIntField so we need to max to 0 ourselves or the value will wrap around
int tmp = field.min != null && field.max != null
? EditorGUI.IntSlider(rect, label, Mathf.Max(0, (int)field.GetValue()), Mathf.Max(0, (int)field.min()), Mathf.Max(0, (int)field.max()))
: EditorGUI.IntField(rect, label, Mathf.Max(0, (int)field.GetValue()));
return (uint)Mathf.Max(0, tmp);
}
}
/// <summary>
/// Builtin Drawer for Float Debug Items.
/// </summary>
[DebugUIDrawer(typeof(DebugUI.FloatField))]
public sealed class DebugUIDrawerFloatField : DebugUIFieldDrawer<float, DebugUI.FloatField, DebugStateFloat>
{
/// <summary>
/// Does the field of the given type
/// </summary>
/// <param name="rect">The rect to draw the field</param>
/// <param name="label">The label for the field</param>
/// <param name="field">The field</param>
/// <param name="state">The state</param>
/// <returns>The current value from the UI</returns>
protected override float DoGUI(Rect rect, GUIContent label, DebugUI.FloatField field, DebugStateFloat state)
{
return field.min != null && field.max != null
? EditorGUI.Slider(rect, label, field.GetValue(), field.min(), field.max())
: EditorGUI.FloatField(rect, label, field.GetValue());
}
}
/// <summary>
/// Builtin Drawer for Enum Debug Items.
/// </summary>
[DebugUIDrawer(typeof(DebugUI.EnumField))]
public sealed class DebugUIDrawerEnumField : DebugUIFieldDrawer<int, DebugUI.EnumField, DebugStateEnum>
{
/// <summary>
/// Does the field of the given type
/// </summary>
/// <param name="rect">The rect to draw the field</param>
/// <param name="label">The label for the field</param>
/// <param name="field">The field</param>
/// <param name="state">The state</param>
/// <returns>The current value from the UI</returns>
protected override int DoGUI(Rect rect, GUIContent label, DebugUI.EnumField field, DebugStateEnum state)
{
int index = Mathf.Max(0, field.currentIndex); // Fallback just in case, we may be handling sub/sectioned enums here
int value = field.GetValue();
if (field.enumNames == null || field.enumValues == null)
{
EditorGUI.LabelField(rect, label, "Can't draw an empty enumeration.");
}
else if (field.enumNames.Length != field.enumValues.Length)
{
EditorGUI.LabelField(rect, label, "Invalid data");
}
else
{
index = EditorGUI.IntPopup(rect, label, index, field.enumNames, field.indexes);
value = field.enumValues[index];
}
return value;
}
}
/// <summary>
/// Builtin Drawer for Object Popup Fields Items.
/// </summary>
[DebugUIDrawer(typeof(DebugUI.ObjectPopupField))]
public sealed class DebugUIDrawerObjectPopupField : DebugUIFieldDrawer<UnityEngine.Object, DebugUI.ObjectPopupField, DebugStateObject>
{
/// <summary>
/// Does the field of the given type
/// </summary>
/// <param name="rect">The rect to draw the field</param>
/// <param name="label">The label for the field</param>
/// <param name="field">The field</param>
/// <param name="state">The state</param>
/// <returns>The current value from the UI</returns>
protected override UnityEngine.Object DoGUI(Rect rect, GUIContent label, DebugUI.ObjectPopupField field, DebugStateObject state)
{
var selectedValue = field.GetValue();
rect = EditorGUI.PrefixLabel(rect, label);
var elements = field.getObjects();
if (elements?.Any() ?? false)
{
var elementsArrayNames = elements.Select(e => e.name).ToArray();
var elementsArrayIndices = Enumerable.Range(0, elementsArrayNames.Length).ToArray();
var selectedIndex = selectedValue != null ? Array.IndexOf(elementsArrayNames, selectedValue.name) : 0;
var newSelectedIndex = EditorGUI.IntPopup(rect, selectedIndex, elementsArrayNames, elementsArrayIndices);
if (selectedIndex != newSelectedIndex)
selectedValue = elements.ElementAt(newSelectedIndex);
}
else
{
EditorGUI.LabelField(rect, "Can't draw an empty enumeration.");
}
return selectedValue;
}
}
/// <summary>
/// Builtin Drawer for History Enum Debug Items.
/// </summary>
[DebugUIDrawer(typeof(DebugUI.HistoryEnumField))]
public sealed class DebugUIDrawerHistoryEnumField : DebugUIFieldDrawer<int, DebugUI.HistoryEnumField, DebugStateEnum>
{
/// <summary>
/// Does the field of the given type
/// </summary>
/// <param name="rect">The rect to draw the field</param>
/// <param name="label">The label for the field</param>
/// <param name="field">The field</param>
/// <param name="state">The state</param>
/// <returns>The current value from the UI</returns>
protected override int DoGUI(Rect rect, GUIContent label, DebugUI.HistoryEnumField field, DebugStateEnum state)
{
int index = -1;
int value = field.GetValue();
if (field.enumNames == null || field.enumValues == null)
{
EditorGUILayout.LabelField("Can't draw an empty enumeration.");
}
else
{
index = field.currentIndex;
// Fallback just in case, we may be handling sub/sectionned enums here
if (index < 0)
index = 0;
var labelRect = rect;
labelRect.width = EditorGUIUtility.labelWidth;
const int oneValueWidth = 70;
var valueRects = new Rect[field.historyDepth + 1];
for (int i = 0; i < field.historyDepth + 1; i++)
{
valueRects[i] = rect;
valueRects[i].x += EditorGUIUtility.labelWidth + i * oneValueWidth;
valueRects[i].width = oneValueWidth;
}
EditorGUI.LabelField(labelRect, label);
int indent = EditorGUI.indentLevel;
EditorGUI.indentLevel = 0; //be at left of rects
index = EditorGUI.IntPopup(valueRects[0], index, field.enumNames, field.indexes);
value = field.enumValues[index];
using (new EditorGUI.DisabledScope(true))
{
for (int i = 0; i < field.historyDepth; i++)
EditorGUI.IntPopup(valueRects[i + 1], field.GetHistoryValue(i), field.enumNames, field.indexes);
}
EditorGUI.indentLevel = indent;
value = field.enumValues[index];
}
return value;
}
}
/// <summary>
/// Builtin Drawer for Bitfield Debug Items.
/// </summary>
[DebugUIDrawer(typeof(DebugUI.BitField))]
public sealed class DebugUIDrawerBitField : DebugUIFieldDrawer<Enum, DebugUI.BitField, DebugStateFlags>
{
/// <summary>
/// Does the field of the given type
/// </summary>
/// <param name="rect">The rect to draw the field</param>
/// <param name="label">The label for the field</param>
/// <param name="field">The field</param>
/// <param name="state">The state</param>
/// <returns>The current value from the UI</returns>
protected override Enum DoGUI(Rect rect, GUIContent label, DebugUI.BitField field, DebugStateFlags state)
{
Enum value = field.GetValue();
// Skip first element (with value 0) because EditorGUI.MaskField adds a 'Nothing' field anyway
var enumNames = new string[field.enumNames.Length - 1];
for (int i = 0; i < enumNames.Length; i++)
enumNames[i] = field.enumNames[i + 1].text;
var index = EditorGUI.MaskField(rect, label, (int)Convert.ToInt32(value), enumNames);
value = Enum.Parse(value.GetType(), index.ToString()) as Enum;
return value;
}
}
/// <summary>
/// Builtin Drawer for Foldout Debug Items.
/// </summary>
[DebugUIDrawer(typeof(DebugUI.Foldout))]
public sealed class DebugUIDrawerFoldout : DebugUIDrawer
{
const int k_HeaderVerticalMargin = 2;
/// <summary>
/// Implement this to execute processing before UI rendering.
/// </summary>
/// <param name="widget">Widget that is going to be rendered.</param>
/// <param name="state">Debug State associated with the Debug Item.</param>
public override void Begin(DebugUI.Widget widget, DebugState state)
{
var w = Cast<DebugUI.Foldout>(widget);
var s = Cast<DebugStateBool>(state);
GUIStyle style = w.isHeader ? DebugWindow.Styles.foldoutHeaderStyle : EditorStyles.foldout;
Rect rect = PrepareControlRect(w.isHeader ? style.fixedHeight : -1, w.isHeader);
if (w.isHeader)
GUILayout.Space(k_HeaderVerticalMargin);
bool value = EditorGUI.Foldout(rect, (bool)w.GetValue(), EditorGUIUtility.TrTextContent(w.displayName, w.tooltip), false, style);
if (w.GetValue() != value)
Apply(w, s, value);
if (w.contextMenuItems != null)
{
float contextMenuButtonSize = style.fixedHeight;
var labelRect = EditorGUI.IndentedRect(GUILayoutUtility.GetRect(0f, /*17f*/ 0f));
labelRect.xMax -= 20f + 16 + 5;
var contextMenuRect = new Rect(labelRect.xMax + 3f + 16, labelRect.y - contextMenuButtonSize, contextMenuButtonSize, contextMenuButtonSize);
if (GUI.Button(contextMenuRect, CoreEditorStyles.contextMenuIcon, CoreEditorStyles.contextMenuStyle))
{
var menu = new GenericMenu();
foreach (var item in w.contextMenuItems)
{
menu.AddItem(EditorGUIUtility.TrTextContent(item.displayName), false, () => item.action.Invoke());
}
menu.DropDown(new Rect(new Vector2(contextMenuRect.x, contextMenuRect.yMax), Vector2.zero));
}
}
Rect drawRect = GUILayoutUtility.GetLastRect();
if (w.columnLabels != null && value)
{
int indent = EditorGUI.indentLevel;
EditorGUI.indentLevel = 0; //be at left of rects
for (int i = 0; i < w.columnLabels.Length; i++)
{
var columnRect = drawRect;
columnRect.x += EditorGUIUtility.labelWidth + i * DebugWindow.Styles.foldoutColumnWidth;
columnRect.width = DebugWindow.Styles.foldoutColumnWidth;
string label = w.columnLabels[i] ?? "";
string tooltip = w.columnTooltips?.ElementAtOrDefault(i) ?? "";
EditorGUI.LabelField(columnRect, EditorGUIUtility.TrTextContent(label, tooltip), EditorStyles.miniBoldLabel);
}
EditorGUI.indentLevel = indent;
}
EditorGUI.indentLevel++;
}
/// <summary>
/// OnGUI implementation for Foldout DebugUIDrawer.
/// </summary>
/// <param name="widget">DebugUI Widget.</param>
/// <param name="state">Debug State associated with the Debug Item.</param>
/// <returns>The state of the widget.</returns>
public override bool OnGUI(DebugUI.Widget widget, DebugState state)
{
var w = Cast<DebugUI.Foldout>(widget);
return w.opened;
}
/// <summary>
/// End implementation for Foldout DebugUIDrawer.
/// </summary>
/// <param name="widget">DebugUI Widget.</param>
/// <param name="state">Debug State associated with the Debug Item.</param>
public override void End(DebugUI.Widget widget, DebugState state)
{
EditorGUI.indentLevel--;
var w = Cast<DebugUI.Foldout>(widget);
if (w.isHeader)
GUILayout.Space(k_HeaderVerticalMargin);
}
}
/// <summary>
/// Builtin Drawer for Color Debug Items.
/// </summary>
[DebugUIDrawer(typeof(DebugUI.ColorField))]
public sealed class DebugUIDrawerColorField : DebugUIFieldDrawer<Color, DebugUI.ColorField, DebugStateColor>
{
/// <summary>
/// Does the field of the given type
/// </summary>
/// <param name="rect">The rect to draw the field</param>
/// <param name="label">The label for the field</param>
/// <param name="field">The field</param>
/// <param name="state">The state</param>
/// <returns>The current value from the UI</returns>
protected override Color DoGUI(Rect rect, GUIContent label, DebugUI.ColorField field, DebugStateColor state)
{
return EditorGUI.ColorField(rect, label, field.GetValue(), field.showPicker, field.showAlpha, field.hdr);
}
}
/// <summary>
/// Builtin Drawer for Vector2 Debug Items.
/// </summary>
[DebugUIDrawer(typeof(DebugUI.Vector2Field))]
public sealed class DebugUIDrawerVector2Field : DebugUIFieldDrawer<Vector2, DebugUI.Vector2Field, DebugStateVector2>
{
/// <summary>
/// Does the field of the given type
/// </summary>
/// <param name="rect">The rect to draw the field</param>
/// <param name="label">The label for the field</param>
/// <param name="field">The field</param>
/// <param name="state">The state</param>
/// <returns>The current value from the UI</returns>
protected override Vector2 DoGUI(Rect rect, GUIContent label, DebugUI.Vector2Field field, DebugStateVector2 state)
{
return EditorGUILayout.Vector2Field(label, field.GetValue());
}
}
/// <summary>
/// Builtin Drawer for Vector3 Debug Items.
/// </summary>
[DebugUIDrawer(typeof(DebugUI.Vector3Field))]
public sealed class DebugUIDrawerVector3Field : DebugUIFieldDrawer<Vector3, DebugUI.Vector3Field, DebugStateVector3>
{
/// <summary>
/// Does the field of the given type
/// </summary>
/// <param name="rect">The rect to draw the field</param>
/// <param name="label">The label for the field</param>
/// <param name="field">The field</param>
/// <param name="state">The state</param>
/// <returns>The current value from the UI</returns>
protected override Vector3 DoGUI(Rect rect, GUIContent label, DebugUI.Vector3Field field, DebugStateVector3 state)
{
return EditorGUILayout.Vector3Field(label, field.GetValue());
}
}
/// <summary>
/// Builtin Drawer for Vector4 Debug Items.
/// </summary>
[DebugUIDrawer(typeof(DebugUI.Vector4Field))]
public sealed class DebugUIDrawerVector4Field : DebugUIFieldDrawer<Vector4, DebugUI.Vector4Field, DebugStateVector4>
{
/// <summary>
/// Does the field of the given type
/// </summary>
/// <param name="rect">The rect to draw the field</param>
/// <param name="label">The label for the field</param>
/// <param name="field">The field</param>
/// <param name="state">The state</param>
/// <returns>The current value from the UI</returns>
protected override Vector4 DoGUI(Rect rect, GUIContent label, DebugUI.Vector4Field field, DebugStateVector4 state)
{
return EditorGUILayout.Vector4Field(label, field.GetValue());
}
}
/// <summary>
/// Builtin Drawer for <see cref="DebugUI.ObjectField"/> items.
/// </summary>
[DebugUIDrawer(typeof(DebugUI.ObjectField))]
public sealed class DebugUIDrawerObjectField : DebugUIFieldDrawer<UnityEngine.Object, DebugUI.ObjectField, DebugStateObject>
{
/// <summary>
/// Does the field of the given type
/// </summary>
/// <param name="rect">The rect to draw the field</param>
/// <param name="label">The label for the field</param>
/// <param name="field">The field</param>
/// <param name="state">The state</param>
/// <returns>The current value from the UI</returns>
protected override UnityEngine.Object DoGUI(Rect rect, GUIContent label, DebugUI.ObjectField field, DebugStateObject state)
{
return EditorGUI.ObjectField(rect, label, field.GetValue(), field.type, true);
}
}
/// <summary>
/// Builtin Drawer for <see cref="DebugUI.ObjectListField"/> Items.
/// </summary>
[DebugUIDrawer(typeof(DebugUI.ObjectListField))]
public sealed class DebugUIDrawerObjectListField : DebugUIDrawer
{
/// <summary>
/// Implement this to execute UI rendering.
/// </summary>
/// <param name="widget">Widget that is going to be rendered.</param>
/// <param name="state">Debug State associated with the Debug Item.</param>
/// <returns>Returns the state of the widget.</returns>
public override bool OnGUI(DebugUI.Widget widget, DebugState state)
{
var w = Cast<DebugUI.ObjectListField>(widget);
var objects = w.GetValue();
float height = Math.Max(objects != null ? objects.Length : 0, 1) * DebugWindow.Styles.singleRowHeight;
var rect = PrepareControlRect(height);
rect = EditorGUI.PrefixLabel(rect, EditorGUIUtility.TrTextContent(widget.displayName));
EditorGUI.BeginChangeCheck();
DoObjectList(rect, w, objects);
if (EditorGUI.EndChangeCheck())
Apply(w, state, objects);
return true;
}
internal static void DoObjectList(Rect rect, DebugUI.ObjectListField widget, UnityEngine.Object[] objects)
{
if (objects == null || objects.Length == 0)
{
EditorGUI.LabelField(rect, GUIContent.none, EditorGUIUtility.TrTextContent("Empty"));
return;
}
rect.height = EditorGUIUtility.singleLineHeight;
for (int i = 0; i < objects.Length; i++)
{
objects[i] = EditorGUI.ObjectField(rect, GUIContent.none, objects[i], widget.type, true);
rect.y += DebugWindow.Styles.singleRowHeight;
}
}
}
/// <summary>
/// Builtin Drawer for MessageBox Items.
/// </summary>
[DebugUIDrawer(typeof(DebugUI.MessageBox))]
public sealed class DebugUIDrawerMessageBox : DebugUIDrawer
{
/// <summary>
/// Implement this to execute UI rendering.
/// </summary>
/// <param name="widget">Widget that is going to be rendered.</param>
/// <param name="state">Debug State associated with the Debug Item.</param>
/// <returns>Returns the state of the widget.</returns>
public override bool OnGUI(DebugUI.Widget widget, DebugState state)
{
var w = Cast<DebugUI.MessageBox>(widget);
var type = w.style switch
{
DebugUI.MessageBox.Style.Info => MessageType.Info,
DebugUI.MessageBox.Style.Warning => MessageType.Warning,
DebugUI.MessageBox.Style.Error => MessageType.Error,
_ => MessageType.None
};
EditorGUILayout.HelpBox(w.displayName, type);
return true;
}
}
/// <summary>
/// Builtin Drawer for Container Debug Items.
/// </summary>
[DebugUIDrawer(typeof(DebugUI.Container))]
public sealed class DebugUIDrawerContainer : DebugUIDrawer
{
/// <summary>
/// Implement this to execute processing before UI rendering.
/// </summary>
/// <param name="widget">Widget that is going to be rendered.</param>
/// <param name="state">Debug State associated with the Debug Item.</param>
public override void Begin(DebugUI.Widget widget, DebugState state)
{
var w = Cast<DebugUI.Container>(widget);
if (!w.hideDisplayName)
EditorGUILayout.LabelField(EditorGUIUtility.TrTextContent(widget.displayName, widget.tooltip), EditorStyles.boldLabel);
EditorGUI.indentLevel++;
}
/// <summary>
/// Implement this to execute processing after UI rendering.
/// </summary>
/// <param name="widget">Widget that is going to be rendered.</param>
/// <param name="state">Debug State associated with the Debug Item.</param>
public override void End(DebugUI.Widget widget, DebugState state)
{
EditorGUI.indentLevel--;
}
}
/// <summary>
/// Builtin Drawer for Horizontal Box Debug Items.
/// </summary>
[DebugUIDrawer(typeof(DebugUI.HBox))]
public sealed class DebugUIDrawerHBox : DebugUIDrawer
{
/// <summary>
/// Implement this to execute processing before UI rendering.
/// </summary>
/// <param name="widget">Widget that is going to be rendered.</param>
/// <param name="state">Debug State associated with the Debug Item.</param>
public override void Begin(DebugUI.Widget widget, DebugState state)
{
EditorGUILayout.BeginHorizontal();
}
/// <summary>
/// Implement this to execute processing after UI rendering.
/// </summary>
/// <param name="widget">Widget that is going to be rendered.</param>
/// <param name="state">Debug State associated with the Debug Item.</param>
public override void End(DebugUI.Widget widget, DebugState state)
{
EditorGUILayout.EndHorizontal();
}
}
/// <summary>
/// Builtin Drawer for Vertical Box Debug Items.
/// </summary>
[DebugUIDrawer(typeof(DebugUI.VBox))]
public sealed class DebugUIDrawerVBox : DebugUIDrawer
{
/// <summary>
/// Implement this to execute processing before UI rendering.
/// </summary>
/// <param name="widget">Widget that is going to be rendered.</param>
/// <param name="state">Debug State associated with the Debug Item.</param>
public override void Begin(DebugUI.Widget widget, DebugState state)
{
EditorGUILayout.BeginVertical();
}
/// <summary>
/// Implement this to execute processing after UI rendering.
/// </summary>
/// <param name="widget">Widget that is going to be rendered.</param>
/// <param name="state">Debug State associated with the Debug Item.</param>
public override void End(DebugUI.Widget widget, DebugState state)
{
EditorGUILayout.EndVertical();
}
}
/// <summary>
/// Builtin Drawer for Table Debug Items.
/// </summary>
[DebugUIDrawer(typeof(DebugUI.Table))]
public sealed class DebugUIDrawerTable : DebugUIDrawer
{
/// <summary>
/// Implement this to execute UI rendering.
/// </summary>
/// <param name="widget">Widget that is going to be rendered.</param>
/// <param name="state">Debug State associated with the Debug Item.</param>
/// <returns>Returns the state of the widget.</returns>
public override bool OnGUI(DebugUI.Widget widget, DebugState state)
{
const float k_ScrollBarHeight = 15;
var w = Cast<DebugUI.Table>(widget);
var header = w.Header;
var visible = header.state.visibleColumns;
float contentHeight = 0.0f;
foreach (DebugUI.Table.Row row in w.children)
contentHeight += row != null ? GetRowHeight(row, visible) : EditorGUIUtility.singleLineHeight;
// Put some space before the array
PrepareControlRect(EditorGUIUtility.singleLineHeight * 0.5f);
// Draw an outline around the table
var rect = EditorGUI.IndentedRect(PrepareControlRect(header.height + contentHeight + k_ScrollBarHeight));
rect = DrawOutline(rect);
// Compute rects
var headerRect = new Rect(rect.x, rect.y, rect.width, header.height);
var contentRect = new Rect(rect.x, headerRect.yMax, rect.width, rect.height - headerRect.height);
var viewRect = new Rect(contentRect.x, contentRect.y, header.state.widthOfAllVisibleColumns, contentRect.height);
var rowRect = contentRect;
viewRect.height -= k_ScrollBarHeight;
// Show header
header.OnGUI(headerRect, Mathf.Max(w.scroll.x, 0f));
// Show array content
w.scroll = GUI.BeginScrollView(contentRect, w.scroll, viewRect);
{
var columns = header.state.columns;
for (int r = 0; r < w.children.Count; r++)
{
var row = Cast<DebugUI.Container>(w.children[r]);
rowRect.x = contentRect.x;
rowRect.width = columns[0].width;
rowRect.height = (row is DebugUI.Table.Row tableRow) ? GetRowHeight(tableRow, visible) : EditorGUIUtility.singleLineHeight;
rowRect.xMin += 2;
rowRect.xMax -= 2;
EditorGUI.LabelField(rowRect, GUIContent.none, EditorGUIUtility.TrTextContent(row.displayName), DebugWindow.Styles.centeredLeft);
rowRect.xMin -= 2;
rowRect.xMax += 2;
using (new EditorGUI.DisabledScope(w.isReadOnly))
{
for (int c = 1; c < visible.Length; c++)
{
rowRect.x += rowRect.width;
rowRect.width = columns[visible[c]].width;
if (!row.isHidden)
DisplayChild(rowRect, row.children[visible[c] - 1]);
}
rowRect.y += rowRect.height;
}
}
}
GUI.EndScrollView(false);
return false;
}
internal float GetRowHeight(DebugUI.Table.Row row, int[] visibleColumns)
{
float height = EditorGUIUtility.singleLineHeight;
for (int c = 1; c < visibleColumns.Length; c++)
{
var child = row.children[visibleColumns[c] - 1] as DebugUI.ObjectListField;
if (child == null || child.GetValue() == null)
continue;
height = Mathf.Max(height, child.GetValue().Length * DebugWindow.Styles.singleRowHeight);
}
return height;
}
internal Rect DrawOutline(Rect rect)
{
if (Event.current.type != EventType.Repaint)
return rect;
float size = 1.0f;
var color = EditorGUIUtility.isProSkin ? new Color(0.12f, 0.12f, 0.12f, 1.333f) : new Color(0.6f, 0.6f, 0.6f, 1.333f);
Color orgColor = GUI.color;
GUI.color = GUI.color * color;
GUI.DrawTexture(new Rect(rect.x, rect.y, rect.width, size), EditorGUIUtility.whiteTexture);
GUI.DrawTexture(new Rect(rect.x, rect.yMax - size, rect.width, size), EditorGUIUtility.whiteTexture);
GUI.DrawTexture(new Rect(rect.x, rect.y + 1, size, rect.height - 2 * size), EditorGUIUtility.whiteTexture);
GUI.DrawTexture(new Rect(rect.xMax - size, rect.y + 1, size, rect.height - 2 * size), EditorGUIUtility.whiteTexture);
GUI.color = orgColor;
return new Rect(rect.x + size, rect.y + size, rect.width - 2 * size, rect.height - 2 * size);
}
internal void DisplayChild(Rect rect, DebugUI.Widget child)
{
rect.xMin += 2;
rect.xMax -= 2;
if (child.isHidden)
{
EditorGUI.LabelField(rect, "-");
}
else
{
if (child.GetType() == typeof(DebugUI.Value))
{
var widget = Cast<DebugUI.Value>(child);
EditorGUI.LabelField(rect, GUIContent.none, EditorGUIUtility.TrTextContent(widget.GetValue().ToString()));
}
else if (child.GetType() == typeof(DebugUI.ColorField))
{
var widget = Cast<DebugUI.ColorField>(child);
EditorGUI.ColorField(rect, GUIContent.none, widget.GetValue(), false, widget.showAlpha, widget.hdr);
}
else if (child.GetType() == typeof(DebugUI.BoolField))
{
var widget = Cast<DebugUI.BoolField>(child);
EditorGUI.Toggle(rect, GUIContent.none, widget.GetValue());
}
else if (child.GetType() == typeof(DebugUI.ObjectField))
{
var widget = Cast<DebugUI.ObjectField>(child);
EditorGUI.ObjectField(rect, GUIContent.none, widget.GetValue(), widget.type, true);
}
else if (child.GetType() == typeof(DebugUI.ObjectListField))
{
var widget = Cast<DebugUI.ObjectListField>(child);
DebugUIDrawerObjectListField.DoObjectList(rect, widget, widget.GetValue());
}
}
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 7f9e548e5e2920b47987881c21171ef6
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,244 @@
using System;
using System.Text;
using UnityEditor.Rendering.Analytics;
using UnityEngine;
using UnityEngine.Rendering;
namespace UnityEditor.Rendering
{
/// <summary>
/// Attribute specifying wich type of Debug Item should this drawer be used with.
/// </summary>
public class DebugUIDrawerAttribute : Attribute
{
internal readonly Type type;
/// <summary>
/// Constructor for DebugUIDraw Attribute
/// </summary>
/// <param name="type">Type of Debug Item this draw should be used with.</param>
public DebugUIDrawerAttribute(Type type)
{
this.type = type;
}
}
/// <summary>
/// Debug Item Drawer
/// </summary>
public class DebugUIDrawer
{
/// <summary>
/// Cast into the proper type.
/// </summary>
/// <typeparam name="T">Type of the drawer</typeparam>
/// <param name="o">Object to be cast</param>
/// <returns>Returns o cast to type T</returns>
protected T Cast<T>(object o)
where T : class
{
if (o == null) return null;
if (o is T casted)
return casted;
StringBuilder info = new StringBuilder("Cast Exception:");
switch (o)
{
case DebugUI.Widget value:
info.AppendLine($"Query Path : {value.queryPath}");
break;
case DebugState state:
info.AppendLine($"Query Path : {state.queryPath}");
break;
}
info.AppendLine($"Object to Cast Type : {o.GetType().AssemblyQualifiedName}");
info.AppendLine($"Target Cast Type : {typeof(T).AssemblyQualifiedName}");
throw new InvalidCastException(info.ToString());
}
/// <summary>
/// Implement this to execute processing before UI rendering.
/// </summary>
/// <param name="widget">Widget that is going to be rendered.</param>
/// <param name="state">Debug State associated with the Debug Item.</param>
public virtual void Begin(DebugUI.Widget widget, DebugState state)
{ }
/// <summary>
/// Implement this to execute UI rendering.
/// </summary>
/// <param name="widget">Widget that is going to be rendered.</param>
/// <param name="state">Debug State associated with the Debug Item.</param>
/// <returns>Returns the state of the widget.</returns>
public virtual bool OnGUI(DebugUI.Widget widget, DebugState state)
{
return true;
}
/// <summary>
/// Implement this to execute processing after UI rendering.
/// </summary>
/// <param name="widget">Widget that is going to be rendered.</param>
/// <param name="state">Debug State associated with the Debug Item.</param>
public virtual void End(DebugUI.Widget widget, DebugState state)
{ }
/// <summary>
/// Applies a value to the widget and the Debug State of the Debug Item.
/// </summary>
/// <param name="widget">Debug Item widget.</param>
/// <param name="state">Debug State associated with the Debug Item</param>
/// <param name="value">Input value.</param>
protected void Apply(DebugUI.IValueField widget, DebugState state, object value)
{
Undo.RegisterCompleteObjectUndo(state, $"Modified Value '{state.queryPath}'");
state.SetValue(value, widget);
widget.SetValue(value);
EditorUtility.SetDirty(state);
DebugState.m_CurrentDirtyState = state;
UnityEditorInternal.InternalEditorUtility.RepaintAllViews();
}
/// <summary>
/// Prepares the rendering Rect of the Drawer.
/// </summary>
/// <param name="height">Height of the rect.</param>
/// <param name="fullWidth">Whether to reserve full width for the element.</param>
/// <returns>Appropriate Rect for drawing.</returns>
protected Rect PrepareControlRect(float height = -1, bool fullWidth = false)
{
if (height < 0)
height = EditorGUIUtility.singleLineHeight;
var rect = GUILayoutUtility.GetRect(1f, 1f, height, height);
const float paddingLeft = 4f;
rect.width -= paddingLeft;
rect.xMin += paddingLeft;
EditorGUIUtility.labelWidth = fullWidth ? rect.width : rect.width / 2f;
return rect;
}
}
/// <summary>
/// Common class to help drawing fields
/// </summary>
/// <typeparam name="TValue">The internal value of the field</typeparam>
/// <typeparam name="TField">The type of the field widget</typeparam>
/// <typeparam name="TState">The state of the field</typeparam>
public abstract class DebugUIFieldDrawer<TValue, TField, TState> : DebugUIDrawer
where TField : DebugUI.Field<TValue>
where TState : DebugState
{
private TValue value { get; set; }
/// <summary>
/// Implement this to execute processing before UI rendering.
/// </summary>
/// <param name="widget">Widget that is going to be rendered.</param>
/// <param name="state">Debug State associated with the Debug Item.</param>
public override void Begin(DebugUI.Widget widget, DebugState state)
{
EditorGUI.BeginChangeCheck();
}
/// <summary>
/// Implement this to execute UI rendering.
/// </summary>
/// <param name="widget">Widget that is going to be rendered.</param>
/// <param name="state">Debug State associated with the Debug Item.</param>
/// <returns>Returns the state of the widget.</returns>
public override bool OnGUI(DebugUI.Widget widget, DebugState state)
{
value = DoGUI(
PrepareControlRect(),
EditorGUIUtility.TrTextContent(widget.displayName, widget.tooltip),
Cast<TField>(widget),
Cast<TState>(state)
);
return true;
}
/// <summary>
/// Does the field of the given type
/// </summary>
/// <param name="rect">The rect to draw the field</param>
/// <param name="label">The label for the field</param>
/// <param name="field">The field</param>
/// <param name="state">The state</param>
/// <returns>The current value from the UI</returns>
protected abstract TValue DoGUI(Rect rect, GUIContent label, TField field, TState state);
/// <summary>
/// Implement this to execute processing after UI rendering.
/// </summary>
/// <param name="widget">Widget that is going to be rendered.</param>
/// <param name="state">Debug State associated with the Debug Item.</param>
public override void End(DebugUI.Widget widget, DebugState state)
{
if (EditorGUI.EndChangeCheck())
{
var w = Cast<TField>(widget);
var s = Cast<TState>(state);
Apply(w, s, value);
DebugManagerWidgetUsedAnalytic.Send(widget.queryPath, value);
}
}
}
/// <summary>
/// Common class to help drawing widgets
/// </summary>
/// <typeparam name="TWidget">The widget</typeparam>
public abstract class DebugUIWidgetDrawer<TWidget> : DebugUIDrawer
where TWidget : DebugUI.Widget
{
/// <summary>
/// Implement this to execute processing before UI rendering.
/// </summary>
/// <param name="widget">Widget that is going to be rendered.</param>
/// <param name="state">Debug State associated with the Debug Item.</param>
public override void Begin(DebugUI.Widget widget, DebugState state)
{
}
/// <summary>
/// Implement this to execute UI rendering.
/// </summary>
/// <param name="widget">Widget that is going to be rendered.</param>
/// <param name="state">Debug State associated with the Debug Item.</param>
/// <returns>Returns the state of the widget.</returns>
public override bool OnGUI(DebugUI.Widget widget, DebugState state)
{
DoGUI(
PrepareControlRect(),
EditorGUIUtility.TrTextContent(widget.displayName, widget.tooltip),
Cast<TWidget>(widget)
);
return true;
}
/// <summary>
/// Does the field of the given type
/// </summary>
/// <param name="rect">The rect to draw the field</param>
/// <param name="label">The label for the field</param>
/// <param name="w">The widget</param>
protected abstract void DoGUI(Rect rect, GUIContent label, TWidget w);
/// <summary>
/// Implement this to execute processing after UI rendering.
/// </summary>
/// <param name="widget">Widget that is going to be rendered.</param>
/// <param name="state">Debug State associated with the Debug Item.</param>
public override void End(DebugUI.Widget widget, DebugState state)
{
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 3581d3ff7a65eee458feb865b7f29154
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,79 @@
using System;
using System.Linq;
using UnityEditor;
using UnityEditor.Rendering;
using UnityEditorInternal;
using UnityEngine.Rendering;
namespace UnityEngine.Rendering.UI
{
[CustomEditor(typeof(DebugUIHandlerCanvas))]
sealed class DebugUIHandlerCanvasEditor : Editor
{
SerializedProperty m_PanelPrefab;
SerializedProperty m_Prefabs;
ReorderableList m_PrefabList;
static string[] s_Types; // Assembly qualified names
static string[] s_DisplayTypes; // Pretty names
static DebugUIHandlerCanvasEditor()
{
s_Types = CoreUtils.GetAllTypesDerivedFrom<DebugUI.Widget>()
.Where(t => !t.IsAbstract)
.Select(t => t.AssemblyQualifiedName)
.ToArray();
s_DisplayTypes = new string[s_Types.Length];
for (int i = 0; i < s_Types.Length; i++)
s_DisplayTypes[i] = Type.GetType(s_Types[i]).Name;
}
void OnEnable()
{
var o = new PropertyFetcher<DebugUIHandlerCanvas>(serializedObject);
m_PanelPrefab = o.Find(x => x.panelPrefab);
m_Prefabs = o.Find(x => x.prefabs);
m_PrefabList = new ReorderableList(serializedObject, m_Prefabs, true, true, true, true)
{
drawHeaderCallback = rect => EditorGUI.LabelField(rect, "Widget Prefabs"),
drawElementCallback = (rect, index, isActive, isFocused) =>
{
var element = m_PrefabList.serializedProperty.GetArrayElementAtIndex(index);
rect.y += 2f;
const float kTypeWidth = 100f;
// Type selector
var typeProp = element.FindPropertyRelative("type");
int typeIndex = ArrayUtility.IndexOf(s_Types, typeProp.stringValue);
typeIndex = Mathf.Max(typeIndex, 0);
typeIndex = EditorGUI.Popup(new Rect(rect.x, rect.y, kTypeWidth, EditorGUIUtility.singleLineHeight), typeIndex, s_DisplayTypes);
typeProp.stringValue = s_Types[typeIndex];
// Prefab
EditorGUI.PropertyField(
new Rect(rect.x + kTypeWidth + 2f, rect.y, rect.width - kTypeWidth - 2f, EditorGUIUtility.singleLineHeight),
element.FindPropertyRelative("prefab"), GUIContent.none);
},
onSelectCallback = list =>
{
var prefab = list.serializedProperty.GetArrayElementAtIndex(list.index).FindPropertyRelative("prefab").objectReferenceValue as GameObject;
if (prefab)
EditorGUIUtility.PingObject(prefab.gameObject);
}
};
}
public override void OnInspectorGUI()
{
serializedObject.Update();
EditorGUILayout.PropertyField(m_PanelPrefab);
EditorGUILayout.Space();
m_PrefabList.DoLayoutList();
serializedObject.ApplyModifiedProperties();
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: f1d41f85c15ac6048a850b51ff36c098
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,626 @@
#if ENABLE_INPUT_SYSTEM && ENABLE_INPUT_SYSTEM_PACKAGE
#define USE_INPUT_SYSTEM
#endif
using System;
using System.Collections.Generic;
using System.Linq;
using UnityEditor.Callbacks;
using UnityEditorInternal;
using UnityEngine;
using UnityEngine.Assertions;
using UnityEngine.Rendering;
namespace UnityEditor.Rendering
{
#pragma warning disable 414
[Serializable]
sealed class WidgetStateDictionary : SerializedDictionary<string, DebugState> { }
sealed class DebugWindowSettings : ScriptableObject
{
// Keep these settings in a separate scriptable object so we can handle undo/redo on them
// without the rest of the debug window interfering
public int currentStateHash;
public int selectedPanel;
void OnEnable()
{
hideFlags = HideFlags.HideAndDontSave;
}
}
sealed class DebugWindow : EditorWindow, IHasCustomMenu
{
static Styles s_Styles;
static GUIStyle s_SplitterLeft;
static float splitterPos = 150f;
const float minSideBarWidth = 100;
const float minContentWidth = 100;
bool dragging = false;
[SerializeField]
WidgetStateDictionary m_WidgetStates;
[SerializeField]
DebugWindowSettings m_Settings;
[SerializeField]
int m_DebugTreeState;
bool m_IsDirty;
Vector2 m_PanelScroll;
Vector2 m_ContentScroll;
static bool s_TypeMapDirty;
static Dictionary<Type, Type> s_WidgetStateMap; // DebugUI.Widget type -> DebugState type
static Dictionary<Type, DebugUIDrawer> s_WidgetDrawerMap; // DebugUI.Widget type -> DebugUIDrawer
public static bool open
{
get => DebugManager.instance.displayEditorUI;
private set => DebugManager.instance.displayEditorUI = value;
}
[DidReloadScripts]
static void OnEditorReload()
{
s_TypeMapDirty = true;
//find if it where open, relink static event end propagate the info
open = (Resources.FindObjectsOfTypeAll<DebugWindow>()?.Length ?? 0) > 0;
}
static void RebuildTypeMaps()
{
// Map states to widget (a single state can map to several widget types if the value to
// serialize is the same)
var attrType = typeof(DebugStateAttribute);
var stateTypes = CoreUtils.GetAllTypesDerivedFrom<DebugState>()
.Where(
t => t.IsDefined(attrType, false)
&& !t.IsAbstract
);
s_WidgetStateMap = new Dictionary<Type, Type>();
foreach (var stateType in stateTypes)
{
var attr = (DebugStateAttribute)stateType.GetCustomAttributes(attrType, false)[0];
foreach (var t in attr.types)
s_WidgetStateMap.Add(t, stateType);
}
// Drawers
attrType = typeof(DebugUIDrawerAttribute);
var types = CoreUtils.GetAllTypesDerivedFrom<DebugUIDrawer>()
.Where(
t => t.IsDefined(attrType, false)
&& !t.IsAbstract
);
s_WidgetDrawerMap = new Dictionary<Type, DebugUIDrawer>();
foreach (var t in types)
{
var attr = (DebugUIDrawerAttribute)t.GetCustomAttributes(attrType, false)[0];
var inst = (DebugUIDrawer)Activator.CreateInstance(t);
s_WidgetDrawerMap.Add(attr.type, inst);
}
// Done
s_TypeMapDirty = false;
}
[MenuItem("Window/Analysis/Rendering Debugger", priority = 10005)]
static void Init()
{
var window = GetWindow<DebugWindow>();
window.titleContent = Styles.windowTitle;
}
[MenuItem("Window/Analysis/Rendering Debugger", validate = true)]
static bool ValidateMenuItem()
{
return RenderPipelineManager.currentPipeline != null;
}
void OnEnable()
{
open = true;
DebugManager.instance.refreshEditorRequested = false;
hideFlags = HideFlags.HideAndDontSave;
autoRepaintOnSceneChange = true;
if (m_Settings == null)
m_Settings = CreateInstance<DebugWindowSettings>();
// States are ScriptableObjects (necessary for Undo/Redo) but are not saved on disk so when the editor is closed then reopened, any existing debug window will have its states set to null
// Since we don't care about persistence in this case, we just re-init everything.
if (m_WidgetStates == null || !AreWidgetStatesValid())
m_WidgetStates = new WidgetStateDictionary();
if (s_WidgetStateMap == null || s_WidgetDrawerMap == null || s_TypeMapDirty)
RebuildTypeMaps();
Undo.undoRedoPerformed += OnUndoRedoPerformed;
DebugManager.instance.onSetDirty += MarkDirty;
// First init
m_DebugTreeState = DebugManager.instance.GetState();
UpdateWidgetStates();
EditorApplication.update -= Repaint;
var panels = DebugManager.instance.panels;
var selectedPanelIndex = m_Settings.selectedPanel;
if (selectedPanelIndex >= 0
&& selectedPanelIndex < panels.Count
&& panels[selectedPanelIndex].editorForceUpdate)
EditorApplication.update += Repaint;
}
// Note: this won't get called if the window is opened when the editor itself is closed
void OnDestroy()
{
open = false;
DebugManager.instance.onSetDirty -= MarkDirty;
Undo.ClearUndo(m_Settings);
DestroyWidgetStates();
}
public void DestroyWidgetStates()
{
if (m_WidgetStates == null)
return;
// Clear all the states from memory
foreach (var state in m_WidgetStates)
{
var s = state.Value;
Undo.ClearUndo(s); // Don't leave dangling states in the global undo/redo stack
DestroyImmediate(s);
}
m_WidgetStates.Clear();
}
public void ReloadWidgetStates()
{
if (m_WidgetStates == null)
return;
// Clear all the states from memory
foreach (var state in m_WidgetStates)
{
var widget = DebugManager.instance.GetItem(state.Key);
if (widget == null)
{
var s = state.Value;
Undo.ClearUndo(s); // Don't leave dangling states in the global undo/redo stack
DestroyImmediate(s);
}
}
UpdateWidgetStates();
}
bool AreWidgetStatesValid()
{
foreach (var state in m_WidgetStates)
{
if (state.Value == null)
{
return false;
}
}
return true;
}
void MarkDirty()
{
m_IsDirty = true;
}
// We use item states to keep a cached value of each serializable debug items in order to
// handle domain reloads, play mode entering/exiting and undo/redo
// Note: no removal of orphan states
void UpdateWidgetStates()
{
foreach (var panel in DebugManager.instance.panels)
UpdateWidgetStates(panel);
}
void UpdateWidgetStates(DebugUI.IContainer container)
{
// Skip runtime only containers, we won't draw them so no need to serialize them either
if (container is DebugUI.Widget actualWidget && actualWidget.isInactiveInEditor)
return;
// Recursively update widget states
foreach (var widget in container.children)
{
// Skip non-serializable widgets but still traverse them in case one of their
// children needs serialization support
if (widget is DebugUI.IValueField valueField)
{
// Skip runtime & readonly only items
if (widget.isInactiveInEditor)
return;
string guid = widget.queryPath;
if (!m_WidgetStates.TryGetValue(guid, out var state) || state == null)
{
var widgetType = widget.GetType();
if (s_WidgetStateMap.TryGetValue(widgetType, out Type stateType))
{
Assert.IsNotNull(stateType);
var inst = (DebugState)CreateInstance(stateType);
inst.queryPath = guid;
inst.SetValue(valueField.GetValue(), valueField);
m_WidgetStates[guid] = inst;
}
}
}
// Recurse if the widget is a container
if (widget is DebugUI.IContainer containerField)
UpdateWidgetStates(containerField);
}
}
public void ApplyStates(bool forceApplyAll = false)
{
if (!forceApplyAll && DebugState.m_CurrentDirtyState != null)
{
ApplyState(DebugState.m_CurrentDirtyState.queryPath, DebugState.m_CurrentDirtyState);
DebugState.m_CurrentDirtyState = null;
return;
}
foreach (var state in m_WidgetStates)
ApplyState(state.Key, state.Value);
DebugState.m_CurrentDirtyState = null;
}
void ApplyState(string queryPath, DebugState state)
{
if (!(DebugManager.instance.GetItem(queryPath) is DebugUI.IValueField widget))
return;
widget.SetValue(state.GetValue());
}
void OnUndoRedoPerformed()
{
int stateHash = ComputeStateHash();
// Something has been undone / redone, re-apply states to the debug tree
if (stateHash != m_Settings.currentStateHash)
{
ApplyStates(true);
m_Settings.currentStateHash = stateHash;
}
Repaint();
}
int ComputeStateHash()
{
unchecked
{
int hash = 13;
foreach (var state in m_WidgetStates)
hash = hash * 23 + state.Value.GetHashCode();
return hash;
}
}
void Update()
{
// If the render pipeline asset has been reloaded we force-refresh widget states in case
// some debug values need to be refresh/recreated as well (e.g. frame settings on HD)
if (DebugManager.instance.refreshEditorRequested)
{
ReloadWidgetStates();
m_IsDirty = true;
DebugManager.instance.refreshEditorRequested = false;
}
int? requestedPanelIndex = DebugManager.instance.GetRequestedEditorWindowPanelIndex();
if (requestedPanelIndex != null)
{
m_Settings.selectedPanel = requestedPanelIndex.Value;
}
int treeState = DebugManager.instance.GetState();
if (m_DebugTreeState != treeState || m_IsDirty)
{
UpdateWidgetStates();
ApplyStates();
m_DebugTreeState = treeState;
m_IsDirty = false;
}
}
void OnGUI()
{
if (s_Styles == null)
{
s_Styles = new Styles();
s_SplitterLeft = new GUIStyle();
}
var panels = DebugManager.instance.panels;
int itemCount = panels.Count(x => !x.isInactiveInEditor && x.children.Count(w => !w.isInactiveInEditor) > 0);
if (itemCount == 0)
{
EditorGUILayout.HelpBox("No debug item found.", MessageType.Info);
return;
}
// Background color
var wrect = position;
wrect.x = 0;
wrect.y = 0;
var oldColor = GUI.color;
GUI.color = s_Styles.skinBackgroundColor;
GUI.DrawTexture(wrect, EditorGUIUtility.whiteTexture);
GUI.color = oldColor;
GUILayout.BeginHorizontal(EditorStyles.toolbar);
GUILayout.FlexibleSpace();
if (GUILayout.Button(Styles.resetButtonContent, EditorStyles.toolbarButton))
{
DebugManager.instance.Reset();
DestroyWidgetStates();
UpdateWidgetStates();
InternalEditorUtility.RepaintAllViews();
}
GUILayout.EndHorizontal();
using (new EditorGUILayout.HorizontalScope())
{
// Side bar
using (var scrollScope = new EditorGUILayout.ScrollViewScope(m_PanelScroll, s_Styles.sectionScrollView, GUILayout.Width(splitterPos)))
{
if (m_Settings.selectedPanel >= panels.Count)
m_Settings.selectedPanel = 0;
// Validate container id
while (panels[m_Settings.selectedPanel].isInactiveInEditor || panels[m_Settings.selectedPanel].children.Count(x => !x.isInactiveInEditor) == 0)
{
m_Settings.selectedPanel++;
if (m_Settings.selectedPanel >= panels.Count)
m_Settings.selectedPanel = 0;
}
// Root children are containers
for (int i = 0; i < panels.Count; i++)
{
var panel = panels[i];
if (panel.isInactiveInEditor)
continue;
if (panel.children.Count(x => !x.isInactiveInEditor) == 0)
continue;
var elementRect = GUILayoutUtility.GetRect(EditorGUIUtility.TrTextContent(panel.displayName), s_Styles.sectionElement, GUILayout.ExpandWidth(true));
if (m_Settings.selectedPanel == i && Event.current.type == EventType.Repaint)
s_Styles.selected.Draw(elementRect, false, false, false, false);
EditorGUI.BeginChangeCheck();
GUI.Toggle(elementRect, m_Settings.selectedPanel == i, panel.displayName, s_Styles.sectionElement);
if (EditorGUI.EndChangeCheck())
{
Undo.RegisterCompleteObjectUndo(m_Settings, $"Debug Panel '{panel.displayName}' Selection");
var previousPanel = m_Settings.selectedPanel >= 0 && m_Settings.selectedPanel < panels.Count
? panels[m_Settings.selectedPanel]
: null;
if (previousPanel != null && previousPanel.editorForceUpdate && !panel.editorForceUpdate)
EditorApplication.update -= Repaint;
else if ((previousPanel == null || !previousPanel.editorForceUpdate) && panel.editorForceUpdate)
EditorApplication.update += Repaint;
m_Settings.selectedPanel = i;
}
}
m_PanelScroll = scrollScope.scrollPosition;
}
Rect splitterRect = new Rect(splitterPos - 3, 0, 6, Screen.height);
GUI.Box(splitterRect, "", s_SplitterLeft);
const float topMargin = 2f;
GUILayout.Space(topMargin);
// Main section - traverse current container
using (var changedScope = new EditorGUI.ChangeCheckScope())
{
using (new EditorGUILayout.VerticalScope())
{
const float leftMargin = 4f;
GUILayout.Space(leftMargin);
var selectedPanel = panels[m_Settings.selectedPanel];
using (var scrollScope = new EditorGUILayout.ScrollViewScope(m_ContentScroll))
{
TraverseContainerGUI(selectedPanel);
m_ContentScroll = scrollScope.scrollPosition;
}
}
if (changedScope.changed)
{
m_Settings.currentStateHash = ComputeStateHash();
DebugManager.instance.ReDrawOnScreenDebug();
}
}
// Splitter events
if (Event.current != null)
{
switch (Event.current.rawType)
{
case EventType.MouseDown:
if (splitterRect.Contains(Event.current.mousePosition))
{
dragging = true;
}
break;
case EventType.MouseDrag:
if (dragging)
{
splitterPos += Event.current.delta.x;
splitterPos = Mathf.Clamp(splitterPos, minSideBarWidth, Screen.width - minContentWidth);
Repaint();
}
break;
case EventType.MouseUp:
if (dragging)
{
dragging = false;
}
break;
}
}
EditorGUIUtility.AddCursorRect(splitterRect, MouseCursor.ResizeHorizontal);
}
}
void OnWidgetGUI(DebugUI.Widget widget)
{
if (widget.isInactiveInEditor || widget.isHidden)
return;
// State will be null for stateless widget
m_WidgetStates.TryGetValue(widget.queryPath, out DebugState state);
GUILayout.Space(4);
if (!s_WidgetDrawerMap.TryGetValue(widget.GetType(), out DebugUIDrawer drawer))
{
EditorGUILayout.LabelField("Drawer not found (" + widget.GetType() + ").");
}
else
{
drawer.Begin(widget, state);
if (drawer.OnGUI(widget, state))
{
if (widget is DebugUI.IContainer container)
TraverseContainerGUI(container);
}
drawer.End(widget, state);
}
}
void TraverseContainerGUI(DebugUI.IContainer container)
{
// /!\ SHAAAAAAAME ALERT /!\
// A container can change at runtime because of the way IMGUI works and how we handle
// onValueChanged on widget so we have to take this into account while iterating
try
{
foreach (var widget in container.children)
OnWidgetGUI(widget);
}
catch (InvalidOperationException)
{
Repaint();
}
}
public class Styles
{
public static float s_DefaultLabelWidth = 0.5f;
public static GUIContent windowTitle { get; } = EditorGUIUtility.TrTextContent("Rendering Debugger");
public static GUIContent resetButtonContent { get; } = EditorGUIUtility.TrTextContent("Reset");
public static GUIStyle foldoutHeaderStyle { get; } = new GUIStyle(EditorStyles.foldoutHeader)
{
fixedHeight = 20,
fontStyle = FontStyle.Bold,
margin = new RectOffset(0, 0, 0, 0)
};
public readonly GUIStyle sectionScrollView = "PreferencesSectionBox";
public readonly GUIStyle sectionElement = new GUIStyle("PreferencesSection");
public readonly GUIStyle selected = "OL SelectedRow";
public readonly GUIStyle sectionHeader = new GUIStyle(EditorStyles.largeLabel);
public readonly Color skinBackgroundColor;
public static GUIStyle centeredLeft = new GUIStyle(EditorStyles.label) { alignment = TextAnchor.MiddleLeft };
public static float singleRowHeight = EditorGUIUtility.singleLineHeight + EditorGUIUtility.standardVerticalSpacing;
public static int foldoutColumnWidth = 70;
public Styles()
{
Color textColorDarkSkin = new Color32(210, 210, 210, 255);
Color textColorLightSkin = new Color32(102, 102, 102, 255);
Color backgroundColorDarkSkin = new Color32(38, 38, 38, 128);
Color backgroundColorLightSkin = new Color32(128, 128, 128, 96);
sectionScrollView = new GUIStyle(sectionScrollView);
sectionScrollView.overflow.bottom += 1;
sectionElement.alignment = TextAnchor.MiddleLeft;
sectionHeader.fontStyle = FontStyle.Bold;
sectionHeader.fontSize = 18;
sectionHeader.margin.top = 10;
sectionHeader.margin.left += 1;
sectionHeader.normal.textColor = EditorGUIUtility.isProSkin ? textColorDarkSkin : textColorLightSkin;
skinBackgroundColor = EditorGUIUtility.isProSkin ? backgroundColorDarkSkin : backgroundColorLightSkin;
}
}
public void AddItemsToMenu(GenericMenu menu)
{
menu.AddItem(EditorGUIUtility.TrTextContent("Expand All"), false, () => SetExpanded(true));
menu.AddItem(EditorGUIUtility.TrTextContent("Collapse All"), false, () => SetExpanded(false));
}
void SetExpanded(bool value)
{
var panels = DebugManager.instance.panels;
foreach (var p in panels)
{
foreach (var w in p.children)
{
if (w.GetType() == typeof(DebugUI.Foldout))
{
if (m_WidgetStates.TryGetValue(w.queryPath, out DebugState state))
{
var foldout = (DebugUI.Foldout)w;
state.SetValue(value, foldout);
foldout.SetValue(value);
}
}
}
}
}
}
#pragma warning restore 414
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 41147144ff556e246b736135eb26f185
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,34 @@
using UnityEngine.Rendering.UI;
namespace UnityEditor.Rendering.UI
{
[CustomEditor(typeof(UIFoldout), true)]
sealed class UIFoldoutEditor : Editor
{
SerializedProperty m_IsOn;
SerializedProperty m_Content;
SerializedProperty m_ArrowClosed;
SerializedProperty m_ArrowOpened;
void OnEnable()
{
var o = new PropertyFetcher<UIFoldout>(serializedObject);
m_IsOn = o.Find("m_IsOn");
m_Content = o.Find(x => x.content);
m_ArrowClosed = o.Find(x => x.arrowClosed);
m_ArrowOpened = o.Find(x => x.arrowOpened);
}
public override void OnInspectorGUI()
{
serializedObject.Update();
EditorGUILayout.PropertyField(m_IsOn);
EditorGUILayout.PropertyField(m_Content);
EditorGUILayout.PropertyField(m_ArrowClosed);
EditorGUILayout.PropertyField(m_ArrowOpened);
serializedObject.ApplyModifiedProperties();
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: e89075dc59755d8479f34df9e20926e1
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant: