A game about forced loneliness, made by TACStudios
1using System;
2using System.Collections.Generic;
3using System.Linq;
4using System.Reflection;
5using System.Text.RegularExpressions;
6using UnityEngine.Assertions;
7
8namespace UnityEngine.Rendering
9{
10 public partial class DebugUI
11 {
12 /// <summary>
13 /// Generic field - will be serialized in the editor if it's not read-only
14 /// </summary>
15 /// <typeparam name="T">The type of data managed by the field.</typeparam>
16 public abstract class Field<T> : Widget, IValueField
17 {
18 /// <summary>
19 /// Getter for this field.
20 /// </summary>
21 public Func<T> getter { get; set; }
22 /// <summary>
23 /// Setter for this field.
24 /// </summary>
25 public Action<T> setter { get; set; }
26
27 // This should be an `event` but they don't play nice with object initializers in the
28 // version of C# we use.
29 /// <summary>
30 /// Callback used when the value of the field changes.
31 /// </summary>
32 public Action<Field<T>, T> onValueChanged;
33
34 /// <summary>
35 /// Function used to validate the value when updating the field.
36 /// </summary>
37 /// <param name="value">Input value.</param>
38 /// <returns>Validated value.</returns>
39 object IValueField.ValidateValue(object value)
40 {
41 return ValidateValue((T)value);
42 }
43
44 /// <summary>
45 /// Function used to validate the value when updating the field.
46 /// </summary>
47 /// <param name="value">Input value.</param>
48 /// <returns>Validated value.</returns>
49 public virtual T ValidateValue(T value)
50 {
51 return value;
52 }
53
54 /// <summary>
55 /// Get the value of the field.
56 /// </summary>
57 /// <returns>Value of the field.</returns>
58 object IValueField.GetValue()
59 {
60 return GetValue();
61 }
62
63 /// <summary>
64 /// Get the value of the field.
65 /// </summary>
66 /// <returns>Value of the field.</returns>
67 public T GetValue()
68 {
69 Assert.IsNotNull(getter);
70 return getter();
71 }
72
73 /// <summary>
74 /// Set the value of the field.
75 /// </summary>
76 /// <param name="value">Input value.</param>
77 public void SetValue(object value)
78 {
79 SetValue((T)value);
80 }
81
82 /// <summary>
83 /// Set the value of the field.
84 /// </summary>
85 /// <param name="value">Input value.</param>
86 public virtual void SetValue(T value)
87 {
88 Assert.IsNotNull(setter);
89 var v = ValidateValue(value);
90
91 if (v == null || !v.Equals(getter()))
92 {
93 setter(v);
94 onValueChanged?.Invoke(this, v);
95 }
96 }
97 }
98
99 /// <summary>
100 /// Boolean field.
101 /// </summary>
102 public class BoolField : Field<bool> { }
103 /// <summary>
104 /// An array of checkboxes that Unity displays in a horizontal row.
105 /// </summary>
106 public class HistoryBoolField : BoolField
107 {
108 /// <summary>
109 /// History getter for this field.
110 /// </summary>
111 public Func<bool>[] historyGetter { get; set; }
112 /// <summary>
113 /// Depth of the field's history.
114 /// </summary>
115 public int historyDepth => historyGetter?.Length ?? 0;
116 /// <summary>
117 /// Get the value of the field at a certain history index.
118 /// </summary>
119 /// <param name="historyIndex">Index of the history to query.</param>
120 /// <returns>Value of the field at the provided history index.</returns>
121 public bool GetHistoryValue(int historyIndex)
122 {
123 Assert.IsNotNull(historyGetter);
124 Assert.IsTrue(historyIndex >= 0 && historyIndex < historyGetter.Length, "out of range historyIndex");
125 Assert.IsNotNull(historyGetter[historyIndex]);
126 return historyGetter[historyIndex]();
127 }
128 }
129
130 /// <summary>
131 /// A slider for an integer.
132 /// </summary>
133 public class IntField : Field<int>
134 {
135 /// <summary>
136 /// Minimum value function.
137 /// </summary>
138 public Func<int> min;
139 /// <summary>
140 /// Maximum value function.
141 /// </summary>
142 public Func<int> max;
143
144 // Runtime-only
145 /// <summary>
146 /// Step increment.
147 /// </summary>
148 public int incStep = 1;
149 /// <summary>
150 /// Step increment multiplier.
151 /// </summary>
152 public int intStepMult = 10;
153
154 /// <summary>
155 /// Function used to validate the value when updating the field.
156 /// </summary>
157 /// <param name="value">Input value.</param>
158 /// <returns>Validated value.</returns>
159 public override int ValidateValue(int value)
160 {
161 if (min != null) value = Mathf.Max(value, min());
162 if (max != null) value = Mathf.Min(value, max());
163 return value;
164 }
165 }
166
167 /// <summary>
168 /// A slider for a positive integer.
169 /// </summary>
170 public class UIntField : Field<uint>
171 {
172 /// <summary>
173 /// Minimum value function.
174 /// </summary>
175 public Func<uint> min;
176 /// <summary>
177 /// Maximum value function.
178 /// </summary>
179 public Func<uint> max;
180
181 // Runtime-only
182 /// <summary>
183 /// Step increment.
184 /// </summary>
185 public uint incStep = 1u;
186 /// <summary>
187 /// Step increment multiplier.
188 /// </summary>
189 public uint intStepMult = 10u;
190
191 /// <summary>
192 /// Function used to validate the value when updating the field.
193 /// </summary>
194 /// <param name="value">Input value.</param>
195 /// <returns>Validated value.</returns>
196 public override uint ValidateValue(uint value)
197 {
198 if (min != null) value = (uint)Mathf.Max((int)value, (int)min());
199 if (max != null) value = (uint)Mathf.Min((int)value, (int)max());
200 return value;
201 }
202 }
203
204 /// <summary>
205 /// A slider for a float.
206 /// </summary>
207 public class FloatField : Field<float>
208 {
209 /// <summary>
210 /// Minimum value function.
211 /// </summary>
212 public Func<float> min;
213 /// <summary>
214 /// Maximum value function.
215 /// </summary>
216 public Func<float> max;
217
218 // Runtime-only
219 /// <summary>
220 /// Step increment.
221 /// </summary>
222 public float incStep = 0.1f;
223 /// <summary>
224 /// Step increment multiplier.
225 /// </summary>
226 public float incStepMult = 10f;
227 /// <summary>
228 /// Number of decimals.
229 /// </summary>
230 public int decimals = 3;
231
232 /// <summary>
233 /// Function used to validate the value when updating the field.
234 /// </summary>
235 /// <param name="value">Input value.</param>
236 /// <returns>Validated value.</returns>
237 public override float ValidateValue(float value)
238 {
239 if (min != null) value = Mathf.Max(value, min());
240 if (max != null) value = Mathf.Min(value, max());
241 return value;
242 }
243 }
244
245 /// <summary>
246 /// Generic <see cref="EnumField"/> that stores enumNames and enumValues
247 /// </summary>
248 /// <typeparam name="T">The inner type of the field</typeparam>
249 public abstract class EnumField<T> : Field<T>
250 {
251 /// <summary>
252 /// List of names of the enumerator entries.
253 /// </summary>
254 public GUIContent[] enumNames;
255
256 private int[] m_EnumValues;
257
258 /// <summary>
259 /// List of values of the enumerator entries.
260 /// </summary>
261 public int[] enumValues
262 {
263 get => m_EnumValues;
264 set
265 {
266 if (value?.Distinct().Count() != value?.Count())
267 Debug.LogWarning($"{displayName} - The values of the enum are duplicated, this might lead to a errors displaying the enum");
268 m_EnumValues = value;
269 }
270 }
271
272
273 // Space-delimit PascalCase (https://stackoverflow.com/questions/155303/net-how-can-you-split-a-caps-delimited-string-into-an-array)
274 static Regex s_NicifyRegEx = new("([a-z](?=[A-Z])|[A-Z](?=[A-Z][a-z]))", RegexOptions.Compiled);
275
276 /// <summary>
277 /// Automatically fills the enum names with a given <see cref="Type"/>
278 /// </summary>
279 /// <param name="enumType">The enum type</param>
280 protected void AutoFillFromType(Type enumType)
281 {
282 if (enumType == null || !enumType.IsEnum)
283 throw new ArgumentException($"{nameof(enumType)} must not be null and it must be an Enum type");
284
285 using (ListPool<GUIContent>.Get(out var tmpNames))
286 using (ListPool<int>.Get(out var tmpValues))
287 {
288 var enumEntries = enumType.GetFields(BindingFlags.Public | BindingFlags.Static)
289 .Where(fieldInfo => !fieldInfo.IsDefined(typeof(ObsoleteAttribute)) && !fieldInfo.IsDefined(typeof(HideInInspector)));
290 foreach (var fieldInfo in enumEntries)
291 {
292 var description = fieldInfo.GetCustomAttribute<InspectorNameAttribute>();
293 var displayName = new GUIContent(description == null ? s_NicifyRegEx.Replace(fieldInfo.Name, "$1 ") : description.displayName);
294 tmpNames.Add(displayName);
295 tmpValues.Add((int)Enum.Parse(enumType, fieldInfo.Name));
296 }
297 enumNames = tmpNames.ToArray();
298 enumValues = tmpValues.ToArray();
299 }
300 }
301 }
302
303 /// <summary>
304 /// A dropdown that contains the values from an enum.
305 /// </summary>
306 public class EnumField : EnumField<int>
307 {
308 internal int[] quickSeparators;
309
310 private int[] m_Indexes;
311 internal int[] indexes => m_Indexes ??= Enumerable.Range(0, enumNames?.Length ?? 0).ToArray();
312
313 /// <summary>
314 /// Get the enumeration value index.
315 /// </summary>
316 public Func<int> getIndex { get; set; }
317 /// <summary>
318 /// Set the enumeration value index.
319 /// </summary>
320 public Action<int> setIndex { get; set; }
321
322 /// <summary>
323 /// Current enumeration value index.
324 /// </summary>
325 public int currentIndex
326 {
327 get => getIndex();
328 set => setIndex(value);
329 }
330
331 /// <summary>
332 /// Generates enumerator values and names automatically based on the provided type.
333 /// </summary>
334 public Type autoEnum
335 {
336 set
337 {
338 AutoFillFromType(value);
339 InitQuickSeparators();
340 }
341 }
342
343 internal void InitQuickSeparators()
344 {
345 var enumNamesPrefix = enumNames.Select(x =>
346 {
347 string[] splitted = x.text.Split('/');
348 if (splitted.Length == 1)
349 return "";
350 else
351 return splitted[0];
352 });
353 quickSeparators = new int[enumNamesPrefix.Distinct().Count()];
354 string lastPrefix = null;
355 for (int i = 0, wholeNameIndex = 0; i < quickSeparators.Length; ++i)
356 {
357 var currentTestedPrefix = enumNamesPrefix.ElementAt(wholeNameIndex);
358 while (lastPrefix == currentTestedPrefix)
359 {
360 currentTestedPrefix = enumNamesPrefix.ElementAt(++wholeNameIndex);
361 }
362 lastPrefix = currentTestedPrefix;
363 quickSeparators[i] = wholeNameIndex++;
364 }
365 }
366
367 /// <summary>
368 /// Set the value of the field.
369 /// </summary>
370 /// <param name="value">Input value.</param>
371 public override void SetValue(int value)
372 {
373 Assert.IsNotNull(setter);
374 var validValue = ValidateValue(value);
375
376 // There might be cases that the value does not map the index, look for the correct index
377 var newCurrentIndex = Array.IndexOf(enumValues, validValue);
378
379 if (currentIndex != newCurrentIndex && !validValue.Equals(getter()))
380 {
381 setter(validValue);
382 onValueChanged?.Invoke(this, validValue);
383
384 if (newCurrentIndex > -1)
385 currentIndex = newCurrentIndex;
386 }
387 }
388 }
389
390 /// <summary>
391 /// A dropdown that contains a list of Unity objects.
392 /// </summary>
393 public class ObjectPopupField : Field<Object>
394 {
395 /// <summary>
396 /// Callback to obtain the elemtents of the pop up
397 /// </summary>
398 public Func<IEnumerable<Object>> getObjects { get; set; }
399 }
400
401 /// <summary>
402 /// Enumerator field with history.
403 /// </summary>
404 public class HistoryEnumField : EnumField
405 {
406 /// <summary>
407 /// History getter for this field.
408 /// </summary>
409 public Func<int>[] historyIndexGetter { get; set; }
410 /// <summary>
411 /// Depth of the field's history.
412 /// </summary>
413 public int historyDepth => historyIndexGetter?.Length ?? 0;
414 /// <summary>
415 /// Get the value of the field at a certain history index.
416 /// </summary>
417 /// <param name="historyIndex">Index of the history to query.</param>
418 /// <returns>Value of the field at the provided history index.</returns>
419 public int GetHistoryValue(int historyIndex)
420 {
421 Assert.IsNotNull(historyIndexGetter);
422 Assert.IsTrue(historyIndex >= 0 && historyIndex < historyIndexGetter.Length, "out of range historyIndex");
423 Assert.IsNotNull(historyIndexGetter[historyIndex]);
424 return historyIndexGetter[historyIndex]();
425 }
426 }
427
428 /// <summary>
429 /// Bitfield enumeration field.
430 /// </summary>
431 public class BitField : EnumField<Enum>
432 {
433 Type m_EnumType;
434
435 /// <summary>
436 /// Generates bitfield values and names automatically based on the provided type.
437 /// </summary>
438 public Type enumType
439 {
440 get => m_EnumType;
441 set
442 {
443 m_EnumType = value;
444 AutoFillFromType(value);
445 }
446 }
447 }
448
449 /// <summary>
450 /// Maskfield enumeration field.
451 /// </summary>
452 public class MaskField : EnumField<uint>
453 {
454 /// <summary>
455 /// Fills the enum using the provided names
456 /// </summary>
457 /// <param name="names">names to fill the enum</param>
458 public void Fill(string[] names)
459 {
460 using (ListPool<GUIContent>.Get(out var tmpNames))
461 using (ListPool<int>.Get(out var tmpValues))
462 {
463 for (int i=0; i<(names.Length); ++i)
464 {
465 tmpNames.Add(new GUIContent(names[i]));
466 tmpValues.Add(i);
467 }
468 enumNames = tmpNames.ToArray();
469 enumValues = tmpValues.ToArray();
470 }
471 }
472
473 /// <summary>
474 /// Assigns a value to the maskfield.
475 /// </summary>
476 /// <param name="value">value for the maskfield</param>
477 public override void SetValue(uint value)
478 {
479 Assert.IsNotNull(setter);
480 var validValue = ValidateValue(value);
481
482 if (!validValue.Equals(getter()))
483 {
484 setter(validValue);
485 onValueChanged?.Invoke(this, validValue);
486 }
487 }
488 }
489
490 /// <summary>
491 /// Color field.
492 /// </summary>
493 public class ColorField : Field<Color>
494 {
495 /// <summary>
496 /// HDR color.
497 /// </summary>
498 public bool hdr = false;
499 /// <summary>
500 /// Show alpha of the color field.
501 /// </summary>
502 public bool showAlpha = true;
503
504 // Editor-only
505 /// <summary>
506 /// Show the color picker.
507 /// </summary>
508 public bool showPicker = true;
509
510 // Runtime-only
511 /// <summary>
512 /// Step increment.
513 /// </summary>
514 public float incStep = 0.025f;
515 /// <summary>
516 /// Step increment multiplier.
517 /// </summary>
518 public float incStepMult = 5f;
519 /// <summary>
520 /// Number of decimals.
521 /// </summary>
522 public int decimals = 3;
523
524 /// <summary>
525 /// Function used to validate the value when updating the field.
526 /// </summary>
527 /// <param name="value">Input value.</param>
528 /// <returns>Validated value.</returns>
529 public override Color ValidateValue(Color value)
530 {
531 if (!hdr)
532 {
533 value.r = Mathf.Clamp01(value.r);
534 value.g = Mathf.Clamp01(value.g);
535 value.b = Mathf.Clamp01(value.b);
536 value.a = Mathf.Clamp01(value.a);
537 }
538
539 return value;
540 }
541 }
542
543 /// <summary>
544 /// Vector2 field.
545 /// </summary>
546 public class Vector2Field : Field<Vector2>
547 {
548 // Runtime-only
549 /// <summary>
550 /// Step increment.
551 /// </summary>
552 public float incStep = 0.025f;
553 /// <summary>
554 /// Step increment multiplier.
555 /// </summary>
556 public float incStepMult = 10f;
557 /// <summary>
558 /// Number of decimals.
559 /// </summary>
560 public int decimals = 3;
561 }
562
563 /// <summary>
564 /// Vector3 field.
565 /// </summary>
566 public class Vector3Field : Field<Vector3>
567 {
568 // Runtime-only
569 /// <summary>
570 /// Step increment.
571 /// </summary>
572 public float incStep = 0.025f;
573 /// <summary>
574 /// Step increment multiplier.
575 /// </summary>
576 public float incStepMult = 10f;
577 /// <summary>
578 /// Number of decimals.
579 /// </summary>
580 public int decimals = 3;
581 }
582
583 /// <summary>
584 /// Vector4 field.
585 /// </summary>
586 public class Vector4Field : Field<Vector4>
587 {
588 // Runtime-only
589 /// <summary>
590 /// Step increment.
591 /// </summary>
592 public float incStep = 0.025f;
593 /// <summary>
594 /// Step increment multiplier.
595 /// </summary>
596 public float incStepMult = 10f;
597 /// <summary>
598 /// Number of decimals.
599 /// </summary>
600 public int decimals = 3;
601 }
602
603 /// <summary>
604 /// A field for selecting a Unity object.
605 /// </summary>
606 public class ObjectField : Field<Object>
607 {
608 /// <summary>
609 /// Object type.
610 /// </summary>
611 public Type type = typeof(Object);
612 }
613
614 /// <summary>
615 /// A list of fields for selecting Unity objects.
616 /// </summary>
617 public class ObjectListField : Field<Object[]>
618 {
619 /// <summary>
620 /// Objects type.
621 /// </summary>
622 public Type type = typeof(Object);
623 }
624
625 /// <summary>
626 /// A read-only message box with an icon.
627 /// </summary>
628 public class MessageBox : Widget
629 {
630 /// <summary>
631 /// Label style defines text color and background.
632 /// </summary>
633 public enum Style
634 {
635 /// <summary>
636 /// Info category
637 /// </summary>
638 Info,
639 /// <summary>
640 /// Warning category
641 /// </summary>
642 Warning,
643 /// <summary>
644 /// Error category
645 /// </summary>
646 Error
647 }
648
649 /// <summary>
650 /// Style used to render displayName.
651 /// </summary>
652 public Style style = Style.Info;
653
654 /// <summary>
655 /// Message Callback to feed the new message to the widget
656 /// </summary>
657 public Func<string> messageCallback = null;
658
659 /// <summary>
660 /// This obtains the message from the display name or from the message callback if it is not null
661 /// </summary>
662 public string message => messageCallback == null ? displayName : messageCallback();
663 }
664
665 /// <summary>
666 /// Widget that will show into the Runtime UI only
667 /// Warning the user if the Runtime Debug Shaders variants are being stripped from the build.
668 /// </summary>
669 public class RuntimeDebugShadersMessageBox : MessageBox
670 {
671 /// <summary>
672 /// Constructs a <see cref="RuntimeDebugShadersMessageBox"/>
673 /// </summary>
674 public RuntimeDebugShadersMessageBox()
675 {
676 displayName =
677 "Warning: the debug shader variants are missing. Ensure that the \"Strip Runtime Debug Shaders\" option is disabled in the SRP Graphics Settings.";
678 style = DebugUI.MessageBox.Style.Warning;
679 isHiddenCallback = () =>
680 {
681#if !UNITY_EDITOR
682 if (GraphicsSettings.TryGetRenderPipelineSettings<ShaderStrippingSetting>(out var shaderStrippingSetting))
683 return !shaderStrippingSetting.stripRuntimeDebugShaders;
684#endif
685 return true;
686 };
687 }
688 }
689 }
690}