using System; using System.Collections.Generic; using System.IO; using System.Reflection; using Newtonsoft.Json.Serialization; using UnityEditor; using UnityEngine; using UnityEngine.Serialization; using Object = UnityEngine.Object; namespace Needle.Engine { /// /// Add to FileReference field to specify the allowed type and extensions /// [AttributeUsage(AttributeTargets.Field | AttributeTargets.Property | AttributeTargets.Class)] public class FileReferenceTypeAttribute : Attribute { public readonly Type AllowedType; /** File types that aren't allowed to be assigned */ public readonly Type[] ExcludedTypes; public readonly string[] AllowedExtensions; public FileReferenceTypeAttribute() { } public FileReferenceTypeAttribute(Type type = null, params string[] allowedExtensions) { this.AllowedType = type; this.AllowedExtensions = allowedExtensions; } public FileReferenceTypeAttribute(Type type = null, Type[] excludedTypes = null, params string[] allowedExtensions) { this.AllowedType = type; this.AllowedExtensions = allowedExtensions; this.ExcludedTypes = excludedTypes; } } /// /// Add the FileReferenceType attribute to specify the allowed type and extensions /// [Serializable] public class FileReference { public enum FileReferenceMode { Object = 0, Path = 1, } public FileReferenceMode Mode = FileReferenceMode.Object; [FormerlySerializedAs("Texture")] public Object File; public string String; } // TODO: should we also exclude Texture3D here? [Serializable, FileReferenceType(typeof(Texture), new[]{typeof(RenderTexture)})] public class ImageReference : FileReference { } #if UNITY_EDITOR [CustomPropertyDrawer(typeof(FileReference), true)] [CustomPropertyDrawer(typeof(ImageReference), true)] public class FileReferenceDrawer : PropertyDrawer { private static readonly Dictionary> cachedAttributes = new Dictionary>(); public override void OnGUI(Rect position, SerializedProperty property, GUIContent label) { if (cachedAttributes.TryGetValue(property, out var list)) { if (TryValidateCustomAttributes(list, position, property, label, true)) return; } else { if (TryValidateCustomAttributes(fieldInfo.GetCustomAttributes(typeof(FileReferenceTypeAttribute)), position, property, label)) return; if (TryValidateCustomAttributes(fieldInfo.FieldType.GetCustomAttributes(), position, property, label)) return; } var mode = property.FindPropertyRelative(nameof(FileReference.Mode)); var modeRect = new Rect(position); modeRect.width = 60; position.width -= modeRect.width; modeRect.x = position.x + position.width; EditorGUI.PropertyField(modeRect, mode, GUIContent.none); switch ((FileReference.FileReferenceMode)mode.intValue) { case FileReference.FileReferenceMode.Object: EditorGUI.PropertyField(position, property.FindPropertyRelative(nameof(FileReference.File)), label); break; case FileReference.FileReferenceMode.Path: EditorGUI.PropertyField(position, property.FindPropertyRelative(nameof(FileReference.String)), label); break; } } private static bool TryValidateCustomAttributes( IEnumerable attributes, Rect position, SerializedProperty property, GUIContent label, bool isCached = false) { if (!cachedAttributes.TryGetValue(property, out var cache)) { cache = new List(); cachedAttributes.Add(property, cache); } foreach (var attr in attributes) { if (attr is FileReferenceTypeAttribute fileAttr) { if(!isCached) cache.Add(attr); if (fileAttr.AllowedType != null) { Object OnValidate(Object[] references, Type type, SerializedProperty p) { if (references.Length == 0) return null; var r = references[0]; if (r) { if (!fileAttr.AllowedType.IsInstanceOfType(r)) return null; if (fileAttr.ExcludedTypes != null) { foreach(var excluded in fileAttr.ExcludedTypes) { if (excluded.IsInstanceOfType(r)) { Debug.LogWarning("File Type " + r.GetType() + " is not assignable to " + property.propertyPath); return null; } } } if (fileAttr.AllowedExtensions.Length > 0) { var path = AssetDatabase.GetAssetPath(r); if (!string.IsNullOrEmpty(path)) { var foundAllowed = false; for (var i = 0; i < fileAttr.AllowedExtensions.Length; i++) { if (foundAllowed) break; var ext = fileAttr.AllowedExtensions[i]; if (path.EndsWith(ext)) foundAllowed = true; } if (!foundAllowed) { Debug.LogWarning("File Type " + Path.GetExtension(path) + " is not assignable to " + property.propertyPath); return null; } } } } return r; } // TODO: this can be cached PublicEditorGUI.ObjectField(position, property.FindPropertyRelative(nameof(FileReference.File)), fileAttr.AllowedType, label, OnValidate); return true; } } } return false; } } #endif // #if UNITY_EDITOR // [CustomPropertyDrawer(typeof(ImageReference))] // public class ImageReferenceDrawer : PropertyDrawer // { // public override void OnGUI(Rect position, SerializedProperty property, GUIContent label) // { // EditorGUI.PropertyField(position, property.FindPropertyRelative(nameof(ImageReference.File)), label); // } // } // #endif }