Skip to content

Instantly share code, notes, and snippets.

@oxysoft
Created September 13, 2020 18:57
Show Gist options
  • 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

@tylerdrewwork
Copy link

tylerdrewwork commented Jul 5, 2024

Thank you @funnymanwin! Extremely helpful. I have also updated it to include tooltips of the error when hovering over the property:

#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);

    private GUIStyle _tooltipStyle;

    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;
        }

        _tooltipStyle = new GUIStyle(GUI.skin.label)
        {
            normal = { textColor = Color.white },
            active = { textColor = Color.white },
            hover = { textColor = Color.white },
            onNormal = { textColor = Color.white },
            onActive = { textColor = Color.white },
            onHover = { textColor = Color.white },
        };
    }

    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);

            Rect rect = GUIHelper.GetCurrentLayoutRect();
            
            if (error != null)
            {
                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);
                }
                
                EditorGUI.LabelField(rect, new GUIContent(" ", $"ERROR: {error.Message}"), _tooltipStyle);
            }
            else if (warning != null)
            {
                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);
                }
                
                EditorGUI.LabelField(rect, new GUIContent(" ", $"WARNING: {warning.Message}"), _tooltipStyle);
            }

            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

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