// 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