Files
stas-barecky/Library/PackageCache/com.unity.2d.sprite@82f01c8c75ca/Editor/SpriteEditorModule/SpriteFrameModule/SpriteFrameModule.cs
2026-01-08 20:43:08 +05:00

557 lines
22 KiB
C#

using System;
using System.IO;
using UnityEngine;
using UnityEditorInternal;
using System.Collections.Generic;
using System.Text;
using UnityTexture2D = UnityEngine.Texture2D;
using UnityEditor.ShortcutManagement;
using UnityEditor.U2D.Sprites.Overlay;
using UnityEditor.U2D.Sprites.SpriteFrameEditor;
using Object = UnityEngine.Object;
namespace UnityEditor.U2D.Sprites
{
[RequireSpriteDataProvider(typeof(ITextureDataProvider))]
internal partial class SpriteFrameModule : SpriteFrameModuleBase
{
public enum AutoSlicingMethod
{
DeleteAll = 0,
Smart = 1,
Safe = 2
}
private bool[] m_AlphaPixelCache;
SpriteFrameModuleContext m_SpriteFrameModuleContext;
SpriteFrameModeOverlay m_ModeOverlay;
public event Action onModuleDeactivated = () => { };
ISpriteEditorModuleMode m_CurrentModuleMode = null;
SpriteFrameModeUndoObject m_ModeChangeUndoObject;
private StringBuilder m_SpriteNameStringBuilder;
bool m_SpriteRectValidated = false;
private List<Rect> m_PotentialRects;
Texture2D m_TextureToSlice;
public List<Rect> potentialRects
{
set => m_PotentialRects = value;
}
internal static Func<string, string, string, string, string, int> onShowComplexDialog = EditorUtility.DisplayDialogComplex;
internal static Func<string, string, string, string, bool> onShowDialog = EditorUtility.DisplayDialog;
public SpriteFrameModule(ISpriteEditor sw, IEventSystem es, IUndoSystem us, IAssetDatabase ad) :
base("Sprite Editor", sw, es, us, ad)
{}
public override void SetModuleModes(IEnumerable<Type> modes)
{
base.SetModuleModes(modes);
foreach (var mode in this.modes)
{
mode.onModeRequestActivate += OnModuleModeRequestActivate;
}
}
public void OnModuleModeRequestActivate(ISpriteEditorModuleMode activatingModuleMode)
{
if (activatingModuleMode == null)
activatingModuleMode = GetDefaultMode();
if (activatingModuleMode == m_CurrentModuleMode)
return;
var oldMode = m_CurrentModuleMode;
m_CurrentModuleMode?.DeactivateMode();
m_CurrentModuleMode = activatingModuleMode;
var activated = m_CurrentModuleMode?.ActivateMode();
// Mode did not activate
if(activated.HasValue && !activated.Value)
{
m_CurrentModuleMode?.DeactivateMode();
m_CurrentModuleMode = oldMode;
//Reactivate old mode
m_CurrentModuleMode?.ActivateMode();
}
}
public override bool ApplyRevert(bool apply)
{
var returnValue = base.ApplyRevert(apply);
if (!apply)
ClearTextureOverride();
var dataProviderApplied = new HashSet<Type>();
dataProviderApplied.Add(typeof(ISpriteEditorDataProvider));
foreach(var mode in modes)
{
returnValue |= mode.ApplyModeData(apply, dataProviderApplied);
}
if(apply)
m_SourceOverrideCallback?.Invoke(spriteAssetPath);
return returnValue;
}
public void OnModeActivate(SpriteFrameModeToolStripBase arg2)
{
if (!UndoObject.undoing)
{
if (m_ModeChangeUndoObject == null)
{
m_ModeChangeUndoObject = UndoObject.Create<SpriteFrameModeUndoObject, EquatableType>(arg2.GetType(), undoSystem);
}
else
{
m_ModeChangeUndoObject.SetData(arg2.GetType(), "Change Mode");
}
}
}
protected override void UndoCallback()
{
base.UndoCallback();
UndoObject.BeginUndo();
if (m_ModeChangeUndoObject != null && m_ModeChangeUndoObject.VersionChanged(true))
{
var modeToolStrip = m_ModeOverlay.GetSpriteFrameModeToolStrip(m_ModeChangeUndoObject.data);
m_ModeOverlay.OnOverlayToggleCallback(modeToolStrip);
var moduleMode = GetModeByType(modeToolStrip.GetSpriteFrameModeType());
moduleMode?.RequestModeToActivate();
}
UndoObject.EndUndo();
}
class SpriteFrameModuleContext : IShortcutContext
{
SpriteFrameModule m_SpriteFrameModule;
public SpriteFrameModuleContext(SpriteFrameModule spriteFrame)
{
m_SpriteFrameModule = spriteFrame;
}
public bool active
{
get { return true; }
}
public SpriteFrameModule spriteFrameModule
{
get { return m_SpriteFrameModule; }
}
}
[FormerlyPrefKeyAs("Sprite Editor/Trim", "#t")]
[Shortcut("Sprite Editor/Trim", typeof(SpriteFrameModuleContext), KeyCode.T, ShortcutModifiers.Shift)]
static void ShortcutTrim(ShortcutArguments args)
{
if (!string.IsNullOrEmpty(GUI.GetNameOfFocusedControl()))
return;
var spriteFrameContext = (SpriteFrameModuleContext)args.context;
spriteFrameContext.spriteFrameModule.TrimAlpha();
spriteFrameContext.spriteFrameModule.spriteEditor.RequestRepaint();
}
public override void OnModuleActivate()
{
base.OnModuleActivate();
m_ModeOverlay = spriteEditor.GetOverlay<SpriteFrameModeOverlay>(SpriteFrameModeOverlay.k_OverlayId);
if (m_ModeOverlay != null)
{
m_ModeOverlay.Activate(this);
var defaultMode = GetDefaultMode();
OnModuleModeRequestActivate(defaultMode);
m_ModeOverlay.OnOverlayToggleCallback(GetDefaultToolStrip());
}
m_SpriteRectValidated = false;
spriteEditor.enableMouseMoveEvent = true;
m_SpriteFrameModuleContext = new SpriteFrameModuleContext(this);
ShortcutIntegration.instance.contextManager.RegisterToolContext(m_SpriteFrameModuleContext);
m_SpriteNameStringBuilder = new StringBuilder(GetSpriteNamePrefix() + "_");
m_PotentialRects = null;
RegisterDataChangeCallback(OnTextureDataProviderDataChanged);
SignalModuleActivate();
}
SpriteFrameModeToolStripBase GetDefaultToolStrip()
{
return m_ModeOverlay.GetSpriteFrameModeToolStrip(typeof(SpriteFrameEditorModeToggle));
}
ISpriteEditorModuleMode GetDefaultMode()
{
var defaultModeToggleStrip = GetDefaultToolStrip();
var moduleModeType = defaultModeToggleStrip.GetSpriteFrameModeType();
return GetModeByType(moduleModeType);
}
ISpriteEditorModuleMode GetModeByType(Type t)
{
foreach (var mode in modes)
{
if (mode.GetType() == t)
{
return mode;
}
}
return null;
}
void ValidateSpriteRects()
{
if (m_TextureDataProvider != null && !m_SpriteRectValidated)
{
m_SpriteRectValidated = true;
int width, height;
m_TextureDataProvider.GetTextureActualWidthAndHeight(out width, out height);
HashSet<GUID> spriteIDs = new HashSet<GUID>();
var emptyGuid = new GUID();
List<SpriteRect> updatedSpriteRectID = new List<SpriteRect>();
for (int i = 0; i < m_RectsCache.spriteRects.Count; ++i)
{
// Validate rect is still within the bounds of the texture
var s = m_RectsCache.spriteRects[i];
if(s.rect.x < 0 || s.rect.y < 0 || s.rect.xMax > width || s.rect.yMax > height)
{
var response = onShowComplexDialog("Invalid Sprite Rect", $"Sprite Rect {s.name} is outside the bounds of the texture.", "Remove", "Keep", "Resize");
switch (response)
{
case 0:
m_RectsCache.Remove(s);
i--;
SetDataModified();
break;
case 2:
s.rect = ClampSpriteRect(s.rect, width, height);
SetDataModified();
break;
}
}
// Validate sprite id uniqueness
var spriteId = s.spriteID;
if (spriteId == emptyGuid || spriteIDs.Contains(spriteId))
{
if (onShowDialog("Invalid Sprite ID", $"Sprite Rect {s.name} has an invalid ID.\nSprite with invalid ID can result in Sprite reference breakage.", "Reassign ID", "Keep"))
{
updatedSpriteRectID.Add(s);
}
}
spriteIDs.Add(spriteId);
}
for(int i = 0; i < updatedSpriteRectID.Count; ++i)
{
m_RectsCache.Remove(updatedSpriteRectID[i]);
updatedSpriteRectID[i].spriteID = GUID.Generate();
m_RectsCache.Add(updatedSpriteRectID[i], true);
SetDataModified();
}
}
}
public override void OnModuleDeactivate()
{
base.OnModuleDeactivate();
m_CurrentModuleMode?.DeactivateMode();
m_CurrentModuleMode = null;
m_ModeOverlay?.Deactivate();
m_ModeOverlay = null;
EditorApplication.delayCall -= ValidateSpriteRects;
m_SpriteRectValidated = true;
ShortcutIntegration.instance.contextManager.DeregisterToolContext(m_SpriteFrameModuleContext);
m_PotentialRects = null;
m_AlphaPixelCache = null;
UnregisterDataChangeCallback(OnTextureDataProviderDataChanged);
CleanUpDataDataProviderOverride();
foreach (var mode in modes)
{
mode.onModeRequestActivate -= OnModuleModeRequestActivate;
}
if(m_TextureToSlice != null)
Object.DestroyImmediate(m_TextureToSlice);
modes.Clear();
UndoObject.Dispose(m_ModeChangeUndoObject);
m_ModeChangeUndoObject = null;
onModuleDeactivated();
}
void OnTextureDataProviderDataChanged(ISpriteEditorDataProvider obj)
{
if(m_TextureToSlice != null)
Object.DestroyImmediate(m_TextureToSlice);
m_TextureToSlice = null;
}
public static SpriteImportMode GetSpriteImportMode(ISpriteEditorDataProvider dataProvider)
{
return dataProvider == null ? SpriteImportMode.None : dataProvider.spriteImportMode;
}
public override bool CanBeActivated()
{
var mode = GetSpriteImportMode(spriteEditor.GetDataProvider<ISpriteEditorDataProvider>());
return mode != SpriteImportMode.Polygon && mode != SpriteImportMode.None;
}
private string GenerateSpriteNameWithIndex(int startIndex)
{
int originalLength = m_SpriteNameStringBuilder.Length;
m_SpriteNameStringBuilder.Append(startIndex);
var name = m_SpriteNameStringBuilder.ToString();
m_SpriteNameStringBuilder.Length = originalLength;
return name;
}
private bool PixelHasAlpha(int x, int y, UnityTexture2D texture)
{
if (m_AlphaPixelCache == null)
{
m_AlphaPixelCache = new bool[texture.width * texture.height];
Color32[] pixels = texture.GetPixels32();
for (int i = 0; i < pixels.Length; i++)
m_AlphaPixelCache[i] = pixels[i].a != 0;
}
int index = y * (int)texture.width + x;
return m_AlphaPixelCache[index];
}
private string GetSpriteNamePrefix()
{
return Path.GetFileNameWithoutExtension(spriteAssetPath);
}
public void DoAutomaticSlicing(int minimumSpriteSize, int alignment, Vector2 pivot, Vector2 pivotPixels, PivotUnitMode pivotUnitMode, AutoSlicingMethod slicingMethod)
{
m_RectsCache.RegisterUndo(undoSystem, "Automatic Slicing");
if (slicingMethod == AutoSlicingMethod.DeleteAll)
m_RectsCache.Clear();
var textureToUse = GetTextureToSlice();
List<Rect> frames = new List<Rect>(InternalSpriteUtility.GenerateAutomaticSpriteRectangles(textureToUse, minimumSpriteSize, 0));
if (frames.Count == 0)
frames.Add(new Rect(0, 0, textureToUse.width, textureToUse.height));
int index = 0;
int originalCount = m_RectsCache.spriteRects.Count;
foreach (Rect frame in frames)
{
var framePivot = pivot;
if (pivotUnitMode == PivotUnitMode.Pixels)
framePivot = pivotPixels / frame.size;
m_RectsCache.AddSprite(frame, alignment, framePivot, slicingMethod, originalCount, ref index, GenerateSpriteNameWithIndex);
}
if (slicingMethod == AutoSlicingMethod.DeleteAll)
m_RectsCache.ClearUnusedFileID();
selected = null;
NotifyOnSpriteRectChanged();
spriteEditor.SetDataModified();
Repaint();
}
UnityTexture2D GetTextureToSlice()
{
m_TextureDataProvider.GetTextureActualWidthAndHeight(out var width, out var height);
var readableTexture = m_TextureDataProvider.GetReadableTexture2D();
if (readableTexture == null || (readableTexture.width == width && readableTexture.height == height))
return readableTexture;
// we want to slice based on the original texture slice. Upscale the imported texture
if (m_TextureToSlice == null)
{
// we want to slice based on the original texture slice. Upscale the imported texture
m_TextureToSlice = UnityEditor.SpriteUtility.CreateTemporaryDuplicate(readableTexture, width, height);
m_TextureToSlice.hideFlags = HideFlags.HideAndDontSave;
}
return m_TextureToSlice;
}
public IEnumerable<Rect> GetGridRects(Vector2 size, Vector2 offset, Vector2 padding, bool keepEmptyRects)
{
var textureToUse = GetTextureToSlice();
return InternalSpriteUtility.GenerateGridSpriteRectangles(textureToUse, offset, size, padding, keepEmptyRects);
}
public void DoGridSlicing(Vector2 size, Vector2 offset, Vector2 padding, int alignment, Vector2 pivot, AutoSlicingMethod slicingMethod, bool keepEmptyRects = false)
{
var frames = GetGridRects(size, offset, padding, keepEmptyRects);
m_RectsCache.RegisterUndo(undoSystem, "Grid Slicing");
if (slicingMethod == AutoSlicingMethod.DeleteAll)
m_RectsCache.Clear();
int index = 0;
int originalCount = m_RectsCache.spriteRects.Count;
foreach (Rect frame in frames)
m_RectsCache.AddSprite(frame, alignment, pivot, slicingMethod, originalCount, ref index, GenerateSpriteNameWithIndex);
if (slicingMethod == AutoSlicingMethod.DeleteAll)
m_RectsCache.ClearUnusedFileID();
selected = null;
NotifyOnSpriteRectChanged();
spriteEditor.SetDataModified();
Repaint();
}
public void DoIsometricGridSlicing(Vector2 size, Vector2 offset, int alignment, Vector2 pivot, AutoSlicingMethod slicingMethod, bool keepEmptyRects = false, bool isAlternate = false)
{
var frames = IsometricSlicingUtility.GetIsometricRects(GetTextureToSlice(), size, offset, isAlternate, keepEmptyRects);
List<Vector2[]> outlines = new List<Vector2[]>(4);
outlines.Add(new[] { new Vector2(0.0f, -size.y / 2)
, new Vector2(size.x / 2, 0.0f)
, new Vector2(0.0f, size.y / 2)
, new Vector2(-size.x / 2, 0.0f)});
m_RectsCache.RegisterUndo(undoSystem, "Isometric Grid Slicing");
if (slicingMethod == AutoSlicingMethod.DeleteAll)
m_RectsCache.Clear();
int index = 0;
var spriteRects = m_RectsCache.GetSpriteRects();
int originalCount = spriteRects.Count;
foreach (var frame in frames)
{
var spriteIndex = m_RectsCache.AddSprite(frame, alignment, pivot, slicingMethod, originalCount, ref index, GenerateSpriteNameWithIndex);
var outlineRect = new OutlineSpriteRect(spriteRects[spriteIndex]);
outlineRect.outlines = outlines;
spriteRects[spriteIndex] = outlineRect;
}
if (slicingMethod == AutoSlicingMethod.DeleteAll)
m_RectsCache.ClearUnusedFileID();
selected = null;
NotifyOnSpriteRectChanged();
spriteEditor.SetDataModified();
Repaint();
}
public void ScaleSpriteRect(Rect r)
{
if (selected != null)
{
m_RectsCache.RegisterUndo(undoSystem, "Scale sprite");
selected.rect = ClampSpriteRect(r, textureActualWidth, textureActualHeight);
selected.border = ClampSpriteBorderToRect(selected.border, selected.rect);
NotifyOnSpriteRectChanged();
spriteEditor.SetDataModified();
}
}
public void TrimAlpha()
{
var texture = GetTextureToSlice();
if (texture == null)
return;
Rect rect = selected.rect;
int xMin = (int)rect.xMax;
int xMax = (int)rect.xMin;
int yMin = (int)rect.yMax;
int yMax = (int)rect.yMin;
for (int y = (int)rect.yMin; y < (int)rect.yMax; y++)
{
for (int x = (int)rect.xMin; x < (int)rect.xMax; x++)
{
if (PixelHasAlpha(x, y, texture))
{
xMin = Mathf.Min(xMin, x);
xMax = Mathf.Max(xMax, x);
yMin = Mathf.Min(yMin, y);
yMax = Mathf.Max(yMax, y);
}
}
}
// Case 582309: Return an empty rectangle if no pixel has an alpha
if (xMin > xMax || yMin > yMax)
rect = new Rect(0, 0, 0, 0);
else
rect = new Rect(xMin, yMin, xMax - xMin + 1, yMax - yMin + 1);
if (rect.width <= 0 && rect.height <= 0)
{
m_RectsCache.Remove(selected);
spriteEditor.SetDataModified();
selected = null;
}
else
{
rect = ClampSpriteRect(rect, texture.width, texture.height);
if (selected.rect != rect)
spriteEditor.SetDataModified();
selected.rect = rect;
PopulateSpriteFrameInspectorField();
}
}
public void DuplicateSprite()
{
if (selected != null)
{
m_RectsCache.RegisterUndo(undoSystem, "Duplicate sprite");
var index = 0;
var createdIndex = -1;
while (createdIndex == -1)
{
createdIndex = m_RectsCache.AddSprite(selected.rect, (int)selected.alignment, selected.pivot, GenerateSpriteNameWithIndex(index++), selected.border);
}
spriteEditor.SetDataModified();
selected = m_RectsCache.spriteRects[createdIndex];
NotifyOnSpriteRectChanged();
}
}
public void CreateSprite(Rect rect)
{
rect = ClampSpriteRect(rect, textureActualWidth, textureActualHeight);
m_RectsCache.RegisterUndo(undoSystem, "Create sprite");
var index = 0;
var createdIndex = -1;
while (createdIndex == -1)
{
createdIndex = m_RectsCache.AddSprite(rect, 0, Vector2.zero, GenerateSpriteNameWithIndex(index++), Vector4.zero);
}
spriteEditor.SetDataModified();
selected = m_RectsCache.spriteRects[createdIndex];
NotifyOnSpriteRectChanged();
}
public void DeleteSprite()
{
if (selected != null)
{
m_RectsCache.RegisterUndo(undoSystem, "Delete sprite");
m_RectsCache.Remove(selected);
selected = null;
NotifyOnSpriteRectChanged();
spriteEditor.SetDataModified();
}
}
public bool IsOnlyUsingDefaultNamedSpriteRects()
{
var onlyDefaultNames = true;
var names = m_RectsCache.spriteNames;
var defaultName = m_SpriteNameStringBuilder.ToString();
foreach (var name in names)
{
if (!name.Contains(defaultName))
{
onlyDefaultNames = false;
break;
}
}
return onlyDefaultNames;
}
public List<SpriteRect> GetSpriteRectWorkingData()
{
return m_RectsCache.GetSpriteRects();
}
}
}