summaryrefslogtreecommitdiff
path: root/Assets/Scripts/Editor/InspectorUtility.cs
blob: 5c1e6e05153b54a72d18da703c2311f93f3098dc (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
using UnityEngine;
using UnityEditor;
using System;
using System.Collections.Generic;
using System.IO;

/// <summary>
/// Collection of tools and helpers for drawing inspectors
/// </summary>
public class InspectorUtility
{
	/// <summary>Put multiple properties on a single inspector line, with
	/// optional label overrides.  Passing null as a label (or sublabel) override will
	/// cause the property's displayName to be used as a label.  For no label at all,
	/// pass GUIContent.none.</summary>
	/// <param name="rect">Rect in which to draw</param>
	/// <param name="label">Main label</param>
	/// <param name="props">Properties to place on the line</param>
	/// <param name="subLabels">Sublabels for the properties</param>
	public static void MultiPropertyOnLine(
		Rect rect,
		GUIContent label,
		SerializedProperty[] props, GUIContent[] subLabels)
	{
		if (props == null || props.Length == 0)
			return;

		const int hSpace = 2;
		int indentLevel = EditorGUI.indentLevel;
		float labelWidth = EditorGUIUtility.labelWidth;

		float totalSubLabelWidth = 0;
		int numBoolColumns = 0;
		List<GUIContent> actualLabels = new List<GUIContent>();
		for (int i = 0; i < props.Length; ++i)
		{
			GUIContent sublabel = new GUIContent(props[i].displayName, props[i].tooltip);
			if (subLabels != null && subLabels.Length > i && subLabels[i] != null)
				sublabel = subLabels[i];
			actualLabels.Add(sublabel);
			totalSubLabelWidth += GUI.skin.label.CalcSize(sublabel).x;
			if (i > 0)
				totalSubLabelWidth += hSpace;
			// Special handling for toggles, or it looks stupid
			if (props[i].propertyType == SerializedPropertyType.Boolean)
			{
				totalSubLabelWidth += rect.height;
				++numBoolColumns;
			}
		}

		float subFieldWidth = rect.width - labelWidth - totalSubLabelWidth;
		float numCols = props.Length - numBoolColumns;
		float colWidth = numCols == 0 ? 0 : subFieldWidth / numCols;

		// Main label.  If no first sublabel, then main label must take on that
		// role, for mouse dragging value-scrolling support
		int subfieldStartIndex = 0;
		if (label == null)
			label = new GUIContent(props[0].displayName, props[0].tooltip);
		if (actualLabels[0] != GUIContent.none)
			rect = EditorGUI.PrefixLabel(rect, label);
		else
		{
			rect.width = labelWidth + colWidth;
			EditorGUI.PropertyField(rect, props[0], label);
			rect.x += rect.width + hSpace;
			subfieldStartIndex = 1;
		}

		for (int i = subfieldStartIndex; i < props.Length; ++i)
		{
			EditorGUI.indentLevel = 0;
			EditorGUIUtility.labelWidth = GUI.skin.label.CalcSize(actualLabels[i]).x;
			if (props[i].propertyType == SerializedPropertyType.Boolean)
			{
				rect.width = EditorGUIUtility.labelWidth + rect.height;
				props[i].boolValue = EditorGUI.ToggleLeft(rect, actualLabels[i], props[i].boolValue);
			}
			else
			{
				rect.width = EditorGUIUtility.labelWidth + colWidth;
				EditorGUI.PropertyField(rect, props[i], actualLabels[i]);
			}
			rect.x += rect.width + hSpace;
		}

		EditorGUIUtility.labelWidth = labelWidth;
		EditorGUI.indentLevel = indentLevel;
	}

	/// <summary>
	/// Normalize a curve so that each of X and Y axes ranges from 0 to 1
	/// </summary>
	/// <param name="curve">Curve to normalize</param>
	/// <returns>The normalized curve</returns>
	public static AnimationCurve NormalizeCurve(AnimationCurve curve)
	{
		Keyframe[] keys = curve.keys;
		if (keys.Length > 0)
		{
			float minTime = keys[0].time;
			float maxTime = minTime;
			float minVal = keys[0].value;
			float maxVal = minVal;
			for (int i = 0; i < keys.Length; ++i)
			{
				minTime = Mathf.Min(minTime, keys[i].time);
				maxTime = Mathf.Max(maxTime, keys[i].time);
				minVal = Mathf.Min(minVal, keys[i].value);
				maxVal = Mathf.Max(maxVal, keys[i].value);
			}
			float range = maxTime - minTime;
			float timeScale = range < 0.0001f ? 1 : 1 / range;
			range = maxVal - minVal;
			float valScale = range < 1 ? 1 : 1 / range;
			float valOffset = 0;
			if (range < 1)
			{
				if (minVal > 0 && minVal + range <= 1)
					valOffset = minVal;
				else
					valOffset = 1 - range;
			}
			for (int i = 0; i < keys.Length; ++i)
			{
				keys[i].time = (keys[i].time - minTime) * timeScale;
				keys[i].value = ((keys[i].value - minVal) * valScale) + valOffset;
			}
			curve.keys = keys;
		}
		return curve;
	}

	/// <summary>
	/// Remove the "Cinemachine" prefix, then call the standard Unity Nicify.
	/// </summary>
	/// <param name="name">The name to nicify</param>
	/// <returns>The nicified name</returns>
	public static string NicifyClassName(string name)
	{
		if (name.StartsWith("Cinemachine"))
			name = name.Substring(11); // Trim the prefix
		return ObjectNames.NicifyVariableName(name);
	}

	// Temporarily here
	/// <summary>
	/// Create a game object.  Uses ObjectFactory if the Unity version is sufficient.
	/// </summary>
	/// <param name="name">Name to give the object</param>
	/// <param name="types">Optional components to add</param>
	/// <returns></returns>
	public static GameObject CreateGameObject(string name, params Type[] types)
	{
		return ObjectFactory.CreateGameObject(name, types);
	}

	/// <summary>
	/// Force a repaint of the Game View
	/// </summary>
	/// <param name="unused">Like it says</param>
	public static void RepaintGameView(UnityEngine.Object unused = null)
	{
		EditorApplication.QueuePlayerLoopUpdate();
		UnityEditorInternal.InternalEditorUtility.RepaintAllViews();
	}

}