// Animancer // https://kybernetik.com.au/animancer // Copyright 2018-2024 Kybernetik // #if UNITY_EDITOR using System; using System.Collections.Generic; using UnityEditor; using UnityEngine; using Object = UnityEngine.Object; namespace Animancer.Editor.Tools { /// [Editor-Only] [Pro-Only] /// A base for modifying s. /// /// /// Documentation: /// /// Animancer Tools /// /// https://kybernetik.com.au/animancer/api/Animancer.Editor.Tools/SpriteModifierTool /// [Serializable] public abstract class SpriteModifierTool : AnimancerToolsWindow.Tool { /************************************************************************************************************************/ private static readonly List SelectedSprites = new(); private static bool _HasGatheredSprites; /// The currently selected s. public static List Sprites { get { if (!_HasGatheredSprites) { _HasGatheredSprites = true; GatherSelectedSprites(SelectedSprites); } return SelectedSprites; } } /// public override void OnSelectionChanged() { _HasGatheredSprites = false; } /************************************************************************************************************************/ /// public override void DoBodyGUI() { #if !UNITY_2D_SPRITE EditorGUILayout.HelpBox( "This tool works best with Unity's '2D Sprite' package." + " You should import it via the Package Manager before using this tool.", MessageType.Warning); if (AnimancerGUI.TryUseClickEventInLastRect()) EditorApplication.ExecuteMenuItem("Window/Package Manager"); #endif } /************************************************************************************************************************/ /// /// Adds all s in the or their sub-assets to the /// list of `sprites`. /// public static void GatherSelectedSprites(List sprites) { sprites.Clear(); var selection = Selection.objects; for (int i = 0; i < selection.Length; i++) { var selected = selection[i]; if (selected is Sprite sprite) { sprites.Add(sprite); } else if (selected is Texture2D texture) { sprites.AddRange(LoadAllSpritesInTexture(texture)); } } sprites.Sort(NaturalCompare); } /************************************************************************************************************************/ /// Returns all the sub-assets of the `texture`. public static Sprite[] LoadAllSpritesInTexture(Texture2D texture) => LoadAllSpritesAtPath(AssetDatabase.GetAssetPath(texture)); /// Returns all the assets at the `path`. public static Sprite[] LoadAllSpritesAtPath(string path) { var assets = AssetDatabase.LoadAllAssetsAtPath(path); var sprites = new List(); for (int j = 0; j < assets.Length; j++) { if (assets[j] is Sprite sprite) sprites.Add(sprite); } return sprites.ToArray(); } /************************************************************************************************************************/ /// Calls on the s. public static int NaturalCompare(Object a, Object b) => EditorUtility.NaturalCompare(a.name, b.name); /************************************************************************************************************************/ /// The message to confirm that the user is certain they want to apply the changes. protected virtual string AreYouSure => "Are you sure you want to modify these Sprites?"; /// Called immediately after the user confirms they want to apply changes. protected virtual void BeforeApply() { } /// Called after all changes are applied. protected virtual void AfterApply() { } /// Applies the desired modifications to the `data` before it is saved. protected virtual void Modify(SpriteDataEditor data, int index, Sprite sprite) { } /// Applies the desired modifications to the `data` before it is saved. protected virtual void Modify(TextureImporter importer, List sprites) { var dataEditor = new SpriteDataEditor(importer); var hasError = false; for (int i = 0; i < sprites.Count; i++) { var sprite = sprites[i]; var index = dataEditor.IndexOf(sprite); if (index < 0) continue; Modify(dataEditor, index, sprite); sprites.RemoveAt(i--); if (!dataEditor.ValidateBounds(index, sprite)) hasError = true; } if (!hasError) dataEditor.Apply(); } /************************************************************************************************************************/ /// /// Asks the user if they want to modify the target s and calls /// on each of them before saving any changes. /// protected void AskAndApply() { if (!EditorUtility.DisplayDialog("Are You Sure?", AreYouSure + "\n\nThis operation cannot be undone.", "Modify", "Cancel")) return; BeforeApply(); var pathToSprites = new Dictionary>(); var sprites = Sprites; for (int i = 0; i < sprites.Count; i++) { var sprite = sprites[i]; var path = AssetDatabase.GetAssetPath(sprite); if (!pathToSprites.TryGetValue(path, out var spritesAtPath)) pathToSprites.Add(path, spritesAtPath = new()); spritesAtPath.Add(sprite); } foreach (var asset in pathToSprites) { var importer = (TextureImporter)AssetImporter.GetAtPath(asset.Key); Modify(importer, asset.Value); if (asset.Value.Count > 0) { var message = StringBuilderPool.Instance.Acquire() .Append("Modification failed: unable to find data in '") .Append(asset.Key) .Append("' for ") .Append(asset.Value.Count) .Append(" Sprites:"); for (int i = 0; i < sprites.Count; i++) { message.AppendLine() .Append(" - ") .Append(sprites[i].name); } Debug.LogError(message.ReleaseToString(), AssetDatabase.LoadAssetAtPath(asset.Key)); } } AfterApply(); } /************************************************************************************************************************/ } } #endif