Add property validators

This commit is contained in:
VladV 2022-01-15 19:25:12 +03:00
parent ff35842fe1
commit a1da85433e
19 changed files with 425 additions and 2 deletions

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: e710031376614be5903f943dc4c4d07f
timeCreated: 1642261774

View File

@ -0,0 +1,25 @@
using TriInspector;
using TriInspector.Validators;
using UnityEditor;
[assembly: RegisterTriValueValidator(typeof(MissingReferenceValidator<>))]
namespace TriInspector.Validators
{
public class MissingReferenceValidator<T> : TriValueValidator<T>
where T : UnityEngine.Object
{
public override TriValidationResult Validate(TriValue<T> propertyValue)
{
if (propertyValue.Property.TryGetSerializedProperty(out var serializedProperty) &&
serializedProperty.propertyType == SerializedPropertyType.ObjectReference &&
serializedProperty.objectReferenceValue == null &&
serializedProperty.objectReferenceInstanceIDValue != 0)
{
return TriValidationResult.Warning($"{propertyValue.Property.DisplayName} is missing");
}
return TriValidationResult.Valid;
}
}
}

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: e6349ecb04a34792bd193b53f6cc0ca3
timeCreated: 1642263604

View File

@ -0,0 +1,38 @@
using TriInspector.Validators;
using TriInspector;
[assembly: RegisterTriAttributeValidator(typeof(RequiredValidator), ApplyOnArrayElement = true)]
namespace TriInspector.Validators
{
public class RequiredValidator : TriAttributeValidator<RequiredAttribute>
{
public override TriValidationResult Validate(TriProperty property)
{
if (property.FieldType == typeof(string))
{
var isNull = string.IsNullOrEmpty((string) property.Value);
if (isNull)
{
var message = Attribute.Message ?? $"{property.DisplayName} is required";
return TriValidationResult.Error(message);
}
}
else if (typeof(UnityEngine.Object).IsAssignableFrom(property.FieldType))
{
var isNull = null == (UnityEngine.Object) property.Value;
if (isNull)
{
var message = Attribute.Message ?? $"{property.DisplayName} is required";
return TriValidationResult.Error(message);
}
}
else
{
return TriValidationResult.Error("RequiredAttribute only valid on Object and String");
}
return TriValidationResult.Valid;
}
}
}

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 58b713b3023343c3b571cd1b3fa7fdab
timeCreated: 1642261781

View File

@ -62,4 +62,28 @@ namespace TriInspector
public Type ProcessorType { get; }
}
[AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)]
public class RegisterTriValueValidatorAttribute : Attribute
{
public RegisterTriValueValidatorAttribute(Type validatorType)
{
ValidatorType = validatorType;
}
public Type ValidatorType { get; }
public bool ApplyOnArrayElement { get; set; } = true;
}
[AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)]
public class RegisterTriAttributeValidatorAttribute : Attribute
{
public RegisterTriAttributeValidatorAttribute(Type validatorType)
{
ValidatorType = validatorType;
}
public Type ValidatorType { get; }
public bool ApplyOnArrayElement { get; set; }
}
}

View File

@ -26,6 +26,11 @@ namespace TriInspector.Elements
element = drawers[index].CreateElementInternal(property, element);
}
if (property.HasValidators)
{
AddChild(new TriPropertyValidationResultElement(property));
}
AddChild(element);
}

View File

@ -0,0 +1,43 @@
using System.Collections.Generic;
namespace TriInspector.Elements
{
public class TriPropertyValidationResultElement : TriElement
{
private readonly TriProperty _property;
private IReadOnlyList<TriValidationResult> _validationResults;
public TriPropertyValidationResultElement(TriProperty property)
{
_property = property;
}
public override bool Update()
{
var dirty = base.Update();
dirty |= GenerateValidationResults();
return dirty;
}
private bool GenerateValidationResults()
{
if (_property.ValidationResults == _validationResults)
{
return false;
}
_validationResults = _property.ValidationResults;
RemoveAllChildren();
foreach (var result in _validationResults)
{
AddChild(new TriInfoBoxElement(result.Message, result.MessageType));
}
return true;
}
}
}

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 2e1128573c5f481189ccbc210cbf6b18
timeCreated: 1642262467

View File

@ -52,6 +52,21 @@ namespace TriInspector
{
Profiler.EndSample();
}
Profiler.BeginSample("TriInspector.RunValidation()");
try
{
if (_inspector.ValidationRequired)
{
_inspector.ValidationRequired = false;
_inspector.RunValidation();
}
}
finally
{
Profiler.EndSample();
}
EditorStack.Push(this);
Profiler.BeginSample("TriInspector.DoLayout()");
@ -65,7 +80,10 @@ namespace TriInspector
EditorStack.Pop();
}
serializedObject.ApplyModifiedProperties();
if (serializedObject.ApplyModifiedProperties())
{
_inspector.RequestValidation();
}
if (_inspector.RepaintRequired)
{

View File

@ -1,6 +1,7 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using JetBrains.Annotations;
using TriInspector.Utilities;
using UnityEditor;
@ -10,11 +11,15 @@ namespace TriInspector
{
public sealed class TriProperty : ITriPropertyParent
{
private static readonly IReadOnlyList<TriValidationResult> EmptyValidationResults =
new List<TriValidationResult>();
private readonly TriPropertyDefinition _definition;
private readonly int _propertyIndex;
private readonly ITriPropertyParent _parent;
[CanBeNull] private readonly SerializedProperty _serializedProperty;
private List<TriProperty> _childrenProperties;
private List<TriValidationResult> _validationResults;
private GUIContent _displayNameBackingField;
@ -36,6 +41,9 @@ namespace TriInspector
Update();
}
[PublicAPI]
public string DisplayName => DisplayNameContent.text;
[PublicAPI]
public GUIContent DisplayNameContent
{
@ -137,6 +145,11 @@ namespace TriInspector
public ITriPropertyParent Parent => _parent;
public bool HasValidators => _definition.Validators.Count != 0;
public IReadOnlyList<TriValidationResult> ValidationResults =>
_validationResults ?? EmptyValidationResults;
[PublicAPI]
public bool IsExpanded
{
@ -200,7 +213,10 @@ namespace TriInspector
public void SetValue(object value)
{
// save any pending changes
PropertyTree.SerializedObject.ApplyModifiedProperties();
if (PropertyTree.SerializedObject.ApplyModifiedProperties())
{
PropertyTree.RequestValidation();
}
// record object state for undp
Undo.RegisterCompleteObjectUndo(PropertyTree.TargetObjects, "Inspector");
@ -215,6 +231,8 @@ namespace TriInspector
// actualize
PropertyTree.SerializedObject.Update();
Update();
PropertyTree.RequestValidation();
}
internal void Update()
@ -301,6 +319,25 @@ namespace TriInspector
}
}
internal void RunValidation()
{
if (HasValidators)
{
_validationResults = _definition.Validators
.Select(it => it.Validate(this))
.Where(it => it.MessageType != MessageType.None)
.ToList();
}
if (_childrenProperties != null)
{
foreach (var childrenProperty in _childrenProperties)
{
childrenProperty.RunValidation();
}
}
}
[PublicAPI]
public bool TryGetSerializedProperty(out SerializedProperty serializedProperty)
{

View File

@ -17,6 +17,7 @@ namespace TriInspector
private TriPropertyDefinition _arrayElementDefinitionBackingField;
private IReadOnlyList<TriCustomDrawer> _drawersBackingField;
private IReadOnlyList<TriValidator> _validatorsBackingField;
private IReadOnlyList<TriPropertyHideProcessor> _hideProcessorsBackingField;
private IReadOnlyList<TriPropertyDisableProcessor> _disableProcessorsBackingField;
@ -117,6 +118,22 @@ namespace TriInspector
}
}
public IReadOnlyList<TriValidator> Validators
{
get
{
if (_validatorsBackingField == null)
{
_validatorsBackingField = Enumerable.Empty<TriValidator>()
.Concat(TriDrawersUtilities.CreateValueValidatorsFor(FieldType))
.Concat(TriDrawersUtilities.CreateAttributeValidatorsFor(Attributes))
.ToList();
}
return _validatorsBackingField;
}
}
public object GetValue(TriProperty property, int targetIndex)
{
var parentValue = property.Parent.GetValue(targetIndex);

View File

@ -30,6 +30,8 @@ namespace TriInspector
})
.ToList();
ValidationRequired = true;
_mode = mode;
_inspectorElement = new TriInspectorElement(this);
_inspectorElement.AttachInternal();
@ -52,6 +54,7 @@ namespace TriInspector
public bool IsInlineEditor => (_mode & TriEditorMode.InlineEditor) != 0;
internal bool RepaintRequired { get; set; }
internal bool ValidationRequired { get; set; }
object ITriPropertyParent.GetValue(int targetIndex) => TargetObjects[targetIndex];
@ -81,6 +84,14 @@ namespace TriInspector
_inspectorElement.Update();
}
internal void RunValidation()
{
foreach (var property in Properties)
{
property.RunValidation();
}
}
internal void DoLayout()
{
var width = EditorGUIUtility.currentViewWidth;
@ -93,6 +104,11 @@ namespace TriInspector
{
RepaintRequired = true;
}
public void RequestValidation()
{
ValidationRequired = true;
}
}
[Flags]

