using System; using System.Linq; using UnityEngine.Assertions; namespace UnityEngine.Rendering { /// <summary> /// Debug UI Class /// </summary> public partial class DebugUI { /// <summary> /// Flags for Debug UI widgets. /// </summary> [Flags] public enum Flags { /// <summary> /// None. /// </summary> None = 0, /// <summary> /// This widget is Editor only. /// </summary> EditorOnly = 1 << 1, /// <summary> /// This widget is Runtime only. /// </summary> RuntimeOnly = 1 << 2, /// <summary> /// This widget will force the Debug Editor Window refresh. /// </summary> EditorForceUpdate = 1 << 3, /// <summary> /// This widget will appear in the section "Frequently Used" /// </summary> FrequentlyUsed = 1 << 4 } /// <summary> /// Base class for all debug UI widgets. /// </summary> public abstract class Widget { // Set to null until it's added to a panel, be careful /// <summary> /// Panels containing the widget. /// </summary> protected Panel m_Panel; /// <summary> /// Panels containing the widget. /// </summary> public virtual Panel panel { get { return m_Panel; } internal set { m_Panel = value; } } /// <summary> /// Parent container. /// </summary> protected IContainer m_Parent; /// <summary> /// Parent container. /// </summary> public virtual IContainer parent { get { return m_Parent; } internal set { m_Parent = value; } } /// <summary> /// Flags for the widget. /// </summary> public Flags flags { get; set; } /// <summary> /// Display name. /// </summary> public string displayName { get; set; } /// <summary> /// Tooltip. /// </summary> public string tooltip { get; set; } /// <summary> /// Path of the widget. /// </summary> public string queryPath { get; private set; } /// <summary> /// True if the widget is Editor only. /// </summary> public bool isEditorOnly => flags.HasFlag(Flags.EditorOnly); /// <summary> /// True if the widget is Runtime only. /// </summary> public bool isRuntimeOnly => flags.HasFlag(Flags.RuntimeOnly); /// <summary> /// True if the widget is inactive in the editor (i.e. widget is runtime only and the application is not 'Playing'). /// </summary> public bool isInactiveInEditor => (isRuntimeOnly && !Application.isPlaying); /// <summary> /// Optional delegate that can be used to conditionally hide widgets at runtime (e.g. due to state of other widgets). /// </summary> public Func<bool> isHiddenCallback; /// <summary> /// If <see cref="isHiddenCallback">shouldHideDelegate</see> has been set and returns true, the widget is hidden from the UI. /// </summary> public bool isHidden => isHiddenCallback?.Invoke() ?? false; internal virtual void GenerateQueryPath() { queryPath = displayName.Trim(); if (m_Parent != null) queryPath = m_Parent.queryPath + " -> " + queryPath; } /// <summary> /// Returns the hash code of the widget. /// </summary> /// <returns>The hash code of the widget.</returns> public override int GetHashCode() { return queryPath.GetHashCode() ^ isHidden.GetHashCode(); } /// <summary> /// Helper struct to allow more compact initialization of widgets. /// </summary> public struct NameAndTooltip { /// <summary> /// The name /// </summary> public string name; /// <summary> /// The tooltip /// </summary> public string tooltip; } /// <summary> /// Helper setter to allow more compact initialization of widgets. /// </summary> public NameAndTooltip nameAndTooltip { set { displayName = value.name; tooltip = value.tooltip; } } } /// <summary> /// Interface for widgets that can contain other widgets. /// </summary> public interface IContainer { /// <summary> /// List of children of the container. /// </summary> ObservableList<Widget> children { get; } /// <summary> /// Display name of the container. /// </summary> string displayName { get; set; } /// <summary> /// Path of the container. /// </summary> string queryPath { get; } } /// <summary> /// Any widget that implements this will be considered for serialization (only if the setter is set and thus is not read-only) /// </summary> public interface IValueField { /// <summary> /// Return the value of the field. /// </summary> /// <returns>Value of the field.</returns> object GetValue(); /// <summary> /// Set the value of the field. /// </summary> /// <param name="value">Input value.</param> void SetValue(object value); /// <summary> /// Function used to validate the value when setting it. /// </summary> /// <param name="value"></param> /// <returns></returns> object ValidateValue(object value); } // Miscellaneous /// <summary> /// Button widget. /// </summary> public class Button : Widget { /// <summary> /// Action performed by the button. /// </summary> public Action action { get; set; } } /// <summary> /// Read only Value widget. /// </summary> public class Value : Widget { /// <summary> /// Getter for the Value. /// </summary> public Func<object> getter { get; set; } /// <summary> /// Refresh rate for the read-only value (runtime only) /// </summary> public float refreshRate = 0.1f; /// <summary> /// Optional C# numeric format string, using following syntax: "{0[:numericFormatString]}" /// See https://docs.microsoft.com/en-us/dotnet/standard/base-types/standard-numeric-format-strings /// and https://docs.microsoft.com/en-us/dotnet/standard/base-types/composite-formatting /// Example: 123.45678 with formatString "{0:F2} ms" --> "123.45 ms". /// </summary> public string formatString = null; /// <summary> /// Constructor. /// </summary> public Value() { displayName = ""; } /// <summary> /// Returns the value of the widget. /// </summary> /// <returns>The value of the widget.</returns> public virtual object GetValue() { Assert.IsNotNull(getter); return getter(); } /// <summary> /// Returns the formatted value string for display purposes. /// </summary> /// <param name="value">Value to be formatted.</param> /// <returns>The formatted value string.</returns> public virtual string FormatString(object value) { return string.IsNullOrEmpty(formatString) ? $"{value}" : string.Format(formatString, value); } } /// <summary> /// Progress bar value. /// </summary> public class ProgressBarValue : Value { /// <summary> /// Minimum value. /// </summary> public float min = 0f; /// <summary> /// Maximum value. /// </summary> public float max = 1f; /// <summary> /// Get the current progress string, remapped to [0, 1] range, representing the progress between min and max. /// </summary> /// <param name="value">Value to be formatted.</param> /// <returns>Formatted progress percentage string between 0% and 100%.</returns> public override string FormatString(object value) { static float Remap01(float v, float x0, float y0) => (v - x0) / (y0 - x0); float clamped = Mathf.Clamp((float)value, min, max); float percentage = Remap01(clamped, min, max); return $"{percentage:P1}"; } } /// <summary> /// Tuple of Value widgets for creating tabular UI. /// </summary> public class ValueTuple : Widget { /// <summary> /// Number of elements in the tuple. /// </summary> public int numElements { get { Assert.IsTrue(values.Length > 0); return values.Length; } } /// <summary> /// Value widgets. /// </summary> public Value[] values; /// <summary> /// Refresh rate for the read-only values (runtime only) /// </summary> public float refreshRate => values.FirstOrDefault()?.refreshRate ?? 0.1f; /// <summary> /// The currently pinned element index, or -1 if none are pinned. /// </summary> public int pinnedElementIndex = -1; } } }