2025-05-09 15:40:34 +08:00

153 lines
5.2 KiB
C#

// Animancer // https://kybernetik.com.au/animancer // Copyright 2018-2024 Kybernetik //
#if UNITY_EDITOR
//#define LOG_CUSTOM_GUI_FACTORY
using System;
using System.Collections.Generic;
using System.Reflection;
using System.Runtime.CompilerServices;
using UnityEditor;
using UnityEngine;
namespace Animancer.Editor
{
/// <summary>[Editor-Only] Draws a custom GUI for an object.</summary>
/// <remarks>
/// Every non-abstract type implementing this interface must have at least one <see cref="CustomGUIAttribute"/>.
/// </remarks>
/// https://kybernetik.com.au/animancer/api/Animancer.Editor/CustomGUIFactory
///
public static class CustomGUIFactory
{
/************************************************************************************************************************/
private static readonly Dictionary<Type, Type>
TargetTypeToGUIType = new();
static CustomGUIFactory()
{
foreach (var guiType in TypeCache.GetTypesWithAttribute(typeof(CustomGUIAttribute)))
{
if (guiType.IsAbstract ||
guiType.IsInterface)
continue;
if (!typeof(ICustomGUI).IsAssignableFrom(guiType))
{
Debug.LogWarning(
$"{guiType.FullName} has a {nameof(CustomGUIAttribute)}" +
$" but doesn't implement {nameof(ICustomGUI)}.");
continue;
}
var attribute = guiType.GetCustomAttribute<CustomGUIAttribute>();
if (attribute.TargetType != null)
{
TargetTypeToGUIType.Add(attribute.TargetType, guiType);
}
}
}
/************************************************************************************************************************/
private static readonly ConditionalWeakTable<object, ICustomGUI>
TargetToGUI = new();
/// <summary>Returns an existing <see cref="ICustomGUI"/> for the `targetType` or creates one if necessary.</summary>
/// <remarks>Returns null if the `targetType` is null or no valid <see cref="ICustomGUI"/> type is found.</remarks>
public static ICustomGUI GetOrCreateForType(Type targetType)
{
if (targetType == null)
return null;
if (TargetToGUI.TryGetValue(targetType, out var gui))
return gui;
gui = Create(targetType);
TargetToGUI.Add(targetType, gui);
return gui;
}
/// <summary>Returns an existing <see cref="ICustomGUI"/> for the `value` or creates one if necessary.</summary>
/// <remarks>Returns null if the `value` is null or no valid <see cref="ICustomGUI"/> type is found.</remarks>
public static ICustomGUI GetOrCreateForObject(object value)
{
if (value == null)
return null;
if (TargetToGUI.TryGetValue(value, out var gui))
return gui;
gui = Create(value.GetType());
if (gui != null)
gui.Value = value;
TargetToGUI.Add(value, gui);
return gui;
}
/************************************************************************************************************************/
/// <summary>Creates an <see cref="ICustomGUI"/> for the `targetType`.</summary>
/// <remarks>Returns null if the `value` is null or no valid <see cref="ICustomGUI"/> type is found.</remarks>
public static ICustomGUI Create(Type targetType)
{
if (!TryGetGUIType(targetType, out var guiType))
return null;
try
{
if (guiType.IsGenericTypeDefinition)
guiType = guiType.MakeGenericType(targetType);
var gui = (ICustomGUI)Activator.CreateInstance(guiType);
return gui;
}
catch (Exception exception)
{
Debug.LogException(exception);
return null;
}
}
/************************************************************************************************************************/
/// <summary>Tries to determine the valid <see cref="ICustomGUI"/> type for drawing the `target`.</summary>
public static bool TryGetGUIType(Type target, out Type gui)
{
// Try the target and its base types.
var type = target;
while (type != null && type != typeof(object))
{
if (TargetTypeToGUIType.TryGetValue(type, out gui))
return true;
type = type.BaseType;
}
// Try any interfaces.
var interfaces = target.GetInterfaces();
for (int i = 0; i < interfaces.Length; i++)
if (TargetTypeToGUIType.TryGetValue(interfaces[i], out gui))
return true;
// Try base object.
return TargetTypeToGUIType.TryGetValue(typeof(object), out gui);
}
/************************************************************************************************************************/
}
}
#endif