65
Editor/TriValidator.cs Normal file
View File

@ -0,0 +1,65 @@
using System;
using JetBrains.Annotations;
using UnityEditor;
namespace TriInspector
{
public abstract class TriValidator
{
internal bool ApplyOnArrayElement { get; set; }
[PublicAPI]
public abstract TriValidationResult Validate(TriProperty property);
}
public abstract class TriAttributeValidator : TriValidator
{
internal Attribute RawAttribute { get; set; }
}
public abstract class TriAttributeValidator<TAttribute> : TriAttributeValidator
where TAttribute : Attribute
{
[PublicAPI]
public TAttribute Attribute => (TAttribute) RawAttribute;
}
public abstract class TriValueValidator : TriValidator
{
}
public abstract class TriValueValidator<T> : TriValueValidator
{
public sealed override TriValidationResult Validate(TriProperty property)
{
return Validate(new TriValue<T>(property));
}
[PublicAPI]
public abstract TriValidationResult Validate(TriValue<T> propertyValue);
}
public readonly struct TriValidationResult
{
public static TriValidationResult Valid => new TriValidationResult(null, MessageType.None);
private TriValidationResult(string message, MessageType messageType)
{
Message = message;
MessageType = messageType;
}
public string Message { get; }
public MessageType MessageType { get; }
public static TriValidationResult Error(string error)
{
return new TriValidationResult(error, MessageType.Error);
}
public static TriValidationResult Warning(string error)
{
return new TriValidationResult(error, MessageType.Warning);
}
}
}

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: b90ba6bdef614e9aa246f371506ea113
timeCreated: 1642260754

View File

