You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

380 lines
14 KiB

2 years ago
/******************************************************************************
* Spine Runtimes Software License v2.5
*
* Copyright (c) 2013-2016, Esoteric Software
* All rights reserved.
*
* You are granted a perpetual, non-exclusive, non-sublicensable, and
* non-transferable license to use, install, execute, and perform the Spine
* Runtimes software and derivative works solely for personal or internal
* use. Without the written permission of Esoteric Software (see Section 2 of
* the Spine Software License Agreement), you may not (a) modify, translate,
* adapt, or develop new applications using the Spine Runtimes or otherwise
* create derivative works or improvements of the Spine Runtimes or (b) remove,
* delete, alter, or obscure any trademarks or any copyright, trademark, patent,
* or other intellectual property or proprietary rights notices on or in the
* Software, including any copy thereof. Redistributions in binary or source
* form must include this license and terms.
*
* THIS SOFTWARE IS PROVIDED BY ESOTERIC SOFTWARE "AS IS" AND ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
* EVENT SHALL ESOTERIC SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, BUSINESS INTERRUPTION, OR LOSS OF
* USE, DATA, OR PROFITS) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
* IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*****************************************************************************/
using UnityEngine;
using UnityEditor;
using System.Collections.Generic;
using System.Reflection;
namespace Spine.Unity.Editor {
public static class SpineInspectorUtility {
public static string Pluralize (int n, string singular, string plural) {
return n + " " + (n == 1 ? singular : plural);
}
public static string PluralThenS (int n) {
return n == 1 ? "" : "s";
}
public static string EmDash {
get { return "\u2014"; }
}
static GUIContent tempContent;
internal static GUIContent TempContent (string text, Texture2D image = null, string tooltip = null) {
if (tempContent == null) tempContent = new GUIContent();
tempContent.text = text;
tempContent.image = image;
tempContent.tooltip = tooltip;
return tempContent;
}
public static void PropertyFieldWideLabel (SerializedProperty property, GUIContent label = null, float minimumLabelWidth = 150) {
EditorGUIUtility.labelWidth = minimumLabelWidth;
EditorGUILayout.PropertyField(property, label ?? TempContent(property.displayName, null, property.tooltip));
EditorGUIUtility.labelWidth = 0; // Resets to default
}
public static void PropertyFieldFitLabel (SerializedProperty property, GUIContent label = null, float extraSpace = 5f) {
label = label ?? TempContent(property.displayName, null, property.tooltip);
float width = GUI.skin.label.CalcSize(TempContent(label.text)).x + extraSpace;
if (label.image != null)
width += EditorGUIUtility.singleLineHeight;
PropertyFieldWideLabel(property, label, width);
}
/// <summary>Multi-edit-compatible version of EditorGUILayout.ToggleLeft(SerializedProperty)</summary>
public static void ToggleLeftLayout (SerializedProperty property, GUIContent label = null, float width = 120f) {
if (label == null) label = SpineInspectorUtility.TempContent(property.displayName, tooltip: property.tooltip);
if (property.hasMultipleDifferentValues) {
bool previousShowMixedValue = EditorGUI.showMixedValue;
EditorGUI.showMixedValue = true;
bool clicked = EditorGUILayout.ToggleLeft(label, property.boolValue, GUILayout.Width(width));
if (clicked) property.boolValue = true; // Set all values to true when clicked.
EditorGUI.showMixedValue = previousShowMixedValue;
} else {
property.boolValue = EditorGUILayout.ToggleLeft(label, property.boolValue, GUILayout.Width(width));
}
}
/// <summary>Multi-edit-compatible version of EditorGUILayout.ToggleLeft(SerializedProperty)</summary>
public static void ToggleLeft (Rect rect, SerializedProperty property, GUIContent label = null) {
if (label == null) label = SpineInspectorUtility.TempContent(property.displayName, tooltip: property.tooltip);
if (property.hasMultipleDifferentValues) {
bool previousShowMixedValue = EditorGUI.showMixedValue;
EditorGUI.showMixedValue = true;
bool clicked = EditorGUI.ToggleLeft(rect, label, property.boolValue);
if (clicked) property.boolValue = true; // Set all values to true when clicked.
EditorGUI.showMixedValue = previousShowMixedValue;
} else {
property.boolValue = EditorGUI.ToggleLeft(rect, label, property.boolValue);
}
}
public static bool UndoRedoPerformed (UnityEngine.Event current) {
return current.type == EventType.ValidateCommand && current.commandName == "UndoRedoPerformed";
}
public static Texture2D UnityIcon<T>() {
return EditorGUIUtility.ObjectContent(null, typeof(T)).image as Texture2D;
}
public static Texture2D UnityIcon(System.Type type) {
return EditorGUIUtility.ObjectContent(null, type).image as Texture2D;
}
#region SerializedProperty Helpers
public static SerializedProperty FindBaseOrSiblingProperty (this SerializedProperty property, string propertyName) {
if (string.IsNullOrEmpty(propertyName)) return null;
SerializedProperty relativeProperty = property.serializedObject.FindProperty(propertyName); // baseProperty
// If base property is not found, look for the sibling property.
if (relativeProperty == null) {
string propertyPath = property.propertyPath;
int localPathLength = property.name.Length;
string newPropertyPath = propertyPath.Remove(propertyPath.Length - localPathLength, localPathLength) + propertyName;
relativeProperty = property.serializedObject.FindProperty(newPropertyPath);
// If a direct sibling property was not found, try to find the sibling of the array.
if (relativeProperty == null && property.isArray) {
int propertyPathLength = propertyPath.Length;
int dotCount = 0;
const int SiblingOfListDotCount = 3;
for (int i = 1; i < propertyPathLength; i++) {
if (propertyPath[propertyPathLength - i] == '.') {
dotCount++;
if (dotCount >= SiblingOfListDotCount) {
localPathLength = i - 1;
break;
}
}
}
newPropertyPath = propertyPath.Remove(propertyPath.Length - localPathLength, localPathLength) + propertyName;
relativeProperty = property.serializedObject.FindProperty(newPropertyPath);
}
}
return relativeProperty;
}
#endregion
#region Layout Scopes
static GUIStyle grayMiniLabel;
public static GUIStyle GrayMiniLabel {
get {
if (grayMiniLabel == null) {
grayMiniLabel = new GUIStyle(EditorStyles.centeredGreyMiniLabel);
grayMiniLabel.alignment = TextAnchor.UpperLeft;
}
return grayMiniLabel;
}
}
public class LabelWidthScope : System.IDisposable {
public LabelWidthScope (float minimumLabelWidth = 190f) {
EditorGUIUtility.labelWidth = minimumLabelWidth;
}
public void Dispose () {
EditorGUIUtility.labelWidth = 0f;
}
}
public class IndentScope : System.IDisposable {
public IndentScope () { EditorGUI.indentLevel++; }
public void Dispose () { EditorGUI.indentLevel--; }
}
public class BoxScope : System.IDisposable {
readonly bool indent;
static GUIStyle boxScopeStyle;
public static GUIStyle BoxScopeStyle {
get {
if (boxScopeStyle == null) {
boxScopeStyle = new GUIStyle(EditorStyles.helpBox);
RectOffset p = boxScopeStyle.padding; // RectOffset is a class
p.right += 6;
p.top += 1;
p.left += 3;
}
return boxScopeStyle;
}
}
public BoxScope (bool indent = true) {
this.indent = indent;
EditorGUILayout.BeginVertical(BoxScopeStyle);
if (indent) EditorGUI.indentLevel++;
}
public void Dispose () {
if (indent) EditorGUI.indentLevel--;
EditorGUILayout.EndVertical();
}
}
#endregion
#region Button
const float CenterButtonMaxWidth = 270f;
const float CenterButtonHeight = 35f;
static GUIStyle spineButtonStyle;
static GUIStyle SpineButtonStyle {
get {
if (spineButtonStyle == null) {
spineButtonStyle = new GUIStyle(GUI.skin.button);
spineButtonStyle.name = "Spine Button";
spineButtonStyle.padding = new RectOffset(10, 10, 10, 10);
}
return spineButtonStyle;
}
}
public static bool LargeCenteredButton (string label, bool sideSpace = true, float maxWidth = CenterButtonMaxWidth) {
if (sideSpace) {
bool clicked;
using (new EditorGUILayout.HorizontalScope()) {
EditorGUILayout.Space();
clicked = GUILayout.Button(label, SpineButtonStyle, GUILayout.MaxWidth(maxWidth), GUILayout.Height(CenterButtonHeight));
EditorGUILayout.Space();
}
EditorGUILayout.Space();
return clicked;
} else {
return GUILayout.Button(label, GUILayout.MaxWidth(CenterButtonMaxWidth), GUILayout.Height(CenterButtonHeight));
}
}
public static bool LargeCenteredButton (GUIContent content, bool sideSpace = true, float maxWidth = CenterButtonMaxWidth) {
if (sideSpace) {
bool clicked;
using (new EditorGUILayout.HorizontalScope()) {
EditorGUILayout.Space();
clicked = GUILayout.Button(content, SpineButtonStyle, GUILayout.MaxWidth(maxWidth), GUILayout.Height(CenterButtonHeight));
EditorGUILayout.Space();
}
EditorGUILayout.Space();
return clicked;
} else {
return GUILayout.Button(content, GUILayout.MaxWidth(CenterButtonMaxWidth), GUILayout.Height(CenterButtonHeight));
}
}
public static bool CenteredButton (GUIContent content, float height = 20f, bool sideSpace = true, float maxWidth = CenterButtonMaxWidth) {
if (sideSpace) {
bool clicked;
using (new EditorGUILayout.HorizontalScope()) {
EditorGUILayout.Space();
clicked = GUILayout.Button(content, GUILayout.MaxWidth(maxWidth), GUILayout.Height(height));
EditorGUILayout.Space();
}
EditorGUILayout.Space();
return clicked;
} else {
return GUILayout.Button(content, GUILayout.MaxWidth(maxWidth), GUILayout.Height(height));
}
}
#endregion
#region Multi-Editing Helpers
public static bool TargetsUseSameData (SerializedObject so) {
if (so.isEditingMultipleObjects) {
int n = so.targetObjects.Length;
var first = so.targetObjects[0] as IHasSkeletonDataAsset;
for (int i = 1; i < n; i++) {
var sr = so.targetObjects[i] as IHasSkeletonDataAsset;
if (sr != null && sr.SkeletonDataAsset != first.SkeletonDataAsset)
return false;
}
}
return true;
}
public static SerializedObject GetRenderersSerializedObject (SerializedObject serializedObject) {
if (serializedObject.isEditingMultipleObjects) {
var renderers = new List<Object>();
foreach (var o in serializedObject.targetObjects) {
var component = o as Component;
if (component != null) {
var renderer = component.GetComponent<Renderer>();
if (renderer != null)
renderers.Add(renderer);
}
}
return new SerializedObject(renderers.ToArray());
} else {
var component = serializedObject.targetObject as Component;
if (component != null) {
var renderer = component.GetComponent<Renderer>();
if (renderer != null)
return new SerializedObject(renderer);
}
}
return null;
}
#endregion
#region Sorting Layer Field Helpers
static readonly GUIContent SortingLayerLabel = new GUIContent("Sorting Layer", "MeshRenderer.sortingLayerID");
static readonly GUIContent OrderInLayerLabel = new GUIContent("Order in Layer", "MeshRenderer.sortingOrder");
static MethodInfo m_SortingLayerFieldMethod;
static MethodInfo SortingLayerFieldMethod {
get {
if (m_SortingLayerFieldMethod == null)
m_SortingLayerFieldMethod = typeof(EditorGUILayout).GetMethod("SortingLayerField", BindingFlags.Static | BindingFlags.NonPublic, null, new [] { typeof(GUIContent), typeof(SerializedProperty), typeof(GUIStyle) }, null);
return m_SortingLayerFieldMethod;
}
}
public struct SerializedSortingProperties {
public SerializedObject renderer;
public SerializedProperty sortingLayerID;
public SerializedProperty sortingOrder;
public SerializedSortingProperties (Renderer r) : this(new SerializedObject(r)) {}
public SerializedSortingProperties (Object[] renderers) : this(new SerializedObject(renderers)) {}
/// <summary>
/// Initializes a new instance of the
/// <see cref="Spine.Unity.Editor.SpineInspectorUtility.SerializedSortingProperties"/> struct.
/// </summary>
/// <param name="rendererSerializedObject">SerializedObject of the renderer. Use
/// <see cref="Spine.Unity.Editor.SpineInspectorUtility.GetRenderersSerializedObject"/> to easily generate this.</param>
public SerializedSortingProperties (SerializedObject rendererSerializedObject) {
renderer = rendererSerializedObject;
sortingLayerID = renderer.FindProperty("m_SortingLayerID");
sortingOrder = renderer.FindProperty("m_SortingOrder");
}
public void ApplyModifiedProperties () {
renderer.ApplyModifiedProperties();
// SetDirty
if (renderer.isEditingMultipleObjects)
foreach (var o in renderer.targetObjects)
EditorUtility.SetDirty(o);
else
EditorUtility.SetDirty(renderer.targetObject);
}
}
public static void SortingPropertyFields (SerializedSortingProperties prop, bool applyModifiedProperties) {
if (applyModifiedProperties)
EditorGUI.BeginChangeCheck();
if (SpineInspectorUtility.SortingLayerFieldMethod != null && prop.sortingLayerID != null)
SpineInspectorUtility.SortingLayerFieldMethod.Invoke(null, new object[] { SortingLayerLabel, prop.sortingLayerID, EditorStyles.popup } );
else
EditorGUILayout.PropertyField(prop.sortingLayerID);
EditorGUILayout.PropertyField(prop.sortingOrder, OrderInLayerLabel);
if (applyModifiedProperties && EditorGUI.EndChangeCheck())
prop.ApplyModifiedProperties();
}
#endregion
}
}