SpineAttributeDrawers.cs 30 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728
  1. /******************************************************************************
  2. * Spine Runtimes License Agreement
  3. * Last updated July 28, 2023. Replaces all prior versions.
  4. *
  5. * Copyright (c) 2013-2023, 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 or
  13. * 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 THE
  27. * SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  28. *****************************************************************************/
  29. // Contributed by: Mitch Thompson
  30. #if UNITY_2023_2_OR_NEWER
  31. #define MENU_REQUIRES_DIFFERENT_NESTED_NAME
  32. #endif
  33. using Spine;
  34. using System;
  35. using System.Collections.Generic;
  36. using System.Linq;
  37. using System.Reflection;
  38. using UnityEditor;
  39. using UnityEngine;
  40. namespace Spine.Unity.Editor {
  41. public struct SpineDrawerValuePair {
  42. public string stringValue;
  43. public SerializedProperty property;
  44. public SpineDrawerValuePair (string val, SerializedProperty property) {
  45. this.stringValue = val;
  46. this.property = property;
  47. }
  48. }
  49. public abstract class SpineTreeItemDrawerBase<T> : PropertyDrawer where T : SpineAttributeBase {
  50. protected SkeletonDataAsset skeletonDataAsset;
  51. internal const string NoneStringConstant = "<None>";
  52. internal virtual string NoneString { get { return NoneStringConstant; } }
  53. GUIContent noneLabel;
  54. GUIContent NoneLabel (Texture2D image = null) {
  55. if (noneLabel == null) noneLabel = new GUIContent(NoneString);
  56. noneLabel.image = image;
  57. return noneLabel;
  58. }
  59. static GUIStyle errorPopupStyle;
  60. GUIStyle ErrorPopupStyle {
  61. get {
  62. if (errorPopupStyle == null) errorPopupStyle = new GUIStyle(EditorStyles.popup);
  63. errorPopupStyle.normal.textColor = Color.red;
  64. errorPopupStyle.hover.textColor = Color.red;
  65. errorPopupStyle.focused.textColor = Color.red;
  66. errorPopupStyle.active.textColor = Color.red;
  67. return errorPopupStyle;
  68. }
  69. }
  70. protected T TargetAttribute { get { return (T)attribute; } }
  71. protected SerializedProperty SerializedProperty { get; private set; }
  72. protected abstract Texture2D Icon { get; }
  73. protected bool IsValueValid (SerializedProperty property) {
  74. if (skeletonDataAsset != null) {
  75. SkeletonData skeletonData = skeletonDataAsset.GetSkeletonData(true);
  76. if (skeletonData != null && !string.IsNullOrEmpty(property.stringValue))
  77. return IsValueValid(skeletonData, property);
  78. }
  79. return true;
  80. }
  81. protected virtual bool IsValueValid (SkeletonData skeletonData, SerializedProperty property) { return true; }
  82. public override void OnGUI (Rect position, SerializedProperty property, GUIContent label) {
  83. SerializedProperty = property;
  84. if (property.propertyType != SerializedPropertyType.String) {
  85. EditorGUI.LabelField(position, "ERROR:", "May only apply to type string");
  86. return;
  87. }
  88. // Handle multi-editing when instances don't use the same SkeletonDataAsset.
  89. if (!SpineInspectorUtility.TargetsUseSameData(property.serializedObject)) {
  90. EditorGUI.DelayedTextField(position, property, label);
  91. return;
  92. }
  93. SerializedProperty dataField = property.FindBaseOrSiblingProperty(TargetAttribute.dataField);
  94. if (dataField != null) {
  95. UnityEngine.Object objectReferenceValue = dataField.objectReferenceValue;
  96. if (objectReferenceValue is SkeletonDataAsset) {
  97. skeletonDataAsset = (SkeletonDataAsset)objectReferenceValue;
  98. } else if (objectReferenceValue is IHasSkeletonDataAsset) {
  99. IHasSkeletonDataAsset hasSkeletonDataAsset = (IHasSkeletonDataAsset)objectReferenceValue;
  100. if (hasSkeletonDataAsset != null)
  101. skeletonDataAsset = hasSkeletonDataAsset.SkeletonDataAsset;
  102. } else if (objectReferenceValue != null) {
  103. EditorGUI.LabelField(position, "ERROR:", "Invalid reference type");
  104. return;
  105. }
  106. } else {
  107. UnityEngine.Object targetObject = property.serializedObject.targetObject;
  108. IHasSkeletonDataAsset hasSkeletonDataAsset = targetObject as IHasSkeletonDataAsset;
  109. if (hasSkeletonDataAsset == null) {
  110. Component component = targetObject as Component;
  111. if (component != null)
  112. hasSkeletonDataAsset = component.GetComponentInChildren(typeof(IHasSkeletonDataAsset)) as IHasSkeletonDataAsset;
  113. }
  114. if (hasSkeletonDataAsset != null)
  115. skeletonDataAsset = hasSkeletonDataAsset.SkeletonDataAsset;
  116. }
  117. if (skeletonDataAsset == null) {
  118. if (TargetAttribute.fallbackToTextField) {
  119. EditorGUI.PropertyField(position, property); //EditorGUI.TextField(position, label, property.stringValue);
  120. } else {
  121. EditorGUI.LabelField(position, "ERROR:", "Must have reference to a SkeletonDataAsset");
  122. }
  123. skeletonDataAsset = property.serializedObject.targetObject as SkeletonDataAsset;
  124. if (skeletonDataAsset == null) return;
  125. }
  126. position = EditorGUI.PrefixLabel(position, label);
  127. Texture2D image = Icon;
  128. GUIStyle usedStyle = IsValueValid(property) ? EditorStyles.popup : ErrorPopupStyle;
  129. string propertyStringValue = (property.hasMultipleDifferentValues) ? SpineInspectorUtility.EmDash : property.stringValue;
  130. if (!TargetAttribute.avoidGenericMenu) {
  131. if (GUI.Button(position, string.IsNullOrEmpty(propertyStringValue) ? NoneLabel(image) :
  132. SpineInspectorUtility.TempContent(propertyStringValue, image), usedStyle))
  133. Selector(property);
  134. } else {
  135. SkeletonData skeletonData = skeletonDataAsset.GetSkeletonData(false);
  136. List<GUIContent> contentList = new List<GUIContent>();
  137. List<string> valueList = new List<string>();
  138. PopulatePopupList(ref contentList, ref valueList, image, property, TargetAttribute, skeletonData);
  139. int currentIndex = valueList.IndexOf(propertyStringValue);
  140. int previousIndex = currentIndex;
  141. currentIndex = EditorGUI.Popup(position, currentIndex, contentList.ToArray());
  142. if (previousIndex != currentIndex) {
  143. property.stringValue = valueList[currentIndex];
  144. property.serializedObject.ApplyModifiedProperties();
  145. }
  146. }
  147. }
  148. public ISkeletonComponent GetTargetSkeletonComponent (SerializedProperty property) {
  149. SerializedProperty dataField = property.FindBaseOrSiblingProperty(TargetAttribute.dataField);
  150. if (dataField != null) {
  151. ISkeletonComponent skeletonComponent = dataField.objectReferenceValue as ISkeletonComponent;
  152. if (dataField.objectReferenceValue != null && skeletonComponent != null) // note the overloaded UnityEngine.Object == null check. Do not simplify.
  153. return skeletonComponent;
  154. } else {
  155. Component component = property.serializedObject.targetObject as Component;
  156. if (component != null)
  157. return component.GetComponentInChildren(typeof(ISkeletonComponent)) as ISkeletonComponent;
  158. }
  159. return null;
  160. }
  161. protected virtual void Selector (SerializedProperty property) {
  162. SkeletonData data = skeletonDataAsset.GetSkeletonData(true);
  163. if (data == null) return;
  164. GenericMenu menu = new GenericMenu();
  165. PopulateMenu(menu, property, this.TargetAttribute, data);
  166. menu.ShowAsContext();
  167. }
  168. protected abstract void PopulateMenu (GenericMenu menu, SerializedProperty property, T targetAttribute, SkeletonData data);
  169. protected virtual void HandleSelect (object menuItemObject) {
  170. SpineDrawerValuePair clickedItem = (SpineDrawerValuePair)menuItemObject;
  171. SerializedProperty serializedProperty = clickedItem.property;
  172. if (serializedProperty.serializedObject.isEditingMultipleObjects) serializedProperty.stringValue = "oaifnoiasf��123526"; // HACK: to trigger change on multi-editing.
  173. serializedProperty.stringValue = clickedItem.stringValue;
  174. serializedProperty.serializedObject.ApplyModifiedProperties();
  175. }
  176. protected virtual void PopulatePopupList (ref List<GUIContent> contentList, ref List<string> valueList,
  177. Texture2D image, SerializedProperty property, T targetAttribute, SkeletonData data) {
  178. contentList.Add(new GUIContent("Type Not Supported"));
  179. valueList.Add(string.Empty);
  180. }
  181. public override float GetPropertyHeight (SerializedProperty property, GUIContent label) {
  182. return 18;
  183. }
  184. }
  185. [CustomPropertyDrawer(typeof(SpineSlot))]
  186. public class SpineSlotDrawer : SpineTreeItemDrawerBase<SpineSlot> {
  187. protected override Texture2D Icon { get { return SpineEditorUtilities.Icons.slot; } }
  188. protected override bool IsValueValid (SkeletonData skeletonData, SerializedProperty property) {
  189. return skeletonData.FindSlot(property.stringValue) != null;
  190. }
  191. protected override void PopulateMenu (GenericMenu menu, SerializedProperty property, SpineSlot targetAttribute, SkeletonData data) {
  192. if (TargetAttribute.includeNone)
  193. menu.AddItem(new GUIContent(NoneString), !property.hasMultipleDifferentValues && string.IsNullOrEmpty(property.stringValue), HandleSelect, new SpineDrawerValuePair(string.Empty, property));
  194. IEnumerable<SlotData> orderedSlots = data.Slots.Items.OrderBy(slotData => slotData.Name);
  195. foreach (SlotData slotData in orderedSlots) {
  196. int slotIndex = slotData.Index;
  197. string name = slotData.Name;
  198. if (name.StartsWith(targetAttribute.startsWith, StringComparison.Ordinal)) {
  199. if (targetAttribute.containsBoundingBoxes) {
  200. List<Skin.SkinEntry> skinEntries = new List<Skin.SkinEntry>();
  201. foreach (Skin skin in data.Skins) {
  202. skin.GetAttachments(slotIndex, skinEntries);
  203. }
  204. bool hasBoundingBox = false;
  205. foreach (Skin.SkinEntry entry in skinEntries) {
  206. BoundingBoxAttachment bbAttachment = entry.Attachment as BoundingBoxAttachment;
  207. if (bbAttachment != null) {
  208. string menuLabel = bbAttachment.IsWeighted() ? name + " (!)" : name;
  209. menu.AddItem(new GUIContent(menuLabel), !property.hasMultipleDifferentValues && name == property.stringValue, HandleSelect, new SpineDrawerValuePair(name, property));
  210. hasBoundingBox = true;
  211. break;
  212. }
  213. }
  214. if (!hasBoundingBox)
  215. menu.AddDisabledItem(new GUIContent(name));
  216. } else {
  217. menu.AddItem(new GUIContent(name), !property.hasMultipleDifferentValues && name == property.stringValue, HandleSelect, new SpineDrawerValuePair(name, property));
  218. }
  219. }
  220. }
  221. }
  222. }
  223. [CustomPropertyDrawer(typeof(SpineSkin))]
  224. public class SpineSkinDrawer : SpineTreeItemDrawerBase<SpineSkin> {
  225. const string DefaultSkinName = "default";
  226. protected override Texture2D Icon { get { return SpineEditorUtilities.Icons.skin; } }
  227. internal override string NoneString { get { return TargetAttribute.defaultAsEmptyString ? DefaultSkinName : NoneStringConstant; } }
  228. protected override bool IsValueValid (SkeletonData skeletonData, SerializedProperty property) {
  229. return skeletonData.FindSkin(property.stringValue) != null;
  230. }
  231. public static void GetSkinMenuItems (SkeletonData data, List<string> outputNames, List<GUIContent> outputMenuItems, bool includeNone = true) {
  232. if (data == null) return;
  233. if (outputNames == null) return;
  234. if (outputMenuItems == null) return;
  235. ExposedList<Skin> skins = data.Skins;
  236. outputNames.Clear();
  237. outputMenuItems.Clear();
  238. Texture2D icon = SpineEditorUtilities.Icons.skin;
  239. if (includeNone) {
  240. outputNames.Add("");
  241. outputMenuItems.Add(new GUIContent(NoneStringConstant, icon));
  242. }
  243. foreach (Skin s in skins) {
  244. string skinName = s.Name;
  245. outputNames.Add(skinName);
  246. outputMenuItems.Add(new GUIContent(skinName, icon));
  247. }
  248. }
  249. protected override void PopulateMenu (GenericMenu menu, SerializedProperty property, SpineSkin targetAttribute, SkeletonData data) {
  250. menu.AddDisabledItem(new GUIContent(skeletonDataAsset.name));
  251. menu.AddSeparator("");
  252. if (targetAttribute.includeNone)
  253. menu.AddItem(new GUIContent(NoneStringConstant), !property.hasMultipleDifferentValues && string.IsNullOrEmpty(property.stringValue), HandleSelect, new SpineDrawerValuePair(string.Empty, property));
  254. for (int i = 0; i < data.Skins.Count; i++) {
  255. string name = data.Skins.Items[i].Name;
  256. if (name.StartsWith(targetAttribute.startsWith, StringComparison.Ordinal)) {
  257. bool isDefault = string.Equals(name, DefaultSkinName, StringComparison.Ordinal);
  258. string choiceValue = TargetAttribute.defaultAsEmptyString && isDefault ? string.Empty : name;
  259. menu.AddItem(new GUIContent(name), !property.hasMultipleDifferentValues && choiceValue == property.stringValue, HandleSelect, new SpineDrawerValuePair(choiceValue, property));
  260. }
  261. }
  262. }
  263. protected override void PopulatePopupList (ref List<GUIContent> contentList, ref List<string> valueList,
  264. Texture2D image, SerializedProperty property, SpineSkin targetAttribute, SkeletonData data) {
  265. if (targetAttribute.includeNone) {
  266. contentList.Add(new GUIContent(NoneStringConstant, image));
  267. valueList.Add(string.Empty);
  268. }
  269. for (int i = 0; i < data.Skins.Count; i++) {
  270. string name = data.Skins.Items[i].Name;
  271. if (name.StartsWith(targetAttribute.startsWith, StringComparison.Ordinal)) {
  272. bool isDefault = string.Equals(name, DefaultSkinName, StringComparison.Ordinal);
  273. string choiceValue = TargetAttribute.defaultAsEmptyString && isDefault ? string.Empty : name;
  274. contentList.Add(new GUIContent(name, image));
  275. valueList.Add(choiceValue);
  276. }
  277. }
  278. }
  279. }
  280. [CustomPropertyDrawer(typeof(SpineAnimation))]
  281. public class SpineAnimationDrawer : SpineTreeItemDrawerBase<SpineAnimation> {
  282. protected override Texture2D Icon { get { return SpineEditorUtilities.Icons.animation; } }
  283. protected override bool IsValueValid (SkeletonData skeletonData, SerializedProperty property) {
  284. return skeletonData.FindAnimation(property.stringValue) != null;
  285. }
  286. public static void GetAnimationMenuItems (SkeletonData data, List<string> outputNames, List<GUIContent> outputMenuItems, bool includeNone = true) {
  287. if (data == null) return;
  288. if (outputNames == null) return;
  289. if (outputMenuItems == null) return;
  290. ExposedList<Animation> animations = data.Animations;
  291. outputNames.Clear();
  292. outputMenuItems.Clear();
  293. if (includeNone) {
  294. outputNames.Add("");
  295. outputMenuItems.Add(new GUIContent(NoneStringConstant, SpineEditorUtilities.Icons.animation));
  296. }
  297. foreach (Animation a in animations) {
  298. string animationName = a.Name;
  299. outputNames.Add(animationName);
  300. outputMenuItems.Add(new GUIContent(animationName, SpineEditorUtilities.Icons.animation));
  301. }
  302. }
  303. protected override void PopulateMenu (GenericMenu menu, SerializedProperty property, SpineAnimation targetAttribute, SkeletonData data) {
  304. ExposedList<Animation> animations = skeletonDataAsset.GetAnimationStateData().SkeletonData.Animations;
  305. if (TargetAttribute.includeNone)
  306. menu.AddItem(new GUIContent(NoneString), !property.hasMultipleDifferentValues && string.IsNullOrEmpty(property.stringValue), HandleSelect, new SpineDrawerValuePair(string.Empty, property));
  307. for (int i = 0; i < animations.Count; i++) {
  308. string name = animations.Items[i].Name;
  309. if (name.StartsWith(targetAttribute.startsWith, StringComparison.Ordinal))
  310. menu.AddItem(new GUIContent(name), !property.hasMultipleDifferentValues && name == property.stringValue, HandleSelect, new SpineDrawerValuePair(name, property));
  311. }
  312. }
  313. protected override void PopulatePopupList (ref List<GUIContent> contentList, ref List<string> valueList,
  314. Texture2D image, SerializedProperty property, SpineAnimation targetAttribute, SkeletonData data) {
  315. ExposedList<Animation> animations = data.Animations;
  316. if (targetAttribute.includeNone) {
  317. contentList.Add(new GUIContent(NoneString, image));
  318. valueList.Add(string.Empty);
  319. }
  320. for (int i = 0; i < animations.Count; i++) {
  321. string name = animations.Items[i].Name;
  322. if (name.StartsWith(targetAttribute.startsWith, StringComparison.Ordinal)) {
  323. contentList.Add(new GUIContent(name, image));
  324. valueList.Add(name);
  325. }
  326. }
  327. }
  328. }
  329. [CustomPropertyDrawer(typeof(SpineEvent))]
  330. public class SpineEventNameDrawer : SpineTreeItemDrawerBase<SpineEvent> {
  331. protected override Texture2D Icon { get { return SpineEditorUtilities.Icons.userEvent; } }
  332. protected override bool IsValueValid (SkeletonData skeletonData, SerializedProperty property) {
  333. return skeletonData.FindEvent(property.stringValue) != null;
  334. }
  335. public static void GetEventMenuItems (SkeletonData data, List<string> eventNames, List<GUIContent> menuItems, bool includeNone = true) {
  336. if (data == null) return;
  337. ExposedList<EventData> animations = data.Events;
  338. eventNames.Clear();
  339. menuItems.Clear();
  340. if (includeNone) {
  341. eventNames.Add("");
  342. menuItems.Add(new GUIContent(NoneStringConstant, SpineEditorUtilities.Icons.userEvent));
  343. }
  344. foreach (EventData a in animations) {
  345. string animationName = a.Name;
  346. eventNames.Add(animationName);
  347. menuItems.Add(new GUIContent(animationName, SpineEditorUtilities.Icons.userEvent));
  348. }
  349. }
  350. protected override void PopulateMenu (GenericMenu menu, SerializedProperty property, SpineEvent targetAttribute, SkeletonData data) {
  351. ExposedList<EventData> events = skeletonDataAsset.GetSkeletonData(false).Events;
  352. if (TargetAttribute.includeNone)
  353. menu.AddItem(new GUIContent(NoneString), !property.hasMultipleDifferentValues && string.IsNullOrEmpty(property.stringValue), HandleSelect, new SpineDrawerValuePair(string.Empty, property));
  354. for (int i = 0; i < events.Count; i++) {
  355. EventData eventObject = events.Items[i];
  356. string name = eventObject.Name;
  357. if (name.StartsWith(targetAttribute.startsWith, StringComparison.Ordinal)) {
  358. if (!TargetAttribute.audioOnly || !string.IsNullOrEmpty(eventObject.AudioPath)) {
  359. menu.AddItem(new GUIContent(name), !property.hasMultipleDifferentValues && name == property.stringValue, HandleSelect, new SpineDrawerValuePair(name, property));
  360. }
  361. }
  362. }
  363. }
  364. }
  365. [CustomPropertyDrawer(typeof(SpineIkConstraint))]
  366. public class SpineIkConstraintDrawer : SpineTreeItemDrawerBase<SpineIkConstraint> {
  367. protected override Texture2D Icon { get { return SpineEditorUtilities.Icons.constraintIK; } }
  368. protected override bool IsValueValid (SkeletonData skeletonData, SerializedProperty property) {
  369. return skeletonData.FindIkConstraint(property.stringValue) != null;
  370. }
  371. protected override void PopulateMenu (GenericMenu menu, SerializedProperty property, SpineIkConstraint targetAttribute, SkeletonData data) {
  372. ExposedList<IkConstraintData> constraints = skeletonDataAsset.GetSkeletonData(false).IkConstraints;
  373. if (TargetAttribute.includeNone)
  374. menu.AddItem(new GUIContent(NoneString), !property.hasMultipleDifferentValues && string.IsNullOrEmpty(property.stringValue), HandleSelect, new SpineDrawerValuePair(string.Empty, property));
  375. for (int i = 0; i < constraints.Count; i++) {
  376. string name = constraints.Items[i].Name;
  377. if (name.StartsWith(targetAttribute.startsWith, StringComparison.Ordinal))
  378. menu.AddItem(new GUIContent(name), !property.hasMultipleDifferentValues && name == property.stringValue, HandleSelect, new SpineDrawerValuePair(name, property));
  379. }
  380. }
  381. }
  382. [CustomPropertyDrawer(typeof(SpineTransformConstraint))]
  383. public class SpineTransformConstraintDrawer : SpineTreeItemDrawerBase<SpineTransformConstraint> {
  384. protected override Texture2D Icon { get { return SpineEditorUtilities.Icons.constraintTransform; } }
  385. protected override bool IsValueValid (SkeletonData skeletonData, SerializedProperty property) {
  386. return skeletonData.FindTransformConstraint(property.stringValue) != null;
  387. }
  388. protected override void PopulateMenu (GenericMenu menu, SerializedProperty property, SpineTransformConstraint targetAttribute, SkeletonData data) {
  389. ExposedList<TransformConstraintData> constraints = skeletonDataAsset.GetSkeletonData(false).TransformConstraints;
  390. if (TargetAttribute.includeNone)
  391. menu.AddItem(new GUIContent(NoneString), !property.hasMultipleDifferentValues && string.IsNullOrEmpty(property.stringValue), HandleSelect, new SpineDrawerValuePair(string.Empty, property));
  392. for (int i = 0; i < constraints.Count; i++) {
  393. string name = constraints.Items[i].Name;
  394. if (name.StartsWith(targetAttribute.startsWith, StringComparison.Ordinal))
  395. menu.AddItem(new GUIContent(name), !property.hasMultipleDifferentValues && name == property.stringValue, HandleSelect, new SpineDrawerValuePair(name, property));
  396. }
  397. }
  398. }
  399. [CustomPropertyDrawer(typeof(SpinePathConstraint))]
  400. public class SpinePathConstraintDrawer : SpineTreeItemDrawerBase<SpinePathConstraint> {
  401. protected override Texture2D Icon { get { return SpineEditorUtilities.Icons.constraintPath; } }
  402. protected override bool IsValueValid (SkeletonData skeletonData, SerializedProperty property) {
  403. return skeletonData.FindPathConstraint(property.stringValue) != null;
  404. }
  405. protected override void PopulateMenu (GenericMenu menu, SerializedProperty property, SpinePathConstraint targetAttribute, SkeletonData data) {
  406. ExposedList<PathConstraintData> constraints = skeletonDataAsset.GetSkeletonData(false).PathConstraints;
  407. if (TargetAttribute.includeNone)
  408. menu.AddItem(new GUIContent(NoneString), !property.hasMultipleDifferentValues && string.IsNullOrEmpty(property.stringValue), HandleSelect, new SpineDrawerValuePair(string.Empty, property));
  409. for (int i = 0; i < constraints.Count; i++) {
  410. string name = constraints.Items[i].Name;
  411. if (name.StartsWith(targetAttribute.startsWith, StringComparison.Ordinal))
  412. menu.AddItem(new GUIContent(name), !property.hasMultipleDifferentValues && name == property.stringValue, HandleSelect, new SpineDrawerValuePair(name, property));
  413. }
  414. }
  415. }
  416. [CustomPropertyDrawer(typeof(SpineAttachment))]
  417. public class SpineAttachmentDrawer : SpineTreeItemDrawerBase<SpineAttachment> {
  418. protected override Texture2D Icon { get { return SpineEditorUtilities.Icons.genericAttachment; } }
  419. protected override void PopulateMenu (GenericMenu menu, SerializedProperty property, SpineAttachment targetAttribute, SkeletonData data) {
  420. ISkeletonComponent skeletonComponent = GetTargetSkeletonComponent(property);
  421. List<Skin> validSkins = new List<Skin>();
  422. if (skeletonComponent != null && targetAttribute.currentSkinOnly) {
  423. Skin currentSkin = null;
  424. SerializedProperty skinProperty = property.FindBaseOrSiblingProperty(targetAttribute.skinField);
  425. if (skinProperty != null) currentSkin = skeletonComponent.Skeleton.Data.FindSkin(skinProperty.stringValue);
  426. currentSkin = currentSkin ?? skeletonComponent.Skeleton.Skin;
  427. if (currentSkin != null)
  428. validSkins.Add(currentSkin);
  429. else
  430. validSkins.Add(data.Skins.Items[0]);
  431. } else {
  432. foreach (Skin skin in data.Skins)
  433. if (skin != null) validSkins.Add(skin);
  434. }
  435. List<string> attachmentNames = new List<string>();
  436. List<string> placeholderNames = new List<string>();
  437. string prefix = "";
  438. if (skeletonComponent != null && targetAttribute.currentSkinOnly)
  439. menu.AddDisabledItem(new GUIContent((skeletonComponent as Component).gameObject.name + " (Skeleton)"));
  440. else
  441. menu.AddDisabledItem(new GUIContent(skeletonDataAsset.name));
  442. menu.AddSeparator("");
  443. if (TargetAttribute.includeNone) {
  444. const string NullAttachmentName = "";
  445. menu.AddItem(new GUIContent("Null"), !property.hasMultipleDifferentValues && property.stringValue == NullAttachmentName, HandleSelect, new SpineDrawerValuePair(NullAttachmentName, property));
  446. menu.AddSeparator("");
  447. }
  448. Skin defaultSkin = data.Skins.Items[0];
  449. SerializedProperty slotProperty = property.FindBaseOrSiblingProperty(TargetAttribute.slotField);
  450. string slotMatch = "";
  451. if (slotProperty != null) {
  452. if (slotProperty.propertyType == SerializedPropertyType.String)
  453. slotMatch = slotProperty.stringValue.ToLower();
  454. }
  455. foreach (Skin skin in validSkins) {
  456. string skinPrefix = skin.Name + "/";
  457. if (validSkins.Count > 1)
  458. prefix = skinPrefix;
  459. for (int i = 0; i < data.Slots.Count; i++) {
  460. if (slotMatch.Length > 0 && !(data.Slots.Items[i].Name.Equals(slotMatch, StringComparison.OrdinalIgnoreCase)))
  461. continue;
  462. attachmentNames.Clear();
  463. placeholderNames.Clear();
  464. List<Skin.SkinEntry> skinEntries = new List<Skin.SkinEntry>();
  465. skin.GetAttachments(i, skinEntries);
  466. foreach (Skin.SkinEntry entry in skinEntries) {
  467. attachmentNames.Add(entry.Name);
  468. }
  469. if (skin != defaultSkin) {
  470. foreach (Skin.SkinEntry entry in skinEntries) {
  471. placeholderNames.Add(entry.Name);
  472. }
  473. skinEntries.Clear();
  474. defaultSkin.GetAttachments(i, skinEntries);
  475. foreach (Skin.SkinEntry entry in skinEntries) {
  476. attachmentNames.Add(entry.Name);
  477. }
  478. }
  479. for (int a = 0; a < attachmentNames.Count; a++) {
  480. string attachmentPath = attachmentNames[a];
  481. string menuPath = prefix + data.Slots.Items[i].Name + "/" + attachmentPath;
  482. string name = attachmentNames[a];
  483. if (targetAttribute.returnAttachmentPath)
  484. name = skin.Name + "/" + data.Slots.Items[i].Name + "/" + attachmentPath;
  485. if (targetAttribute.placeholdersOnly && !placeholderNames.Contains(attachmentPath)) {
  486. menu.AddDisabledItem(new GUIContent(menuPath));
  487. } else {
  488. menu.AddItem(new GUIContent(menuPath), !property.hasMultipleDifferentValues && name == property.stringValue, HandleSelect, new SpineDrawerValuePair(name, property));
  489. }
  490. }
  491. }
  492. }
  493. }
  494. }
  495. [CustomPropertyDrawer(typeof(SpineBone))]
  496. public class SpineBoneDrawer : SpineTreeItemDrawerBase<SpineBone> {
  497. protected override Texture2D Icon { get { return SpineEditorUtilities.Icons.bone; } }
  498. protected override bool IsValueValid (SkeletonData skeletonData, SerializedProperty property) {
  499. return skeletonData.FindBone(property.stringValue) != null;
  500. }
  501. protected override void PopulateMenu (GenericMenu menu, SerializedProperty property, SpineBone targetAttribute, SkeletonData data) {
  502. menu.AddDisabledItem(new GUIContent(skeletonDataAsset.name));
  503. menu.AddSeparator("");
  504. if (TargetAttribute.includeNone)
  505. menu.AddItem(new GUIContent(NoneString), !property.hasMultipleDifferentValues && string.IsNullOrEmpty(property.stringValue), HandleSelect, new SpineDrawerValuePair(string.Empty, property));
  506. for (int i = 0; i < data.Bones.Count; i++) {
  507. BoneData bone = data.Bones.Items[i];
  508. string name = bone.Name;
  509. if (name.StartsWith(targetAttribute.startsWith, StringComparison.Ordinal)) {
  510. // jointName = "root/hip/bone" to show a hierarchial tree.
  511. string jointName = name;
  512. BoneData iterator = bone;
  513. while ((iterator = iterator.Parent) != null) {
  514. #if MENU_REQUIRES_DIFFERENT_NESTED_NAME
  515. jointName = string.Format("{0} /{1}", iterator.Name, jointName);
  516. #else
  517. jointName = string.Format("{0}/{1}", iterator.Name, jointName);
  518. #endif
  519. }
  520. menu.AddItem(new GUIContent(jointName), !property.hasMultipleDifferentValues && name == property.stringValue, HandleSelect, new SpineDrawerValuePair(name, property));
  521. }
  522. }
  523. }
  524. }
  525. [CustomPropertyDrawer(typeof(SpineAtlasRegion))]
  526. public class SpineAtlasRegionDrawer : PropertyDrawer {
  527. SerializedProperty atlasProp;
  528. protected SpineAtlasRegion TargetAttribute { get { return (SpineAtlasRegion)attribute; } }
  529. public override void OnGUI (Rect position, SerializedProperty property, GUIContent label) {
  530. if (property.propertyType != SerializedPropertyType.String) {
  531. EditorGUI.LabelField(position, "ERROR:", "May only apply to type string");
  532. return;
  533. }
  534. string atlasAssetFieldName = TargetAttribute.atlasAssetField;
  535. if (string.IsNullOrEmpty(atlasAssetFieldName))
  536. atlasAssetFieldName = "atlasAsset";
  537. atlasProp = property.FindBaseOrSiblingProperty(atlasAssetFieldName);
  538. if (atlasProp == null) {
  539. EditorGUI.LabelField(position, "ERROR:", "Must have AtlasAsset variable!");
  540. return;
  541. } else if (atlasProp.objectReferenceValue == null) {
  542. EditorGUI.LabelField(position, "ERROR:", "Atlas variable must not be null!");
  543. return;
  544. } else if (!atlasProp.objectReferenceValue.GetType().IsSubclassOf(typeof(AtlasAssetBase)) &&
  545. atlasProp.objectReferenceValue.GetType() != typeof(AtlasAssetBase)) {
  546. EditorGUI.LabelField(position, "ERROR:", "Atlas variable must be of type AtlasAsset!");
  547. }
  548. position = EditorGUI.PrefixLabel(position, label);
  549. if (GUI.Button(position, property.stringValue, EditorStyles.popup))
  550. Selector(property);
  551. }
  552. void Selector (SerializedProperty property) {
  553. GenericMenu menu = new GenericMenu();
  554. AtlasAssetBase atlasAsset = (AtlasAssetBase)atlasProp.objectReferenceValue;
  555. Atlas atlas = atlasAsset.GetAtlas();
  556. FieldInfo field = typeof(Atlas).GetField("regions", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
  557. List<AtlasRegion> regions = (List<AtlasRegion>)field.GetValue(atlas);
  558. for (int i = 0; i < regions.Count; i++) {
  559. string name = regions[i].name;
  560. menu.AddItem(new GUIContent(name), !property.hasMultipleDifferentValues && name == property.stringValue, HandleSelect, new SpineDrawerValuePair(name, property));
  561. }
  562. menu.ShowAsContext();
  563. }
  564. static void HandleSelect (object val) {
  565. SpineDrawerValuePair pair = (SpineDrawerValuePair)val;
  566. pair.property.stringValue = pair.stringValue;
  567. pair.property.serializedObject.ApplyModifiedProperties();
  568. }
  569. }
  570. }