using UnityEngine; using UnityEngine.UIElements; namespace AibisDream.FrameAnimation.Editor { internal sealed class FrameAnimationPreviewElement : VisualElement { private static Texture2D checkerTexture; private readonly Image image; private readonly Label emptyLabel; private Sprite sprite; private FrameAnimationPreviewZoomMode zoomMode = FrameAnimationPreviewZoomMode.Fit; private float manualZoom = 1f; private Vector2 pan; private Vector2 dragStart; private Vector2 panStart; private bool dragging; public FrameAnimationPreviewElement() { AddToClassList("fa-preview"); focusable = true; image = new Image { scaleMode = ScaleMode.ScaleToFit, pickingMode = PickingMode.Ignore }; image.AddToClassList("fa-preview__image"); emptyLabel = new Label("Empty Frame") { pickingMode = PickingMode.Ignore }; emptyLabel.AddToClassList("fa-preview__empty"); Add(image); Add(emptyLabel); RegisterCallback(_ => UpdateImageLayout()); RegisterCallback(OnPointerDown); RegisterCallback(OnPointerMove); RegisterCallback(OnPointerUp); SetBackground(FrameAnimationPreviewBackground.Checkerboard); } public void SetDisplay(Sprite value, bool isEmpty, string emptyText = "Empty Frame") { sprite = value; image.sprite = value; image.style.display = value != null ? DisplayStyle.Flex : DisplayStyle.None; emptyLabel.text = string.IsNullOrEmpty(emptyText) ? "Empty Frame" : emptyText; emptyLabel.style.display = value == null || isEmpty ? DisplayStyle.Flex : DisplayStyle.None; UpdateImageLayout(); } public void SetBackground(FrameAnimationPreviewBackground background) { switch (background) { case FrameAnimationPreviewBackground.Dark: style.backgroundImage = StyleKeyword.None; style.backgroundColor = new StyleColor(new Color(0.08f, 0.08f, 0.08f)); break; case FrameAnimationPreviewBackground.Light: style.backgroundImage = StyleKeyword.None; style.backgroundColor = new StyleColor(new Color(0.72f, 0.72f, 0.72f)); break; default: style.backgroundImage = new StyleBackground(GetCheckerTexture()); style.backgroundColor = StyleKeyword.Null; break; } } public void SetZoom(FrameAnimationPreviewZoomMode mode, float value) { zoomMode = mode; manualZoom = Mathf.Clamp(value, 0.25f, 8f); if (mode == FrameAnimationPreviewZoomMode.Fit) { pan = Vector2.zero; } UpdateImageLayout(); } private void UpdateImageLayout() { if (sprite == null || resolvedStyle.width <= 0f || resolvedStyle.height <= 0f) { return; } var sourceSize = sprite.rect.size; if (sourceSize.x <= 0f || sourceSize.y <= 0f) { return; } var scale = zoomMode == FrameAnimationPreviewZoomMode.Fit ? Mathf.Min(resolvedStyle.width / sourceSize.x, resolvedStyle.height / sourceSize.y) : GetScale(); var size = sourceSize * Mathf.Max(0.0001f, scale); image.style.width = size.x; image.style.height = size.y; image.style.left = (resolvedStyle.width - size.x) * 0.5f + pan.x; image.style.top = (resolvedStyle.height - size.y) * 0.5f + pan.y; } private float GetScale() { return zoomMode switch { FrameAnimationPreviewZoomMode.One => 1f, FrameAnimationPreviewZoomMode.Two => 2f, FrameAnimationPreviewZoomMode.Three => 3f, FrameAnimationPreviewZoomMode.Four => 4f, FrameAnimationPreviewZoomMode.Eight => 8f, FrameAnimationPreviewZoomMode.Manual => manualZoom, _ => 1f }; } private void OnPointerDown(PointerDownEvent evt) { if (zoomMode == FrameAnimationPreviewZoomMode.Fit || evt.button != 0) return; dragging = true; dragStart = new Vector2(evt.position.x, evt.position.y); panStart = pan; this.CapturePointer(evt.pointerId); evt.StopPropagation(); } private void OnPointerMove(PointerMoveEvent evt) { if (!dragging || !this.HasPointerCapture(evt.pointerId)) return; var position = new Vector2(evt.position.x, evt.position.y); pan = panStart + position - dragStart; UpdateImageLayout(); evt.StopPropagation(); } private void OnPointerUp(PointerUpEvent evt) { if (!dragging || !this.HasPointerCapture(evt.pointerId)) return; dragging = false; this.ReleasePointer(evt.pointerId); evt.StopPropagation(); } private static Texture2D GetCheckerTexture() { if (checkerTexture != null) return checkerTexture; const int textureSize = 128; const int cellSize = 8; checkerTexture = new Texture2D(textureSize, textureSize, TextureFormat.RGBA32, false) { name = "Frame Animation Preview Checker", hideFlags = HideFlags.HideAndDontSave, wrapMode = TextureWrapMode.Repeat, filterMode = FilterMode.Point }; var light = new Color32(106, 106, 106, 255); var dark = new Color32(78, 78, 78, 255); var pixels = new Color32[textureSize * textureSize]; for (var y = 0; y < textureSize; y++) for (var x = 0; x < textureSize; x++) { pixels[y * textureSize + x] = ((x / cellSize) + (y / cellSize)) % 2 == 0 ? light : dark; } checkerTexture.SetPixels32(pixels); checkerTexture.Apply(false, true); return checkerTexture; } } }