using System; using System.Collections.Generic; using System.Collections.ObjectModel; using UnityEngine.Assertions; using UnityEngine.Rendering.UI; using UnityEngine.UI; namespace UnityEngine.Rendering { using UnityObject = UnityEngine.Object; /// /// IDebugData interface. /// public interface IDebugData { /// Get the reset callback for this DebugData /// The reset callback Action GetReset(); //Action GetLoad(); //Action GetSave(); } /// /// Manager class for the Debug Window. /// public sealed partial class DebugManager { static readonly Lazy s_Instance = new Lazy(() => new DebugManager()); /// /// Global instance of the DebugManager. /// public static DebugManager instance => s_Instance.Value; ReadOnlyCollection m_ReadOnlyPanels; readonly List m_Panels = new List(); void UpdateReadOnlyCollection() { m_Panels.Sort(); m_ReadOnlyPanels = m_Panels.AsReadOnly(); } /// /// List of currently registered debug panels. /// public ReadOnlyCollection panels { get { if (m_ReadOnlyPanels == null) UpdateReadOnlyCollection(); return m_ReadOnlyPanels; } } /// /// Callback called when the runtime UI changed. /// public event Action onDisplayRuntimeUIChanged = delegate { }; /// /// Callback called when the debug window is dirty. /// public event Action onSetDirty = delegate { }; event Action resetData; /// /// Force an editor request. /// public bool refreshEditorRequested; int? m_RequestedPanelIndex; GameObject m_Root; DebugUIHandlerCanvas m_RootUICanvas; GameObject m_PersistentRoot; DebugUIHandlerPersistentCanvas m_RootUIPersistentCanvas; // Knowing if the DebugWindows is open, is done by event as it is in another assembly. // The DebugWindows is responsible to link its event to ToggleEditorUI. bool m_EditorOpen = false; /// /// Is the debug editor window open. /// public bool displayEditorUI => m_EditorOpen; /// /// Toggle the debug window. /// /// State of the debug window. public void ToggleEditorUI(bool open) => m_EditorOpen = open; private bool m_EnableRuntimeUI = true; /// /// Controls whether runtime UI can be enabled. When this is set to false, there will be no overhead /// from debug GameObjects or runtime initialization. /// public bool enableRuntimeUI { get => m_EnableRuntimeUI; set { if (value != m_EnableRuntimeUI) { m_EnableRuntimeUI = value; DebugUpdater.SetEnabled(value); } } } /// /// Displays the runtime version of the debug window. /// public bool displayRuntimeUI { get => m_Root != null && m_Root.activeInHierarchy; set { if (value) { m_Root = UnityObject.Instantiate(Resources.Load("DebugUICanvas")).gameObject; m_Root.name = "[Debug Canvas]"; m_Root.transform.localPosition = Vector3.zero; m_RootUICanvas = m_Root.GetComponent(); #if UNITY_ANDROID || UNITY_IPHONE || UNITY_TVOS || UNITY_SWITCH var canvasScaler = m_Root.GetComponent(); canvasScaler.uiScaleMode = CanvasScaler.ScaleMode.ScaleWithScreenSize; #endif m_Root.SetActive(true); } else { CoreUtils.Destroy(m_Root); m_Root = null; m_RootUICanvas = null; } onDisplayRuntimeUIChanged(value); DebugUpdater.HandleInternalEventSystemComponents(value); } } /// /// Displays the persistent runtime debug window. /// public bool displayPersistentRuntimeUI { get => m_RootUIPersistentCanvas != null && m_PersistentRoot.activeInHierarchy; set { if (value) { EnsurePersistentCanvas(); } else { CoreUtils.Destroy(m_PersistentRoot); m_PersistentRoot = null; m_RootUIPersistentCanvas = null; } } } DebugManager() { #if DEVELOPMENT_BUILD || UNITY_EDITOR RegisterInputs(); RegisterActions(); #endif } /// /// Refresh the debug window. /// public void RefreshEditor() { refreshEditorRequested = true; } /// /// Reset the debug window. /// public void Reset() { resetData?.Invoke(); ReDrawOnScreenDebug(); } /// /// Request the runtime debug UI be redrawn on the next update. /// public void ReDrawOnScreenDebug() { if (displayRuntimeUI) m_RootUICanvas?.RequestHierarchyReset(); } /// /// Register debug data. /// /// Data to be registered. public void RegisterData(IDebugData data) => resetData += data.GetReset(); /// /// Register debug data. /// /// Data to be registered. public void UnregisterData(IDebugData data) => resetData -= data.GetReset(); /// /// Get hashcode state of the Debug Window. /// /// public int GetState() { int hash = 17; foreach (var panel in m_Panels) hash = hash * 23 + panel.GetHashCode(); return hash; } internal void RegisterRootCanvas(DebugUIHandlerCanvas root) { Assert.IsNotNull(root); m_Root = root.gameObject; m_RootUICanvas = root; } internal void ChangeSelection(DebugUIHandlerWidget widget, bool fromNext) { m_RootUICanvas.ChangeSelection(widget, fromNext); } internal void SetScrollTarget(DebugUIHandlerWidget widget) { if (m_RootUICanvas != null) m_RootUICanvas.SetScrollTarget(widget); } void EnsurePersistentCanvas() { if (m_RootUIPersistentCanvas == null) { var uiManager = UnityObject.FindObjectOfType(); if (uiManager == null) { m_PersistentRoot = UnityObject.Instantiate(Resources.Load("DebugUIPersistentCanvas")).gameObject; m_PersistentRoot.name = "[Debug Canvas - Persistent]"; m_PersistentRoot.transform.localPosition = Vector3.zero; } else { m_PersistentRoot = uiManager.gameObject; } m_RootUIPersistentCanvas = m_PersistentRoot.GetComponent(); } } internal void TogglePersistent(DebugUI.Widget widget) { if (widget == null) return; var valueWidget = widget as DebugUI.Value; if (valueWidget == null) { Debug.Log("Only DebugUI.Value items can be made persistent."); return; } EnsurePersistentCanvas(); m_RootUIPersistentCanvas.Toggle(valueWidget); } void OnPanelDirty(DebugUI.Panel panel) { onSetDirty(); } /// /// Request DebugWindow to open the specified panel. /// /// Index of the debug window panel to activate. public void RequestEditorWindowPanelIndex(int index) { // Similar to RefreshEditor(), this function is required to bypass a dependency problem where DebugWindow // cannot be accessed from the Core.Runtime assembly. Should there be a better way to allow editor-dependent // features in DebugUI? m_RequestedPanelIndex = index; } internal int? GetRequestedEditorWindowPanelIndex() { int? requestedIndex = m_RequestedPanelIndex; m_RequestedPanelIndex = null; return requestedIndex; } // TODO: Optimally we should use a query path here instead of a display name /// /// Returns a debug panel. /// /// Name of the debug panel. /// Create the panel if it does not exists. /// Group index. /// Replace an existing panel. /// public DebugUI.Panel GetPanel(string displayName, bool createIfNull = false, int groupIndex = 0, bool overrideIfExist = false) { DebugUI.Panel p = null; foreach (var panel in m_Panels) { if (panel.displayName == displayName) { p = panel; break; } } if (p != null) { if (overrideIfExist) { p.onSetDirty -= OnPanelDirty; RemovePanel(p); p = null; } else return p; } if (createIfNull) { p = new DebugUI.Panel { displayName = displayName, groupIndex = groupIndex }; p.onSetDirty += OnPanelDirty; m_Panels.Add(p); UpdateReadOnlyCollection(); } return p; } // TODO: Use a query path here as well instead of a display name /// /// Remove a debug panel. /// /// Name of the debug panel to remove. public void RemovePanel(string displayName) { DebugUI.Panel panel = null; foreach (var p in m_Panels) { if (p.displayName == displayName) { p.onSetDirty -= OnPanelDirty; panel = p; break; } } RemovePanel(panel); } /// /// Remove a debug panel. /// /// Reference to the debug panel to remove. public void RemovePanel(DebugUI.Panel panel) { if (panel == null) return; m_Panels.Remove(panel); UpdateReadOnlyCollection(); } /// /// Get a Debug Item. /// /// Path of the debug item. /// Reference to the requested debug item. public DebugUI.Widget GetItem(string queryPath) { foreach (var panel in m_Panels) { var w = GetItem(queryPath, panel); if (w != null) return w; } return null; } /// /// Get a debug item from a specific container. /// /// Path of the debug item. /// Container to query. /// Reference to the requested debug item. DebugUI.Widget GetItem(string queryPath, DebugUI.IContainer container) { foreach (var child in container.children) { if (child.queryPath == queryPath) return child; var containerChild = child as DebugUI.IContainer; if (containerChild != null) { var w = GetItem(queryPath, containerChild); if (w != null) return w; } } return null; } } }