Editor/ControlWidgets/TypeCreatorControlWidget.cs
using Editor;
using ExtendedEditor.Attributes;
using ExtendedEditor.TypeLibraryFixes;
using Sandbox;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
namespace ExtendedEditor.ControlWidgets;
[CustomEditor(typeof(TypeCreator))]
public class TypeCreatorControlWidget : ControlObjectWidget
{
private const string ArgumentsPropertyName = "Arguments";
private Type? Type
{
get
{
return SerializedObject.GetProperty(nameof(TypeCreator.Type))?.GetValue<Type>();
}
}
private readonly Layout _argumentsLayout;
private ConstructorInfo? _constructor;
public TypeCreatorControlWidget(SerializedProperty property) : base(property.Fix(), true)
{
SerializedObject.OnPropertyChanged += OnPropertyChanged;
Layout = Layout.Column();
Layout.Margin = 4;
Layout.Spacing = 4;
var attributes = SerializedProperty.GetAttributes();
var limiter = new TypeSelectAttribute() { AllowNone = false, AllowAbstract = false };
if(attributes.FirstOrDefault(x => x is TypeSelectAttribute) is TypeSelectAttribute outsideLimiter)
{
if(outsideLimiter.ValidatorName is not null)
{
outsideLimiter = new TypeSelectAttribute(outsideLimiter);
outsideLimiter.FindAndAppendValidatorMethod(SerializedProperty);
}
attributes = attributes.Where(x => x is not TypeSelectAttribute);
limiter = outsideLimiter.RestrictBy(limiter);
}
var attributesWithLimiter = attributes.Append(limiter);
var typeProperty = SerializedObject.GetProperty(nameof(TypeCreator.Type));
var typePropertyProxy = typeProperty.CreateProxy(attributesWithLimiter);
var argumentsProperty = SerializedObject.GetProperty(ArgumentsPropertyName);
var argumentsPropertyProxy = argumentsProperty.CreateProxy(argumentsProperty.GetAttributes());
Layout.Add(new GenericTypeSelectControlWidget(typePropertyProxy));
_argumentsLayout = Layout.Add(Layout.Row());
if(!UpdateArguments())
RebuildArgumentsLayout();
}
private void RebuildArgumentsLayout()
{
_argumentsLayout.Clear(true);
var ignoreAttributes = SerializedProperty.GetAttributes<IgnoreArgumentAttribute>();
var parameters = (_constructor?.GetParameters() ?? [])
.Index().Where(x => !ignoreAttributes.Any(a => a.Index == x.Index)).Select(x => x.Item).ToArray();
Layout.Margin = parameters.Length > 0 ? 4 : 0;
if(parameters.Length == 0)
return;
var argumentsProperty = SerializedObject.GetProperty(ArgumentsPropertyName);
var argumentsList = Layout.Column();
_argumentsLayout.Add(argumentsList);
var arguments = argumentsProperty.GetValue<Any<object>[]>() ?? [];
for(int i = 0; i < arguments.Length; ++i)
{
int index = i;
var argument = arguments[i];
if(argument.Value is null)
continue;
var type = parameters[i].ParameterType;
var attributes = new Attribute[] {
new TypeSelectAttribute(false, false)
{
RequiredBaseTypes = [type],
},
new InlineEditorAttribute()
};
var name = parameters[i].Name!;
var property = TypeLibrary.CreatePropertyFixed(name, () => arguments[index],
v => arguments[index] = v, attributes, SerializedObject);
argumentsList.Add(ControlSheet.CreateRow(property));
}
}
private void OnPropertyChanged(SerializedProperty serializedProperty)
{
if(serializedProperty.Name == nameof(TypeCreator.Type) && serializedProperty.Parent == SerializedObject)
UpdateArguments();
}
private bool UpdateArguments()
{
var ignoreAttributes = SerializedProperty.GetAttributes<IgnoreArgumentAttribute>();
var requireArgumentAttributes = SerializedProperty.GetAttributes<RequireArgumentAttribute>();
var argumentsProperty = SerializedObject.GetProperty(ArgumentsPropertyName);
var type = Type;
if(type is null)
{
argumentsProperty.SetValue(Array.Empty<Any<object>>());
RebuildArgumentsLayout();
return true;
}
var oldConstructor = _constructor;
_constructor = type.GetConstructors(BindingFlags.Instance | BindingFlags.Public)
.Where(x =>
{
var parameters = x.GetParameters();
foreach(var attribute in requireArgumentAttributes)
{
if(attribute.Index < 0 || attribute.Index >= parameters.Length || attribute.Type == null)
continue;
var parameterInfo = parameters[attribute.Index];
var parameterType = parameterInfo.ParameterType;
if(parameterType.IsByRef)
parameterType = parameterType.GetElementType();
if(attribute.Type != parameterType)
return false;
}
return true;
})
.OrderBy(x => x.GetParameters().Length).FirstOrDefault();
if(_constructor == oldConstructor)
return false;
if(_constructor is null)
{
Log.Error($"Constructor for {type.Name} not found.");
argumentsProperty.SetValue(Array.Empty<Any<object>>());
RebuildArgumentsLayout();
return true;
}
var parameters = _constructor.GetParameters().Index()
.Where(x => !ignoreAttributes.Any(a => a.Index == x.Index))
.Select(x => x.Item).ToArray();
var arguments = new Any<object>[parameters.Length];
var oldArguments = argumentsProperty.GetValue<Any<object>[]>() ?? [];
bool changedArgumentsValues = arguments.Length != oldArguments.Length;
for(int i = 0; i < arguments.Length; ++i)
{
var parameterInfo = parameters[i];
var parameterType = parameterInfo.ParameterType;
if(parameterType.IsByRef)
{
if(parameterInfo.IsOut)
{
arguments[i] = default!;
continue;
}
parameterType = parameterType.GetElementType()!;
}
if(oldArguments.Length > i && (oldArguments[i] is Any<object> oldSerialzableObject) && (oldSerialzableObject.Value?.GetType().IsAssignableTo(parameterType) ?? false))
{
arguments[i] = oldArguments[i];
}
else
{
if(parameterType.IsGenericType && parameterType.GetGenericTypeDefinition() == typeof(IEnumerable<>))
{
var listType = typeof(List<>).MakeGenericType([parameterType.GetGenericArguments()[0]]);
arguments[i] = new(TypeLibrary.Create<object>(listType));
}
else
{
arguments[i] = new(TypeLibrary.Create<object>(parameterType));
}
changedArgumentsValues = true;
}
if(arguments[i].Value is null)
Log.Error($"Couldn't create parameter of type {parameterType}.");
}
if(changedArgumentsValues)
{
argumentsProperty.SetValue(arguments);
RebuildArgumentsLayout();
}
return changedArgumentsValues;
}
protected override void PaintUnder()
{
if(!PaintBackground)
return;
var backgroundColor = Theme.ControlBackground.Lighten(0.5f);
Paint.ClearPen();
Paint.SetBrush(backgroundColor);
Paint.DrawRect(LocalRect, Theme.ControlRadius);
}
}