#if ENABLE_UIELEMENTS_MODULE && UNITY_ENABLE_CHECKS #define ENABLE_RENDERING_DEBUGGER_UI #endif using System; using System.Collections.Generic; using UnityEngine.Assertions; #if ENABLE_RENDERING_DEBUGGER_UI using UnityEngine.UIElements; #endif namespace UnityEngine.Rendering { #if ENABLE_RENDERING_DEBUGGER_UI internal static class DebugUIExtensions { public static List<(Label, VisualElement)> CreatePanels(List panels, DebugUI.Context context) { List<(Label, VisualElement)> returnList = new(); foreach (var p in panels) { var tab = new Label(p.displayName) { name = p.displayName + "_Tab", focusable = true }; tab.AddToClassList("debug-window-tab-item"); var panel = p.Create(context); returnList.Add((tab, panel)); } return returnList; } /// /// Utility to add a scheduled item to the panel's tracker so it can be disabled when the widget is not visible. /// /// Widget that contains the scheduler /// VisualElement whose scheduler is used to create the ScheduledItem /// Callback that creates the ScheduledItem. internal static void ScheduleTracked(this DebugUI.Widget widget, VisualElement element, Func scheduledItemCreator) { ScheduleTracked(widget, element, widget.m_Context, scheduledItemCreator); } /// /// Utility to add a scheduled item to the panel's tracker, using an explicit context for Add/Remove /// rather than reading widget.m_Context lazily. Use this when the same widget instance may be /// rendered into multiple panels (which mutates m_Context), and you want this scheduled item /// tracked under a specific context. /// internal static void ScheduleTracked(this DebugUI.Widget widget, VisualElement element, DebugUI.Context context, Func scheduledItemCreator) { element.RegisterCallback(_ => { var scheduledItem = scheduledItemCreator.Invoke(); DebugManager.instance.schedulerTracker.Add(context, widget, scheduledItem); scheduledItem.Pause(); // All schedulers start paused, and are enabled/disabled by DebugManager based on widget visibility. }); element.RegisterCallback(_ => { DebugManager.instance.schedulerTracker.Remove(context, widget, element); }); } public static bool IsAnyRuntimeContext(this DebugUI.Context context) { return (context == DebugUI.Context.Runtime) || (context == DebugUI.Context.RuntimePersistent); } } #endif /// /// The `DebugUI` class defines a collection of debug UI components that are useful for debugging and profiling Rendering features /// /// /// Widgets can be added to the UI, customized, and manipulated at runtime or during editor sessions. The class supports various /// widget types, including buttons, read-only value fields, and progress bars. It also provides mechanisms for organizing these /// widgets into containers. Each widget has a set of flags to control its visibility and behavior depending on whether it is /// used in the editor or at runtime. The widgets can also contain callbacks for conditional visibility at runtime. /// Important Notes: /// - Widgets can be nested inside containers, allowing for organized groupings of debug UI elements. /// - Widgets may be runtime-only, editor-only, or both, allowing them to behave differently depending on the application's /// state (e.g., whether it is in the editor or playing at runtime). /// - `DebugUI` also includes helper methods for widget initialization, such as compact initialization using the `NameAndTooltip` struct. /// /// This API lets you do the following: /// - Specify widget behavior such as "EditorOnly" and "RuntimeOnly". /// - Show dynamic data with optional formatting. /// - Specify delegate functions to show or hide widgets /// /// Related Resources: /// - [Debug UI Overview](https://docs.unity3d.com/6000.0/Documentation/Manual/urp/features/rendering-debugger.html) /// - [Rendering Debugger Controls](https://docs.unity3d.com/6000.0/Documentation/Manual/urp/features/rendering-debugger-add-controls.html). /// - [Using Rendering Debugger](https://docs.unity.cn/Packages/com.unity.render-pipelines.high-definition@16.0/manual/use-the-rendering-debugger.html). /// /// /// /// using UnityEngine; /// using UnityEngine.Rendering; /// /// [ExecuteInEditMode] /// public class DebugUIExample : MonoBehaviour /// { /// void OnEnable() /// { /// // Create a button widget that logs a message when you select it /// DebugUI.Button button = new DebugUI.Button /// { /// displayName = "Log Message Button", /// action = () => Debug.Log("Button selected") /// }; /// /// // Create a value widget that displays the current time /// DebugUI.Value timeValue = new DebugUI.Value /// { /// // Set the display label /// displayName = "Current Time", /// /// // Set the format for the time /// getter = () => System.DateTime.Now.ToString("HH:mm:ss"), /// /// // Set the value to refresh every second /// refreshRate = 1f /// }; /// /// // Add widgets a new Rendering Debugger panel /// var panel = DebugManager.instance.GetPanel("My Custom Panel", createIfNull: true); /// /// // Add the widgets to the panel /// panel.children.Add(button); /// panel.children.Add(timeValue); /// } /// } /// /// public partial class DebugUI { /// /// A column of checkboxes for enabling and disabling flags. /// [Flags] public enum Flags { /// /// None. /// None = 0, /// /// This widget is Editor only. /// EditorOnly = 1 << 1, /// /// This widget is Runtime only. /// RuntimeOnly = 1 << 2, /// /// This widget will force the Debug Editor Window refresh. /// [Obsolete("This is no longer used. #from(6000.5)")] EditorForceUpdate = 1 << 3, /// /// This widget will appear in the section "Frequently Used" /// [Obsolete("This is no longer used. #from(6000.5)")] FrequentlyUsed = 1 << 4 } /// /// Context in which the widget is used. /// public enum Context { /// Invalid context Invalid = -1, /// Editor context Editor = 0, /// Runtime context Runtime = 1, /// Runtime persistent context RuntimePersistent = 2, /// Count Count } /// /// Base class for all debug UI widgets. /// public abstract class Widget { /// /// The order of the widget /// public int order { get; set; } = 0; // Set to null until it's added to a panel, be careful /// /// Panels containing the widget. /// protected Panel m_Panel; /// /// Panels containing the widget. /// public virtual Panel panel { get { return m_Panel; } internal set { m_Panel = value; } } #if ENABLE_RENDERING_DEBUGGER_UI /// /// Creates the VisualElement for the widget that will be added to the panel UI. /// /// The widget's VisualElement protected abstract VisualElement Create(); /// /// Controls the way the widget is hidden. /// When set to false (default), the widget is removed from the layout (DisplayStyle.None). /// When set to true, the widget is not rendered but takes space in the layout (Visibility.Hidden). /// internal bool keepLayoutWhenHidden { private get; set; } internal bool m_IsHiddenBySearchFilter; internal string m_AdditionalSearchText = string.Empty; protected internal bool m_RequiresLegacyStateHandling = false; void UpdateElementVisibility() { if (m_IsHiddenBySearchFilter) { m_VisualElement.style.display = DisplayStyle.None; return; } if (keepLayoutWhenHidden) { m_VisualElement.style.display = DisplayStyle.Flex; m_VisualElement.style.visibility = isHidden ? Visibility.Hidden : Visibility.Visible; } else { m_VisualElement.style.display = isHidden ? DisplayStyle.None : DisplayStyle.Flex; } } int ComputeIndentationLevel() { int indentLevel = 0; Container currentParent = parent as Container; while (currentParent != null) { indentLevel++; currentParent = currentParent.parent as Container; } return indentLevel; } // Once the element is attached to the panel, recalculate the base field label width // whenever the window is resized, or the tab/panel divider is moved. internal void CustomAlignBaseFieldLabelWhenResized(VisualElement label) { if (m_Context == Context.Editor) { label.RegisterCallbackOnce(panelEvt => { // Use the tab container element (we want to exclude tabs) var contentPanel = panelEvt.destinationPanel.visualTree.Q("debug-window-tab-container"); contentPanel.RegisterCallback(geometryEvt => { CustomAlignBaseFieldLabel(label, geometryEvt.newRect.width); }); }); } } // We need to do base field label alignment manually in two cases: // 1. In runtime context, where UIE.BaseField alignment logic is disabled // 2. In any context, for elements that do not inherit from UIE.BaseField, but we still want // to align them, such as DebugUI.Value and DebugUI.ProgressBar. internal void CustomAlignBaseFieldLabel(VisualElement label, float width = 0f) { if (label != null) { int indentationLevel = ComputeIndentationLevel(); if (m_Context == Context.Runtime) { // NOTE: In the runtime we don't care about the window width, just use a fixed label width. const float kLabelBaseWidth = 330f; const float kIndentSize = 8f; // Should match .debug-window-container-content margin-left float totalPadding = indentationLevel*kIndentSize; label.style.minWidth = kLabelBaseWidth - totalPadding; } else { // This is trying to match the BaseField field alignment logic as closely as possible. // For the magic numbers & full logic see UIElements/Core/Controls/InputField/BaseField.cs const float kLabelWidthRatio = 0.45f; // Should match UIElements.BaseField m_LabelWidthRatio const float kLabelExtraPadding = 37.0f; // Should match UIElements.BaseField m_LabelExtraPadding const float kIndentSize = 8f; float totalPadding = indentationLevel*kIndentSize + kLabelExtraPadding; label.style.minWidth = Mathf.Ceil(width * kLabelWidthRatio) - totalPadding; } } } internal VisualElement ToVisualElement(Context context) { m_Context = context; m_VisualElement = Create(); if (m_VisualElement == null) { Debug.LogWarning($"Unable to create a Visual Element for type {GetType()}"); return null; } m_VisualElement.AddToClassList("unity-inspector-element"); m_VisualElement.name = displayName; m_VisualElement.tooltip = tooltip; #if UNITY_EDITOR // Support for legacy state handling if (this is ISupportsLegacyStateHandling legacyStateWidget) { m_RequiresLegacyStateHandling = legacyStateWidget.RequiresLegacyStateHandling(); } #endif UpdateElementVisibility(); this.ScheduleTracked(m_VisualElement, () => m_VisualElement.schedule.Execute(UpdateElementVisibility).Every(100)); // In runtime window, figure out indentation level to ensure field alignment if (context == Context.Runtime) { m_VisualElement.RegisterCallbackOnce(_ => { CustomAlignBaseFieldLabel(m_VisualElement.Q