Files
Cielonos/Packages/com.lunawolfstudios.scriptablesheets/Editor/Modules/Shared/Utilities/SerializedPropertyUtility.cs

878 lines
29 KiB
C#
Raw Normal View History

2026-01-17 11:35:49 -05:00
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;
using UnityEditor;
using UnityEditorInternal;
using UnityEngine;
using Object = UnityEngine.Object;
namespace LunaWolfStudiosEditor.ScriptableSheets.Shared
{
public static class SerializedPropertyUtility
{
public static readonly Object[] UnityBuiltInAssets;
private static readonly List<SerializedPropertyType> s_InputFieldPropertyTypes = new List<SerializedPropertyType>()
{
SerializedPropertyType.Integer,
SerializedPropertyType.Float,
SerializedPropertyType.String,
SerializedPropertyType.ArraySize,
SerializedPropertyType.Character,
};
// Some Object fields are marked as editable when they shouldn't be. This list helps catch those.
private static readonly List<string> s_ReadOnlyUnityFields = new List<string>() { UnityConstants.Field.Script };
// Unity has a duplicate StaticEditorFlags value for ContributeGI and LightmapStatic so we remove one of them.
private static readonly string[] s_StaticEditorFlags = Enum.GetNames(typeof(StaticEditorFlags)).Where(name => name != "LightmapStatic").ToArray();
private static Type s_StringTableCollectionType;
private static readonly GUIContent s_CreateButtonContent = EditorGUIUtility.IconContent("Toolbar Plus");
2026-03-20 12:07:44 -04:00
private static readonly GUIContent s_ArraySizePlusButtonContent = EditorGUIUtility.IconContent("Toolbar Plus");
private static readonly GUIContent s_ArraySizeMinusButtonContent = (EditorGUIUtility.IconContent("Toolbar Minus"));
private const float MinArraySizeFieldWidth = 10f;
private const float InlineButtonWidth = 20f;
private const float DoubleButtonWidth = InlineButtonWidth * 2f;
2026-01-17 11:35:49 -05:00
static SerializedPropertyUtility()
{
UnityBuiltInAssets = AssetDatabase.LoadAllAssetsAtPath(UnityConstants.Path.BuiltInExtra);
}
// Draw our own fields to eliminate unwanted header and text attributes that property field uses by default.
2026-03-20 12:07:44 -04:00
public static void DrawProperty(this SerializedProperty property, Rect propertyRect, Object rootObject, bool isCustomField, out bool arraySizeChanged, bool showReadOnly, AssetPreviewSettings previewSettings, string defaultNewAssetPath, Action<ScriptableObject, Type> onObjectCreated, Action onResetTextEditing)
2026-01-17 11:35:49 -05:00
{
arraySizeChanged = false;
// There are some fields on other Unity Asset types that are inaccessible. So only draw our own custom fields.
if (isCustomField)
{
var propertyType = property.propertyType;
switch (propertyType)
{
case SerializedPropertyType.Integer:
propertyRect.height = EditorGUIUtility.singleLineHeight;
if (property.IsTypeInt())
{
property.intValue = EditorGUI.IntField(propertyRect, property.intValue);
}
else if (property.IsTypeLong())
{
if (!showReadOnly && property.propertyPath.EndsWith(UnityConstants.Field.TableEntryReferenceKeyId))
{
DrawTableEntryKeyDropdown(propertyRect, property);
}
else
{
property.longValue = EditorGUI.LongField(propertyRect, property.longValue);
}
}
#if UNITY_2022_1_OR_NEWER
else if (property.IsTypeUInt())
{
property.uintValue = DrawUtility.GUI.UIntField(propertyRect, property.uintValue);
}
else if (property.IsTypeULong())
{
property.ulongValue = DrawUtility.GUI.ULongField(propertyRect, property.ulongValue);
}
#endif
else
{
property.intValue = EditorGUI.IntField(propertyRect, property.intValue);
}
break;
case SerializedPropertyType.ArraySize:
2026-03-20 12:07:44 -04:00
var originalHeight = propertyRect.height;
2026-01-17 11:35:49 -05:00
propertyRect.height = EditorGUIUtility.singleLineHeight;
2026-03-20 12:07:44 -04:00
Rect fieldRect;
Rect sizePlusRect;
Rect sizeMinusRect;
if (originalHeight > EditorGUIUtility.singleLineHeight)
{
// Vertical layout for +/- buttons when line height is greater than 1.
var buttonRect = new Rect(propertyRect.xMax - InlineButtonWidth, propertyRect.y, InlineButtonWidth, originalHeight);
fieldRect = new Rect(propertyRect.x, propertyRect.y, propertyRect.width - InlineButtonWidth, propertyRect.height);
sizePlusRect = new Rect(buttonRect.x, buttonRect.y, buttonRect.width, EditorGUIUtility.singleLineHeight);
sizeMinusRect = new Rect(buttonRect.x, buttonRect.y + EditorGUIUtility.singleLineHeight, buttonRect.width, EditorGUIUtility.singleLineHeight);
}
else
{
// Horizontal layout for +/- buttons when line height is 1.
var buttonRect = new Rect(propertyRect.xMax - DoubleButtonWidth, propertyRect.y, DoubleButtonWidth, propertyRect.height);
fieldRect = new Rect(propertyRect.x, propertyRect.y, propertyRect.width - DoubleButtonWidth, propertyRect.height);
sizePlusRect = new Rect(buttonRect.x, buttonRect.y, InlineButtonWidth, buttonRect.height);
sizeMinusRect = new Rect(buttonRect.x + InlineButtonWidth, buttonRect.y, InlineButtonWidth, buttonRect.height);
}
2026-01-17 11:35:49 -05:00
var previousValue = property.intValue;
2026-03-20 12:07:44 -04:00
var showArraySizeButtons = fieldRect.width > MinArraySizeFieldWidth;
if (!showArraySizeButtons)
{
fieldRect.width = propertyRect.width;
}
property.intValue = EditorGUI.IntField(fieldRect, property.intValue);
if (showArraySizeButtons)
{
s_ArraySizePlusButtonContent.tooltip = "Add";
if (GUI.Button(sizePlusRect, s_ArraySizePlusButtonContent))
{
onResetTextEditing?.Invoke();
property.intValue++;
}
s_ArraySizeMinusButtonContent.tooltip = "Remove";
if (GUI.Button(sizeMinusRect, s_ArraySizeMinusButtonContent))
{
onResetTextEditing?.Invoke();
property.intValue = Mathf.Max(0, property.intValue - 1);
}
}
2026-01-17 11:35:49 -05:00
if (previousValue != property.intValue)
{
arraySizeChanged = true;
}
break;
case SerializedPropertyType.Boolean:
propertyRect.height = EditorGUIUtility.singleLineHeight;
property.boolValue = DrawUtility.GUI.ToggleCenter(propertyRect, property.boolValue);
break;
case SerializedPropertyType.Float:
propertyRect.height = EditorGUIUtility.singleLineHeight;
if (property.IsTypeFloat())
{
property.floatValue = EditorGUI.FloatField(propertyRect, property.floatValue);
}
else if (property.IsTypeDouble())
{
property.doubleValue = EditorGUI.DoubleField(propertyRect, property.floatValue);
}
else
{
property.floatValue = EditorGUI.FloatField(propertyRect, property.floatValue);
}
break;
case SerializedPropertyType.String:
if (!showReadOnly && property.propertyPath.EndsWith(UnityConstants.Field.TableReferenceCollectionName))
{
DrawLocalizationTablePicker(propertyRect, property, previewSettings);
}
else
{
property.stringValue = EditorGUI.TextField(propertyRect, property.stringValue);
}
break;
case SerializedPropertyType.Color:
property.colorValue = EditorGUI.ColorField(propertyRect, GUIContent.none, property.colorValue, true, true, false);
break;
case SerializedPropertyType.ObjectReference:
// Only draw asset previews if the height is greater than 1.
if (propertyRect.height > EditorGUIUtility.singleLineHeight)
{
DrawUtility.TableAssetPreview(property.objectReferenceValue, propertyRect, previewSettings);
propertyRect.height = EditorGUIUtility.singleLineHeight;
}
if (!IsDefaultUnityEngineType(rootObject))
{
var objectType = ReflectionUtility.GetNestedFieldType(rootObject.GetType(), property.propertyPath);
if (objectType != null)
{
// If the Object is null and it's a ScriptableObject then give the option to create a new instance of it directly from here.
if (property.objectReferenceValue == null && typeof(ScriptableObject).IsAssignableFrom(objectType))
{
2026-03-20 12:07:44 -04:00
var objectRect = new Rect(propertyRect.x, propertyRect.y, propertyRect.width - InlineButtonWidth, propertyRect.height);
var plusRect = new Rect(objectRect.xMax, propertyRect.y, InlineButtonWidth, propertyRect.height);
2026-01-17 11:35:49 -05:00
property.objectReferenceValue = EditorGUI.ObjectField(objectRect, property.objectReferenceValue, objectType, false);
s_CreateButtonContent.tooltip = $"Create new {objectType.Name} and auto-assign";
if (GUI.Button(plusRect, s_CreateButtonContent))
{
var newObject = ScriptableObject.CreateInstance(objectType);
var guids = AssetDatabase.FindAssets($"t:{objectType.Name}");
var folderPath = defaultNewAssetPath;
// First check if any existing asset path exists.
if (guids != null && guids.Length > 0)
{
var fullPath = AssetDatabase.GUIDToAssetPath(guids[0]);
var newFolderPath = System.IO.Path.GetDirectoryName(fullPath);
// If they exist in a valid folder than use that. Otherwise use the default new asset path.
if (AssetDatabase.IsValidFolder(newFolderPath))
{
folderPath = newFolderPath;
}
}
var filename = $"New{objectType.Name}{UnityConstants.Extensions.Asset}";
var uniquePath = AssetDatabase.GenerateUniqueAssetPath($"{folderPath}/{filename}");
AssetDatabase.CreateAsset(newObject, uniquePath);
AssetDatabase.SaveAssets();
EditorGUIUtility.PingObject(newObject);
Selection.activeObject = newObject;
property.objectReferenceValue = newObject;
onObjectCreated?.Invoke(newObject, objectType);
}
}
else
{
property.objectReferenceValue = EditorGUI.ObjectField(propertyRect, property.objectReferenceValue, objectType, false);
}
}
else
{
EditorGUI.PropertyField(propertyRect, property, GUIContent.none, false);
}
}
else
{
EditorGUI.PropertyField(propertyRect, property, GUIContent.none, false);
}
break;
case SerializedPropertyType.LayerMask:
propertyRect.height = EditorGUIUtility.singleLineHeight;
property.intValue = EditorGUI.MaskField(propertyRect, GUIContent.none, property.intValue, InternalEditorUtility.layers);
break;
case SerializedPropertyType.Enum:
propertyRect.height = EditorGUIUtility.singleLineHeight;
if (!IsDefaultUnityEngineType(rootObject, out string fullTypeName) && (!fullTypeName.StartsWith(UnityConstants.Type.TMPro) || IsAlignmentProperty(property.propertyPath)) && property.TryGetEnumType(rootObject, out Type enumType))
{
if (enumType.HasFlagsAttribute())
{
// Remove none and bitwise combination values.
var filteredEnumNames = new List<string>();
foreach (var value in Enum.GetValues(enumType))
{
var intValue = (int) value;
if (intValue != 0 && (intValue & (intValue - 1)) == 0)
{
filteredEnumNames.Add(value.ToString());
}
}
property.intValue = EditorGUI.MaskField(propertyRect, GUIContent.none, property.intValue, filteredEnumNames.ToArray());
}
else
{
property.intValue = Convert.ToInt32(EditorGUI.EnumPopup(propertyRect, (Enum) Enum.ToObject(enumType, property.intValue)));
}
}
else
{
// Special case for UnityEngine.UI.Slider Component to remove extra space when rendering the Direction property.
if (fullTypeName == UnityConstants.Type.UnityEngineUISlider && property.propertyPath == UnityConstants.Field.Direction)
{
propertyRect.y -= 8;
}
EditorGUI.PropertyField(propertyRect, property, GUIContent.none, false);
}
break;
case SerializedPropertyType.Character:
propertyRect.height = EditorGUIUtility.singleLineHeight;
var stringValue = EditorGUI.TextField(propertyRect, ((char) property.intValue).ToString());
if (!string.IsNullOrEmpty(stringValue))
{
property.intValue = stringValue[0];
}
break;
case SerializedPropertyType.AnimationCurve:
property.animationCurveValue = EditorGUI.CurveField(propertyRect, property.animationCurveValue);
break;
case SerializedPropertyType.Gradient:
var internalPropertyHeight = EditorGUI.GetPropertyHeight(property, false);
if (internalPropertyHeight > EditorGUIUtility.singleLineHeight)
{
var gradientValue = property.GetGradientValue();
property.SetGradientValue(EditorGUI.GradientField(propertyRect, gradientValue));
}
else
{
// Workaround for Unity bug where EditorGUI.GradientField bleeds outside of its rect.
EditorGUI.PropertyField(propertyRect, property, GUIContent.none, false);
}
break;
case SerializedPropertyType.Generic:
default:
propertyRect.height = EditorGUIUtility.singleLineHeight;
EditorGUI.PropertyField(propertyRect, property, GUIContent.none, false);
break;
}
}
else
{
// Draw Unity layer and tag fields accordingly.
switch (property.propertyPath)
{
case UnityConstants.Field.Layer:
propertyRect.height = EditorGUIUtility.singleLineHeight;
property.intValue = EditorGUI.LayerField(propertyRect, property.intValue);
break;
case UnityConstants.Field.StaticEditorFlags:
propertyRect.height = EditorGUIUtility.singleLineHeight;
var newValue = EditorGUI.MaskField(propertyRect, property.intValue, s_StaticEditorFlags);
// Unity uses -1 to represent everything in a mask field, but StaticEditorFlags uses int.MaxValue to represent everything.
if (newValue <= -1)
{
newValue = int.MaxValue;
}
property.intValue = newValue;
break;
case UnityConstants.Field.Tag:
propertyRect.height = EditorGUIUtility.singleLineHeight;
property.stringValue = EditorGUI.TagField(propertyRect, property.stringValue);
break;
default:
if (property.propertyType == SerializedPropertyType.Boolean)
{
propertyRect.height = EditorGUIUtility.singleLineHeight;
property.boolValue = DrawUtility.GUI.ToggleCenter(propertyRect, property.boolValue);
}
else if (property.propertyType == SerializedPropertyType.String)
{
propertyRect.height = EditorGUIUtility.singleLineHeight;
property.stringValue = EditorGUI.TextField(propertyRect, property.stringValue);
}
else if (property.propertyType == SerializedPropertyType.ObjectReference)
{
// Only draw asset previews if the height is greater than 1.
if (propertyRect.height > EditorGUIUtility.singleLineHeight)
{
DrawUtility.TableAssetPreview(property.objectReferenceValue, propertyRect, previewSettings);
propertyRect.height = EditorGUIUtility.singleLineHeight;
}
EditorGUI.PropertyField(propertyRect, property, GUIContent.none, false);
}
else
{
propertyRect.height = EditorGUIUtility.singleLineHeight;
EditorGUI.PropertyField(propertyRect, property, GUIContent.none, false);
}
break;
}
}
}
public static SerializedPropertyType GetSheetsPropertyType(this SerializedProperty property, bool isScriptableObject)
{
if (isScriptableObject)
{
return property.propertyType;
}
// Handle special cases like Layers and Tags where Unity considers them int and string fields respectively.
switch (property.propertyPath)
{
case UnityConstants.Field.Layer:
return SerializedPropertyType.LayerMask;
case UnityConstants.Field.StaticEditorFlags:
case UnityConstants.Field.Tag:
return SerializedPropertyType.Enum;
default:
return property.propertyType;
}
}
public static string FriendlyPropertyPath(this SerializedProperty property)
{
return FriendlyPropertyPath(property.propertyPath, property.displayName);
}
public static string FriendlyPropertyPath(string propertyPath, string displayName)
{
var friendlyPath = propertyPath;
if (friendlyPath.Contains('.'))
{
friendlyPath = friendlyPath.Replace(UnityConstants.ArrayPropertyPath, "[");
friendlyPath = friendlyPath.Replace($".{UnityConstants.Field.Array}.{UnityConstants.Field.ArraySize}", $".{UnityConstants.Field.ArraySize}");
friendlyPath = friendlyPath.Replace("m_", string.Empty);
if (friendlyPath.Length > 1)
{
var lastIndex = friendlyPath.LastIndexOf('.');
if (lastIndex > 0)
{
var secondLastIndex = friendlyPath.LastIndexOf('.', lastIndex - 1);
if (secondLastIndex > -1)
{
friendlyPath = friendlyPath.Substring(secondLastIndex + 1);
}
else if (friendlyPath.Contains(']') && !friendlyPath.Contains("]."))
{
var lastSegment = friendlyPath.Substring(lastIndex + 1);
friendlyPath = lastSegment;
}
}
friendlyPath = friendlyPath.Replace('.', ' ').Trim();
friendlyPath = Regex.Replace(friendlyPath, @"<([^>]+)>k__BackingField", "$1");
friendlyPath = Regex.Replace(friendlyPath, @"(\p{Ll})(\p{Lu})", "$1 $2");
friendlyPath = Regex.Replace(friendlyPath, @"\b[a-z]", c => c.Value.ToUpper());
if (!string.IsNullOrEmpty(friendlyPath))
{
return friendlyPath;
}
}
}
return displayName;
}
// Needs to handle edge cases like:
// Sprite PPtr<$Sprite>
// PPtr<Material PPtr<Material>
public static string FriendlyType(string type)
{
var lastOpen = type.LastIndexOf('<');
var lastClose = type.LastIndexOf('>');
if (lastOpen != -1 && lastClose != -1 && lastOpen < lastClose)
{
var lastValue = type.Substring(lastOpen + 1, lastClose - lastOpen - 1);
return lastValue.Replace("$", string.Empty);
}
return type;
}
public static string FriendlyType(this SerializedProperty property)
{
return FriendlyType(property.type);
}
public static string GetFloatStringValue(this SerializedProperty property)
{
// We can't use SerializedProperty.numericType until 2022.1 so we can roll our own by checking type.
switch (property.type)
{
case UnityConstants.Type.Float:
return property.floatValue.ToString();
case UnityConstants.Type.Double:
return property.doubleValue.ToString();
default:
return property.floatValue.ToString();
}
}
public static string GetIntStringValue(this SerializedProperty property)
{
switch (property.type)
{
case UnityConstants.Type.Int:
return property.intValue.ToString();
case UnityConstants.Type.Long:
return property.longValue.ToString();
case UnityConstants.Type.UInt:
#if UNITY_2022_1_OR_NEWER
return property.uintValue.ToString();
#else
return property.intValue.ToString();
#endif
case UnityConstants.Type.ULong:
#if UNITY_2022_1_OR_NEWER
return property.ulongValue.ToString();
#else
return property.longValue.ToString();
#endif
default:
return property.intValue.ToString();
}
}
public static bool TrySetFloatValue(this SerializedProperty property, string value)
{
switch (property.type)
{
case UnityConstants.Type.Float:
if (float.TryParse(value, out float floatValue))
{
property.floatValue = floatValue;
return true;
}
else
{
return false;
}
case UnityConstants.Type.Double:
if (double.TryParse(value, out double doubleValue))
{
property.doubleValue = doubleValue;
return true;
}
else
{
return false;
}
default:
if (float.TryParse(value, out float defaultValue))
{
property.floatValue = defaultValue;
return true;
}
else
{
return false;
}
}
}
public static bool TrySetIntValue(this SerializedProperty property, string value)
{
switch (property.type)
{
case UnityConstants.Type.Int:
if (int.TryParse(value, out int intValue))
{
property.intValue = intValue;
return true;
}
else
{
return false;
}
case UnityConstants.Type.Long:
if (long.TryParse(value, out long longValue))
{
property.longValue = longValue;
return true;
}
else
{
return false;
}
#if UNITY_2022_1_OR_NEWER
case UnityConstants.Type.UInt:
if (uint.TryParse(value, out uint uintValue))
{
property.uintValue = uintValue;
return true;
}
else
{
return false;
}
case UnityConstants.Type.ULong:
if (ulong.TryParse(value, out ulong ulongValue))
{
property.ulongValue = ulongValue;
return true;
}
else
{
return false;
}
#endif
default:
if (int.TryParse(value, out int defaultValue))
{
property.intValue = defaultValue;
return true;
}
else
{
return false;
}
}
}
public static Gradient GetGradientValue(this SerializedProperty property)
{
#if UNITY_2022_1_OR_NEWER
return property.gradientValue;
#else
var propertyInfo = property.GetGradientPropertyInfo();
return propertyInfo?.GetValue(property, null) as Gradient;
#endif
}
public static void SetGradientValue(this SerializedProperty property, Gradient newValue)
{
#if UNITY_2022_1_OR_NEWER
property.gradientValue = newValue;
#else
var propertyInfo = property.GetGradientPropertyInfo();
propertyInfo?.SetValue(property, newValue);
#endif
}
#if !UNITY_2022_1_OR_NEWER
private static System.Reflection.PropertyInfo GetGradientPropertyInfo(this SerializedProperty property)
{
var propertyName = "gradientValue";
var bindingFlags = System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance;
System.Reflection.PropertyInfo propertyInfo = typeof(SerializedProperty).GetProperty(propertyName, bindingFlags);
if (propertyInfo != null)
{
return propertyInfo;
}
else
{
Debug.LogWarning($"Unable to find property '{propertyName}' for {nameof(SerializedProperty)} at {property.propertyPath}.");
return null;
}
}
#endif
public static bool IsArraySizeOrElement(this SerializedProperty property)
{
return property.propertyType == SerializedPropertyType.ArraySize || property.IsArrayElement();
}
public static bool IsArrayElement(this SerializedProperty property)
{
return IsArrayElement(property.propertyPath);
}
public static bool IsArrayElement(string propertyPath)
{
return propertyPath.Contains('[');
}
public static bool IsAssetReference(this SerializedProperty property)
{
return property.type.Contains(UnityConstants.Type.AssetReference) && property.FindPropertyRelative(UnityConstants.Field.AssetRefGuid) != null;
}
public static bool IsInputFieldProperty(this SerializedProperty property, bool isScriptableObject)
{
var propertyType = property.GetSheetsPropertyType(isScriptableObject);
return s_InputFieldPropertyTypes.Contains(propertyType);
}
public static bool IsPropertyVisible(this SerializedProperty property, bool showArrays, bool showReadOnly, out bool isReadOnly)
{
var isArraySizeOrElement = property.IsArraySizeOrElement();
isReadOnly = property.IsReadOnly();
return (showArrays || !isArraySizeOrElement) && (showReadOnly || !isReadOnly) && !property.hasVisibleChildren
&& property.propertyType != SerializedPropertyType.Generic && property.propertyType != SerializedPropertyType.ManagedReference;
}
public static bool IsReadOnly(this SerializedProperty property)
{
return s_ReadOnlyUnityFields.Contains(property.name) || !property.editable;
}
public static bool IsTypeDouble(this SerializedProperty property)
{
return property.type == UnityConstants.Type.Double;
}
public static bool IsTypeFloat(this SerializedProperty property)
{
return property.type == UnityConstants.Type.Float;
}
public static bool IsTypeInt(this SerializedProperty property)
{
return property.type == UnityConstants.Type.Int;
}
public static bool IsTypeLong(this SerializedProperty property)
{
return property.type == UnityConstants.Type.Long;
}
public static bool IsTypeUInt(this SerializedProperty property)
{
return property.type == UnityConstants.Type.UInt;
}
public static bool IsTypeULong(this SerializedProperty property)
{
return property.type == UnityConstants.Type.ULong;
}
public static bool TryGetEnumType(this SerializedProperty property, Object rootObject, out Type enumType)
{
var enumPropertyPath = property.propertyPath;
enumType = ReflectionUtility.GetNestedFieldType(rootObject.GetType(), enumPropertyPath);
if (enumType != null && enumType.IsEnum)
{
return true;
}
else
{
Debug.LogWarning($"Property on {nameof(Object)} {rootObject.name} at path {enumPropertyPath} is not a valid type of enum.");
return false;
}
}
// Various default Unity Engine Components have different limitations on how backing fields are serialized and accessed.
private static bool IsDefaultUnityEngineType(Object rootObject)
{
return IsDefaultUnityEngineType(rootObject, out string fullTypeName);
}
private static bool IsDefaultUnityEngineType(Object rootObject, out string fullTypeName)
{
fullTypeName = rootObject.GetType().FullName;
return fullTypeName.StartsWith(UnityConstants.Type.UnityEngine);
}
// Handle special case for TMPro alignment enum properties.
private static bool IsAlignmentProperty(string propertyPath)
{
return propertyPath == UnityConstants.Field.HorizontalAlignment || propertyPath == UnityConstants.Field.VerticalAlignment || propertyPath == UnityConstants.Field.TextAlignment;
}
private static Type GetSharedTableCollectionType()
{
if (s_StringTableCollectionType == null)
{
s_StringTableCollectionType = Type.GetType($"{UnityConstants.Type.UnityLocalizationSharedTableData}, {UnityConstants.Type.UnityLocalization}");
}
return s_StringTableCollectionType;
}
// Get GUID from "GUID:xxxx" or return null.
private static string GetGuidFrom(string value)
{
if (string.IsNullOrEmpty(value))
{
return null;
}
if (!value.StartsWith(UnityConstants.Guid, StringComparison.OrdinalIgnoreCase))
{
return null;
}
return value.Substring(UnityConstants.Guid.Length);
}
private static void DrawLocalizationTablePicker(Rect rect, SerializedProperty property, AssetPreviewSettings previewSettings)
{
var sharedTableType = GetSharedTableCollectionType();
// If the localization package isn't installed then fallback to a simple text field.
if (sharedTableType == null)
{
property.stringValue = EditorGUI.TextField(rect, property.stringValue);
return;
}
Object currentValue = null;
var guid = GetGuidFrom(property.stringValue);
if (!string.IsNullOrEmpty(guid))
{
var path = AssetDatabase.GUIDToAssetPath(guid);
currentValue = AssetDatabase.LoadAssetAtPath(path, sharedTableType);
}
if (rect.height > EditorGUIUtility.singleLineHeight)
{
DrawUtility.TableAssetPreview(currentValue, rect, previewSettings);
rect.height = EditorGUIUtility.singleLineHeight;
}
var newValue = EditorGUI.ObjectField(rect, currentValue, sharedTableType, false);
if (currentValue != newValue)
{
if (newValue == null)
{
property.stringValue = string.Empty;
}
else
{
var path = AssetDatabase.GetAssetPath(newValue);
var newGuid = AssetDatabase.AssetPathToGUID(path);
property.stringValue = $"{UnityConstants.Guid}{newGuid}";
}
}
}
private static void DrawTableEntryKeyDropdown(Rect rect, SerializedProperty property)
{
var sharedTableType = GetSharedTableCollectionType();
// If the localization package isn't installed then fallback to a simple long field.
if (sharedTableType == null)
{
property.longValue = EditorGUI.LongField(rect, property.longValue);
return;
}
var tableRefPath = property.propertyPath.Replace(UnityConstants.Field.TableEntryReferenceKeyId, UnityConstants.Field.TableReferenceCollectionName);
var tableRefProp = property.serializedObject.FindProperty(tableRefPath);
var guid = GetGuidFrom(tableRefProp.stringValue);
if (string.IsNullOrEmpty(guid))
{
EditorGUI.LabelField(rect, "<No Table Selected>");
return;
}
var path = AssetDatabase.GUIDToAssetPath(guid);
var sharedTable = AssetDatabase.LoadAssetAtPath(path, sharedTableType);
if (sharedTable == null)
{
EditorGUI.LabelField(rect, "<Missing Table>");
return;
}
var sharedEntriesProp = sharedTableType.GetProperty(UnityConstants.Field.SharedTableDataEntries);
if (sharedEntriesProp == null)
{
EditorGUI.LabelField(rect, "<Entries Not Found>");
return;
}
var entries = sharedEntriesProp.GetValue(sharedTable) as IEnumerable<object>;
if (entries == null)
{
EditorGUI.LabelField(rect, "<No Entries>");
return;
}
var ids = new List<long>();
var labels = new List<string>();
var entryType = entries.GetType().GetGenericArguments()[0];
var idProp = entryType.GetProperty(UnityConstants.Field.SharedTableDataId);
var keyProp = entryType.GetProperty(UnityConstants.Field.SharedTableDataKey);
foreach (var entry in entries)
{
var id = (long) idProp.GetValue(entry);
ids.Add(id);
var key = (string) keyProp.GetValue(entry);
labels.Add(key);
}
if (ids.Count <= 0)
{
EditorGUI.LabelField(rect, "<Empty Table>");
return;
}
var currentIndex = ids.IndexOf(property.longValue);
var newIndex = EditorGUI.Popup(rect, currentIndex, labels.ToArray());
if (currentIndex != newIndex && newIndex >= 0 && newIndex < ids.Count)
{
property.longValue = ids[newIndex];
}
}
}
}