/Assets/Spine/Editor/spine-unity/Modules/SkeletonRenderSeparator/Editor/SkeletonRenderSeparatorInspector.cs
C# | 323 lines | 228 code | 49 blank | 46 comment | 80 complexity | ce02c2654c6569620ce9c04fcde6b975 MD5 | raw file
1/******************************************************************************
2 * Spine Runtimes License Agreement
3 * Last updated January 1, 2020. Replaces all prior versions.
4 *
5 * Copyright (c) 2013-2020, Esoteric Software LLC
6 *
7 * Integration of the Spine Runtimes into software or otherwise creating
8 * derivative works of the Spine Runtimes is permitted under the terms and
9 * conditions of Section 2 of the Spine Editor License Agreement:
10 * http://esotericsoftware.com/spine-editor-license
11 *
12 * Otherwise, it is permitted to integrate the Spine Runtimes into software
13 * or otherwise create derivative works of the Spine Runtimes (collectively,
14 * "Products"), provided that each user of the Products must obtain their own
15 * Spine Editor license and redistribution of the Products in any form must
16 * include this license and copyright notice.
17 *
18 * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY
19 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
20 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
21 * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY
22 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
23 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
24 * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND
25 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
27 * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28 *****************************************************************************/
29
30#if UNITY_2018_3 || UNITY_2019 || UNITY_2018_3_OR_NEWER
31#define NEW_PREFAB_SYSTEM
32#endif
33
34using UnityEngine;
35using UnityEditor;
36
37using System.Collections.Generic;
38
39using Spine.Unity;
40using Spine.Unity.Editor;
41
42namespace Spine.Unity.Examples {
43
44 [CustomEditor(typeof(SkeletonRenderSeparator))]
45 public class SkeletonRenderSeparatorInspector : UnityEditor.Editor {
46 SkeletonRenderSeparator component;
47
48 // Properties
49 SerializedProperty skeletonRenderer_, copyPropertyBlock_, copyMeshRendererFlags_, partsRenderers_;
50 static bool partsRenderersExpanded = false;
51
52 // For separator field.
53 SerializedObject skeletonRendererSerializedObject;
54 SerializedProperty separatorNamesProp;
55 static bool skeletonRendererExpanded = true;
56 bool slotsReapplyRequired = false;
57 bool partsRendererInitRequired = false;
58
59 void OnEnable () {
60 if (component == null)
61 component = target as SkeletonRenderSeparator;
62
63 skeletonRenderer_ = serializedObject.FindProperty("skeletonRenderer");
64 copyPropertyBlock_ = serializedObject.FindProperty("copyPropertyBlock");
65 copyMeshRendererFlags_ = serializedObject.FindProperty("copyMeshRendererFlags");
66
67 var partsRenderers = component.partsRenderers;
68 partsRenderers_ = serializedObject.FindProperty("partsRenderers");
69 partsRenderers_.isExpanded = partsRenderersExpanded || // last state
70 partsRenderers.Contains(null) || // null items found
71 partsRenderers.Count < 1 || // no parts renderers
72 (skeletonRenderer_.objectReferenceValue != null && SkeletonRendererSeparatorCount + 1 > partsRenderers.Count); // not enough parts renderers
73 }
74
75 int SkeletonRendererSeparatorCount {
76 get {
77 if (Application.isPlaying)
78 return component.SkeletonRenderer.separatorSlots.Count;
79 else
80 return separatorNamesProp == null ? 0 : separatorNamesProp.arraySize;
81 }
82 }
83
84 public override void OnInspectorGUI () {
85
86 // Restore mesh part for undo logic after undo of "Add Parts Renderer".
87 // Triggers regeneration and assignment of the mesh filter's mesh.
88
89 bool isMeshFilterAlwaysNull = false;
90 #if UNITY_EDITOR && NEW_PREFAB_SYSTEM
91 // Don't store mesh or material at the prefab, otherwise it will permanently reload
92 var prefabType = UnityEditor.PrefabUtility.GetPrefabAssetType(component);
93 if (UnityEditor.PrefabUtility.IsPartOfPrefabAsset(component) &&
94 (prefabType == UnityEditor.PrefabAssetType.Regular || prefabType == UnityEditor.PrefabAssetType.Variant)) {
95 isMeshFilterAlwaysNull = true;
96 }
97 #endif
98
99 if (!isMeshFilterAlwaysNull && component.GetComponent<MeshFilter>() && component.GetComponent<MeshFilter>().sharedMesh == null) {
100 component.OnDisable();
101 component.OnEnable();
102 }
103
104 var componentRenderers = component.partsRenderers;
105 int totalParts;
106
107 using (new SpineInspectorUtility.LabelWidthScope()) {
108 bool componentEnabled = component.enabled;
109 bool checkBox = EditorGUILayout.Toggle("Enable Separator", componentEnabled);
110 if (checkBox != componentEnabled)
111 component.enabled = checkBox;
112 if (component.SkeletonRenderer.disableRenderingOnOverride && !component.enabled)
113 EditorGUILayout.HelpBox("By default, SkeletonRenderer's MeshRenderer is disabled while the SkeletonRenderSeparator takes over rendering. It is re-enabled when SkeletonRenderSeparator is disabled.", MessageType.Info);
114
115 EditorGUILayout.PropertyField(copyPropertyBlock_);
116 EditorGUILayout.PropertyField(copyMeshRendererFlags_);
117 }
118
119 // SkeletonRenderer Box
120 using (new SpineInspectorUtility.BoxScope(false)) {
121 // Fancy SkeletonRenderer foldout reference field
122 {
123 EditorGUI.indentLevel++;
124 EditorGUI.BeginChangeCheck();
125 var foldoutSkeletonRendererRect = EditorGUILayout.GetControlRect(true, EditorGUIUtility.singleLineHeight);
126 EditorGUI.PropertyField(foldoutSkeletonRendererRect, skeletonRenderer_);
127 if (EditorGUI.EndChangeCheck())
128 serializedObject.ApplyModifiedProperties();
129 if (component.SkeletonRenderer != null) {
130 skeletonRendererExpanded = EditorGUI.Foldout(foldoutSkeletonRendererRect, skeletonRendererExpanded, "");
131 }
132 EditorGUI.indentLevel--;
133 }
134
135 int separatorCount = 0;
136 EditorGUI.BeginChangeCheck();
137 if (component.SkeletonRenderer != null) {
138 // Separators from SkeletonRenderer
139 {
140 bool skeletonRendererMismatch = skeletonRendererSerializedObject != null && skeletonRendererSerializedObject.targetObject != component.SkeletonRenderer;
141 if (separatorNamesProp == null || skeletonRendererMismatch) {
142 if (component.SkeletonRenderer != null) {
143 skeletonRendererSerializedObject = new SerializedObject(component.SkeletonRenderer);
144 separatorNamesProp = skeletonRendererSerializedObject.FindProperty("separatorSlotNames");
145 separatorNamesProp.isExpanded = true;
146 }
147 }
148
149 if (separatorNamesProp != null) {
150 if (skeletonRendererExpanded) {
151 EditorGUI.indentLevel++;
152 SkeletonRendererInspector.SeparatorsField(separatorNamesProp);
153 EditorGUI.indentLevel--;
154 }
155 separatorCount = this.SkeletonRendererSeparatorCount;
156 }
157 }
158
159 if (SkeletonRendererSeparatorCount == 0) {
160 EditorGUILayout.HelpBox("Separators are empty. Change the size to 1 and choose a slot if you want the render to be separated.", MessageType.Info);
161 }
162 }
163
164 if (EditorGUI.EndChangeCheck()) {
165 skeletonRendererSerializedObject.ApplyModifiedProperties();
166
167 if (!Application.isPlaying)
168 slotsReapplyRequired = true;
169 }
170
171
172 totalParts = separatorCount + 1;
173 var counterStyle = skeletonRendererExpanded ? EditorStyles.label : EditorStyles.miniLabel;
174 EditorGUILayout.LabelField(string.Format("{0}: separates into {1}.", SpineInspectorUtility.Pluralize(separatorCount, "separator", "separators"), SpineInspectorUtility.Pluralize(totalParts, "part", "parts") ), counterStyle);
175 }
176
177 // Parts renderers
178 using (new SpineInspectorUtility.BoxScope(false)) {
179 EditorGUI.indentLevel++;
180 EditorGUILayout.PropertyField(this.partsRenderers_, true);
181 EditorGUI.indentLevel--;
182
183 // Null items warning
184 bool nullItemsFound = componentRenderers.Contains(null);
185 if (nullItemsFound)
186 EditorGUILayout.HelpBox("Some items in the parts renderers list are null and may cause problems.\n\nYou can right-click on that element and choose 'Delete Array Element' to remove it.", MessageType.Warning);
187
188 // (Button) Match Separators count
189 if (separatorNamesProp != null) {
190 int currentRenderers = 0;
191 foreach (var r in componentRenderers) {
192 if (r != null)
193 currentRenderers++;
194 }
195 int extraRenderersNeeded = totalParts - currentRenderers;
196
197 if (component.enabled && component.SkeletonRenderer != null && extraRenderersNeeded > 0) {
198 EditorGUILayout.HelpBox(string.Format("Insufficient parts renderers. Some parts will not be rendered."), MessageType.Warning);
199 string addMissingLabel = string.Format("Add the missing renderer{1} ({0}) ", extraRenderersNeeded, SpineInspectorUtility.PluralThenS(extraRenderersNeeded));
200 if (GUILayout.Button(addMissingLabel, GUILayout.Height(30f))) {
201 AddPartsRenderer(extraRenderersNeeded);
202 DetectOrphanedPartsRenderers(component);
203 partsRendererInitRequired = true;
204 }
205 }
206 }
207
208 if (partsRenderers_.isExpanded != partsRenderersExpanded) partsRenderersExpanded = partsRenderers_.isExpanded;
209 if (partsRenderers_.isExpanded) {
210 using (new EditorGUILayout.HorizontalScope()) {
211 // (Button) Destroy Renderers button
212 if (componentRenderers.Count > 0) {
213 if (GUILayout.Button("Clear Parts Renderers")) {
214 // Do you really want to destroy all?
215 Undo.RegisterCompleteObjectUndo(component, "Clear Parts Renderers");
216 if (EditorUtility.DisplayDialog("Destroy Renderers", "Do you really want to destroy all the Parts Renderer GameObjects in the list?", "Destroy", "Cancel")) {
217 foreach (var r in componentRenderers) {
218 if (r != null)
219 Undo.DestroyObjectImmediate(r.gameObject);
220 }
221 componentRenderers.Clear();
222 // Do you also want to destroy orphans? (You monster.)
223 DetectOrphanedPartsRenderers(component);
224 }
225 }
226 }
227
228 // (Button) Add Part Renderer button
229 if (GUILayout.Button("Add Parts Renderer")) {
230 AddPartsRenderer(1);
231 partsRendererInitRequired = true;
232 }
233 }
234 }
235 }
236
237 serializedObject.ApplyModifiedProperties();
238
239 if (partsRendererInitRequired) {
240 Undo.RegisterCompleteObjectUndo(component.GetComponent<MeshRenderer>(), "Add Parts Renderers");
241 component.OnEnable();
242 partsRendererInitRequired = false;
243 }
244
245 if (slotsReapplyRequired && UnityEngine.Event.current.type == EventType.Repaint) {
246 component.SkeletonRenderer.ReapplySeparatorSlotNames();
247 component.SkeletonRenderer.LateUpdate();
248 SceneView.RepaintAll();
249 slotsReapplyRequired = false;
250 }
251 }
252
253 public void AddPartsRenderer (int count) {
254 var componentRenderers = component.partsRenderers;
255 bool emptyFound = componentRenderers.Contains(null);
256 if (emptyFound) {
257 bool userClearEntries = EditorUtility.DisplayDialog("Empty entries found", "Null entries found. Do you want to remove null entries before adding the new renderer? ", "Clear Empty Entries", "Don't Clear");
258 if (userClearEntries) componentRenderers.RemoveAll(x => x == null);
259 }
260
261 Undo.RegisterCompleteObjectUndo(component, "Add Parts Renderers");
262 for (int i = 0; i < count; i++) {
263 int index = componentRenderers.Count;
264 var smr = SkeletonPartsRenderer.NewPartsRendererGameObject(component.transform, index.ToString());
265 Undo.RegisterCreatedObjectUndo(smr.gameObject, "New Parts Renderer GameObject.");
266 componentRenderers.Add(smr);
267
268 // increment renderer sorting order.
269 if (index == 0) continue;
270 var prev = componentRenderers[index - 1]; if (prev == null) continue;
271
272 var prevMeshRenderer = prev.GetComponent<MeshRenderer>();
273 var currentMeshRenderer = smr.GetComponent<MeshRenderer>();
274 if (prevMeshRenderer == null || currentMeshRenderer == null) continue;
275
276 int prevSortingLayer = prevMeshRenderer.sortingLayerID;
277 int prevSortingOrder = prevMeshRenderer.sortingOrder;
278 currentMeshRenderer.sortingLayerID = prevSortingLayer;
279 currentMeshRenderer.sortingOrder = prevSortingOrder + SkeletonRenderSeparator.DefaultSortingOrderIncrement;
280 }
281
282 }
283
284 /// <summary>Detects orphaned parts renderers and offers to delete them.</summary>
285 public void DetectOrphanedPartsRenderers (SkeletonRenderSeparator component) {
286 var children = component.GetComponentsInChildren<SkeletonPartsRenderer>();
287
288 var orphans = new System.Collections.Generic.List<SkeletonPartsRenderer>();
289 foreach (var r in children) {
290 if (!component.partsRenderers.Contains(r))
291 orphans.Add(r);
292 }
293
294 if (orphans.Count > 0) {
295 if (EditorUtility.DisplayDialog("Destroy Submesh Renderers", "Unassigned renderers were found. Do you want to delete them? (These may belong to another Render Separator in the same hierarchy. If you don't have another Render Separator component in the children of this GameObject, it's likely safe to delete. Warning: This operation cannot be undone.)", "Delete", "Cancel")) {
296 foreach (var o in orphans) {
297 Undo.DestroyObjectImmediate(o.gameObject);
298 }
299 }
300 }
301 }
302
303 #region SkeletonRenderer Context Menu Item
304 [MenuItem ("CONTEXT/SkeletonRenderer/Add Skeleton Render Separator")]
305 static void AddRenderSeparatorComponent (MenuCommand cmd) {
306 var skeletonRenderer = cmd.context as SkeletonRenderer;
307 var newComponent = skeletonRenderer.gameObject.AddComponent<SkeletonRenderSeparator>();
308
309 Undo.RegisterCreatedObjectUndo(newComponent, "Add SkeletonRenderSeparator");
310 }
311
312 // Validate
313 [MenuItem ("CONTEXT/SkeletonRenderer/Add Skeleton Render Separator", true)]
314 static bool ValidateAddRenderSeparatorComponent (MenuCommand cmd) {
315 var skeletonRenderer = cmd.context as SkeletonRenderer;
316 var separator = skeletonRenderer.GetComponent<SkeletonRenderSeparator>();
317 bool separatorNotOnObject = separator == null;
318 return separatorNotOnObject;
319 }
320 #endregion
321
322 }
323}