Skip to content

Instantly share code, notes, and snippets.

@Mikilo
Last active May 14, 2024 17:36
Show Gist options
  • Save Mikilo/8cb969a50a1eac87c9500d4f9f181324 to your computer and use it in GitHub Desktop.
Save Mikilo/8cb969a50a1eac87c9500d4f9f181324 to your computer and use it in GitHub Desktop.
Show/HideIf attributes for Unity.
using UnityEngine;
namespace NGTools
{
public enum MultiOp
{
None = -1,
/// <summary>Checks if the field's value equals one of the requirements.</summary>
Equals,
/// <summary>Checks if the field's value differs from all the requirements.</summary>
Diff,
}
public enum Op
{
None = -1,
/// <summary>Checks if the field's value is equal to the requirement.</summary>
Equals,
/// <summary>Checks if the field's value is different from the requirement.</summary>
Diff,
/// <summary>Checks if the field's value is superior than the requirement.</summary>
Sup,
/// <summary>Checks if the field's value is less than the requirement.</summary>
Inf,
/// <summary>Checks if the field's value is greater or equal to the requirement.</summary>
SupEquals,
/// <summary>Checks if the field's value is less than or equal to the requirement.</summary>
InfEquals,
}
public class ShowIfAttribute : PropertyAttribute
{
public readonly string fieldName;
public readonly Op @operator;
public readonly MultiOp multiOperator;
public readonly object[] values;
public ShowIfAttribute(string fieldName, Op @operator, object value)
{
this.fieldName = fieldName;
this.@operator = @operator;
this.multiOperator = MultiOp.None;
this.values = new object[] { value };
}
public ShowIfAttribute(string fieldName, MultiOp multiOperator, params object[] values)
{
this.fieldName = fieldName;
this.@operator = Op.None;
this.multiOperator = multiOperator;
this.values = values;
}
}
public class HideIfAttribute : PropertyAttribute
{
public readonly string fieldName;
public readonly Op @operator;
public readonly MultiOp multiOperator;
public readonly object[] values;
public HideIfAttribute(string fieldName, Op @operator, object value)
{
this.fieldName = fieldName;
this.@operator = @operator;
this.multiOperator = MultiOp.None;
this.values = new object[] { value };
}
public HideIfAttribute(string fieldName, MultiOp multiOperator, params object[] values)
{
this.fieldName = fieldName;
this.@operator = Op.None;
this.multiOperator = multiOperator;
this.values = values;
}
}
}
using NGTools;
using System;
using System.Reflection;
using UnityEditor;
namespace NGToolsEditor
{
using UnityEngine;
[CustomPropertyDrawer(typeof(ShowIfAttribute))]
internal sealed class ShowIfDrawer : PropertyDrawer
{
private ConditionalRenderer renderer;
public override float GetPropertyHeight(SerializedProperty property, GUIContent label)
{
if (this.renderer == null)
this.renderer = new ConditionalRenderer("ShowIf", this, base.GetPropertyHeight, true);
return this.renderer.GetPropertyHeight(property, label);
}
public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
{
this.renderer.OnGUI(position, property, label);
}
}
[CustomPropertyDrawer(typeof(HideIfAttribute))]
internal sealed class HideIfDrawer : PropertyDrawer
{
private ConditionalRenderer renderer;
public override float GetPropertyHeight(SerializedProperty property, GUIContent label)
{
if (this.renderer == null)
this.renderer = new ConditionalRenderer("HideIf", this, base.GetPropertyHeight, false);
return this.renderer.GetPropertyHeight(property, label);
}
public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
{
this.renderer.OnGUI(position, property, label);
}
}
internal sealed class ConditionalRenderer
{
private const float EmptyHeight = -2F;
private string name;
private Func<SerializedProperty, GUIContent, float> getPropertyHeight;
private PropertyDrawer drawer;
private bool normalBooleanValue;
private string errorAttribute = null;
private FieldInfo conditionField;
private string fieldName;
private Op @operator;
private MultiOp multiOperator;
private object[] values;
private object lastValue;
private string lastValueStringified;
private string[] targetValueStringified;
private Decimal[] targetValueDecimaled;
private bool conditionResult;
private bool invalidHeight = true;
private float cachedHeight;
private Func<SerializedProperty, GUIContent, float> PropertyHeight;
public ConditionalRenderer(string name, PropertyDrawer drawer, Func<SerializedProperty, GUIContent, float> getPropertyHeight, bool normalBooleanValue)
{
this.name = name;
this.drawer = drawer;
this.getPropertyHeight = getPropertyHeight;
this.normalBooleanValue = normalBooleanValue;
}
public float GetPropertyHeight(SerializedProperty property, GUIContent label)
{
if (this.fieldName == null)
this.InitializeDrawer(property);
if (this.errorAttribute != null)
return 16F;
if (this.conditionField == null)
return this.getPropertyHeight(property, label);
return this.PropertyHeight(property, label);
}
public void OnGUI(Rect position, SerializedProperty property, GUIContent label)
{
if (this.errorAttribute != null)
{
Color restore = GUI.contentColor;
GUI.contentColor = Color.black;
EditorGUI.LabelField(position, label.text, this.errorAttribute);
GUI.contentColor = restore;
}
else if (this.conditionField == null || this.conditionResult == this.normalBooleanValue)
{
EditorGUI.BeginChangeCheck();
EditorGUI.PropertyField(position, property, label, property.isExpanded);
if (EditorGUI.EndChangeCheck() == true)
this.invalidHeight = true;
}
}
private void InitializeDrawer(SerializedProperty property)
{
ShowIfAttribute showIfAttr = (this.drawer.attribute as ShowIfAttribute);
if (showIfAttr != null)
{
this.fieldName = showIfAttr.fieldName;
this.@operator = showIfAttr.@operator;
this.multiOperator = showIfAttr.multiOperator;
this.values = showIfAttr.values;
}
else
{
HideIfAttribute hideIfAttr = (this.drawer.attribute as HideIfAttribute);
if (hideIfAttr != null)
{
this.fieldName = hideIfAttr.fieldName;
this.@operator = hideIfAttr.@operator;
this.multiOperator = hideIfAttr.multiOperator;
this.values = hideIfAttr.values;
}
else
this.errorAttribute = "ShowIfAttribute or HideIfAttribute is required by field " + this.name + ".";
}
this.conditionField = this.drawer.fieldInfo.DeclaringType.GetField(this.fieldName, BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public);
if (this.conditionField == null)
{
this.errorAttribute = this.name + " is requiring field \"" + this.fieldName + "\".";
return;
}
else if (this.@operator != Op.None)
{
if (this.values[0] == null)
{
this.targetValueStringified = new string[] { string.Empty };
this.PropertyHeight = this.GetHeightAllOpsString;
if (this.@operator != Op.Equals &&
this.@operator != Op.Diff)
{
this.errorAttribute = this.name + " is requiring a null value whereas its operator is \"" + this.@operator + "\" which is impossible.";
}
}
else if (this.values[0] is Boolean)
{
this.targetValueStringified = new string[] { this.values[0].ToString() };
this.PropertyHeight = this.GetHeightAllOpsString;
if (this.@operator != Op.Equals &&
this.@operator != Op.Diff)
{
this.errorAttribute = this.name + " is requiring a boolean whereas its operator is \"" + this.@operator + "\" which is impossible.";
}
}
else if (this.values[0] is Int32 ||
this.values[0] is Single ||
this.values[0] is Enum ||
this.values[0] is Double ||
this.values[0] is Decimal ||
this.values[0] is Int16 ||
this.values[0] is Int64 ||
this.values[0] is UInt16 ||
this.values[0] is UInt32 ||
this.values[0] is UInt64 ||
this.values[0] is Byte ||
this.values[0] is SByte)
{
this.targetValueDecimaled = new Decimal[] { Convert.ToDecimal(this.values[0]) };
this.PropertyHeight = this.GetHeightAllOpsScalar;
}
else
{
this.targetValueStringified = new string[] { this.values[0].ToString() };
this.PropertyHeight = this.GetHeightAllOpsString;
}
}
else if (this.multiOperator != MultiOp.None)
{
if (this.CheckUseOfNonScalarValue() == true)
{
this.targetValueStringified = new string[this.values.Length];
for (int i = 0; i < this.values.Length; i++)
{
if (this.values[i] != null)
this.targetValueStringified[i] = this.values[i].ToString();
else
this.targetValueStringified[i] = string.Empty;
}
this.PropertyHeight = this.GetHeightMultiOpsString;
}
else
{
this.targetValueDecimaled = new Decimal[this.values.Length];
for (int i = 0; i < this.values.Length; i++)
this.targetValueDecimaled[i] = Convert.ToDecimal(this.values[i]);
this.PropertyHeight = this.GetHeightMultiOpsScalar;
}
}
// Force the next update.
object newValue = this.conditionField.GetValue(property.serializedObject.targetObject);
if (this.lastValue == newValue)
this.lastValue = true;
}
private bool CheckUseOfNonScalarValue()
{
for (int i = 0; i < this.values.Length; i++)
{
if (this.values[i] == null ||
this.values[i] is String ||
this.values[i] is Boolean)
{
return true;
}
}
return false;
}
private float GetHeightAllOpsString(SerializedProperty property, GUIContent label)
{
object newValue = this.conditionField.GetValue(property.serializedObject.targetObject);
if (this.lastValue != newValue)
{
this.lastValue = newValue;
if (this.lastValue != null &&
// Unity Object is not referenced as real null, it is fake. Don't trust them.
(typeof(Object).IsAssignableFrom(this.lastValue.GetType()) == false ||
((this.lastValue as Object).ToString() != "null")))
{
this.lastValueStringified = this.lastValue.ToString();
}
else
this.lastValueStringified = string.Empty;
if (this.@operator == Op.Equals)
this.conditionResult = this.lastValueStringified.Equals(this.targetValueStringified[0]);
else if (this.@operator == Op.Diff)
this.conditionResult = this.lastValueStringified.Equals(this.targetValueStringified[0]) == false;
else if (this.@operator == Op.Sup)
this.conditionResult = this.lastValueStringified.CompareTo(this.targetValueStringified[0]) > 0;
else if (this.@operator == Op.Inf)
this.conditionResult = this.lastValueStringified.CompareTo(this.targetValueStringified[0]) < 0;
else if (this.@operator == Op.SupEquals)
this.conditionResult = this.lastValueStringified.CompareTo(this.targetValueStringified[0]) >= 0;
else if (this.@operator == Op.InfEquals)
this.conditionResult = this.lastValueStringified.CompareTo(this.targetValueStringified[0]) <= 0;
}
return this.CalculateHeight(property, label);
}
private float GetHeightAllOpsScalar(SerializedProperty property, GUIContent label)
{
object newValue = this.conditionField.GetValue(property.serializedObject.targetObject);
if (newValue.Equals(this.lastValue) == false)
{
this.lastValue = newValue;
try
{
Decimal value = Convert.ToDecimal(newValue);
if (this.@operator == Op.Equals)
this.conditionResult = value == this.targetValueDecimaled[0];
else if (this.@operator == Op.Diff)
this.conditionResult = value != this.targetValueDecimaled[0];
else if (this.@operator == Op.Sup)
this.conditionResult = value > this.targetValueDecimaled[0];
else if (this.@operator == Op.Inf)
this.conditionResult = value < this.targetValueDecimaled[0];
else if (this.@operator == Op.SupEquals)
this.conditionResult = value >= this.targetValueDecimaled[0];
else if (this.@operator == Op.InfEquals)
this.conditionResult = value <= this.targetValueDecimaled[0];
}
catch
{
}
}
return this.CalculateHeight(property, label);
}
private float GetHeightMultiOpsString(SerializedProperty property, GUIContent label)
{
object newValue = this.conditionField.GetValue(property.serializedObject.targetObject);
if (this.lastValue != newValue)
{
this.lastValue = newValue;
if (this.lastValue != null &&
// Unity Object is not referenced as real null, it is fake. Don't trust them.
(typeof(Object).IsAssignableFrom(this.lastValue.GetType()) == false ||
((this.lastValue as Object).ToString() != "null")))
{
this.lastValueStringified = this.lastValue.ToString();
}
else
this.lastValueStringified = string.Empty;
if (this.multiOperator == MultiOp.Equals)
{
this.conditionResult = !this.normalBooleanValue;
for (int i = 0; i < this.targetValueStringified.Length; i++)
{
if (this.lastValueStringified.Equals(this.targetValueStringified[i]) == true)
{
this.conditionResult = this.normalBooleanValue;
break;
}
}
}
else if (this.multiOperator == MultiOp.Diff)
{
int i = 0;
this.conditionResult = this.normalBooleanValue;
for (; i < this.targetValueStringified.Length; i++)
{
if (this.lastValueStringified.Equals(this.targetValueStringified[i]) == true)
{
this.conditionResult = !this.normalBooleanValue;
break;
}
}
}
}
return this.CalculateHeight(property, label);
}
private float GetHeightMultiOpsScalar(SerializedProperty property, GUIContent label)
{
object newValue = this.conditionField.GetValue(property.serializedObject.targetObject);
if (newValue.Equals(this.lastValue) == false)
{
this.lastValue = newValue;
try
{
Decimal value = Convert.ToDecimal(newValue);
if (this.multiOperator == MultiOp.Equals)
{
this.conditionResult = !this.normalBooleanValue;
for (int i = 0; i < this.targetValueDecimaled.Length; i++)
{
if (value == this.targetValueDecimaled[i])
{
this.conditionResult = this.normalBooleanValue;
break;
}
}
}
else if (this.multiOperator == MultiOp.Diff)
{
int i = 0;
this.conditionResult = this.normalBooleanValue;
for (; i < this.targetValueDecimaled.Length; i++)
{
if (value == this.targetValueDecimaled[i])
{
this.conditionResult = !this.normalBooleanValue;
break;
}
}
}
}
catch
{
}
}
return this.CalculateHeight(property, label);
}
private float CalculateHeight(SerializedProperty property, GUIContent label)
{
if (this.conditionResult == this.normalBooleanValue)
{
if (this.invalidHeight == true)
{
this.invalidHeight = false;
this.cachedHeight = EditorGUI.GetPropertyHeight(property, label, property.isExpanded);
}
return this.cachedHeight;
}
return ConditionalRenderer.EmptyHeight;
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment