using System; using System.Collections; using System.Collections.Generic; using System.Linq; using System.Reflection; using JetBrains.Annotations; using TriInspector.Resolvers; using TriInspector.Utilities; using UnityEngine; namespace TriInspector { public class TriPropertyDefinition { private readonly ValueGetterDelegate _valueGetter; [CanBeNull] private readonly ValueSetterDelegate _valueSetter; private TriPropertyDefinition _arrayElementDefinitionBackingField; private IReadOnlyList _drawersBackingField; private IReadOnlyList _validatorsBackingField; private IReadOnlyList _hideProcessorsBackingField; private IReadOnlyList _disableProcessorsBackingField; internal TriPropertyDefinition(int order, FieldInfo fi) : this(fi, order, fi.Name, fi.FieldType, MakeGetter(fi), MakeSetter(fi), false) { } internal TriPropertyDefinition(int order, PropertyInfo pi) : this(pi, order, pi.Name, pi.PropertyType, MakeGetter(pi), MakeSetter(pi), false) { } internal TriPropertyDefinition(int order, MethodInfo mi) : this(mi, order, mi.Name, typeof(MethodInfo), MakeGetter(mi), MakeSetter(mi), false) { } internal TriPropertyDefinition( MemberInfo memberInfo, int order, string fieldName, Type fieldType, ValueGetterDelegate valueGetter, ValueSetterDelegate valueSetter, bool isArrayElement) { MemberInfo = memberInfo; Name = fieldName; FieldType = fieldType; IsArrayElement = isArrayElement; _valueGetter = valueGetter; _valueSetter = valueSetter; Attributes = memberInfo?.GetCustomAttributes().ToList() ?? new List(); Order = Attributes.TryGet(out PropertyOrderAttribute orderAttribute) ? orderAttribute.Order : order; IsReadOnly = _valueSetter == null || Attributes.TryGet(out ReadOnlyAttribute _); if (TriReflectionUtilities.IsArrayOrList(FieldType, out var elementType)) { IsArray = true; ArrayElementType = elementType; } if (Attributes.TryGet(out OnValueChangedAttribute onValueChangedAttribute)) { OnValueChanged = ActionResolver.Resolve(this, onValueChangedAttribute.Method); } if (Attributes.TryGet(out LabelTextAttribute labelTextAttribute)) { CustomLabel = ValueResolver.ResolveString(this, labelTextAttribute.Text); } if (Attributes.TryGet(out PropertyTooltipAttribute tooltipAttribute)) { CustomTooltip = ValueResolver.ResolveString(this, tooltipAttribute.Tooltip); } } public MemberInfo MemberInfo { get; } public Type FieldType { get; } public string Name { get; } public int Order { get; } public IReadOnlyList Attributes { get; } public bool IsReadOnly { get; } public bool IsArrayElement { get; } public Type ArrayElementType { get; } public bool IsArray { get; } [CanBeNull] public ActionResolver OnValueChanged { get; } [CanBeNull] public ValueResolver CustomLabel { get; } [CanBeNull] public ValueResolver CustomTooltip { get; } public IReadOnlyList HideProcessors { get { if (_hideProcessorsBackingField == null) { _hideProcessorsBackingField = TriDrawersUtilities.CreateHideProcessorsFor(Attributes) .Where(it => CanApplyOn(this, applyOnArrayElement: false)) .ToList(); foreach (var processor in _hideProcessorsBackingField) { processor.Initialize(this); } } return _hideProcessorsBackingField; } } public IReadOnlyList DisableProcessors { get { if (_disableProcessorsBackingField == null) { _disableProcessorsBackingField = TriDrawersUtilities.CreateDisableProcessorsFor(Attributes) .Where(it => CanApplyOn(this, applyOnArrayElement: false)) .ToList(); foreach (var processor in _disableProcessorsBackingField) { processor.Initialize(this); } } return _disableProcessorsBackingField; } } public IReadOnlyList Drawers { get { if (_drawersBackingField == null) { _drawersBackingField = Enumerable.Empty() .Concat(TriDrawersUtilities.CreateValueDrawersFor(FieldType)) .Concat(TriDrawersUtilities.CreateAttributeDrawersFor(Attributes)) .Concat(new[] { new ValidatorsDrawer {Order = TriDrawerOrder.Validator,}, }) .Where(it => CanApplyOn(this, it.ApplyOnArrayElement)) .OrderBy(it => it.Order) .ToList(); foreach (var drawer in _drawersBackingField) { drawer.Initialize(this); } } return _drawersBackingField; } } public IReadOnlyList Validators { get { if (_validatorsBackingField == null) { _validatorsBackingField = Enumerable.Empty() .Concat(TriDrawersUtilities.CreateValueValidatorsFor(FieldType)) .Concat(TriDrawersUtilities.CreateAttributeValidatorsFor(Attributes)) .Where(it => CanApplyOn(this, it.ApplyOnArrayElement)) .ToList(); foreach (var validator in _validatorsBackingField) { validator.Initialize(this); } } return _validatorsBackingField; } } public object GetValue(TriProperty property, int targetIndex) { return _valueGetter(property, targetIndex); } public bool SetValue(TriProperty property, object value, int targetIndex, out object parentValue) { if (IsReadOnly) { Debug.LogError("Cannot set value for readonly property"); parentValue = default; return false; } parentValue = _valueSetter?.Invoke(property, targetIndex, value); return true; } public TriPropertyDefinition ArrayElementDefinition { get { if (_arrayElementDefinitionBackingField == null) { if (!IsArray) { throw new InvalidOperationException( $"Cannot get array element definition for non array property: {FieldType}"); } var elementMember = MemberInfo; var elementGetter = new ValueGetterDelegate((self, targetIndex) => { var parentValue = (IList) self.Parent.GetValue(targetIndex); return parentValue[self.IndexInArray]; }); var elementSetter = new ValueSetterDelegate((self, targetIndex, value) => { var parentValue = (IList) self.Parent.GetValue(targetIndex); parentValue[self.IndexInArray] = value; return parentValue; }); _arrayElementDefinitionBackingField = new TriPropertyDefinition(elementMember, 0, "Element", ArrayElementType, elementGetter, elementSetter, true); } return _arrayElementDefinitionBackingField; } } private static ValueGetterDelegate MakeGetter(FieldInfo fi) { return (self, targetIndex) => { var parentValue = self.Parent.GetValue(targetIndex); return fi.GetValue(parentValue); }; } private static ValueSetterDelegate MakeSetter(FieldInfo fi) { return (self, targetIndex, value) => { var parentValue = self.Parent.GetValue(targetIndex); fi.SetValue(parentValue, value); return parentValue; }; } private static ValueGetterDelegate MakeGetter(PropertyInfo pi) { var method = pi.GetMethod; return (self, targetIndex) => { var parentValue = self.Parent.GetValue(targetIndex); return method.Invoke(parentValue, null); }; } private static ValueSetterDelegate MakeSetter(PropertyInfo pi) { var method = pi.SetMethod; if (method == null) { return null; } return (self, targetIndex, value) => { var parentValue = self.Parent.GetValue(targetIndex); method.Invoke(parentValue, new[] {value,}); return parentValue; }; } private static ValueGetterDelegate MakeGetter(MethodInfo mi) { return (self, targetIndex) => mi; } private static ValueSetterDelegate MakeSetter(MethodInfo mi) { return (self, targetIndex, value) => { var parentValue = self.Parent.GetValue(targetIndex); return parentValue; }; } private static bool CanApplyOn(TriPropertyDefinition definition, bool? applyOnArrayElement) { if (!applyOnArrayElement.HasValue) { return true; } if (definition.IsArrayElement && !applyOnArrayElement.Value || definition.IsArray && applyOnArrayElement.Value) { return false; } return true; } public delegate object ValueGetterDelegate(TriProperty self, int targetIndex); public delegate object ValueSetterDelegate(TriProperty self, int targetIndex, object value); } }