using System; using System.Collections.Generic; using System.Linq; using UnityEngine; namespace AibisDream.FrameAnimation.Editor { internal enum FrameAnimationImportIssueCode { JsonMissing, TextureMissing, JsonInvalid, FramesMissing, FrameNameInvalid, FrameNameDuplicate, FrameDurationInvalid, FrameRectInvalid, TextureSizeMismatch, TrimmedUnsupported, RotatedUnsupported, TagNameInvalid, TagNameDuplicate, TagRangeInvalid, TagDirectionInvalid, TagConflict, PlayableIdConflict, ImportSourceIdInvalid, ImportSourceIdDuplicate, ImportSourceReferenceInvalid, ImportedClipOwnershipInvalid, TextureOwnershipConflict, SpriteMatchFailed, TextureImporterInvalid, TextureWriteFailed, SourceChanged, ImageNameMismatch, NoTags, ApplyFailed } internal enum FrameAnimationImportChangeKind { Added, Updated, Missing, Unchanged, Error } internal enum FrameAnimationSpriteChangeKind { Added, Updated, Retained, Unchanged, Error } internal sealed class FrameAnimationImportIssue { public FrameAnimationValidationSeverity Severity { get; } public FrameAnimationImportIssueCode Code { get; } public string SourceId { get; } public string TargetId { get; } public string Message { get; } public FrameAnimationImportIssue( FrameAnimationValidationSeverity severity, FrameAnimationImportIssueCode code, string sourceId, string targetId, string message) { Severity = severity; Code = code; SourceId = sourceId ?? string.Empty; TargetId = targetId ?? string.Empty; Message = message ?? string.Empty; } } internal sealed class AsepriteSourceFrame { public string FrameName { get; } public RectInt Rect { get; } public int DurationMs { get; } public bool Rotated { get; } public bool Trimmed { get; } public RectInt SpriteSourceSize { get; } public Vector2Int SourceSize { get; } public AsepriteSourceFrame( string frameName, RectInt rect, int durationMs, bool rotated, bool trimmed, RectInt spriteSourceSize, Vector2Int sourceSize) { FrameName = frameName ?? string.Empty; Rect = rect; DurationMs = durationMs; Rotated = rotated; Trimmed = trimmed; SpriteSourceSize = spriteSourceSize; SourceSize = sourceSize; } } internal sealed class AsepriteSourceTag { public string Name { get; } public int From { get; } public int To { get; } public string Direction { get; } public AsepriteSourceTag(string name, int from, int to, string direction) { Name = name ?? string.Empty; From = from; To = to; Direction = direction ?? string.Empty; } } internal sealed class AsepriteSpriteSlot { public RectInt Rect { get; } public string CanonicalFrameName { get; } public IReadOnlyList SourceIndices { get; } public IReadOnlyList FrameNames { get; } public AsepriteSpriteSlot( RectInt rect, string canonicalFrameName, IReadOnlyList sourceIndices, IReadOnlyList frameNames) { Rect = rect; CanonicalFrameName = canonicalFrameName ?? string.Empty; SourceIndices = sourceIndices ?? Array.Empty(); FrameNames = frameNames ?? Array.Empty(); } } internal sealed class AsepriteSourceDocument { public IReadOnlyList Frames { get; } public IReadOnlyList SpriteSlots { get; } public IReadOnlyDictionary SpriteSlotBySourceIndex { get; } public IReadOnlyList Tags { get; } public Vector2Int TextureSize { get; } public string ImageName { get; } public AsepriteSourceDocument( IReadOnlyList frames, IReadOnlyList tags, Vector2Int textureSize, string imageName) { Frames = frames ?? Array.Empty(); BuildSpriteSlots(Frames, out var spriteSlots, out var slotBySourceIndex); SpriteSlots = spriteSlots; SpriteSlotBySourceIndex = slotBySourceIndex; Tags = tags ?? Array.Empty(); TextureSize = textureSize; ImageName = imageName ?? string.Empty; } private static void BuildSpriteSlots( IReadOnlyList frames, out IReadOnlyList spriteSlots, out IReadOnlyDictionary slotBySourceIndex) { var builders = new List(); var buildersByRect = new Dictionary(); for (var sourceIndex = 0; sourceIndex < frames.Count; sourceIndex++) { var frame = frames[sourceIndex]; if (!buildersByRect.TryGetValue(frame.Rect, out var builder)) { builder = new SpriteSlotBuilder(frame.Rect, frame.FrameName); buildersByRect.Add(frame.Rect, builder); builders.Add(builder); } builder.SourceIndices.Add(sourceIndex); builder.FrameNames.Add(frame.FrameName); } var slots = builders.Select(builder => builder.Build()).ToArray(); var index = new Dictionary(); foreach (var slot in slots) { foreach (var sourceIndex in slot.SourceIndices) { index[sourceIndex] = slot; } } spriteSlots = slots; slotBySourceIndex = index; } private sealed class SpriteSlotBuilder { public RectInt Rect { get; } public string CanonicalFrameName { get; } public List SourceIndices { get; } = new List(); public List FrameNames { get; } = new List(); public SpriteSlotBuilder(RectInt rect, string canonicalFrameName) { Rect = rect; CanonicalFrameName = canonicalFrameName ?? string.Empty; } public AsepriteSpriteSlot Build() { return new AsepriteSpriteSlot( Rect, CanonicalFrameName, SourceIndices.ToArray(), FrameNames.ToArray()); } } } internal sealed class FrameAnimationClipDiff { public FrameAnimationImportChangeKind Kind { get; } public string SourceTagName { get; } public FrameClip Clip { get; } public IReadOnlyList SourceIndices { get; } public string Summary { get; } public FrameAnimationClipDiff( FrameAnimationImportChangeKind kind, string sourceTagName, FrameClip clip, IReadOnlyList sourceIndices, string summary) { Kind = kind; SourceTagName = sourceTagName ?? string.Empty; Clip = clip; SourceIndices = sourceIndices ?? Array.Empty(); Summary = summary ?? string.Empty; } } internal sealed class FrameAnimationSpriteDiff { public FrameAnimationSpriteChangeKind Kind { get; } public string FrameName { get; } public IReadOnlyList AliasFrameNames { get; } public int SourceFrameCount => AliasFrameNames.Count; public Rect Rect { get; } public string Summary { get; } public FrameAnimationSpriteDiff( FrameAnimationSpriteChangeKind kind, string frameName, Rect rect, string summary, IReadOnlyList aliasFrameNames = null) { Kind = kind; FrameName = frameName ?? string.Empty; AliasFrameNames = aliasFrameNames ?? Array.Empty(); Rect = rect; Summary = summary ?? string.Empty; } } internal interface IFrameAnimationImportPreviewSink { void AddIssue(FrameAnimationImportIssue issue); void AddSpriteDiff(FrameAnimationSpriteDiff diff); } internal sealed class FrameAnimationImportSourcePreview : IFrameAnimationImportPreviewSink { private readonly List issues = new List(); private readonly List clipDiffs = new List(); private readonly List spriteDiffs = new List(); public FrameAnimationImportSource Source { get; } public AsepriteSourceDocument Document { get; internal set; } public FrameAnimationSpritePlan SpritePlan { get; internal set; } public string CurrentHash { get; internal set; } = string.Empty; public IReadOnlyList Issues => issues; public IReadOnlyList ClipDiffs => clipDiffs; public IReadOnlyList SpriteDiffs => spriteDiffs; public bool HasErrors => issues.Any(issue => issue.Severity == FrameAnimationValidationSeverity.Error); public bool HasSpriteChanges => spriteDiffs.Any(diff => diff.Kind == FrameAnimationSpriteChangeKind.Added || diff.Kind == FrameAnimationSpriteChangeKind.Updated); public FrameAnimationImportSourcePreview(FrameAnimationImportSource source) { Source = source; } public void AddIssue(FrameAnimationImportIssue issue) { if (issue != null) { issues.Add(issue); } } public void AddClipDiff(FrameAnimationClipDiff diff) { if (diff != null) { clipDiffs.Add(diff); } } public void AddSpriteDiff(FrameAnimationSpriteDiff diff) { if (diff != null) { spriteDiffs.Add(diff); } } } internal sealed class FrameAnimationImportPreview { private readonly List sources = new List(); public FrameAnimationGraph Graph { get; } public IReadOnlyList Sources => sources; public bool HasErrors => sources.Any(source => source.HasErrors); public bool HasSpriteChanges => sources.Any(source => source.HasSpriteChanges); public FrameAnimationImportPreview(FrameAnimationGraph graph) { Graph = graph; } public void AddSource(FrameAnimationImportSourcePreview preview) { if (preview != null) { sources.Add(preview); } } } }