Singularity/Library/PackageCache/com.unity.2d.sprite@1.0.0/Editor/SpriteEditorModule/SpriteFrameModule/SpriteFrameModuleBase.cs
2024-05-06 11:45:45 -07:00

639 lines
22 KiB
C#

using System;
using System.Collections;
using System.Linq;
using System.Collections.Generic;
using UnityEngine;
using UnityEditorInternal;
namespace UnityEditor.U2D.Sprites
{
internal class SpriteRectModel : ScriptableObject, ISerializationCallbackReceiver
{
[Serializable]
struct StringGUID
{
[SerializeField]
string m_StringGUID;
public StringGUID(GUID guid)
{
m_StringGUID = guid.ToString();
}
public static implicit operator GUID(StringGUID d) => new GUID(d.m_StringGUID);
public static implicit operator StringGUID(GUID d) => new StringGUID(d);
}
[Serializable]
class StringGUIDList : List<StringGUID>, IReadOnlyList<GUID>
{
GUID IReadOnlyList<GUID>.this[int index]
{
get => this[index];
}
IEnumerator<GUID> IEnumerable<GUID>.GetEnumerator()
{
// Not used for now
throw new NotImplementedException();
}
}
/// <summary>
/// List of all SpriteRects
/// </summary>
[SerializeField] private List<SpriteRect> m_SpriteRects;
/// <summary>
/// List of all names in the Name-FileId Table
/// </summary>
[SerializeField] private List<string> m_SpriteNames;
/// <summary>
/// List of all FileIds in the Name-FileId Table
/// </summary>
[SerializeField] private StringGUIDList m_SpriteFileIds;
/// <summary>
/// HashSet of all names currently in use by SpriteRects
/// </summary>
private HashSet<string> m_NamesInUse;
private HashSet<GUID> m_InternalIdsInUse;
public IReadOnlyList<SpriteRect> spriteRects => m_SpriteRects;
public IReadOnlyList<string> spriteNames => m_SpriteNames;
public IReadOnlyList<GUID> spriteFileIds => m_SpriteFileIds;
private SpriteRectModel()
{
m_SpriteNames = new List<string>();
m_SpriteFileIds = new StringGUIDList();
Clear();
}
public void SetSpriteRects(List<SpriteRect> newSpriteRects)
{
m_SpriteRects = newSpriteRects;
m_NamesInUse = new HashSet<string>();
m_InternalIdsInUse = new HashSet<GUID>();
for (var i = 0; i < m_SpriteRects.Count; ++i)
{
m_NamesInUse.Add(m_SpriteRects[i].name);
m_InternalIdsInUse.Add(m_SpriteRects[i].spriteID);
}
}
public void SetNameFileIdPairs(IEnumerable<SpriteNameFileIdPair> pairs)
{
m_SpriteNames.Clear();
m_SpriteFileIds.Clear();
foreach (var pair in pairs)
AddNameFileIdPair(pair.name, pair.GetFileGUID());
}
public int FindIndex(Predicate<SpriteRect> match)
{
int i = 0;
foreach (var spriteRect in m_SpriteRects)
{
if (match.Invoke(spriteRect))
return i;
i++;
}
return -1;
}
public void Clear()
{
m_SpriteRects = new List<SpriteRect>();
m_NamesInUse = new HashSet<string>();
m_InternalIdsInUse = new HashSet<GUID>();
}
public bool Add(SpriteRect spriteRect, bool shouldReplaceInTable = false)
{
if (spriteRect.spriteID.Empty())
{
spriteRect.spriteID = GUID.Generate();
}
else
{
if (IsInternalIdInUsed(spriteRect.spriteID))
return false;
}
if (shouldReplaceInTable)
{
// replace id from sprite to file id table
if (!UpdateIdInNameIdPair(spriteRect.name, spriteRect.spriteID))
{
// add it into file id table if update wasn't successful i.e. it doesn't exist yet
AddNameFileIdPair(spriteRect.name, spriteRect.spriteID);
}
}
else
{
// Since we are not replacing the file id table,
// look for any existing id and set it to the SpriteRect
var index = m_SpriteNames.FindIndex(x => x == spriteRect.name);
if (index >= 0)
{
if (IsInternalIdInUsed(m_SpriteFileIds[index]))
return false;
spriteRect.spriteID = m_SpriteFileIds[index];
}
else
AddNameFileIdPair(spriteRect.name, spriteRect.spriteID);
}
m_SpriteRects.Add(spriteRect);
m_NamesInUse.Add(spriteRect.name);
m_InternalIdsInUse.Add(spriteRect.spriteID);
return true;
}
public void Remove(SpriteRect spriteRect)
{
m_SpriteRects.Remove(spriteRect);
m_NamesInUse.Remove(spriteRect.name);
m_InternalIdsInUse.Remove(spriteRect.spriteID);
}
/// <summary>
/// Checks whether or not the name is currently in use by any of the SpriteRects in the texture.
/// </summary>
/// <param name="rectName">The name to check for</param>
/// <returns>True if the name is currently in use</returns>
public bool IsNameUsed(string rectName)
{
return m_NamesInUse.Contains(rectName);
}
/// <summary>
/// Checks whether or not the id is currently in use by any of the SpriteRects in the texture.
/// </summary>
/// <param name="rectName">The id to check for</param>
/// <returns>True if the name is currently in use</returns>
public bool IsInternalIdInUsed(GUID internalId)
{
return m_InternalIdsInUse.Contains(internalId);
}
public List<SpriteRect> GetSpriteRects()
{
return m_SpriteRects;
}
public bool Rename(string oldName, string newName, GUID fileId)
{
if (!IsNameUsed(oldName))
return false;
if (IsNameUsed(newName))
return false;
var index = m_SpriteNames.FindIndex(x => x == oldName);
if (index >= 0)
{
m_SpriteNames.RemoveAt(index);
m_SpriteFileIds.RemoveAt(index);
}
index = m_SpriteNames.FindIndex(x => x == newName);
if (index >= 0)
m_SpriteFileIds[index] = fileId;
else
AddNameFileIdPair(newName, fileId);
m_NamesInUse.Remove(oldName);
m_NamesInUse.Add(newName);
return true;
}
void AddNameFileIdPair(string spriteName, GUID fileId)
{
m_SpriteNames.Add(spriteName);
m_SpriteFileIds.Add(fileId);
}
bool UpdateIdInNameIdPair(string spriteName, GUID newFileId)
{
var index = m_SpriteNames.FindIndex(x => x == spriteName);
if (index >= 0)
{
m_SpriteFileIds[index] = newFileId;
return true;
}
return false;
}
public void ClearUnusedFileID()
{
m_SpriteNames.Clear();
m_SpriteFileIds.Clear();
foreach (var sprite in m_SpriteRects)
{
m_SpriteNames.Add(sprite.name);
m_SpriteFileIds.Add(sprite.spriteID);
}
}
void ISerializationCallbackReceiver.OnBeforeSerialize()
{}
void ISerializationCallbackReceiver.OnAfterDeserialize()
{
SetSpriteRects(m_SpriteRects);
}
}
internal class OutlineSpriteRect : SpriteRect
{
public List<Vector2[]> outlines;
public OutlineSpriteRect(SpriteRect rect)
{
this.name = rect.name;
this.originalName = rect.originalName;
this.pivot = rect.pivot;
this.alignment = rect.alignment;
this.border = rect.border;
this.rect = rect.rect;
this.spriteID = rect.spriteID;
outlines = new List<Vector2[]>();
}
}
internal abstract partial class SpriteFrameModuleBase : SpriteEditorModuleBase
{
protected SpriteRectModel m_RectsCache;
protected ITextureDataProvider m_TextureDataProvider;
protected ISpriteEditorDataProvider m_SpriteDataProvider;
protected ISpriteNameFileIdDataProvider m_NameFileIdDataProvider;
string m_ModuleName;
internal enum PivotUnitMode
{
Normalized,
Pixels
}
private PivotUnitMode m_PivotUnitMode = PivotUnitMode.Normalized;
protected SpriteFrameModuleBase(string name, ISpriteEditor sw, IEventSystem es, IUndoSystem us, IAssetDatabase ad)
{
spriteEditor = sw;
eventSystem = es;
undoSystem = us;
assetDatabase = ad;
m_ModuleName = name;
}
// implements ISpriteEditorModule
public override void OnModuleActivate()
{
spriteImportMode = SpriteFrameModule.GetSpriteImportMode(spriteEditor.GetDataProvider<ISpriteEditorDataProvider>());
m_TextureDataProvider = spriteEditor.GetDataProvider<ITextureDataProvider>();
m_NameFileIdDataProvider = spriteEditor.GetDataProvider<ISpriteNameFileIdDataProvider>();
m_SpriteDataProvider = spriteEditor.GetDataProvider<ISpriteEditorDataProvider>();
int width, height;
m_TextureDataProvider.GetTextureActualWidthAndHeight(out width, out height);
textureActualWidth = width;
textureActualHeight = height;
m_RectsCache = ScriptableObject.CreateInstance<SpriteRectModel>();
m_RectsCache.hideFlags = HideFlags.HideAndDontSave;
var spriteList = m_SpriteDataProvider.GetSpriteRects().ToList();
m_RectsCache.SetSpriteRects(spriteList);
spriteEditor.spriteRects = spriteList;
if (m_NameFileIdDataProvider == null)
m_NameFileIdDataProvider = new DefaultSpriteNameFileIdDataProvider(spriteList);
var nameFileIdPairs = m_NameFileIdDataProvider.GetNameFileIdPairs();
m_RectsCache.SetNameFileIdPairs(nameFileIdPairs);
if (spriteEditor.selectedSpriteRect != null)
spriteEditor.selectedSpriteRect = m_RectsCache.spriteRects.FirstOrDefault(x => x.spriteID == spriteEditor.selectedSpriteRect.spriteID);
AddMainUI(spriteEditor.GetMainVisualContainer());
undoSystem.RegisterUndoCallback(UndoCallback);
}
public override void OnModuleDeactivate()
{
if (m_RectsCache != null)
{
undoSystem.ClearUndo(m_RectsCache);
ScriptableObject.DestroyImmediate(m_RectsCache);
m_RectsCache = null;
}
undoSystem.UnregisterUndoCallback(UndoCallback);
RemoveMainUI(spriteEditor.GetMainVisualContainer());
}
public override bool ApplyRevert(bool apply)
{
if (apply)
{
var array = m_RectsCache != null ? m_RectsCache.spriteRects.ToArray() : null;
m_SpriteDataProvider.SetSpriteRects(array);
var spriteNames = m_RectsCache?.spriteNames;
var spriteFileIds = m_RectsCache?.spriteFileIds;
if (spriteNames != null && spriteFileIds != null)
{
var pairList = new List<SpriteNameFileIdPair>(spriteNames.Count);
for (var i = 0; i < spriteNames.Count; ++i)
pairList.Add(new SpriteNameFileIdPair(spriteNames[i], spriteFileIds[i]));
m_NameFileIdDataProvider.SetNameFileIdPairs(pairList.ToArray());
}
var outlineDataProvider = m_SpriteDataProvider.GetDataProvider<ISpriteOutlineDataProvider>();
var physicsDataProvider = m_SpriteDataProvider.GetDataProvider<ISpritePhysicsOutlineDataProvider>();
foreach (var rect in array)
{
if (rect is OutlineSpriteRect outlineRect)
{
if (outlineRect.outlines.Count > 0)
{
outlineDataProvider.SetOutlines(outlineRect.spriteID, outlineRect.outlines);
physicsDataProvider.SetOutlines(outlineRect.spriteID, outlineRect.outlines);
}
}
}
if (m_RectsCache != null)
undoSystem.ClearUndo(m_RectsCache);
}
else
{
if (m_RectsCache != null)
{
undoSystem.ClearUndo(m_RectsCache);
var spriteList = m_SpriteDataProvider.GetSpriteRects().ToList();
m_RectsCache.SetSpriteRects(spriteList);
var nameFileIdPairs = m_NameFileIdDataProvider.GetNameFileIdPairs();
m_RectsCache.SetNameFileIdPairs(nameFileIdPairs);
spriteEditor.spriteRects = spriteList;
if (spriteEditor.selectedSpriteRect != null)
spriteEditor.selectedSpriteRect = m_RectsCache.spriteRects.FirstOrDefault(x => x.spriteID == spriteEditor.selectedSpriteRect.spriteID);
}
}
return true;
}
public override string moduleName
{
get { return m_ModuleName; }
}
// injected interfaces
protected IEventSystem eventSystem
{
get;
private set;
}
protected IUndoSystem undoSystem
{
get;
private set;
}
protected IAssetDatabase assetDatabase
{
get;
private set;
}
protected SpriteRect selected
{
get { return spriteEditor.selectedSpriteRect; }
set { spriteEditor.selectedSpriteRect = value; }
}
protected SpriteImportMode spriteImportMode
{
get; private set;
}
protected string spriteAssetPath
{
get { return assetDatabase.GetAssetPath(m_TextureDataProvider.texture); }
}
public bool hasSelected
{
get { return spriteEditor.selectedSpriteRect != null; }
}
public SpriteAlignment selectedSpriteAlignment
{
get { return selected.alignment; }
}
public Vector2 selectedSpritePivot
{
get { return selected.pivot; }
}
private Vector2 selectedSpritePivotInCurUnitMode
{
get
{
return m_PivotUnitMode == PivotUnitMode.Pixels
? ConvertFromNormalizedToRectSpace(selectedSpritePivot, selectedSpriteRect)
: selectedSpritePivot;
}
}
public int CurrentSelectedSpriteIndex()
{
if (m_RectsCache != null && selected != null)
return m_RectsCache.FindIndex(x => x.spriteID == selected.spriteID);
return -1;
}
public Vector4 selectedSpriteBorder
{
get { return ClampSpriteBorderToRect(selected.border, selected.rect); }
set
{
undoSystem.RegisterCompleteObjectUndo(m_RectsCache, "Change Sprite Border");
spriteEditor.SetDataModified();
selected.border = ClampSpriteBorderToRect(value, selected.rect);
}
}
public Rect selectedSpriteRect
{
get { return selected.rect; }
set
{
undoSystem.RegisterCompleteObjectUndo(m_RectsCache, "Change Sprite rect");
spriteEditor.SetDataModified();
selected.rect = ClampSpriteRect(value, textureActualWidth, textureActualHeight);
}
}
public string selectedSpriteName
{
get { return selected.name; }
set
{
if (selected.name == value)
return;
if (m_RectsCache.IsNameUsed(value))
return;
undoSystem.RegisterCompleteObjectUndo(m_RectsCache, "Change Sprite Name");
spriteEditor.SetDataModified();
string oldName = selected.name;
string newName = InternalEditorUtility.RemoveInvalidCharsFromFileName(value, true);
// These can only be changed in sprite multiple mode
if (string.IsNullOrEmpty(selected.originalName) && (newName != oldName))
selected.originalName = oldName;
// Is the name empty?
if (string.IsNullOrEmpty(newName))
newName = oldName;
// Did the rename succeed?
if (m_RectsCache.Rename(oldName, newName, selected.spriteID))
selected.name = newName;
}
}
public int spriteCount
{
get { return m_RectsCache.spriteRects.Count; }
}
public Vector4 GetSpriteBorderAt(int i)
{
return m_RectsCache.spriteRects[i].border;
}
public Rect GetSpriteRectAt(int i)
{
return m_RectsCache.spriteRects[i].rect;
}
public int textureActualWidth { get; private set; }
public int textureActualHeight { get; private set; }
public void SetSpritePivotAndAlignment(Vector2 pivot, SpriteAlignment alignment)
{
undoSystem.RegisterCompleteObjectUndo(m_RectsCache, "Change Sprite Pivot");
spriteEditor.SetDataModified();
selected.alignment = alignment;
selected.pivot = SpriteEditorUtility.GetPivotValue(alignment, pivot);
}
public bool containsMultipleSprites
{
get { return spriteImportMode == SpriteImportMode.Multiple; }
}
protected void SnapPivotToSnapPoints(Vector2 pivot, out Vector2 outPivot, out SpriteAlignment outAlignment)
{
Rect rect = selectedSpriteRect;
// Convert from normalized space to texture space
Vector2 texturePos = new Vector2(rect.xMin + rect.width * pivot.x, rect.yMin + rect.height * pivot.y);
Vector2[] snapPoints = GetSnapPointsArray(rect);
// Snapping is now a firm action, it will always snap to one of the snapping points.
SpriteAlignment snappedAlignment = SpriteAlignment.Custom;
float nearestDistance = float.MaxValue;
for (int alignment = 0; alignment < snapPoints.Length; alignment++)
{
float distance = (texturePos - snapPoints[alignment]).magnitude * m_Zoom;
if (distance < nearestDistance)
{
snappedAlignment = (SpriteAlignment)alignment;
nearestDistance = distance;
}
}
outAlignment = snappedAlignment;
outPivot = ConvertFromTextureToNormalizedSpace(snapPoints[(int)snappedAlignment], rect);
}
protected void SnapPivotToPixels(Vector2 pivot, out Vector2 outPivot, out SpriteAlignment outAlignment)
{
outAlignment = SpriteAlignment.Custom;
Rect rect = selectedSpriteRect;
float unitsPerPixelX = 1.0f / rect.width;
float unitsPerPixelY = 1.0f / rect.height;
outPivot.x = Mathf.Round(pivot.x / unitsPerPixelX) * unitsPerPixelX;
outPivot.y = Mathf.Round(pivot.y / unitsPerPixelY) * unitsPerPixelY;
}
private void UndoCallback()
{
UIUndoCallback();
}
protected static Rect ClampSpriteRect(Rect rect, float maxX, float maxY)
{
// Clamp rect to width height
rect = FlipNegativeRect(rect);
Rect newRect = new Rect();
newRect.xMin = Mathf.Clamp(rect.xMin, 0, maxX - 1);
newRect.yMin = Mathf.Clamp(rect.yMin, 0, maxY - 1);
newRect.xMax = Mathf.Clamp(rect.xMax, 1, maxX);
newRect.yMax = Mathf.Clamp(rect.yMax, 1, maxY);
// Prevent width and height to be 0 value after clamping.
if (Mathf.RoundToInt(newRect.width) == 0)
newRect.width = 1;
if (Mathf.RoundToInt(newRect.height) == 0)
newRect.height = 1;
return SpriteEditorUtility.RoundedRect(newRect);
}
protected static Rect FlipNegativeRect(Rect rect)
{
Rect newRect = new Rect();
newRect.xMin = Mathf.Min(rect.xMin, rect.xMax);
newRect.yMin = Mathf.Min(rect.yMin, rect.yMax);
newRect.xMax = Mathf.Max(rect.xMin, rect.xMax);
newRect.yMax = Mathf.Max(rect.yMin, rect.yMax);
return newRect;
}
protected static Vector4 ClampSpriteBorderToRect(Vector4 border, Rect rect)
{
Rect flipRect = FlipNegativeRect(rect);
float w = flipRect.width;
float h = flipRect.height;
Vector4 newBorder = new Vector4();
// Make sure borders are within the width/height and left < right and top < bottom
newBorder.x = Mathf.RoundToInt(Mathf.Clamp(border.x, 0, Mathf.Min(Mathf.Abs(w - border.z), w))); // Left
newBorder.z = Mathf.RoundToInt(Mathf.Clamp(border.z, 0, Mathf.Min(Mathf.Abs(w - newBorder.x), w))); // Right
newBorder.y = Mathf.RoundToInt(Mathf.Clamp(border.y, 0, Mathf.Min(Mathf.Abs(h - border.w), h))); // Bottom
newBorder.w = Mathf.RoundToInt(Mathf.Clamp(border.w, 0, Mathf.Min(Mathf.Abs(h - newBorder.y), h))); // Top
return newBorder;
}
}
}