@ -12,6 +12,8 @@ namespace TriInspector.Utilities
private static IDictionary<Type, TriGroupDrawer> _allGroupDrawersCacheBackingField;
private static IReadOnlyList<RegisterTriAttributeDrawerAttribute> _allAttributeDrawerTypesBackingField;
private static IReadOnlyList<RegisterTriValueDrawerAttribute> _allValueDrawerTypesBackingField;
private static IReadOnlyList<RegisterTriAttributeValidatorAttribute> _allAttributeValidatorTypesBackingField;
private static IReadOnlyList<RegisterTriValueValidatorAttribute> _allValueValidatorTypesBackingField;
private static IReadOnlyList<RegisterTriPropertyHideProcessor> _allHideProcessorTypesBackingField;
private static IReadOnlyList<RegisterTriPropertyDisableProcessor> _allDisableProcessorTypesBackingField;
@ -72,6 +74,42 @@ namespace TriInspector.Utilities
}
}
public static IReadOnlyList<RegisterTriValueValidatorAttribute> AllValueValidatorTypes
{
get
{
if (_allValueValidatorTypesBackingField == null)
{
_allValueValidatorTypesBackingField = (
from asm in TriReflectionUtilities.Assemblies
from attr in asm.GetCustomAttributes<RegisterTriValueValidatorAttribute>()
where IsValueValidatorType(attr.ValidatorType, out _)
select attr
).ToList();
}
return _allValueValidatorTypesBackingField;
}
}
public static IReadOnlyList<RegisterTriAttributeValidatorAttribute> AllAttributeValidatorTypes
{
get
{
if (_allAttributeValidatorTypesBackingField == null)
{
_allAttributeValidatorTypesBackingField = (
from asm in TriReflectionUtilities.Assemblies
from attr in asm.GetCustomAttributes<RegisterTriAttributeValidatorAttribute>()
where IsAttributeValidatorType(attr.ValidatorType, out _)
select attr
).ToList();
}
return _allAttributeValidatorTypesBackingField;
}
}
public static IReadOnlyList<RegisterTriPropertyHideProcessor> AllHideProcessors
{
get
@ -133,6 +171,16 @@ namespace TriInspector.Utilities
return TryGetBaseGenericTargetType(type, typeof(TriAttributeDrawer<>), out attributeType);
}
private static bool IsValueValidatorType(Type type, out Type valueType)
{
return TryGetBaseGenericTargetType(type, typeof(TriValueValidator<>), out valueType);
}
private static bool IsAttributeValidatorType(Type type, out Type attributeType)
{
return TryGetBaseGenericTargetType(type, typeof(TriAttributeValidator<>), out attributeType);
}
private static bool IsHideProcessorType(Type type, out Type attributeType)
{
return TryGetBaseGenericTargetType(type, typeof(TriPropertyHideProcessor<>), out attributeType);
@ -171,6 +219,31 @@ namespace TriInspector.Utilities
});
}
public static IEnumerable<TriValueValidator> CreateValueValidatorsFor(Type valueType)
{
return
from validator in AllValueValidatorTypes
where IsValueValidatorType(validator.ValidatorType, out var vt) &&
IsValidTargetType(vt, valueType)
select CreateInstance<TriValueValidator>(validator.ValidatorType, valueType,
it => { it.ApplyOnArrayElement = validator.ApplyOnArrayElement; });
}
public static IEnumerable<TriAttributeValidator> CreateAttributeValidatorsFor(
IReadOnlyList<Attribute> attributes)
{
return
from attribute in attributes
from validator in AllAttributeValidatorTypes
where IsAttributeValidatorType(validator.ValidatorType, out var vt) &&
IsValidTargetType(vt, attribute.GetType())
select CreateInstance<TriAttributeValidator>(validator.ValidatorType, attribute.GetType(), it =>
{
it.ApplyOnArrayElement = validator.ApplyOnArrayElement;
it.RawAttribute = attribute;
});
}
public static IEnumerable<TriPropertyHideProcessor> CreateHideProcessorsFor(IReadOnlyList<Attribute> attributes)
{
return

View File

@ -17,6 +17,9 @@ public class BasicSample : TriMonoBehaviour
[PropertySpace(SpaceBefore = 10, SpaceAfter = 20)]
[PropertyTooltip("My Tooltip")]
public float unityField;
[Required]
public Material mat;
[InlineEditor]
public SampleScriptableObject objectReference;
@ -133,6 +136,37 @@ public class TriBoxGroupDrawer : TriGroupDrawer<DeclareBoxGroupAttribute>
}
```
Custom Value Validator
```csharp
using TriInspector;
[assembly: RegisterTriValueValidator(typeof(MissingReferenceValidator<>))]
public class MissingReferenceValidator<T> : TriValueValidator<T>
where T : UnityEngine.Object
{
public override TriValidationResult Validate(TriValue<T> propertyValue)
{
// ...
}
}
```
Custom Attribute Validator
```csharp
using TriInspector;
[assembly: RegisterTriAttributeValidator(typeof(RequiredValidator), ApplyOnArrayElement = true)]
public class RequiredValidator : TriAttributeValidator<RequiredAttribute>
{
public override TriValidationResult Validate(TriProperty property)
{
// ...
}
}
```
Property Hide Processor
```csharp
using TriInspector;

View File

@ -0,0 +1,10 @@
using System;
namespace TriInspector
{
[AttributeUsage((AttributeTargets.Field | AttributeTargets.Property))]
public class RequiredAttribute : Attribute
{
public string Message { get; set; }
}
}

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: d886b45ac8b1474db5cdfd07c859e616
timeCreated: 1642261718