SpriteModifierTool.cs 8.1 KB

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