SpineAttributeDrawers.cs 25 KB

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