342 lines
12 KiB
C#
342 lines
12 KiB
C#
using System;
|
|
using System.Collections;
|
|
using System.Collections.Generic;
|
|
using UnityEditor;
|
|
using UnityEditor.UIElements;
|
|
using UnityEngine;
|
|
using UnityEngine.UIElements;
|
|
|
|
namespace Alchemy.Editor.Elements
|
|
{
|
|
/// <summary>
|
|
/// Visual Element that creates a suitable input Field from object type
|
|
/// </summary>
|
|
public sealed class GenericField : VisualElement
|
|
{
|
|
const string CreateButtonText = "Create...";
|
|
|
|
public GenericField(object obj, Type type, string label, bool isDelayed = false)
|
|
{
|
|
Build(obj, type, label, isDelayed);
|
|
GUIHelper.ScheduleAdjustLabelWidth(this);
|
|
}
|
|
|
|
void Build(object obj, Type type, string label, bool isDelayed)
|
|
{
|
|
Clear();
|
|
|
|
// Add [Create...] button
|
|
if (obj == null && !typeof(UnityEngine.Object).IsAssignableFrom(type))
|
|
{
|
|
var nullLabelElement = new VisualElement()
|
|
{
|
|
style = {
|
|
width = Length.Percent(100f),
|
|
height = EditorGUIUtility.singleLineHeight + EditorGUIUtility.standardVerticalSpacing,
|
|
paddingLeft = 3f,
|
|
flexDirection = FlexDirection.Row
|
|
}
|
|
};
|
|
|
|
nullLabelElement.Add(new Label(label + " (Null)")
|
|
{
|
|
style = {
|
|
flexGrow = 1f,
|
|
unityTextAlign = TextAnchor.MiddleLeft
|
|
}
|
|
});
|
|
|
|
// TODO: support polymorphism
|
|
if (type == typeof(string))
|
|
{
|
|
nullLabelElement.Add(new Button(() =>
|
|
{
|
|
var instance = "";
|
|
Build(instance, type, label, isDelayed);
|
|
OnValueChanged?.Invoke(instance);
|
|
})
|
|
{
|
|
text = CreateButtonText
|
|
});
|
|
}
|
|
else if (type.IsArray && type.GetArrayRank() == 1)
|
|
{
|
|
nullLabelElement.Add(new Button(() =>
|
|
{
|
|
var instance = Array.CreateInstance(type.GetElementType(), 0);
|
|
Build(instance, type, label, isDelayed);
|
|
OnValueChanged?.Invoke(instance);
|
|
})
|
|
{
|
|
text = CreateButtonText
|
|
});
|
|
}
|
|
else if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>)) // nullable
|
|
{
|
|
nullLabelElement.Add(new Button(() =>
|
|
{
|
|
var instance = Activator.CreateInstance(type, Activator.CreateInstance(type.GenericTypeArguments[0]));
|
|
Build(instance, type, label, isDelayed);
|
|
OnValueChanged?.Invoke(instance);
|
|
})
|
|
{
|
|
text = CreateButtonText
|
|
});
|
|
}
|
|
else if (TypeHelper.HasDefaultConstructor(type))
|
|
{
|
|
nullLabelElement.Add(new Button(() =>
|
|
{
|
|
var instance = TypeHelper.CreateDefaultInstance(type);
|
|
Build(instance, type, label, isDelayed);
|
|
OnValueChanged?.Invoke(instance);
|
|
})
|
|
{
|
|
text = CreateButtonText
|
|
});
|
|
}
|
|
|
|
Add(nullLabelElement);
|
|
|
|
return;
|
|
}
|
|
|
|
this.isDelayed = isDelayed;
|
|
|
|
if (type == typeof(bool))
|
|
{
|
|
AddField(new Toggle(label), (bool)obj);
|
|
}
|
|
else if (type == typeof(int))
|
|
{
|
|
AddField(new IntegerField(label), (int)obj);
|
|
}
|
|
else if (type == typeof(sbyte))
|
|
{
|
|
AddSmallIntegerField(label, (sbyte)obj, sbyte.MinValue, sbyte.MaxValue, x => (sbyte)x);
|
|
}
|
|
else if (type == typeof(byte))
|
|
{
|
|
AddSmallIntegerField(label, (byte)obj, byte.MinValue, byte.MaxValue, x => (byte)x);
|
|
}
|
|
else if (type == typeof(short))
|
|
{
|
|
AddSmallIntegerField(label, (short)obj, short.MinValue, short.MaxValue, x => (short)x);
|
|
}
|
|
else if (type == typeof(ushort))
|
|
{
|
|
AddSmallIntegerField(label, (ushort)obj, ushort.MinValue, ushort.MaxValue, x => (ushort)x);
|
|
}
|
|
|
|
else if (type == typeof(uint))
|
|
{
|
|
#if UNITY_2022_1_OR_NEWER
|
|
AddField(new UnsignedIntegerField(label), (uint)obj);
|
|
#else
|
|
var value = (uint)obj;
|
|
var control = new LongField(label);
|
|
control.value = value;
|
|
control.RegisterValueChangedCallback(x =>
|
|
{
|
|
var newValue = (uint)Math.Clamp(control.value, 0, uint.MaxValue);
|
|
OnValueChanged?.Invoke(newValue);
|
|
control.value = newValue;
|
|
});
|
|
|
|
Add(control);
|
|
#endif
|
|
|
|
}
|
|
else if (type == typeof(long))
|
|
{
|
|
AddField(new LongField(label), (long)obj);
|
|
}
|
|
else if (type == typeof(ulong))
|
|
{
|
|
#if UNITY_2022_1_OR_NEWER
|
|
AddField(new UnsignedLongField(label), (ulong)obj);
|
|
#else
|
|
var value = (ulong)obj;
|
|
var control = new LongField(label);
|
|
control.value = (long)value;
|
|
control.RegisterValueChangedCallback(x =>
|
|
{
|
|
var newValue = Math.Clamp(control.value, 0, long.MaxValue);
|
|
OnValueChanged?.Invoke((ulong)newValue);
|
|
control.value = newValue;
|
|
});
|
|
|
|
Add(control);
|
|
#endif
|
|
}
|
|
else if (type == typeof(float))
|
|
{
|
|
AddField(new FloatField(label), (float)obj);
|
|
}
|
|
else if (type == typeof(double) || type == typeof(decimal))
|
|
{
|
|
AddField(new DoubleField(label), (double)obj);
|
|
}
|
|
else if (type == typeof(string))
|
|
{
|
|
AddField(new TextField(label), (string)obj);
|
|
}
|
|
else if (type == typeof(char))
|
|
{
|
|
var charField = new TextField(label, 1, false, false, default) { value = obj.ToString() };
|
|
charField.RegisterValueChangedCallback(x =>
|
|
{
|
|
if (string.IsNullOrEmpty(x.newValue)) return;
|
|
OnValueChanged?.Invoke(x.newValue[0]);
|
|
});
|
|
Add(charField);
|
|
}
|
|
else if (type.IsEnum)
|
|
{
|
|
var value = (Enum)obj;
|
|
if (value.GetType().HasCustomAttribute<FlagsAttribute>()) AddField(new EnumFlagsField(label, value), value);
|
|
else AddField(new EnumField(label, value), value);
|
|
}
|
|
else if (type == typeof(Vector2))
|
|
{
|
|
AddField(new Vector2Field(label), (Vector2)obj);
|
|
}
|
|
else if (type == typeof(Vector2Int))
|
|
{
|
|
AddField(new Vector2IntField(label), (Vector2Int)obj);
|
|
}
|
|
else if (type == typeof(Vector3))
|
|
{
|
|
AddField(new Vector3Field(label), (Vector3)obj);
|
|
}
|
|
else if (type == typeof(Vector3Int))
|
|
{
|
|
AddField(new Vector3IntField(label), (Vector3Int)obj);
|
|
}
|
|
else if (type == typeof(Vector4))
|
|
{
|
|
AddField(new Vector4Field(label), (Vector4)obj);
|
|
}
|
|
else if (type == typeof(Rect))
|
|
{
|
|
AddField(new RectField(label), (Rect)obj);
|
|
}
|
|
else if (type == typeof(RectInt))
|
|
{
|
|
AddField(new RectIntField(label), (RectInt)obj);
|
|
}
|
|
else if (type == typeof(Bounds))
|
|
{
|
|
AddField(new BoundsField(label), (Bounds)obj);
|
|
}
|
|
else if (type == typeof(BoundsInt))
|
|
{
|
|
AddField(new BoundsIntField(label), (BoundsInt)obj);
|
|
}
|
|
else if (type == typeof(Color))
|
|
{
|
|
AddField(new ColorField(label), (Color)obj);
|
|
}
|
|
else if (type == typeof(Gradient))
|
|
{
|
|
AddField(new GradientField(label), (Gradient)obj);
|
|
}
|
|
else if (type == typeof(Hash128))
|
|
{
|
|
AddField(new Hash128Field(label), (Hash128)obj);
|
|
}
|
|
else if (type == typeof(AnimationCurve))
|
|
{
|
|
AddField(new CurveField(label), (AnimationCurve)obj);
|
|
}
|
|
else if (type == typeof(Gradient))
|
|
{
|
|
AddField(new GradientField(label), (Gradient)obj);
|
|
}
|
|
else if (typeof(UnityEngine.Object).IsAssignableFrom(type))
|
|
{
|
|
AddField(new ObjectField(label) { objectType = type }, (UnityEngine.Object)obj);
|
|
}
|
|
else if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(HashSet<>))
|
|
{
|
|
var field = new HashSetField(obj, label);
|
|
field.OnValueChanged += x => OnValueChanged?.Invoke(x);
|
|
|
|
Add(field);
|
|
}
|
|
else if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Dictionary<,>))
|
|
{
|
|
var field = new DictionaryField(obj, label);
|
|
field.OnValueChanged += x => OnValueChanged?.Invoke(x);
|
|
Add(field);
|
|
}
|
|
else if (typeof(IList).IsAssignableFrom(type))
|
|
{
|
|
var field = new ListField((IList)obj, label);
|
|
field.OnValueChanged += x => OnValueChanged?.Invoke(x);
|
|
Add(field);
|
|
}
|
|
else
|
|
{
|
|
var field = new ClassField(obj, type, label);
|
|
field.OnValueChanged += x => OnValueChanged?.Invoke(x);
|
|
Add(field);
|
|
}
|
|
}
|
|
|
|
public event Action<object> OnValueChanged;
|
|
bool isDelayed;
|
|
bool changed;
|
|
|
|
void AddSmallIntegerField<T>(string label, int value, int minValue, int maxValue, Func<int, T> convert)
|
|
{
|
|
var control = new IntegerField(label) { value = value };
|
|
control.RegisterValueChangedCallback(x =>
|
|
{
|
|
var newValue = Math.Clamp(x.newValue, minValue, maxValue);
|
|
control.SetValueWithoutNotify(newValue);
|
|
if (isDelayed)
|
|
{
|
|
changed = true;
|
|
}
|
|
else
|
|
{
|
|
OnValueChanged?.Invoke(convert(newValue));
|
|
}
|
|
});
|
|
if (isDelayed)
|
|
{
|
|
control.RegisterCallback<FocusOutEvent>(_ =>
|
|
{
|
|
if (!changed) return;
|
|
OnValueChanged?.Invoke(convert(control.value));
|
|
changed = false;
|
|
});
|
|
}
|
|
Add(control);
|
|
}
|
|
|
|
void AddField<T>(BaseField<T> control, T value)
|
|
{
|
|
control.value = value;
|
|
if (isDelayed && control is not ObjectField) // ignore ObjectField
|
|
{
|
|
control.RegisterValueChangedCallback(x => changed = true);
|
|
control.RegisterCallback<FocusOutEvent>(x =>
|
|
{
|
|
if (changed)
|
|
{
|
|
OnValueChanged?.Invoke(control.value);
|
|
changed = false;
|
|
}
|
|
});
|
|
}
|
|
else
|
|
{
|
|
control.RegisterValueChangedCallback(x => OnValueChanged?.Invoke(x.newValue));
|
|
}
|
|
Add(control);
|
|
}
|
|
}
|
|
}
|