Skip to content

Instantly share code, notes, and snippets.

@oxysoft
Created September 13, 2020 18:57
Show Gist options
  • Star 7 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save oxysoft/dfe81ef124ffcf527a7f33670afdf9c2 to your computer and use it in GitHub Desktop.
Save oxysoft/dfe81ef124ffcf527a7f33670afdf9c2 to your computer and use it in GitHub Desktop.
using System;
using System.Collections.Generic;
using Sirenix.OdinInspector.Editor;
using Sirenix.OdinInspector.Editor.Validation;
using Sirenix.Utilities.Editor;
using UnityEngine;
[DrawerPriority(0.0, 10000.1)]
public class MiniValidationDrawer<T> : OdinValueDrawer<T>, IDisposable
{
private List<ValidationResult> _validationResults;
private bool _rerunFullValidation;
private object _shakeGroupKey;
private ValidationComponent _validationComponent;
private static readonly Color BG = new Color(1, 0, 0, 0.15f);
private static readonly Color Shadow = new Color(0, 0, 0, 0.3f);
private static readonly Color Band = new Color(1, 0, 0, 0.5f);
private static readonly Color WarnBG = new Color(0.79f, 0.52f, 0f, 0.15f);
private static readonly Color WarnShadow = new Color(0, 0, 0, .3f);
private static readonly Color WarnBand = new Color(1f, 0.62f, 0.22f, 0.5f);
protected override bool CanDrawValueProperty(InspectorProperty property)
{
ValidationComponent component = property.GetComponent<ValidationComponent>();
return component != null && component.ValidatorLocator.PotentiallyHasValidatorsFor(property);
}
protected override void Initialize()
{
_validationComponent = Property.GetComponent<ValidationComponent>();
_validationComponent.ValidateProperty(ref _validationResults);
if (_validationResults.Count > 0)
{
_shakeGroupKey = UniqueDrawerKey.Create(Property, this);
Property.Tree.OnUndoRedoPerformed += OnUndoRedoPerformed;
ValueEntry.OnValueChanged += OnValueChanged;
ValueEntry.OnChildValueChanged += OnChildValueChanged;
}
else
{
SkipWhenDrawing = true;
}
}
protected override void DrawPropertyLayout(GUIContent label)
{
if (_validationResults.Count == 0)
{
CallNextDrawer(label);
}
else
{
GUILayout.BeginVertical();
SirenixEditorGUI.BeginShakeableGroup(_shakeGroupKey);
ValidationResult error = null;
ValidationResult warning = null;
for (var i = 0; i < _validationResults.Count; ++i)
{
ValidationResult result = _validationResults[i];
if (Event.current.type == EventType.Layout && (_rerunFullValidation || result.Setup.Validator.RevalidationCriteria == RevalidationCriteria.Always))
{
ValidationResultType resultType = result.ResultType;
result.Setup.ParentInstance = Property.ParentValues[0];
result.Setup.Value = ValueEntry.Values[0];
result.RerunValidation();
if (resultType != result.ResultType && result.ResultType != ValidationResultType.Valid)
SirenixEditorGUI.StartShakingGroup(_shakeGroupKey);
}
switch (result.ResultType)
{
case ValidationResultType.Error:
error = result;
break;
case ValidationResultType.Warning:
warning = result;
break;
case ValidationResultType.Valid when !string.IsNullOrEmpty(result.Message):
SirenixEditorGUI.InfoMessageBox(result.Message);
break;
}
}
if (Event.current.type == EventType.Layout)
_rerunFullValidation = false;
// The hack materializes: we skip the next drawer which should be the default ValidationDrawer
Property.GetActiveDrawerChain().MoveNext();
CallNextDrawer(label);
if (error != null)
{
if (label != null)
label.tooltip = $"ERROR: {error.Message}";
Rect rect = GUIHelper.GetCurrentLayoutRect();
if (Event.current.type == EventType.Repaint)
{
SirenixEditorGUI.DrawSolidRect(rect, BG);
SirenixEditorGUI.DrawBorders(rect, 0, 0, 1, 0, Shadow);
SirenixEditorGUI.DrawBorders(rect, 3, 0, 0, 0, BG);
}
}
else if (warning != null)
{
label.tooltip = $"WARNING: {warning.Message}";
Rect rect = GUIHelper.GetCurrentLayoutRect();
if (Event.current.type == EventType.Repaint)
{
SirenixEditorGUI.DrawSolidRect(rect, WarnBG);
SirenixEditorGUI.DrawBorders(rect, 0, 0, 1, 0, WarnShadow);
SirenixEditorGUI.DrawBorders(rect, 3, 0, 0, 0, WarnBand);
}
}
SirenixEditorGUI.EndShakeableGroup(_shakeGroupKey);
GUILayout.EndVertical();
}
}
public void Dispose()
{
if (_validationResults.Count > 0)
{
Property.Tree.OnUndoRedoPerformed -= OnUndoRedoPerformed;
ValueEntry.OnValueChanged -= OnValueChanged;
ValueEntry.OnChildValueChanged -= OnChildValueChanged;
}
_validationResults = null;
}
private void OnUndoRedoPerformed()
{
_rerunFullValidation = true;
}
private void OnValueChanged(int index)
{
_rerunFullValidation = true;
}
private void OnChildValueChanged(int index)
{
_rerunFullValidation = true;
}
}
using UnityEngine;
@oxysoft
Copy link
Author

