CustomGUIFactory.cs 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152
  1. // Animancer // https://kybernetik.com.au/animancer // Copyright 2018-2024 Kybernetik //
  2. #if UNITY_EDITOR
  3. //#define LOG_CUSTOM_GUI_FACTORY
  4. using System;
  5. using System.Collections.Generic;
  6. using System.Reflection;
  7. using System.Runtime.CompilerServices;
  8. using UnityEditor;
  9. using UnityEngine;
  10. namespace Animancer.Editor
  11. {
  12. /// <summary>[Editor-Only] Draws a custom GUI for an object.</summary>
  13. /// <remarks>
  14. /// Every non-abstract type implementing this interface must have at least one <see cref="CustomGUIAttribute"/>.
  15. /// </remarks>
  16. /// https://kybernetik.com.au/animancer/api/Animancer.Editor/CustomGUIFactory
  17. ///
  18. public static class CustomGUIFactory
  19. {
  20. /************************************************************************************************************************/
  21. private static readonly Dictionary<Type, Type>
  22. TargetTypeToGUIType = new();
  23. static CustomGUIFactory()
  24. {
  25. foreach (var guiType in TypeCache.GetTypesWithAttribute(typeof(CustomGUIAttribute)))
  26. {
  27. if (guiType.IsAbstract ||
  28. guiType.IsInterface)
  29. continue;
  30. if (!typeof(ICustomGUI).IsAssignableFrom(guiType))
  31. {
  32. Debug.LogWarning(
  33. $"{guiType.FullName} has a {nameof(CustomGUIAttribute)}" +
  34. $" but doesn't implement {nameof(ICustomGUI)}.");
  35. continue;
  36. }
  37. var attribute = guiType.GetCustomAttribute<CustomGUIAttribute>();
  38. if (attribute.TargetType != null)
  39. {
  40. TargetTypeToGUIType.Add(attribute.TargetType, guiType);
  41. }
  42. }
  43. }
  44. /************************************************************************************************************************/
  45. private static readonly ConditionalWeakTable<object, ICustomGUI>
  46. TargetToGUI = new();
  47. /// <summary>Returns an existing <see cref="ICustomGUI"/> for the `targetType` or creates one if necessary.</summary>
  48. /// <remarks>Returns null if the `targetType` is null or no valid <see cref="ICustomGUI"/> type is found.</remarks>
  49. public static ICustomGUI GetOrCreateForType(Type targetType)
  50. {
  51. if (targetType == null)
  52. return null;
  53. if (TargetToGUI.TryGetValue(targetType, out var gui))
  54. return gui;
  55. gui = Create(targetType);
  56. TargetToGUI.Add(targetType, gui);
  57. return gui;
  58. }
  59. /// <summary>Returns an existing <see cref="ICustomGUI"/> for the `value` or creates one if necessary.</summary>
  60. /// <remarks>Returns null if the `value` is null or no valid <see cref="ICustomGUI"/> type is found.</remarks>
  61. public static ICustomGUI GetOrCreateForObject(object value)
  62. {
  63. if (value == null)
  64. return null;
  65. if (TargetToGUI.TryGetValue(value, out var gui))
  66. return gui;
  67. gui = Create(value.GetType());
  68. if (gui != null)
  69. gui.Value = value;
  70. TargetToGUI.Add(value, gui);
  71. return gui;
  72. }
  73. /************************************************************************************************************************/
  74. /// <summary>Creates an <see cref="ICustomGUI"/> for the `targetType`.</summary>
  75. /// <remarks>Returns null if the `value` is null or no valid <see cref="ICustomGUI"/> type is found.</remarks>
  76. public static ICustomGUI Create(Type targetType)
  77. {
  78. if (!TryGetGUIType(targetType, out var guiType))
  79. return null;
  80. try
  81. {
  82. if (guiType.IsGenericTypeDefinition)
  83. guiType = guiType.MakeGenericType(targetType);
  84. var gui = (ICustomGUI)Activator.CreateInstance(guiType);
  85. return gui;
  86. }
  87. catch (Exception exception)
  88. {
  89. Debug.LogException(exception);
  90. return null;
  91. }
  92. }
  93. /************************************************************************************************************************/
  94. /// <summary>Tries to determine the valid <see cref="ICustomGUI"/> type for drawing the `target`.</summary>
  95. public static bool TryGetGUIType(Type target, out Type gui)
  96. {
  97. // Try the target and its base types.
  98. var type = target;
  99. while (type != null && type != typeof(object))
  100. {
  101. if (TargetTypeToGUIType.TryGetValue(type, out gui))
  102. return true;
  103. type = type.BaseType;
  104. }
  105. // Try any interfaces.
  106. var interfaces = target.GetInterfaces();
  107. for (int i = 0; i < interfaces.Length; i++)
  108. if (TargetTypeToGUIType.TryGetValue(interfaces[i], out gui))
  109. return true;
  110. // Try base object.
  111. return TargetTypeToGUIType.TryGetValue(typeof(object), out gui);
  112. }
  113. /************************************************************************************************************************/
  114. }
  115. }
  116. #endif