A game about forced loneliness, made by TACStudios
at master 537 lines 19 kB view raw
1using System; 2using System.Collections.Generic; 3#if UNITY_EDITOR 4using UnityEditor; 5#endif 6 7namespace UnityEngine.Rendering 8{ 9 public partial class DebugUI 10 { 11 /// <summary> 12 /// 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) 13 /// </summary> 14 public class Container : Widget, IContainer 15 { 16 const string k_IDToken = "#"; 17 internal bool hideDisplayName => string.IsNullOrEmpty(displayName) || displayName.StartsWith(k_IDToken); 18 19 /// <summary> 20 /// List of children. 21 /// </summary> 22 public ObservableList<Widget> children { get; private set; } 23 24 /// <summary> 25 /// Panel the container is attached to. 26 /// </summary> 27 public override Panel panel 28 { 29 get { return m_Panel; } 30 internal set 31 { 32 /// Frequenlty used panels do now own widgets 33 if (value != null && value.flags.HasFlag(DebugUI.Flags.FrequentlyUsed)) 34 return; 35 36 m_Panel = value; 37 38 // Bubble down 39 int numChildren = children.Count; 40 for (int i = 0; i < numChildren; i++) 41 children[i].panel = value; 42 } 43 } 44 45 /// <summary> 46 /// Constructor 47 /// </summary> 48 public Container() 49 : this(string.Empty, new ObservableList<Widget>()) 50 { 51 } 52 53 /// <summary> 54 /// Constructor for a container without header 55 /// </summary> 56 /// <param name="id">The id of the container</param> 57 public Container(string id) 58 : this($"{k_IDToken}{id}", new ObservableList<Widget>()) 59 { 60 } 61 62 /// <summary> 63 /// Constructor. 64 /// </summary> 65 /// <param name="displayName">Display name of the container.</param> 66 /// <param name="children">List of attached children.</param> 67 public Container(string displayName, ObservableList<Widget> children) 68 { 69 this.displayName = displayName; 70 this.children = children; 71 children.ItemAdded += OnItemAdded; 72 children.ItemRemoved += OnItemRemoved; 73 74 // Call OnAdded callback for already existing items to ensure their panel & parent are set 75 for (int i = 0; i < this.children.Count; i++) 76 OnItemAdded(this.children, new ListChangedEventArgs<Widget>(i, this.children[i])); 77 } 78 79 internal override void GenerateQueryPath() 80 { 81 base.GenerateQueryPath(); 82 83 int numChildren = children.Count; 84 for (int i = 0; i < numChildren; i++) 85 children[i].GenerateQueryPath(); 86 } 87 88 /// <summary> 89 /// Method called when a children is added. 90 /// </summary> 91 /// <param name="sender">Sender widget.</param> 92 /// <param name="e">List of added children.</param> 93 protected virtual void OnItemAdded(ObservableList<Widget> sender, ListChangedEventArgs<Widget> e) 94 { 95 if (e.item != null) 96 { 97 e.item.panel = m_Panel; 98 e.item.parent = this; 99 } 100 101 if (m_Panel != null) 102 m_Panel.SetDirty(); 103 } 104 105 /// <summary> 106 /// Method called when a children is removed. 107 /// </summary> 108 /// <param name="sender">Sender widget.</param> 109 /// <param name="e">List of removed children.</param> 110 protected virtual void OnItemRemoved(ObservableList<Widget> sender, ListChangedEventArgs<Widget> e) 111 { 112 if (e.item != null) 113 { 114 e.item.panel = null; 115 e.item.parent = null; 116 } 117 118 if (m_Panel != null) 119 m_Panel.SetDirty(); 120 } 121 122 /// <summary> 123 /// Returns the hash code of the widget. 124 /// </summary> 125 /// <returns>Hash code of the widget.</returns> 126 public override int GetHashCode() 127 { 128 int hash = 17; 129 hash = hash * 23 + queryPath.GetHashCode(); 130 hash = hash * 23 + isHidden.GetHashCode(); 131 132 int numChildren = children.Count; 133 for (int i = 0; i < numChildren; i++) 134 hash = hash * 23 + children[i].GetHashCode(); 135 136 return hash; 137 } 138 } 139 140 /// <summary> 141 /// Unity-like foldout that can be collapsed. 142 /// </summary> 143 public class Foldout : Container, IValueField 144 { 145 /// <summary> 146 /// Context menu item. 147 /// </summary> 148 public struct ContextMenuItem 149 { 150 /// <summary> 151 /// Name of the item displayed in context menu dropdown. 152 /// </summary> 153 public string displayName; 154 155 /// <summary> 156 /// Callback when context menu item is selected. 157 /// </summary> 158 public Action action; 159 } 160 161 /// <summary> 162 /// Always false. 163 /// </summary> 164 public bool isReadOnly { get { return false; } } 165 166 /// <summary> 167 /// Opened state of the foldout. 168 /// </summary> 169 public bool opened; 170 171 /// <summary> 172 /// Draw the foldout in full width using a header style. 173 /// </summary> 174 public bool isHeader; 175 176 /// <summary> 177 /// Optional list of context menu items. If the list is not provided, no context menu button will be displayed. 178 /// </summary> 179 public List<ContextMenuItem> contextMenuItems = null; 180 181 private bool m_Dirty; 182 private string[] m_ColumnLabels; 183 private string[] m_ColumnTooltips; 184 185 /// <summary> 186 /// List of columns labels. 187 /// </summary> 188 public string[] columnLabels 189 { 190 get => m_ColumnLabels; 191 set 192 { 193 m_ColumnLabels = value; 194 m_Dirty = true; 195 } 196 } 197 198 /// <summary> 199 /// List of columns label tooltips. 200 /// </summary> 201 public string[] columnTooltips 202 { 203 get => m_ColumnTooltips; 204 set 205 { 206 m_ColumnTooltips = value; 207 m_Dirty = true; 208 } 209 } 210 211 private List<GUIContent> m_RowContents = new(); 212 internal List<GUIContent> rowContents 213 { 214 get 215 { 216 if (m_Dirty) 217 { 218 if (m_ColumnTooltips == null) 219 { 220 m_ColumnTooltips = new string[m_ColumnLabels.Length]; 221 Array.Fill(columnTooltips, string.Empty); 222 } 223 else 224 { 225 if (m_ColumnTooltips.Length != m_ColumnLabels.Length) 226 throw new Exception( 227 $"Dimension for labels and tooltips on {nameof(DebugUI.Foldout)} - {displayName}, do not match"); 228 } 229 230 m_RowContents.Clear(); 231 for (int i = 0; i < m_ColumnLabels.Length; ++i) 232 { 233 string label = columnLabels[i] ?? string.Empty; 234 string tooltip = m_ColumnTooltips[i] ?? string.Empty; 235 m_RowContents.Add( 236#if UNITY_EDITOR 237 EditorGUIUtility.TrTextContent(label, tooltip) 238#else 239 new GUIContent(label, tooltip) 240#endif 241 ); 242 } 243 244 m_Dirty = false; 245 } 246 247 return m_RowContents; 248 } 249 } 250 251 /// <summary> 252 /// Constructor. 253 /// </summary> 254 public Foldout() : base() { } 255 /// <summary> 256 /// Constructor. 257 /// </summary> 258 /// <param name="displayName">Display name of the foldout.</param> 259 /// <param name="children">List of attached children.</param> 260 /// <param name="columnLabels">Optional list of column names.</param> 261 /// <param name="columnTooltips">Optional list of tooltips for column name labels.</param> 262 public Foldout(string displayName, ObservableList<Widget> children, string[] columnLabels = null, string[] columnTooltips = null) 263 : base(displayName, children) 264 { 265 this.columnLabels = columnLabels; 266 this.columnTooltips = columnTooltips; 267 } 268 269 /// <summary> 270 /// Get the opened state of the foldout. 271 /// </summary> 272 /// <returns>True if the foldout is opened.</returns> 273 public bool GetValue() => opened; 274 275 /// <summary> 276 /// Get the opened state of the foldout. 277 /// </summary> 278 /// <returns>True if the foldout is opened.</returns> 279 object IValueField.GetValue() => GetValue(); 280 281 /// <summary> 282 /// Set the opened state of the foldout. 283 /// </summary> 284 /// <param name="value">True to open the foldout, false to close it.</param> 285 public void SetValue(object value) => SetValue((bool)value); 286 287 /// <summary> 288 /// Validates the value of the widget before setting it. 289 /// </summary> 290 /// <param name="value">Input value.</param> 291 /// <returns>The validated value.</returns> 292 public object ValidateValue(object value) => value; 293 294 /// <summary> 295 /// Set the value of the widget. 296 /// </summary> 297 /// <param name="value">Input value.</param> 298 public void SetValue(bool value) => opened = value; 299 } 300 301 /// <summary> 302 /// Horizontal Layout Container. 303 /// </summary> 304 public class HBox : Container 305 { 306 /// <summary> 307 /// Constructor. 308 /// </summary> 309 public HBox() 310 { 311 displayName = "HBox"; 312 } 313 } 314 315 /// <summary> 316 /// Vertical Layout Container. 317 /// </summary> 318 public class VBox : Container 319 { 320 /// <summary> 321 /// Constructor. 322 /// </summary> 323 public VBox() 324 { 325 displayName = "VBox"; 326 } 327 } 328 329 /// <summary> 330 /// Array Container. 331 /// </summary> 332 public class Table : Container 333 { 334 static GUIStyle columnHeaderStyle = new GUIStyle() 335 { 336 alignment = TextAnchor.MiddleCenter 337 }; 338 339 /// <summary>Row Container.</summary> 340 public class Row : Foldout 341 { 342 /// <summary>Constructor.</summary> 343 public Row() { displayName = "Row"; } 344 } 345 346 /// <summary> 347 /// True if the table is read only. 348 /// </summary> 349 public bool isReadOnly = false; 350 351 /// <summary>Constructor.</summary> 352 public Table() { displayName = "Array"; } 353 354 /// <summary> 355 /// Set column visibility. 356 /// </summary> 357 /// <param name="index">Index of the column.</param> 358 /// <param name="visible">True if the column should be visible.</param> 359 public void SetColumnVisibility(int index, bool visible) 360 { 361#if UNITY_EDITOR 362 var header = Header; 363 if (index < 0 || index >= m_ColumnCount) 364 return; 365 366 index++; 367 if (header.IsColumnVisible(index) != visible) 368 { 369 var newVisibleColumns = new System.Collections.Generic.List<int>(header.state.visibleColumns); 370 if (newVisibleColumns.Contains(index)) 371 { 372 newVisibleColumns.Remove(index); 373 } 374 else 375 { 376 newVisibleColumns.Add(index); 377 newVisibleColumns.Sort(); 378 } 379 header.state.visibleColumns = newVisibleColumns.ToArray(); 380 381 var cols = header.state.columns; 382 for (int i = 0; i < cols.Length; i++) 383 cols[i].width = 50f; 384 header.ResizeToFit(); 385 } 386#else 387 var columns = VisibleColumns; 388 if (index < 0 || index > columns.Length) 389 return; 390 391 columns[index] = visible; 392#endif 393 } 394 395 /// <summary> 396 /// Get column visibility. 397 /// </summary> 398 /// <param name="index">Index of the column.</param> 399 /// <returns>True if the column is visible.</returns> 400 public bool GetColumnVisibility(int index) 401 { 402#if UNITY_EDITOR 403 var header = Header; 404 if (index < 0 || index >= m_ColumnCount) 405 return false; 406 407 return header.IsColumnVisible(index + 1); 408#else 409 var columns = VisibleColumns; 410 if (index < 0 || index > columns.Length) 411 return false; 412 413 return columns[index]; 414#endif 415 } 416 417#if UNITY_EDITOR 418 /// <summary> 419 /// The scroll position of the table. 420 /// </summary> 421 public Vector2 scroll = Vector2.zero; 422 423 int m_ColumnCount; 424 UnityEditor.IMGUI.Controls.MultiColumnHeader m_Header = null; 425 426 /// <summary> 427 /// The table header for drawing 428 /// </summary> 429 public UnityEditor.IMGUI.Controls.MultiColumnHeader Header 430 { 431 get 432 { 433 if (m_Header != null) 434 return m_Header; 435 436 if (children.Count != 0) 437 { 438 m_ColumnCount = ((Container)children[0]).children.Count; 439 for (int i = 1; i < children.Count; i++) 440 { 441 if (((Container)children[i]).children.Count != m_ColumnCount) 442 { 443 Debug.LogError("All rows must have the same number of children."); 444 return null; 445 } 446 } 447 } 448 449 UnityEditor.IMGUI.Controls.MultiColumnHeaderState.Column CreateColumn(string name, string tooltip) 450 { 451 var col = new UnityEditor.IMGUI.Controls.MultiColumnHeaderState.Column() 452 { 453 canSort = false, 454 headerTextAlignment = TextAlignment.Center, 455 headerContent = new GUIContent(name, tooltip ?? string.Empty) 456 }; 457 458 columnHeaderStyle.CalcMinMaxWidth(col.headerContent, out col.width, out float _); 459 col.width = Mathf.Min(col.width, 50f); 460 return col; 461 } 462 463 var cols = new UnityEditor.IMGUI.Controls.MultiColumnHeaderState.Column[m_ColumnCount + 1]; 464 cols[0] = CreateColumn(displayName, tooltip); 465 cols[0].allowToggleVisibility = false; 466 for (int i = 0; i < m_ColumnCount; i++) 467 { 468 var elem = ((Container) children[0]).children[i]; 469 cols[i + 1] = CreateColumn(elem.displayName, elem.tooltip); 470 } 471 472 var state = new UnityEditor.IMGUI.Controls.MultiColumnHeaderState(cols); 473 m_Header = new UnityEditor.IMGUI.Controls.MultiColumnHeader(state) { height = 23 }; 474 m_Header.ResizeToFit(); 475 return m_Header; 476 } 477 } 478#else 479 bool[] m_Header = null; 480 481 /// <summary> 482 /// The visible columns 483 /// </summary> 484 public bool[] VisibleColumns 485 { 486 get 487 { 488 if (m_Header != null) 489 return m_Header; 490 491 int columnCount = 0; 492 if (children.Count != 0) 493 { 494 columnCount = ((Container)children[0]).children.Count; 495 for (int i = 1; i < children.Count; i++) 496 { 497 if (((Container)children[i]).children.Count != columnCount) 498 { 499 Debug.LogError("All rows must have the same number of children."); 500 return null; 501 } 502 } 503 } 504 505 m_Header = new bool[columnCount]; 506 for (int i = 0; i < columnCount; i++) 507 m_Header[i] = true; 508 509 return m_Header; 510 } 511 } 512#endif 513 514 /// <summary> 515 /// Method called when a children is added. 516 /// </summary> 517 /// <param name="sender">Sender widget.</param> 518 /// <param name="e">List of added children.</param> 519 protected override void OnItemAdded(ObservableList<Widget> sender, ListChangedEventArgs<Widget> e) 520 { 521 base.OnItemAdded(sender, e); 522 m_Header = null; 523 } 524 525 /// <summary> 526 /// Method called when a children is removed. 527 /// </summary> 528 /// <param name="sender">Sender widget.</param> 529 /// <param name="e">List of removed children.</param> 530 protected override void OnItemRemoved(ObservableList<Widget> sender, ListChangedEventArgs<Widget> e) 531 { 532 base.OnItemRemoved(sender, e); 533 m_Header = null; 534 } 535 } 536 } 537}