using System; using System.Collections.Generic; using UnityEngine; using UnityEngine.Rendering; using UnityEngine.Rendering.HighDefinition; using UnityEngine.UIElements; namespace UnityEditor.Rendering.HighDefinition { using CED = CoreEditorDrawer; // Mirrors MaterialQuality enum and adds `FromQualitySettings` enum MaterialQualityMode { Low, Medium, High, FromQualitySettings, } static class MaterialQualityModeExtensions { public static MaterialQuality Into(this MaterialQualityMode quality) { switch (quality) { case MaterialQualityMode.High: return MaterialQuality.High; case MaterialQualityMode.Medium: return MaterialQuality.Medium; case MaterialQualityMode.Low: return MaterialQuality.Low; case MaterialQualityMode.FromQualitySettings: return (MaterialQuality)0; default: throw new ArgumentOutOfRangeException(nameof(quality)); } } public static MaterialQualityMode Into(this MaterialQuality quality) { if (quality == (MaterialQuality)0) return MaterialQualityMode.FromQualitySettings; switch (quality) { case MaterialQuality.High: return MaterialQualityMode.High; case MaterialQuality.Medium: return MaterialQualityMode.Medium; case MaterialQuality.Low: return MaterialQualityMode.Low; default: throw new ArgumentOutOfRangeException(nameof(quality)); } } } interface IDefaultFrameSettingsType { FrameSettingsRenderType GetFrameSettingsType(); } partial class FrameSettingsUI { enum Expandable { RenderingPasses = 1 << 0, RenderingSettings = 1 << 1, LightingSettings = 1 << 2, AsynComputeSettings = 1 << 3, LightLoop = 1 << 4, } readonly static ExpandedState k_ExpandedState = new ExpandedState(~(-1), "HDRP"); static Rect lastBoxRect; internal static CED.IDrawer Inspector(bool withOverride = true) => CED.Group( CED.Group((serialized, owner) => { lastBoxRect = EditorGUILayout.BeginVertical("box"); // Add dedicated scope here and on each FrameSettings field to have the contextual menu on everything Rect rect = GUILayoutUtility.GetRect(1, EditorGUIUtility.singleLineHeight); using (new FrameSettingsAreaImGUI.TitleDrawingScope(rect, FrameSettingsUI.frameSettingsHeaderContent, serialized)) { EditorGUI.LabelField(rect, FrameSettingsUI.frameSettingsHeaderContent, EditorStyles.boldLabel); } }), InspectorInnerbox(withOverride), CED.Group((serialized, owner) => { EditorGUILayout.EndVertical(); using (new FrameSettingsAreaImGUI.TitleDrawingScope(lastBoxRect, FrameSettingsUI.frameSettingsHeaderContent, serialized)) { //Nothing to draw. //We just want to have a big blue bar at left that match the whole framesetting box. //This is because framesettings will be considered as one big block from prefab point //of view as there is no way to separate it bit per bit in serialization and Prefab //override API rely on SerializedProperty. } }) ); //separated to add enum popup on default frame settings internal static CED.IDrawer InspectorInnerbox(bool withOverride = true, bool isBoxed = true) => CED.Group( CED.FoldoutGroup(renderingSettingsHeaderContent, Expandable.RenderingPasses, k_ExpandedState, isBoxed ? FoldoutOption.Indent | FoldoutOption.Boxed : FoldoutOption.Indent, CED.Group(206, (serialized, owner) => Drawer_Section(0, serialized.data, serialized.mask, owner, withOverride)) ), CED.FoldoutGroup(lightSettingsHeaderContent, Expandable.LightingSettings, k_ExpandedState, isBoxed ? FoldoutOption.Indent | FoldoutOption.Boxed : FoldoutOption.Indent, CED.Group(206, (serialized, owner) => Drawer_Section(1, serialized.data, serialized.mask, owner, withOverride)) ), CED.FoldoutGroup(asyncComputeSettingsHeaderContent, Expandable.AsynComputeSettings, k_ExpandedState, isBoxed ? FoldoutOption.Indent | FoldoutOption.Boxed : FoldoutOption.Indent, CED.Group(206, (serialized, owner) => Drawer_Section(2, serialized.data, serialized.mask, owner, withOverride)) ), CED.FoldoutGroup(lightLoopSettingsHeaderContent, Expandable.LightLoop, k_ExpandedState, isBoxed ? FoldoutOption.Indent | FoldoutOption.Boxed : FoldoutOption.Indent, CED.Group(206, (serialized, owner) => Drawer_Section(3, serialized.data, serialized.mask, owner, withOverride)) ) ); static HDRenderPipelineAsset GetHDRPAssetFor(Editor owner) { HDRenderPipelineAsset hdrpAsset; if (owner is HDRenderPipelineEditor) { // When drawing the inspector of a selected HDRPAsset in Project windows, access HDRP by owner drawing itself hdrpAsset = (owner as HDRenderPipelineEditor).target as HDRenderPipelineAsset; } else { // Else rely on GraphicsSettings are you should be in hdrp and owner could be probe or camera. hdrpAsset = HDRenderPipeline.currentAsset; } return hdrpAsset; } static FrameSettingsRenderType? GetDefaultFrameSettingsFor(Editor owner) { if (owner is IHDProbeEditor) return (owner as IDefaultFrameSettingsType).GetFrameSettingsType(); else if (owner is HDCameraEditor) return FrameSettingsRenderType.Camera; return null; } private static void Drawer_Section(int index, SerializedFrameSettings.Data data, SerializedFrameSettings.Mask mask, Editor owner, bool withOverride) { FrameSettingsAreaImGUI.DrawWithOverride( FrameSettingsExtractedDatas.CreateBoundInstance(data, GetDefaultFrameSettingsFor(owner), GetHDRPAssetFor(owner)), index, mask); } } // Drawing methods with ImGUI static class FrameSettingsAreaImGUI { internal const int k_IndentPerLevel = 15; const int k_CheckBoxWidth = 15; const int k_CheckboxLabelSeparator = 5; const int k_LabelFieldSeparator = 2; const int k_OverridesHeadersHeight = 17; const int k_OverridesHeadersPadding = 3; const int k_OverridesHeadersAllWidth = 17; const int k_OverridesHeadersNoneWidth = 50; static readonly GUIContent overrideTooltip = EditorGUIUtility.TrTextContent("", "Override this setting in component."); public static void DrawWithOverride(FrameSettingsExtractedDatas.DataLinked boundInstance, int groupIndex, SerializedFrameSettings.Mask serializedMask) { EditorGUI.BeginChangeCheck(); var oldState = GUI.enabled; if (GUI.enabled) { Rect rect = GUILayoutUtility.GetRect(0f, k_OverridesHeadersHeight, GUILayout.ExpandWidth(false)); OverridesHeaders(rect, boundInstance, serializedMask, groupIndex); } foreach(var field in boundInstance.GetDescriptorForGroup(groupIndex)) { var line = GUILayoutUtility.GetRect(1, EditorGUIUtility.singleLineHeight); var borderedLine = line; borderedLine.x -= 1; borderedLine.width += 2; var remainingsOnLine = DrawOverridePart(line, field, serializedMask); var currentIndent = EditorGUI.indentLevel; EditorGUI.indentLevel = field.indentLevel; GUIContent label = EditorGUIUtility.TrTextContent(field.displayedName, field.tooltip); using (new TitleDrawingScope(remainingsOnLine, label, field.boundData.boundData, serializedMask)) remainingsOnLine = DrawLabel(remainingsOnLine, label); DrawFieldPart(remainingsOnLine, field, true); EditorGUI.indentLevel = currentIndent; GUI.enabled = oldState; } if (EditorGUI.EndChangeCheck()) { boundInstance.boundData.ApplyModifiedProperties(); serializedMask.ApplyModifiedProperties(); } } public static void DrawWithOverride(Rect rect, FrameSettingsExtractedDatas.DataLinked boundInstance, int groupIndex, SerializedFrameSettings.Mask serializedMask) { EditorGUI.BeginChangeCheck(); var oldState = GUI.enabled; if (GUI.enabled) { Rect overridesHeadersRect = new Rect(rect.x, rect.y, rect.width, k_OverridesHeadersHeight); OverridesHeaders(overridesHeadersRect, boundInstance, serializedMask, groupIndex); rect.y += k_OverridesHeadersHeight; rect.height -= k_OverridesHeadersHeight; } var linePosition = GetLines(rect, groupIndex).GetEnumerator(); foreach(var field in boundInstance.GetDescriptorForGroup(groupIndex)) { linePosition.MoveNext(); var remainingsOnLine = DrawOverridePart(linePosition.Current, field, serializedMask); var currentIndent = EditorGUI.indentLevel; EditorGUI.indentLevel = field.indentLevel; GUIContent label = EditorGUIUtility.TrTextContent(field.displayedName, field.tooltip); using (new TitleDrawingScope(remainingsOnLine, label, field.boundData.boundData, serializedMask)) remainingsOnLine = DrawLabel(remainingsOnLine, label); DrawFieldPart(remainingsOnLine, field, true); EditorGUI.indentLevel = currentIndent; GUI.enabled = oldState; } if (EditorGUI.EndChangeCheck()) { boundInstance.boundData.ApplyModifiedProperties(); serializedMask.ApplyModifiedProperties(); } } static public void DrawWithoutOverride(FrameSettingsExtractedDatas.DataLinked boundInstance, int groupIndex) { EditorGUI.BeginChangeCheck(); var oldState = GUI.enabled; foreach(var field in boundInstance.GetDescriptorForGroup(groupIndex)) { var remainingsOnLine = GUILayoutUtility.GetRect(1, EditorGUIUtility.singleLineHeight); var currentIndent = EditorGUI.indentLevel; EditorGUI.indentLevel = field.indentLevel; GUIContent label = EditorGUIUtility.TrTextContent(field.displayedName, field.tooltip); using (new TitleDrawingScope(remainingsOnLine, label, field.boundData.boundData)) remainingsOnLine = DrawLabel(remainingsOnLine, label); DrawFieldPart(remainingsOnLine, field, false); EditorGUI.indentLevel = currentIndent; GUI.enabled = oldState; } if (EditorGUI.EndChangeCheck()) boundInstance.boundData.ApplyModifiedProperties(); } static public void DrawWithoutOverride(Rect rect, FrameSettingsExtractedDatas.DataLinked boundInstance, int groupIndex) { EditorGUI.BeginChangeCheck(); var oldState = GUI.enabled; var linePosition = GetLines(rect, groupIndex).GetEnumerator(); foreach (var field in boundInstance.GetDescriptorForGroup(groupIndex)) { linePosition.MoveNext(); var remainingsOnLine = linePosition.Current; var currentIndent = EditorGUI.indentLevel; EditorGUI.indentLevel = field.indentLevel; GUIContent label = EditorGUIUtility.TrTextContent(field.displayedName, field.tooltip); using (new TitleDrawingScope(remainingsOnLine, label, field.boundData.boundData)) remainingsOnLine = DrawLabel(remainingsOnLine, label); DrawFieldPart(remainingsOnLine, field, false); EditorGUI.indentLevel = currentIndent; GUI.enabled = oldState; } if (EditorGUI.EndChangeCheck()) boundInstance.boundData.ApplyModifiedProperties(); } static public float CalcHeightWithoutOverride(int groupIndex) { var lineAmount = FrameSettingsExtractedDatas.GetGroupLength(groupIndex); return lineAmount * EditorGUIUtility.singleLineHeight + (lineAmount - 1) * EditorGUIUtility.standardVerticalSpacing; } static public float CalcHeightWithOverride(int groupIndex) { return k_OverridesHeadersHeight + CalcHeightWithoutOverride(groupIndex); } static public IEnumerable GetLines(Rect encompassingRect, int groupIndex) { Rect line = encompassingRect; line.height = EditorGUIUtility.singleLineHeight; for(int i = FrameSettingsExtractedDatas.GetGroupLength(groupIndex); i > 0; --i) { yield return line; line.y += EditorGUIUtility.singleLineHeight + EditorGUIUtility.standardVerticalSpacing; } } static Rect DrawLabel(Rect rect, GUIContent label) { Rect labelRect = rect; labelRect.width = EditorGUIUtility.labelWidth - k_IndentPerLevel * EditorGUI.indentLevel; labelRect.x += EditorGUI.indentLevel * k_IndentPerLevel; Rect fieldRect = rect; fieldRect.x = labelRect.xMax + k_LabelFieldSeparator; fieldRect.width -= fieldRect.x - rect.x; EditorGUI.HandlePrefixLabel(rect, labelRect, label); return fieldRect; } static Rect DrawOverridePart(Rect rect, FrameSettingsExtractedDatas.DataLinked.FieldDescriptor descriptor, SerializedFrameSettings.Mask mask) { int currentIndent = EditorGUI.indentLevel; EditorGUI.indentLevel = 0; Rect overrideRect = rect; overrideRect.width = k_CheckBoxWidth; Rect fieldRect = rect; fieldRect.x += k_CheckBoxWidth + k_CheckboxLabelSeparator; fieldRect.width -= fieldRect.x - rect.x; var overrideInterface = descriptor.GetOverrideInterface(mask); var val = overrideInterface.overrided; bool originalValue = val ?? false; overrideRect.yMin += 4f; var overrideable = overrideInterface.IsOverrideableWithDependencies(); GUI.enabled &= overrideable; if (overrideable) { bool modifiedValue = EditorGUI.Toggle(overrideRect, overrideTooltip, originalValue, val.HasValue ? CoreEditorStyles.smallTickbox : CoreEditorStyles.smallMixedTickbox); if (originalValue ^ modifiedValue) overrideInterface.overrided = modifiedValue; GUI.enabled &= modifiedValue; } EditorGUI.indentLevel = currentIndent; return fieldRect; } static void DrawFieldPart(Rect fieldRect, FrameSettingsExtractedDatas.DataLinked.FieldDescriptor descriptor, bool haveOverride) { int currentIndent = EditorGUI.indentLevel; EditorGUI.indentLevel = 0; if (haveOverride && !GUI.enabled) { //When disabled, show default values instead DrawFieldPartWithDefaultValues(fieldRect, descriptor); } else { //When enabled, show serialized ones for this instance DrawFieldPartWithCurrentValues(fieldRect, descriptor); } EditorGUI.indentLevel = currentIndent; } static void DrawFieldPartWithDefaultValues(Rect fieldRect, FrameSettingsExtractedDatas.DataLinked.FieldDescriptor descriptor) { if (descriptor.overridedDefaultValue == null) { switch (descriptor.displayType) { case FrameSettingsFieldAttribute.DisplayType.BoolAsCheckbox: DrawFieldShape(fieldRect, descriptor.enabledInDefault ?? false); break; case FrameSettingsFieldAttribute.DisplayType.BoolAsEnumPopup: //shame but it is not possible to use Convert.ChangeType to convert int into enum in current C# //rely on string parsing for the moment var oldEnumValue = Enum.Parse(descriptor.targetType, (descriptor.enabledInDefault ?? false) ? "1" : "0"); DrawFieldShape(fieldRect, oldEnumValue); break; case FrameSettingsFieldAttribute.DisplayType.Others: if (descriptor.overridedGetter == null) throw new Exception($"{nameof(FrameSettingsFieldAttribute.DisplayType.Others)} can only be used with an overridedGetter in {nameof(FrameSettingsExtractedDatas.DataLinked)}.AddDynamicOverrides()"); var oldValue = descriptor.overridedGetter(); DrawFieldShape(fieldRect, oldValue); break; default: throw new ArgumentException("Unknown FrameSettingsFieldAttribute"); } } else DrawFieldShape(fieldRect, descriptor.overridedDefaultValue()); } static void DrawFieldPartWithCurrentValues(Rect fieldRect, FrameSettingsExtractedDatas.DataLinked.FieldDescriptor descriptor) { EditorGUI.showMixedValue = descriptor.hasMultipleDifferentValues; switch (descriptor.displayType) { case FrameSettingsFieldAttribute.DisplayType.BoolAsCheckbox: bool oldBool = descriptor.enabled ?? false; bool newBool = (bool)DrawFieldShape(fieldRect, oldBool); if (oldBool ^ newBool) { Undo.RecordObjects(descriptor.targetObjects, $"Changed FrameSettings {descriptor.displayedName}"); descriptor.enabled = newBool; descriptor.callbackOnChange?.Invoke(oldBool, newBool); } break; case FrameSettingsFieldAttribute.DisplayType.BoolAsEnumPopup: //shame but it is not possible to use Convert.ChangeType to convert int into enum in current C# //Also, Enum.Equals and Enum operator!= always send true here. As it seams to compare object reference instead of value. var oldBoolValue = descriptor.enabled; int oldEnumIntValue = -1; int newEnumIntValue; object newEnumValue; if (oldBoolValue.HasValue) { var oldEnumValue = Enum.GetValues(descriptor.targetType).GetValue(oldBoolValue.Value ? 1 : 0); newEnumValue = Convert.ChangeType(DrawFieldShape(fieldRect, oldEnumValue), descriptor.targetType); oldEnumIntValue = ((IConvertible)oldEnumValue).ToInt32(System.Globalization.CultureInfo.CurrentCulture); newEnumIntValue = ((IConvertible)newEnumValue).ToInt32(System.Globalization.CultureInfo.CurrentCulture); } else //in multi edition, do not assume any previous value { newEnumIntValue = EditorGUI.Popup(fieldRect, -1, Enum.GetNames(descriptor.targetType)); newEnumValue = newEnumIntValue < 0 ? null : Enum.GetValues(descriptor.targetType).GetValue(newEnumIntValue); } if (oldEnumIntValue != newEnumIntValue) { Undo.RecordObjects(descriptor.targetObjects, $"Changed FrameSettings {descriptor.displayedName}"); descriptor.enabled = Convert.ToInt32(newEnumValue) == 1; descriptor.callbackOnChange?.Invoke(oldEnumIntValue, newEnumIntValue); } break; case FrameSettingsFieldAttribute.DisplayType.Others: if (descriptor.overridedGetter == null || descriptor.overridedSetter == null) throw new Exception($"{nameof(FrameSettingsFieldAttribute.DisplayType.Others)} can only be used with an overridedGetter and a overridedSetter in {nameof(FrameSettingsExtractedDatas.DataLinked)}.AddDynamicOverrides()"); var oldValue = descriptor.overridedGetter(); EditorGUI.BeginChangeCheck(); var newValue = DrawFieldShape(fieldRect, oldValue); // We need an extensive check here, otherwise in some case with boxing or polymorphism // the != operator won't be accurate. (This is the case for enum types). var valuesAreEquals = oldValue == null && newValue == null || oldValue != null && oldValue.Equals(newValue); // If the UI reported a change, we also assign values. // When assigning to a multiple selection, the equals check may fail while there was indeed a change. if (EditorGUI.EndChangeCheck() || !valuesAreEquals) { Undo.RecordObjects(descriptor.targetObjects, $"Changed FrameSettings {descriptor.displayedName}"); descriptor.overridedSetter(newValue); descriptor.callbackOnChange?.Invoke(oldValue, newValue); } break; default: throw new ArgumentException("Unknown FrameSettingsFieldAttribute"); } EditorGUI.showMixedValue = false; } static object DrawFieldShape(Rect rect, object field) { switch (field) { case bool boolean: return EditorGUI.Toggle(rect, boolean); case int integer: return EditorGUI.IntField(rect, integer); case float floatValue: return EditorGUI.FloatField(rect, floatValue); case Enum enumeration: return EditorGUI.EnumPopup(rect, enumeration); default: EditorGUI.LabelField(rect, new GUIContent("Unsupported type")); Debug.LogError($"Unsupported format {field.GetType()} in OverridableSettingsArea.cs. Please add it!"); return null; } } static void OverridesHeaders(Rect rect, FrameSettingsExtractedDatas.DataLinked boundInstance, SerializedFrameSettings.Mask mask, int groupIndex) { if (GUI.Button(new Rect(rect.x + k_OverridesHeadersPadding, rect.y, k_OverridesHeadersAllWidth, rect.height), EditorGUIUtility.TrTextContent("All", "Toggle all overrides on. To maximize performances you should only toggle overrides that you actually need."), CoreEditorStyles.miniLabelButton)) { boundInstance.SetAllAllowedOverridesTo(mask, groupIndex, true); GUI.changed = true; } if (GUI.Button(new Rect(rect.x + k_OverridesHeadersAllWidth + k_OverridesHeadersPadding*2, rect.y, k_OverridesHeadersNoneWidth, rect.height), EditorGUIUtility.TrTextContent("None", "Toggle all overrides off."), CoreEditorStyles.miniLabelButton)) { boundInstance.SetAllAllowedOverridesTo(mask, groupIndex, false); GUI.changed = true; } } public struct TitleDrawingScope : IDisposable { bool m_HasOverride; public TitleDrawingScope(Rect rect, GUIContent label, SerializedFrameSettings serialized) : this(rect, label, serialized.data, serialized.mask) { } public TitleDrawingScope(Rect rect, GUIContent label, SerializedFrameSettings.Data data, SerializedFrameSettings.Mask mask = null) { EditorGUI.BeginProperty(rect, label, data.root); m_HasOverride = mask != null; if (m_HasOverride) EditorGUI.BeginProperty(rect, label, mask.root); } void IDisposable.Dispose() { EditorGUI.EndProperty(); if (m_HasOverride) EditorGUI.EndProperty(); } } } // Drawing methods with UITK class FrameSettingsArea : VisualElement { internal const string k_StylesheetPathFormat = "Packages/com.unity.render-pipelines.high-definition/Editor/USS/FrameSettings{0}.uss"; const string k_FieldClass = "frame-settings-field"; protected Dictionary m_QuickAccess = new(); Dictionary>> m_LateInitQueues = new(); protected FrameSettingsExtractedDatas.DataLinked m_BoundInstance; protected int m_GroupIndex; protected FrameSettingsArea(int groupIndex) { styleSheets.Add(AssetDatabase.LoadAssetAtPath(string.Format(k_StylesheetPathFormat, ""))); styleSheets.Add(AssetDatabase.LoadAssetAtPath(string.Format(k_StylesheetPathFormat, EditorGUIUtility.isProSkin ? "Dark" : "Light"))); m_GroupIndex = groupIndex; } public FrameSettingsArea(FrameSettingsExtractedDatas.DataLinked boundInstance, int groupIndex) : this(groupIndex) { if (boundInstance == null) throw new ArgumentNullException(nameof(boundInstance)); if (!FrameSettingsExtractedDatas.Exists(groupIndex)) throw new ArgumentOutOfRangeException(nameof(groupIndex)); m_BoundInstance = boundInstance; GenerateLayoutForGroup(); FinishDelayedInit(); } public void GenerateLayoutForGroup() { VisualElement header = CreateHeader(); if (header != null) Add(header); foreach (var descriptor in m_BoundInstance.GetDescriptorForGroup(m_GroupIndex)) Add(CreateFieldLayout(descriptor), descriptor.field); } protected virtual VisualElement CreateHeader() => null; protected virtual LineField CreateFieldLayout(FrameSettingsExtractedDatas.DataLinked.FieldDescriptor descriptor) => new LineField(descriptor.field, CreateInnerFieldLayout(descriptor)); VisualElement CreateInnerFieldLayout(FrameSettingsExtractedDatas.DataLinked.FieldDescriptor descriptor) { switch (descriptor.displayType) { case FrameSettingsFieldAttribute.DisplayType.BoolAsCheckbox: return CreateField(descriptor.enabledUnchecked, descriptor, v => descriptor.enabledUnchecked = (bool)v); case FrameSettingsFieldAttribute.DisplayType.BoolAsEnumPopup: var val = (Enum)Enum.GetValues(descriptor.targetType).GetValue(descriptor.enabledUnchecked ? 1 : 0); Action setter = v => descriptor.enabledUnchecked = ((IConvertible)v).ToInt32(System.Globalization.CultureInfo.InvariantCulture) == 1; return CreateField(val, descriptor, setter); case FrameSettingsFieldAttribute.DisplayType.Others: { if (descriptor.overridedGetter == null || descriptor.overridedSetter == null) throw new Exception($"{nameof(FrameSettingsFieldAttribute.DisplayType.Others)} can only be used with an overridedGetter and a overridedSetter in {nameof(FrameSettingsExtractedDatas.DataLinked)}.AddDynamicOverrides()"); return CreateField(descriptor.overridedGetter(), descriptor); } default: throw new ArgumentException($"Unknown {nameof(FrameSettingsFieldAttribute.DisplayType)}"); } } VisualElement CreateField(T value, FrameSettingsExtractedDatas.DataLinked.FieldDescriptor descriptor, Action registerChange = null) { switch (value) //only kept bool, int, float and enum as nothing else is used { case bool boolean: return SetUp(new Toggle() { value = boolean }, descriptor, registerChange); case int integer: return SetUp(new IntegerField() { value = integer }, descriptor, registerChange); case float floatValue: return SetUp(new FloatField() { value = floatValue }, descriptor, registerChange); case Enum enumeration: return SetUp(new EnumField(enumeration), descriptor, registerChange); default: throw new ArgumentException($"Unsupported format {typeof(T)} in OverridableFrameSettingsArea.cs. Please add it!"); } } VisualElement SetUp(BaseField field, FrameSettingsExtractedDatas.DataLinked.FieldDescriptor descriptor, Action registerChange = null) { //initilization styling field.label = descriptor.displayedName; field.tooltip = descriptor.tooltip; field.showMixedValue = descriptor.hasMultipleDifferentValues; //dynamic styling field.labelElement.style.paddingLeft = 1 + descriptor.indentLevel * FrameSettingsAreaImGUI.k_IndentPerLevel; field.AddToClassList("unity-base-field__aligned"); //Align with other BaseField field.AddToClassList(k_FieldClass); if (descriptor.fieldDependentLabel != FrameSettingsField.None) { // workaround because IPrefixLabel is not public var encapsulatedDescriptor = descriptor; void RegisterValueChanged(BaseField otherField) { otherField.RegisterValueChangedCallback(evt => { field.labelElement.text = encapsulatedDescriptor.displayedName; }); } AddDelayedInit(descriptor.fieldDependentLabel, elt => { switch (elt) { case Toggle toggle: RegisterValueChanged(toggle); return; case IntegerField integreField: RegisterValueChanged(integreField); return; case FloatField floatField: RegisterValueChanged(floatField); return; case EnumField enumField: RegisterValueChanged(enumField); return; default: throw new ArgumentException($"Unsupported format BaseField in {nameof(FrameSettingsArea)}. Please add it! Found {elt.GetType()}"); } }); } //callback on change field.RegisterValueChangedCallback(evt => { Undo.RecordObjects(descriptor.targetObjects, $"Changed FrameSettings {descriptor.displayedName}"); if (descriptor.overridedSetter != null) descriptor.overridedSetter(evt.newValue); else registerChange(evt.newValue); descriptor.callbackOnChange?.Invoke(evt.previousValue, evt.newValue); m_BoundInstance.boundData.ApplyModifiedProperties(); }); return field; } // Usefull to add dependency between bits. This is mainly used to rename labels on other bit change. protected void AddDelayedInit(FrameSettingsField forField, Action action) { if (m_QuickAccess.ContainsKey(forField)) action?.Invoke(m_QuickAccess[forField].field); else if (action != null) { if (!m_LateInitQueues.ContainsKey(forField)) m_LateInitQueues[forField] = new(); m_LateInitQueues[forField].Enqueue(action); } } void Add(LineField element, FrameSettingsField field) { Add(m_QuickAccess[field] = element); if (!m_LateInitQueues.ContainsKey(field)) return; while (m_LateInitQueues[field].TryDequeue(out var action)) action(element.field); } protected void FinishDelayedInit() { foreach (var kpv in m_LateInitQueues) { if (kpv.Value == null) continue; while (kpv.Value.TryDequeue(out var action)) action(m_QuickAccess[kpv.Key].field); } } internal class LineField : VisualElement { FrameSettingsField m_FrameSettingsField; VisualElement m_Field; List m_FieldsDependingOnThisOne; public VisualElement field => m_Field; public FrameSettingsField frameSettingsField => m_FrameSettingsField; public T GetFistChildAs() where T : VisualElement { var e = Children().GetEnumerator(); e.MoveNext(); return e.Current as T; } public LineField(FrameSettingsField frameSettingsField, VisualElement field) { name = $"line-field-{frameSettingsField}"; m_FrameSettingsField = frameSettingsField; Add(m_Field = field); style.flexDirection = FlexDirection.Row; m_FieldsDependingOnThisOne = new(); } internal void AddDependence(FrameSettingsField dependentField) => m_FieldsDependingOnThisOne.Add(dependentField); internal void PropagateActionToDependenteLineFields(FrameSettingsArea host, Action action) { if (action == null) return; foreach (var field in m_FieldsDependingOnThisOne) action(host.m_QuickAccess[field]); } } } class FrameSettingsAreaWithOverrides : FrameSettingsArea { const string k_OverrideHeaderClass = "frame-settings-override-header"; const string k_OverrideCheckboxClass = "frame-settings-override-checkbox"; SerializedFrameSettings.Mask m_Mask; public FrameSettingsAreaWithOverrides(FrameSettingsExtractedDatas.DataLinked boundInstance, SerializedFrameSettings.Mask serializedMask, int groupIndex) : base(groupIndex) { if (boundInstance == null) throw new ArgumentNullException(nameof(boundInstance)); if (serializedMask == null) throw new ArgumentNullException(nameof(serializedMask)); if (!FrameSettingsExtractedDatas.Exists(groupIndex)) throw new ArgumentOutOfRangeException(nameof(groupIndex)); m_BoundInstance = boundInstance; m_Mask = serializedMask; GenerateLayoutForGroup(); FinishDelayedInit(); } protected override VisualElement CreateHeader() { void UpdateDisplayFromSerializedValues() { foreach ( var line in m_QuickAccess.Values) { var descriptor = m_BoundInstance.GetFieldDescriptor(line.frameSettingsField); var overrideInterface = descriptor.GetOverrideInterface(m_Mask); var toggle = line.GetFistChildAs(); toggle.SetValueWithoutNotify(overrideInterface.overridedUnchecked); toggle.showMixedValue = overrideInterface.hasMultipleDifferentOverrides; UpdateFieldAlongOverrides(toggle, line.field, descriptor); } } var line = new VisualElement(); line.Add(new Button(() => { Undo.RecordObjects(m_Mask.root.serializedObject.targetObjects, $"Changed all FrameSettings override"); m_BoundInstance.SetAllAllowedOverridesTo(m_Mask, m_GroupIndex, true); m_Mask.ApplyModifiedProperties(); UpdateDisplayFromSerializedValues(); }) { text = "All", tooltip = "Toggle all overrides on. To maximize performances you should only toggle overrides that you actually need.", name = "All", }); line.Add(new Button(() => { Undo.RecordObjects(m_Mask.root.serializedObject.targetObjects, $"Changed all FrameSettings override"); m_BoundInstance.SetAllAllowedOverridesTo(m_Mask, m_GroupIndex, false); m_Mask.ApplyModifiedProperties(); UpdateDisplayFromSerializedValues(); }) { text = "None", tooltip = "Toggle all overrides off.", name = "None", }); line.AddToClassList(k_OverrideHeaderClass); return line; } protected override LineField CreateFieldLayout(FrameSettingsExtractedDatas.DataLinked.FieldDescriptor descriptor) { var line = base.CreateFieldLayout(descriptor); var overrideCheckbox = CreateOverride(descriptor); line.Insert(0, overrideCheckbox); return line; } VisualElement GetSiblingFieldOnLine(Toggle t) => (t.hierarchy.parent as LineField).field; void UpdateFieldAlongOverrides(Toggle overrideToggle, VisualElement field, FrameSettingsExtractedDatas.DataLinked.FieldDescriptor descriptor) { void SetToDefaultValue() { var overrided = descriptor.overridedDefaultValue?.Invoke(); switch (field) { case Toggle toggleField: toggleField.SetValueWithoutNotify((bool)(overrided ?? descriptor.enabledInDefault.Value)); return; case IntegerField integerField: integerField.SetValueWithoutNotify((int)(overrided ?? descriptor.overridedGetter())); return; case FloatField floatField: floatField.SetValueWithoutNotify((float)(overrided ?? descriptor.overridedGetter())); return; case EnumField enumField: object oldEnumValue; switch (descriptor.displayType) { case FrameSettingsFieldAttribute.DisplayType.BoolAsEnumPopup: oldEnumValue = overrided ?? Enum.Parse(descriptor.targetType, (descriptor.enabledInDefault ?? false) ? "1" : "0"); break; default: oldEnumValue = overrided ?? descriptor.overridedGetter(); break; } enumField.SetValueWithoutNotify(oldEnumValue as Enum); return; default: throw new ArgumentException($"Unsupported format BaseField in {nameof(FrameSettingsArea)}. Please add it! Found {field.GetType()}"); } } void SetToSerializedValueBack() { switch (field) { case Toggle toggleField: toggleField.SetValueWithoutNotify(descriptor.enabledUnchecked); toggleField.showMixedValue = descriptor.hasMultipleDifferentValues; break; case IntegerField integerField: integerField.SetValueWithoutNotify((int)descriptor.overridedGetter()); integerField.showMixedValue = descriptor.hasMultipleDifferentValues; break; case FloatField floatField: floatField.SetValueWithoutNotify((float)descriptor.overridedGetter()); floatField.showMixedValue = descriptor.hasMultipleDifferentValues; break; case EnumField enumField: Enum value; switch (descriptor.displayType) { case FrameSettingsFieldAttribute.DisplayType.BoolAsEnumPopup: value = (Enum)Enum.GetValues(descriptor.targetType).GetValue(descriptor.enabledUnchecked ? 1 : 0); break; default: value = (Enum)descriptor.overridedGetter(); break; } enumField.SetValueWithoutNotify(value); enumField.showMixedValue = descriptor.hasMultipleDifferentValues; break; default: throw new ArgumentException($"Unsupported format BaseField in {nameof(FrameSettingsArea)}. Please add it! Found {field.GetType()}"); } } var overrideInterface = descriptor.GetOverrideInterface(m_Mask); bool visibleToggle = overrideInterface.IsOverrideableWithDependencies(); overrideToggle.style.visibility = visibleToggle ? Visibility.Visible : Visibility.Hidden; bool locallyOverrided = overrideInterface.overridedUnchecked; if (visibleToggle && locallyOverrided) { SetToSerializedValueBack(); field.SetEnabled(true); } else { field.SetEnabled(false); SetToDefaultValue(); } } Toggle CreateOverride(FrameSettingsExtractedDatas.DataLinked.FieldDescriptor descriptor) { //Initialize override toggle var o = descriptor.GetOverrideInterface(m_Mask); var toggle = new Toggle() { value = o.overridedUnchecked, showMixedValue = o.hasMultipleDifferentOverrides, }; toggle.AddToClassList(k_OverrideCheckboxClass); //Handle direct impact of override toggle on value to display //Line is not fully constructed yet. So we must delay AddDelayedInit(descriptor.field, elt => { //Line not fully constructed yet -> Delaying UpdateFieldAlongOverrides(toggle, GetSiblingFieldOnLine(toggle), descriptor); toggle.RegisterValueChangedCallback(evt => { //Serialization of override mask Undo.RecordObjects(descriptor.targetObjects, $"Changed FrameSettings {descriptor.displayedName} override"); o.overridedUnchecked = evt.newValue; m_Mask.ApplyModifiedProperties(); //Display of field LineField line = toggle.hierarchy.parent as LineField; UpdateFieldAlongOverrides(toggle, line.field, descriptor); line.PropagateActionToDependenteLineFields(this, l => UpdateFieldAlongOverrides(l.GetFistChildAs(), l.field, descriptor.boundData.GetFieldDescriptor(l.frameSettingsField))); }); }); //Handle dependent fields impact on value to display foreach (var field in descriptor.dependencies) { AddDelayedInit(field, elt => { m_QuickAccess[field].AddDependence(descriptor.field); //As there is no common interface allowing to RegisterValueChangedCallback //without knowing the type first, only handling supported types. switch (elt) { case Toggle toggle: RegisterValueChanged(toggle); return; case IntegerField integreField: RegisterValueChanged(integreField); return; case FloatField floatField: RegisterValueChanged(floatField); return; case EnumField enumField: RegisterValueChanged(enumField); return; default: throw new ArgumentException($"Unsupported format BaseField in {nameof(FrameSettingsArea)}. Please add it! Found {elt.GetType()}"); } }); } void RegisterValueChanged(BaseField otherField) { otherField.RegisterValueChangedCallback(evt => UpdateFieldAlongOverrides(toggle, GetSiblingFieldOnLine(toggle), descriptor)); } return toggle; } } }