Files
2025-11-30 08:35:03 +02:00

193 lines
5.6 KiB
C#

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
{
/// <summary>
/// Add to FileReference field to specify the allowed type and extensions
/// </summary>
[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;
}
}
/// <summary>
/// Add the FileReferenceType attribute to specify the allowed type and extensions
/// </summary>
[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<SerializedProperty, IList<object>> cachedAttributes =
new Dictionary<SerializedProperty, IList<object>>();
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<object> attributes,
Rect position,
SerializedProperty property,
GUIContent label, bool isCached = false)
{
if (!cachedAttributes.TryGetValue(property, out var cache))
{
cache = new List<object>();
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
}