Editor/ControlWidgets/AnyControlWidget.cs
using Editor;
using ExtendedEditor.Attributes;
using ExtendedEditor.TypeLibraryFixes;
using Sandbox;
using System;
using System.Linq;

namespace ExtendedEditor.ControlWidgets;

[CustomEditor(typeof(Any<>))]
public class AnyControlWidget : ControlObjectWidget
{
    public override bool SupportsMultiEdit => false;

    private Widget? _valueWidget;

    private Type? Type
    {
        get => _valueProperty?.GetValue<object>()?.GetType();
        set
        {
            if(value == Type)
                return;

            if(value is null || value.IsAbstract)
            {
                _valueProperty.SetValue<object?>(null);
                return;
            }

            var newValue = TypeLibrary.Create<object>(value);
            _valueProperty.SetValue(newValue);

            _valueWidget?.Enabled = newValue is not null;
            RebuildLayout();
        }
    }

    private Widget _typeWidget = null!;
    private SerializedProperty _valueProperty;
    private Type? _lastBuildType;

    public AnyControlWidget(SerializedProperty property) : base(property.Fix(), true)
    {
        _valueProperty = SerializedObject.GetProperty(nameof(Any<>.Value));

        Layout = Layout.Column();
        Layout.Margin = 4;
        Layout.Spacing = 4;
        RebuildLayout();
    }

    private void RebuildLayout()
    {
        var value = _valueProperty.GetValue<object>();
        var currentType = value?.GetType();
        if(currentType != null && currentType == _lastBuildType)
            return;

        Layout.Clear(true);
        _valueWidget = null;

        _valueProperty = SerializedObject.GetProperty(nameof(Any<>.Value));

        var limiter = new TypeSelectAttribute()
        {
            AllowNone = true,
            AllowAbstract = false,
            RequiredBaseTypes = [SerializedProperty.PropertyType.GetGenericArguments()[0]]
        };

        var typePropertyAttributes = SerializedProperty.GetAttributes();
        if(typePropertyAttributes.FirstOrDefault(x => x is TypeSelectAttribute) is TypeSelectAttribute outsideLimiter)
        {
            if(outsideLimiter.ValidatorName is not null)
            {
                outsideLimiter = new TypeSelectAttribute(outsideLimiter);
                outsideLimiter.FindAndAppendValidatorMethod(SerializedProperty);
            }

            typePropertyAttributes = typePropertyAttributes.Where(x => x is not TypeSelectAttribute);
            limiter = outsideLimiter.RestrictBy(limiter);
        }

        typePropertyAttributes = typePropertyAttributes.Append(limiter);

        var typeProperty = TypeLibrary.CreatePropertyFixed("Type", () => Type, t => Type = t,
            [.. typePropertyAttributes], SerializedObject);

        _lastBuildType = currentType;

        if(value is not null)
        {
            var attributes = SerializedProperty.GetAttributes();
            var inline = attributes.FirstOrDefault(x => x is InlineEditorAttribute);
            if(inline is not null)
                attributes = attributes.Where(x => x is not InlineEditorAttribute).Append(new InlineEditorAttribute() { Label = false });
            _valueProperty = _valueProperty.CreateProxy(currentType!, attributes);
        }
        else if(limiter.AllowNone == false)
        {
            var types = TypeSelectControlWidget.GetPossibleTypes(typeProperty.GetAttributes());
            if(types.Any())
            {
                var newType = types.First(x => !x.IsAbstract);
                if(Type != newType)
                {
                    Type = newType;
                    return;
                }
            }
        }

        _typeWidget = new GenericTypeSelectControlWidget(typeProperty);
        Layout.Add(_typeWidget);

        if(value is null)
            _valueWidget = null;
        else
            _valueWidget = Layout.Add(Create(_valueProperty));
    }

    [EditorEvent.Hotload]
    private void OnHotload()
    {
        _lastBuildType = null;
        RebuildLayout();
    }

    protected override void OnValueChanged()
    {
        RebuildLayout();
    }

    public override void ChildValuesChanged(Widget source)
    {
        if(_typeWidget == source || _typeWidget.IsAncestorOf(source))
            RebuildLayout();
    }

    protected override void PaintUnder()
    {
        bool isInline = SerializedProperty.HasAttribute<InlineEditorAttribute>();

        if(isInline)
        {
            Paint.ClearPen();
            Paint.SetBrush(Theme.WidgetBackground);
            Paint.DrawRect(LocalRect, Theme.ControlRadius);
            return;
        }

        base.PaintUnder();
    }
}