@@ -0,0 +1,343 @@
using System ;
using System.Collections.Generic ;
using System.Linq ;
using Newtonsoft.Json.UnityConverters.Configuration ;
using UnityEditor ;
using UnityEditor.AnimatedValues ;
using UnityEngine ;
// Unity 2020 also has a type cache: UnityEditor.TypeCache:
// https://docs.unity3d.com/2019.2/Documentation/ScriptReference/TypeCache.html
using TypeCache = Newtonsoft . Json . UnityConverters . Utility . TypeCache ;
namespace Newtonsoft.Json.UnityConverters.Editor
{
using Editor = UnityEditor . Editor ;
[CustomEditor(typeof(UnityConvertersConfig))]
public class UnityConvertersConfigEditor : Editor
{
private SerializedProperty _useUnityContractResolver ;
private SerializedProperty _useAllOutsideConverters ;
private SerializedProperty _outsideConverters ;
private SerializedProperty _useAllUnityConverters ;
private SerializedProperty _unityConverters ;
private SerializedProperty _useAllJsonNetConverters ;
private SerializedProperty _jsonNetConverters ;
private SerializedProperty _autoSyncConverters ;
private AnimBool _outsideConvertersShow ;
private AnimBool _unityConvertersShow ;
private AnimBool _jsonNetConvertersShow ;
private AnimBool _autoSyncConvertersShow ;
private bool _isDirty ;
private static class Styles
{
public static GUIStyle headerStyle ;
public static GUIStyle boldHeaderStyle ;
static Styles ( )
{
headerStyle = new GUIStyle { fontSize = 20 , wordWrap = true , normal = EditorStyles . label . normal } ;
boldHeaderStyle = new GUIStyle { fontSize = 20 , fontStyle = FontStyle . Bold , wordWrap = true , normal = EditorStyles . label . normal } ;
}
}
private void OnEnable ( )
{
var config = ( UnityConvertersConfig ) target ;
var grouped = UnityConverterInitializer . FindGroupedConverters ( config ) ;
// Hack around the "SerializedObjectNotCreatableException: Object at index 0 is null"
// error message
try
{
_ = serializedObject ;
}
catch ( Exception )
{
return ;
}
_useUnityContractResolver = serializedObject . FindProperty ( nameof ( UnityConvertersConfig . useUnityContractResolver ) ) ;
_useAllOutsideConverters = serializedObject . FindProperty ( nameof ( UnityConvertersConfig . useAllOutsideConverters ) ) ;
_outsideConverters = serializedObject . FindProperty ( nameof ( UnityConvertersConfig . outsideConverters ) ) ;
_useAllUnityConverters = serializedObject . FindProperty ( nameof ( UnityConvertersConfig . useAllUnityConverters ) ) ;
_unityConverters = serializedObject . FindProperty ( nameof ( UnityConvertersConfig . unityConverters ) ) ;
_useAllJsonNetConverters = serializedObject . FindProperty ( nameof ( UnityConvertersConfig . useAllJsonNetConverters ) ) ;
_jsonNetConverters = serializedObject . FindProperty ( nameof ( UnityConvertersConfig . jsonNetConverters ) ) ;
_autoSyncConverters = serializedObject . FindProperty ( nameof ( UnityConvertersConfig . autoSyncConverters ) ) ;
_outsideConvertersShow = new AnimBool ( _outsideConverters . isExpanded ) ;
_unityConvertersShow = new AnimBool ( _unityConverters . isExpanded ) ;
_jsonNetConvertersShow = new AnimBool ( _jsonNetConverters . isExpanded ) ;
_autoSyncConvertersShow = new AnimBool ( ! _autoSyncConverters . boolValue ) ;
_outsideConvertersShow . valueChanged . AddListener ( Repaint ) ;
_unityConvertersShow . valueChanged . AddListener ( Repaint ) ;
_jsonNetConvertersShow . valueChanged . AddListener ( Repaint ) ;
serializedObject . Update ( ) ;
AddAndSetupConverters ( _outsideConverters , grouped . outsideConverters , _useAllOutsideConverters . boolValue ) ;
AddAndSetupConverters ( _unityConverters , grouped . unityConverters , _useAllUnityConverters . boolValue ) ;
AddAndSetupConverters ( _jsonNetConverters , grouped . jsonNetConverters , _useAllJsonNetConverters . boolValue ) ;
serializedObject . ApplyModifiedProperties ( ) ;
}
public override void OnInspectorGUI ( )
{
_isDirty = false ;
EditorGUILayout . LabelField ( "Settings for the converters of" , Styles . headerStyle ) ;
EditorGUILayout . LabelField ( "Newtonsoft.Json-for-Unity.Converters" , Styles . boldHeaderStyle ) ;
serializedObject . Update ( ) ;
EditorGUILayout . Space ( ) ;
ToggleLeft ( _useUnityContractResolver , "Custom 'Newtonsoft.Json.Serialization.IContractResolver' defined to" +
" properly handle the 'UnityEngine.SerializeFieldAttribute' attribute and correctly creates" +
" 'UnityEngine.ScriptableObject' via 'ScriptableObject.Create()' instead of the default" +
" 'new ScriptableObject()'." ) ;
EditorGUILayout . Space ( ) ;
FoldedConverters ( _useAllOutsideConverters , _outsideConverters , _outsideConvertersShow ,
"Registers all classes outside of the 'Newtonsoft.Json.*' namespace" +
" that derive from 'Newtonsoft.Json.JsonConverter' that has a default constructor." ) ;
EditorGUILayout . Space ( ) ;
FoldedConverters ( _useAllUnityConverters , _unityConverters , _unityConvertersShow ,
"Registers all classes inside of the 'Newtonsoft.Json.UnityConverters.*' namespace." ) ;
EditorGUILayout . Space ( ) ;
FoldedConverters ( _useAllJsonNetConverters , _jsonNetConverters , _jsonNetConvertersShow ,
"Registers all classes inside of the 'Newtonsoft.Json.UnityConverters.*' namespace." ) ;
EditorGUILayout . Space ( ) ;
ToggleLeft ( _autoSyncConverters ,
"Automatic synchronization of JsonConverter types is enabled by default," +
" but can induce a heavy spike on each assembly reload (such as when entering play mode) on bigger projects." ) ;
_autoSyncConvertersShow . target = ! _autoSyncConverters . boolValue ;
if ( EditorGUILayout . BeginFadeGroup ( _autoSyncConvertersShow . faded ) )
{
EditorGUILayout . HelpBox ( "The Newtonsoft.Json-for-Unity.Converters package will no longer automatically" +
" look for new JsonConverters.\n\n" +
"Having this automatic scan disabled reduces the load spike caused on larger projects every time you enter play mode," +
" but you must instead remember to press the sync button below every time you add a new JsonConverter." ,
MessageType . Warning ) ;
if ( GUILayout . Button ( "Manual Converter Sync Now" ) )
{
var grouped = ConverterGrouping . Create ( UnityConverterInitializer . FindConverters ( ) ) ;
AddAndSetupConverters ( _outsideConverters , grouped . outsideConverters , _useAllOutsideConverters . boolValue ) ;
AddAndSetupConverters ( _unityConverters , grouped . unityConverters , _useAllUnityConverters . boolValue ) ;
AddAndSetupConverters ( _jsonNetConverters , grouped . jsonNetConverters , _useAllJsonNetConverters . boolValue ) ;
}
}
EditorGUILayout . EndFadeGroup ( ) ;
serializedObject . ApplyModifiedProperties ( ) ;
if ( _isDirty )
{
UnityConverterInitializer . RefreshSettingsFromConfig ( ) ;
}
}
private void AddAndSetupConverters ( SerializedProperty arrayProperty , IList < Type > converterTypes , bool newAreEnabledByDefault )
{
var elements = EnumerateArrayElements ( arrayProperty ) . ToArray ( ) ;
var elementTypes = elements
. Select ( e = > TypeCache . FindType (
name : e . FindPropertyRelative ( nameof ( ConverterConfig . converterName ) ) . stringValue ,
assemblyName : e . FindPropertyRelative ( nameof ( ConverterConfig . converterAssembly ) ) . stringValue
) )
. ToArray ( ) ;
// Refresh missing fields on existing types
for ( int i = 0 ; i < elements . Length ; i + + )
{
SerializedProperty elem = elements [ i ] ;
Type type = elementTypes [ i ] ;
var assemblyNameProp = elem . FindPropertyRelative ( nameof ( ConverterConfig . converterAssembly ) ) ;
if ( string . IsNullOrEmpty ( assemblyNameProp . stringValue ) )
{
assemblyNameProp . stringValue = type . Assembly . GetName ( ) . Name ;
}
}
Type [ ] missingConverters = converterTypes
. Where ( type = > ! elementTypes . Contains ( type ) )
. ToArray ( ) ;
// Cleanup excess types
for ( int i = elementTypes . Length - 1 ; i > = 0 ; i - - )
{
if ( converterTypes . Contains ( elementTypes [ i ] ) )
{
continue ;
}
string typeName = arrayProperty . GetArrayElementAtIndex ( i ) . FindPropertyRelative ( nameof ( ConverterConfig . converterName ) ) . stringValue ;
Debug . Log ( $"Removed type from JsonConverter list: \" { typeName } \ "" , target ) ;
arrayProperty . DeleteArrayElementAtIndex ( i ) ;
}
foreach ( Type converterType in missingConverters )
{
int nextIndex = arrayProperty . arraySize ;
arrayProperty . InsertArrayElementAtIndex ( nextIndex ) ;
SerializedProperty elemProp = arrayProperty . GetArrayElementAtIndex ( nextIndex ) ;
SerializedProperty enabledProp = elemProp . FindPropertyRelative ( nameof ( ConverterConfig . enabled ) ) ;
SerializedProperty converterNameProp = elemProp . FindPropertyRelative ( nameof ( ConverterConfig . converterName ) ) ;
SerializedProperty assemblyNameProp = elemProp . FindPropertyRelative ( nameof ( ConverterConfig . converterAssembly ) ) ;
enabledProp . boolValue = newAreEnabledByDefault ;
converterNameProp . stringValue = converterType . FullName ;
assemblyNameProp . stringValue = converterType . Assembly . GetName ( ) . Name ;
}
}
private static IEnumerable < SerializedProperty > EnumerateArrayElements ( SerializedProperty arrayProperty )
{
for ( int i = 0 ; i < arrayProperty . arraySize ; i + + )
{
yield return arrayProperty . GetArrayElementAtIndex ( i ) ;
}
}
private void FoldedConverters (
SerializedProperty useAllBoolProperty ,
SerializedProperty convertersArrayProperty ,
AnimBool foldoutAnim ,
string tooltip )
{
EditorGUILayout . BeginVertical ( EditorStyles . helpBox ) ;
ToggleLeft ( useAllBoolProperty , tooltip ) ;
EditorGUI . BeginDisabledGroup ( useAllBoolProperty . boolValue | | convertersArrayProperty . arraySize = = 0 ) ;
FoldoutConvertersList ( convertersArrayProperty , foldoutAnim ) ;
EditorGUI . EndDisabledGroup ( ) ;
EditorGUILayout . EndVertical ( ) ;
EditorGUILayout . Space ( ) ;
}
private static void ToggleLeft ( SerializedProperty property , string tooltip )
{
var content = new GUIContent {
text = property . displayName ,
tooltip = tooltip ,
} ;
property . boolValue = EditorGUILayout . ToggleLeft ( content , property . boolValue ) ;
}
private void FoldoutConvertersList ( SerializedProperty property , AnimBool fadedAnim )
{
string displayName = $"{property.displayName} ({(property.arraySize == 0 ? " none found " : property.arraySize.ToString())})" ;
EditorGUI . indentLevel + + ;
property . isExpanded = EditorGUILayout . Foldout ( property . isExpanded , displayName , true ) ;
EditorGUI . indentLevel - - ;
fadedAnim . target = property . isExpanded ;
if ( EditorGUILayout . BeginFadeGroup ( fadedAnim . faded ) )
{
EditorGUI . indentLevel + + ;
var allConfigsWithType = EnumerateArrayElements ( property )
. Select ( o = > (
serializedProperty : o ,
type : TypeCache . FindType (
name : o . FindPropertyRelative ( nameof ( ConverterConfig . converterName ) ) . stringValue ,
assemblyName : o . FindPropertyRelative ( nameof ( ConverterConfig . converterAssembly ) ) . stringValue
)
) )
. Where ( o = > o . type ! = null )
. OrderBy ( o = > o . type . FullName ) ;
foreach ( var namespaceGroup in allConfigsWithType . GroupBy ( o = > GetTypeNamespace ( o . type ) ) )
{
var groupLabel = new GUIContent {
tooltip = GetNamespaceTooltip ( namespaceGroup ) ,
text = GetNamespaceHeader ( namespaceGroup ) ,
} ;
EditorGUILayout . LabelField ( groupLabel , EditorStyles . boldLabel ) ;
EditorGUI . indentLevel + + ;
foreach ( var configWithType in namespaceGroup . OrderBy ( o = > o . type ? . Name ) )
{
SerializedProperty enabledProp = configWithType . serializedProperty . FindPropertyRelative ( nameof ( ConverterConfig . enabled ) ) ;
if ( configWithType . type ! = null )
{
var toggleContent = new GUIContent {
text = configWithType . type . Name ,
tooltip = configWithType . type . AssemblyQualifiedName ,
} ;
bool oldValue = enabledProp . boolValue ;
enabledProp . boolValue = EditorGUILayout . ToggleLeft ( toggleContent , enabledProp . boolValue ) ;
if ( oldValue ! = enabledProp . boolValue )
{
_isDirty = true ;
}
}
else
{
if ( enabledProp . boolValue )
{
enabledProp . boolValue = false ;
}
SerializedProperty converterNameProp = configWithType . serializedProperty . FindPropertyRelative ( nameof ( ConverterConfig . converterName ) ) ;
EditorGUI . BeginDisabledGroup ( true ) ;
EditorGUILayout . ToggleLeft ( $"Unknown type: {converterNameProp.stringValue}" , false ) ;
EditorGUI . EndDisabledGroup ( ) ;
}
}
EditorGUI . indentLevel - - ;
EditorGUILayout . Space ( ) ;
}
EditorGUI . indentLevel - - ;
}
EditorGUILayout . EndFadeGroup ( ) ;
}
private static string GetNamespaceHeader ( IGrouping < string , ( SerializedProperty serializedProperty , Type type ) > namespaceGroup )
{
return $"{namespaceGroup.Key ?? " < Misconfigured converters > "} ({namespaceGroup.Count()})" ;
}
private static string GetNamespaceTooltip ( IGrouping < string , ( SerializedProperty serializedProperty , Type type ) > namespaceGroup )
{
switch ( namespaceGroup . Key )
{
case "global::" :
return "Converters found with a default constructor from the global namespace." ;
case null :
return "Converters that was found in the configuration but was" +
" unable to map them to existing types. Maybe the type got renamed, or moved to a different" +
" namespace?" ;
default :
return $"Converters found with a default constructor from the namespace '{namespaceGroup.Key}'." ;
}
}
private static string GetTypeNamespace ( Type type )
{
return type is null ? null : ( type . Namespace ? ? "global::" ) ;
}
}
}