153 lines
5.2 KiB
C#
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
|
|
|