using System; using System.Collections.Generic; using System.ComponentModel; namespace UnityEngine.Rendering { public partial class DebugUI { /// /// Base class for "container" type widgets, although it can be used on its own (if a display name is set then it'll behave as a group with a header) /// public class Container : Widget, IContainer { const string k_IDToken = "#"; internal bool hideDisplayName => string.IsNullOrEmpty(displayName) || displayName.StartsWith(k_IDToken); /// /// List of children. /// public ObservableList children { get; private set; } /// /// Panel the container is attached to. /// public override Panel panel { get { return m_Panel; } internal set { /// Frequenlty used panels do now own widgets if (value != null && value.flags.HasFlag(DebugUI.Flags.FrequentlyUsed)) return; m_Panel = value; // Bubble down int numChildren = children.Count; for (int i = 0; i < numChildren; i++) children[i].panel = value; } } /// /// Constructor /// public Container() : this(string.Empty, new ObservableList()) { } /// /// Constructor for a container without header /// /// The id of the container public Container(string id) : this($"{k_IDToken}{id}", new ObservableList()) { } /// /// Constructor. /// /// Display name of the container. /// List of attached children. public Container(string displayName, ObservableList children) { this.displayName = displayName; this.children = children; children.ItemAdded += OnItemAdded; children.ItemRemoved += OnItemRemoved; // Call OnAdded callback for already existing items to ensure their panel & parent are set for (int i = 0; i < this.children.Count; i++) OnItemAdded(this.children, new ListChangedEventArgs(i, this.children[i])); } internal override void GenerateQueryPath() { base.GenerateQueryPath(); int numChildren = children.Count; for (int i = 0; i < numChildren; i++) children[i].GenerateQueryPath(); } /// /// Method called when a children is added. /// /// Sender widget. /// List of added children. protected virtual void OnItemAdded(ObservableList sender, ListChangedEventArgs e) { if (e.item != null) { e.item.panel = m_Panel; e.item.parent = this; } if (m_Panel != null) m_Panel.SetDirty(); } /// /// Method called when a children is removed. /// /// Sender widget. /// List of removed children. protected virtual void OnItemRemoved(ObservableList sender, ListChangedEventArgs e) { if (e.item != null) { e.item.panel = null; e.item.parent = null; } if (m_Panel != null) m_Panel.SetDirty(); } /// /// Returns the hash code of the widget. /// /// Hash code of the widget. public override int GetHashCode() { int hash = 17; hash = hash * 23 + queryPath.GetHashCode(); hash = hash * 23 + isHidden.GetHashCode(); int numChildren = children.Count; for (int i = 0; i < numChildren; i++) hash = hash * 23 + children[i].GetHashCode(); return hash; } } /// /// Unity-like foldout that can be collapsed. /// public class Foldout : Container, IValueField { /// /// Context menu item. /// public struct ContextMenuItem { /// /// Name of the item displayed in context menu dropdown. /// public string displayName; /// /// Callback when context menu item is selected. /// public Action action; } /// /// Always false. /// public bool isReadOnly { get { return false; } } /// /// Opened state of the foldout. /// public bool opened; /// /// Draw the foldout in full width using a header style. /// public bool isHeader; /// /// Optional list of context menu items. If the list is not provided, no context menu button will be displayed. /// public List contextMenuItems = null; /// /// List of columns labels. /// public string[] columnLabels { get; set; } = null; /// /// List of columns label tooltips. /// public string[] columnTooltips { get; set; } = null; /// /// Constructor. /// public Foldout() : base() { } /// /// Constructor. /// /// Display name of the foldout. /// List of attached children. /// Optional list of column names. /// Optional list of tooltips for column name labels. public Foldout(string displayName, ObservableList children, string[] columnLabels = null, string[] columnTooltips = null) : base(displayName, children) { this.columnLabels = columnLabels; this.columnTooltips = columnTooltips; } /// /// Get the opened state of the foldout. /// /// True if the foldout is opened. public bool GetValue() => opened; /// /// Get the opened state of the foldout. /// /// True if the foldout is opened. object IValueField.GetValue() => GetValue(); /// /// Set the opened state of the foldout. /// /// True to open the foldout, false to close it. public void SetValue(object value) => SetValue((bool)value); /// /// Validates the value of the widget before setting it. /// /// Input value. /// The validated value. public object ValidateValue(object value) => value; /// /// Set the value of the widget. /// /// Input value. public void SetValue(bool value) => opened = value; } /// /// Horizontal Layout Container. /// public class HBox : Container { /// /// Constructor. /// public HBox() { displayName = "HBox"; } } /// /// Vertical Layout Container. /// public class VBox : Container { /// /// Constructor. /// public VBox() { displayName = "VBox"; } } /// /// Array Container. /// public class Table : Container { static GUIStyle columnHeaderStyle = new GUIStyle() { alignment = TextAnchor.MiddleCenter }; /// Row Container. public class Row : Foldout { /// Constructor. public Row() { displayName = "Row"; } } /// /// True if the table is read only. /// public bool isReadOnly = false; /// Constructor. public Table() { displayName = "Array"; } /// /// Set column visibility. /// /// Index of the column. /// True if the column should be visible. public void SetColumnVisibility(int index, bool visible) { #if UNITY_EDITOR var header = Header; if (index < 0 || index >= m_ColumnCount) return; index++; if (header.IsColumnVisible(index) != visible) { var newVisibleColumns = new System.Collections.Generic.List(header.state.visibleColumns); if (newVisibleColumns.Contains(index)) { newVisibleColumns.Remove(index); } else { newVisibleColumns.Add(index); newVisibleColumns.Sort(); } header.state.visibleColumns = newVisibleColumns.ToArray(); var cols = header.state.columns; for (int i = 0; i < cols.Length; i++) cols[i].width = 50f; header.ResizeToFit(); } #else var columns = VisibleColumns; if (index < 0 || index > columns.Length) return; columns[index] = visible; #endif } /// /// Get column visibility. /// /// Index of the column. /// True if the column is visible. public bool GetColumnVisibility(int index) { #if UNITY_EDITOR var header = Header; if (index < 0 || index >= m_ColumnCount) return false; return header.IsColumnVisible(index + 1); #else var columns = VisibleColumns; if (index < 0 || index > columns.Length) return false; return columns[index]; #endif } #if UNITY_EDITOR /// /// The scroll position of the table. /// public Vector2 scroll = Vector2.zero; int m_ColumnCount; UnityEditor.IMGUI.Controls.MultiColumnHeader m_Header = null; /// /// The table header for drawing /// public UnityEditor.IMGUI.Controls.MultiColumnHeader Header { get { if (m_Header != null) return m_Header; if (children.Count != 0) { m_ColumnCount = ((Container)children[0]).children.Count; for (int i = 1; i < children.Count; i++) { if (((Container)children[i]).children.Count != m_ColumnCount) { Debug.LogError("All rows must have the same number of children."); return null; } } } UnityEditor.IMGUI.Controls.MultiColumnHeaderState.Column CreateColumn(string name, string tooltip) { var col = new UnityEditor.IMGUI.Controls.MultiColumnHeaderState.Column() { canSort = false, headerTextAlignment = TextAlignment.Center, headerContent = new GUIContent(name, tooltip ?? string.Empty) }; columnHeaderStyle.CalcMinMaxWidth(col.headerContent, out col.width, out float _); col.width = Mathf.Min(col.width, 50f); return col; } var cols = new UnityEditor.IMGUI.Controls.MultiColumnHeaderState.Column[m_ColumnCount + 1]; cols[0] = CreateColumn(displayName, tooltip); cols[0].allowToggleVisibility = false; for (int i = 0; i < m_ColumnCount; i++) { var elem = ((Container) children[0]).children[i]; cols[i + 1] = CreateColumn(elem.displayName, elem.tooltip); } var state = new UnityEditor.IMGUI.Controls.MultiColumnHeaderState(cols); m_Header = new UnityEditor.IMGUI.Controls.MultiColumnHeader(state) { height = 23 }; m_Header.ResizeToFit(); return m_Header; } } #else bool[] m_Header = null; /// /// The visible columns /// public bool[] VisibleColumns { get { if (m_Header != null) return m_Header; int columnCount = 0; if (children.Count != 0) { columnCount = ((Container)children[0]).children.Count; for (int i = 1; i < children.Count; i++) { if (((Container)children[i]).children.Count != columnCount) { Debug.LogError("All rows must have the same number of children."); return null; } } } m_Header = new bool[columnCount]; for (int i = 0; i < columnCount; i++) m_Header[i] = true; return m_Header; } } #endif /// /// Method called when a children is added. /// /// Sender widget. /// List of added children. protected override void OnItemAdded(ObservableList sender, ListChangedEventArgs e) { base.OnItemAdded(sender, e); m_Header = null; } /// /// Method called when a children is removed. /// /// Sender widget. /// List of removed children. protected override void OnItemRemoved(ObservableList sender, ListChangedEventArgs e) { base.OnItemRemoved(sender, e); m_Header = null; } } } }