SpriteModifierTool.cs 9.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241
  1. // Animancer // https://kybernetik.com.au/animancer // Copyright 2022 Kybernetik //
  2. #if UNITY_EDITOR
  3. using System;
  4. using System.Collections.Generic;
  5. using UnityEditor;
  6. using UnityEngine;
  7. using Object = UnityEngine.Object;
  8. namespace Animancer.Editor.Tools
  9. {
  10. /// <summary>[Editor-Only] [Pro-Only]
  11. /// A base <see cref="AnimancerToolsWindow.Tool"/> for modifying <see cref="Sprite"/>s.
  12. /// </summary>
  13. /// <remarks>
  14. /// Documentation: <see href="https://kybernetik.com.au/animancer/docs/manual/tools">Animancer Tools</see>
  15. /// </remarks>
  16. /// https://kybernetik.com.au/animancer/api/Animancer.Editor.Tools/SpriteModifierTool
  17. ///
  18. [Serializable]
  19. public abstract class SpriteModifierTool : AnimancerToolsWindow.Tool
  20. {
  21. /************************************************************************************************************************/
  22. private static readonly List<Sprite> SelectedSprites = new List<Sprite>();
  23. private static bool _HasGatheredSprites;
  24. /// <summary>The currently selected <see cref="Sprite"/>s.</summary>
  25. public static List<Sprite> Sprites
  26. {
  27. get
  28. {
  29. if (!_HasGatheredSprites)
  30. {
  31. _HasGatheredSprites = true;
  32. GatherSelectedSprites(SelectedSprites);
  33. }
  34. return SelectedSprites;
  35. }
  36. }
  37. /// <inheritdoc/>
  38. public override void OnSelectionChanged()
  39. {
  40. _HasGatheredSprites = false;
  41. }
  42. /************************************************************************************************************************/
  43. /// <inheritdoc/>
  44. public override void DoBodyGUI()
  45. {
  46. const string Message = "This tool works best with Unity's '2D Sprite' package.";
  47. if (!SpriteDataEditor.Is2dPackagePresent)
  48. {
  49. EditorGUILayout.HelpBox(
  50. Message + " You should import it via the Package Manager before using this tool.",
  51. MessageType.Warning);
  52. if (AnimancerGUI.TryUseClickEventInLastRect())
  53. EditorApplication.ExecuteMenuItem("Window/Package Manager");
  54. }
  55. else
  56. {
  57. EditorGUI.BeginChangeCheck();
  58. var isDefined = SpriteDataEditor.IsSpritePackageSymbolDefined;
  59. if (!isDefined)
  60. {
  61. EditorGUILayout.HelpBox(Message, MessageType.Warning);
  62. if (AnimancerGUI.TryUseClickEventInLastRect())
  63. EditorApplication.ExecuteMenuItem("Window/Package Manager");
  64. }
  65. using (ObjectPool.Disposable.AcquireContent(out var label,
  66. "Use 2D Sprite Package",
  67. "This toggle adds or removes the scripting define symbol '" +
  68. SpriteDataEditor.SpritePackageDefineSymbol + "'" +
  69. " so you will need to wait for Unity to recompile everything if you change it."))
  70. {
  71. isDefined = EditorGUILayout.ToggleLeft(label, isDefined);
  72. }
  73. if (EditorGUI.EndChangeCheck())
  74. SpriteDataEditor.IsSpritePackageSymbolDefined = isDefined;
  75. }
  76. }
  77. /************************************************************************************************************************/
  78. /// <summary>
  79. /// Adds all <see cref="Sprite"/>s in the <see cref="Selection.objects"/> or their sub-assets to the
  80. /// list of `sprites`.
  81. /// </summary>
  82. public static void GatherSelectedSprites(List<Sprite> sprites)
  83. {
  84. sprites.Clear();
  85. var selection = Selection.objects;
  86. for (int i = 0; i < selection.Length; i++)
  87. {
  88. var selected = selection[i];
  89. if (selected is Sprite sprite)
  90. {
  91. sprites.Add(sprite);
  92. }
  93. else if (selected is Texture2D texture)
  94. {
  95. sprites.AddRange(LoadAllSpritesInTexture(texture));
  96. }
  97. }
  98. sprites.Sort(NaturalCompare);
  99. }
  100. /************************************************************************************************************************/
  101. /// <summary>Returns all the <see cref="Sprite"/> sub-assets of the `texture`.</summary>
  102. public static Sprite[] LoadAllSpritesInTexture(Texture2D texture)
  103. => LoadAllSpritesAtPath(AssetDatabase.GetAssetPath(texture));
  104. /// <summary>Returns all the <see cref="Sprite"/> assets at the `path`.</summary>
  105. public static Sprite[] LoadAllSpritesAtPath(string path)
  106. {
  107. var assets = AssetDatabase.LoadAllAssetsAtPath(path);
  108. var sprites = new List<Sprite>();
  109. for (int j = 0; j < assets.Length; j++)
  110. {
  111. if (assets[j] is Sprite sprite)
  112. sprites.Add(sprite);
  113. }
  114. return sprites.ToArray();
  115. }
  116. /************************************************************************************************************************/
  117. /// <summary>Calls <see cref="EditorUtility.NaturalCompare"/> on the <see cref="Object.name"/>s.</summary>
  118. public static int NaturalCompare(Object a, Object b) => EditorUtility.NaturalCompare(a.name, b.name);
  119. /************************************************************************************************************************/
  120. /// <summary>The message to confirm that the user is certain they want to apply the changes.</summary>
  121. protected virtual string AreYouSure => "Are you sure you want to modify these Sprites?";
  122. /// <summary>Called immediately after the user confirms they want to apply changes.</summary>
  123. protected virtual void PrepareToApply() { }
  124. /// <summary>Applies the desired modifications to the `data` before it is saved.</summary>
  125. protected virtual void Modify(SpriteDataEditor data, int index, Sprite sprite) { }
  126. /// <summary>Applies the desired modifications to the `data` before it is saved.</summary>
  127. protected virtual void Modify(TextureImporter importer, List<Sprite> sprites)
  128. {
  129. var dataEditor = new SpriteDataEditor(importer);
  130. var hasError = false;
  131. for (int i = 0; i < sprites.Count; i++)
  132. {
  133. var sprite = sprites[i];
  134. var index = dataEditor.IndexOf(sprite);
  135. if (index < 0)
  136. continue;
  137. Modify(dataEditor, index, sprite);
  138. sprites.RemoveAt(i--);
  139. if (!dataEditor.ValidateBounds(index, sprite))
  140. hasError = true;
  141. }
  142. if (!hasError)
  143. dataEditor.Apply();
  144. }
  145. /************************************************************************************************************************/
  146. /// <summary>
  147. /// Asks the user if they want to modify the target <see cref="Sprite"/>s and calls <see cref="Modify"/>
  148. /// on each of them before saving any changes.
  149. /// </summary>
  150. protected void AskAndApply()
  151. {
  152. if (!EditorUtility.DisplayDialog("Are You Sure?",
  153. AreYouSure + "\n\nThis operation cannot be undone.",
  154. "Modify", "Cancel"))
  155. return;
  156. PrepareToApply();
  157. var pathToSprites = new Dictionary<string, List<Sprite>>();
  158. var sprites = Sprites;
  159. for (int i = 0; i < sprites.Count; i++)
  160. {
  161. var sprite = sprites[i];
  162. var path = AssetDatabase.GetAssetPath(sprite);
  163. if (!pathToSprites.TryGetValue(path, out var spritesAtPath))
  164. pathToSprites.Add(path, spritesAtPath = new List<Sprite>());
  165. spritesAtPath.Add(sprite);
  166. }
  167. foreach (var asset in pathToSprites)
  168. {
  169. var importer = (TextureImporter)AssetImporter.GetAtPath(asset.Key);
  170. Modify(importer, asset.Value);
  171. if (asset.Value.Count > 0)
  172. {
  173. var message = ObjectPool.AcquireStringBuilder()
  174. .Append("Modification failed: unable to find data in '")
  175. .Append(asset.Key)
  176. .Append("' for ")
  177. .Append(asset.Value.Count)
  178. .Append(" Sprites:");
  179. for (int i = 0; i < sprites.Count; i++)
  180. {
  181. message.AppendLine()
  182. .Append(" - ")
  183. .Append(sprites[i].name);
  184. }
  185. Debug.LogError(message.ReleaseToString(), AssetDatabase.LoadAssetAtPath<Object>(asset.Key));
  186. }
  187. }
  188. }
  189. /************************************************************************************************************************/
  190. }
  191. }
  192. #endif