// MIT License - Copyright (c) 2025 wallstop // Full license text: https://github.com/wallstop/unity-helpers/blob/main/LICENSE namespace WallstopStudios.UnityHelpers.Editor { #if UNITY_EDITOR using System; using System.Collections.Generic; using UnityEditor; using UnityEngine; /// /// Handles sprite preview rendering and texture extraction for animation events. /// internal static class AnimationEventSpritePreviewRenderer { public static void Draw( AnimationEventItem item, AnimationEventEditorViewModel viewModel, Dictionary spriteTextureCache ) { SetupPreviewData(item, viewModel, spriteTextureCache); string spriteName = item.sprite == null ? string.Empty : item.sprite.name; if (item.texture != null) { GUILayout.Label(item.texture); return; } if (!item.isTextureReadable && !string.IsNullOrEmpty(spriteName)) { DrawReadWriteFixButton(item, spriteName); return; } if (item.isInvalidTextureRect && !string.IsNullOrEmpty(spriteName)) { GUILayout.Label($"Sprite '{spriteName}' is packed too tightly inside its texture"); } } private static void SetupPreviewData( AnimationEventItem item, AnimationEventEditorViewModel viewModel, Dictionary spriteTextureCache ) { if (item.texture != null) { return; } if (!TryFindSpriteForEvent(item, viewModel.ReferenceCurve, out Sprite sprite)) { item.sprite = null; item.isTextureReadable = false; return; } item.sprite = sprite; if (spriteTextureCache.TryGetValue(sprite, out Texture2D cachedTexture)) { item.texture = cachedTexture; item.isTextureReadable = true; item.isInvalidTextureRect = false; return; } Texture2D preview = AssetPreview.GetAssetPreview(sprite); if (preview != null) { item.texture = preview; item.isTextureReadable = true; item.isInvalidTextureRect = false; spriteTextureCache[sprite] = preview; return; } item.isTextureReadable = sprite.texture.isReadable; item.isInvalidTextureRect = false; if (!item.isTextureReadable) { return; } Rect? maybeRect = null; try { maybeRect = sprite.textureRect; } catch (Exception) { item.isInvalidTextureRect = true; } if (maybeRect == null) { return; } Texture2D copied = CopyTexture(maybeRect.Value, sprite.texture); item.texture = copied; spriteTextureCache[sprite] = copied; } private static void DrawReadWriteFixButton(AnimationEventItem item, string spriteName) { using (new EditorGUILayout.HorizontalScope()) { GUILayout.Label($"Sprite '{spriteName}' required \"Read/Write\" enabled"); if (item.sprite == null || !GUILayout.Button("Fix")) { return; } string assetPath = AssetDatabase.GetAssetPath(item.sprite.texture); if (string.IsNullOrEmpty(assetPath)) { return; } if (AssetImporter.GetAtPath(assetPath) is not TextureImporter importer) { return; } Undo.RecordObject(importer, "Enable Texture Read/Write"); importer.isReadable = true; EditorUtility.SetDirty(importer); importer.SaveAndReimport(); EditorUtility.SetDirty(item.sprite); } } private static bool TryFindSpriteForEvent( AnimationEventItem item, IReadOnlyList referenceCurve, out Sprite sprite ) { sprite = null; if (referenceCurve == null || referenceCurve.Count == 0) { return false; } foreach (ObjectReferenceKeyframe keyFrame in referenceCurve) { if (keyFrame.time <= item.animationEvent.time) { if (keyFrame.value is Sprite frameSprite) { sprite = frameSprite; continue; } continue; } return sprite != null; } return sprite != null; } private static Texture2D CopyTexture(Rect textureRect, Texture2D sourceTexture) { int width = Mathf.CeilToInt(textureRect.width); int height = Mathf.CeilToInt(textureRect.height); Texture2D texture = new(width, height) { wrapMode = TextureWrapMode.Clamp, filterMode = FilterMode.Point, }; Vector2 offset = textureRect.position; int offsetX = Mathf.CeilToInt(offset.x); int offsetY = Mathf.CeilToInt(offset.y); for (int x = 0; x < width; x++) { for (int y = 0; y < height; y++) { Color pixel = sourceTexture.GetPixel(offsetX + x, offsetY + y); texture.SetPixel(x, y, pixel); } } texture.Apply(); return texture; } } #endif }