oxysoft commented Sep 13, 2020

MiniValidationDrawer

Prerequisite: Odin Inspector
Simply drop this script into your project's Editor folder or assembly.

The tooltip of labels (when mouse hovered) will be replaced with the error message. Also works with warnings.

Before
image

After
image

@PointedOne
Copy link

I'm not sure why but I had to duplicate line 97 to get rid of message box

@funnymanwin
Copy link

This doesn't work with new Odin version :(
Can you update your tool please!

@funnymanwin
Copy link

I updated your code. Compatible with new versions of Odin. Anyway u will not read this, because you abandoned this script long time ago. So I hope somebody will say thanks to me (i spent a lot of hours trying to fix it):

#if UNITY_EDITOR

using System;
using System.Collections.Generic;
using Sirenix.OdinInspector.Editor;
using Sirenix.OdinInspector.Editor.Validation;
using Sirenix.Utilities.Editor;
using UnityEditor;
using UnityEngine;

[DrawerPriority(0.0, 10000.1)]
public class MiniValidationDrawer<T> : OdinValueDrawer<T>, IDisposable
{
	private List<ValidationResult> _validationResults;
	private bool                   _rerunFullValidation;
	private object                 _shakeGroupKey;
	private ValidationComponent    _validationComponent;

	private static readonly Color BG     = new Color(1, 0, 0, 0.15f);
	private static readonly Color Shadow = new Color(0, 0, 0, 0.3f);
	private static readonly Color Band   = new Color(1, 0, 0, 0.5f);

	private static readonly Color WarnBG     = new Color(0.79f, 0.52f, 0f, 0.15f);
	private static readonly Color WarnShadow = new Color(0, 0, 0, .3f);
	private static readonly Color WarnBand   = new Color(1f, 0.62f, 0.22f, 0.5f);

	protected override bool CanDrawValueProperty(InspectorProperty property)
	{
		ValidationComponent component = property.GetComponent<ValidationComponent>();
		return component != null && component.ValidatorLocator.PotentiallyHasValidatorsFor(property);
	}

	protected override void Initialize()
	{
		_validationComponent = Property.GetComponent<ValidationComponent>();
		_validationComponent.ValidateProperty(ref _validationResults);
		if (_validationResults.Count > 0)
		{
			_shakeGroupKey                    =  UniqueDrawerKey.Create(Property, this);
			Property.Tree.OnUndoRedoPerformed += OnUndoRedoPerformed;
			ValueEntry.OnValueChanged         += OnValueChanged;
			ValueEntry.OnChildValueChanged    += OnChildValueChanged;
		}
		else
		{
			SkipWhenDrawing = true;
		}
	}

