using System; using System.Collections.Generic; using System.Text; using UnityEditor.Rendering.Analytics; using UnityEngine; using UnityEngine.Rendering; namespace UnityEditor.Rendering { /// /// Attribute specifying wich type of Debug Item should this drawer be used with. /// public class DebugUIDrawerAttribute : Attribute { internal readonly Type type; /// /// Constructor for DebugUIDraw Attribute /// /// Type of Debug Item this draw should be used with. public DebugUIDrawerAttribute(Type type) { this.type = type; } } /// /// Debug Item Drawer /// public class DebugUIDrawer { /// /// Cast into the proper type. /// /// Type of the drawer /// Object to be cast /// Returns o cast to type T protected T Cast(object o) where T : class { if (o == null) return null; if (o is T casted) return casted; StringBuilder info = new StringBuilder("Cast Exception:"); switch (o) { case DebugUI.Widget value: info.AppendLine($"Query Path : {value.queryPath}"); break; case DebugState state: info.AppendLine($"Query Path : {state.queryPath}"); break; } info.AppendLine($"Object to Cast Type : {o.GetType().AssemblyQualifiedName}"); info.AppendLine($"Target Cast Type : {typeof(T).AssemblyQualifiedName}"); throw new InvalidCastException(info.ToString()); } /// /// Implement this to execute processing before UI rendering. /// /// Widget that is going to be rendered. /// Debug State associated with the Debug Item. public virtual void Begin(DebugUI.Widget widget, DebugState state) { } /// /// Implement this to execute UI rendering. /// /// Widget that is going to be rendered. /// Debug State associated with the Debug Item. /// Returns the state of the widget. public virtual bool OnGUI(DebugUI.Widget widget, DebugState state) { return true; } /// /// Implement this to execute processing after UI rendering. /// /// Widget that is going to be rendered. /// Debug State associated with the Debug Item. public virtual void End(DebugUI.Widget widget, DebugState state) { } /// /// Applies a value to the widget and the Debug State of the Debug Item. /// /// Debug Item widget. /// Debug State associated with the Debug Item /// Input value. protected void Apply(DebugUI.IValueField widget, DebugState state, object value) { Undo.RegisterCompleteObjectUndo(state, $"Modified Value '{state.queryPath}'"); state.SetValue(value, widget); widget.SetValue(value); EditorUtility.SetDirty(state); DebugState.m_CurrentDirtyState = state; UnityEditorInternal.InternalEditorUtility.RepaintAllViews(); } /// /// Prepares the rendering Rect of the Drawer. /// /// Height of the rect. /// Whether to reserve full width for the element. /// Appropriate Rect for drawing. protected Rect PrepareControlRect(float height = -1, bool fullWidth = false) { if (height < 0) height = EditorGUIUtility.singleLineHeight; var rect = GUILayoutUtility.GetRect(1f, 1f, height, height); const float paddingLeft = 4f; rect.width -= paddingLeft; rect.xMin += paddingLeft; EditorGUIUtility.labelWidth = fullWidth ? rect.width : rect.width / 2f; return rect; } } /// /// Common class to help drawing fields /// /// The internal value of the field /// The type of the field widget /// The state of the field public abstract class DebugUIFieldDrawer : DebugUIDrawer where TField : DebugUI.Field where TState : DebugState { private TValue value { get; set; } /// /// Implement this to execute processing before UI rendering. /// /// Widget that is going to be rendered. /// Debug State associated with the Debug Item. public override void Begin(DebugUI.Widget widget, DebugState state) { EditorGUI.BeginChangeCheck(); } /// /// Implement this to execute UI rendering. /// /// Widget that is going to be rendered. /// Debug State associated with the Debug Item. /// Returns the state of the widget. public override bool OnGUI(DebugUI.Widget widget, DebugState state) { value = DoGUI( PrepareControlRect(), EditorGUIUtility.TrTextContent(widget.displayName, widget.tooltip), Cast(widget), Cast(state) ); return true; } /// /// Does the field of the given type /// /// The rect to draw the field /// The label for the field /// The field /// The state /// The current value from the UI protected abstract TValue DoGUI(Rect rect, GUIContent label, TField field, TState state); struct WidgetChangedAction { public string query_path; public TValue previous_value; public TValue new_value; } static List s_Analytic = new List(); /// /// Implement this to execute processing after UI rendering. /// /// Widget that is going to be rendered. /// Debug State associated with the Debug Item. public override void End(DebugUI.Widget widget, DebugState state) { if (EditorGUI.EndChangeCheck()) { var w = Cast(widget); var s = Cast(state); s_Analytic.Clear(); s_Analytic.Add(new() { query_path = widget.queryPath, previous_value = w.GetValue(), new_value = value }); Apply(w, s, value); GraphicsToolUsageAnalytic.ActionPerformed("Widget Value Changed", s_Analytic.ToNestedColumn()); } } } /// /// Common class to help drawing widgets /// /// The widget public abstract class DebugUIWidgetDrawer : DebugUIDrawer where TWidget : DebugUI.Widget { /// /// Implement this to execute processing before UI rendering. /// /// Widget that is going to be rendered. /// Debug State associated with the Debug Item. public override void Begin(DebugUI.Widget widget, DebugState state) { } /// /// Implement this to execute UI rendering. /// /// Widget that is going to be rendered. /// Debug State associated with the Debug Item. /// Returns the state of the widget. public override bool OnGUI(DebugUI.Widget widget, DebugState state) { DoGUI( PrepareControlRect(), EditorGUIUtility.TrTextContent(widget.displayName, widget.tooltip), Cast(widget) ); return true; } /// /// Does the field of the given type /// /// The rect to draw the field /// The label for the field /// The widget protected abstract void DoGUI(Rect rect, GUIContent label, TWidget w); /// /// Implement this to execute processing after UI rendering. /// /// Widget that is going to be rendered. /// Debug State associated with the Debug Item. public override void End(DebugUI.Widget widget, DebugState state) { } } }