Heroes_of_Hiis/Assets/Oculus/Interaction/Editor/EditorBase.cs

533 lines
18 KiB
C#

/************************************************************************************
Copyright : Copyright (c) Facebook Technologies, LLC and its affiliates. All rights reserved.
Your use of this SDK or tool is subject to the Oculus SDK License Agreement, available at
https://developer.oculus.com/licenses/oculussdk/
Unless required by applicable law or agreed to in writing, the Utilities SDK distributed
under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
ANY KIND, either express or implied. See the License for the specific language governing
permissions and limitations under the License.
************************************************************************************/
using System;
using System.Linq;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Assertions;
using UnityEditor;
namespace Oculus.Interaction.Editor
{
/// <summary>
/// A utility class for building custom editors with less work required.
/// </summary>
public class EditorBase : UnityEditor.Editor
{
#region API
protected virtual void OnEnable() { }
protected virtual void OnDisable() { }
/// <summary>
/// You must put all of the editor specifications into OnInit
/// </summary>
protected virtual void OnInit() { }
/// <summary>
/// Call in OnInit with one or more property names to hide them from the inspector.
///
/// This is preferable to using [HideInInspector] because it still allows the property to
/// be viewed when using the Inspector debug mode.
/// </summary>
protected void Hide(params string[] properties)
{
Assert.IsTrue(properties.Length > 0, "Should always hide at least one property.");
if (!ValidateProperties(properties))
{
return;
}
_hiddenProperties.UnionWith(properties);
}
/// <summary>
/// Call in OnInit with one or more property names to defer drawing them until after all
/// non-deferred properties have been drawn. All deferred properties will be drawn in the order
/// they are passed in to calls to Defer.
/// </summary>
protected void Defer(params string[] properties)
{
Assert.IsTrue(properties.Length > 0, "Should always defer at least one property.");
if (!ValidateProperties(properties))
{
return;
}
foreach (var property in properties)
{
if (_deferredProperties.Contains(property))
{
continue;
}
_deferredProperties.Add(property);
_deferredActions.Add(() =>
{
DrawProperty(serializedObject.FindProperty(property));
});
}
}
/// <summary>
/// Call in OnInit with a single property name and a custom property drawer. Equivalent
/// to calling Draw and then Defer for the property.
/// </summary>
protected void Defer(string property, Action<SerializedProperty> customDrawer)
{
Draw(property, customDrawer);
Defer(property);
}
/// <summary>
/// Call in OnInit with a single delegate to have it be called after all other non-deferred
/// properties have been drawn.
/// </summary>
protected void Defer(Action deferredAction)
{
_deferredActions.Add(deferredAction);
}
/// <summary>
/// Call in OnInit to specify a custom drawer for a single property. Whenever the property is drawn,
/// it will use the provided property drawer instead of the default one.
/// </summary>
protected void Draw(string property, Action<SerializedProperty> drawer)
{
if (!ValidateProperties(property))
{
return;
}
_customDrawers.Add(property, drawer);
}
/// <summary>
/// Call in OnInit to specify a custom drawer for a single property. Include an extra property that gets
/// lumped in with the primary property. The extra property is not drawn normally, and is instead grouped in
/// with the primary property. Can be used in situations where a collection of properties need to be drawn together.
/// </summary>
protected void Draw(string property,
string withExtra0,
Action<SerializedProperty, SerializedProperty> drawer)
{
if (!ValidateProperties(property, withExtra0))
{
return;
}
Hide(withExtra0);
Draw(property, p =>
{
drawer(p,
serializedObject.FindProperty(withExtra0));
});
}
protected void Draw(string property,
string withExtra0,
string withExtra1,
Action<SerializedProperty, SerializedProperty, SerializedProperty> drawer)
{
if (!ValidateProperties(property, withExtra0, withExtra1))
{
return;
}
Hide(withExtra0);
Hide(withExtra1);
Draw(property, p =>
{
drawer(p,
serializedObject.FindProperty(withExtra0),
serializedObject.FindProperty(withExtra1));
});
}
protected void Draw(string property,
string withExtra0,
string withExtra1,
string withExtra2,
Action<SerializedProperty, SerializedProperty, SerializedProperty, SerializedProperty>
drawer)
{
if (!ValidateProperties(property, withExtra0, withExtra1, withExtra2))
{
return;
}
Hide(withExtra0);
Hide(withExtra1);
Hide(withExtra2);
Draw(property, p =>
{
drawer(p,
serializedObject.FindProperty(withExtra0),
serializedObject.FindProperty(withExtra1),
serializedObject.FindProperty(withExtra2));
});
}
protected void Draw(string property,
string withExtra0,
string withExtra1,
string withExtra2,
string withExtra3,
Action<SerializedProperty, SerializedProperty, SerializedProperty, SerializedProperty,
SerializedProperty> drawer)
{
if (!ValidateProperties(property, withExtra0, withExtra1, withExtra2, withExtra3))
{
return;
}
Hide(withExtra0);
Hide(withExtra1);
Hide(withExtra2);
Hide(withExtra3);
Draw(property, p =>
{
drawer(p,
serializedObject.FindProperty(withExtra0),
serializedObject.FindProperty(withExtra1),
serializedObject.FindProperty(withExtra2),
serializedObject.FindProperty(withExtra3));
});
}
protected void Conditional(string boolPropName, bool showIf, params string[] toHide)
{
if (!ValidateProperties(boolPropName) || !ValidateProperties(toHide))
{
return;
}
var boolProp = serializedObject.FindProperty(boolPropName);
if (boolProp.propertyType != SerializedPropertyType.Boolean)
{
Debug.LogError(
$"Must provide a Boolean property to this Conditional method, but the property {boolPropName} had a type of {boolProp.propertyType}");
return;
}
List<Func<bool>> conditions;
foreach (var prop in toHide)
{
if (!_propertyDrawConditions.TryGetValue(prop, out conditions))
{
conditions = new List<Func<bool>>();
_propertyDrawConditions[prop] = conditions;
}
conditions.Add(() =>
{
if (boolProp.hasMultipleDifferentValues)
{
return false;
}
else
{
return boolProp.boolValue == showIf;
}
});
}
}
protected void Conditional<T>(string enumPropName, T showIf, params string[] toHide)
where T : Enum
{
if (!ValidateProperties(enumPropName) || !ValidateProperties(toHide))
{
return;
}
var enumProp = serializedObject.FindProperty(enumPropName);
if (enumProp.propertyType != SerializedPropertyType.Enum)
{
Debug.LogError(
$"Must provide a Boolean property to this Conditional method, but the property {enumPropName} had a type of {enumProp.propertyType}");
return;
}
List<Func<bool>> conditions;
foreach (var prop in toHide)
{
if (!_propertyDrawConditions.TryGetValue(prop, out conditions))
{
conditions = new List<Func<bool>>();
_propertyDrawConditions[prop] = conditions;
}
conditions.Add(() =>
{
if (enumProp.hasMultipleDifferentValues)
{
return false;
}
else
{
return enumProp.intValue == showIf.GetHashCode();
}
});
}
}
/// <summary>
/// Call in OnInit to specify a custom decorator for a single property. Before a property is drawn,
/// all of the decorators will be drawn first.
/// </summary>
protected void Decorate(string property, Action<SerializedProperty> decorator)
{
if (!ValidateProperties(property))
{
return;
}
List<Action<SerializedProperty>> decorators;
if (!_customDecorators.TryGetValue(property, out decorators))
{
decorators = new List<Action<SerializedProperty>>();
_customDecorators[property] = decorators;
}
decorators.Add(decorator);
}
/// <summary>
/// Call in OnInit to specify a custom grouping behaviour for a range of properties. Specify the first
/// and last property (inclusive) and the action to take BEFORE the first property is drawn, and the action
/// to take AFTER the last property is drawn.
/// </summary>
protected void Group(string firstProperty, string lastProperty, Action beginGroup,
Action endGroup)
{
if (!ValidateProperties(firstProperty) || !ValidateProperties(lastProperty))
{
return;
}
_groupBegins.Add(firstProperty, beginGroup);
_groupEnds.Add(lastProperty, endGroup);
}
/// <summary>
/// A utility version of the more generic Group method.
/// Call in OnInit to specify a range of properties that should be grouped within a styled vertical
/// layout group.
/// </summary>
protected void Group(string firstProperty, string lastProperty, GUIStyle style)
{
if (style == null)
{
Debug.LogError(
"Cannot provide a null style to EditorBase.Group. If you are acquiring a " +
"Style from the EditorStyles class, try calling Group from with on OnInit instead " +
"of from within OnEnable.");
return;
}
Group(firstProperty,
lastProperty,
() => EditorGUILayout.BeginVertical(style),
() => EditorGUILayout.EndVertical());
}
/// <summary>
/// Groups the given properties into a foldout with a given name.
/// </summary>
protected void Foldout(string firstProperty, string lastProperty, string foldoutName,
bool showByDefault = false)
{
Group(firstProperty,
lastProperty,
() =>
{
bool shouldShow;
if (!_foldouts.TryGetValue(foldoutName, out shouldShow))
{
shouldShow = showByDefault;
}
shouldShow = EditorGUILayout.Foldout(shouldShow, foldoutName);
_foldouts[foldoutName] = shouldShow;
EditorGUI.indentLevel++;
_currentStates.Push(shouldShow);
},
() =>
{
EditorGUI.indentLevel--;
_currentStates.Pop();
});
}
protected virtual void OnBeforeInspector() { }
protected virtual void OnAfterInspector(bool anyPropertiesModified) { }
#endregion
#region IMPLEMENTATION
[NonSerialized]
private bool _hasInitBeenCalled = false;
private HashSet<string> _hiddenProperties = new HashSet<string>();
private HashSet<string> _deferredProperties = new HashSet<string>();
private List<Action> _deferredActions = new List<Action>();
private Dictionary<string, bool> _foldouts = new Dictionary<string, bool>();
private Stack<bool> _currentStates = new Stack<bool>();
private Dictionary<string, Action<SerializedProperty>> _customDrawers =
new Dictionary<string, Action<SerializedProperty>>();
private Dictionary<string, List<Action<SerializedProperty>>> _customDecorators =
new Dictionary<string, List<Action<SerializedProperty>>>();
private Dictionary<string, Action> _groupBegins = new Dictionary<string, Action>();
private Dictionary<string, Action> _groupEnds = new Dictionary<string, Action>();
private Dictionary<string, List<Func<bool>>> _propertyDrawConditions =
new Dictionary<string, List<Func<bool>>>();
public override void OnInspectorGUI()
{
if (!_hasInitBeenCalled)
{
OnInit();
_hasInitBeenCalled = true;
}
SerializedProperty it = serializedObject.GetIterator();
it.NextVisible(enterChildren: true);
//Draw script header
EditorGUI.BeginDisabledGroup(true);
EditorGUILayout.PropertyField(it);
EditorGUI.EndDisabledGroup();
OnBeforeInspector();
EditorGUI.BeginChangeCheck();
while (it.NextVisible(enterChildren: false))
{
//Don't draw deferred properties in this pass, we will draw them after everything else
if (_deferredProperties.Contains(it.name))
{
continue;
}
DrawProperty(it);
}
foreach (var deferredAction in _deferredActions)
{
deferredAction();
}
bool anyModified = EditorGUI.EndChangeCheck();
OnAfterInspector(anyModified);
serializedObject.ApplyModifiedProperties();
}
private void DrawProperty(SerializedProperty property)
{
Action groupBeginAction;
if (_groupBegins.TryGetValue(property.name, out groupBeginAction))
{
groupBeginAction();
}
try
{
//Don't draw if we are in a property that is currently hidden by a foldout
if (_currentStates.Any(s => s == false))
{
return;
}
//Don't draw hidden properties
if (_hiddenProperties.Contains(property.name))
{
return;
}
List<Func<bool>> conditions;
if (_propertyDrawConditions.TryGetValue(property.name, out conditions))
{
foreach (var condition in conditions)
{
if (!condition())
{
return;
}
}
}
//First draw all decorators for the property
List<Action<SerializedProperty>> decorators;
if (_customDecorators.TryGetValue(property.name, out decorators))
{
foreach (var decorator in decorators)
{
decorator(property);
}
}
//Then draw the property itself, using a custom drawer if needed
Action<SerializedProperty> customDrawer;
if (_customDrawers.TryGetValue(property.name, out customDrawer))
{
customDrawer(property);
}
else
{
EditorGUILayout.PropertyField(property, includeChildren: true);
}
}
finally
{
Action groupEndAction;
if (_groupEnds.TryGetValue(property.name, out groupEndAction))
{
groupEndAction();
}
}
}
private bool ValidateProperties(params string[] properties)
{
foreach (var property in properties)
{
if (serializedObject.FindProperty(property) == null)
{
Debug.LogWarning(
$"Could not find property {property}, maybe it was deleted or renamed?");
return false;
}
}
return true;
}
#endregion
}
}