	protected override void DrawPropertyLayout(GUIContent label)
	{
		if (_validationResults.Count == 0)
		{
			CallNextDrawer(label);
		}
		else
		{
			GUILayout.BeginVertical();
			SirenixEditorGUI.BeginShakeableGroup(_shakeGroupKey);

			ValidationResult error   = null;
			ValidationResult warning = null;

			for (var i = 0; i < _validationResults.Count; ++i)
			{
				ValidationResult result = _validationResults[i];

				if (Event.current.type == EventType.Layout && (_rerunFullValidation || result.Setup.Validator != null && ((Validator) result.Setup.Validator).RevalidationCriteria == RevalidationCriteria.Always))
				{
					_rerunFullValidation = false;
					ValidationResultType resultType = result.ResultType;

					result.Setup.ParentInstance = Property.ParentValues[0];
					_validationComponent.ValidatorLocator.GetValidators(Property)[i].Initialize(Property);
					_validationComponent.ValidatorLocator.GetValidators(Property)[i].RunValidation(ref result);
					

					if (resultType != result.ResultType && result.ResultType != ValidationResultType.Valid)
						SirenixEditorGUI.StartShakingGroup(_shakeGroupKey);
				}

				switch (result.ResultType)
				{
					case ValidationResultType.Error:
						error = result;
						break;

					case ValidationResultType.Warning:
						warning = result;
						break;

					case ValidationResultType.Valid when !string.IsNullOrEmpty(result.Message):
						SirenixEditorGUI.InfoMessageBox(result.Message);
						break;
				}
			}

			if (Event.current.type == EventType.Layout)
				_rerunFullValidation = false;

			// The hack materializes: we skip the next drawer which should be the default ValidationDrawer
			Property.GetActiveDrawerChain().MoveNext();
			CallNextDrawer(label);

			if (error != null)
			{
				if (label != null)
					label.tooltip = $"ERROR: {error.Message}";

				Rect rect = GUIHelper.GetCurrentLayoutRect();
				if (Event.current.type == EventType.Repaint)
				{
					SirenixEditorGUI.DrawSolidRect(rect, BG);
					SirenixEditorGUI.DrawBorders(rect, 0, 0, 1, 0, Shadow);
					SirenixEditorGUI.DrawBorders(rect, 3, 0, 0, 0, BG);
				}
			}
			else if (warning != null)
			{
				label.tooltip = $"WARNING: {warning.Message}";

				Rect rect = GUIHelper.GetCurrentLayoutRect();
				if (Event.current.type == EventType.Repaint)
				{
					SirenixEditorGUI.DrawSolidRect(rect, WarnBG);
					SirenixEditorGUI.DrawBorders(rect, 0, 0, 1, 0, WarnShadow);
					SirenixEditorGUI.DrawBorders(rect, 3, 0, 0, 0, WarnBand);
				}
			}

			SirenixEditorGUI.EndShakeableGroup(_shakeGroupKey);
			GUILayout.EndVertical();
		}
	}

	public void Dispose()
	{
		if (_validationResults.Count > 0)
		{
			Property.Tree.OnUndoRedoPerformed -= OnUndoRedoPerformed;
			ValueEntry.OnValueChanged         -= OnValueChanged;
			ValueEntry.OnChildValueChanged    -= OnChildValueChanged;
		}

		_validationResults = null;
	}

	private void OnUndoRedoPerformed()
	{
		_rerunFullValidation = true;
	}

	private void OnValueChanged(int index)
	{
		_rerunFullValidation = true;
	}

	private void OnChildValueChanged(int index)
	{
		_rerunFullValidation = true;
	}
}

#endif

Or you can just download Unity Package file which includes AllRequiredValidator.cs also:
https://drive.google.com/file/d/1i1Kmzrd8fikuAZqnFYe4-jsAnwO_YnHO/view?usp=sharing
)image

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment