Tri-Inspector/Editor/TriProperty.cs

750 lines
23 KiB
C#
Raw Permalink Normal View History

2021-12-07 10:20:36 -05:00
using System;
using System.Collections;
using System.Collections.Generic;
2022-01-15 11:25:12 -05:00
using System.Linq;
2022-01-18 12:41:11 -05:00
using System.Reflection;
2023-04-06 06:33:19 -04:00
using System.Text;
2021-12-07 10:20:36 -05:00
using JetBrains.Annotations;
using TriInspector.Utilities;
using UnityEditor;
using UnityEngine;
namespace TriInspector
{
2022-06-01 03:35:33 -04:00
public sealed class TriProperty
2021-12-07 10:20:36 -05:00
{
2023-04-06 06:33:19 -04:00
private static readonly StringBuilder SharedPropertyPathStringBuilder = new StringBuilder();
2022-01-15 11:25:12 -05:00
private static readonly IReadOnlyList<TriValidationResult> EmptyValidationResults =
new List<TriValidationResult>();
2021-12-07 10:20:36 -05:00
private readonly TriPropertyDefinition _definition;
2022-01-06 10:43:09 -05:00
private readonly int _propertyIndex;
2022-06-01 03:35:33 -04:00
[CanBeNull] private readonly SerializedObject _serializedObject;
2021-12-07 10:20:36 -05:00
[CanBeNull] private readonly SerializedProperty _serializedProperty;
private List<TriProperty> _childrenProperties;
2022-01-15 11:25:12 -05:00
private List<TriValidationResult> _validationResults;
2021-12-07 10:20:36 -05:00
private GUIContent _displayNameBackingField;
2023-04-06 06:33:19 -04:00
private string _propertyPath;
2022-01-20 05:12:52 -05:00
private string _isExpandedPrefsKey;
2022-05-29 07:41:40 -04:00
private int _lastUpdateFrame;
private bool _isUpdating;
[CanBeNull] private object _value;
[CanBeNull] private Type _valueType;
private bool _isValueMixed;
2022-06-02 03:18:13 -04:00
public event Action<TriProperty> ValueChanged;
public event Action<TriProperty> ChildValueChanged;
2022-06-01 03:35:33 -04:00
internal TriProperty(
TriPropertyTree propertyTree,
TriProperty parent,
TriPropertyDefinition definition,
SerializedObject serializedObject
)
{
Parent = parent;
_definition = definition;
_propertyIndex = -1;
_serializedProperty = null;
_serializedObject = serializedObject;
PropertyTree = propertyTree;
PropertyType = GetPropertyType(this);
}
2021-12-07 10:20:36 -05:00
internal TriProperty(
TriPropertyTree propertyTree,
2022-06-01 03:35:33 -04:00
TriProperty parent,
2021-12-07 10:20:36 -05:00
TriPropertyDefinition definition,
2022-01-06 10:43:09 -05:00
int propertyIndex,
2021-12-07 10:20:36 -05:00
[CanBeNull] SerializedProperty serializedProperty)
{
2022-06-01 03:35:33 -04:00
Parent = parent;
2021-12-07 10:20:36 -05:00
_definition = definition;
2022-01-06 10:43:09 -05:00
_propertyIndex = propertyIndex;
2021-12-07 10:20:36 -05:00
_serializedProperty = serializedProperty?.Copy();
2022-06-01 03:35:33 -04:00
_serializedObject = _serializedProperty?.serializedObject;
2021-12-07 10:20:36 -05:00
PropertyTree = propertyTree;
PropertyType = GetPropertyType(this);
}
2022-11-13 06:18:29 -05:00
internal TriPropertyDefinition Definition => _definition;
2022-05-29 07:41:40 -04:00
[PublicAPI]
public TriPropertyType PropertyType { get; }
[PublicAPI]
public TriPropertyTree PropertyTree { get; }
2022-06-01 03:35:33 -04:00
[PublicAPI]
public TriProperty Parent { get; }
[PublicAPI]
public TriProperty Owner => IsArrayElement ? Parent.Owner : Parent;
2022-06-01 03:35:33 -04:00
[PublicAPI]
public bool IsRootProperty => Parent == null;
2022-01-21 12:12:46 -05:00
[PublicAPI]
public string RawName => _definition.Name;
2022-01-15 11:25:12 -05:00
[PublicAPI]
public string DisplayName => DisplayNameContent.text;
2022-07-12 06:09:21 -04:00
public IEqualityComparer Comparer => TriEqualityComparer.Of(ValueType);
2021-12-07 10:20:36 -05:00
[PublicAPI]
public GUIContent DisplayNameContent
{
get
{
2022-05-30 06:15:09 -04:00
if (TriPropertyOverrideContext.Current != null &&
TriPropertyOverrideContext.Current.TryGetDisplayName(this, out var overrideName))
{
2022-05-30 06:15:09 -04:00
return overrideName;
}
2021-12-07 10:20:36 -05:00
if (_displayNameBackingField == null)
{
2022-05-29 10:39:48 -04:00
if (TryGetAttribute(out HideLabelAttribute _) || IsArrayElement)
2022-01-06 10:43:09 -05:00
{
_displayNameBackingField = new GUIContent("");
2022-01-06 10:43:09 -05:00
}
else
{
_displayNameBackingField = new GUIContent(ObjectNames.NicifyVariableName(_definition.Name));
}
2021-12-07 10:20:36 -05:00
}
2022-11-11 12:17:12 -05:00
if (IsArrayElement)
{
if (TriUnityInspectorUtilities.TryGetSpecialArrayElementName(this, out var specialName))
{
_displayNameBackingField.text = specialName;
}
else
{
_displayNameBackingField.text = TriUnityInspectorUtilities.GetStandardArrayElementName(this);
}
2022-11-11 12:17:12 -05:00
}
else
2021-12-07 10:20:36 -05:00
{
if (_definition.CustomLabel != null)
2021-12-07 10:20:36 -05:00
{
_displayNameBackingField.text = _definition.CustomLabel.GetValue(this, "");
2021-12-07 10:20:36 -05:00
}
if (_definition.CustomTooltip != null)
2021-12-07 10:20:36 -05:00
{
_displayNameBackingField.tooltip = _definition.CustomTooltip.GetValue(this, "");
2021-12-07 10:20:36 -05:00
}
}
return _displayNameBackingField;
}
}
2023-04-06 06:33:19 -04:00
[PublicAPI]
public string PropertyPath
{
get
{
if (_propertyPath == null)
{
SharedPropertyPathStringBuilder.Clear();
BuildPropertyPath(this, SharedPropertyPathStringBuilder);
_propertyPath = SharedPropertyPathStringBuilder.ToString();
}
return _propertyPath;
}
}
2021-12-07 10:20:36 -05:00
[PublicAPI]
public bool IsVisible
{
get
{
foreach (var processor in _definition.HideProcessors)
{
if (processor.IsHidden(this))
{
return false;
}
}
return true;
}
}
2021-12-07 10:20:36 -05:00
[PublicAPI]
public bool IsEnabled
{
get
{
if (_definition.IsReadOnly)
2021-12-07 10:20:36 -05:00
{
return false;
}
2022-01-06 10:43:09 -05:00
foreach (var processor in _definition.DisableProcessors)
{
if (processor.IsDisabled(this))
{
return false;
}
}
2021-12-07 10:20:36 -05:00
return true;
}
}
[PublicAPI]
public Type FieldType => _definition.FieldType;
[PublicAPI]
public Type ArrayElementType => _definition.ArrayElementType;
[PublicAPI]
public bool IsArrayElement => _definition.IsArrayElement;
2022-01-08 12:25:50 -05:00
[PublicAPI]
public bool IsArray => _definition.IsArray;
2022-01-06 10:43:09 -05:00
public int IndexInArray => IsArrayElement
? _propertyIndex
: throw new InvalidOperationException("Cannot read IndexInArray for !IsArrayElement");
2021-12-07 10:20:36 -05:00
public IReadOnlyList<TriCustomDrawer> AllDrawers => _definition.Drawers;
internal IReadOnlyList<string> ExtensionErrors => _definition.ExtensionErrors;
2022-01-15 11:25:12 -05:00
public bool HasValidators => _definition.Validators.Count != 0;
public IReadOnlyList<TriValidationResult> ValidationResults =>
_validationResults ?? EmptyValidationResults;
2021-12-07 10:20:36 -05:00
[PublicAPI]
public bool IsExpanded
{
get
{
if (_serializedProperty != null)
{
return _serializedProperty.isExpanded;
}
2022-01-21 23:36:15 -05:00
if (_isExpandedPrefsKey == null)
{
_isExpandedPrefsKey = $"TriInspector.expanded.{PropertyPath}";
2022-01-21 23:36:15 -05:00
}
2022-01-20 05:12:52 -05:00
return SessionState.GetBool(_isExpandedPrefsKey, false);
2021-12-07 10:20:36 -05:00
}
set
{
if (IsExpanded == value)
{
return;
}
if (_serializedProperty != null)
{
_serializedProperty.isExpanded = value;
}
2022-01-20 05:12:52 -05:00
else if (_isExpandedPrefsKey != null)
{
SessionState.SetBool(_isExpandedPrefsKey, value);
2022-01-20 05:12:52 -05:00
}
2021-12-07 10:20:36 -05:00
}
}
[PublicAPI]
[CanBeNull]
2022-05-29 07:41:40 -04:00
public Type ValueType
{
get
{
if (PropertyType != TriPropertyType.Reference)
{
return _definition.FieldType;
}
2022-05-29 07:41:40 -04:00
UpdateIfRequired();
return _valueType;
}
}
2021-12-07 10:20:36 -05:00
2022-05-29 07:41:40 -04:00
public bool IsValueMixed
{
get
{
if (PropertyTree.TargetsCount == 1)
{
return false;
}
2022-05-29 07:41:40 -04:00
UpdateIfRequired();
return _isValueMixed;
}
}
2022-01-08 10:36:08 -05:00
2021-12-07 10:20:36 -05:00
[PublicAPI]
2022-05-29 07:41:40 -04:00
[CanBeNull]
public object Value
{
get
{
UpdateIfRequired();
return _value;
}
}
2021-12-07 10:20:36 -05:00
[PublicAPI]
2022-05-29 07:41:40 -04:00
public IReadOnlyList<TriProperty> ChildrenProperties
{
get
{
if (_childrenProperties != null && PropertyType == TriPropertyType.Generic)
{
return _childrenProperties;
}
2022-05-29 07:41:40 -04:00
UpdateIfRequired();
2021-12-07 10:20:36 -05:00
2022-05-29 07:41:40 -04:00
return PropertyType == TriPropertyType.Generic || PropertyType == TriPropertyType.Reference
? _childrenProperties
: throw new InvalidOperationException("Cannot read ChildrenProperties for " + PropertyType);
}
}
2021-12-07 10:20:36 -05:00
[PublicAPI]
2022-05-29 07:41:40 -04:00
public IReadOnlyList<TriProperty> ArrayElementProperties
{
get
{
UpdateIfRequired();
return PropertyType == TriPropertyType.Array
? _childrenProperties
: throw new InvalidOperationException("Cannot read ArrayElementProperties for " + PropertyType);
}
}
2021-12-07 10:20:36 -05:00
2022-08-17 13:46:26 -04:00
[PublicAPI]
public bool TryGetMemberInfo(out MemberInfo memberInfo)
{
return _definition.TryGetMemberInfo(out memberInfo);
}
2022-01-20 05:05:35 -05:00
public object GetValue(int targetIndex)
2021-12-07 10:20:36 -05:00
{
2022-01-06 10:43:09 -05:00
return _definition.GetValue(this, targetIndex);
2021-12-07 10:20:36 -05:00
}
[PublicAPI]
public void SetValue(object value)
2022-05-09 03:29:33 -04:00
{
ModifyAndRecordForUndo(targetIndex => SetValueRecursive(this, value, targetIndex));
}
[PublicAPI]
public void SetValues(Func<int, object> getValue)
{
ModifyAndRecordForUndo(targetIndex =>
{
var value = getValue.Invoke(targetIndex);
SetValueRecursive(this, value, targetIndex);
});
}
2022-05-09 03:29:33 -04:00
public void ModifyAndRecordForUndo(Action<int> call)
2021-12-07 10:20:36 -05:00
{
2022-06-02 03:18:13 -04:00
PropertyTree.ApplyChanges();
2022-01-07 10:36:38 -05:00
2022-05-18 02:20:51 -04:00
PropertyTree.ForceCreateUndoGroup();
2021-12-07 10:20:36 -05:00
2022-05-17 06:33:23 -04:00
for (var targetIndex = 0; targetIndex < PropertyTree.TargetsCount; targetIndex++)
2021-12-07 10:20:36 -05:00
{
2022-05-09 03:29:33 -04:00
call.Invoke(targetIndex);
2021-12-07 10:20:36 -05:00
}
2022-06-02 03:18:13 -04:00
PropertyTree.Update(forceUpdate: true);
2022-05-11 04:54:19 -04:00
NotifyValueChanged();
2022-06-02 03:18:13 -04:00
PropertyTree.RequestValidation();
PropertyTree.RequestRepaint();
2022-05-11 04:54:19 -04:00
}
public void NotifyValueChanged()
{
2022-06-01 03:35:33 -04:00
NotifyValueChanged(this);
2022-05-11 04:54:19 -04:00
}
2022-06-01 03:35:33 -04:00
private void NotifyValueChanged(TriProperty property)
2022-05-11 04:54:19 -04:00
{
2022-06-02 03:18:13 -04:00
if (property == this)
{
ValueChanged?.Invoke(property);
}
else
{
ChildValueChanged?.Invoke(property);
}
2022-06-01 03:35:33 -04:00
Parent?.NotifyValueChanged(property);
2021-12-07 10:20:36 -05:00
}
2022-05-29 07:41:40 -04:00
private void UpdateIfRequired(bool forceUpdate = false)
2021-12-07 10:20:36 -05:00
{
2022-05-29 07:41:40 -04:00
if (_isUpdating)
{
throw new InvalidOperationException("Recursive call detected");
}
2022-01-20 05:05:35 -05:00
2022-05-29 07:41:40 -04:00
if (_lastUpdateFrame == PropertyTree.RepaintFrame && !forceUpdate)
{
return;
}
2021-12-07 10:20:36 -05:00
2022-05-29 07:41:40 -04:00
_isUpdating = true;
2021-12-07 10:20:36 -05:00
2022-05-29 07:41:40 -04:00
try
2021-12-07 10:20:36 -05:00
{
2022-05-29 07:41:40 -04:00
_lastUpdateFrame = PropertyTree.RepaintFrame;
2022-01-21 23:36:15 -05:00
2022-05-29 07:41:40 -04:00
ReadValue(this, out var newValue, out var newValueIsMixed);
2021-12-07 10:20:36 -05:00
2022-05-29 07:41:40 -04:00
var newValueType = FieldType.IsValueType ? FieldType
: ReferenceEquals(_value, newValue) ? _valueType
: newValue?.GetType();
var valueTypeChanged = _valueType != newValueType;
_value = newValue;
_valueType = newValueType;
_isValueMixed = newValueIsMixed;
switch (PropertyType)
{
case TriPropertyType.Generic:
case TriPropertyType.Reference:
if (_childrenProperties == null || valueTypeChanged)
2021-12-07 10:20:36 -05:00
{
2022-05-29 07:41:40 -04:00
if (_childrenProperties == null)
2021-12-07 10:20:36 -05:00
{
2022-05-29 07:41:40 -04:00
_childrenProperties = new List<TriProperty>();
}
_childrenProperties.Clear();
2021-12-07 10:20:36 -05:00
2022-05-29 07:41:40 -04:00
var selfType = PropertyType == TriPropertyType.Reference ? _valueType : FieldType;
if (selfType != null)
{
var properties = TriTypeDefinition.GetCached(selfType).Properties;
for (var index = 0; index < properties.Count; index++)
{
var childDefinition = properties[index];
2022-06-01 03:35:33 -04:00
var childSerializedProperty = _serializedProperty != null
? _serializedProperty.FindPropertyRelative(childDefinition.Name)
: _serializedObject?.FindProperty(childDefinition.Name);
2022-05-29 07:41:40 -04:00
var childProperty = new TriProperty(PropertyTree, this,
childDefinition, index, childSerializedProperty);
_childrenProperties.Add(childProperty);
}
2021-12-07 10:20:36 -05:00
}
}
2022-05-29 07:41:40 -04:00
break;
2021-12-07 10:20:36 -05:00
2022-05-29 07:41:40 -04:00
case TriPropertyType.Array:
if (_childrenProperties == null)
{
_childrenProperties = new List<TriProperty>();
}
2021-12-07 10:20:36 -05:00
2022-05-29 07:41:40 -04:00
var listSize = ((IList) newValue)?.Count ?? 0;
2021-12-07 10:20:36 -05:00
2022-05-29 07:41:40 -04:00
while (_childrenProperties.Count < listSize)
{
var index = _childrenProperties.Count;
var elementDefinition = _definition.ArrayElementDefinition;
var elementSerializedReference = _serializedProperty?.GetArrayElementAtIndex(index);
2021-12-07 10:20:36 -05:00
2022-05-29 07:41:40 -04:00
var elementProperty = new TriProperty(PropertyTree, this,
elementDefinition, index, elementSerializedReference);
2021-12-07 10:20:36 -05:00
2022-05-29 07:41:40 -04:00
_childrenProperties.Add(elementProperty);
}
2021-12-07 10:20:36 -05:00
2022-05-29 07:41:40 -04:00
while (_childrenProperties.Count > listSize)
{
_childrenProperties.RemoveAt(_childrenProperties.Count - 1);
}
2021-12-07 10:20:36 -05:00
2022-05-29 07:41:40 -04:00
break;
}
2021-12-07 10:20:36 -05:00
}
2022-05-29 07:41:40 -04:00
finally
{
2022-05-29 07:41:40 -04:00
_isUpdating = false;
}
2021-12-07 10:20:36 -05:00
}
2022-01-15 11:25:12 -05:00
internal void RunValidation()
{
2022-05-29 07:41:40 -04:00
UpdateIfRequired();
2022-01-15 11:25:12 -05:00
if (HasValidators)
{
_validationResults = _definition.Validators
.Select(it => it.Validate(this))
.Where(it => !it.IsValid)
2022-01-15 11:25:12 -05:00
.ToList();
}
2022-01-15 11:25:12 -05:00
if (_childrenProperties != null)
{
foreach (var childrenProperty in _childrenProperties)
{
childrenProperty.RunValidation();
}
}
}
internal void EnumerateValidationResults(Action<TriProperty, TriValidationResult> call)
{
2022-05-29 07:41:40 -04:00
UpdateIfRequired();
if (_validationResults != null)
{
foreach (var result in _validationResults)
{
call.Invoke(this, result);
}
}
if (_childrenProperties != null)
{
foreach (var childrenProperty in _childrenProperties)
{
childrenProperty.EnumerateValidationResults(call);
}
}
}
2021-12-07 10:20:36 -05:00
[PublicAPI]
public bool TryGetSerializedProperty(out SerializedProperty serializedProperty)
{
serializedProperty = _serializedProperty;
return serializedProperty != null;
}
[PublicAPI]
public bool TryGetAttribute<TAttribute>(out TAttribute attribute)
where TAttribute : Attribute
{
if (ValueType != null)
{
foreach (var attr in TriReflectionUtilities.GetAttributesCached(ValueType))
{
if (attr is TAttribute typedAttr)
{
attribute = typedAttr;
return true;
}
}
}
foreach (var attr in _definition.Attributes)
{
if (attr is TAttribute typedAttr)
{
attribute = typedAttr;
return true;
}
}
attribute = null;
return false;
}
2023-04-06 06:33:19 -04:00
internal static void BuildPropertyPath(TriProperty property, StringBuilder sb)
{
if (property.IsRootProperty)
{
return;
}
if (property.Parent != null && !property.Parent.IsRootProperty)
{
BuildPropertyPath(property.Parent, sb);
sb.Append('.');
}
if (property.IsArrayElement)
{
sb.Append("Array.data[").Append(property.IndexInArray).Append(']');
}
else
{
sb.Append(property.RawName);
}
}
2022-01-07 10:36:38 -05:00
private static void SetValueRecursive(TriProperty property, object value, int targetIndex)
{
// for value types we must recursively set all parent objects
// because we cannot directly modify structs
// but we can re-set entire parent value
while (property._definition.SetValue(property, value, targetIndex, out var parentValue) &&
property.Parent != null)
2022-01-07 10:36:38 -05:00
{
2022-06-01 03:35:33 -04:00
property = property.Parent;
2022-01-07 10:36:38 -05:00
value = parentValue;
}
}
2022-01-20 05:05:35 -05:00
private static void ReadValue(TriProperty property, out object newValue, out bool isMixed)
2022-01-10 03:10:07 -05:00
{
2022-01-20 05:05:35 -05:00
newValue = property.GetValue(0);
2022-05-17 06:33:23 -04:00
if (property.PropertyTree.TargetsCount == 1)
2022-01-10 03:10:07 -05:00
{
2022-01-20 05:05:35 -05:00
isMixed = false;
return;
2022-01-10 03:10:07 -05:00
}
switch (property.PropertyType)
{
case TriPropertyType.Array:
{
2022-01-20 05:05:35 -05:00
var list = (IList) newValue;
2022-05-17 06:33:23 -04:00
for (var i = 1; i < property.PropertyTree.TargetsCount; i++)
2022-01-20 05:05:35 -05:00
{
if (list == null)
{
break;
}
var otherList = (IList) property.GetValue(i);
if (otherList == null || otherList.Count < list.Count)
{
newValue = list = otherList;
}
}
isMixed = true;
return;
2022-01-10 03:10:07 -05:00
}
case TriPropertyType.Reference:
{
2022-05-17 06:33:23 -04:00
for (var i = 1; i < property.PropertyTree.TargetsCount; i++)
2022-01-10 03:10:07 -05:00
{
2022-01-20 05:05:35 -05:00
var otherValue = property.GetValue(i);
2022-01-10 03:10:07 -05:00
2022-01-20 05:05:35 -05:00
if (newValue?.GetType() != otherValue?.GetType())
2022-01-10 03:10:07 -05:00
{
2022-01-20 05:05:35 -05:00
isMixed = true;
newValue = null;
return;
2022-01-10 03:10:07 -05:00
}
}
2022-01-20 05:05:35 -05:00
isMixed = false;
return;
2022-01-10 03:10:07 -05:00
}
case TriPropertyType.Generic:
{
2022-01-20 05:05:35 -05:00
isMixed = false;
return;
2022-01-10 03:10:07 -05:00
}
case TriPropertyType.Primitive:
{
2022-05-17 06:33:23 -04:00
for (var i = 1; i < property.PropertyTree.TargetsCount; i++)
2022-01-10 03:10:07 -05:00
{
2022-01-20 05:05:35 -05:00
var otherValue = property.GetValue(i);
2022-07-12 06:09:21 -04:00
if (!property.Comparer.Equals(otherValue, newValue))
2022-01-10 03:10:07 -05:00
{
2022-01-20 05:05:35 -05:00
isMixed = true;
return;
2022-01-10 03:10:07 -05:00
}
}
2022-01-20 05:05:35 -05:00
isMixed = false;
return;
2022-01-10 03:10:07 -05:00
}
default:
{
2022-01-20 05:05:35 -05:00
Debug.LogError($"Unexpected property type: {property.PropertyType}");
isMixed = true;
return;
2022-01-10 03:10:07 -05:00
}
}
}
2021-12-07 10:20:36 -05:00
private static TriPropertyType GetPropertyType(TriProperty property)
{
if (property._serializedProperty != null)
{
if (property._serializedProperty.isArray &&
property._serializedProperty.propertyType != SerializedPropertyType.String)
{
return TriPropertyType.Array;
}
if (property._serializedProperty.propertyType == SerializedPropertyType.ManagedReference)
{
return TriPropertyType.Reference;
}
if (property._serializedProperty.propertyType == SerializedPropertyType.Generic)
{
return TriPropertyType.Generic;
}
return TriPropertyType.Primitive;
}
2022-06-01 03:35:33 -04:00
if (property._serializedObject != null)
{
return TriPropertyType.Generic;
}
2021-12-07 10:20:36 -05:00
if (property._definition.FieldType.IsPrimitive ||
property._definition.FieldType == typeof(string) ||
typeof(UnityEngine.Object).IsAssignableFrom(property._definition.FieldType))
2021-12-07 10:20:36 -05:00
{
return TriPropertyType.Primitive;
}
if (property._definition.FieldType.IsValueType)
{
return TriPropertyType.Generic;
}
if (property._definition.IsArray)
{
return TriPropertyType.Array;
}
return TriPropertyType.Reference;
}
}
public enum TriPropertyType
{
Array,
Reference,
Generic,
Primitive,
}
}