using System; using System.Collections; using System.Collections.Generic; using UnityEditor; using UnityEditor.UIElements; using UnityEngine; using UnityEngine.UIElements; namespace Alchemy.Editor.Elements { /// /// Visual Element that creates a suitable input Field from object type /// 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()) 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 OnValueChanged; bool isDelayed; bool changed; void AddSmallIntegerField(string label, int value, int minValue, int maxValue, Func 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(_ => { if (!changed) return; OnValueChanged?.Invoke(convert(control.value)); changed = false; }); } Add(control); } void AddField(BaseField control, T value) { control.value = value; if (isDelayed && control is not ObjectField) // ignore ObjectField { control.RegisterValueChangedCallback(x => changed = true); control.RegisterCallback(x => { if (changed) { OnValueChanged?.Invoke(control.value); changed = false; } }); } else { control.RegisterValueChangedCallback(x => OnValueChanged?.Invoke(x.newValue)); } Add(control); } } }