using System; using System.Collections.Generic; using System.Linq; using System.Reflection; using System.Text.RegularExpressions; using UnityEngine.Assertions; namespace UnityEngine.Rendering { public partial class DebugUI { /// /// Generic field - will be serialized in the editor if it's not read-only /// /// The type of data managed by the field. public abstract class Field : Widget, IValueField { /// /// Getter for this field. /// public Func getter { get; set; } /// /// Setter for this field. /// public Action setter { get; set; } // This should be an `event` but they don't play nice with object initializers in the // version of C# we use. /// /// Callback used when the value of the field changes. /// public Action, T> onValueChanged; /// /// Function used to validate the value when updating the field. /// /// Input value. /// Validated value. object IValueField.ValidateValue(object value) { return ValidateValue((T)value); } /// /// Function used to validate the value when updating the field. /// /// Input value. /// Validated value. public virtual T ValidateValue(T value) { return value; } /// /// Get the value of the field. /// /// Value of the field. object IValueField.GetValue() { return GetValue(); } /// /// Get the value of the field. /// /// Value of the field. public T GetValue() { Assert.IsNotNull(getter); return getter(); } /// /// Set the value of the field. /// /// Input value. public void SetValue(object value) { SetValue((T)value); } /// /// Set the value of the field. /// /// Input value. public virtual void SetValue(T value) { Assert.IsNotNull(setter); var v = ValidateValue(value); if (v == null || !v.Equals(getter())) { setter(v); onValueChanged?.Invoke(this, v); } } } /// /// Boolean field. /// public class BoolField : Field { } /// /// An array of checkboxes that Unity displays in a horizontal row. /// public class HistoryBoolField : BoolField { /// /// History getter for this field. /// public Func[] historyGetter { get; set; } /// /// Depth of the field's history. /// public int historyDepth => historyGetter?.Length ?? 0; /// /// Get the value of the field at a certain history index. /// /// Index of the history to query. /// Value of the field at the provided history index. public bool GetHistoryValue(int historyIndex) { Assert.IsNotNull(historyGetter); Assert.IsTrue(historyIndex >= 0 && historyIndex < historyGetter.Length, "out of range historyIndex"); Assert.IsNotNull(historyGetter[historyIndex]); return historyGetter[historyIndex](); } } /// /// A slider for an integer. /// public class IntField : Field { /// /// Minimum value function. /// public Func min; /// /// Maximum value function. /// public Func max; // Runtime-only /// /// Step increment. /// public int incStep = 1; /// /// Step increment multiplier. /// public int intStepMult = 10; /// /// Function used to validate the value when updating the field. /// /// Input value. /// Validated value. public override int ValidateValue(int value) { if (min != null) value = Mathf.Max(value, min()); if (max != null) value = Mathf.Min(value, max()); return value; } } /// /// A slider for a positive integer. /// public class UIntField : Field { /// /// Minimum value function. /// public Func min; /// /// Maximum value function. /// public Func max; // Runtime-only /// /// Step increment. /// public uint incStep = 1u; /// /// Step increment multiplier. /// public uint intStepMult = 10u; /// /// Function used to validate the value when updating the field. /// /// Input value. /// Validated value. public override uint ValidateValue(uint value) { if (min != null) value = (uint)Mathf.Max((int)value, (int)min()); if (max != null) value = (uint)Mathf.Min((int)value, (int)max()); return value; } } /// /// A slider for a float. /// public class FloatField : Field { /// /// Minimum value function. /// public Func min; /// /// Maximum value function. /// public Func max; // Runtime-only /// /// Step increment. /// public float incStep = 0.1f; /// /// Step increment multiplier. /// public float incStepMult = 10f; /// /// Number of decimals. /// public int decimals = 3; /// /// Function used to validate the value when updating the field. /// /// Input value. /// Validated value. public override float ValidateValue(float value) { if (min != null) value = Mathf.Max(value, min()); if (max != null) value = Mathf.Min(value, max()); return value; } } /// /// Generic that stores enumNames and enumValues /// /// The inner type of the field public abstract class EnumField : Field { /// /// List of names of the enumerator entries. /// public GUIContent[] enumNames; private int[] m_EnumValues; /// /// List of values of the enumerator entries. /// public int[] enumValues { get => m_EnumValues; set { if (value?.Distinct().Count() != value?.Count()) Debug.LogWarning($"{displayName} - The values of the enum are duplicated, this might lead to a errors displaying the enum"); m_EnumValues = value; } } // Space-delimit PascalCase (https://stackoverflow.com/questions/155303/net-how-can-you-split-a-caps-delimited-string-into-an-array) static Regex s_NicifyRegEx = new("([a-z](?=[A-Z])|[A-Z](?=[A-Z][a-z]))", RegexOptions.Compiled); /// /// Automatically fills the enum names with a given /// /// The enum type protected void AutoFillFromType(Type enumType) { if (enumType == null || !enumType.IsEnum) throw new ArgumentException($"{nameof(enumType)} must not be null and it must be an Enum type"); using (ListPool.Get(out var tmpNames)) using (ListPool.Get(out var tmpValues)) { var enumEntries = enumType.GetFields(BindingFlags.Public | BindingFlags.Static) .Where(fieldInfo => !fieldInfo.IsDefined(typeof(ObsoleteAttribute)) && !fieldInfo.IsDefined(typeof(HideInInspector))); foreach (var fieldInfo in enumEntries) { var description = fieldInfo.GetCustomAttribute(); var displayName = new GUIContent(description == null ? s_NicifyRegEx.Replace(fieldInfo.Name, "$1 ") : description.displayName); tmpNames.Add(displayName); tmpValues.Add((int)Enum.Parse(enumType, fieldInfo.Name)); } enumNames = tmpNames.ToArray(); enumValues = tmpValues.ToArray(); } } } /// /// A dropdown that contains the values from an enum. /// public class EnumField : EnumField { internal int[] quickSeparators; private int[] m_Indexes; internal int[] indexes => m_Indexes ??= Enumerable.Range(0, enumNames?.Length ?? 0).ToArray(); /// /// Get the enumeration value index. /// public Func getIndex { get; set; } /// /// Set the enumeration value index. /// public Action setIndex { get; set; } /// /// Current enumeration value index. /// public int currentIndex { get => getIndex(); set => setIndex(value); } /// /// Generates enumerator values and names automatically based on the provided type. /// public Type autoEnum { set { AutoFillFromType(value); InitQuickSeparators(); } } internal void InitQuickSeparators() { var enumNamesPrefix = enumNames.Select(x => { string[] splitted = x.text.Split('/'); if (splitted.Length == 1) return ""; else return splitted[0]; }); quickSeparators = new int[enumNamesPrefix.Distinct().Count()]; string lastPrefix = null; for (int i = 0, wholeNameIndex = 0; i < quickSeparators.Length; ++i) { var currentTestedPrefix = enumNamesPrefix.ElementAt(wholeNameIndex); while (lastPrefix == currentTestedPrefix) { currentTestedPrefix = enumNamesPrefix.ElementAt(++wholeNameIndex); } lastPrefix = currentTestedPrefix; quickSeparators[i] = wholeNameIndex++; } } /// /// Set the value of the field. /// /// Input value. public override void SetValue(int value) { Assert.IsNotNull(setter); var validValue = ValidateValue(value); // There might be cases that the value does not map the index, look for the correct index var newCurrentIndex = Array.IndexOf(enumValues, validValue); if (currentIndex != newCurrentIndex && !validValue.Equals(getter())) { setter(validValue); onValueChanged?.Invoke(this, validValue); if (newCurrentIndex > -1) currentIndex = newCurrentIndex; } } } /// /// A dropdown that contains a list of Unity objects. /// public class ObjectPopupField : Field { /// /// Callback to obtain the elemtents of the pop up /// public Func> getObjects { get; set; } } /// /// Enumerator field with history. /// public class HistoryEnumField : EnumField { /// /// History getter for this field. /// public Func[] historyIndexGetter { get; set; } /// /// Depth of the field's history. /// public int historyDepth => historyIndexGetter?.Length ?? 0; /// /// Get the value of the field at a certain history index. /// /// Index of the history to query. /// Value of the field at the provided history index. public int GetHistoryValue(int historyIndex) { Assert.IsNotNull(historyIndexGetter); Assert.IsTrue(historyIndex >= 0 && historyIndex < historyIndexGetter.Length, "out of range historyIndex"); Assert.IsNotNull(historyIndexGetter[historyIndex]); return historyIndexGetter[historyIndex](); } } /// /// Bitfield enumeration field. /// public class BitField : EnumField { Type m_EnumType; /// /// Generates bitfield values and names automatically based on the provided type. /// public Type enumType { get => m_EnumType; set { m_EnumType = value; AutoFillFromType(value); } } } /// /// Color field. /// public class ColorField : Field { /// /// HDR color. /// public bool hdr = false; /// /// Show alpha of the color field. /// public bool showAlpha = true; // Editor-only /// /// Show the color picker. /// public bool showPicker = true; // Runtime-only /// /// Step increment. /// public float incStep = 0.025f; /// /// Step increment multiplier. /// public float incStepMult = 5f; /// /// Number of decimals. /// public int decimals = 3; /// /// Function used to validate the value when updating the field. /// /// Input value. /// Validated value. public override Color ValidateValue(Color value) { if (!hdr) { value.r = Mathf.Clamp01(value.r); value.g = Mathf.Clamp01(value.g); value.b = Mathf.Clamp01(value.b); value.a = Mathf.Clamp01(value.a); } return value; } } /// /// Vector2 field. /// public class Vector2Field : Field { // Runtime-only /// /// Step increment. /// public float incStep = 0.025f; /// /// Step increment multiplier. /// public float incStepMult = 10f; /// /// Number of decimals. /// public int decimals = 3; } /// /// Vector3 field. /// public class Vector3Field : Field { // Runtime-only /// /// Step increment. /// public float incStep = 0.025f; /// /// Step increment multiplier. /// public float incStepMult = 10f; /// /// Number of decimals. /// public int decimals = 3; } /// /// Vector4 field. /// public class Vector4Field : Field { // Runtime-only /// /// Step increment. /// public float incStep = 0.025f; /// /// Step increment multiplier. /// public float incStepMult = 10f; /// /// Number of decimals. /// public int decimals = 3; } /// /// A field for selecting a Unity object. /// public class ObjectField : Field { /// /// Object type. /// public Type type = typeof(Object); } /// /// A list of fields for selecting Unity objects. /// public class ObjectListField : Field { /// /// Objects type. /// public Type type = typeof(Object); } /// /// A read-only message box with an icon. /// public class MessageBox : Widget { /// /// Label style defines text color and background. /// public enum Style { /// /// Info category /// Info, /// /// Warning category /// Warning, /// /// Error category /// Error } /// /// Style used to render displayName. /// public Style style = Style.Info; /// /// Message Callback to feed the new message to the widget /// public Func messageCallback = null; /// /// This obtains the message from the display name or from the message callback if it is not null /// public string message => messageCallback == null ? displayName : messageCallback(); } /// /// Widget that will show into the Runtime UI only /// Warning the user if the Runtime Debug Shaders variants are being stripped from the build. /// public class RuntimeDebugShadersMessageBox : MessageBox { /// /// Constructs a /// public RuntimeDebugShadersMessageBox() { displayName = "Warning: the debug shader variants are missing. Ensure that the \"Strip Runtime Debug Shaders\" option is disabled in the SRP Graphics Settings."; style = DebugUI.MessageBox.Style.Warning; isHiddenCallback = () => { #if !UNITY_EDITOR if (GraphicsSettings.TryGetRenderPipelineSettings(out var shaderStrippingSetting)) return !shaderStrippingSetting.stripRuntimeDebugShaders; #endif return true; }; } } } }