A game about forced loneliness, made by TACStudios
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}