using System; using UnityEngine; using UnityEngine.Assertions; using UnityEngine.Rendering.HighDefinition; namespace UnityEditor.Rendering.HighDefinition { /// /// Serialized version of . /// internal class SerializedScalableSettingValue { public SerializedProperty level; public SerializedProperty useOverride; public SerializedProperty @override; /// /// true when the value evaluated has different multiple values. /// false if it has a single value. /// public bool hasMultipleValues => useOverride.hasMultipleDifferentValues || useOverride.boolValue && @override.hasMultipleDifferentValues || !useOverride.boolValue && level.hasMultipleDifferentValues; public SerializedScalableSettingValue(SerializedProperty property) { level = property.FindPropertyRelative("m_Level"); useOverride = property.FindPropertyRelative("m_UseOverride"); @override = property.FindPropertyRelative("m_Override"); } /// /// Evaluate the value of the scalable setting value. /// /// The type of the scalable setting. /// The scalable setting to use to evaluate levels. /// The evaluated value. /// true when the value was evaluated, false when the value could not be evaluated. public bool TryGetValue(SerializedScalableSetting setting, out T value) where T : struct { if (hasMultipleValues) { value = default; return false; } if (useOverride.boolValue) { value = @override.GetInline(); return true; } else { var actualLevel = level.intValue; return setting.TryGetLevelValue(actualLevel, out value); } } /// /// Evaluate the value of the scalable setting value. /// /// The type of the scalable setting. /// The scalable setting to use to evaluate levels. /// The evaluated value. /// true when the value was evaluated, false when the value could not be evaluated. public bool TryGetValue(ScalableSetting setting, out T value) where T : struct { if (hasMultipleValues) { value = default; return false; } if (useOverride.boolValue) { value = @override.GetInline(); return true; } else { var actualLevel = level.intValue; return setting.TryGet(actualLevel, out value); } } } internal static class SerializedScalableSettingValueUI { /// /// Draw the level enum popup for a scalable setting value. /// /// The popup displays the level available and a `custom` entry to provide an explicit value. /// /// The rect to use to draw the popup. /// The label to use for the popup. /// The schema of the scalable setting. This provides the number of levels availables. /// The level to use, when is false. /// Whether to use the custom value or the level's value. /// /// If the field uses a level value, then (level, true) is returned, where level is the level used. /// Otherwise, (-1, 0) is returned. /// public static (int level, bool useOverride) LevelFieldGUI( Rect rect, GUIContent label, ScalableSettingSchema schema, int level, bool useOverride ) { var enumValue = useOverride ? 0 : level + 1; var levelCount = schema.levelCount; var options = new GUIContent[levelCount + 1]; options[0] = new GUIContent("Custom"); Array.Copy(schema.levelNames, 0, options, 1, levelCount); var newValue = EditorGUI.Popup(rect, label, enumValue, options); return (newValue - 1, newValue == 0); } /// Draws the level popup and the associated value in a field style GUI with an int field. /// The scalable setting value to draw. /// The label to use for the field. /// The associated scalable setting. This one defines the levels for this value. /// A string describing the source of the scalable settings. Usually the name of the containing asset. public static void LevelAndIntGUILayout( this SerializedScalableSettingValue self, GUIContent label, ScalableSetting sourceValue, string sourceName ) => LevelAndGUILayout(self, label, sourceValue, sourceName); /// Draws the level popup and the associated value in a field style GUI with an toggle field. /// The scalable setting value to draw. /// The label to use for the field. /// The associated scalable setting. This one defines the levels for this value. /// A string describing the source of the scalable settings. Usually the name of the containing asset. public static void LevelAndToggleGUILayout( this SerializedScalableSettingValue self, GUIContent label, ScalableSetting sourceValue, string sourceName ) => LevelAndGUILayout(self, label, sourceValue, sourceName); /// Draws the level popup and the associated value in a field style GUI with an Enum field. /// The scalable setting value to draw. /// The label to use for the field. /// The associated scalable setting. This one defines the levels for this value. /// A string describing the source of the scalable settings. Usually the name of the containing asset. public static void LevelAndEnumGUILayout ( this SerializedScalableSettingValue self, GUIContent label, ScalableSetting sourceValue, string sourceName ) where H : Enum => LevelAndGUILayout>(self, label, sourceValue, sourceName); /// /// Draw the level enum popup for a scalable setting value. /// /// The popup displays the level available and a `custom` entry to provide an explicit value. /// /// The rect to use to draw the popup. /// The label to use for the popup. /// The schema of the scalable setting. This provides the number of levels availables. /// The rect to use to render the value of the field. (Either the custom value or the level value). static Rect LevelFieldGUILayout( SerializedScalableSettingValue self, GUIContent label, ScalableSettingSchema schema ) { Assert.IsNotNull(schema); // Match const defined in EditorGUI.cs const int k_IndentPerLevel = 15; const int k_PrefixPaddingRight = 2; const int k_ValueUnitSeparator = 2; const int k_EnumWidth = 70; float indent = k_IndentPerLevel * EditorGUI.indentLevel; Rect lineRect = EditorGUILayout.GetControlRect(); Rect labelRect = lineRect; Rect levelRect = lineRect; Rect fieldRect = lineRect; labelRect.width = EditorGUIUtility.labelWidth; // Dealing with indentation add space before the actual drawing // Thus resize accordingly to have a coherent aspect levelRect.x += labelRect.width - indent + k_PrefixPaddingRight; levelRect.width = k_EnumWidth + indent; fieldRect.x = levelRect.x + levelRect.width + k_ValueUnitSeparator - indent; fieldRect.width -= fieldRect.x - lineRect.x; label = EditorGUI.BeginProperty(labelRect, label, self.level); label = EditorGUI.BeginProperty(labelRect, label, self.@override); label = EditorGUI.BeginProperty(labelRect, label, self.useOverride); { EditorGUI.LabelField(labelRect, label); } EditorGUI.EndProperty(); EditorGUI.EndProperty(); EditorGUI.EndProperty(); EditorGUI.BeginProperty(levelRect, label, self.level); EditorGUI.BeginProperty(levelRect, label, self.useOverride); { EditorGUI.BeginChangeCheck(); using (new EditorGUI.MixedValueScope(self.useOverride.hasMultipleDifferentValues || !self.useOverride.boolValue && self.level.hasMultipleDifferentValues)) { var (level, useOverride) = LevelFieldGUI( levelRect, GUIContent.none, schema, EditorGUI.showMixedValue ? -1 : self.level.intValue, // Force the GUI to update even if we select the same level as the first item self.useOverride.boolValue ); if (EditorGUI.EndChangeCheck()) { self.useOverride.boolValue = useOverride; if (!self.useOverride.boolValue) self.level.intValue = level; } } } EditorGUI.EndProperty(); EditorGUI.EndProperty(); return fieldRect; } /// /// Draw the scalable setting as a popup and field GUI in a single line. /// /// This helper statically dispatch the appropriate GUI call depending /// on the multiselection state of the serialized properties. /// /// The type of the scalable property. /// The renderer of the property. It must implements /// The scalable property to render. /// The label of the scalable property field. /// The source of the scalable setting. /// A description of the scalable setting, usually the name of its containing asset. /// /// The id of the schema to use when the scalable setting is null. /// Defaults to . /// static void LevelAndGUILayout( this SerializedScalableSettingValue self, GUIContent label, ScalableSetting sourceValue, string sourceName, ScalableSettingSchemaId defaultSchema = default ) where FieldGUI : IFieldGUI, new() { var resolvedDefaultSchema = defaultSchema.Equals(default) ? ScalableSettingSchemaId.With3Levels : defaultSchema; var gui = new FieldGUI(); var schema = ScalableSettingSchema.GetSchemaOrNull(sourceValue?.schemaId) ?? ScalableSettingSchema.GetSchemaOrNull(resolvedDefaultSchema); var fieldRect = LevelFieldGUILayout(self, label, schema); EditorGUI.BeginProperty(fieldRect, label, self.level); EditorGUI.BeginProperty(fieldRect, label, self.@override); EditorGUI.BeginProperty(fieldRect, label, self.useOverride); if (!self.useOverride.hasMultipleDifferentValues && self.useOverride.boolValue) { // All fields have custom values // So we show the custom value field GUI. var showMixedValues = EditorGUI.showMixedValue; EditorGUI.showMixedValue = self.@override.hasMultipleDifferentValues || showMixedValues; gui.CustomGUI(fieldRect, self, label, sourceValue, sourceName); EditorGUI.showMixedValue = showMixedValues; } else { if (self.useOverride.hasMultipleDifferentValues || !self.useOverride.boolValue && self.level.hasMultipleDifferentValues) // Scalable settings have either: // - custom or level values // - level values with different levels gui.MixedValueDescriptionGUI(fieldRect, self, label, sourceValue, sourceName); else // Scalable settings have all the same level value gui.LevelValueDescriptionGUI(fieldRect, self, label, sourceValue, sourceName); } EditorGUI.EndProperty(); EditorGUI.EndProperty(); EditorGUI.EndProperty(); } /// /// Renderer interface for a scalable setting. /// /// Implement this in a struct so the GUI calls can be inlined in /// . /// /// For an example, . /// /// The type of the scalable setting. interface IFieldGUI { /// /// Draw the custom value field. /// /// Assumes that all scalable properties uses a custom value. /// /// The rect to use to draw the value field. /// The scalable setting value to render. /// The label that was used for this scalable setting value. /// The scalable setting that defines the levels. /// A description of the scalable setting source. void CustomGUI( Rect fieldRect, SerializedScalableSettingValue self, GUIContent label, ScalableSetting sourceValue, string sourceName ); /// /// Draw the value field in a mixed value state. /// /// Scalable properties have either custom and level values or levels values with different levels. /// /// The rect to use to draw the value field. /// The scalable setting value to render. /// The label that was used for this scalable setting value. /// The scalable setting that defines the levels. /// A description of the scalable setting source. void MixedValueDescriptionGUI( Rect fieldRect, SerializedScalableSettingValue self, GUIContent label, ScalableSetting sourceValue, string sourceName ); /// /// Draw the value of a level for a scalable setting value. /// /// Scalable properties have all level values with the same level. /// This functions is expected to draw the level value with its source description. /// /// The rect to use to draw the value field. /// The scalable setting value to render. /// The label that was used for this scalable setting value. /// The scalable setting that defines the levels. /// A description of the scalable setting source. void LevelValueDescriptionGUI( Rect fieldRect, SerializedScalableSettingValue self, GUIContent label, ScalableSetting sourceValue, string sourceName ); } #region Field Renderer Implementations struct IntFieldGUI : IFieldGUI { public void CustomGUI( Rect fieldRect, SerializedScalableSettingValue self, GUIContent label, ScalableSetting sourceValue, string sourceName ) { self.@override.intValue = EditorGUI.IntField(fieldRect, self.@override.intValue); } public void LevelValueDescriptionGUI( Rect fieldRect, SerializedScalableSettingValue self, GUIContent label, ScalableSetting sourceValue, string sourceName ) { if (sourceValue != null && sourceValue[self.level.intValue] >= 0) { EditorGUI.LabelField(fieldRect, $"{sourceValue[self.level.intValue]} ({sourceName})"); } else { EditorGUI.LabelField(fieldRect, $"--- ({sourceName})"); } } public void MixedValueDescriptionGUI( Rect fieldRect, SerializedScalableSettingValue self, GUIContent label, ScalableSetting sourceValue, string sourceName ) { EditorGUI.LabelField(fieldRect, $"---"); } } struct ToggleFieldGUI : IFieldGUI { public void CustomGUI( Rect fieldRect, SerializedScalableSettingValue self, GUIContent label, ScalableSetting sourceValue, string sourceName ) { self.@override.boolValue = EditorGUI.Toggle(fieldRect, self.@override.boolValue); } public void LevelValueDescriptionGUI( Rect fieldRect, SerializedScalableSettingValue self, GUIContent label, ScalableSetting sourceValue, string sourceName ) { var enabled = GUI.enabled; GUI.enabled = false; EditorGUI.Toggle(fieldRect, sourceValue != null ? sourceValue[self.level.intValue] : false); fieldRect.x += 25; fieldRect.width -= 25; EditorGUI.LabelField(fieldRect, $"({sourceName})"); GUI.enabled = enabled; } public void MixedValueDescriptionGUI( Rect fieldRect, SerializedScalableSettingValue self, GUIContent label, ScalableSetting sourceValue, string sourceName ) { EditorGUI.LabelField(fieldRect, $"---"); } } struct EnumFieldGUI : IFieldGUI { public void CustomGUI( Rect fieldRect, SerializedScalableSettingValue self, GUIContent label, ScalableSetting sourceValue, string sourceName ) { // Due to a constraint in the scalability setting, we cannot simply precise the H type as an Enum in the struct declaration. // this shenanigans are not pretty, but we do not fall into a high complexity everytime we want to support a new enum. Enum data = (Enum)Enum.Parse(typeof(H), self.@override.intValue.ToString()); self.@override.intValue = (int)(object)EditorGUI.EnumPopup(fieldRect, data); } public void LevelValueDescriptionGUI( Rect fieldRect, SerializedScalableSettingValue self, GUIContent label, ScalableSetting sourceValue, string sourceName ) { var enabled = GUI.enabled; GUI.enabled = false; // See the comment in the function above. var defaultValue = (Enum)(Enum.GetValues(typeof(H)).GetValue(0)); EditorGUI.EnumPopup(fieldRect, sourceValue != null ? (Enum)(object)(sourceValue[self.level.intValue]) : defaultValue); fieldRect.x += 25; fieldRect.width -= 25; GUI.enabled = enabled; } public void MixedValueDescriptionGUI( Rect fieldRect, SerializedScalableSettingValue self, GUIContent label, ScalableSetting sourceValue, string sourceName ) { EditorGUI.LabelField(fieldRect, $"---"); } } #endregion } }